\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"
\n",
"
\n",
""
],
"text/plain": [
"Row\n",
" [0] Column\n",
" [0] Row\n",
" [0] Spacer(width=30)\n",
" [1] Checkbox(name='Normalize')\n",
" [1] ParamFunction(function)\n",
" [1] Spacer(width=20)\n",
" [2] Column(width=150)\n",
" [0] Spacer(height=10)\n",
" [1] FloatSlider(end=2, name='log₁₀ β', start=-1)\n",
" [2] FloatSlider(end=2, name='log₁₀ γ', start=-1)\n",
" [3] FloatSlider(end=10, name='nx', start=0.1, value=2)\n",
" [4] FloatSlider(end=10, name='ny', start=0.1, value=2)\n",
" [5] FloatSlider(end=20, name='t_max', start=1, value=10)\n",
" [6] FloatSlider(end=2, name='log₁₀ x0', start=-1, value=0.3010299956639812)"
]
},
"execution_count": 12,
"metadata": {
"application/vnd.holoviews_exec.v0+json": {
"id": "1213"
}
},
"output_type": "execute_result"
}
],
"source": [
"widgets = pn.Column(\n",
" pn.Spacer(height=10),\n",
" log_beta_slider,\n",
" log_gamma_slider, \n",
" nx_slider,\n",
" ny_slider,\n",
" t_max_slider,\n",
" log_x0_slider,\n",
" width=150,\n",
")\n",
"\n",
"left_column = pn.Column(\n",
" pn.Row(pn.Spacer(width=30), normalize_checkbox), cascade_response_plot,\n",
")\n",
"\n",
"# Final layout\n",
"pn.Row(left_column, pn.Spacer(width=20), widgets)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Importantly, when exploring the plot interactively, we see that when Y cooperatively activates Z (that is, $n_y$ is large), the delay is longer. The delay is also naturally longer as $\\gamma$ gets small. This makes sense, since $\\gamma$ is the ratio of the decay rate of Z to that of Y. As we saw in the previous lesson, the decay rate sets the speed of the response, and if Z decays more slowly than Y, its dynamics will be slower.\n",
"\n",
"Note that the above conclusions are clearer when we plot the normalized curves."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Speeding up interactions and Hill functions\n",
"\n",
"The process of building the interactive plot may have seemed like a lot of steps. However, if we look back at what we did to build it, we did the following.\n",
"\n",
"1. Wrote a function to make a plot.\n",
"2. Built widgets to control the parameters of the plot.\n",
"3. Make a layout positioning the plot(s) and widgets.\n",
"\n",
"You pretty much do step 1 whenever you make any plot. Panel makes the interactivity easy by providing widgets and function decorators.\n",
"\n",
"When we built the plots above, we wrote a function that constructs a new plot every time we change a parameter value. This works well, but it can become slow, since we are re-generating the entire graphic with each update. To have faster responses, we could instead update only the *data* that is plotted.\n",
"\n",
"To illustrate how this work, and to get a feel for Hill functions in the meantime, we can look at how tuning the Hill coefficient and Hill $k$ values affect the shape of a Hill function.\n",
"\n",
"We start by writing a function to make a plot of an increasing (in blue) and decreasing (in orange) Hill function. We assume that $k$ and the concentration $x$ are in the same (unspecified) units. We will not need any Panel decorator here, as will become clear momentarily."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"def hill_plot(k=1, n=1):\n",
" # Set up plot\n",
" p = bokeh.plotting.figure(\n",
" frame_width=450,\n",
" frame_height=250,\n",
" x_axis_label=\"x\",\n",
" y_axis_label=\"f(x;k,n)\",\n",
" x_range=[0, 10],\n",
" )\n",
"\n",
" # Set up empty data source\n",
" x = np.linspace(0, 10, 400)\n",
" y_decreasing = 1 / (1 + (x / k) ** n)\n",
" y_increasing = 1 - y_decreasing\n",
" source = bokeh.models.ColumnDataSource(\n",
" dict(x=x, y_increasing=y_increasing, y_decreasing=y_decreasing)\n",
" )\n",
"\n",
" # Populate glyphs\n",
" p.line(\"x\", \"y_increasing\", source=source, line_width=2, color=colors[0])\n",
" p.line(\"x\", \"y_decreasing\", source=source, line_width=2, color=colors[1])\n",
"\n",
" return p"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we can build sliders for $k$ and $n$."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"k_slider = pn.widgets.FloatSlider(\n",
" name=\"k\", start=0.1, end=10, step=0.1, value=1, width=150\n",
")\n",
"n_slider = pn.widgets.FloatSlider(\n",
" name=\"n\", start=0.1, end=10, step=0.1, value=1, width=150\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we need to make sure the Bokeh plot is in a Panel **pane**, so that we can link it to the slider, which we need to do explicitly since we're updating the data source and not just replotting."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"p_pane = pn.pane.Bokeh(hill_plot())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Next, we need a **callback**. This is a function that gets executed every time the slider changes. In our previous example, by default, the callback was to regenerate the entire plot. Here, we update the data source in our callback. We have to get into the guts of the Bokeh figure, pulling out the glyph renderer and adjusting its properties, including its data source."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"def k_callback(target, event):\n",
" # New value of k\n",
" k = event.new\n",
"\n",
" # Fetch value of n\n",
" n = n_slider.value\n",
"\n",
" # Extract data source\n",
" gr = target.object.renderers[0]\n",
" source = gr.data_source\n",
"\n",
" # Update data source\n",
" x = source.data[\"x\"]\n",
" source.data[\"y_decreasing\"] = 1 / (1 + (x / k) ** n)\n",
" source.data[\"y_increasing\"] = 1 - source.data[\"y_decreasing\"]\n",
"\n",
"\n",
"def n_callback(target, event):\n",
" # Fetch value of k\n",
" k = k_slider.value\n",
"\n",
" # New value of n\n",
" n = event.new\n",
"\n",
" # Extract data source\n",
" gr = target.object.renderers[0]\n",
" source = gr.data_source\n",
"\n",
" # Update data source\n",
" x = source.data[\"x\"]\n",
" source.data[\"y_decreasing\"] = 1 / (1 + (x / k) ** n)\n",
" source.data[\"y_increasing\"] = 1 - source.data[\"y_decreasing\"]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, we need to **link** the sliders with the plot."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Watcher(inst=FloatSlider(end=10, name='n', start=0.1, value=1, width=150), cls=