September 13, 2021    Share on: Twitter | Facebook | HackerNews | Reddit

Interactive plots for blogging

It is always desired to support blog post with good visuals and the interactive plots are very good for exploration and better understanding of the results and underlying data. Including interactive plots involves usage of javascript for the given visualization library and makes embedding interactive figures not that straightforward. This post provides exploration of methods used to embed interactive plot in jupyter notebook that is later transformed into HTML webpage. Article is divided into three parts dedicated to major python plotting libraries: plotly, bokeh and altair.

Plotly

Create baseline plot data

In [1]:
import plotly
from plotly import express as px

gapminder = px.data.gapminder()

fig = px.scatter(
    gapminder.query("year==2007"),
    x="gdpPercap",
    y="lifeExp",
    size="pop",
    color="continent",
    hover_name="country",
    log_x=True,
    size_max=60,
    height=480,
    width=600,
)

Display

Method 1: template for js snippet (Figure is displayed twice)

In this solution display(HTML) is rendering proper plot for the blog, and the fig.show() provides preview while working in the notebook.

In [2]:
plot_json = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
In [3]:
from jinja2 import Template

template = """
<div id="plotly-timeseries"></div>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script>
var graph = {{plot_json}};
Plotly.plot('plotly-timeseries', graph, {});
</script>
"""
data = {"plot_json": plot_json}
j2_template = Template(template)

from IPython.core.display import HTML

# This will be visible in blog post
display(HTML(j2_template.render(data)))

Next cell contains only fig.show(). This is a preview (display() above do not render in notebook) that can be either manually removed from the final version or can be filtered out using cell tags (remove_cell)

Method 2: Dump plotly figure to HTML string & pass to display.HTML() (no display in notebook)

The plot below is not displayed in notebook

In [5]:
import IPython

plotly_graph = fig.to_html(include_plotlyjs="cdn")
IPython.display.HTML(plotly_graph)
Out[5]:

Method 3. with HTML with plotly figure and display in IFrame

NOTE: this method requires manual copying generated file to output dir (e.g. docs)

In [6]:
with open("plotly_graph.html", "wt") as f:
    f.write(fig.to_html(include_plotlyjs="cdn"))
In [7]:
# iframe with and height should be litte bit larger than those rendered by plotly

IPython.display.IFrame("plotly_graph.html", width=650, height=500)
Out[7]:

Bokeh

In [8]:
# see: https://stackoverflow.com/a/43880597/3247880
import pandas as pd
import numpy as np
import bokeh

print("Bokeh version:", bokeh.__version__)
from bokeh.plotting import figure, show
from bokeh.models.sources import ColumnDataSource
from bokeh.io import output_file, output_notebook
from bokeh.models import HoverTool
from bokeh.embed import file_html
from bokeh.resources import CDN

output_notebook()

df = pd.DataFrame(np.random.normal(0, 5, (100, 2)), columns=["x", "y"])
df.head(2)
Bokeh version: 2.3.3
Loading BokehJS …
Out[8]:
x y
0 1.625303 -3.450590
1 0.331879 -6.498201
In [9]:
source = ColumnDataSource(df)
hover = HoverTool(tooltips=[("x", "@x"), ("y", "@y")])
myplot = figure(
    plot_width=600,
    plot_height=400,
    tools="hover,box_zoom,box_select,crosshair,reset",
)
_ = myplot.circle("x", "y", size=7, fill_alpha=0.5, source=source)
# show(myplot, notebook_handle=True)
In [10]:
myplot_html = file_html(myplot, CDN)
IPython.display.HTML(myplot_html)
Out[10]:
Bokeh Application

Altair

For altair displaying frontends refer to documentation: Displaying Altair Charts. By default both Jupyter Notebook and JupyterLab will render if there is web connection. See the documentation for offline rendering.

In [11]:
import altair as alt
from vega_datasets import data

source = data.cars()

alt.Chart(source).mark_circle(size=60).encode(
    x="Horsepower",
    y="Miles_per_Gallon",
    color="Origin",
    tooltip=["Name", "Origin", "Horsepower", "Miles_per_Gallon"],
).interactive()
Out[11]:
In [12]:
import altair as alt
import pandas as pd

# HTML renderer requires a web connection in order to load relevant
# Javascript libraries.
alt.renderers.enable("html")
source = pd.DataFrame(
    {
        "Letter": ["A", "B", "C", "D", "E", "F", "G", "H", "I"],
        "Frequency": [28, 55, 43, 91, 81, 53, 19, 87, 52],
    }
)

alt.Chart(source).mark_bar().encode(
    x="Letter",
    y="Frequency",
    tooltip=["Letter", "Frequency"],
).interactive()
Out[12]:
Out[13]: