From 314a947dbec22893619627f07989274cdc596d09 Mon Sep 17 00:00:00 2001 From: justinbois Date: Tue, 18 May 2021 13:27:46 -0700 Subject: [PATCH] v. 0.1.5. Added jsplots.py for JavaScript-based plots in notebooks in the book --- biocircuits/__init__.py | 3 +- biocircuits/jsplots.py | 142 ++++++++++++++++++++++++++++++++++++++++ biocircuits/viz.py | 24 +++---- setup.py | 2 +- 4 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 biocircuits/jsplots.py diff --git a/biocircuits/__init__.py b/biocircuits/__init__.py index 50f8b54..b1b21a4 100644 --- a/biocircuits/__init__.py +++ b/biocircuits/__init__.py @@ -10,8 +10,9 @@ from .dynsys import * from .gillespie import * from .rd import * +from . import jsplots __author__ = """Justin Bois""" __email__ = "bois@caltech.edu" -__version__ = "0.1.4" +__version__ = "0.1.5" diff --git a/biocircuits/jsplots.py b/biocircuits/jsplots.py new file mode 100644 index 0000000..b6c1e9d --- /dev/null +++ b/biocircuits/jsplots.py @@ -0,0 +1,142 @@ +import warnings + +import numpy as np + +import matplotlib.streamplot + +import bokeh.application +import bokeh.application.handlers +import bokeh.layouts +import bokeh.models +import bokeh.palettes +import bokeh.plotting + +import colorcet + +from . import utils + + +def turing_dispersion_relation(): + """Plot of dispersion relation for Turing patterns. + + Replaces Python code: + + def dispersion_relation(k_vals, d, mu): + lam = np.empty_like(k_vals) + for i, k in enumerate(k_vals): + A = np.array([[1-d*k**2, 1], + [-2*mu, -mu - k**2]]) + lam[i] = np.linalg.eigvals(A).real.max() + + return lam + + d_slider = pn.widgets.FloatSlider( + name="d", start=0.01, end=1, value=0.05, step=0.01, width=150 + ) + + mu_slider = pn.widgets.FloatSlider( + name="μ", start=0.01, end=2, value=1.5, step=0.005, width=150 + ) + + + @pn.depends(d_slider.param.value, mu_slider.param.value) + def plot_dispersion_relation(d, mu): + k = np.linspace(0, 10, 200) + lam_max_real_part = dispersion_relation(k, d, mu) + + p = bokeh.plotting.figure( + frame_width=350, + frame_height=200, + x_axis_label="k", + y_axis_label="Re[λ-max]", + x_range=[0, 10], + ) + + p.line(k, lam_max_real_part, color="black", line_width=2) + + return p + + + pn.Column( + pn.Row(pn.Spacer(width=50), d_slider, mu_slider), + pn.Spacer(height=20), + plot_dispersion_relation, + ) + + """ + d_slider = bokeh.models.Slider( + title="d", start=0.01, end=1, value=0.05, step=0.01, width=150 + ) + + mu_slider = bokeh.models.Slider( + title="μ", start=0.01, end=2, value=1.5, step=0.005, width=150 + ) + + k = np.linspace(0, 10, 500) + k2 = k ** 2 + mu = mu_slider.value + d = d_slider.value + b = mu + (1.0 + d) * k2 - 1.0 + c = (mu + k ** 2) * (d * k ** 2 - 1.0) + 2.0 * mu + discriminant = b ** 2 - 4.0 * c + + lam = np.empty_like(k) + inds = discriminant <= 0 + lam[inds] = -b[inds] / 2.0 + + inds = discriminant > 0 + lam[inds] = (-b[inds] + np.sqrt(discriminant[inds])) / 2.0 + + cds = bokeh.models.ColumnDataSource(dict(k=k, lam=lam)) + + p = bokeh.plotting.figure( + frame_width=350, + frame_height=200, + x_axis_label="k", + y_axis_label="Re[λ-max]", + x_range=[0, 10], + ) + + p.line(source=cds, x="k", y="lam", color="black", line_width=2) + + js_code = """ + function dispersion_relation(mu, d, k) { + let k2 = k**2; + let b = mu + (1.0 + d) * k2 - 1.0; + let c = (mu + k**2) * (d * k**2 - 1.0) + 2.0 * mu + let discriminant = b**2 - 4.0 * c; + + if (discriminant < 0) { + return -b / 2.0; + } + else { + return (-b + Math.sqrt(discriminant)) / 2.0; + } + } + + let mu = mu_slider.value; + let d = d_slider.value; + let k = cds.data['k']; + let lam = cds.data['lam']; + + for (let i = 0; i < k.length; i++) { + lam[i] = dispersion_relation(mu, d, k[i]); + } + + cds.change.emit(); + """ + callback = bokeh.models.CustomJS( + args=dict(cds=cds, d_slider=d_slider, mu_slider=mu_slider), code=js_code + ) + mu_slider.js_on_change("value", callback) + d_slider.js_on_change("value", callback) + + layout = bokeh.layouts.column( + bokeh.layouts.row( + bokeh.models.Spacer(width=60), d_slider, mu_slider, width=400 + ), + bokeh.models.Spacer(height=20), + p, + ) + + return layout diff --git a/biocircuits/viz.py b/biocircuits/viz.py index ef9a6b2..d63cc97 100644 --- a/biocircuits/viz.py +++ b/biocircuits/viz.py @@ -462,30 +462,28 @@ def xyt_plot( # Callback js_code = """ function sortedIndex(array, value) { - var low = 0, + let low = 0, high = array.length; while (low < high) { - var mid = (low + high) >>> 1; + let mid = (low + high) >>> 1; if (array[mid] < value) low = mid + 1; else high = mid; } return low; } -var x = source_plot.data['x']; -var x_len = x.length; -var t = source_t.data['t']; +let x = source_plot.data['x']; +let x_len = x.length; +let t = source_t.data['t']; -var i = sortedIndex(t, cb_obj.value); - -var k; +let i = sortedIndex(t, cb_obj.value); """ for var_name in ["y_" + str(j) for j in range(len(y))]: - js_code += f"""var {var_name} = source_plot.data['{var_name}']; -var {var_name}_source = source.data['{var_name}']; -for (k = 0; k < x_len; k++) {var_name}[k] = {var_name}_source[x_len * i + k]; + js_code += f"""let {var_name} = source_plot.data['{var_name}']; +let {var_name}_source = source.data['{var_name}']; +for (let k = 0; k < x_len; k++) {var_name}[k] = {var_name}_source[x_len * i + k]; """ @@ -498,7 +496,9 @@ def xyt_plot( t_slider.js_on_change("value", callback) - return bokeh.layouts.column(t_slider, p) + return bokeh.layouts.column( + bokeh.layouts.row(bokeh.models.Spacer(width=10), t_slider), p + ) def phase_portrait( diff --git a/setup.py b/setup.py index ecdb9f9..35c1e44 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from codecs import open from os import path -__version__ = '0.1.4' +__version__ = '0.1.5' here = path.abspath(path.dirname(__file__))