#!/usr/bin/env python
# encoding: utf-8
"""displays interactive plots through bokeh, allows to save them in html format.
This plugin uses bokeh (https://bokeh.pydata.org/en/latest/) to build and display interactive plots.
It adds npkd.bokeh_fig, a python dictionary containing the styles (colors, lines...) and npkd.bokeh_plot containing the plot itself (bokeh figure format).
To make it work in the notebook add:
from bokeh.io import show,output_notebook
output_notebook()
And do:
npkd.bokeh(show=True)
To save the plot in html format after doing npkd.bokeh(show=True):
from bokeh.resources import CDN
from bokeh.embed import file_html
html_text = file_html(npkd.bokeh_plot, CDN, "Title")
with open("bokehplot.html", "w") as file:
file.write(html_text)
"""
from __future__ import print_function
from spike import NPKError
from spike.NPKData import NPKData_plugin, parsezoom
import numpy as np
import bokeh
import bokeh.plotting as bk
from bokeh.models import Range1d
import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
BokehMax = 60000000
########################################################################
[docs]def get_contour_data(ax):
"""
Get informations about contours created by matplotlib.
ax is the input matplotlob contour ax (cf. fig,ax produced by matplotlib)
xs and ys are the different contour lines got out of the matplotlib. col is the color corresponding to the lines.
"""
xs = []
ys = []
col = []
isolevelid = 0
for isolevel in ax.collections:
isocol = isolevel.get_color()[0]
thecol = 3 * [None]
theiso = str(ax.collections[isolevelid].get_array())
isolevelid += 1
for i in range(3):
thecol[i] = int(255 * isocol[i])
thecol = '#%02x%02x%02x' % (thecol[0], thecol[1], thecol[2])
for path in isolevel.get_paths():
v = path.vertices
x = v[:, 0]
y = v[:, 1]
xs.append(x.tolist())
ys.append(y.tolist())
col.append(thecol)
return xs, ys, col
[docs]def bokeh_display(npkd, scale=1.0, autoscalethresh=3.0, absmax=None, show=False, title=None, label=None, xlabel="_def_",
ylabel="_def_", axis=None, image=False, mode3D=False, zoom=None, mpldic={},
dbkdic={}, dfigdic={}, linewidth=1, color=None, plot_width=600, plot_height=400,
sizing_mode=None, redraw=False, tools="pan, box_zoom, box_select, reset, save"):
"""
Display using bokeh instead of matplotlib
scale allows to increase the vertical scale of display
absmax overwrite the value for the largest point, which will not be computed
display is scaled so that the largest point is first computed (and stored in absmax),
and then the value at absmax/scale is set full screen
show will call bk.show() at the end, allowing every declared display to be shown on-screen
useless in ipython/jupyter notebook
title add a title to the bokeh plot
label add a label text to plot
xlabel, ylabel axes label (default is self.currentunit - use None to remove)
axis used as axis if present, axis length should match experiment length
in 2D, should be a pair (xaxis,yaxis)
image if True, the function will generate the 2D NMR FID of data,
if False (Default), the function present contour plots.
mode3D not implemented
zoom is a tuple defining the zoom window (left,right) or ((F1_limits),(F2_limits))
defined in the current axis unit (points, ppm, m/z etc ....)
mpldic a dictionnary passed as is to the matplotlib plot command
dbkdic a dictionnary passed as is to populated the parameters of the bokeh graph
dfigdic a dictionnary passed as is to populated the content of the bokeh figure
linewidth linewidth for the plots (useful for example when using seaborn)
color if 1D is the color of the curve,
if 2D FID is the palette name to be used,
if 2D contour is the color set to be used by matplotlib.
plot_width, plot_height width and height of the plot
sizing_mode if provided, resize plot according to the window chosen sizes.
e.g. "scale_width", "scale_height", "scale_both"
tools a string containing the tools to be available for bokeh interactivity.
e.g. "pan, box_zoom, box_select, reset, save" (see bokeh doc for more info)
"""
#Settings global graphical parameters
dbk = {}
dbk['tools'] = tools
dbk['title'] = title
if sizing_mode is not None:
dbk['sizing_mode'] = sizing_mode
else:
dbk['plot_width'] = plot_width
dbk['plot_height'] = plot_height
if npkd.dim == 1:
if not absmax: # absmax is the largest point on spectrum, either given from call, or handled internally
if not npkd.absmax: # compute it if absent
#print "computing absmax...",
npkd.absmax = np.nanmax( np.abs(npkd.buffer) )
else:
npkd.absmax = absmax
mmin = -npkd.absmax/scale
mmax = npkd.absmax/scale
step = npkd.axis1.itype+1
z1, z2 = parsezoom(npkd,zoom)
while (z2-z1)//step > BokehMax:
step += 2
if axis is None:
ax = npkd.axis1.unit_axis()
else:
ax = axis
# fig.set_xscale(npkd.axis1.units[npkd.axis1.currentunit].scale) # set unit scale (log / linear)
# if npkd.axis1.units[npkd.axis1.currentunit].reverse: # set reverse mode
# ax.invert_xaxis()
if xlabel == "_def_":
xlabel = npkd.axis1.currentunit
if ylabel == "_def_":
ylabel = "a.u."
if xlabel is not None:
dbk['x_axis_label'] = xlabel
if ylabel is not None:
dbk['y_axis_label'] = ylabel
dbk.update(dbkdic)
dbk['x_range'] = (npkd.axis1.itoc(z1), npkd.axis1.itoc(z2))
p = bk.figure(**dbk)
dfig = {}
dfig['x'] = ax[z1:z2:step]
dfig['y'] = npkd.buffer[z1:z2:step].clip(mmin,mmax)
dfig['legend'] = label
dfig['line_width'] = linewidth
dfig['color'] = color
dfig.update(dfigdic)
p.line(**dfig)
npkd.bokeh_fig = dfig
npkd.bokeh_plot = p
elif npkd.dim == 2 and image:
# If image is set to True (Default False) - used to display 2D NMR FID as images
bout1 = npkd.axis1.itoc(npkd.size1-1)
bout2 = npkd.axis2.itoc(npkd.size2-1)
deb1 = npkd.axis1.itoc(0)
deb2 = npkd.axis2.itoc(0)
dbk['x_range'] = (deb2, bout2)
dbk['y_range'] = (deb1, bout1)
if xlabel == "_def_":
xlabel = npkd.axis2.currentunit
if ylabel == "_def_":
ylabel = npkd.axis1.currentunit
if xlabel is not None:
dbk['x_axis_label'] = xlabel
if ylabel is not None:
dbk['y_axis_label'] = ylabel
dbk.update(dbkdic)
p = bk.figure(**dbk)
dfig = {}
dfig['image'] = [npkd.get_buffer().real]
if color is None:
dfig['palette'] = bokeh.palettes.brewer['OrRd'][9][:-1] + bokeh.palettes.brewer['Blues'][9][::-1]
else:
dfig['palette'] = color #ex. "Magma256"
dfig['x'] = deb2
dfig['y'] = deb1
dfig['dw'] = bout2
dfig['dh'] = bout1
dfig['legend'] = label
dfig.update(dfigdic)
p.image(**dfig)
npkd.bokeh_fig = dfig
npkd.bokeh_plot = p
elif npkd.dim == 2 and not image:
# When image is False, contour plots are generated for display.
fig, ax = plt.subplots(figsize=(9,9)) #The figure is created as if it was a classical matplotlib figure
z1lo, z1up, z2lo, z2up = parsezoom(npkd,zoom)
print(z1lo, z1up, z2lo, z2up)
dbk['y_axis_type'] = npkd.axis1.units[npkd.axis1.currentunit].scale # set unit scale (log / linear) for bokeh
dbk['y_range'] = (npkd.axis1.itoc(z1lo), npkd.axis1.itoc(z1up))
dbk['x_axis_type'] = npkd.axis2.units[npkd.axis2.currentunit].scale # set unit scale (log / linear) for bokeh
dbk['x_range'] = (npkd.axis2.itoc(z2lo), npkd.axis2.itoc(z2up))
npkd.display(scale=scale, autoscalethresh=autoscalethresh, absmax=absmax, show=False, new_fig = False, axis=axis,
zoom=zoom, title=None, figure=ax, color=color, mpldic=mpldic)
if npkd.axis1.units[npkd.axis1.currentunit].reverse:
ax.invert_yaxis()
if npkd.axis2.units[npkd.axis2.currentunit].reverse:
ax.invert_xaxis()
if xlabel == "_def_":
xlabel = npkd.axis2.currentunit
if ylabel == "_def_":
ylabel = npkd.axis1.currentunit
if xlabel is not None:
dbk['x_axis_label'] = xlabel
if ylabel is not None:
dbk['y_axis_label'] = ylabel
dbk.update(dbkdic)
xs, ys, col = get_contour_data(ax)
if redraw:
npkd.bokeh_fig['xs'] = xs
npkd.bokeh_fig['ys'] = ys
else:
p = bk.figure(**dbk)
xs, ys, col = get_contour_data(ax)
dfig = {}
dfig['xs'] = xs
dfig['ys'] = ys
dfig['color'] = col
dfig.update(dfigdic)
p.multi_line(**dfig)
npkd.bokeh_fig = dfig
npkd.bokeh_plot = p
del fig, ax
if show:
bk.show(npkd.bokeh_plot)
return npkd
NPKData_plugin("bokeh", bokeh_display)