{
"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": [
"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(name=\"Create\", button_type=\"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=\"Parent Plot: 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\"Child Plot: Selected Points({len(indices)})\"\n",
" else:\n",
" filtered_df = df\n",
" title = f\"Child Plot: 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
}