bokeh hover multiline with datetime axis - python

I want to create a multiline Bokeh plot with datetime axis and a hover tool that shows the datetime of the data point. This should be supported and I have tried to obtain the intended behaviour in two ways:
Use hover.formatters to format the x-value. This has no effect on the plot.
Add a description variable with the correctly formatted date/time values. This results in a hover tool where all date/time values are displayed in a list for each point.
I have included a smaller example of my code that illustrates my approach and the result. It is used in conjunction with a checkboxgroup that updates the data. This is why a new ColumnDataSource is made from the dataframe.
import pandas as pd
import numpy as np
from bokeh.io import output_file, show
from bokeh.plotting import figure
from bokeh.models import HoverTool, ColumnDataSource
from bokeh.palettes import Spectral4
from bokeh.layouts import column
#output_file("demo.html")
available_quant = ["LACTIC_ACID", "GLUCOSE", "XYLOSE", "FORMIC_ACID"]
quant_legend = ["Lactic acid", "Glucose", "Xylose", "Formic acid"]
Create a dataframe with 4 quantities and the time
datelist = pd.date_range(end = pd.datetime.today(), periods=100).tolist()
desc = datelist
for i, date in enumerate(datelist):
desc[i] = str(date)
RT_x = np.linspace(-5, 5, num=100)
lactic = RT_x**2
data = {'time': datelist, 'desc': desc, 'LACTIC_ACID': RT_x**2 + 2, 'GLUCOSE': RT_x**2, 'XYLOSE': RT_x**2 - 2, 'FORMIC_ACID': RT_x**2 - 4}
df = pd.DataFrame.from_dict(data)
df['time'] = pd.to_datetime(df['time'], format = "%Y-%m-%d %H:%M:%S")
Copy the relevant data to a columndatasource
substance_colors = Spectral4
quant_to_plot = available_quant
xs = []
ys = []
xsprint = []
colors = []
labels = []
for i, substance in enumerate(quant_to_plot):
xs.append(list(df['time']))
ys.append(list(df[substance]))
xsprint.append(list(df['desc']))
index = available_quant.index(substance)
colors.append(substance_colors[index])
labels.append(quant_legend[index])
new_src = ColumnDataSource(data={'x': xs, 'y': ys, 'desc': xsprint, 'color': colors, 'label': labels})
Make the first plot using hover.formatters
p = figure(plot_width=800, plot_height=400, x_axis_type="datetime", title = 'Demo', x_axis_label = 'Time', y_axis_label = 'c [g/mL]')
p.multi_line('x','y', color = 'color', legend = 'label', source = new_src)
hover = HoverTool(tooltips=[('Type','#label'),
('Time','$x'),
('Conc','$y')],
formatters={'Time': 'datetime'},
mode = 'mouse',
line_policy='next')
p.add_tools(hover)
p.legend.location = "top_left"
p.legend.orientation = "horizontal"
Make second plot using description variable
p2 = figure(plot_width=800, plot_height=400, x_axis_type="datetime", title = 'Demo', x_axis_label = 'Time', y_axis_label = 'c [g/mL]')
p2.multi_line('x','y', color = 'color', legend = 'label', source = new_src)
hover = HoverTool(tooltips=[('Type','#label'),
('Time','#desc'),
('Conc','$y')],
mode = 'mouse',
line_policy='nearest')
p2.add_tools(hover)
mylayout = column(p, p2)
show(mylayout)
Am I missing something trivial? I am running Bokeh 0.13.0 and python 3.6.4.

The first approach works with the following modification of the hovertool:
hover = HoverTool(tooltips=[('Type','#label'),
('Time','$x{%F}'),
('Conc','$y')],
formatters={'$x': 'datetime'},
mode = 'mouse',
line_policy='nearest')

Related

Python: how to update bokeh patches field?

I am working with bokeh and I would like to update the field value of a patch.
First I conver a geodataframe gdf into a GeoJSONDataSource
from bokeh.models import LogColorMapper
from bokeh.palettes import Viridis256 as palette
from bokeh.models.glyphs import Patch
from bokeh.models import GeoJSONDataSource, Select
geo_gdf = GeoJSONDataSource(geojson=gdf.to_json())
w = 200
h = 200
field = 'col1' ## choose col1 as field to plot
vmin = min(gdf[field])
vmax = max(gdf[field])
## create plot
p = figure(plot_width=w,plot_height=h)
color_mapper = LogColorMapper(palette=palette, low=vmin, high=vmax)
## Patches definition
grid=p.patches('xs', 'ys', source = geo_source2, name="grid",
fill_color={'field': field, 'transform': color_mapper},
fill_alpha=0.5, line_color="gray", line_width=2)
I would like to change the field by selecting an option
select = Select(title="Options", options = ['Col1', 'Col2'], value = 'Col1')
def update_plot(attrname, old, new):
option = select.value
if option == 'Col1':
p.field = 'Col1'
if option == 'Col2':
p.field = 'Col2'
select.on_change('value', update_plot)
curdoc().add_root(select)
field is not a property of the glyph, it is a specification for how a particular property (e.g. fill_color) should be handled. You will need to update it the same way you set it:
grid.glyph.fill_color = {'field': option, 'transform': color_mapper}

Change the appearance of points on the map on SVG pandas_bokeh

I'm new here. I would like to know what are the possibilities of changing the appearance of points in pandas_bokeh or other libraries allowing to create dashboards with maps (I have an icon in SVG). I want to change icon for variable my_hover.tooltips = [('SIEC', '#siec')],
I tried running Bokeh with hovertool_string on chart. But I don't know, how to change it on the map.
This is example what i want
from bokeh.plotting import figure, show, output_file
from bokeh.tile_providers import CARTODBPOSITRON
from bokeh.io import show, output_file
from bokeh.models import Plot, Range1d, MultiLine, Circle, HoverTool, TapTool, BoxSelectTool
from bokeh.models.graphs import from_networkx, NodesAndLinkedEdges, EdgesAndLinkedNodes
from bokeh.palettes import Spectral4
from bokeh.models import ColumnDataSource, CDSView, GroupFilter
palette = itertools.cycle(sns.color_palette())
colors = itertools.cycle(palette)
lista_kolorow = [
"#000000", "#FFFF00", "#1CE6FF", "#FF34FF", "#FF4A46", "#008941", "#006FA6", "#A30059",
"#FFDBE5", "#7A4900", "#0000A6", "#63FFAC", "#B79762", "#004D43", "#8FB0FF", "#997D87",
"#5A0007", "#809693", "#FEFFE6", "#1B4400", "#4FC601", "#3B5DFF", "#4A3B53", "#FF2F80",
"#61615A", "#BA0900", "#6B7900", "#00C2A0", "#FFAA92", "#FF90C9", "#B903AA", "#D16100",
"#DDEFFF", "#000035", "#7B4F4B", "#A1C299", "#300018", "#0AA6D8", "#013349", "#00846F",
"#372101", "#FFB500", "#C2FFED", "#A079BF", "#CC0744", "#C0B9B2", "#C2FF99", "#001E09",
"#00489C", "#6F0062", "#0CBD66", "#EEC3FF", "#456D75", "#B77B68", "#7A87A1", "#788D66",
"#885578", "#FAD09F", "#FF8A9A", "#D157A0", "#BEC459", "#456648", "#0086ED", "#886F4C",
"#34362D", "#B4A8BD", "#00A6AA", "#452C2C", "#636375", "#A3C8C9", "#FF913F", "#938A81",
"#575329", "#00FECF", "#B05B6F", "#8CD0FF", "#3B9700", "#04F757", "#C8A1A1", "#1E6E00",
"#7900D7", "#A77500", "#6367A9", "#A05837", "#6B002C", "#772600", "#D790FF", "#9B9700",
"#549E79", "#FFF69F", "#201625", "#72418F", "#BC23FF", "#99ADC0", "#3A2465", "#922329",
"#5B4534", "#FDE8DC", "#404E55", "#0089A3", "#CB7E98", "#A4E804", "#324E72", "#6A3A4C"
]
## tworzenie mapy
output_file("tile.html")
source = ColumnDataSource(
data=dict(lat=result['LATITUDE'].tolist(),
lon=result['LONGITUDE'].tolist(),
merkor_x=result['merkor_x'].tolist(),
merkor_y=result['merkor_y'].tolist(),
siec=result['SIEC'].tolist(),
adres=result['ADRES_CZYSTY'].tolist()))
dict_source = {}
for i in np.arange(0,len(result['SIEC'].unique())):
dict_source["source_" + result['SIEC'].unique()[i]] = ColumnDataSource(
data=dict(lat=result[result["SIEC"]== result['SIEC'].unique()[i]]['LATITUDE'].tolist(),
lon=result[result["SIEC"]== result['SIEC'].unique()[i]]['LONGITUDE'].tolist(),
merkor_x=result[result["SIEC"]== result['SIEC'].unique()[i]]['merkor_x'].tolist(),
merkor_y=result[result["SIEC"]== result['SIEC'].unique()[i]]['merkor_y'].tolist(),
siec=result[result["SIEC"]== result['SIEC'].unique()[i]]['SIEC'].tolist(),
adres=result[result["SIEC"]== result['SIEC'].unique()[i]]['ADRES_CZYSTY'].tolist()))
dict_view = {}
for i in np.arange(0,len(result['SIEC'].unique())):
dict_view[result['SIEC'].unique()[i]+ '_view'] = CDSView(source=list(dict_source.values())[i])
common_figure_kwargs = {
'plot_width': 1800,
'plot_height' : 1500,
'x_axis_label': 'Points'
}
dict_common_circle_kwargs = {}
for i in np.arange(0,len(result['SIEC'].unique())):
dict_common_circle_kwargs["common_circle_kwargs_" + result['SIEC'].unique()[i]] = {
'x': 'merkor_x',
'y': 'merkor_y',
'source': list(dict_source.values())[i],
'size': 3,
'alpha': 0.7}
dict_common_shop_kwargs = {}
for i in np.arange(0,len(result['SIEC'].unique())):
dict_common_shop_kwargs["common_" + result['SIEC'].unique()[i] +"_kwargs"] = {
'view': list(dict_view.values())[i],
'color': lista_kolorow[i],
'legend': result['SIEC'].unique()[i]}
mapa = figure(**common_figure_kwargs, x_range=(1558321.476598351, 2694056.6889853813), y_range=(6274531.544317098, 7331682.201156591),
x_axis_type="mercator", y_axis_type="mercator")
mapa.add_tile(CARTODBPOSITRON)
for i in np.arange(0,len(result['SIEC'].unique())):
mapa.circle(**list(dict_common_circle_kwargs.values())[i], **list(dict_common_shop_kwargs.values())[i])
mapa.legend.click_policy = 'hide'
mapa.xaxis.axis_label = 'SZEROKOSC'
mapa.yaxis.axis_label = 'DLUGOSC'
mapa.legend.location = "top_left"
my_hover = HoverTool()
my_hover.tooltips = [('SIEC', '#siec'),
('SZEROKOŚĆ', '#lat'),
('DŁUGOŚĆ', '#lon'),
('ADRES', '#adres')]
mapa.add_tools(my_hover)
I do not have any error messages, I just do not know how to do it ...
If you want to replace points on the map with images you can use image_url like in this example (the solution is for Bokeh v1.1.0 as in first place it was not clear to me that you were targeting pandas_bokeh). To fix the size of images use h_units = 'screen', w_units = 'screen'
from bokeh.plotting import figure, show
p = figure(name = 'Doggys', x_range = (2, 18), y_range = (-10, 10))
p.image_url(url = ['https://cdn3.iconfinder.com/data/icons/line/36/dog_head-512.png'], x = [10], y = [0], w = [50], h = [50], h_units = 'screen', w_units = 'screen')
p.image_url(url = ['https://cdn3.iconfinder.com/data/icons/dogs-outline/100/dog-02-512.png'], x = [5], y = [5], w = [50], h = [50], h_units = 'screen', w_units = 'screen')
show(p)

Modify the format of display on hover in Bokeh

I have datetime on x-axis and I was trying to plot it as a datetime, but apparently, according to this Bokeh can only have number axes. Unless that's changed by now, then please let me know.
But I was wondering if there's maybe at least a way to display datetime on hover rather than timestamp (something like 153286000)?
p.select_one(HoverTool).tooltips = [('Datetime', '#x'),('Position', '#y')]
I tried adding .to_datetime() but that didn't work.
You need to set the x_axis_type = "datetime" and use formatters for the datetime like this:
p.select_one(HoverTool).formatters = {'Datetime': 'datetime'}
See full example below (Bokeh v1.1.0). See also Bokeh documentation on tooltips formatting.
import numpy as np
from bokeh.io import output_file, show
from bokeh.models import ColumnDataSource, HoverTool
from bokeh.plotting import figure
from bokeh.sampledata.stocks import AAPL
def datetime(x):
return np.array(x, dtype = np.datetime64)
source = ColumnDataSource(data = {'date' : datetime(AAPL['date'][::10]),
'adj close' : AAPL['adj_close'][::10],
'volume' : AAPL['volume'][::10]})
p = figure(plot_height = 250, x_axis_type = "datetime", sizing_mode = "scale_width")
p.background_fill_color = "#f5f5f5"
p.grid.grid_line_color = "white"
p.xaxis.axis_label = 'Date'
p.yaxis.axis_label = 'Price'
p.axis.axis_line_color = None
p.line(x = 'date', y = 'adj close', line_width = 2, color = '#ebbd5b', source = source)
hover = HoverTool(mode = 'vline')
hover.tooltips = [('date', '#date{%F}'), ('close', '$#{adj close}{%0.2f}'), ('volume', '#volume{0.00 a}')]
hover.formatters = {'date': 'datetime', 'adj close' : 'printf'}
p.add_tools(hover)
show(p)
Result:

How to update Pretext in Bokeh with a Select tool

I have a bokeh plot that updates my plot through a select tool. The select tool contains subjects that update the plot where the values are x='Polarity'and y='Subjectivity'.
Here is a dummy data for what I want:
import pandas as pd
import random
list_type = ['All', 'Compliment', 'Sport', 'Remaining', 'Finance', 'Infrastructure', 'Complaint', 'Authority',
'Danger', 'Health', 'English']
df = pd.concat([pd.DataFrame({'Subject' : [list_type[i] for t in range(110)],
'Polarity' : [random.random() for t in range(110)],
'Subjectivity' : [random.random() for t in range(110)]}) for i in range(len(list_type))], axis=0)
My code for updating the plot looks like this:
options = []
options.append('All')
options.extend(df['Subject'].unique().tolist())
source = ColumnDataSource(df)
p = figure()
r = p.circle(x='Polarity', y='Subjectivity', source = source)
select = Select(title="Subject", options=options, value="All")
output_notebook()
def update_plot(attr, old, new):
if select.value=="All":
df_filter = df.copy()
else:
df_filter = df[df['Subject']==select.value]
source1 = ColumnDataSource(df_filter)
r.data_source.data = source1.data
select.on_change('value', update_plot)
layout = column(row(select, width=400), p)
#show(layout)
curdoc().add_root(layout)
I want to add a 'Pretext' that has a df.describe(), that can update with the plot through the select tool. I tried this by adding these codes but it displays nothing:
stats = PreText(text='', width=500)
t1 = select.value
def update_stats(df, t1):
stats.text = str(df[[t1, select.value+'_returns']].describe())
select.on_change('value', update_plot, update_stats)
layout = column(row(select, width=400), p, stats)
curdoc().add_root(layout)
show(layout)
Anyone know a solution? Thanks!
You don't need two separate function for that, you can just change your original function update_plot to add statement to change the text for PreText as stats.text = str(df_filter.describe()). The function will look as below -
def update_plot(attr, old, new):
if select.value=="All":
df_filter = df.copy()
else:
df_filter = df[df['Subject']==select.value]
source1 = ColumnDataSource(df_filter)
r.data_source.data = source1.data
stats.text = str(df_filter.describe())
Entire code
from bokeh.models.widgets import Select, PreText
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, curdoc
from bokeh.plotting import figure, show
import pandas as pd
import random
list_type = ['All', 'Compliment', 'Sport', 'Remaining', 'Finance', 'Infrastructure', 'Complaint', 'Authority',
'Danger', 'Health', 'English']
df = pd.concat([pd.DataFrame({'Subject' : [list_type[i] for t in range(110)],
'Polarity' : [random.random() for t in range(110)],
'Subjectivity' : [random.random() for t in range(110)]}) for i in range(len(list_type))], axis=0)
options = []
options.append('All')
options.extend(df['Subject'].unique().tolist())
source = ColumnDataSource(df)
p = figure()
r = p.circle(x='Polarity', y='Subjectivity', source = source)
select = Select(title="Subject", options=options, value="All")
#output_notebook()
stats = PreText(text=str(df.describe()), width=500)
def update_plot(attr, old, new):
if select.value=="All":
df_filter = df.copy()
else:
df_filter = df[df['Subject']==select.value]
source1 = ColumnDataSource(df_filter)
r.data_source.data = source1.data
stats.text = str(df_filter.describe())
select.on_change('value', update_plot)
layout = column(row(select, width=400), p, stats)
#show(layout)
curdoc().add_root(layout)

Changing colors on bokeh patches plot real time

I'm trying to create a bokeh plot of the US States, and color each of the state according to some data. Now using this tutorial I managed to create this, but I also want to enhance it, and add a slider to it, to change the values displayed. For example like displaying separate years.
With the help of this tutorial, I managed to add the slider, and the underlying data does change, according to the hover text, but the colors aren't recalculated, and so the visual representation does not match the values.
This is the code I've used, from a Jupyter notebook, so anybody who wants to try can reproduce
from bokeh.io import show, output_notebook
from bokeh.models import (
ColumnDataSource,
HoverTool,
LogColorMapper,
Range1d, CustomJS, Slider
)
from bokeh.palettes import Inferno256 as palette
from bokeh.plotting import figure
from bokeh.layouts import row, widgetbox
from bokeh.sampledata.us_counties import data as counties
from bokeh.sampledata.us_states import data as states
from bokeh.sampledata.unemployment import data as unemployment
import pandas as pd
import random
output_notebook()
palette.reverse()
states_accumulated ={}
available_state_codes = states.keys()
for key, value in counties.items():
state_name = value["state"].upper()
if state_name in states.keys() and "number" not in states[state_name]:
states[state_name]["number"] = key[0]
for key,state in states.items():
state["code"] = key
state_list = []
for key,state in states.items():
state_list.append(state)
unemployment_transf = []
for key,value in unemployment.items():
unemployment_transf.append({
"State":key[0],
"County":key[1],
"Value":value
})
unemp_df = pd.DataFrame(unemployment_transf)
unemp_sum = unemp_df.groupby("State").mean()["Value"]
unemp_sum = unemp_sum.sort_index()
unemp_sum_flat = {key:value for key, value in unemp_sum.items()}
for state in state_list:
state["value"] = unemp_sum_flat[state["number"]]
state_df = pd.DataFrame(state_list)
color_mapper = LogColorMapper(palette=palette)
state_xy = (list(state_df["lons"].values),list(state_df["lats"].values))
max_x = max([max(l) for l in state_xy[0]])
max_y = max([max(l) for l in state_xy[1]])
min_x = min([min(l) for l in state_xy[0]])
min_y = min([min(l) for l in state_xy[1]])
data=dict(
x=state_xy[0],
y=state_xy[1],
name=list(state_df["name"].values),
used = list(state_df["value"].values)
)
data['1999'] = list(state_df["value"].values)
data['2000'] = [random.randrange(0,10) for i in range(len(state_xy[0]))]
source = ColumnDataSource(data)
TOOLS = "pan,wheel_zoom,reset,hover,save"
p = figure(
title="States", tools=TOOLS,
x_axis_location=None, y_axis_location=None
)
p.width=450
p.height = 450
p.x_range= Range1d(-170,-60)
p.y_range = Range1d(min_y-10,max_y+10)
p.grid.grid_line_color = None
renderer = p.patches('x', 'y', source=source,
fill_color={'field': 'used', 'transform': color_mapper},
fill_alpha=0.7, line_color="white", line_width=0.5)
hover = p.select_one(HoverTool)
hover.point_policy = "follow_mouse"
hover.tooltips = [
("Name", "#name"),
("Unemployment rate)", "#used%"),
("(Long, Lat)", "($x, $y)"),
]
callback = CustomJS(args=dict(source=source,plot=p,color_mapper = color_mapper,renderer = renderer), code="""
var data = source.data;
var year = year.value;
used = data['used']
should_be = data[String(year)]
for (i = 0; i < should_be.length; i++) {
used[i] = should_be[i];
}
""")
year_slider = Slider(start=1999, end=2000, value=1999, step=1,
title="year", callback=callback)
callback.args["year"] = year_slider
layout = row(
p,
widgetbox(year_slider),
)
show(layout)
Sample images of the plot:
What I would like to accomplish, is that when I change the slider, the colors on the plot should change. Now I think the JS callback should call some kind of redraw or recalculate, but I haven't found any documentation about it. Is there a way to do this?
append source.change.emit() to the Javascipt code to trigger the change event.
Appending source.trigger("change"); to the CustomJS seems to solve the problem, now as the slider changes, the colors change.

Categories