Interactive floor plan graph using Python Bokeh - python

I am trying to create an interactive floor-plan using Python.
Is there a way to make polygons clickable and then bring them to front in Bokeh?
In the below example:
import json
from bokeh.io import output_notebook, show
from bokeh.models import GeoJSONDataSource
from bokeh.plotting import figure, output_notebook, show
xs_dict = [
[ { 'exterior': [1, 8, 8, 1], 'holes': [] } ],
[ { 'exterior': [5, 8, 8, 5], 'holes': [] } ]
]
ys_dict = [
[ { 'exterior': [2, 2, 10, 10], 'holes': [] } ],
[ { 'exterior': [7, 7, 10, 10], 'holes': [] } ]
]
xs1 = [[[p['exterior'], *p['holes']] for p in mp] for mp in xs_dict]
ys1 = [[[p['exterior'], *p['holes']] for p in mp] for mp in ys_dict]
p = figure(plot_width=300, plot_height=300)
p.multi_polygons(xs=xs1,
ys=ys1, color=['red','green'])
This creates two polygons (outer red and inner green).
The desired behavior is when a user clicks on the green one, the green would come to front and allow user to explore that one?
Eventually, I will have more nested polygons within the green one, so a user can go to multiple depth by selecting a desired polygon. A button to go back to the previous hierarchy would be very useful as well.

If the answer to my comment to the question is "yes", then here's an example that can get you started. You can reset the zoom by clicking on the "Reset" tool button in the tool panel.
from bokeh.io import show
from bokeh.models import TapTool, CustomJS, ColumnDataSource
from bokeh.plotting import figure
base_xs = [0, 1, 1, 0]
base_ys = [0, 0, 1, 1]
def offset(base, d):
return [i + d for i in base]
ds = ColumnDataSource(dict(xs=[[[offset(base_xs, dx)]] for dx in base_xs],
ys=[[[offset(base_ys, dy)]] for dy in base_ys],
color=['red', 'green', 'blue', 'yellow']))
tt = TapTool(behavior='inspect')
p = figure(tools=[tt, 'reset'])
p.multi_polygons('xs', 'ys', color='color', source=ds)
tt.callback = CustomJS(args=dict(x_range=p.x_range, y_range=p.y_range),
code="""\
const {source} = cb_data;
const idx = source.inspected.indices[0];
const {xs, ys} = source.data;
const poly_xs = xs[idx].flat(2);
const poly_ys = ys[idx].flat(2);
x_range.start = Math.min(...poly_xs);
x_range.end = Math.max(...poly_xs);
y_range.start = Math.min(...poly_ys);
y_range.end = Math.max(...poly_ys);
""")
show(p)

Related

How to set order of the nodes in Sankey Diagram Plotly

So i am traying to make a cycle that gives different sankey diagram the thing is due to the plotly optimization the node are in different positions. I will like to set the standard order to be [Formal, Informal, Unemployed, Inactive]
import matplotlib.pyplot as plt
import pandas as pd
import plotly.graph_objects as go
df = pd.read_csv(path, delimiter=",")
Lista_Paises = df["code"].unique().tolist()
Lista_DF = []
for x in Lista_Paises:
DF_x = df[df["code"] == x]
Lista_DF.append(DF_x)
def grafico(df):
df = df.astype({"Source": "category", "Value": "float", "Target": "category"})
def category(i):
if i == "Formal":
return 0
if i == "Informal":
return 1
if i == "Unemployed":
return 2
if i == "Inactive":
return 3
def color(i):
if i == "Formal":
return "#9FB5D5"
if i == "Informal":
return "#E3EEF9"
if i == "Unemployed":
return "#E298AE"
if i == "Inactive":
return "#FCEFBC"
df['Source_cat'] = df["Source"].apply(category).astype("int")
df['Target_cat'] = df["Target"].apply(category).astype("int")
# df['Source_cat'] = LabelEncoder().fit_transform(df.Source)
# df['Target_cat'] = LabelEncoder().fit_transform(df.Target)
df["Color"] = df["Source"].apply(color).astype("str")
df = df.sort_values(by=["Source_cat", "Target_cat"])
Lista_Para_Sumar = df["Source_cat"].nunique()
Lista_Para_Tags = df["Source"].unique().tolist()
Suma = Lista_Para_Sumar
df["out"] = df["Target_cat"] + Suma
TAGS = Lista_Para_Tags + Lista_Para_Tags
Origen = df['Source_cat'].tolist()
Destino = df["out"].tolist()
Valor = df["Value"].tolist()
Color = df["Color"].tolist()
return (TAGS, Origen, Destino, Valor, Color)
def Sankey(TAGS: object, Origen: object, Destino: object, Valor: object, Color: object, titulo: str) -> object:
label = TAGS
source = Origen
target = Destino
value = Valor
link = dict(source=source, target=target, value=value,
color=Color)
node = dict(x=[0, 0, 0, 0, 1, 1, 1, 1], y=[1, 0.75, 0.5, 0.25, 0, 1, 0.75, 0.5, 0.25, 0], label=label, pad=35,
thickness=10,
color=["#305CA3", "#C1DAF1", "#C9304E", "#F7DC70", "#305CA3", "#C1DAF1", "#C9304E", "#F7DC70"])
data = go.Sankey(link=link, node=node, arrangement='snap')
fig = go.Figure(data)
fig.update_layout(title_text=titulo + "-" + "Mujeres", font_size=10, )
plt.plot(alpha=0.01)
titulo_guardar = (str(titulo) + ".png")
fig.write_image("/Users/agudelo/Desktop/GRAFICOS PNUD/Graficas/MUJERES/" + titulo_guardar, engine="kaleido")
for y in Lista_DF:
TAGS, Origen, Destino, Valor, Color = grafico(y)
titulo = str(y["code"].unique())
titulo = titulo.replace("[", "")
titulo = titulo.replace("]", "")
titulo = titulo.replace("'", "")
Sankey(TAGS, Origen, Destino, Valor, Color, titulo)
The expected result should be.
The expected result due to the correct order:
The real result i am getting is:
I had a similar problem earlier. I hope this will work for you. As I did not have your data, I created some dummy data. Sorry about the looooong explanation. Here are the steps that should help you reach your goal...
This is what I did:
Order the data and sort it - used pd.Categorical to set the order and then df.sort to sort the data so that the input is sorted by source and then destination.
For the sankey node, you need to set the x and y positions. x=0, y=0 starts at top left. This is important as you are telling plotly the order you want the nodes. One weird thing is that it sometimes errors if x or y is at 0 or 1. Keep it very close, but not the same number... wish I knew why
For the other x and y entries, I used ratios as my total adds up to 285. For eg. Source-Informal starts at x = 0.001 and y = 75/285 as Source-Formal = 75 and this will start right after that
Based on step 1, the link -> source and destination should also be sorted. But, pls do check.
Note: I didn't color the links, but think you already have achieved that...
Hope this helps resolve your issue...
My data - sankey.csv
source,destination,value
Formal,Formal,20
Formal,Informal, 10
Formal,Unemployed,30
Formal,Inactive,15
Informal,Formal,20
Informal,Informal,15
Informal,Unemployed,25
Informal,Inactive,25
Unemployed,Formal,5
Unemployed,Informal,10
Unemployed,Unemployed,10
Unemployed,Inactive,5
Inactive,Formal,30
Inactive,Informal,20
Inactive,Unemployed,20
Inactive,Inactive,25
The code
import plotly.graph_objects as go
import pandas as pd
df = pd.read_csv('sankey.csv') #Read above CSV
#Sort by Source and then Destination
df['source'] = pd.Categorical(df['source'], ['Formal','Informal', 'Unemployed', 'Inactive'])
df['destination'] = pd.Categorical(df['destination'], ['Formal','Informal', 'Unemployed', 'Inactive'])
df.sort_values(['source', 'destination'], inplace = True)
df.reset_index(drop=True)
mynode = dict(
pad = 15,
thickness = 20,
line = dict(color = "black", width = 0.5),
label = ['Formal', 'Informal', 'Unemployed', 'Inactive', 'Formal', 'Informal', 'Unemployed', 'Inactive'],
x = [0.001, 0.001, 0.001, 0.001, 0.999, 0.999, 0.999, 0.999],
y = [0.001, 75/285, 160/285, 190/285, 0.001, 75/285, 130/285, 215/285],
color = ["#305CA3", "#C1DAF1", "#C9304E", "#F7DC70", "#305CA3", "#C1DAF1", "#C9304E", "#F7DC70"])
mylink = dict(
source = [ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3 ],
target = [ 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7 ],
value = df.value.to_list())
fig = go.Figure(data=[go.Sankey(
arrangement='snap',
node = mynode,
link = mylink
)])
fig.update_layout(title_text="Basic Sankey Diagram", font_size=20)
fig.show()
The output

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)

Highlighting multiple hex_tiles by hovering in bokeh

I try to visualize my data in a hex map. For this I use python bokeh and the corresponding hex_tile function in the figure class. My data belongs to one of 8 different classes, each having a different color. The image below shows the current visualization:
I would like to add the possibility to change the color of the element (and ideally all its class members) when the mouse hovers over it.
I know, that it is somewhat possible, as bokeh themselves provide the following example:
https://docs.bokeh.org/en/latest/docs/gallery/hexbin.html
However, I do not know how to implement this myself (as this seems to be a feature for the hexbin function and not the simple hex_tile function)
Currently I provide my data in a ColumnDataSource:
source = ColumnDataSource(data=dict(
r=x_row,
q=y_col,
color=colors_array,
ipc_class=ipc_array
))
where "ipc_class" describes one of the 8 classes the element belongs to.
For the mouse hover tooltip I used the following code:
TOOLTIPS = [
("index", "$index"),
("(r,q)", "(#r, #q)"),
("ipc_class", "#ipc_class")
]
and then I visualized everything with:
p = figure(plot_width=1600, plot_height=1000, title="Ipc to Hexes with colors", match_aspect=True,
tools="wheel_zoom,reset,pan", background_fill_color='#440154', tooltips=TOOLTIPS)
p.grid.visible = False
p.hex_tile('q', 'r', source=source, fill_color='color')
I would like the visualization to add a function, where hovering over one element will result in one of the following:
1. Highlight the current element by changing its color
2. Highlight multiple elements of the same class when one is hovered over by changing its color
3. Change the color of the outer line of the hex_tile element (or complete class) when the element is hovered over
Which of these features is possible with bokeh and how would I go about it?
EDIT:
After trying to reimplement the suggestion by Tony, all elements will turn pink as soon as my mouse hits the graph and the color won´t turn back. My code looks like this:
source = ColumnDataSource(data=dict(
x=x_row,
y=y_col,
color=colors_array,
ipc_class=ipc_array
))
p = figure(plot_width=800, plot_height=800, title="Ipc to Square with colors", match_aspect=True,
tools="wheel_zoom,reset,pan", background_fill_color='#440154')
p.grid.visible = False
p.hex_tile('x', 'y', source=source, fill_color='color')
###################################
code = '''
for (i in cb_data.renderer.data_source.data['color'])
cb_data.renderer.data_source.data['color'][i] = colors[i];
if (cb_data.index.indices != null) {
hovered_index = cb_data.index.indices[0];
hovered_color = cb_data.renderer.data_source.data['color'][hovered_index];
for (i = 0; i < cb_data.renderer.data_source.data['color'].length; i++) {
if (cb_data.renderer.data_source.data['color'][i] == hovered_color)
cb_data.renderer.data_source.data['color'][i] = 'pink';
}
}
cb_data.renderer.data_source.change.emit();
'''
TOOLTIPS = [
("index", "$index"),
("(x,y)", "(#x, #y)"),
("ipc_class", "#ipc_class")
]
callback = CustomJS(args=dict(colors=colors), code=code)
hover = HoverTool(tooltips=TOOLTIPS, callback=callback)
p.add_tools(hover)
########################################
output_file("hexbin.html")
show(p)
basically, I removed the tooltips from the figure function and put them down to the hover tool. As I already have red in my graph, I replaced the hover color to "pink". As I am not quite sure what each line in the "code" variable is supposed to do, I am quite helpless with this. I think one mistake may be, that my ColumnDataSource looks somewhat different from Tony's and I do not know what was done to "classifiy" the first and third element, as well as the second and fourth element together. For me, it would be perfect, if the classification would be done by the "ipc_class" variable.
Following the discussion from previous post here comes the solution targeted for the OP code (Bokeh v1.1.0). What I did is:
1) Added a HoverTool
2) Added a JS callback to the HoverTool which:
Resets the hex colors to the original ones (colors_array passed in the callback)
Inspects the index of currently hovered hex (hovered_index)
Gets the ip_class of currently hovered hex (hovered_ip_class)
Walks through the data_source.data['ip_class'] and finds all hexagons with the same ip_class as the hovered one and sets a new color for it (pink)
Send source.change.emit() signal to the BokehJS to update the model
The code:
from bokeh.plotting import figure, show, output_file
from bokeh.models import ColumnDataSource, CustomJS, HoverTool
colors_array = ["green", "green", "blue", "blue"]
x_row = [0, 1, 2, 3]
y_col = [1, 1, 1, 1]
ipc_array = ['A', 'B', 'A', 'B']
source = ColumnDataSource(data = dict(
x = x_row,
y = y_col,
color = colors_array,
ipc_class = ipc_array
))
p = figure(plot_width = 800, plot_height = 800, title = "Ipc to Square with colors", match_aspect = True,
tools = "wheel_zoom,reset,pan", background_fill_color = '#440154')
p.grid.visible = False
p.hex_tile('x', 'y', source = source, fill_color = 'color')
###################################
code = '''
for (let i in cb_data.renderer.data_source.data['color'])
cb_data.renderer.data_source.data['color'][i] = colors[i];
if (cb_data.index.indices != null) {
const hovered_index = cb_data.index.indices[0];
const hovered_ipc_class = cb_data.renderer.data_source.data['ipc_class'][hovered_index];
for (let i = 0; i < cb_data.renderer.data_source.data['ipc_class'].length; i++) {
if (cb_data.renderer.data_source.data['ipc_class'][i] == hovered_ipc_class)
cb_data.renderer.data_source.data['color'][i] = 'pink';
}
}
cb_data.renderer.data_source.change.emit();
'''
TOOLTIPS = [
("index", "$index"),
("(x,y)", "(#x, #y)"),
("ipc_class", "#ipc_class")
]
callback = CustomJS(args = dict(ipc_array = ipc_array, colors = colors_array), code = code)
hover = HoverTool(tooltips = TOOLTIPS, callback = callback)
p.add_tools(hover)
########################################
output_file("hexbin.html")
show(p)
Result:
Maybe something like this to start with (Bokeh v1.1.0):
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CustomJS, HoverTool
colors = ["green", "blue", "green", "blue"]
source = ColumnDataSource(dict(r = [0, 1, 2, 3], q = [1, 1, 1, 1], color = colors))
plot = figure(plot_width = 300, plot_height = 300, match_aspect = True)
plot.hex_tile('r', 'q', fill_color = 'color', source = source)
code = '''
for (i in cb_data.renderer.data_source.data['color'])
cb_data.renderer.data_source.data['color'][i] = colors[i];
if (cb_data.index.indices != null) {
hovered_index = cb_data.index.indices[0];
hovered_color = cb_data.renderer.data_source.data['color'][hovered_index];
for (i = 0; i < cb_data.renderer.data_source.data['color'].length; i++) {
if (cb_data.renderer.data_source.data['color'][i] == hovered_color)
cb_data.renderer.data_source.data['color'][i] = 'red';
}
}
cb_data.renderer.data_source.change.emit();
'''
callback = CustomJS(args = dict(colors = colors), code = code)
hover = HoverTool(tooltips = [('R', '#r')], callback = callback)
plot.add_tools(hover)
show(plot)
Result:
Another approach is to update cb_data.index.indices to include all those indices that have ipc_class in common, and add hover_color="pink" to hex_tile. So in the CustomJS code one would loop the ipc_class column and get the indices that match the ipc_class of the currently hovered item.
In this setup there is not need to update the color column in the data source.
Code below tested used Bokeh version 3.0.2.
from bokeh.plotting import figure, show, output_file
from bokeh.models import ColumnDataSource, CustomJS, HoverTool
colors_array = ["green", "green", "blue", "blue"]
x_row = [0, 1, 2, 3]
y_col = [1, 1, 1, 1]
ipc_array = ['A', 'B', 'A', 'B']
source = ColumnDataSource(data = dict(
x = x_row,
y = y_col,
color = colors_array,
ipc_class = ipc_array
))
plot = figure(
width = 800,
height = 800,
title = "Ipc to Square with colors",
match_aspect = True,
tools = "wheel_zoom,reset,pan",
background_fill_color = '#440154'
)
plot.grid.visible = False
plot.hex_tile(
'x', 'y',
source = source,
fill_color = 'color',
hover_color = 'pink' # Added!
)
code = '''
const hovered_index = cb_data.index.indices;
const src_data = cb_data.renderer.data_source.data;
if (hovered_index.length > 0) {
const hovered_ipc_class = src_data['ipc_class'][hovered_index];
var idx_common_ipc_class = hovered_index;
for (let i = 0; i < src_data['ipc_class'].length; i++) {
if (i === hovered_index[0]) {
continue;
}
if (src_data['ipc_class'][i] === hovered_ipc_class) {
idx_common_ipc_class.push(i);
}
}
cb_data.index.indices = idx_common_ipc_class;
cb_data.renderer.data_source.change.emit();
}
'''
TOOLTIPS = [
("index", "$index"),
("(x,y)", "(#x, #y)"),
("ipc_class", "#ipc_class")
]
callback = CustomJS(code = code)
hover = HoverTool(
tooltips = TOOLTIPS,
callback = callback
)
plot.add_tools(hover)
output_file("hexbin.html")
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")])

How to reference Bokeh quad values on-select within callback

I'm trying to extract values from quads when I click on them using TapTool
The following code works for a hover tool but not for tap.
from bokeh.plotting import figure, show
from bokeh.models import HoverTool, CustomJS, ColumnDataSource
source = generate_column_data_source()
quad_plot = generate_quad_plot(source )
code = ''' var hovered_ind = cb_data.index['1d'].indices[0];
var data = source.data
console.log(hovered_ind)
if(hovered_ind != undefined){
console.log('inside', hovered_ind)
var top = data['top'][hovered_ind]
var bottom = data['bottom'][hovered_ind]
var left = data['left'][hovered_ind]
var right = data['right'][hovered_ind]
console.log(top, bottom, left, right)
} '''
callback = CustomJS(code=code, args={'source': source})
quad_plot.add_tools(TapTool( callback=callback))
show(quad_plot)
It seems that cb_data.index['1d'].indices[0]; exists for hover interactions, but not for tap-selection interactions.
For tap-selection, cb_data has a single attribute named geometry, that gives me x,y,vx,and vypoints.
I do not believe these parameters to be exact enough to guarantee efficient indexing/lookup of values from my source.
Is there any way to get that precise of an index with TapTool?
UPDATE: in modern Bokeh it is just source.selected.indices (no more ['1d'] etc)
Glyphs which are selected with the taptool are accessible through the source.selected attribute.
To answer your last comment regarding 0d,1d and 2d: For a point glyph you can access it through source.selected['0d'].indices, for line like objects '1d', and then multiline/patches glyphs through ['2d'].
http://docs.bokeh.org/en/latest/docs/reference/models/sources.html (scroll down to selected attribute)
from bokeh.plotting import figure, show
from bokeh.models import HoverTool, CustomJS, ColumnDataSource, TapTool
top = [2, 3, 4]
bottom = [1, 2, 3]
left = [1, 2, 3]
right = [1.2, 2.5, 3.7]
data = {'top':top, 'bottom':bottom, 'left':left, 'right':right}
source = ColumnDataSource(data)
quad_plot = figure(plot_width=300, plot_height=300)
quad_plot.quad(top="top", bottom="bottom", left="left",
right="right",source=source, color="#B3DE69")
tap_code = """
var selected= source.selected['1d'].indices
console.log('tap, you selected:', selected)
"""
tap_callback = CustomJS(code = tap_code, args={'source': source})
quad_plot.add_tools(TapTool(callback=tap_callback))
show(quad_plot)

Categories