M*N layout of Bokeh + Holoviews plots - bokeh

I've N plots and i want to show the plots in M*N layout where M=1,2,3,.....
import holoviews as hv
from bokeh.layouts import layout as bk_lyout
from bokeh.layouts import row, column
from bokeh.models import (HoverTool, Panel, CustomJS)
from bokeh.models.widgets import (Tabs, Select, Button)
from bokeh.models.widgets.inputs import AutocompleteInput
from dask import dataframe as dd
from bokeh.core.enums import SizingMode
from holoviews.operation.datashader import datashade
def get_vmap(x, y, label=''):
if x not in cols or y not in cols:
return None
hover = HoverTool(tooltips=[('x-value', '#' + x + '{%F %H:%M:%Ss %6Nms}'),
('y-value', '$y')],
formatters={'#timestamp': 'datetime'})
curve_generated = curve(x, y, label=label)
vmap = datashade(curve_generated, normalization='linear').opts(width=400, height=400)
range_stream = hv.streams.RangeX(source=curve_generated)
filtered_zoom = curve_generated.apply(xrange_filter, streams=[range_stream])
hover_enabled = filtered_zoom.opts(tools=[hover])
# hover_enabled = hv.util.Dynamic(aggregate(curve_generated, width=50, height=50), operation=hv.QuadMesh).opts(tools=[hover], alpha=0, hover_alpha=0.1)
return vmap * hover_enabled
plot_layout = column(children=[])
def modify_doc(doc):
plots = []
for key, value in PLOT_INFO.items():
overlay_plots = []
for carrier in range(7):
car_value = value.format(carrier)
plot_label = "{}-Carrier_{}".format(key.split("_vs_")[1], carrier)
carrier_plot = get_vmap('timestamp', car_value, label=plot_label)
if carrier_plot is not None:
print('\n Found plot for Carrier {}, {}'.format(carrier, carrier_plot))
overlay_plots.append(carrier_plot)
overlaid_plot = None
for plot in overlay_plots:
overlaid_plot = overlaid_plot * plot if overlaid_plot is not None else plot
#overlaid_plot = hv.Overlay(overlay_plots).collate()
if overlaid_plot is not None:
try:
hv_overlay_plot = renderer.get_plot(overlaid_plot, doc)
hv_overlay_plot = bk_lyout([[hv_overlay_plot.state]], sizing_mode='fixed')
select.js_link('value', hv_overlay_plot, 'sizing_mode')
plots.append(hv_overlay_plot)
plot_layout.children.append(hv_overlay_plot)
except Exception as e:
print(e)
tab = Panel(child=plot_layout, title='Interactive Dashboard')
Here plot_layout.children has list of plots and i want to show them in M*N. for example 10 plots, 2 in each row and user should have option to modify it like select 5 plots then 2 rows.

Related

Highlight point in scatterplot when hovering over image

I'm trying to link a grid of images to a scatterplot, so that the right scatterplot point is highlighted when hovering over the corresponding image.
Problem is that when registering DOM events iteratively, they seem to get overwritten, and as a result only the last point of the scatterplot gets highlighted. How can I register independent events for each image.
See reproducible examples below.
import io
import random
import ipywidgets as ipw
from ipyevents import Event
import numpy as np
import PIL
from PIL import Image as PILImage
from bqplot import LinearScale, Figure, Axis, Scatter
# Random ipywidget image generator
def random_image():
arr = np.random.randint(255, size=(200, 200, 3), dtype=np.uint8)
img = PILImage.fromarray(arr)
with io.BytesIO() as fileobj:
img.save(fileobj, 'PNG')
img_b = fileobj.getvalue()
img_w = ipw.Image(value=img_b)
return img_w
# Create data
data = [{'x': idx, 'y': random.randint(1,10), 'image': random_image()}
for idx in range(1,6)]
# Create scatter plot
xs = LinearScale()
ys = LinearScale()
xa = Axis(scale=xs)
ya = Axis(scale=ys, orientation='vertical')
points = Scatter(x=[d['x'] for d in data],
y=[d['y'] for d in data],
scales={'x': xs, 'y': ys})
highlighted_point = Scatter(x=[-1000], y=[-1000], # Dummy point out of view
scales={'x': xs, 'y': ys},
preserve_domain={'x': True, 'y': True},
colors=['red'])
fig = Figure(marks=[points, highlighted_point], axes=[xa,ya])
# Add DOM events to images
img_list = []
for d in data:
img = d['image']
event = Event(source=img, watched_events=['mouseenter'])
def handle_event(event):
highlighted_point.x = [d['x']]
highlighted_point.y = [d['y']]
event.on_dom_event(handle_event)
img_list.append(img)
images = ipw.HBox(img_list)
ipw.VBox([fig, images])
I found a way to do it using functools.partial
import functools
# Event handler
def handle_event(idx, event):
highlighted_point.x = [data[idx]['x']]
highlighted_point.y = [data[idx]['y']]
# Add DOM events to images
img_list = []
for idx, d in enumerate(data):
img = d['image']
event = Event(source=img, watched_events=['mouseenter'])
event.on_dom_event(functools.partial(handle_event, idx))
img_list.append(img)
images = ipw.HBox(img_list)
ipw.VBox([fig, images])

Bokeh: How to show both color and marker type in legend

I want to plot legend that shows both line color and marker type when using boekh package in jupyter notebook.
I have many lines in one plot. To distinguish them, I tried my best to distinguish them by evenly distributing their color in the color space. However, when the number of lines reaches e.g. 9, the color of some lines are quite similar. So, I want to add different marker types on top of different colors so that when two lines are similar in color, they have different marker type.
It was straight-forward with matplotlib, but not straight-forward with bokeh. Below is the code I have now that can only plot legend with color.
import pandas as pd
import numpy as np
import math
import matplotlib.pyplot as plt
%matplotlib inline
from bokeh.plotting import figure, show, ColumnDataSource, save, output_notebook, output_file, reset_output
from bokeh.io import export_svgs,export_png
from bokeh.models import HoverTool, Legend
from bokeh.layouts import gridplot
import colorsys # needed for generating N equally extinguishable colors
from operator import add # needed for add lists
d = {'Sex': ['male', 'male','male','male', 'male','male','female','female','female','female','female','female'], 'age': [20, 20,20, 25,25,25,20, 20,20,25,25,25], 'working_hours': [20,30,40,20,30,40,20,30,40,20,30,40],'income': [1000, 2000,3000,1500, 2500,3500,1100, 2100,3100,1300, 2300,3300] }
values = pd.DataFrame(data=d)
x_var = 'working_hours'
x_var_dimension = 'H'
y_var = 'income'
y_var_dimension = 'Dollars'
hover = HoverTool(tooltips=[("data (x,y)", "(#x, #y)")])
TOOLS=[hover]
p= figure(width=1200, height=600,tools=TOOLS, x_axis_type='linear', x_axis_label='%s [%s]'%(x_var, x_var_dimension),y_axis_label='%s [%s]'%(y_var, y_var_dimension))
nr_expressions_row_col=9
figs_array_row_col = []
figs_row_row_col=[]
legend_its_row_col = []
legend_its_row_col_renderer = []
loop_count = 0;
for key, group in values.groupby(['Sex']):
for key_sub1, group_sub1 in group.groupby(['age']):
loop_count+=1
#print type(key)
#print group_sub1
#print count
#hover = HoverTool(tooltips=[("data (x,y)", "($x, $y)")])
x_data = group_sub1[x_var].values;
y_data = group_sub1[y_var].values
(color_r,color_g,color_b) = colorsys.hsv_to_rgb(loop_count*1.0/nr_expressions_row_col, 1, 1)
plot_row_col_line = p.line(x_data, y_data,line_color=(int(255*color_r),int(255*color_g),int(255*color_b)))
legend_its_row_col.append(("%s %s"%(key,key_sub1), [plot_row_col_line]))
legend_row_col = Legend(items = legend_its_row_col, location=(0,0))
legend_row_col.click_policy = 'hide'
legend_row_col.background_fill_alpha = 0
p.add_layout(legend_row_col, 'left')
figs_row_row_col.append(p)
figs_array_row_col.append(figs_row_row_col)
grid_row_col = gridplot(figs_array_row_col)
reset_output()
output_notebook()
show(grid_row_col)
What I can get with my code is:
What I want is:
This should give the result you want, I've added a cyclic list with all the markers that can be used with p.scatter and it takes another marker every iteration. After this I add it with the line glyph to the legend dictionary.
#!/usr/bin/python3
import pandas as pd
import numpy as np
import math
from bokeh.plotting import figure, show, ColumnDataSource, save, output_file, reset_output
from bokeh.models import HoverTool, Legend
from bokeh.layouts import gridplot
import colorsys # needed for generating N equally extinguishable colors
from itertools import cycle
d = {'Sex': ['male', 'male','male','male', 'male','male','female','female','female','female','female','female'], 'age': [20, 20,20, 25,25,25,20, 20,20,25,25,25], 'working_hours': [20,30,40,20,30,40,20,30,40,20,30,40],'income': [1000, 2000,3000,1500, 2500,3500,1100, 2100,3100,1300, 2300,3300] }
values = pd.DataFrame(data=d)
x_var = 'working_hours'
x_var_dimension = 'H'
y_var = 'income'
y_var_dimension = 'Dollars'
hover = HoverTool(tooltips=[("data (x,y)", "(#x, #y)")])
TOOLS=[hover]
p= figure(width=1200, height=600,tools=TOOLS, x_axis_type='linear', x_axis_label='%s [%s]'%(x_var, x_var_dimension),y_axis_label='%s [%s]'%(y_var, y_var_dimension))
nr_expressions_row_col=9
figs_array_row_col = []
figs_row_row_col=[]
legend_its_row_col = []
legend_its_row_col_renderer = []
loop_count = 0;
markers = ['circle', 'square', 'triangle', 'asterisk', 'circle_x', 'square_x', 'inverted_triangle', 'x', 'circle_cross', 'square_cross', 'diamond', 'cross']
pool = cycle(markers)
for key, group in values.groupby(['Sex']):
for key_sub1, group_sub1 in group.groupby(['age']):
loop_count+=1
x_data = group_sub1[x_var].values;
y_data = group_sub1[y_var].values
(color_r,color_g,color_b) = colorsys.hsv_to_rgb(loop_count*1.0/nr_expressions_row_col, 1, 1)
plot_row_col_line = p.line(x_data, y_data,line_color=(int(255*color_r),int(255*color_g),int(255*color_b)))
plot_row_col_glyph = p.scatter(x_data, y_data, color=(int(255*color_r),int(255*color_g),int(255*color_b)), size=10, marker=next(pool))
legend_its_row_col.append(("%s %s"%(key,key_sub1), [plot_row_col_line, plot_row_col_glyph]))
legend_row_col = Legend(items = legend_its_row_col, location=(0,0))
legend_row_col.click_policy = 'hide'
legend_row_col.background_fill_alpha = 0
p.add_layout(legend_row_col, 'left')
figs_row_row_col.append(p)
figs_array_row_col.append(figs_row_row_col)
grid_row_col = gridplot(figs_array_row_col)
reset_output()
show(grid_row_col)

Adding interaction to heat map to show another bokeh plot based on the selection

I'm trying to add interaction to heatmap(using rect) using CustomJS to show another bokeh plot based on the selected value.
This is what I've tried
heat_map_df_stack = pd.DataFrame(heat_map_df.stack(), columns=['rate']).reset_index()
....
issue_heat_map = figure(title="",
x_range=issues, y_range=list(reversed(products)),
x_axis_location="above", plot_width=400, plot_height=400,
tools=TOOLS, toolbar_location='below',
tooltips=[('Product & Issue Id', '#Product #Issue'), ('Issue Count', '#rate')],
name='issue_heat_map')
....
issue_heat_map.rect(x="Issue", y="Product", width=1, height=1,
source=heat_map_df_stack,
fill_color={'field': 'rate', 'transform': mapper},
line_color=None)
....
taptool = issue_heat_map.select(type=TapTool)
taptool.callback = CustomJS(args = dict(source = ""), code =
"""
console.log('test')
console.log(cb_obj)
var inds = cb_obj.selected;
window.alert(inds);
""")
On click of the rect or selection, nothing is happening now.
[Edit] : I updated the above code. Now I'm able to see console log and alert, but have no clue on how to get selected value from the heat map.
Here is a version using the bokeh server. The code is a adaption of the heatmap example from the bokeh gallery.
from math import pi
import pandas as pd
import numpy as np
from bokeh.io import curdoc
from bokeh.models import LinearColorMapper, BasicTicker, PrintfTickFormatter, ColorBar
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.layouts import gridplot
from bokeh.sampledata.unemployment1948 import data
data['Year'] = data['Year'].astype(str)
data = data.set_index('Year')
data.drop('Annual', axis=1, inplace=True)
data.columns.name = 'Month'
years = list(data.index)
months = list(data.columns)
# reshape to 1D array or rates with a month and year for each row.
df = pd.DataFrame(data.stack(), columns=['rate']).reset_index()
source = ColumnDataSource(df)
# this is the colormap from the original NYTimes plot
colors = ["#75968f", "#a5bab7", "#c9d9d3", "#e2e2e2", "#dfccce", "#ddb7b1", "#cc7878", "#933b41", "#550b1d"]
mapper = LinearColorMapper(palette=colors, low=df.rate.min(), high=df.rate.max())
TOOLS = "hover,save,pan,box_zoom,reset,wheel_zoom, tap"
p = figure(title="US Unemployment ({0} - {1})".format(years[0], years[-1]),
x_range=years, y_range=list(reversed(months)),
x_axis_location="above", plot_width=900, plot_height=400,
tools=TOOLS, toolbar_location='below',
tooltips=[('date', '#Month #Year'), ('rate', '#rate%')])
p.grid.grid_line_color = None
p.axis.axis_line_color = None
p.axis.major_tick_line_color = None
p.axis.major_label_text_font_size = "5pt"
p.axis.major_label_standoff = 0
p.xaxis.major_label_orientation = pi / 3
heatmap = p.rect(x="Year", y="Month", width=1, height=1,
source=source,
fill_color={'field': 'rate', 'transform': mapper},
line_color=None)
color_bar = ColorBar(color_mapper=mapper, major_label_text_font_size="5pt",
ticker=BasicTicker(desired_num_ticks=len(colors)),
formatter=PrintfTickFormatter(format="%d%%"),
label_standoff=6, border_line_color=None, location=(0, 0))
p.add_layout(color_bar, 'right')
# Adding the tap interaction + plot
other_source = ColumnDataSource({'x': range(10), 'y': range(10)})
other_plot = figure(title="Other Plot")
other_line = other_plot.line(x='x', y='y', source=other_source)
def update(attr, old, new):
if not old:
old = [1]
if new:
other_source.data.update(y=np.array(other_source.data['y'])/old[0]*new[0])
source.selected.on_change('indices', update)
curdoc().add_root(gridplot([[p, other_plot]]))
The important part are the last few lines, where I set up the second plot and add the update function to change the slope of the line in the second plot according to the selected rect from the heatmap.

push_notebook does not update bokeh chart

It is kind of a complex example, but I desperately hope to get help...
I'm using jupyter-notebook 5.2.0, bokeh version is 0.12.9 and ipywidgets is 7.0.1.
Here is my DataFrame df:
import numpy as np
import pandas as pd
import datetime
import string
start = int(datetime.datetime(2017,1,1).strftime("%s"))
end = int(datetime.datetime(2017,12,31).strftime("%s"))
# set parameters of DataFrame df for simualtion
size, numcats = 100,10
rints = np.random.randint(start, end + 1, size = size)
df = pd.DataFrame(rints, columns = ['zeit'])
df["bytes"] = np.random.randint(5,20,size=size)
df["attr1"] = np.random.randint(5,100,size=size)
df["ind"] = ["{}{}".format(i,j) for i in string.ascii_uppercase for j in string.ascii_uppercase][:len(df)]
choices = list(string.ascii_uppercase)[:numcats]
df['who']= np.random.choice(choices, len(df))
df["zeit"] = pd.to_datetime(df["zeit"], unit='s')
df.zeit = df.zeit.dt.date
df.sort_values('zeit', inplace = True)
df = df.reset_index(drop=True)
df.head(3)
Now, let's create a bar plot, also using hover tool:
from bokeh.io import show, output_notebook, push_notebook
from bokeh.models import ColumnDataSource, HoverTool
from bokeh.plotting import figure
import ipywidgets as widgets
output_notebook()
# setup figure
hover = HoverTool(tooltips=[
("index", "$index"),
("ind", "#ind"),
("who", "#who"),
("bytes", "#bytes"),
("attr1", "#attr1"),
])
fig = figure(x_range=list(df.ind), plot_height=250, title="Test Bars",
toolbar_location=None, tools=[hover])
x = fig.vbar(x="ind", top="bytes", width=0.9, source=ColumnDataSource(df))
h=show(fig, notebook_handle=True)
I'm using a ipywidgets.widgets.SelectionRangeSlider to select a range of dates:
import ipywidgets as widgets
# create slider
dates = list(pd.date_range(df.zeit.min(), df.zeit.max(), freq='D'))
options = [(i.strftime('%d.%m.%Y'), i) for i in dates]
index = (0, len(dates)-1)
myslider = widgets.SelectionRangeSlider(
options = options,
index = index,
description = 'Test',
orientation = 'horizontal',
layout={'width': '500px'}
)
def update_source(df, start, end):
x = df[(df.zeit >= start) & (df.zeit < end)]
#data = pd.DataFrame(x.groupby('who')['bytes'].sum())
#data.sort_values(by="bytes", inplace=True)
#data.reset_index(inplace=True)
#return data
return x
def gui(model, bars):
def myupdate(control1):
start = control1[0].date()
end = control1[1].date()
#display(update_source(model, start, end).head(4))
data = update_source(model, start, end)
return myupdate
widgets.interactive(gui(df, x), control1 = myslider)
The problem is, I can't get an update to the graph from the widget:
x.data_source = ColumnDataSource(update_source(df, myslider.value[0].date(), myslider.value[1].date()))
push_notebook(handle=h)
At least, it does something with the plot, as hover is not working anymore...
What am I missing? Or is this a bug?
Thanks for any help
Markus
Figured out how to do it using bokeh: https://github.com/bokeh/bokeh/issues/7082, but unfortunately it only works sometimes...
Best to use CDSViewer.

Patch glyph not updated when using multiple ColumnDataSources in bokeh app

I am trying to use the bokeh server to plot a time series together with a shaded percentile band around, and this, since bokeh does not support the fill_between function from matplotlib, requires the construction of a patch object of double dimension. Hence, I need two ColumnDataSources to hold the data. However, only the first curve is rendered correctly when the data changes. Although the data_source of the GlyphRenderer is updated, the figure does not change. I use bokeh 0.12.3, and have tried with several servers and browsers. A complete, and reasonably minimal example:
import numpy as np
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.layouts import column
from bokeh.io import curdoc
from bokeh.models.widgets import Select
class AppData:
def __init__(self, n):
self.p_source = None
self.c_source = None
self.x = np.linspace(0, 10, 20)
self.n = n
self.ys = [np.sin(self.x) - i for i in range(self.n)]
self.line = None
self.patch = None
def update_module(self, a, b):
assert b - a == 5
p_data = dict() if self.p_source is None else self.p_source.data
c_data = dict() if self.c_source is None else self.c_source.data
ys = [self.ys[j] for j in range(a, b)]
if "x" not in c_data:
c_data["x"] = self.x
p_data["x"] = c_data["x"].tolist() + c_data["x"][::-1].tolist()
n_r = len(ys[0])
n_p = 2*n_r
if "ys" not in p_data:
p_data["ys"] = np.empty((n_p))
p_data["ys"][:n_r] = ys[0]
p_data["ys"][n_r:] = np.flipud(ys[-1])
c_data["y"] = ys[2]
if self.p_source is None:
self.p_source = ColumnDataSource(data=p_data)
else:
self.p_source.data.update(p_data)
if self.c_source is None:
self.c_source = ColumnDataSource(data=c_data)
else:
self.c_source.data.update(c_data)
if self.line is not None:
print(max(self.line.data_source.data["y"]))
print(max(self.patch.data_source.data["ys"])) # The value changes, but the figure does not!
# initialize
app_data = AppData(10)
app_data.update_module(4, 4 + 5)
s1 = figure(width=500, plot_height=125, title=None, toolbar_location="above")
app_data.line = s1.line("x", "y", source=app_data.c_source)
app_data.patch = s1.patch("x", "ys", source=app_data.p_source, alpha=0.3, line_width=0)
select = Select(title="Case", options=[str(i) for i in range(5)], value="4")
def select_case(attrname, old, new):
a = int(select.value)
app_data.update_module(a, a + 5)
select.on_change('value', select_case)
layout = column(select, s1)
curdoc().add_root(layout)
curdoc().title = "Example of patches not being updated"
I am certainly not very experienced in using bokeh, so I could very well be using the system wrong. However, any help on this matter would be of great help!

Resources