bqplot

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
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)

Your First Plot

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.

Using bqplot's interactive elements

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.

Understanding how bqplot uses the Grammar of Graphics paradigm

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]