{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Creating interactive dashboards"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import holoviews as hv\n",
    "from bokeh.sampledata import stocks\n",
    "from holoviews.operation.timeseries import rolling, rolling_outlier_std\n",
    "from holoviews.streams import Stream\n",
    "hv.notebook_extension('bokeh')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the [Data Processing Pipelines section](./13-Data_Pipelines.ipynb) we discovered how to declare a ``DynamicMap`` and control multiple processing steps with the use of custom streams as described in the [Responding to Events](./11-Responding_to_Events.ipynb) guide. Here we will use the same example exploring a dataset of stock timeseries and build a small dashboard using the [``paramBokeh``](https://ioam.github.io/parambokeh/) library, which allows us to declare easily declare custom widgets and link them to our streams. We will begin by once again declaring our function that loads the stock data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Curve {+framewise}\n",
    "\n",
    "def load_symbol(symbol, variable='adj_close', **kwargs):\n",
    "    df = pd.DataFrame(getattr(stocks, symbol))\n",
    "    df['date'] = df.date.astype('datetime64[ns]')\n",
    "    return hv.Curve(df, ('date', 'Date'), variable)\n",
    "\n",
    "stock_symbols = ['AAPL', 'IBM', 'FB', 'GOOG', 'MSFT']\n",
    "dmap = hv.DynamicMap(load_symbol, kdims='Symbol').redim.values(Symbol=stock_symbols)\n",
    "dmap"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Building dashboards\n",
    "\n",
    "Controlling stream events manually from the Python prompt can be a bit cumbersome. However since you can now trigger events from Python we can easily bind any Python based widget framework to the stream. HoloViews itself is based on param and param has various UI toolkits that accompany it and allow you to quickly generate a set of widgets. Here we will use ``parambokeh``, which is based on bokeh to control our stream values.\n",
    "\n",
    "To do so we will declare a ``StockExplorer`` class which inherits from ``Stream`` and defines two parameters, the ``rolling_window`` as an integer and the ``symbol`` as an ObjectSelector. Additionally we define a view method, which defines the DynamicMap and applies the two operations we have already played with, returning an overlay of the smoothed ``Curve`` and outlier ``Scatter``.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import param\n",
    "import parambokeh\n",
    "\n",
    "class StockExplorer(Stream):\n",
    "    \n",
    "    rolling_window = param.Integer(default=10, bounds=(1, 365))\n",
    "    \n",
    "    symbol = param.ObjectSelector(default='AAPL', objects=stock_symbols)\n",
    "    \n",
    "    def view(self):\n",
    "        stocks = hv.DynamicMap(load_symbol, kdims=[], streams=[self])\n",
    "\n",
    "        # Apply rolling mean\n",
    "        smoothed = rolling(stocks, streams=[self])\n",
    "\n",
    "        # Find outliers\n",
    "        outliers = rolling_outlier_std(stocks, streams=[self])\n",
    "        return smoothed * outliers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Now that we have defined this ``Parameterized`` class we can instantiate it and pass it to the paramnb.Widgets function, which will display the widgets. Additionally we call the ``StockExplorer.view`` method to display the DynamicMap."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%opts Curve [width=600] {+framewise} Scatter (color='red' marker='triangle')\n",
    "explorer = StockExplorer()\n",
    "parambokeh.Widgets(explorer, continuous_update=True, callback=explorer.event, on_init=True)\n",
    "explorer.view()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"https://assets.holoviews.org/gifs/guides/user_guide/Dashboards/dashboard_simple.gif\" width=600>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Replacing the output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In HoloViews you have to declare the type of data that you want to display using Elements. Changing the types returned by a DynamicMap is generally not supported. Therefore ``ParamNB`` provides the ability to completely redraw the output by replacing the object that is being displayed. Here we will extend the class we created above to draw directly to a ``view`` parameter, which we can assign to. ``view`` parameters like ``HTML`` support a ``renderer`` argument, which converts whatever you assign to the object to the correct representation. Therefore we will define a quick function that returns the HTML representation of our HoloViews object and also computes the size:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def render(obj, view):\n",
    "    renderer = hv.renderer('bokeh')\n",
    "    return renderer.get_plot(obj).state"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can extend the ``StockExplorer`` class from above with a custom ``event`` method. By default ``event`` is called on ``Stream`` instances to notify any subscribers. We can intercept this call when we want to redraw, here we will instead assign to our ``output`` parameter whenever the ``variable`` changes, which will trigger a full redraw:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class AdvancedStockExplorer(StockExplorer):\n",
    "    \n",
    "    output = parambokeh.view.Plot(renderer=render)\n",
    "    \n",
    "    variable = param.ObjectSelector(default='adj_close', objects=[c for c in stocks.AAPL.keys() if c!= 'date'])\n",
    "\n",
    "    def event(self, **kwargs):\n",
    "        if self.output is None or 'variable' in kwargs:\n",
    "            self.output = self.view()\n",
    "        else:\n",
    "            super(AdvancedStockExplorer, self).event(**kwargs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "explorer = AdvancedStockExplorer()\n",
    "parambokeh.Widgets(explorer, continuous_update=True, callback=explorer.event, on_init=True, view_position='right')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"https://assets.holoviews.org/gifs/guides/user_guide/Dashboards/dashboard_advanced.gif\" width=600>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see using streams we have bound the widgets to the streams letting us easily control the stream values and making it trivial to define complex dashboards. ``paramBokeh`` is only one widget framework we could use, we could also use ``paramNB`` to use ipywidgets or simply use ipywidgets. For more information on how to deploy bokeh apps from HoloViews and build dashboards see the [Deploying Bokeh Apps](./Deploying_Bokeh_Apps.ipynb)."
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
