Adding error bars on top of bar charts in Bokeh - python

I am trying to create bar chart with error bars on top. I looked at the following answer to generate such view. My code works until I do p.line(y_err_x, y_err_y, color="black" ) presumably due to x axis indexing as I get the following error: Unable to get property 'A' of undefined or null reference
What is the appropriate use? Thanks in advance!
from bokeh.io import show, output_notebook
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
output_notebook()
groups= ['A', 'B', 'C', 'D']
counts = [5, 3, 4, 2]
yerr = [1,2,3,4]
source = ColumnDataSource(data=dict(groups=groups, counts=counts))
p = figure(x_range=groups, plot_height=350, toolbar_location=None, title="Values")
p.vbar(x='groups', top='counts', width=0.9, source=source, legend="groups",
line_color='white', fill_color=factor_cmap('groups', palette=["#962980","#295f96","#29966c","#968529"],
factors=groups))
y_err_x = []
y_err_y = []
for px, py, err in zip(groups, counts, yerr):
y_err_x.append((px, px))
y_err_y.append((py - err, py + err))
p.line(y_err_x, y_err_y, color="black" )
p.xgrid.grid_line_color = None
p.legend.orientation = "horizontal"
p.legend.location = "top_center"
show(p)

The technique of that answer is no longer necessary. Error annotations are now built into Bokeh, see the documentation:
https://docs.bokeh.org/en/latest/docs/user_guide/annotations.html#whiskers
and
https://docs.bokeh.org/en/latest/docs/user_guide/annotations.html#bands
Here is a complete example:
from bokeh.io import show, output_file
from bokeh.models import ColumnDataSource, Whisker
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
output_file("error.html")
groups= ['A', 'B', 'C', 'D']
counts = [5, 3, 4, 2]
error = [0.8, 0.4, 0.4, 0.3]
upper = [x+e for x,e in zip(counts, error) ]
lower = [x-e for x,e in zip(counts, error) ]
source = ColumnDataSource(data=dict(groups=groups, counts=counts, upper=upper, lower=lower))
p = figure(x_range=groups, plot_height=350, toolbar_location=None, title="Values", y_range=(0,7))
p.vbar(x='groups', top='counts', width=0.9, source=source, legend="groups",
line_color='white', fill_color=factor_cmap('groups', palette=["#962980","#295f96","#29966c","#968529"],
factors=groups))
p.add_layout(
Whisker(source=source, base="groups", upper="upper", lower="lower", level="overlay")
)
p.xgrid.grid_line_color = None
p.legend.orientation = "horizontal"
p.legend.location = "top_center"
show(p)

Related

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)

Bokeh: Not able to update format of hover tooltip

I'm trying to update the format of an already defined hover tooltip, but I do not observe any change. The change I do in the example below is changing the x-axis between number and time scale ('00:00:00'). The x-axis is updated as expected. Using Bokeh version 0.12.16, mac OS X, Safari browser.
Any hints with respect to what I'm doing wrong is appreciated.
from bokeh.plotting import figure, ColumnDataSource
from bokeh.models import HoverTool, NumeralTickFormatter, AdaptiveTicker
from bokeh.models.widgets import RadioGroup
from bokeh.layouts import row, widgetbox
from bokeh.io import curdoc
def update_axis_format(new):
if new == 0:
format_num = '0'
mantissas= [1,2,5]
else:
format_num = '00:00:00'
mantissas=[3.6, 7.2, 18]
p.xaxis[0].formatter = NumeralTickFormatter(format = format_num)
p.xaxis.ticker = AdaptiveTicker(base = 10, mantissas = mantissas)
p.xgrid.ticker = AdaptiveTicker(base = 10, mantissas = mantissas)
p.tools[0].tooltips[2] = ("x", "#x{{{}}}".format(format_num))
source = ColumnDataSource(data=dict(
x=[10, 2000, 10000, 40000, 50000],
y=[2, 5, 8, 2, 7],
desc=['A', 'b', 'C', 'd', 'E'],
))
hover = HoverTool(tooltips=[
("index", "$index"),
("desc", "#desc"),
("x", "#x")
])
p = figure(plot_width=400, plot_height=400, tools=[hover],
title="Mouse over the dots")
p.circle('x', 'y', size=20, source=source)
xaxis_format = RadioGroup(
labels=["x-axis as number", "x-axis as time"], active=0)
xaxis_format.on_click(update_axis_format)
widget = widgetbox(xaxis_format)
curdoc().add_root(row(widget,p))
The BokehJS code is not sensitive to "internal" (i.e. in place) changes to tooltips. You need to replace the tooltips value entirely. E.g. this simplified code works as expected:
def update_axis_format(new):
if new == 0:
format_num = '0'
mantissas= [1,2,5]
else:
format_num = '00:00:00'
mantissas=[3.6, 7.2, 18]
p.xaxis[0].formatter = NumeralTickFormatter(format = format_num)
p.xaxis.ticker = AdaptiveTicker(base = 10, mantissas = mantissas)
p.xgrid.ticker = AdaptiveTicker(base = 10, mantissas = mantissas)
# replace all of tooltips, not just part
p.tools[0].tooltips = [("x", "#x{{{}}}".format(format_num))]
hover = HoverTool(tooltips=[("x", "#x")])

Bokeh server - How to manipulate a selection in a callback function

I am plotting several patches that are grouped by a category "group" in the data source. What would I like to achieve is the following: By clicking on one patch, not only the patch itself but all patches of the same group should be highlighted as selected.
I found that ColumnDataSource has an attribute selected. However, manipulating this attribute in the callback function does not have the desired effect.
import os
from bokeh.models import ColumnDataSource, Patches
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.io import output_file, curdoc
import pandas as pd
x = [[1,2,4], [3,5,6], [7,9,7], [5,7,6]]
y = [[4,2,1], [6,5,8], [3,9,6], [2,2,1]]
group = ['A', 'A', 'B', 'B']
id = [0,1,2,3]
df = pd.DataFrame(data=dict(x=x, y=y, group=group, id=id))
source = ColumnDataSource(df)
p = figure(tools="tap")
renderer = p.patches('x', 'y', source=source)
# Event handler
def my_tap_handler(attr,old,new):
global source
group_name = source.data['group'][new['1d']['indices'][0]]
group_indices = df['id'][df['group'] == group_name]
source.selected.indices = list(group_indices)
print("source.selected.indices", source.selected.indices)
selected_patches = Patches(fill_color="#a6cee3")
renderer.selection_glyph = selected_patches
# Event
renderer.data_source.on_change("selected", my_tap_handler)
#######################################
# Set up layouts and add to document
curdoc().add_root(row(p, width=800))
You can do the selection in Javascript, but if you really want to do this in Python, here is an example:
import os
from bokeh.models import ColumnDataSource, Patches, CustomJS
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.io import output_file, curdoc
import pandas as pd
def app(doc):
x = [[1,2,4], [3,5,6], [7,9,7], [5,7,6]]
y = [[4,2,1], [6,5,8], [3,9,6], [2,2,1]]
group = ['A', 'A', 'B', 'B']
id = [0,1,2,3]
df = pd.DataFrame(data=dict(x=x, y=y, group=group, id=id))
source = ColumnDataSource(df)
p = figure(tools="tap")
renderer = p.patches('x', 'y', source=source)
def my_tap_handler(attr,old,new):
indices = source.selected.indices
if len(indices) == 1:
group = source.data["group"][indices[0]]
new_indices = [i for i, g in enumerate(source.data["group"]) if g == group]
if new_indices != indices:
source.selected = Selection(indices=new_indices)
selected_patches = Patches(fill_color="#a6cee3")
renderer.selection_glyph = selected_patches
source.on_change("selected", my_tap_handler)
doc.add_root(row(p, width=800))
show(app)

Bokeh Title Spacing

i'm looking for a way to reduce the spacing in a Bokeh plot, this is a HeatMap that I've made in Bokeh:
I want the title to be more close to the plot but I haven't found the way to do this. ¿Any ideas on how to solve this?
Here is my code:
from bokeh.io import output_file
from bokeh.io import show
from bokeh.models import (
ColumnDataSource,
HoverTool,
LinearColorMapper
)
from bokeh.plotting import figure
output_file('test.html', mode='inline')
source = ColumnDataSource(RandomData)
TOOLS = "hover,save"
# Creating the Figure
HM = figure(title="HeatMap",
x_range=[str(i) for i in range(1,32)],
y_range=[str(i) for i in range(1643,1600,-1)]+[str(i) for i in range(6043,6000,-1)],
x_axis_location="above", plot_width=500, plot_height=970,
tools=TOOLS, toolbar_location='right')
# Figure Styling
HM.grid.grid_line_color = None
HM.axis.axis_line_color = None
HM.axis.major_tick_line_color = None
HM.axis.major_label_text_font_size = "7pt"
HM.axis.major_label_text_alpha = 0.5
HM.axis.major_label_standoff = 0
HM.toolbar.logo = None
HM.title.text_font = 'century gothic'
HM.title.text_font_size = '14pt'
HM.title.text_font_style = 'normal'
HM.title.text_color = '#6a94d8'
HM.title_location = 'below'
HM.title.align = 'center'
# Color Mapping
mapper = LinearColorMapper(palette='Blues9', low=RandomData.Total.max(),
high=RandomData.Total.min())
# Creating the Glyphs
HM.rect(x='XData', y="YData", width=1, height=1,source=source,
fill_color={'field': 'Total','transform': mapper},line_alpha=0.05)
show(HM)

Link a Span or Cursor in between plots with Bokeh in Python

I would like a cursor that is linked between to plots in Bokeh. So if I move my cursor on one plot, an equivalent line shows up on an adjacent plot. I haven't figured out how to do it with the built in cursor tool. So my current solution is to draw a line on each plot that shares a source. Then when I hover over either plot, the source is updated.
I have 2 issues with this method:
1. It seems like a workaround
2. Currently the lines are finite length. I would like the line to be infinite, so no matter how the graph is resized, the line runs off the edge. Currently the line I draw is finite. The right way to draw an infinite horizontal line is a Span annotation, but I am having a hard time figuring out how to pass/update the Span location through my callback. See my code below.
from bokeh.io import gridplot, show, output_notebook, output_file
from bokeh.plotting import figure
from bokeh.models import HoverTool, ColumnDataSource, CustomJS, Span
output_notebook()
x = list(range(11))
y1 = x
y2 = [10 - i for i in x]
source = ColumnDataSource({'x0': [0], 'y0': [2], 'x1': [10], 'y1': [2]})
# create a new plot
s1 = figure(width=250, plot_height=250, tools="", title=None)
cr1 = s1.circle(x, y1, size=10, color="navy", alpha=0.5)
sr1 = s1.segment(x0='x0', y0='y0', x1='x1', y1='y1', color='red', alpha=1, line_width=1, source=source, )
sp1 = Span(location=source.data['y0'][0], dimension='width', line_color='green')
s1.renderers.extend([sp1,])
# create another one
s2 = figure(width=250, height=250, title=None)
cr2 = s2.triangle(x, y1, size=10, color="firebrick", alpha=0.5)
sr2 = s2.segment(x0='x0', y0='y0', x1='x1', y1='y1', color='red', alpha=1, line_width=1, source=source, )
# put all the plots in an HBox
p = gridplot([[s1,s2],[]])
code = """
var data = {'x0': [], 'y0': [], 'x1': [], 'y1': []};
var cdata = circle.get('data');
var indices = cb_data.index['1d'].indices;
for (i=0; i < indices.length; i++) {
ind0 = indices[i];
data['x0'].push(0);
data['y0'].push(cdata.y[ind0]);
data['x1'].push(10);
data['y1'].push(cdata.y[ind0]);
}
segment.set('data', data);
"""
callback1 = CustomJS(args={'circle': cr1.data_source, 'segment': sr2.data_source}, code=code)
s1.add_tools(HoverTool(tooltips=None, callback=callback1, renderers=[cr1]))
callback2 = CustomJS(args={'circle': cr2.data_source, 'segment': sr2.data_source}, code=code)
s2.add_tools(HoverTool(tooltips=None, callback=callback2, renderers=[cr2]))
# show the results
show(p)
Thanks to #bigreddot for his answer on the spans. I had tried and not gotten in to work, but figured it out with his hints. The working code is below. I implemented a span in each plot and then edit the location of each.
from bokeh.io import gridplot, show, output_notebook, output_file
from bokeh.plotting import figure
from bokeh.models import HoverTool, ColumnDataSource, CustomJS, Span
output_file('Test.html')
#output_notebook()
x = list(range(11))
y1 = x
y2 = [10 - i for i in x]
# create a new plot
s1 = figure(width=250, plot_height=250, tools="", title=None)
cr1 = s1.circle(x, y1, size=10, color="navy", alpha=0.5)
sp1 = Span(location=source.data['y0'][0], dimension='width', line_color='green', render_mode='css')
s1.renderers.extend([sp1,])
# create another one
s2 = figure(width=250, height=250, title=None)
cr2 = s2.triangle(x, y1, size=10, color="firebrick", alpha=0.5)
sp2 = Span(location=source.data['y0'][0], dimension='width', line_color='green', render_mode='css')
s2.renderers.extend([sp2,])
# put all the plots in an HBox
p = gridplot([[s1,s2],[]])
code = """
var cdata = circle.get('data');
var indices = cb_data.index['1d'].indices;
var sum = 0;
for (i=0; i < indices.length; i++) {
sum += cdata.y[indices[i]];
}
var avg = sum/indices.length
span1.set('location', [avg])
span2.set('location', [avg])
"""
callback1 = CustomJS(args={'circle': cr1.data_source, 'span1': sp1, 'span2': sp2}, code=code)
s1.add_tools(HoverTool(tooltips=None, callback=callback1, renderers=[cr1]))
callback2 = CustomJS(args={'circle': cr2.data_source, 'span1': sp1, 'span2': sp2}, code=code)
s2.add_tools(HoverTool(tooltips=None, callback=callback2, renderers=[cr2]))
# show the results
show(p)

Categories