{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Plotly pane displays [Plotly plots](https://plotly.com/python/) within a Panel application. It enhances the speed of plot updates by using binary serialization for array data contained in the Plotly object. \n",
    "\n",
    "It uses [plotly.js](https://plotly.com/javascript/) **version {{PLOTLY_VERSION}}**  \n",
    "\n",
    "Please remember that to use the Plotly pane in a Jupyter notebook, you must activate the [Panel extension](https://panel.holoviz.org/api/cheatsheet.html#pn-extension) and include `\"plotly\"` as an argument. This step ensures that plotly.js is properly set up."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "\n",
    "pn.extension(\"plotly\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Parameters:\n",
    "\n",
    "For details on other options for customizing the component see the general [layout](../../how_to/layout/index.md) and [styling](../../how_to/styling/index.md) how-to guides as well as the specific [Style Plotly Plots](../../how_to/styling/plotly.md) how-to guide.\n",
    "\n",
    "### Core\n",
    "\n",
    "* **``object``** (object): The Plotly `Figure` or dictionary object being displayed.\n",
    "* **``config``** (dict): Additional configuration of the plot. See [Plotly configuration options](https://plotly.com/javascript/configuration-options/).\n",
    "\n",
    "### Update in Place\n",
    "\n",
    "* **``link_figure``** (bool, default: True): Update the displayed Plotly figure when the Plotly `Figure` is modified in place.\n",
    "\n",
    "### Events\n",
    "\n",
    "* **``click_data``** (dict): Click event data from `plotly_click` event.\n",
    "* **``clickannotation_data``** (dict): Clickannotation event data from `plotly_clickannotation` event.\n",
    "* **``hover_data``** (dict): Hover event data from `plotly_hover` and `plotly_unhover` events.\n",
    "* **``relayout_data``** (dict): Relayout event data from `plotly_relayout` event\n",
    "* **``restyle_data``** (dict): Restyle event data from `plotly_restyle` event\n",
    "* **``selected_data``** (dict): Selected event data from `plotly_selected` and `plotly_deselect` events.\n",
    "* **``viewport``** (dict): Current viewport state, i.e. the x- and y-axis limits of the displayed plot. Updated on `plotly_relayout`, `plotly_relayouting` and `plotly_restyle` events.\n",
    "* **``viewport_update_policy``** (str, default = 'mouseup'): Policy by which the viewport parameter is updated during user interactions\n",
    "    * ``mouseup``: updates are synchronized when mouse button is released after panning\n",
    "    * ``continuous``: updates are synchronized continually while panning\n",
    "    * ``throttle``: updates are synchronized while panning, at intervals determined by the viewport_update_throttle parameter\n",
    "* **``viewport_update_throttle``** (int, default = 200, bounds = (0, None)): Time interval in milliseconds at which viewport updates are synchronized when viewport_update_policy is \"throttle\".\n",
    "\n",
    "___"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As with most other types ``Panel`` will automatically convert a Plotly `Figure` to a `Plotly` pane if it is passed to the `pn.panel` function or a Panel layout, but a `Plotly` pane can also be constructed directly using the `pn.pane.Plotly` constructor:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Basic Example\n",
    "\n",
    "Lets create a basic example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import plotly.graph_objs as go\n",
    "\n",
    "import panel as pn\n",
    "\n",
    "pn.extension(\"plotly\")\n",
    "\n",
    "xx = np.linspace(-3.5, 3.5, 100)\n",
    "yy = np.linspace(-3.5, 3.5, 100)\n",
    "x, y = np.meshgrid(xx, yy)\n",
    "z = np.exp(-((x - 1) ** 2) - y**2) - (x**3 + y**4 - x / 5) * np.exp(-(x**2 + y**2))\n",
    "\n",
    "surface=go.Surface(z=z)\n",
    "fig = go.Figure(data=[surface])\n",
    "\n",
    "fig.update_layout(\n",
    "    title=\"Plotly 3D Plot\",\n",
    "    width=500,\n",
    "    height=500,\n",
    "    margin=dict(t=50, b=50, r=50, l=50),\n",
    ")\n",
    "\n",
    "plotly_pane = pn.pane.Plotly(fig)\n",
    "plotly_pane"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once created `Plotly` pane can be updated by assigning a new figure object"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_fig = go.Figure(data=[go.Surface(z=np.sin(z+1))])\n",
    "new_fig.update_layout(\n",
    "    title=\"Plotly 3D Plot\",\n",
    "    width=500,\n",
    "    height=500,\n",
    "    margin=dict(t=50, b=50, r=50, l=50),\n",
    ")\n",
    "\n",
    "plotly_pane.object = new_fig"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lets reset the Plotly pane"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plotly_pane.object = fig"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Layout Example\n",
    "\n",
    "The `Plotly` pane supports layouts and subplots of arbitrary complexity, allowing even deeply nested Plotly figures to be displayed:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "from plotly import subplots\n",
    "\n",
    "pn.extension(\"plotly\")\n",
    "\n",
    "heatmap = go.Heatmap(\n",
    "    z=[[1, 20, 30],\n",
    "       [20, 1, 60],\n",
    "       [30, 60, 1]],\n",
    "    showscale=False)\n",
    "\n",
    "y0 = np.random.randn(50)\n",
    "y1 = np.random.randn(50)+1\n",
    "\n",
    "box_1 = go.Box(y=y0)\n",
    "box_2 = go.Box(y=y1)\n",
    "data = [heatmap, box_1, box_2]\n",
    "\n",
    "fig_layout = subplots.make_subplots(\n",
    "    rows=2, cols=2, specs=[[{}, {}], [{'colspan': 2}, None]],\n",
    "    subplot_titles=('First Subplot','Second Subplot', 'Third Subplot')\n",
    ")\n",
    "\n",
    "fig_layout.append_trace(box_1, 1, 1)\n",
    "fig_layout.append_trace(box_2, 1, 2)\n",
    "fig_layout.append_trace(heatmap, 2, 1)\n",
    "\n",
    "fig_layout['layout'].update(height=600, width=600, title='i <3 subplots')\n",
    "\n",
    "pn.pane.Plotly(fig_layout)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Responsive Plots\n",
    "\n",
    "Plotly plots can be made responsive using the `autosize` option on a Plotly layout and a responsive `sizing_mode` argument to the `Plotly` pane:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import panel as pn\n",
    "import plotly.express as px\n",
    "\n",
    "pn.extension(\"plotly\")\n",
    "\n",
    "data = pd.DataFrame([\n",
    "    ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4),\n",
    "    ('Friday', 4), ('Saturday', 4), ('Sunday', 4)], columns=['Day', 'Orders']\n",
    ")\n",
    "\n",
    "fig_responsive = px.line(data, x=\"Day\", y=\"Orders\")\n",
    "fig_responsive.update_traces(mode=\"lines+markers\", marker=dict(size=10), line=dict(width=4))\n",
    "fig_responsive.layout.autosize = True\n",
    "\n",
    "responsive = pn.pane.Plotly(fig_responsive, height=300)\n",
    "\n",
    "pn.Column('## A responsive plot', responsive, sizing_mode='stretch_width')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plot Configuration\n",
    "\n",
    "You can set the [Plotly configuration options](https://plotly.com/javascript/configuration-options/) via the `config` parameter. Lets try to configure `scrollZoom`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig_responsive.update_xaxes(automargin=False)\n",
    "\n",
    "responsive_with_zoom = pn.pane.Plotly(fig_responsive, config={\"scrollZoom\": True}, height=300)\n",
    "\n",
    "pn.Column('## A responsive and scroll zoomable plot', responsive_with_zoom, sizing_mode='stretch_width')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try scrolling with the mouse over the plot!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Patching\n",
    "\n",
    "Instead of updating the entire Figure you can efficiently patch traces or the layout if you use a dictionary instead of a Plotly Figure.\n",
    "\n",
    "Note patching will only be efficient if the ``Figure`` is defined as a dictionary, since Plotly will make copies of the traces, which means that modifying them in place has no effect. Modifying an array will send just the array using a binary protocol, leading to fast and efficient updates."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import plotly.graph_objs as go\n",
    "\n",
    "import panel as pn\n",
    "\n",
    "pn.extension(\"plotly\")\n",
    "\n",
    "xx = np.linspace(-3.5, 3.5, 100)\n",
    "yy = np.linspace(-3.5, 3.5, 100)\n",
    "x, y = np.meshgrid(xx, yy)\n",
    "z = np.exp(-((x - 1) ** 2) - y**2) - (x**3 + y**4 - x / 5) * np.exp(-(x**2 + y**2))\n",
    "\n",
    "surface = go.Surface(z=z)\n",
    "layout = go.Layout(\n",
    "    title='Plotly 3D Plot',\n",
    "    autosize=False,\n",
    "    width=500,\n",
    "    height=500,\n",
    "    margin=dict(t=50, b=50, r=50, l=50)\n",
    ")\n",
    "\n",
    "fig_patch = dict(data=[surface], layout=layout)\n",
    "\n",
    "plotly_pane_patch = pn.pane.Plotly(fig_patch)\n",
    "plotly_pane_patch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "surface.z = np.sin(z+1)\n",
    "plotly_pane_patch.object = fig_patch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Similarly, modifying the plot ``layout`` will only modify the layout, leaving the traces unaffected."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig_patch['layout']['width'] = 800\n",
    "\n",
    "plotly_pane_patch.object = fig_patch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lets reset the Plotly pane"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "surface.z = z\n",
    "fig_patch['layout']['width'] = 500\n",
    "\n",
    "plotly_pane_patch.object = fig_patch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Streaming\n",
    "\n",
    "You can stream updates by replacing the entire Figure object. To stream efficiently though you should use patching technique described above.\n",
    "\n",
    "You can stream periodically using `pn.state.add_periodic_callback`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import plotly.graph_objects as go\n",
    "\n",
    "import panel as pn\n",
    "\n",
    "pn.extension(\"plotly\")\n",
    "\n",
    "\n",
    "df_ohlc = pn.cache(pd.read_csv)(\n",
    "    \"https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv\"\n",
    ")\n",
    "\n",
    "start_index = 50\n",
    "\n",
    "stream_data = go.Ohlc(\n",
    "    x=df_ohlc.loc[:start_index, \"Date\"],\n",
    "    open=df_ohlc.loc[:start_index, \"AAPL.Open\"],\n",
    "    high=df_ohlc.loc[:start_index, \"AAPL.High\"],\n",
    "    low=df_ohlc.loc[:start_index, \"AAPL.Low\"],\n",
    "    close=df_ohlc.loc[:start_index, \"AAPL.Close\"],\n",
    ")\n",
    "\n",
    "fig_stream = {\"data\": stream_data, \"layout\": go.Layout(xaxis_rangeslider_visible=False)}\n",
    "\n",
    "plotly_pane_stream = pn.pane.Plotly(fig_stream)\n",
    "\n",
    "\n",
    "def stream():\n",
    "    index = len(stream_data.x)\n",
    "    if index == len(df_ohlc):\n",
    "        index = 0\n",
    "\n",
    "    stream_data[\"x\"] = df_ohlc.loc[:index, \"Date\"]\n",
    "    stream_data[\"open\"] = df_ohlc.loc[:index, \"AAPL.Open\"]\n",
    "    stream_data[\"high\"] = df_ohlc.loc[:index, \"AAPL.High\"]\n",
    "    stream_data[\"low\"] = df_ohlc.loc[:index, \"AAPL.Low\"]\n",
    "    stream_data[\"close\"] = df_ohlc.loc[:index, \"AAPL.Close\"]\n",
    "    plotly_pane_stream.object = fig_stream\n",
    "\n",
    "\n",
    "pn.state.add_periodic_callback(stream, period=100, count=50)\n",
    "\n",
    "plotly_pane_stream"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also stream via a generator or async generator function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from asyncio import sleep\n",
    "\n",
    "import pandas as pd\n",
    "import plotly.graph_objects as go\n",
    "\n",
    "import panel as pn\n",
    "\n",
    "pn.extension(\"plotly\")\n",
    "\n",
    "\n",
    "df_ohlc = pn.cache(pd.read_csv)(\n",
    "    \"https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv\"\n",
    ")\n",
    "\n",
    "start_index = 50\n",
    "\n",
    "gen_data = go.Ohlc(\n",
    "    x=df_ohlc.loc[:start_index, \"Date\"],\n",
    "    open=df_ohlc.loc[:start_index, \"AAPL.Open\"],\n",
    "    high=df_ohlc.loc[:start_index, \"AAPL.High\"],\n",
    "    low=df_ohlc.loc[:start_index, \"AAPL.Low\"],\n",
    "    close=df_ohlc.loc[:start_index, \"AAPL.Close\"],\n",
    ")\n",
    "layout = go.Layout(xaxis_rangeslider_visible=False)\n",
    "\n",
    "\n",
    "async def stream_generator():\n",
    "    for _ in range(start_index, start_index+50):\n",
    "        index = len(gen_data.x)\n",
    "        if index == len(df_ohlc):\n",
    "            index = 0\n",
    "\n",
    "        gen_data[\"x\"] = df_ohlc.loc[:index, \"Date\"]\n",
    "        gen_data[\"open\"] = df_ohlc.loc[:index, \"AAPL.Open\"]\n",
    "        gen_data[\"high\"] = df_ohlc.loc[:index, \"AAPL.High\"]\n",
    "        gen_data[\"low\"] = df_ohlc.loc[:index, \"AAPL.Low\"]\n",
    "        gen_data[\"close\"] = df_ohlc.loc[:index, \"AAPL.Close\"]\n",
    "        \n",
    "        yield  {\"data\": gen_data, \"layout\": layout}\n",
    "        await sleep(0.05)\n",
    "\n",
    "\n",
    "pn.pane.Plotly(stream_generator)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Update in Place\n",
    "\n",
    "An alternative to updating the figure dictionary is updating the Plotly `Figure` in place, i.e. via its attributes and methods."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import plotly.graph_objects as go\n",
    "\n",
    "import panel as pn\n",
    "\n",
    "pn.extension(\"plotly\")\n",
    "\n",
    "\n",
    "fig_in_place = go.Figure()\n",
    "\n",
    "button = pn.widgets.Button(label=\"Create\", color=\"primary\")\n",
    "\n",
    "\n",
    "def handle_click(clicks):\n",
    "    mod = clicks % 3\n",
    "    if mod == 1:\n",
    "        button.name = \"Update\"\n",
    "        fig_in_place.add_scatter(y=[2, 1, 4, 3])\n",
    "        fig_in_place.add_bar(y=[2, 1, 4, 3])\n",
    "        fig_in_place.layout.title = \"New Figure\"\n",
    "    elif mod == 2:\n",
    "        button.name = \"Reset\"\n",
    "        scatter = fig_in_place.data[0]\n",
    "        scatter.y = [3, 1, 4, 3]\n",
    "        bar = fig_in_place.data[1]\n",
    "        bar.y = [5, 3, 2, 8]\n",
    "        fig_in_place.layout.title.text = \"Updated Figure\"\n",
    "    else:\n",
    "        fig_in_place.data = []\n",
    "        fig_in_place.layout = {\"title\": \"\"}\n",
    "        button.name = \"Create\"\n",
    "\n",
    "pn.bind(handle_click, button.param.clicks, watch=True)\n",
    "button.clicks=1\n",
    "\n",
    "plotly_pane_in_place = pn.pane.Plotly(\n",
    "    fig_in_place,\n",
    "    height=400,\n",
    "    width=700,\n",
    "    # link_figure=False\n",
    ")\n",
    "\n",
    "pn.Column(\n",
    "    button,\n",
    "    plotly_pane_in_place,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This enables you to use the Plotly `Figure` in the same way as you would have been using the Plotly [`FigureWidget`](https://plotly.com/python/figurewidget/).\n",
    "\n",
    "If you for some reason want to disable in place updates, you can set `link_figure=False` when you create the `Plotly` pane. You cannot change this when the pane has been created."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Events\n",
    "\n",
    "The Plotly pane enables you to bind to most of the click, hover, selection and other events described in [Plotly Event Handlers](https://plotly.com/javascript/plotlyjs-events/).\n",
    "\n",
    "### Simple Event Example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import plotly.express as px\n",
    "import panel as pn\n",
    "import pandas as pd\n",
    "\n",
    "pn.extension(\"plotly\")\n",
    "\n",
    "# Create dataframe\n",
    "x = [1, 2, 3, 4, 5]\n",
    "y = [10, 20, 30, 20, 10]\n",
    "df = pd.DataFrame({'x': x, 'y': y})\n",
    "\n",
    "# Create scatter plot\n",
    "fig_events = px.scatter(df, x='x', y='y', title='Click on a Point!', hover_name='x',)\n",
    "fig_events.update_traces(marker=dict(size=20))\n",
    "fig_events.update_layout(autosize=True, hovermode='closest')\n",
    "\n",
    "plotly_pane_event=pn.pane.Plotly(fig_events, height=400, max_width=1200, sizing_mode=\"stretch_width\")\n",
    "\n",
    "# Define Child View\n",
    "def child_view(event):\n",
    "    if not event:\n",
    "        return \"No point clicked\"\n",
    "    try:\n",
    "        point = event[\"points\"][0]\n",
    "        index = point['pointIndex']\n",
    "        x = point['x']\n",
    "        y = point['y']\n",
    "    except Exception as ex:\n",
    "        return f\"You clicked the Plotly Chart! I could not determine the point: {ex}\"\n",
    "    \n",
    "    return f\"**You clicked point {index} at ({x}, {y}) on the Plotly Chart!**\"\n",
    "\n",
    "ichild_view = pn.bind(child_view, plotly_pane_event.param.click_data)\n",
    "\n",
    "# Put things together\n",
    "pn.Column(plotly_pane_event, ichild_view)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Event Inspection\n",
    "\n",
    "The be able to work with the events its a good idea to inspect them. You can do that by printing them or including them in your visualization.\n",
    "\n",
    "Lets display them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "event_parameters = [\"click_data\", \"click_annotation_data\", \"hover_data\", \"relayout_data\", \"restyle_data\", \"selected_data\", \"viewport\"]\n",
    "\n",
    "pn.Param(plotly_pane_event, parameters=event_parameters, max_width=1100, name=\"Plotly Event Parameters\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the plot above, try hovering, clicking, selecting and changing the viewport by panning. Watch how the parameter values just above changes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "### Parent-Child Views\n",
    "\n",
    "A common technique for exploring higher-dimensional datasets is the use of Parent-Child views. This approach allows you to employ a top-down method to initially exing thehree most important dimensions in the parent plot. You can then select a subset of the data points and examine them in greater detail and across additional dimensions.\n",
    "\n",
    "Let's create a plot where Box or Lasso selections in the parent plot update a child plot. We will also customize the action bars using the `config` parameter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import plotly.express as px\n",
    "\n",
    "import panel as pn\n",
    "\n",
    "pn.extension(\"plotly\")\n",
    "df = (\n",
    "    pd.read_csv(\"https://datasets.holoviz.org/penguins/v1/penguins.csv\")\n",
    "    .dropna()\n",
    "    .reset_index(drop=True)\n",
    ")\n",
    "df[\"index\"] = df.index # Used to filter rows for child view\n",
    "\n",
    "color_map = {\"Adelie\": \"blue\", \"Chinstrap\": \"red\", \"Gentoo\": \"green\"}\n",
    "\n",
    "fig_parent = px.scatter(\n",
    "    df,\n",
    "    x=\"flipper_length_mm\",\n",
    "    y=\"body_mass_g\",\n",
    "    color_discrete_map=color_map,\n",
    "    custom_data=\"index\",  # Used to filter rows for child view\n",
    "    color=\"species\",\n",
    "    title=\"<b>Parent Plot</b>: Box or Lasso Select Points\",\n",
    ")\n",
    "\n",
    "\n",
    "def fig_child(selectedData):\n",
    "    if selectedData:\n",
    "        indices = [point[\"customdata\"][0] for point in selectedData[\"points\"]]\n",
    "        filtered_df = df.iloc[indices]\n",
    "        title = f\"<b>Child Plot</b>: Selected Points({len(indices)})\"\n",
    "    else:\n",
    "        filtered_df = df\n",
    "        title = f\"<b>Child Plot</b>: All Points ({len(filtered_df)})\"\n",
    "\n",
    "    fig = px.scatter(\n",
    "        filtered_df,\n",
    "        x=\"bill_length_mm\",\n",
    "        y=\"bill_depth_mm\",\n",
    "        color_discrete_map=color_map,\n",
    "        color=\"species\",\n",
    "        hover_data={\"flipper_length_mm\": True, \"body_mass_g\": True},\n",
    "        title=title,\n",
    "    )\n",
    "    return fig\n",
    "\n",
    "\n",
    "parent_config = {\n",
    "    \"modeBarButtonsToAdd\": [\"select2d\", \"lasso2d\"],\n",
    "    \"modeBarButtonsToRemove\": [\n",
    "        \"zoomIn2d\",\n",
    "        \"zoomOut2d\",\n",
    "        \"pan2d\",\n",
    "        \"zoom2d\",\n",
    "        \"autoScale2d\",\n",
    "    ],\n",
    "    \"displayModeBar\": True,\n",
    "    \"displaylogo\": False,\n",
    "}\n",
    "parent_pane = pn.pane.Plotly(fig_parent, config=parent_config)\n",
    "\n",
    "ifig_child = pn.bind(fig_child, parent_pane.param.selected_data)\n",
    "child_config = {\n",
    "    \"modeBarButtonsToRemove\": [\n",
    "        \"select2d\",\n",
    "        \"lasso2d\",\n",
    "        \"zoomIn2d\",\n",
    "        \"zoomOut2d\",\n",
    "        \"pan2d\",\n",
    "        \"zoom2d\",\n",
    "        \"autoScale2d\",\n",
    "    ],\n",
    "    \"displayModeBar\": True,\n",
    "    \"displaylogo\": False,\n",
    "}\n",
    "child_pane = pn.pane.Plotly(ifig_child, config=child_config)\n",
    "\n",
    "pn.FlexBox(parent_pane, child_pane)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Controls\n",
    "\n",
    "The `Plotly` 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(responsive.controls(jslink=True), responsive)"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
