{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Plotting with Bokeh"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\" role=\"alert\">\n",
    "    This user guide contains a lot of bokeh plots, which may take a little while to load and render.\n",
    "</div>\n",
    "\n",
    "One of the major design principles of HoloViews is that the declaration of data is completely independent from the plotting implementation. This means that the visualization of HoloViews data structures can be performed by different plotting backends. As part of the 1.4 release of HoloViews, a [Bokeh](http://bokeh.pydata.org) backend was added in addition to the default ``matplotlib`` backend. Bokeh provides a powerful platform to generate interactive plots using HTML5 canvas and WebGL, and is ideally suited towards interactive exploration of data.\n",
    "\n",
    "By combining the ease of generating interactive, high-dimensional visualizations with the interactive widgets and fast rendering provided by Bokeh, HoloViews becomes even more powerful.\n",
    "\n",
    "This user guide will cover some basic options on how to style and change various plot attributes and explore some of the more advanced features like interactive tools, linked axes, and brushing."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As usual, the first thing we do is initialize the HoloViews notebook extension, but we now specify the backend specifically."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import holoviews as hv"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hv.extension('bokeh')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We could instead leave the default backend as ``'matplotlib'``, and then switch only some specific cells to use bokeh using a  cell magic:\n",
    "\n",
    "```python\n",
    "%%output backend='bokeh'\n",
    "obj\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Element Style options\n",
    "\n",
    "Most Bokeh Elements support a mixture of the following fill, line, and text style customization options:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from holoviews.plotting.bokeh.element import (line_properties, fill_properties,\n",
    "                                              text_properties)\n",
    "print(\"\"\"\n",
    "      Line properties: %s\\n\n",
    "      Fill properties: %s\\n\n",
    "      Text properties: %s\n",
    "      \"\"\" % (line_properties, fill_properties, text_properties))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here's an example of HoloViews Elements using a Bokeh backend, with bokeh's style options:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "curve_opts = dict(line_width=10, line_color='indianred', line_dash='dotted', line_alpha=0.5)\n",
    "point_opts = dict(fill_color='#00AA00', fill_alpha=0.5, line_width=1, line_color='black', size=5)\n",
    "text_opts  = dict(text_align='center', text_baseline='middle', text_color='gray', text_font='Arial')\n",
    "xs = np.linspace(0, np.pi*4, 100)\n",
    "data = (xs, np.sin(xs))\n",
    "\n",
    "(hv.Curve(data).options(**curve_opts) +\n",
    " hv.Points(data).options(**point_opts) +\n",
    " hv.Text(6, 0, 'Here is some text').options(**text_opts))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice that because the first two plots use the same underlying data, they become linked, such that zooming or panning one of the plots makes the corresponding change on the other."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Sizing Elements"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sizing and aspect of Elements in bokeh are always computed in absolute pixels. To change the size or aspect of an Element set the ``width`` and ``height`` plot options."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Points.A [width=300 height=300] Points.B [width=600 height=300]\n",
    "hv.Points(data, group='A') + hv.Points(data, group='B')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Controlling axes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Bokeh provides a variety of options to control the axes. Here we provide a quick overview of linked plots for the same data displayed differently by applying log axes, disabling axes, rotating ticks, specifying the number of ticks, and supplying an explicit list of ticks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "points = hv.Points(np.exp(xs)) \n",
    "axes_opts = [('Plain', {}),\n",
    "             ('Log', {'logy': True}),\n",
    "             ('None', {'yaxis': None}),\n",
    "             ('Rotate', {'xrotation': 90}),\n",
    "             ('N Ticks', {'xticks': 3}),\n",
    "             ('List Ticks', {'xticks': [0, 20, 50, 90]})]\n",
    "\n",
    "hv.Layout([points.relabel(group=group).options(**opts)\n",
    "           for group, opts in axes_opts]).cols(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Datetime axes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Both the matplotlib and the bokeh backends allow plotting datetime data, if you ensure the dates array is of a datetime dtype. Note also that the legends are interactive and can be toggled by clicking on its entries. Additionally the style of unselected curve can be controlled by setting the ``muted_alpha`` and ``muted_color`` style options."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Overlay [width=600 legend_position='top_left'] Curve (muted_alpha=0.5 muted_color='black')\n",
    "try:\n",
    "    import bokeh.sampledata.stocks\n",
    "except:\n",
    "    import bokeh.sampledata\n",
    "    bokeh.sampledata.download()\n",
    "\n",
    "from bokeh.sampledata.stocks import GOOG, AAPL\n",
    "goog_dates = np.array(GOOG['date'], dtype=np.datetime64)\n",
    "aapl_dates = np.array(AAPL['date'], dtype=np.datetime64)\n",
    "hv.Curve((goog_dates, GOOG['adj_close']), 'Date', 'Stock Index', label='Google') *\\\n",
    "hv.Curve((aapl_dates, AAPL['adj_close']), 'Date', 'Stock Index', label='Apple')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Categorical axes\n",
    "\n",
    "A number of Elements will also support categorical (i.e. string types) as dimension values, these include ``HeatMap``, ``Points``, ``Scatter``, ``Curve``, ``ErrorBar`` and ``Text`` types.\n",
    "\n",
    "Here we create a set of points indexed by ascending alphabetical x- and y-coordinates and values multiplying the integer index of each coordinate. We then overlay a ``HeatMap`` of the points with the points themselves enabling the hover tool for both and scaling the point size by the 'z' coordines."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Points [size_index='z' tools=['hover']] HeatMap [toolbar='above' tools=['hover']]\n",
    "points = hv.Points([(chr(i+65), chr(j+65), i*j) for i in range(10) for j in range(10)], vdims='z')\n",
    "hv.HeatMap(points) * points"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the example above both axes are categorical because a HeatMap by definition represents 2D categorical coordinates (unlike Image and Raster types). Other Element types will automatically infer a categorical dimension if the coordinates along that dimension include string types.\n",
    "\n",
    "Here we will generate random samples indexed by categories from 'A' to 'E' using the Scatter Element and overlay them.\n",
    "Secondly we compute the mean and standard deviation for each category and finally we overlay these two elements with a curve representing the mean value and a text element specifying the global mean. All these Elements respect the categorical index, providing us a view of the distribution of values in each category:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Overlay [show_legend=False height=400 width=600] \n",
    "%%opts ErrorBars (line_width=5) Scatter [jitter=0.2] (alpha=0.2 size=6)\n",
    "\n",
    "overlay = hv.NdOverlay({group: hv.Scatter(([group]*100, np.random.randn(100)*(i+1)+i))\n",
    "                        for i, group in enumerate(['A', 'B', 'C', 'D', 'E'])})\n",
    "\n",
    "errorbars = hv.ErrorBars([(k, el.reduce(function=np.mean), el.reduce(function=np.std))\n",
    "                          for k, el in overlay.items()])\n",
    "\n",
    "global_mean = hv.Text('A', 12, 'Global mean: %.3f' % overlay.dimension_values('y').mean(), halign='left')\n",
    "\n",
    "errorbars * overlay * hv.Curve(errorbars) * global_mean"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Grid lines"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Grid lines can be controlled throught the combination of ``show_grid`` and ``gridstyle`` parameters. The ``gridstyle`` allows specifying a number of options including:\n",
    "\n",
    "* ``grid_line_color``\n",
    "* ``grid_line_alpha``\n",
    "* ``grid_line_dash``\n",
    "* ``grid_line_width``\n",
    "* ``grid_bounds``\n",
    "* ``grid_band``\n",
    "\n",
    "These options may also be applied to minor grid lines by prepending the ``'minor_'`` prefix and may be applied to a specific axis by replacing ``'grid_`` with ``'xgrid_'`` or ``'ygrid_'``. Here we combine some of these options to generate a complex grid pattern:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid_style = {'grid_line_color': 'black', 'grid_line_width': 1.5, 'ygrid_bounds': (0.3, 0.7),\n",
    "              'minor_xgrid_line_color': 'lightgray', 'xgrid_line_dash': [4, 4]}\n",
    "\n",
    "hv.Points(np.random.rand(10, 2)).options(gridstyle=grid_style, show_grid=True, size=5, width=600)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Containers\n",
    "\n",
    "The bokeh plotting extension also supports a number of additional features relating to container components."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Tabs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using bokeh, both ``(Nd)Overlay`` and ``(Nd)Layout`` types may be displayed inside a ``tabs`` widget. This may be enabled via a plot option ``tabs``, and may even be nested inside a Layout."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Overlay [tabs=True] Curve Image [width=400 height=400]\n",
    "x,y = np.mgrid[-50:51, -50:51] * 0.1\n",
    "\n",
    "img = hv.Image(np.sin(x**2+y**2), bounds=(-1,-1,1,1))\n",
    "img.relabel('Image') * img.sample(x=0).relabel('Cross-section')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Another reason to use ``tabs`` is that some Layout combinations may not be able to be displayed directly using HoloViews. For example, it is not currently possible to display a ``GridSpace`` as part of a ``Layout`` in any backend, and this combination will automatically switch to a ``tab`` representation for the bokeh backend."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Interactive Legends\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When using ``NdOverlay`` and ``Overlay`` containers each element will get a legend entry, which can be used to interactively toggle the visibility of the element. In this example we will create a number of ``Histogram`` elements each with a different mean. By setting a ``muted_fill_alpha`` we can define the style of the element when it is de-selected using the legend, simply try tapping on each legend entry to see the effect:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Histogram [width=600] (alpha=0.8 muted_fill_alpha=0.1)\n",
    "hv.NdOverlay({i: hv.Histogram(np.histogram(np.random.randn(100)+i*2)) for i in range(5)})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The other ``muted_`` options can be used to define other aspects of the Histogram style when it is unselected."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Marginals\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Bokeh backend also supports marginal plots to generate adjoined plots. The most convenient way to build an AdjointLayout is with the ``.hist()`` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "points = hv.Points(np.random.randn(500,2))\n",
    "points.hist(num_bins=51, dimension=['x','y'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When the histogram represents a quantity that is mapped to a value dimension with a corresponding colormap, it will automatically share the colormap, making it useful as a colorbar for that dimension as well as a histogram."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "img.hist(num_bins=100, dimension=['x', 'y'], weight_dimension='z', mean_weighted=True) +\\\n",
    "img.hist(dimension='z')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## HoloMaps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "HoloMaps work in bokeh just as in other backends."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hmap = hv.HoloMap({phase: img.clone(np.sin(x**2+y**2+phase))\n",
    "                   for phase in np.linspace(0, np.pi*2, 6)}, kdims='Phase')\n",
    "hmap.hist(num_bins=100, dimension=['x', 'y'], weight_dimension='z', mean_weighted=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tools: Interactive widgets"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Hover tools"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Some Elements allow revealing additional data by hovering over the data. To enable the hover tool, simply supply ``'hover'`` as a list to the ``tools`` plot option."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Points [tools=['hover']] (size=5) HeatMap [tools=['hover']] \n",
    "%%opts Histogram [tools=['hover']] Layout [shared_axes=False]\n",
    "error = np.random.rand(100, 3)\n",
    "heatmap_data = {(chr(65+i),chr(97+j)):i*j for i in range(5) for j in range(5) if i!=j}\n",
    "data = [np.random.normal() for i in range(10000)]\n",
    "hist = np.histogram(data, 20)\n",
    "hv.Points(error) + hv.HeatMap(heatmap_data).sort() + hv.Histogram(hist)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Selection tools"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Bokeh provides a number of tools for selecting data points including ``tap``, ``box_select``, ``lasso_select`` and ``poly_select``. To distinguish between selected and unselected data points we can also control the color and alpha of the ``selection`` and ``nonselection`` points.  You can try out any of these selection tools and see how the plot is affected:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Points [tools=['box_select', 'lasso_select', 'tap']] (size=10 nonselection_color='red' color='blue')\n",
    "hv.Points(error)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Selection widget with shared axes and linked brushing"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When dealing with complex multi-variate data it is often useful to explore interactions between variables across plots. HoloViews will automatically link the data sources of plots in a Layout if they draw from the same data, allowing for both linked axes and brushing.\n",
    "\n",
    "We'll see what this looks like in practice using a small dataset of macro-economic data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "macro_df = pd.read_csv('http://assets.holoviews.org/macro.csv', '\\t')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By creating two ``Points`` Elements, which both draw their data from the same pandas DataFrame, the two plots become automatically linked.  This linking behavior can be toggled with the ``shared_datasource`` plot option on a ``Layout`` or ``GridSpace``.  You can try selecting data in one plot, and see how the corresponding data (those on the same rows of the DataFrame, even if the plots show different data, will be highlighted in each."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Scatter [tools=['box_select', 'lasso_select']] Layout [shared_axes=True shared_datasource=True]\n",
    "hv.Scatter(macro_df, 'year', 'gdp') +\\\n",
    "hv.Scatter(macro_df, 'gdp',  'unem')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A gridmatrix is a clear use case for linked plotting.  This operation plots any combination of numeric variables against each other, in a grid, and selecting datapoints in any plot will highlight them in all of them.  Such linking can thus reveal how values in a particular range (e.g. very large outliers along one dimension) relate to each of the other dimensions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Scatter [tools=['box_select', 'lasso_select', 'hover'] border=0] Histogram {+axiswise}\n",
    "table = hv.Table(macro_df, kdims=['year', 'country'])\n",
    "matrix = hv.operation.gridmatrix(table.groupby('country'))\n",
    "matrix.select(country=['West Germany', 'United Kingdom', 'United States'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The [Reference Gallery](http://holoviews.org/reference/index.html) shows examples of all the Elements supported for Bokeh, in a format that can be compared with the corresponding matplotlib versions."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Theming\n",
    "\n",
    "Bokeh supports theming via the [Theme object](https://bokeh.pydata.org/en/latest/docs/reference/themes.html) object which can also be using in HoloViews. Applying a Bokeh theme is useful when you need to set detailed aesthetic options not directly exposed via the HoloViews style options.\n",
    "\n",
    "To apply a Bokeh theme, you will need to create a ``Theme`` object:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.themes.theme import Theme\n",
    "\n",
    "theme = Theme(\n",
    "    json={\n",
    "'attrs' : {\n",
    "    'Figure' : {\n",
    "        'background_fill_color': '#2F2F2F',\n",
    "        'border_fill_color': '#2F2F2F',\n",
    "        'outline_line_color': '#444444',\n",
    "    },\n",
    "    'Grid': {\n",
    "        'grid_line_dash': [6, 4],\n",
    "        'grid_line_alpha': .3,\n",
    "    },\n",
    "    \n",
    "    'Axis': {\n",
    "        'major_label_text_color': 'white',\n",
    "        'axis_label_text_color': 'white',\n",
    "        'major_tick_line_color': 'white',\n",
    "        'minor_tick_line_color': 'white',\n",
    "        'axis_line_color': \"white\"\n",
    "    }\n",
    "  }\n",
    "})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Instead of supplying a JSON object, you can also create a Bokeh ``Theme`` object from a [YAML file](https://bokeh.pydata.org/en/latest/docs/reference/themes.html). Once the ``Theme`` object is created, you can apply it by setting it on the ``theme`` parameter of the current Bokeh renderer:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hv.renderer('bokeh').theme = theme"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The theme will then be applied to subsequent plots:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Curve [bgcolor='grey']\n",
    "xs = np.linspace(0, np.pi*4, 100)\n",
    "hv.Curve((xs, np.sin(xs)), label='foo')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To disable theming, you can set the ``theme`` parameter on the Bokeh renderer to ``None``."
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
