Bokeh Plots in a Blog Post
2019-10-28
Markdown is awesome. It is awesome ‘cause it provides a universal interface to write anything and store ’em up. However, it’s been a frustration for me being unable to embed bokeh plots in markdown. Compared with static images, interactive plots is way more condensed in information and can sometimes save a real lot of time. Apparently, by the time I wrote this post, I’ve already figured out a way to embed this kind of plots in my blog, but the question is: how does it work?
In fact, bokeh has already provided a function to prepare everything you need. The function components
in the [bokeh.embed]((https://docs.bokeh.org/en/latest/docs/reference/embed.html) library returns the two parts that you’ll need to copy and paste into the makrdown file (aka. the post). Step by step:
- Plot with bokeh, scale layout with the
gridplot
function specifyingsizing_mode
asscale_width
. Say our final layout object is calledlayout
. - Pass
layout
to thecomponents
function, parse returned values intoscript
anddiv
. - Copy (you may use packages like pyperclip) and paste the head scripts (see below),
script
anddiv
into the markdown file.
Head scripts (they are actually not always required, but considering ease of use I suggest copying them anyway):
<script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/1.3.4/bokeh.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/1.3.4/bokeh-widgets.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/1.3.4/bokeh-tables.min.js"></script>
Whole script that produced the plot at the bottom:
import pyperclip
from bokeh.plotting import figure
from bokeh.layouts import gridplot
from bokeh.embed import components
from bokeh.io import output_file, show
from bokeh.transform import dodge, factor_cmap
from bokeh.sampledata.periodic_table import elements
output_file('periodic.html')
periods = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII']
groups = [str(x) for x in range(1, 19)]
df = elements.copy()
df['atomic mass'] = df['atomic mass'].astype(str)
df['group'] = df['group'].astype(str)
df['period'] = [periods[x-1] for x in df.period]
df = df[df.group!='-']
df = df[df.symbol!='Lr']
df = df[df.symbol!='Lu']
cmap = {
'alkali metal' : '#a6cee3',
'alkaline earth metal' : '#1f78b4',
'metal' : '#d93b43',
'halogen' : '#999d9a',
'metalloid' : '#e08d49',
'noble gas' : '#eaeaea',
'nonmetal' : '#f1d4Af',
'transition metal' : '#599d7A',
}
TOOLTIPS = [
('Name', '@name'),
('Atomic number', '@{atomic number}'),
('Atomic mass', '@{atomic mass}'),
('Type', '@metal'),
('CPK color', '$color[hex, swatch]:CPK'),
('Electronic configuration', '@{electronic configuration}'),
]
p = figure(plot_width=1000, plot_height=450, x_range=groups,
y_range=list(reversed(periods)), tools='hover',
toolbar_location=None, tooltips=TOOLTIPS)
r = p.rect('group', 'period', 0.95, 0.95, source=df, fill_alpha=0.6,
color=factor_cmap('metal',
palette=list(cmap.values()),
factors=list(cmap.keys())))
text_props = {'source': df, 'text_align': 'left', 'text_baseline': 'middle'}
x = dodge('group', -0.4, range=p.x_range)
p.text(x=x, y='period', text='symbol', text_font_style='bold', **text_props)
p.text(x=x, y=dodge('period', 0.3, range=p.y_range), text='atomic number',
text_font_size='8pt', **text_props)
p.text(x=x, y=dodge('period', -0.35, range=p.y_range), text='name',
text_font_size='5pt', **text_props)
p.text(x=x, y=dodge('period', -0.2, range=p.y_range), text='atomic mass',
text_font_size='5pt', **text_props)
p.text(x=['3', '3'], y=['VI', 'VII'], text=['LA', 'AC'],
text_align='center', text_baseline='middle')
p.outline_line_color = None
p.grid.grid_line_color = None
p.axis.axis_line_color = None
p.axis.major_tick_line_color = None
p.axis.major_label_standoff = 0
p.hover.renderers = [r] # only hover element boxes
layout = gridplot([[p]], sizing_mode='scale_width', toolbar_location=None)
script, div = components(layout)
script = '<script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/1.3.4/bokeh.min.js"></script>' + \
'<script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/1.3.4/bokeh-widgets.min.js"></script>' + \
'<script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/1.3.4/bokeh-tables.min.js"></script>' + \
script + '\n' + div
pyperclip.copy(script)
(The plot may look ugly on small screens. Try with a computer.)