{ "metadata": { "name": "03_Widgets" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Adding Interactivity: Widgets" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Widgets are objects built-in to Matplotlib which build upon the event callbacks\n", "we previously discussed, encapsulating more complicated behavior. There are many\n", "possibilities available, and we'll go through a few of them here." ] }, { "cell_type": "code", "collapsed": false, "input": [ "%pylab" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Buttons" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A button in matplotlib is exactly what you think it is: a clickable region, in which\n", "clicking returns a callback that can be linked to any action.\n", "\n", "A simple button can be created like this:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from matplotlib.widgets import Button\n", "\n", "fig = plt.figure()\n", "\n", "def callback(event):\n", " print \"clicked:\", event\n", " sys.stdout.flush()\n", "\n", "ax1 = plt.axes([0.2, 0.5, 0.1, 0.075])\n", "ax2 = plt.axes([0.7, 0.5, 0.1, 0.075])\n", "\n", "b1 = Button(ax1, 'Button 1')\n", "b1.on_clicked(callback)\n", "\n", "b2 = Button(ax2, 'Button 2')\n", "b2.on_clicked(callback)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "from matplotlib.widgets import Button\n", "\n", "fig, ax = plt.subplots()\n", "fig.subplots_adjust(bottom=0.2)\n", "\n", "t = np.linspace(0, 10, 1000)\n", "line, = plt.plot(t, np.sin(t), lw=2)\n", "\n", "class Index:\n", " dt = 0\n", " def next(self, event):\n", " self.dt -= 1\n", " line.set_ydata(np.sin(t + self.dt))\n", " fig.canvas.draw()\n", "\n", " def prev(self, event):\n", " self.dt += 1\n", " line.set_ydata(np.sin(t + self.dt))\n", " fig.canvas.draw()\n", "\n", "callback = Index()\n", "axprev = plt.axes([0.7, 0.05, 0.1, 0.075])\n", "axnext = plt.axes([0.81, 0.05, 0.1, 0.075])\n", "\n", "bnext = Button(axnext, '>')\n", "bnext.on_clicked(callback.next)\n", "\n", "bprev = Button(axprev, '<')\n", "bprev.on_clicked(callback.prev)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Sliders" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A Slider is another type of widget which can be used to select a\n", "continuous value. Let's see an example similar to the previous\n", "one:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from matplotlib.widgets import Slider\n", "\n", "fig, ax = plt.subplots()\n", "fig.subplots_adjust(bottom=0.2, left=0.1)\n", "\n", "t = np.linspace(0, 10, 1000)\n", "line, = plt.plot(t, np.sin(t), lw=2)\n", "\n", "def on_change(val):\n", " line.set_ydata(np.sin(t - val))\n", "\n", "slider_ax = plt.axes([0.1, 0.1, 0.8, 0.02])\n", "slider = Slider(slider_ax, \"Offset\", -5, 5, valinit=0, color='#AAAAAA')\n", "slider.on_changed(on_change)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Selectors" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Selectors can be used to select regions within the plot. Here is an example\n", "where points can be selected and changed to a different color:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from matplotlib.widgets import RectangleSelector\n", "\n", "fig, ax = plt.subplots()\n", "x = np.random.normal(size=1000)\n", "y = np.random.normal(size=1000)\n", "c = np.zeros((1000, 3))\n", "c[:, 2] = 1 # set to blue\n", "points = ax.scatter(x, y, s=20, c=c)\n", "\n", "def selector_callback(eclick, erelease):\n", " x1, y1 = eclick.xdata, eclick.ydata\n", " x2, y2 = erelease.xdata, erelease.ydata\n", " global c\n", " c[(x >= min(x1, x2)) & (x <= max(x1, x2))\n", " & (y >= min(y1, y2)) & (y <= max(y1, y2))] = [1, 0, 0]\n", " points.set_facecolors(c)\n", " fig.canvas.draw()\n", " \n", " \n", "selector = RectangleSelector(ax, selector_callback,\n", " drawtype='box', useblit=True,\n", " button=[1,3], # don't use middle button\n", " minspanx=5, minspany=5,\n", " spancoords='pixels')" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You might imagine using this to dynamically interact with your data. For example,\n", "you could select a region of your plot, and compute the mean or standard deviation\n", "of a third parameter. I've found this useful in interactively exploring multi-dimensional\n", "astronomical data.\n", "\n", "A similar process can be performed with ``widgets.Lasso``." ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Check Buttons" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Check buttons are a set of buttons within which multiple options can\n", "be selected at once. The callback can then be used to update the\n", "plot. Here we'll use the check buttons to display a combination of\n", "three curves:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from matplotlib.widgets import CheckButtons\n", "\n", "fig, ax = plt.subplots()\n", "fig.subplots_adjust(left=0.2)\n", "\n", "# plot some hidden curves\n", "freqs = np.arange(1, 4)\n", "t = np.linspace(0, 2, 1000)\n", "s = np.sin(2 * np.pi * freqs[:, None] * t)\n", "lines = plt.plot(t, s.T, lw=2, visible=False)\n", "ax.set_ylim(-1.5, 1.5)\n", "\n", "# Build check button axes\n", "rax = plt.axes([0.02, 0.4, 0.13, 0.2], aspect='equal')\n", "labels = ('2 Hz', '4 Hz', '6 Hz')\n", "check = CheckButtons(rax, labels, (False, False, False))\n", "\n", "def func(label):\n", " i = labels.index(label)\n", " lines[i].set_visible(not lines[i].get_visible())\n", " fig.canvas.draw()\n", "\n", "check.on_clicked(func)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Radio Buttons" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Radio Buttons, as opposed to check buttons, give a set of buttons\n", "of which only one can be selected at a time. Here we'll use radio\n", "buttons do select a line style for a curve:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from matplotlib.widgets import RadioButtons\n", "\n", "fig, ax = plt.subplots()\n", "fig.subplots_adjust(left=0.3)\n", "\n", "t = np.linspace(0, 10, 1000)\n", "lines = ax.plot(t, np.sin(t))\n", "\n", "rax = plt.axes([0.05, 0.4, 0.15, 0.15], axisbg=axcolor)\n", "radio = RadioButtons(rax, ('-', '--', '-.', 'steps', ':'))\n", "\n", "def stylefunc(label):\n", " lines[0].set_linestyle(label)\n", " plt.draw()\n", " \n", "radio.on_clicked(stylefunc)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Cursor Tracking" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sometimes it's useful to track the cursor so as to better see where in the\n", "plot you're pointing. This can be a simple 1-axes cursor or a multi-axes\n", "cursor.\n", "\n", "First we'll look at a simple single cursor:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from matplotlib.widgets import Cursor\n", "\n", "fig, ax = plt.subplots()\n", "\n", "ax.scatter(np.random.normal(size=1000), np.random.normal(size=1000))\n", "\n", "# useblit = True can lead to better performance on some backends\n", "cursor = Cursor(ax, useblit=True, color='gray', linewidth=1)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To track movements across multiple axes, we can use a multi-cursor:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from matplotlib.widgets import MultiCursor\n", "\n", "fig, ax = plt.subplots(2)\n", "\n", "x, y, z = np.random.normal(0, 1, (3, 1000))\n", "\n", "ax[0].scatter(x, y)\n", "ax[1].scatter(x, z)\n", "\n", "multi = MultiCursor(fig.canvas, ax, useblit=True, color='gray', lw=1)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All of these widgets can be used together to quickly build some very interesting\n", "interactive data analysis tools. Because they are so quick and easy to set up,\n", "they can be deployed on-the-fly to help visualize specific data sets in ways that\n", "are the most convenient." ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Exercise: Zooming to the Cube" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's return to the cube we were using before. Here we'll create a slider\n", "which controls the zoom on the cube." ] }, { "cell_type": "code", "collapsed": false, "input": [ "from tutorial_lib.simple_cube import Cube\n", "c = Cube()\n", "\n", "# Create the figure and axes, and add the cube to the axes\n", "\n", "# create a slider titled \"perspective\" with values from 1 to 20\n", "\n", "# set the slider so that the following function is called when it is changed:\n", "def zoom(val):\n", " c.set_view((0, 0, val))\n", " ax.set_xlim(-val, val)\n", " ax.set_ylim(-val, val)\n", " fig.canvas.draw()" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Bonus:** combine the slider with the rotation scripts we used previously,\n", "so that you can zoom into the cube with the slider, and rotate the cube with\n", "the mouse." ] }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [] } ], "metadata": {} } ] }