Change the appearance of points on the map on SVG pandas_bokeh - python

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)

Related

Bokeh Server: Trouble attaching callback to glyph tap e.g. a circle?

I've been trying to call a python function when a glyph is clicked. I've tried this example https://stackoverflow.com/a/50499396/8925535 and this https://groups.google.com/a/continuum.io/g/bokeh/c/zVzLyvJFWtE/m/eRugLfUuAAAJ but still my callback never gets called. Here's my code
from bokeh.models import Slider, ColumnDataSource
from bokeh.io import curdoc
from bokeh.layouts import row
from bokeh.plotting import figure
from bokeh.palettes import d3
from numpy.random import random
#Create data for the plot
initial_points = 5
data = {'x': random(initial_points),
'y': random(initial_points)
}
source = ColumnDataSource(data = {'x': random(initial_points), 'y': random(initial_points)})
plot = figure(title = "Random scatter plot generator", plot_height=750, plot_width=750,
tools="tap")
plot.circle(x = 'x', y = 'y', source = source, color='green', size=20, alpha=0.5)
slider_widget = Slider(start = 0, end = 20, step = 2, value = initial_points, title = 'Slide
right to increase number of points')
def slider_callback(attr, old, new):
points = slider_widget.value
source.data = {'x': random(points), 'y': random(points)}
slider_widget.on_change('value', slider_callback)
def tap_callback(attr, old, new):
print('Here mate')
source.on_change('selected', tap_callback)
layout = row(slider_widget, plot)
curdoc().add_root(layout)
The callback function in question is tap_callback.
I'd really love to see "Here mate" printed in terminal after clicking a glyph
Replace
source.on_change('selected', tap_callback)
with
source.selected.on_change('indices', tap_callback)

In Bokeh, how can I display different information for points and patches?

I want to display different information for different layers (points and patches) using bokeh.
I downloaded the shapefile and the population information of Haitian cities respectively from here and from here and I merged them.
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import geopandas as gpd
import osmnx as ox
from bokeh.layouts import row, column
from bokeh.models import Select
from bokeh.palettes import Spectral5
from bokeh.plotting import curdoc, figure, save
from bokeh.sampledata.autompg import autompg_clean as df
from bokeh.io import show
from bokeh.models import LogColorMapper
from bokeh.palettes import Viridis6 as palette
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.sampledata.us_counties import data as counties
from bokeh.sampledata.unemployment import data as unemployment
import pandas as pd
import geopandas as gpd
import shapely
color_mapper = LogColorMapper(palette=palette)
Some functions
def getPolyCoords(row, geom, coord_type):
"""Returns the coordinates ('x' or 'y') of edges of a Polygon exterior"""
# Parse the exterior of the coordinate
exterior = row[geom].exterior
if coord_type == 'x':
# Get the x coordinates of the exterior
return list( exterior.coords.xy[0] )
elif coord_type == 'y':
# Get the y coordinates of the exterior
return list( exterior.coords.xy[1] )
def getPointCoords(row, geom, coord_type):
"""Calculates coordinates ('x' or 'y') of a Point geometry"""
if coord_type == 'x':
return row[geom].x
elif coord_type == 'y':
return row[geom].y
Cities data
haiti = gpd.read_file(hti_admbnda_adm2_cnigs_20181129.shp')
haiti = haiti.to_crs({'init': 'epsg:32618'})
haiti = haiti[haiti.index != 98].reset_index(drop=True) ## i=98 is corrupted
pop = pd.read_csv('hti_admnbnda_adm2_cnigs2013c.csv')
level = 2
left = 'adm%dcode'%level
right = 'ADM%d_PCODE'%level
h_geom = pd.merge(pop, haiti, left_on=left, right_on=right)
Then I created a data for bokeh
grid = pd.DataFrame()
grid['x'] = h_geom.apply(getPolyCoords, geom='geometry', coord_type='x', axis=1)
grid['y'] = h_geom.apply(getPolyCoords, geom='geometry', coord_type='y', axis=1)
grid['Name'] = h_geom['adm2_en']
grid['Population'] = h_geom['TOTAL']
data=dict(
x=list(grid['x'].values),
y=list(grid['y'].values),
name=list(grid['Name'].values),
rate=list(grid['Population'].values),
)
From osmnx I get points of schools
selected_amenities = ['school']
place = 'Haiti'
schoolOSM = ox.pois_from_place(place=place, amenities=selected_amenities)
schools = gpd.GeoDataFrame(schoolOSM)
idxok = []
for i in schools.index:
if type(schools['geometry'][i]) == shapely.geometry.point.Point:
idxok.append(i)
schools = schools[schools.index.isin(idxok)]
schools['x'] = schools.apply(getPointCoords, geom='geometry', coord_type='x', axis=1)
schools['y'] = schools.apply(getPointCoords, geom='geometry', coord_type='y', axis=1)
data1=dict(
x=list(schools['x'].values),
y=list(schools['y'].values),
)
Then I want to show the information: I would like to show Name, Population and coordinates for cities while only coordinates for schools.
TOOLS = "pan,wheel_zoom,reset,hover,save"
p = figure(title="Schools Point in Haiti", tools=TOOLS,
x_axis_location=None, y_axis_location=None,
tooltips=[("Name", "#name"), ("Population", "#rate"), ("(Long, Lat)", "($x, $y)")])
p.hover.point_policy = "follow_mouse"
p.patches('x', 'y', source=data,
fill_color={'field': 'rate', 'transform': color_mapper},
fill_alpha=1.0, line_color="black", line_width=1)
# Add points on top (as black points)
p.circle('x', 'y', size=3, source=data1, color="black")
show(p)
In doing so I get the information of Name, Population, Long, Lat for both Schools and Cities. But Schools do not have the info Name and Population, so I get something like
You need to create two separate data sources and two separate HoverTools.
from bokeh.models import HoverTool
data_cities = dict(x = list(cities['x'].values), y = list(cities['y'].values))
data_schools = dict(x = list(schools['x'].values), y = list(schools['y'].values))
cities = p.circle('x', 'y', size = 3, source = data_cities, color = "green")
schools = p.circle('x', 'y', size = 3, source = data_schools, color = "blue")
hover_cities = HoverTool(renderers = [cities], tooltips = [("Name", "#name"), ("Population", "#rate"), ("(Long, Lat)", "($x, $y)")]))
hover_schools = HoverTool(renderers = [schools], tooltips = [("(Long, Lat)", "($x, $y)")]))
p.add_tools(hover_cities)
p.add_tools(hover_schools)

bokeh hover multiline with datetime axis

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')

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")])

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