Related
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)
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)
I am plotting a complex interactive donut chart in bokeh. The code below is a simplification of a component of this chart.
I have a function which compiles a dataframe of data for the donut, and then converts it to a CDS. This data is then plotted as annular wedges.
A radiobutton group should trigger a switch to a different dataframe (as CDS), and replot the annular wedge glyphs.
The example provide (for Jupyter Lab) works in one direction. When initially plotted the button.active == 0 (outer_1). When outer_2 is selected, the chart correctly changes to plot the second dataframe (cds).
But when the outer_1 button is pressed, the glyphs do not change back. The callback is triggered - as the title changes. But the glyphs do not change.
Why are the glyphs not changing in subsequent button presses / callbacks?
I've read a few similar SO posts, and also reviewed a number of bokeh examples (the weather example here is similar)
import pandas as pd
from math import pi
from bokeh.plotting import figure
from bokeh.layouts import column, row
from bokeh.models.widgets import RadioButtonGroup
from bokeh.io import curdoc
from bokeh.models.sources import ColumnDataSource
from bokeh.layouts import column, row
from bokeh.plotting import show, output_notebook
output_notebook()
df = pd.DataFrame({'start':[pi/2, pi, 3*pi/2],
'end' :[pi/2+1.5, pi+1.5, (3*pi/2)+1.5],
'inner': [100,100,100],
'outer': [200,200,200],
'color':['red','green','blue']})
df_2 = pd.DataFrame({'start':[pi/2, pi, 3*pi/2],
'end' :[pi/2+1, pi+1, (3*pi/2)+1],
'inner': [100,100,100],
'outer': [250,300,350],
'color':['orange','gray','purple']})
data_1 = ColumnDataSource(data=df)
data_2 = ColumnDataSource(data=df_2)
def create_doc(doc):
button = RadioButtonGroup(labels=["outer_1", "outer_2"], active=0)
inputs = column(button)
p = figure(plot_width=600, plot_height=600, title="data_1",
x_axis_type=None, y_axis_type=None,
x_range=(-300, 300), y_range=(-300, 300),
min_border=0, outline_line_color=None,
background_fill_color='white', toolbar_location="above")
circle = p.circle(0,0, radius=100, fill_alpha=0, line_color='grey', line_alpha=0.4)
source = [data_1, data_2][button.active]
segments = p.annular_wedge(0,0,'inner', 'outer', 'start', 'end', color='color', alpha=0.6, source=source, name='segments')
r = row (inputs,p)
def callback(attr, old, new):
if button.active == 1:
p.title.text = 'data_2 {}'.format(button.active)
source.data.update(data_2.data)
elif button.active == 0:
p.title.text = 'data_1 {}'.format(button.active)
source.data.update(data_1.data)
button.on_change('active', callback)
doc.add_root(r)
show(create_doc)
The glpyhs change successfully once, but not again, although the changing title text (on button presses) indicate that the buttons & callback continue to work partially.
Why converting to DataFrame knowing that internally the data of ColumnDataSource is a dictionary? The following code works fine for Bokeh v1.1.0
from math import pi
from bokeh.plotting import figure, show, curdoc, Row, Column, output_notebook
from bokeh.models import RadioButtonGroup
output_notebook()
data1 = {'start':[pi/2, pi, 3*pi/2],
'end' :[pi/2+1.5, pi+1.5, (3*pi/2)+1.5],
'inner': [100,100,100],
'outer': [200,200,200],
'color':['red','green','blue']}
data2 = {'start':[pi/2, pi, 3*pi/2],
'end' :[pi/2+1, pi+1, (3*pi/2)+1],
'inner': [100,100,100],
'outer': [250,300,350],
'color':['orange','gray','purple']}
def create_doc(doc):
button = RadioButtonGroup(labels=["outer_1", "outer_2"], active=0)
p = figure(plot_width=600, plot_height=600, title="data_1",
x_axis_type=None, y_axis_type=None,
x_range=(-300, 300), y_range=(-300, 300),
min_border=0, outline_line_color=None,
background_fill_color='white', toolbar_location="above")
circle = p.circle(0,0, radius=100, fill_alpha=0, line_color='grey', line_alpha=0.4)
segments = p.annular_wedge(0,0,'inner', 'outer', 'start', 'end', color='color', alpha=0.6, source = data1, name='segments')
def callback(attr, old, new):
if button.active == 0:
print '000'
p.title.text = 'data_1 {}'.format(button.active)
segments.data_source.data = data1
elif button.active == 1:
print '111'
p.title.text = 'data_2 {}'.format(button.active)
segments.data_source.data = data2
button.on_change('active', callback)
inputs = Column(button)
r = Row(inputs,p)
doc.add_root(r)
show(create_doc)
I would love some help, I'm going in circles here. I know I'm doing something stupid but my plot isn't updating. I can't debug to see if my filter function isn't working or there's a problem that my inputs for the plot aren't linked the dynamic source input. Since even the starting plot doesn't take the initialized parameters I think it's something there. PS- any advice on having a select all, including all in the categorical choices for the select boxes would be amazing too.
Cheers,
Tom
import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook, push_notebook, curdoc
from bokeh.plotting import figure
from bokeh.models import CategoricalColorMapper, HoverTool, ColumnDataSource, Panel, Div
from bokeh.models.widgets import (CheckboxGroup, Slider, Select, TextInput, RangeSlider, Tabs, CheckboxButtonGroup, TableColumn, DataTable, Select)
from bokeh.layouts import layout, column, row, Widgetbox
from bokeh.layouts import widgetbox, row, column
from bokeh.palettes import Category20_16
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
weather = pd.read_csv('YYYYYY.csv', dayfirst=True, parse_dates=True, index_col=[1], encoding = "ISO-8859-1")
def style(p):
# Title
p.title.align = 'center'
p.title.text_font_size = '20pt'
p.title.text_font = 'serif'
# Axis titles
p.xaxis.axis_label_text_font_size = '14pt'
p.xaxis.axis_label_text_font_style = 'bold'
p.yaxis.axis_label_text_font_size = '14pt'
p.yaxis.axis_label_text_font_style = 'bold'
# Tick labels
p.xaxis.major_label_text_font_size = '12pt'
p.yaxis.major_label_text_font_size = '12pt'
return p
def make_plot(src):
p = figure(plot_height=600, plot_width=700, title="'2018'", toolbar_location="below", tooltips=TOOLTIPS)
p.circle(x="Deal_Number", y="USD_Base", source=src, size=7, line_color=None)
p = style(p)
return p
TOOLTIPS=[
("Name", "#Target"),
("$", "#Round"),
("Country", "#CC")
]
def get_dataset(deal_num, ccstring, descstring, vertstring):
df_filter = weather[weather['USD_Base'] >=(deal_num) & weather['CC'].str.contains(ccstring) & weather['Description'].str.contains(descstring) & weather['Vertical Market'].str.contains(vertstring)]
return ColumnDataSource(df_filter)
def update_plot(attr, old, new):
deal_num = int(deal_select.value)
ccstring = str(cc_select.value)
descstring = str(description_select.value)
vertstring = str(vert_select.value)
new_src = get_dataset(deal_num, ccstring, descstring, vertstring)
src.data.update(new_src.data)
# Create Input controls
deal_select = Slider(title="$ Invested", value=0, start=0, end=200, step=2)
cclist = weather["CC"].unique().tolist()
cc_select = Select(title="Country Name:", options= cclist, value='GB')
description_select = TextInput(title="Company description contains")
vertlist = weather["Vertical Market"].unique().tolist()
vert_select = Select(title="Vertical:", options= ['All'] + vertlist, value='None')
controls = widgetbox(deal_select, cc_select, description_select, vert_select)
deal_select.on_change('value', update_plot)
cc_select.on_change('value',update_plot)
description_select.on_change('value',update_plot)
vert_select.on_change('value',update_plot)
# Make the deal data source
src = get_dataset(deal_num = deal_select.value,
ccstring = cc_select.value,
descstring = description_select.value,
vertstring = vert_select.value)
# Make the deal plot
p = make_plot(src)
layout = row(controls, p)
# Make a tab with the layout
tab = Panel(child=layout, title = '2018')
# Put all the tabs into one application
tabs = Tabs(tabs = [tab])
# Put the tabs in the current document for display
curdoc().add_root(tabs)
If you are updating a glyph, you need to change the datasource for that glyph directly. In your case, you should assign the circle glyph to a variable, such as:
circle = p.circle(x="Deal_Number", y="USD_Base", source=src, size=7, line_color=None)
Then in your update_plot(attr, old, new) function try this:
circle = p.select_one({'name':'circle'})
circle.data_source.data = new_src.data
For selecting all, possibly the MultiSelect Widget would work?
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.