From 2bffb504fe22bfe379cda594db60ce727eb0d67e Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 11 Feb 2017 11:59:17 +0000 Subject: [PATCH] Added tutorial about working with renderers and plots --- doc/Tutorials/Plots_and_Renderers.ipynb | 624 ++++++++++++++++++++++++ 1 file changed, 624 insertions(+) create mode 100644 doc/Tutorials/Plots_and_Renderers.ipynb diff --git a/doc/Tutorials/Plots_and_Renderers.ipynb b/doc/Tutorials/Plots_and_Renderers.ipynb new file mode 100644 index 0000000000..c3adfbae37 --- /dev/null +++ b/doc/Tutorials/Plots_and_Renderers.ipynb @@ -0,0 +1,624 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import holoviews as hv\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "HoloViews ordinarily hides the plotting machinery from the user, this allows for very quickly iterating over different visualization and exploring a dataset, however often times it is important to customize the precise details of a plot. HoloViews makes it very easy to customize existing plots, or even create completely novel plots. This manual will provide a general overview of the plotting system.\n", + "\n", + "The separation of the data from the precise details of the visualization is one of the core principles of the HoloViews. [``Elements``](Elements.ipynb) provide thin wrappers around chunks of actual data, while [containers](Containers.ipynb#Spaces) allow composing these Elements into overlays, layouts, grids and animations/widgets. Each Element or container type has a corresponding plotting class, which renders a visual representation of the data for a particular backend. While the precise details of the implementation differ between backends to accomodate the vastly different APIs plotting backends provide, many of the high-level details are shared across backends.\n", + "\n", + "# The Store object\n", + "\n", + "The association between an Element or container and the backend specific plotting class is held on the global ``Store`` object. The ``Store`` object holds a ``registry`` of plot objects for each backend. We can view the registry for each backend by accessing ``Store.registry`` directly:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import holoviews.plotting.mpl\n", + "sorted(hv.Store.registry['matplotlib'].items())[0:5]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Store object provides a global registry not only for the plots themselves but also creates an entry in the OptionsTree for that particular backend. This allows options for that backend to be validated and enables setting plot, style and normalization options via the [Options](Options.ipynb) system. We can view the ``OptionsTree`` object by requestion it from the store. We'll make a copy with just the first few entries so we can view the structure of the tree:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "opts = hv.Store.options(backend='matplotlib')\n", + "hv.core.options.OptionTree(opts.items()[0:10], groups=['plot', 'style', 'norm'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Renderers\n", + "\n", + "HoloViews provides a general ``Renderer`` baseclass, which defines a general interface to render the output from different backends to a number of standard output formats such as ``png``, ``html`` or ``svg``. The ``__call__`` method on the Renderer automatically looks up and instantiates the registered plotting classes for object it is passed and then returns the output in the requested format. To make this a bit clearer we'll break this down step by step, first we'll get a handle on the ``MPLRenderer`` and create an object to render.\n", + "\n", + "Renderers aren't registered with the Store until the corresponding backends have been imported. Loading the notebook extension with ``hv.notebook_extension('matplotlib')`` is one way of loading a backend and registering a renderer. Another is to simply import the corresponding plotting module as we did above." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "hv.Store.renderers" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is only one way to access a Renderer, another is to instantiate a Renderer instance directly, allowing you to override some of the default plot options." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from holoviews.plotting.mpl import MPLRenderer\n", + "renderer = MPLRenderer.instance(dpi=120)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Working with a Renderer\n", + "\n", + "A ``Renderer`` in HoloViews is responsible for instantiating a HoloViews plotting class. It does this by looking up the plotting class in the ``Store.registry``:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "hv.Store.registry['matplotlib'][hv.Curve]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we create a ``Curve`` we can instantiate a plotting class from it using the ``Renderer.get_plot`` method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "curve = hv.Curve(range(10))\n", + "curve_plot = renderer.get_plot(curve)\n", + "curve_plot" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will revisit how to work with ``Plot`` instances later for now all we need to know is that they are responsible for translating the HoloViews object (like the ``Curve``) into a backend specific plotting object, accessible on ``Plot.state``:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print(curve_plot.state)\n", + "curve_plot.state" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In case of the matplotlib backend this is a ``Figure`` object. However the ``Renderer`` ignores the specific representation of the plot providing a unified interface to translating it into a representation that can displayed, i.e. either an image format or an HTML representation.\n", + "\n", + "In this way we can convert the curve directly to its ``png`` representation by calling the ``Renderer`` with the object and the format:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from IPython.display import display_png\n", + "png, info = renderer(curve, fmt='png')\n", + "print(info)\n", + "display_png(png, raw=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "Tip: To find more information about any HoloViews object use ``hv.help`` to print a detailed docstring.\n", + "
\n", + "\n", + "The valid figure display formats can be seen in the docstring of the Renderer or directly on the parameter:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "renderer.params('fig').objects" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this way we can easily render the plot in different formats:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from IPython.display import display_svg\n", + "svg, info = renderer(curve, fmt='svg')\n", + "print(info)\n", + "display_svg(svg, raw=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We could save these byte string representations ourselves but the ``Renderer`` provides a convenient ``save`` method to do so. Simply supply the object the filename and the format, which doubles as the file extension:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "renderer.save(curve, '/tmp/test', fmt='png')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Another convenient way to render the object is wrap it in HTML, which we can do with the ``html`` method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from IPython.display import display_html\n", + "html = renderer.html(curve)\n", + "display_html(html, raw=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Rendering plots containing ``HoloMap`` and ``DynamicMap`` objects will automatically generate a widget class instance, which you can get a handle on with the ``get_widget`` method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "holomap = hv.HoloMap({i: hv.Image(np.random.rand(10, 10)) for i in range(3)})\n", + "widget = renderer.get_widget(holomap, 'widgets')\n", + "widget" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However most of the time it is more convenient to let the Renderer to export the widget HTML, again via a convenient method, which will export a HTML document with all the required JS and CSS dependencies:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "display_html(renderer.static_html(holomap), raw=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This covers the basics of working with HoloViews renderers, and this API is consistent across plotting backends, whether that is matplotlib, bokeh or plotly.\n", + "\n", + "# Plots\n", + "\n", + "Above we saw how the Renderer looks up the appropriate plotting class but so far we haven't seen how the plotting actually works. Since HoloViews already nests the data into semantically meaningful components, which define the rough layout of the plots on the page, the plotting classes follow roughly the same hierarchy. To review this hierarchy have a look at the [nesting diagram](Composing_Data.ipynb#Nesting-hierarchy-) in the Composing Data Tutorial.\n", + "\n", + "The Layout and GridSpace plotting classes set up the figure and axes appropriately and then instantiate the subplots for all the objects that are contained within. For this purpose we will create a relative complex object, a ``Layout`` of ``HoloMap``s containing ``Overlay``s containing ``Elements``. We'll instantiate the matching plotting hierarchy and then inspect it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "hmap1 = hv.HoloMap({i: hv.Image(np.random.rand(10, 10)) * hv.Ellipse(0, 0, 0.2*i) for i in range(5)})\n", + "element = hv.Curve((range(10), np.random.rand(10)))\n", + "layout = hmap1 + element" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see the hierarchy in the object's repr:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print repr(layout)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we've created the object we can again use the ``MPLRenderer`` to instantiate the plot:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "layout_plot = renderer.get_plot(layout)\n", + "layout_plot" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "During instantiation the LayoutPlot expanded each object and created subplots. We can access them via a row, column based index and thereby view the first plot." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "adjoint_plot = layout_plot.subplots[0, 0]\n", + "adjoint_plot" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This plotting layer handles plots adjoined to the plot, and are indexed by their position in the AdjointLayout which may include 'top', 'right' and 'main':" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "overlay_plot = adjoint_plot.subplots['main']\n", + "overlay_plot" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we've drilled all the way down to the OverlayPlot level, as expected this contains two further subplots, one for the ``Image`` and one for ``Text`` Element. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "overlay_plot.subplots.keys()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now you might have noticed that the HoloMap seems to have disappeared from the hierarchy. This is because updating a particular plot is handled by the ``ElementPlots`` itself. With that knowledge we can now have a look at the actual plotting API.\n", + "\n", + "### Traversing plots" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When working with such deeply nested plots accessing the leafs can be a lot of effort, therefore the plots also provide a ``traverse`` method letting you specify the types of plots you want to access:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "layout_plot.traverse(specs=[hv.plotting.mpl.CurvePlot])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plotting API\n", + "\n", + "There a few methods shared by all plotting classes, which allow the renderer to easily create, update and render a plot. The three most important methods and attributes are:\n", + "\n", + "* ``Plot.__init__`` - The constructor already handles a lot of the processing in a plot, it sets up all the subplots if there are any, computes ranges across the object to normalize the display, sets the options that were specified and opens instantiates the figure, axes, model graphs, or canvas objects dependening on the backend.\n", + "* ``Plot.initialize_plot`` - This draws the initial frame to the appopriate figure, axis or canvas, setting up the various artists (matplotlib) or glyphs (bokeh).\n", + "* ``Plot.update`` - This method updates an already instantiated plot with the data corresponding to the supplied key. This key should match the key in the HoloMap.\n", + "\n", + "### Initializing\n", + "\n", + "The Renderer and the widgets use these three methods to instantiate a plot and update to render both static frames and animations or widgets as defined by the ``HoloMap`` or ``DynamicMap``. Above we already instantiated a plot, now we initialize it drawing the first (or rather last frame)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig = layout_plot.initialize_plot()\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Updating\n", + "\n", + "We can see it has rendered the last frame with the key ``4``, we can update the figure with another key simply by calling the ``Plot.update`` method with the corresponding key." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "layout_plot.update(0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plot = hv.plotting.mpl.RasterPlot(holomap)\n", + "plot" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Internally each level of the plotting hierarchy calls updates all the objects below it all the way down to the ElementPlots, which handle updating the plotting data." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Dynamic plot updates" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since DynamicMaps may be updated based on stream events they don't work via quite the same API. Each stream automatically captures the plots it is attached to and whenever it receives an event it will update the plot." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "dmap = hv.DynamicMap(lambda x: hv.Points(np.arange(x)), kdims=[], streams=[hv.streams.PositionX(x=10)])\n", + "plot = renderer.get_plot(dmap)\n", + "plot.initialize_plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Internally the update to the stream value will automatically trigger the plot to update, here we will disable this by setting ``trigger=False`` and explicitly calling ``refresh`` on the plot:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "dmap.event(x=20, trigger=False)\n", + "plot.refresh()\n", + "plot.state" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plotting handles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In addition to accessing the overall state of the plot each plotting class usually keeps direct handles for important plotting elements on the ``handles`` attribute:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plot.handles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we can see how the ``PointPlot`` keeps track of the artist, which is a matplotlib PathCollection``, the axis, the figure and the plot title. In addition all matplotlib plots also keep track of a list of any additional plotting elements which should be considered in the bounding box calculation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is only the top-level API, which is used by the Renderer to render the plot, animation or widget. Each backend has internal APIs to create and update the various plot components. We'll look at those in more detail in the [Matplotlib Plotting](Matplotlib_Plotting.ipynb) and [Bokeh Plotting](Bokeh_Plotting.ipynb) developer guides." + ] + } + ], + "metadata": { + "anaconda-cloud": {}, + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.11" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +}