This notebook is meant to guide you through the first stages of using the bqplot
visualization library. bqplot
is a Grammar of Graphics based interactive visualization library for the Jupyter notebook where every single component of a plot is an interactive iPython widget. What this means is that even after a plot is drawn, you can change almost any aspect of it. This makes the creation of advanced Graphical User Interfaces attainable through just a few simple lines of Python code.
In [ ]:
# Let's begin by importing some libraries we'll need
import numpy as np
from __future__ import print_function # So that this notebook becomes both Python 2 and Python 3 compatible
In [ ]:
# And creating some random data
size = 100
np.random.seed(0)
x_data = np.arange(size)
y_data = np.cumsum(np.random.randn(size) * 100.0)
Let's start by creating a simple Line chart. bqplot
has two different APIs, the first one is a matplotlib
inspired simple API called pyplot
. So let's import that.
In [ ]:
from bqplot import pyplot as plt
Let's plot y_data
against x_data
, and then show
the plot.
In [ ]:
plt.figure(title='My First Plot')
plt.plot(x_data, y_data)
plt.show()
Use the buttons above to Pan (or Zoom), Reset or save the Figure.
Now, let's try creating a new plot. First, we create a brand new Figure
. The Figure
is the final element of any plot that is eventually displayed. You can think of it as a Canvas on which we put all of our other plots.
In [ ]:
# Creating a new Figure and setting it's title
plt.figure(title='My Second Chart')
In [ ]:
# Let's assign the scatter plot to a variable
scatter_plot = plt.scatter(x_data, y_data)
In [ ]:
# Let's show the plot
plt.show()
Since both the x and the y attributes of a bqplot chart are interactive widgets, we can change them. So, let's change the y attribute of the chart.
In [ ]:
scatter_plot.y = np.cumsum(np.random.randn(size) * 100.0)
Re-run the above cell a few times, the same plot should update everytime. But, thats not the only thing that can be changed once a plot has been rendered. Let's try changing some of the other attributes.
In [ ]:
# Say, the color
scatter_plot.colors = ['Red']
In [ ]:
# Or, the marker style
scatter_plot.marker = 'diamond'
It's important to remember that an interactive widget means that the JavaScript
and the Python
communicate. So, the plot can be changed through a single line of python code, or a piece of python code can be triggered by a change in the plot. Let's go through a simple example. Say we have a function foo
:
In [ ]:
def foo(change):
print('This is a trait change. Foo was called by the fact that we moved the Scatter')
print('In fact, the Scatter plot sent us all the new data: ')
print('To access the data, try modifying the function and printing the data variable')
We can call foo
everytime any attribute of our scatter is changed. Say, the y
values:
In [ ]:
# First, we hook up our function `foo` to the colors attribute (or Trait) of the scatter plot
scatter_plot.observe(foo, 'y')
To allow the points in the Scatter
to be moved interactively, we set the enable_move
attribute to True
In [ ]:
scatter_plot.enable_move = True
Go ahead, head over to the chart and move any point in some way. This move (which happens on the JavaScript
side should trigger our Python
function foo
.
bqplot
has two different APIs. One is the matplotlib inspired pyplot
which we used above (you can think of it as similar to qplot
in ggplot2
). The other one, the verbose API, is meant to expose every element of a plot individually, so that their attriutes can be controlled in an atomic way. In order to truly use bqplot
to build complex and feature-rich GUIs, it pays to understand the underlying theory that is used to create a plot.
To understand this verbose API, it helps to revisit what exactly the components of a plot are. The first thing we need is a Scale
.
A Scale
is a mapping from (function that converts) data coordinates to figure coordinates. What this means is that, a Scale
takes a set of values in any arbitrary unit (say number of people, or $, or litres) and converts it to pixels (or colors for a ColorScale
).
In [ ]:
# First, we import the scales
from bqplot import LinearScale
In [ ]:
# Let's create a scale for the x attribute, and a scale for the y attribute
x_sc = LinearScale()
y_sc = LinearScale()
Now, we need to create the actual Mark
that will visually represent the data. Let's pick a Scatter
chart to start.
In [ ]:
from bqplot import Scatter
In [ ]:
scatter_chart = Scatter(x=x_data, y=y_data, scales={'x': x_sc, 'y': y_sc})
Most of the time, the actual Figure
co-ordinates don't really mean anything to us. So, what we need is the visual representation of our Scale
, which is called an Axis
.
In [ ]:
from bqplot import Axis
In [ ]:
x_ax = Axis(label='X', scale=x_sc)
y_ax = Axis(label='Y', scale=y_sc, orientation='vertical')
And finally, we put it all together on a canvas, which is called a Figure
.
In [ ]:
from bqplot import Figure
In [ ]:
fig = Figure(marks=[scatter_chart], title='A Figure', axes=[x_ax, y_ax])
fig
The IPython display machinery displays the last returned value of a cell. If you wish to explicitly display a widget, you can call IPython.display.display
.
In [ ]:
from IPython.display import display
In [ ]:
display(fig)
Now, that the plot has been generated, we can control every single attribute of it. Let's say we wanted to color the chart based on some other data.
In [ ]:
# First, we generate some random color data.
color_data = np.random.randint(0, 2, size=100)
Now, we define a ColorScale to map the color_data to actual colors
In [ ]:
from bqplot import ColorScale
In [ ]:
# The colors trait controls the actual colors we want to map to. It can also take a min, mid, max list of
# colors to be interpolated between for continuous data.
col_sc = ColorScale(colors=['MediumSeaGreen', 'Red'])
In [ ]:
scatter_chart.scales = {'x': x_sc, 'y': y_sc, 'color': col_sc}
# We pass the color data to the Scatter Chart through it's color attribute
scatter_chart.color = color_data
The grammar of graphics framework allows us to overlay multiple visualizations on a single Figure
by having the visualization share the Scales
. So, for example, if we had a Bar
chart that we would like to plot alongside the Scatter
plot, we just pass it the same Scales
.
In [ ]:
from bqplot import Bars
In [ ]:
new_size = 50
scale = 100.
x_data_new = np.arange(new_size)
y_data_new = np.cumsum(np.random.randn(new_size) * scale)
In [ ]:
# All we need to do to add a bar chart to the Figure is pass the same scales to the Mark
bar_chart = Bars(x=x_data_new, y=y_data_new, scales={'x': x_sc, 'y': y_sc})
Finally, we add the new Mark
to the Figure
to update the plot!
In [ ]:
fig.marks = [scatter_chart, bar_chart]
In [ ]: