Here’s an example of the plotting code for one figure (sorry for the non-minimalism!):
def build_precip_fig(df, config):
'''
Builds precip/depth plot for one or more stations.
Parameters
----------
df: a Pandas DataFrame
Must be indexed by datetime, index must be named Date
Must have 2 columns per station (precip and depth).
Columns must follow naming convention.
config: dict
Uses station id for keys (i.e. 'CYFM8')
Returns
-------
bokeh.plotting.figure instance
'''
df.index.name = 'Date'
DAILY_BAR_WIDTH = (0.80 * (1000.0 * 60.0 * 60.0 * 24.0)) # millisecs to a day
source = bkm.ColumnDataSource(df)
fig = bkp.figure(
title="Precipitation & Snow Depth",
name='precip_fig',
x_axis_type='datetime',
y_axis_label='SWE (in)',
height=300,
tools='pan,box_zoom,wheel_zoom,save',
toolbar_location='above'
)
fig.toolbar.logo = None
fig.toolbar.active_drag = None
fig.yaxis.axis_label_standoff = 10
fig.yaxis.minor_tick_line_color = None
#fig.y_range = bkm.Range1d(-5, df[filter(lambda x: '_dir' not in x, df.columns)].max().max())
# Hard-coded y-ranges nice for this panel so you can easily tell small vs large precip amounts
# Otherwise the range would zoom for just trace amounts of precip
# May have to decide how to set this different for mega precip zones like AK
fig.y_range = bkm.Range1d(-0.1, 3)
fig.yaxis.ticker = [0,0.5,1,1.5,2,2.5,3]
fig.extra_y_ranges = {"HS": bkm.Range1d(start=-5, end=150)}
fig.add_layout(bkm.LinearAxis(y_range_name="HS", axis_label='HS (in)', ticker=list(range(0,151,25))), 'right')
df.index.name = 'Date'
stids = list(set([c.split('_')[0] for c in df.columns]))
# Use 'order' entry in config to arrange the stns in preferred plotting order
order_list=[]
for s in stids:
order_list.append(config['stations'][s]['order'])
stids_sorted = [x for _,x in sorted(zip(order_list,stids))]
df['date_hover'] = df.index.strftime('%Y-%m-%d') # Important to do AFTER making stids
source = bkm.ColumnDataSource(df)
legend_items = []
tooltips=[("Date", "@date_hover")]
for stn in stids_sorted:
color = config['stations'][stn]['color']
lname = config['stations'][stn]['long_name']
precip_option = config['stations'][stn]['precip_option']
if precip_option == 'accum_one_hour':
swe = '{}_precip_accum_one_hour_24hr_sum'.format(stn)
tooltips.append(("%s swe | depth" % lname, "@%s_precip_accum_one_hour_24hr_sum{1.11} | @%s_snow_depth_24hr{int}" % (stn, stn)))
elif precip_option == 'snotel':
swe = '{}_snow_water_equiv_24hr_diff'.format(stn)
tooltips.append(("%s swe | depth" % lname, "@%s_snow_water_equiv_24hr_diff{1.11} | @%s_snow_depth_24hr{int}" % (stn, stn)))
elif precip_option == '':
swe = None
tooltips.append(("%s swe | depth" % lname, "0.00 | @%s_snow_depth_24hr{int}" % stn))
depth = '{}_snow_depth_24hr'.format(stn)
if swe is not None:
swebar = fig.vbar(
x='Date',
width=DAILY_BAR_WIDTH,
bottom=0,
top=swe,
line_width=0,
line_alpha=0,
fill_alpha=0.4,
color=color,
name="{}_bar".format(swe),
source=source
)
fig.add_layout(swebar)
depthline = fig.line(x='Date', y=depth, line_width=1.5, name="{}_line".format(depth), color=color, alpha=0.9, y_range_name='HS', source=source)
fig.add_layout(depthline)
if swe is not None:
legend_items.append(bkm.LegendItem(label=lname, renderers=[swebar, depthline]))
else:
legend_items.append(bkm.LegendItem(label=lname, renderers=[depthline]))
hline_hover = fig.line(x='Date', y=1, line_width=0.0, alpha=0.0, source=source)
fig.add_layout(hline_hover)
fig.add_tools(bkm.HoverTool(
renderers=[hline_hover],
tooltips=tooltips,
line_policy="nearest",
mode='vline'
)
)
legend = bkm.Legend(
items=legend_items,
location=(0, 15),
glyph_height=20,
glyph_width=30,
label_height=1,
label_width=50,
label_text_baseline="middle",
label_text_font_size="10pt",
border_line_alpha=0.0,
orientation="horizontal",
padding=5,
spacing=15,
label_standoff=2
)
fig.add_layout(legend, 'below')
fig.legend.click_policy = "hide"
return fig