diff --git a/doc/user_guide/index.rst b/doc/user_guide/index.rst index 0476038aa1..c037f3abc6 100644 --- a/doc/user_guide/index.rst +++ b/doc/user_guide/index.rst @@ -75,6 +75,9 @@ These guides provide detail about specific additional features in HoloViews: * `Installing and Configuring HoloViews `_ Additional information about installation and configuration options. +* `Styling Plots `_ + How to control common styling options including color/style cycles and colormapping. + * `Plotting with Bokeh `_ Styling options and unique `Bokeh `_ features such as plot tools and using bokeh models directly. @@ -119,6 +122,7 @@ These guides provide detail about specific additional features in HoloViews: Working with large data Working with streaming data Creating interactive dashboards + Styling Plots Plotting with Bokeh Deploying Bokeh Apps Plotting with matplotlib diff --git a/examples/user_guide/Styling_Plots.ipynb b/examples/user_guide/Styling_Plots.ipynb new file mode 100644 index 0000000000..842716ee1b --- /dev/null +++ b/examples/user_guide/Styling_Plots.ipynb @@ -0,0 +1,450 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import holoviews as hv\n", + "hv.extension('bokeh', 'matplotlib')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since HoloViews supports a number of rendering backends, the styling options can differ slightly in some cases. However some fundamental concepts such as color cycles, colormapping, setting titles and controlling legends are shared across backends. In this guide we will review how to apply these common styling options to plots." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cycles and Palettes\n", + "\n", + "Frequently we want to plot multiple subsets of data, which is made easy by using ``Overlay`` and ``NdOverlay`` objects. When overlaying multiple elements of the same type they will automatically be assigned style cycles or palettes if any are defined.\n", + "\n", + "* ``Cycle``: A Cycle defines a list of discrete styles\n", + "* ``Palette``: A Palette defines a continuous color space which will be sampled discretely" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Cycle" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A ``Cycle`` can be applied to any of the style options on an element, by default most elements define a ``Cycle`` on the color property, here we will create a set of three ``Points`` object and define a custom ``Cycle``:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "points = (\n", + " hv.Points(np.random.randn(50, 2) ) *\n", + " hv.Points(np.random.randn(50, 2) + 1 ) *\n", + " hv.Points(np.random.randn(50, 2) * 0.5)\n", + ")\n", + "\n", + "cycle = hv.Cycle(['red', 'green', 'blue'])\n", + "points.options({'Points': {'color': cycle, 'size': 5}})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Defaults" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In addition to defining custom color cycles by explicitly defining a list of colors, ``Cycle`` also defines a list of default Cycles generated from bokeh Palettes and matplotlib colormaps:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hv.Cycle.default_cycles.keys()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use one of these default Cycles simply construct the Cycle with the corresponding key:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "xs = np.linspace(0, np.pi*2)\n", + "curves = hv.Overlay([hv.Curve(np.sin(xs+p)) for p in np.linspace(0, np.pi, 10)])\n", + "\n", + "default_cycle = hv.Cycle('Category20')\n", + "curves.options({'Curve': {'color': default_cycle, 'width': 600}})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Markers" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Cycles are not restricted to defining colors, they may be used to define any style option, e.g. we can use them to cycle over a number of marker styles and sizes, which will be expanded by cycling over each item independently. In this case we are cycling over three Cycles, resulting in the following style combinations:\n", + "\n", + "1. ``{'color': '#30a2da', 'marker': 'x', 'size': 10}``\n", + "2. ``{'color': '#fc4f30', 'marker': '^', 'size': 5}``\n", + "3. ``{'color': '#e5ae38', 'marker': '+', 'size': 10}``" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "color = hv.Cycle(['#30a2da', '#fc4f30', '#e5ae38'])\n", + "markers = hv.Cycle(['x', '^', '+'])\n", + "sizes = hv.Cycle([10, 5])\n", + "points.options({'Points': {'marker': markers, 'size': sizes}})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Palettes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Palettes work very similarly but instead of treating the colors as a continuous colorspace which will be sampled at regularly spaced intervals. Again they are made automatically available from existing colormaps:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hv.Palette.colormaps.keys()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As a simple example we will create a Palette from the Spectral colormap and apply it to an Overlay of 6 Ellipses. Comparing it to the spectral ``Cycle`` we can immediately see that the Palette covers the entire color spaces while the Cycle just uses the first 6 colors:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ellipses = hv.Overlay([hv.Ellipse(0, 0, s) for s in range(6)])\n", + "\n", + "spectral = hv.Palette('Spectral')\n", + "spectral_cycle = hv.Cycle('Spectral')\n", + "\n", + "ellipses.relabel('Palette').options({'Ellipse': dict(color=spectral, line_width=5)}) +\\\n", + "ellipses.relabel('Cycle').options({'Ellipse': dict(color=spectral_cycle, line_width=5)})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Colormapping" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Color cycles and styles are useful for categorical plots and when overlaying multiple subsets, but when we want to map data values to a color it is better to use HoloViews' facilities for color mapping. Certain types will apply colormapping automatically, e.g. on ``Image``, ``QuadMesh`` or ``HeatMap`` types the first value dimension is automatically mapped to the color. In other cases the values to colormap can be declared through the ``color_index``, which may reference any dimension by name or index.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Setting a colormap" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A HoloViews colormap can take a number of forms, by default HoloViews will make bokeh Palettes and matplotlib colormaps available to reference by name when the respective backend is imported. Additionally it is possible to declare a colormap as a list of hex or HTML colors." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Named colormaps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The full list of available named colormaps can be accessed using the ``hv.plotting.list_cmaps``. To provide an overview we will create a layout of all available colormaps (except the reversed versions with the ``'_r'`` suffix)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%output backend='matplotlib'\n", + "colormaps = hv.plotting.list_cmaps()\n", + "\n", + "spacing = np.linspace(0, 1, 32)[np.newaxis]\n", + "opts = dict(aspect=6, xaxis=None, yaxis=None, sublabel_format='')\n", + "hv.Layout([hv.Image(spacing, ydensity=1, label=cmap).options(cmap=cmap, **opts)\n", + " for cmap in colormaps if '_r' not in cmap]).options(vspace=0.1, hspace=0.1).cols(8)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use one of these colormaps simply refer to it by name on the ``cmap`` style option:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ls = np.linspace(0, 10, 400)\n", + "xx, yy = np.meshgrid(ls, ls)\n", + "bounds=(-1,-1,1,1) # Coordinate system: (left, bottom, top, right)\n", + "img = hv.Image(np.sin(xx)*np.cos(yy), bounds=bounds)\n", + "\n", + "img.options(cmap='PiYG', colorbar=True, width=400)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Custom colormaps" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively custom colormaps can be declared by defining a list of hex colors:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "img.options(cmap=['#0000ff', '#8888ff', '#ffffff', '#ff8888', '#ff0000'], colorbar=True, width=400)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Discrete color levels" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lastly existing colormaps can be made discrete by defining an integer number of ``color_levels``:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "img.options(cmap='PiYG', color_levels=10, colorbar=True, width=400)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Setting color ranges" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Like other dimension ranges the color range can be controlled by setting the range on the dimension. To make this easier we can use the ``.redim.range`` method to set the range on the 'z' value dimension. We will also set some values in the image array to NaN and then set the range to clip the data at 0 and 0.9. By declaring the ``clipping_colors`` option we can control what color values above and below the defined range as well as NaNs are mapped to:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "clipping = {'min': 'red', 'max': 'green', 'NaN': 'gray'}\n", + "\n", + "img_arr = np.sin(xx)*np.cos(yy)\n", + "img_arr[:190, :127] = np.NaN\n", + "nan_img = hv.Image(img_arr, bounds=bounds)\n", + "\n", + "nan_img.redim.range(z=(0, 0.9)).options(cmap='Blues', colorbar=True, clipping_colors=clipping, width=400)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Other options\n", + "\n", + "* ``logz``: Enable logarithmic color scale (e.g. ``logz=True``)\n", + "* ``symmetric``: Ensures that the color scale is centered on zero (e.g. ``symmetric=True``)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Using color_index" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As mentioned above when plotting elements which do not automatically map colors to certain dimensions we can use the ``color_index`` option to do so explicitly. This allows colormapping both continuously valued and categorical values." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Continuous values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we map the ``color_index`` to a continuous value we can enable a ``colorbar``:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "polygons = hv.Polygons([{('x', 'y'): hv.Ellipse(0, 0, (i, i)).array(), 'z': i} for i in range(1, 10)[::-1]], vdims='z')\n", + "\n", + "polygons.options(color_index='z', colorbar=True, width=380)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Categorical values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively when mapping to a categorical value we automatically get a legend (which can be disabled using the ``show_legend`` option):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "categorical_points = hv.Points((np.random.rand(100), np.random.rand(100), np.random.choice(list('ABCD'), 100)), vdims='Category')\n", + "\n", + "categorical_points.sort('Category').options(color_index='Category', cmap='Category20', size=5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Explicit color mapping\n", + "\n", + "Instead of using a listed colormap an explicit mapping from category to color is also valid, here we will map the categories 'A', 'B', 'C' and 'D' to specific colors:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "explicit_mapping = {'A': 'blue', 'B': 'red', 'C': 'green', 'D': 'purple'}\n", + "\n", + "categorical_points.sort('Category').options(color_index='Category', cmap=explicit_mapping, size=5)" + ] + } + ], + "metadata": { + "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.6.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}