{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# bqplot" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's begin by importing some libraries we'll need\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# And creating some random data\n", "size = 100\n", "np.random.seed(0)\n", "x_data = np.arange(size)\n", "y_data = np.cumsum(np.random.randn(size) * 100.0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Your First Plot" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bqplot import pyplot as plt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's plot `y_data` against `x_data`, and then `show` the plot." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.figure(title=\"My First Plot\")\n", "plt.plot(x_data, y_data)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use the buttons above to Pan (or Zoom), Reset or save the Figure." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using `bqplot`'s interactive elements" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Creating a new Figure and setting it's title\n", "plt.figure(title=\"My Second Chart\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's assign the scatter plot to a variable\n", "scatter_plot = plt.scatter(x_data, y_data)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": false }, "outputs": [], "source": [ "# Let's show the plot\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since both the x and the y attributes of a bqplot chart are interactive widgets, we can change them. So, let's\n", "change the y attribute of the chart." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "scatter_plot.y = np.cumsum(np.random.randn(size) * 100.0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Re-run the above cell a few times, the same plot should update every time. But, that's not the only thing that can be changed once a plot has been rendered. Let's try changing some of the other attributes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Say, the color\n", "scatter_plot.colors = [\"Red\"]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Or, the marker style\n", "scatter_plot.marker = \"diamond\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def foo(change):\n", " print(\n", " \"This is a trait change. Foo was called by the fact that we moved the Scatter\"\n", " )\n", " print(\"In fact, the Scatter plot sent us all the new data: \")\n", " print(\n", " \"To access the data, try modifying the function and printing the data variable\"\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can call `foo` every time any attribute of our scatter is changed. Say, the `y` values:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# First, we hook up our function `foo` to the colors attribute (or Trait) of the scatter plot\n", "scatter_plot.observe(foo, \"y\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To allow the points in the `Scatter` to be moved interactively, we set the `enable_move` attribute to `True`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "scatter_plot.enable_move = True" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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`." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## Understanding how bqplot uses the Grammar of Graphics paradigm" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**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`)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# First, we import the scales\n", "from bqplot import LinearScale" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's create a scale for the x attribute, and a scale for the y attribute\n", "x_sc = LinearScale()\n", "y_sc = LinearScale()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we need to create the actual `Mark` that will visually represent the data. Let's pick a `Scatter` chart to start." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bqplot import Scatter" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "scatter_chart = Scatter(x=x_data, y=y_data, scales={\"x\": x_sc, \"y\": y_sc})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Most of the time, the actual `Figure` coordinates don't really mean anything to us. So, what we need is the visual representation of our `Scale`, which is called an `Axis`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bqplot import Axis" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x_ax = Axis(label=\"X\", scale=x_sc)\n", "y_ax = Axis(label=\"Y\", scale=y_sc, orientation=\"vertical\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And finally, we put it all together on a canvas, which is called a `Figure`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bqplot import Figure" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig = Figure(marks=[scatter_chart], title=\"A Figure\", axes=[x_ax, y_ax])\n", "fig" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.display import display" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "display(fig)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# First, we generate some random color data.\n", "color_data = np.random.randint(0, 2, size=100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we define a ColorScale to map the color_data to actual colors" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bqplot import ColorScale" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# The colors trait controls the actual colors we want to map to. It can also take a min, mid, max list of\n", "# colors to be interpolated between for continuous data.\n", "col_sc = ColorScale(colors=[\"MediumSeaGreen\", \"Red\"])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "scatter_chart.scales = {\"x\": x_sc, \"y\": y_sc, \"color\": col_sc}\n", "# We pass the color data to the Scatter Chart through it's color attribute\n", "scatter_chart.color = color_data" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "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`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bqplot import Bars" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "new_size = 50\n", "scale = 100.0\n", "x_data_new = np.arange(new_size)\n", "y_data_new = np.cumsum(np.random.randn(new_size) * scale)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# All we need to do to add a bar chart to the Figure is pass the same scales to the Mark\n", "bar_chart = Bars(x=x_data_new, y=y_data_new, scales={\"x\": x_sc, \"y\": y_sc})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we add the new `Mark` to the `Figure` to update the plot!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig.marks = [scatter_chart, bar_chart]" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.6" } }, "nbformat": 4, "nbformat_minor": 1 }