I currently have a gmap displaying gps points, however, I was hoping there was a way to colour code my GPS points based on which month they were recorded ? I have looked around online but am struggling to implement it into my own code. My dataset consists of GPS points collected throughout 2017, with a localDate index (in datetime format), and a longitude and latitude:
2017-11-12 |5.043978|118.715237
Bokeh and gmap code:
def plot(lat, lng, zoom=10, map_type='roadmap'):
gmap_options = GMapOptions(lat=lat, lng=lng,
map_type=map_type, zoom=zoom)
# the tools are defined below:
hover = HoverTool(
tooltips = [
# #price refers to the price column
# in the ColumnDataSource.
('Date', '#{Local Date}{%c}'),
('Lat', '#Lat'),
('Lon', '#Lon'),
],
formatters={'#{Local Date}': 'datetime'}
)
# below we replaced 'hover' (the default hover tool),
# by our custom hover tool
p = gmap(api_key, gmap_options, title='Malaysia',
width=bokeh_width, height=bokeh_height,
tools=[hover, 'reset', 'wheel_zoom', 'pan'])
source = ColumnDataSource(day2017Averageddf)
center = p.circle('Lon', 'Lat', size=4, alpha=0.5,
color='yellow', source=source)
show(p)
return p
p = plot(Lat, Lon, map_type='satellite')
The base idea is to pass the colors to the color keyword in p.circle(). You are using one color, but you could create also a list of colors with the correct length and implement your own logic or you could make use of a mapper.
The code below is a copy from the original documentation about mappers.
from bokeh.models import ColumnDataSource
from bokeh.palettes import Spectral6
from bokeh.plotting import figure, output_notebook, show
from bokeh.transform import linear_cmap
output_notebook()
x = [1,2,3,4,5,7,8,9,10]
y = [1,2,3,4,5,7,8,9,10]
#Use the field name of the column source
mapper = linear_cmap(field_name='y', palette=Spectral6 ,low=min(y) ,high=max(y))
source = ColumnDataSource(dict(x=x,y=y))
p = figure(width=300, height=300, title="Linear Color Map Based on Y")
p.circle(x='x', y='y', line_color=mapper,color=mapper, fill_alpha=1, size=12, source=source)
color_bar = ColorBar(color_mapper=mapper['transform'], width=8)
p.add_layout(color_bar, 'right')
show(p)
To come back to you problem. If the items in your Local Date column are of type pd.Timestamp, you can create a column "month" by this line
day2017Averageddf["month"] = day2017Averageddf["Local Date"].month
and use it for the mapper.
Related
I only started to use Bokeh recently. I have a scatter plot in which I would like to color each marker according to a certain third property (say a quantity, while the x-axis is a date and the y-axis is a given value at that point in time).
Assuming my data is in a data frame, I managed to do this using a linear color map as follows:
min_q = df.quantity.min()
max_q = df.quantity.max()
mapper = linear_cmap(field_name='quantity', palette=palettes.Spectral6, low=min_q, high=max_q)
source = ColumnDataSource(data=get_data(df))
p = figure(x_axis_type="datetime")
p.scatter(x="date_column", y="value", marker="triangle", fill_color=mapper, line_color=None, source=source)
color_bar = ColorBar(color_mapper=mapper['transform'], width=8, location=(0,0))
p.add_layout(color_bar, 'right')
This seems to work as expected. Below is the plot I get upon starting the bokeh server.
Then I have a callback function update() triggered upon changing value in some widget (a select or a time picker).
def update():
# get new df (according to new date/select)
df = get_df()
# update min/max for colormap
min_q = df.quantity.min()
max_q = df.quantity.max()
# I think I should not create a new mapper but doing so I get closer
mapper = linear_cmap(field_name='quantity', palette=palettes.Spectral6 ,low=min_q, high=max_q)
color_bar.color_mapper=mapper['transform']
source.data = get_data(df)
# etc
This is the closest I could get. The color map is updated with new values, but it seems that the colors of the marker still follow the original pattern. See picture below (given that quantity I would expect green, but it is blue as it still seen as < 4000 as in the map of the first plot before the callback).
Should I just add a "color" column to the data frame? I feel there is an easier/more convenient way to do that.
EDIT: Here is a minimal working example using the answer by bigreddot:
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.plotting import figure
from bokeh.models import Button, ColumnDataSource, ColorBar, HoverTool
from bokeh.palettes import Spectral6
from bokeh.transform import linear_cmap
import numpy as np
x = [1,2,3,4,5,7,8,9,10]
y = [1,2,3,4,5,7,8,9,10]
z = [1,2,3,4,5,7,8,9,10]
source = ColumnDataSource(dict(x=x, y=y, z=z))
#Use the field name of the column source
mapper = linear_cmap(field_name='z', palette=Spectral6 ,low=min(y) ,high=max(y))
p = figure(plot_width=300, plot_height=300, title="Linear Color Map Based on Y")
p.circle(x='x', y='y', line_color=mapper,color=mapper, fill_alpha=1, size=12, source=source)
color_bar = ColorBar(color_mapper=mapper['transform'], width=8, location=(0,0))
p.add_tools(HoverTool(tooltips="#z", show_arrow=False, point_policy='follow_mouse'))
p.add_layout(color_bar, 'right')
b = Button()
def update():
new_z = np.exp2(z)
mapper = linear_cmap(field_name='z', palette=Spectral6 ,low=min(new_z), high=max(new_z))
color_bar.color_mapper=mapper['transform']
source.data = dict(x=x, y=y, z=new_z)
b.on_click(update)
curdoc().add_root(column(b, p))
Upon update, the circles will be colored according to the original scale: everything bigger than 10 will be red. Instead, I would expect everything blue until the last 3 circle on tops that should be colored green yellow and red respectively.
It's possible that is a bug, feel free to open a GitHub issue.
That said, the above code does not represent best practices for Bokeh usage, which is: always make the smallest update possible. In this case, this means setting new property values on the existing color transform, rather than replacing the existing color transform.
Here is a complete working example (made with Bokeh 1.0.2) that demonstrates the glyph's colormapped colors updating in response to the data column changing:
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.plotting import figure
from bokeh.models import Button, ColumnDataSource, ColorBar
from bokeh.palettes import Spectral6
from bokeh.transform import linear_cmap
x = [1,2,3,4,5,7,8,9,10]
y = [1,2,3,4,5,7,8,9,10]
z = [1,2,3,4,5,7,8,9,10]
#Use the field name of the column source
mapper = linear_cmap(field_name='z', palette=Spectral6 ,low=min(y) ,high=max(y))
source = ColumnDataSource(dict(x=x, y=y, z=z))
p = figure(plot_width=300, plot_height=300, title="Linear Color Map Based on Y")
p.circle(x='x', y='y', line_color=mapper,color=mapper, fill_alpha=1, size=12, source=source)
color_bar = ColorBar(color_mapper=mapper['transform'], width=8, location=(0,0))
p.add_layout(color_bar, 'right')
b = Button()
def update():
new_z = np.exp2(z)
# update the existing transform
mapper['transform'].low=min(new_z)
mapper['transform'].high=max(new_z)
source.data = dict(x=x, y=y, z=new_z)
b.on_click(update)
curdoc().add_root(column(b, p))
Here is the original plot:
And here is the update plot after clicking the button
I'm getting into the Bokeh library with Python, but I'm having some trouble. I have the following code from the Bokeh tutorial website:
from bokeh.plotting import figure
from bokeh.io import output_notebook, show
output_notebook()
from bokeh.sampledata.autompg import autompg
from bokeh.models import HoverTool
from bokeh.plotting import ColumnDataSource
grouped = autompg.groupby("yr")
mpg2 = grouped["mpg"]
avg = mpg2.mean()
std = mpg2.std()
years = list(grouped.groups.keys())
american = autompg[autompg["origin"]==1]
japanese = autompg[autompg["origin"]==3]
p = figure(title="MPG by Year (Japan and US)")
p.vbar(x=years, bottom=avg-std, top=avg+std, width=0.8,
fill_alpha=0.2, line_color=None, legend="MPG 1 stddev")
p.circle(x=japanese["yr"], y=japanese["mpg"], size=10, alpha=0.5,
color="red", legend="Japanese")
p.triangle(x=american["yr"], y=american["mpg"], size=10, alpha=0.3,
color="blue", legend="American")
p.legend.location = "top_left"
show(p)
It works, but I'd like to add the functionality that when you hover over a point, it displays the horsepower. What I tried is
grouped = autompg.groupby("yr")
mpg = grouped["mpg"]
avg = mpg.mean()
std = mpg.std()
years = list(grouped.groups.keys())
american = autompg[autompg["origin"]==1]
japanese = autompg[autompg["origin"]==3]
source = ColumnDataSource(data=
dict(autompg)
)
hover1 = HoverTool(tooltips=[("hp", "#hp")])
p = figure(title="MPG by Year (Japan and US)",tools=[hover1])
p.vbar(x=years, bottom=avg-std, top=avg+std, width=0.8,
fill_alpha=0.2, line_color=None, legend="MPG 1 stddev")
p.circle(x=japanese["yr"], y=japanese["mpg"], size=10, alpha=0.5,
color="red", legend="Japanese")
p.triangle(x=american["yr"], y=american["mpg"], size=10, alpha=0.3,
color="blue", legend="American")
p.legend.location = "top_left"
show(p)
So defining a HoverTool that I hoped would do just that. Unfortunately, it only displays "hp: ???" for every entry. I think it's a problem with the data source, but I don't have much experience here and can't figure it out myself. I've tried the source without dict(), and I've tried to set it as american or japanese, but none of this made a difference.
Thanks!
You need to pass the source to the glyph functions, and refer to the column names for the coordinates. If you pass literal lists/arrays (as you are doing above) to circle, etc. then Bokeh will create a CDS for that data under the covers, but only with the just the data you pass to the glyph function (i.e. without extra columns like "hp"). Since you are trying to plot different glyphs for different subsets of the data, the simplest thing will be to use a CDSView to group them on the client. Something like this instead:
from bokeh.plotting import figure
from bokeh.io import show
from bokeh.sampledata.autompg import autompg
from bokeh.models import ColumnDataSource, CDSView, GroupFilter, HoverTool
p = figure()
# Bokeh factors must be strings
autompg.origin = [str(x) for x in autompg.origin]
source = ColumnDataSource(autompg)
# view for just japanese origin
japanese = CDSView(source=source, filters=[GroupFilter(column_name='origin', group="1")])
# draw circles for just the japanese view
p.circle(x="yr", y="mpg", size=10, alpha=0.5, color="red", legend="Japanese",
source=source, view=japanese)
# view for just japanese origin
american = CDSView(source=source, filters=[GroupFilter(column_name='origin', group="3")])
# draw triangles for just the american view
p.triangle(x="yr", y="mpg", size=10, alpha=0.5, color="blue", legend="american",
source=source, view=american)
p.add_tools(HoverTool(tooltips=[("hp", "#hp")]))
show(p)
With the following code,
from bokeh.plotting import figure, show, output_file
from bokeh.sampledata.iris import flowers
colormap = {'setosa': 'red', 'versicolor': 'green', 'virginica': 'blue'}
colors = [colormap[x] for x in flowers['species']]
p = figure(title = "Iris Morphology")
p.xaxis.axis_label = 'Petal Length'
p.yaxis.axis_label = 'Petal Width'
p.circle(flowers["petal_length"], flowers["petal_width"],
color=colors, fill_alpha=0.2, size=10)
output_file("iris.html", title="iris.py example")
show(p)
I can make a circle plot where I color the species:
But what I want to do is to color all the point based on range of
value in petal_length.
I tried this code but fail:
from bokeh.models import LinearColorMapper
exp_cmap = LinearColorMapper(palette='Viridis256', low = min(flowers["petal_length"]), high = max(flowers["petal_length"]))
p.circle(flowers["petal_length"], flowers["petal_width"],
fill_color = {'field' : flowers["petal_lengh"], 'transform' : exp_cmap})
output_file("iris.html", title="iris.py example")
show(p)
And also in the final desired plot, how can I put the color bar that
show the range of values and the assigned value. Something like this:
I'm using Python 2.7.13.
To answer your first part, there was a small typo (petal_lengh instead of petal_length) but more importantly, using the bokeh.ColumnDataSource will solve your problem (I tried to do it without CDS and only got column errors):
from bokeh.plotting import figure, show, output_file
from bokeh.sampledata.iris import flowers
from bokeh.models import LinearColorMapper
from bokeh.models import ColumnDataSource
p = figure(title = "Iris Morphology")
p.xaxis.axis_label = "Petal Length"
p.yaxis.axis_label = "Petal Width"
source = ColumnDataSource(flowers)
exp_cmap = LinearColorMapper(palette="Viridis256",
low = min(flowers["petal_length"]),
high = max(flowers["petal_length"]))
p.circle("petal_length", "petal_width", source=source, line_color=None,
fill_color={"field":"petal_length", "transform":exp_cmap})
# ANSWER SECOND PART - COLORBAR
# To display a color bar you'll need to import
# the `bokeh.models.ColorBar` class and pass it your mapper.
from bokeh.models import ColorBar
bar = ColorBar(color_mapper=exp_cmap, location=(0,0))
p.add_layout(bar, "left")
show(p)
See also: https://github.com/bokeh/bokeh/blob/master/examples/plotting/file/color_data_map.py
The colormapper transform refers to a column name and does not accept actual literal lists of data. So all the data needs to be in a Bokeh ColumDataSource and the plotting funcs all need to refer to the column names. Fortunately this is straightforward:
p.circle("petal_length", "petal_width", source=flowers, size=20,
fill_color = {'field': 'petal_length', 'transform': exp_cmap})
Directions for legends outside the plot area are documented here:
https://docs.bokeh.org/en/latest/docs/user_guide/styling.html#outside-the-plot-area
I'm trying to make a an interactive plots with bokeh and the hover tool.
More precisely, I'm trying to make a plot like the one I made in seaborn but I'd like it to be more interactive, meaning :
I'd like people to see the income level when they hover over one point.
I'd like the plot to stay scattered like that such thas each point is an individual point, letting people hover over them in the process.
I'd like to pick the colors,to divide between different levels of income.
How would I do that ? I tried this :
x = Belgian_income["Municipalities"]
y = Belgian_income["Average income per inhabitant"]
list_x = list(x)
list_y = list(y)
dict_xy = dict(zip(list_x,list_y))
output_file('test.html')
source = ColumnDataSource(data=dict(x=list_x,y=list_y,desc=str(list_y)))
hover = HoverTool(tooltips=[
("index", "$index"),
("(x,y)", "($x, $y)"),
('desc','#desc'),
])
p = figure(plot_width=400, plot_height=400, tools=[hover],
title="Belgian test")
p.circle('x', 'y', size=20, source=source)
show(p)
But it doesn't work at all, can someone help me ? Thanks a lot.
The main issue in your code is that you provide lists to all columns of the data source, except for the desc - you provide a single string there.
With that fixed, your code works. But the tooltips show X and Y coordinates of the mouse pointer - not the actual data. For the actual data, you have to replace $ in the hover tooltips definitions with #.
Consider this working example:
from math import sin
from random import random
from bokeh.io import output_file, show
from bokeh.models import ColumnDataSource, HoverTool, LinearColorMapper
from bokeh.palettes import plasma
from bokeh.plotting import figure
from bokeh.transform import transform
list_x = list(range(100))
list_y = [random() + sin(i / 20) for i in range(100)]
desc = [str(i) for i in list_y]
source = ColumnDataSource(data=dict(x=list_x, y=list_y, desc=desc))
hover = HoverTool(tooltips=[
("index", "$index"),
("(x,y)", "(#x, #y)"),
('desc', '#desc'),
])
mapper = LinearColorMapper(palette=plasma(256), low=min(list_y), high=max(list_y))
p = figure(plot_width=400, plot_height=400, tools=[hover], title="Belgian test")
p.circle('x', 'y', size=10, source=source,
fill_color=transform('y', mapper))
output_file('test.html')
show(p)
I have a simple multiple data bar graph (non stacked) and wish to be able to be shown the (max) value of the bar chart upon a hover over with the mouse.
I'm having trouble linking the hover location to the data though. I'm not sure how of the syntax/coding for calling an index from the bar chart.
Here is my code:
from bokeh.io import show, output_file
from bokeh.models import ColumnDataSource, FactorRange
from bokeh.plotting import figure
output_file("bars.html")
LOCATIONS = ['CPC','OG2','HS82-83','IG6','IG4','IG10']
CHECKS = ['AID CHECKS', 'ITEMS SCREENED', 'PERSONS SCREENED']
data = {'LOCATIONS' : LOCATIONS,
'AID CHECKS' : [208,622,140,1842,127,1304],
'PERSONS SCREENED' : [201,484,126,1073,81,676],
'ITEMS SCREENED' : [28,71,31,394,32,207]}
x = [ (location, check) for location in LOCATIONS for check in CHECKS ]
counts = sum(zip(data['AID CHECKS'], data['PERSONS SCREENED'], data['ITEMS SCREENED']), ()) # like an hstack
source = ColumnDataSource(data=dict(x=x, counts=counts))
p = figure(x_range=FactorRange(*x), plot_height=600, plot_width=990, title="NPS Locations by Security Checks",
tools="pan,wheel_zoom,box_zoom,reset, save")
p.xaxis.axis_label_text_font_size = "5pt"
p.xaxis.axis_label_text_font_style='bold'
p.vbar(x='x', top='counts', width=0.9, source=source)
p.add_tools(HoverTool(tooltips=[("LOCATION", "#location"), ("TOTAL", "#check")]))
p.y_range.start = 0
p.x_range.range_padding = 0.1
p.xaxis.major_label_orientation = 1
p.xgrid.grid_line_color = None
show(p)
Adjust the following line:
p.add_tools(HoverTool(tooltips=[("LOCATION", "#x"), ("TOTAL", "#counts")]))
See the documentation:
Field names that begin with # are associated with columns in a
ColumnDataSource. For instance the field name "#price" will display
values from the "price" column whenever a hover is triggered. If the
hover is for the 17th glyph, then the hover tooltip will
correspondingly display the 17th price value.