{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "pn.extension('vega')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``Vega`` pane renders Vega-based plots (including those from Altair) inside a panel. It optimizes the plot rendering by using binary serialization for any array data found on the Vega/Altair object, providing huge speedups over the standard JSON serialization employed by Vega natively. Note that to use the ``Vega`` pane in the notebook the Panel extension has to be loaded with 'vega' as an argument to ensure that vega.js is initialized.\n",
    "\n",
    "#### Parameters:\n",
    "\n",
    "For layout and styling related parameters see the [customization user guide](../../user_guide/Customization.ipynb).\n",
    "\n",
    "* **``debounce``** (int or dict): The debounce timeout to apply to selection events, either specified as a single integer value (in milliseconds) or a dictionary that declares a debounce value per event. Debouncing ensures that events are only dispatched N milliseconds after a user is done interacting with the plot.\n",
    "* **``object``** (dict or altair Chart): Either a dictionary containing a Vega or Vega-Lite plot specification, or an Altair Chart\n",
    "* **``theme``** (str): A theme to apply to the plot, must be one of 'excel', 'ggplot2', 'quartz', 'vox', 'fivethirtyeight', 'dark', 'latimes', 'urbaninstitute', 'googlecharts'.\n",
    "* **``show_actions``** (boolean): Whether to show chart actions menu such as save, edit etc.\n",
    "\n",
    "Readonly parameters:\n",
    "\n",
    "* **``selection``** (Selection): The Selection object exposes parameters which reflect the selections declared on the plot into Python. \n",
    "\n",
    "___"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``Vega`` pane supports both ``vega`` and ``vega-lite`` specs which may be provided in a raw form (i.e. a dictionary) or by defining an ``altair`` plot.\n",
    "\n",
    "To display ``vega`` and ``vega-lite`` specification simply construct a ``Vega`` pane directly or pass it to ``pn.panel``:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vegalite = {\n",
    "  \"$schema\": \"https://vega.github.io/schema/vega-lite/v3.json\",\n",
    "  \"data\": {\"url\": \"https://raw.githubusercontent.com/vega/vega/master/docs/data/barley.json\"},\n",
    "  \"mark\": \"bar\",\n",
    "  \"encoding\": {\n",
    "    \"x\": {\"aggregate\": \"sum\", \"field\": \"yield\", \"type\": \"quantitative\"},\n",
    "    \"y\": {\"field\": \"variety\", \"type\": \"nominal\"},\n",
    "    \"color\": {\"field\": \"site\", \"type\": \"nominal\"}\n",
    "  }\n",
    "}\n",
    "vgl_pane = pn.panel(vegalite, height=240)\n",
    "vgl_pane"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Like all other panes, the ``Vega`` pane ``object`` can be updated, either in place and triggering an update:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vegalite['mark'] = 'area'\n",
    "vgl_pane.param.trigger('object')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "or by replacing the ``object`` entirely:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vgl_pane.object = {\n",
    "  \"$schema\": \"https://vega.github.io/schema/vega-lite/v3.json\",\n",
    "  \"data\": {\n",
    "    \"url\": \"https://raw.githubusercontent.com/vega/vega/master/docs/data/disasters.csv\"\n",
    "  },\n",
    "  \"width\": 600,\n",
    "  \"height\": 400,\n",
    "  \"transform\": [\n",
    "    {\"filter\": \"datum.Entity !== 'All natural disasters'\"}\n",
    "  ],\n",
    "  \"mark\": {\n",
    "    \"type\": \"circle\",\n",
    "    \"opacity\": 0.8,\n",
    "    \"stroke\": \"black\",\n",
    "    \"strokeWidth\": 1\n",
    "  },\n",
    "  \"encoding\": {\n",
    "    \"x\": {\n",
    "      \"field\": \"Year\",\n",
    "      \"type\": \"nominal\",\n",
    "      \"axis\": {\"labelAngle\": 90}\n",
    "    },\n",
    "    \"y\": {\"field\": \"Entity\", \"type\": \"nominal\", \"axis\": {\"title\": \"\"}},\n",
    "    \"size\": {\n",
    "      \"field\": \"Deaths\",\n",
    "      \"type\": \"quantitative\",\n",
    "      \"legend\": {\"title\": \"Annual Global Deaths\", \"clipHeight\": 30},\n",
    "      \"scale\": {\"range\": [0, 5000]}\n",
    "    },\n",
    "    \"color\": {\"field\": \"Entity\", \"type\": \"nominal\", \"legend\": None}\n",
    "  }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `vega-lite` specification can also be responsively sized by declaring the width or height to match the container:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "responsive_spec = dict(vgl_pane.object, width='container')\n",
    "\n",
    "vgl_responsive_pane = pn.pane.Vega(responsive_spec)\n",
    "vgl_responsive_pane"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Please note that the `vega` specification does not support setting `width` and `height` to `container`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Altair\n",
    "\n",
    "A more convenient way of defining a Vega chart is to declare it using [altair](https://altair-viz.github.io), which provides a declarative API on top of vega-lite. The ``Vega`` pane will automatically render the Vega-Lite spec when passed an Altair chart:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import altair as alt\n",
    "from vega_datasets import data\n",
    "\n",
    "cars = data.cars()\n",
    "\n",
    "chart = alt.Chart(cars).mark_circle(size=60).encode(\n",
    "    x='Horsepower',\n",
    "    y='Miles_per_Gallon',\n",
    "    color='Origin',\n",
    "    tooltip=['Name', 'Origin', 'Horsepower', 'Miles_per_Gallon']\n",
    ").interactive()\n",
    "\n",
    "altair_pane = pn.panel(chart)\n",
    "altair_pane"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Altair chart can also be updated by updating the pane ``object``:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "altair_pane.object = chart.mark_circle(size=100)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All the usual layouts and composition operators that Altair supports can also be rendered:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "penguins_url = \"https://raw.githubusercontent.com/vega/vega/master/docs/data/penguins.json\"\n",
    "\n",
    "chart1 = alt.Chart(penguins_url).mark_point().encode(\n",
    "    x=alt.X('Beak Length (mm):Q', scale=alt.Scale(zero=False)),\n",
    "    y=alt.Y('Beak Depth (mm):Q', scale=alt.Scale(zero=False)),\n",
    "    color='Species:N'\n",
    ").properties(\n",
    "    height=300,\n",
    "    width=300,\n",
    ")\n",
    "\n",
    "chart2 = alt.Chart(penguins_url).mark_bar().encode(\n",
    "    x='count()',\n",
    "    y=alt.Y('Beak Depth (mm):Q', bin=alt.Bin(maxbins=30)),\n",
    "    color='Species:N'\n",
    ").properties(\n",
    "    height=300,\n",
    "    width=100\n",
    ")\n",
    "\n",
    "pn.panel(chart1 | chart2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Selections\n",
    "\n",
    "The `Vega` pane automatically syncs any selections expressed on the Vega/Altair chart. As an example we can add an Altair `selection_interval` selection to our chart:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "brush = alt.selection_interval(name='brush')  # selection of type \"interval\"\n",
    "\n",
    "chart = alt.Chart(penguins_url).mark_point().encode(\n",
    "    x=alt.X('Beak Length (mm):Q', scale=alt.Scale(zero=False)),\n",
    "    y=alt.Y('Beak Depth (mm):Q', scale=alt.Scale(zero=False)),\n",
    "    color=alt.condition(brush, 'Species:N', alt.value('lightgray'))\n",
    ").properties(\n",
    "    width=250,\n",
    "    height=250\n",
    ").add_selection(\n",
    "    brush\n",
    ")\n",
    "\n",
    "vega_pane = pn.pane.Vega(chart, debounce=10)\n",
    "\n",
    "vega_pane"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note we specified a single `debounce` value, if we declare multiple selections we can instead declare a debounce value per named event by specifying it as a dictionary, e.g. `debounce={'brush': 10, ...}`.\n",
    "\n",
    "The named selection will now appear on the `.selection` sub-object:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vega_pane.selection"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By inspecting the JSON representation of the Altair chart we can see how to express these selections in vega(-lite):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "chart.to_dict()['selection']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For more background see the [Altair](https://altair-viz.github.io/user_guide/interactions.html) documentation on available interactions.\n",
    "\n",
    "Next we're going to create an example that binds the `brush` selection to a function which displays the selected values in a table. To achieve this, we need to know that the selection returns a dictionary in the format `{'column_name': [min, max]}`, which for our Penguins examples can look like this:\n",
    "\n",
    "```python\n",
    "{'Beak Length (mm)': [51.824, 53.952], 'Beak Depth (mm)': [18.796, 18.904]}\n",
    "```\n",
    "\n",
    "To display the selected values in a table, we will use the selection dictionary to construct a pandas query string that can be used with `DataFrame.query()`. Finally we are returning both the query string and the filtered table in a Column:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "df = pd.read_json(\"https://raw.githubusercontent.com/vega/vega/master/docs/data/penguins.json\")\n",
    "\n",
    "def filtered_table(selection):\n",
    "    if not selection:\n",
    "        return '## No selection'\n",
    "    query = ' & '.join(\n",
    "        f'{crange[0]:.3f} <= `{col}` <= {crange[1]:.3f}'\n",
    "        for col, crange in selection.items()\n",
    "    )\n",
    "    return pn.Column(\n",
    "        f'Query: {query}',\n",
    "        pn.pane.DataFrame(df.query(query), width=600, height=300)\n",
    "    )\n",
    "\n",
    "pn.Row(vega_pane, pn.bind(filtered_table, vega_pane.selection.param.brush))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Altair already provides a syntax for filtering one chart based on the selection in another, but one limitation is that these charts need to be displayed in the same layout for the filtering to work. By using Panel to filter one Altair chart based on another, we can place the charts anywhere in our app and still have the filtering work as expected.\n",
    "\n",
    "One way to filter a chart based on the selection in another chart, is to to use the same approach as above and create the second chart with the data filtered via `.query`. Altair also provides a way to do the filtering directly with the `transform_filter` method instead of using pandas. In the example below, we are constructing a [composed](https://vega.github.io/vega-lite/docs/predicate.html#composition) [range predicate](https://vega.github.io/vega-lite/docs/predicate.html#range-predicate) from our selection object and passing it to the `transform_filter` method of the second chart."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def bar_counts(selection):\n",
    "    if not selection:\n",
    "        return '## No selection'\n",
    "    range_predicate = {\n",
    "        'and': [{\n",
    "            'field': key,\n",
    "            'range': [selection[key][0], selection[key][1]]\n",
    "        } for key in selection]\n",
    "    }\n",
    "    return alt.Chart(penguins_url, width=220).mark_bar().encode(\n",
    "        x='count()',\n",
    "        y='Species:N',\n",
    "        color=alt.Color('Species:N', legend=None)\n",
    "    ).transform_filter(\n",
    "        range_predicate\n",
    "    )\n",
    "\n",
    "pn.Column(vega_pane, pn.bind(bar_counts, vega_pane.selection.param.brush))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Controls\n",
    "\n",
    "The `Vega` pane exposes a number of options which can be changed from both Python and Javascript. Try out the effect of these parameters interactively:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pn.Row(vgl_responsive_pane.controls(jslink=True), vgl_responsive_pane, sizing_mode=\"stretch_width\")"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
