How to add language parametr to NumeralTickFormatter in HoverTool in bokeh - python

Let's assume we have the following constructor for HoverTool in bokeh:
hover = HoverTool(tooltips=[("min_temp", "#{min}{0,0.0}°C")])
By default, this creates a tooltip where numbers for the value "min_temp" use comma as a thousand separator and dot for decimals - for example, 10,000.123. However, I want to use spaces to separate thousands instead of a comma, that is 10 000.123.
From what I understand from the documentation, HoverTool uses the NumeralTickFormatter class in bokeh to format numerals, and this class supports the language parametr, but I have no idea how can I change that from the HoverTool strings (besides rewriting the parametr in source code). I think this could solve my problem.

I don't know how to set the "language" in the NumeralTickFormatter but you can make use of the CustomJSHover and create you own rule with a few lines.
Minimal Example
from bokeh.plotting import figure, output_notebook, show
from bokeh.models import HoverTool, CustomJSHover
output_notebook()
p = figure(plot_width=400, plot_height=400, title=None)
x = [999.5, 1000.7, 1001.0]
y = [2, 5, 3]
p.circle(x, y, size=10)
custom = CustomJSHover(code=
'''
var num = special_vars.x.toFixed(2).toString()
var splitted = num.split(".")
for (var i=splitted[0].length-3; 0<i; i-=3){
num = num.slice(0,i)+" "+num.slice(i)
}
return num
''')
p.add_tools(HoverTool(
tooltips=[('x','$x{custom}' )],
formatters={'$x':custom}
))
show(p)
Output

Related

In Bokeh Python, LabelSet annotation does not work well if the x and y axes contain alphabet letters

Maybe the title is not clear, so let me explain my question. I tried LabelSet with all numbers at the x and y axes, it works perfectly. However, when I changed to use all Alphabet letters such as A, A+, B, etc, the LabelSet does not understand and put all annotations on the top left (I attached the image about the error below.)
I tried to debug and find out the problem is at labels = LabelSet(x = 'average_grades', y = 'exam_grades'). If I change x = integer, and y = integer, it works. Please help me to fix it since I want to apply LabelSet in many cases, not only with numbers.
#importing libraries
from bokeh.plotting import figure
from bokeh.io import curdoc
from bokeh.models.annotations import LabelSet, Label
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Select
from bokeh.layouts import layout
#create columndatasource
source=ColumnDataSource(dict(average_grades=["B+","A","D-"],
exam_grades=["A+","C","D"],
student_names=["Stephan","Helder","Riazudidn"]))
#create the figure
grade1 = ["F","D-","D","D+","C-","C","C+","B-","B","B+","A-","A","A+"]
grade2 = ["F","D-","D","D+","C-","C","C+","B-","B","B+","A-","A","A+"]
f = figure(x_range= grade1,
y_range= grade2)
f.plot_width = 1100
f.plot_height = 650
#add labels for glyphs
labels=LabelSet(x='average_grades',y='exam_grades',
text="student_names",
x_offset=20, y_offset=20,
text_color = 'red', source=source, level = 'glyph',
render_mode = "css", text_font_size = "20pt")
f.add_layout(labels)
description = Label(x = 4, y = 2, text="Hello World", render_mode = "css")
f.add_layout(description)
#create glyphs
f.circle(x="average_grades", y="exam_grades", source=source, size=8)
#create function
def update_labels(attr,old,new):
labels.text=select.value
# #labels.text = getattr(select, attr)
#create select widget
options=[("average_grades","Average Grades"),("exam_grades","Exam Grades"),("student_names","Student Names")]
select=Select(title="Attribute",options=options)
select.on_change("value",update_labels)
# #create layout and add to curdoc
lay_out=layout([[select]])
curdoc().add_root(f)
curdoc().add_root(lay_out)
Please, always provide relevant version information with every question.
This just seems like a bug. I can see the problem on latest Bokeh version 2.2.1 however if I try a 2.3 development version, the problem has gone away. So evidently this was fixed at some point recently, though perhaps inadvertently. Please file a GitHub issue so that we can make sure any fix is intentional and preserved under test. The solution is to wait for the next release 2.3 (or a 2.2.2 if one is made).
In the mean time, the only suggestion I can offer is to use integer coordinates in stead of categorical (string) coordinates, then use fixed ticker values with tick label overrides:
p.xaxis.major_label_overrides = {1: "A", 2, "B", ...}
to make it "look the same".

bokeh: How to edit a df or CDS-object through box_select?

I'm trying to label a pandas-df (containing timeseries data) with the help of
a bokeh-lineplot, box_select tool and a TextInput widget in a jupyter-notebook. How can I access the by the box_select selected data points?
I tried to edit a similar problems code (Get selected data contained within box select tool in Bokeh) by changing the CustomJS to something like:
source.callback = CustomJS(args=dict(p=p), code="""
var inds = cb_obj.get('selected')['1d'].indices;
[source.data['xvals'][i] for i in inds] = 'b'
"""
)
but couldn't apply a change on the source of the selected points.
So the shortterm goal is to manipulate a specific column of source of the selected points.
Longterm I want to use a TextInput widget to label the selected points by the supplied Textinput. That would look like:
EDIT:
That's the current code I'm trying in the notebook, to reconstruct the issue:
from random import random
import bokeh as bk
from bokeh.layouts import row
from bokeh.models import CustomJS, ColumnDataSource, HoverTool
from bokeh.plotting import figure, output_file, show, output_notebook
output_notebook()
x = [random() for x in range(20)]
y = [random() for y in range(20)]
hovertool=HoverTool(tooltips=[("Index", "$index"), ("Label", "#label")])
source = ColumnDataSource(data=dict(x=x, y=y, label=[i for i in "a"*20]))
p1 = figure(plot_width=400, plot_height=400, tools="box_select", title="Select Here")
p1.circle('x', 'y', source=source, alpha=0.6)
p1.add_tools(hovertool)
source.selected.js_on_change('indices', CustomJS(args=dict(source=source), code="""
var inds = cb_obj.indices;
for (var i = 0; i < inds.length; i++) {
source.data['label'][inds[i]] = 'b'
}
source.change.emit();
""")
)
layout = row(p1)
show(layout)
The main thing to note is that BokehJS can only automatically notice updates when actual assignments are made, e.g.
source.data = some_new_data
That would trigger an update. If you update the data "in place" then BokehJS is not able to notice that. You will have to be explicit and call source.change.emit() to let BokehJS know something has been updated.
However, you should also know that you are using three different things that are long-deprecated and will be removed in the release after next.
cb_obj.get('selected')
There is no need to ever use .get You can just access properties directly:
cb_obj.selected
The ['1d'] syntax. This dict approach was very clumsy and will be removed very soon. For most selections you want the indices property of the selection:
source.selected.indices
source.callback
This is an ancient ad-hoc callback. There is a newer general mechanism for callbacks on properties that should always be used instead
source.selected.js_on_change('indices', CustomJS(...))
Note that in this case, the cb_obj is the selection, not the data source.
With the help of this guide on how to embed a bokeh server in the notebook I figured out the following minimal example for my purpose:
from random import random
import pandas as pd
import numpy as np
from bokeh.io import output_notebook, show
from bokeh.layouts import column
from bokeh.models import Button
from bokeh.plotting import figure
from bokeh.models import HoverTool, ColumnDataSource, BoxSelectTool
from bokeh.models.widgets import TextInput
output_notebook()
def modify_doc(doc):
# create a plot and style its properties
TOOLS="pan,wheel_zoom,reset"
p = figure(title = "My chart", tools=TOOLS)
p.xaxis.axis_label = 'X'
p.yaxis.axis_label = 'Y'
hovertool=HoverTool(tooltips=[("Index", "$index"), ("Label", "#label")])
source = ColumnDataSource(
data=dict(
xvals=list(range(0, 10)),
yvals=list(np.random.normal(0, 1, 10)),
label = [i for i in "a"*10]
))
p.scatter("xvals", "yvals",source=source, color="white")
p.line("xvals", "yvals",source=source)
p.add_tools(BoxSelectTool(dimensions="width"))
p.add_tools(hovertool)
# create a callback that will add a number in a random location
def callback():
inds = source.selected.indices
for i in inds:
source.data['label'][i] = label_input.value.strip()
print(source.data)
new_data = pd.DataFrame(source.data)
new_data.to_csv("new_data.csv", index=False)
# TextInput to specify the label
label_input = TextInput(title="Label")
# add a button widget and configure with the call back
button = Button(label="Label Data")
button.on_click(callback)
# put the button and plot in a layout and add to the document
doc.add_root(column(button,label_input, p))
show(modify_doc, notebook_url="http://localhost:8888")
That generates the following UI:
BTW: Due to the non-existing box_select tool for the line glyph I use a workaround by combining it with invisible scatter points.
So far so good, is there a more elegant way to access the data.source/new_data df in the notebook outside modify_doc() than exporting it within the callback?

Bokeh not interpreting correct scale with SQL data

Explored lots of various solutions here but not finding one that works. I'm using sqlite and pandas to read data from a SQL database, but Bokeh doesn't like the date. I've tried conversions to datetime, unixepoch, etc. and they all seem to yield the same result.
EDIT: Here's the full code:
from os.path import dirname, join
import pandas as pd
import pandas.io.sql as psql
import numpy as np
import sqlite3
import os
from math import pi
from bokeh.plotting import figure, output_file, show
from bokeh.io import output_notebook, curdoc
from bokeh.models import ColumnDataSource, Div, DatetimeTickFormatter
from bokeh.models.widgets import Slider, Select, RadioButtonGroup
from bokeh.layouts import layout, widgetbox
import warnings
import datetime
warnings.filterwarnings('ignore')
## Set up the SQL Connection
conn = sqlite3.connect('/Users/<>/Documents/python_scripts/reptool/reptool_db')
c = conn.cursor()
## Run the SQL
proj = pd.read_sql(
"""
SELECT
CASE WHEN df is null THEN ds ELSE df END AS 'projdate',
CASE WHEN yhat is null THEN y ELSE yhat END AS 'projvol',
strftime('%Y',ds) as 'year'
FROM forecast
LEFT JOIN actuals
ON forecast.ds = actuals.df
""", con=conn)
# HTML index page and inline CSS stylesheet
desc = Div(text=open("/Users/<>/Documents/python_scripts/reptool/description.html").read(), width=800)
## Rename Columns and create list sets
proj.rename(columns={'projdate': 'x', 'projvol': 'y'}, inplace=True)
x=list(proj['x'])
y=list(proj['y'])
# proj['projdate'] = [datetime.datetime.strptime(x, "%Y-%m-%d").date() for x in proj['projdate']]
# Create input controls
radio_button_group = RadioButtonGroup(
labels=["Actuals", "Forecast","FY Projection"], active=0)
min_year = Slider(title="Period Start", start=2012, end=2018, value=2013, step=1)
max_year = Slider(title="Period End", start=2012, end=2018, value=2017, step=1)
## Declare systemic source
source = ColumnDataSource(data=dict(x=[], y=[], year=[]))
## Bokeh tools
TOOLS="pan,wheel_zoom,box_zoom,reset,xbox_select"
## Set up plot
p = figure(title="REP Forecast", plot_width=900, plot_height=300, tools=TOOLS, x_axis_label='date', x_axis_type='datetime', y_axis_label='volume', active_drag="xbox_select")
p.line(x=proj.index, y=y, line_width=2, line_alpha=0.6)
p.xaxis.major_label_orientation = pi/4
# p.xaxis.formatter = DatetimeTickFormatter(seconds=["%Y:%M"],
# minutes=["%Y:%M"],
# minsec=["%Y:%M"],
# hours=["%Y:%M"])
# axis map
# definitions
def select_rep():
selected = proj[
(proj.year >= min_year.value) &
(proj.year >= max_year.value)
]
return selected
def update():
proj = select_rep()
source.data = dict(
year=proj["year"]
)
controls = [min_year, max_year]
for control in controls:
control.on_change('value', lambda attr, old, new: update())
sizing_mode = 'fixed' # 'scale_width' also looks nice with this example
## Build the html page and inline CSS
inputs = widgetbox(*controls)
l = layout([
[desc],
[p],
[inputs],
], )
# update()
curdoc().add_root(l)
curdoc().title = "REP"
The SQLite output in Terminal.app looks like this:
SQL
The result is, that the x-axis displays in milliseconds. Also, the y-axis is showing up as exponential notation:
Bokeh Plot
The issue seems somehow related to pandas use of indexing, and thus I can't reference "x" here. I rename the columns and force list sets which, by themselves, will print correctly... and should therefore plot into the line properly but as you'll see below, they don't:
proj.rename(columns={'projdate': 'x', 'projvol': 'y'}, inplace=True)
x=list(proj['x'])
y=list(proj['y'])
To get the line to render in Bokeh, I have to pass it the index because passing it anything else doesn't seem to get the glyph to render. So currently I have this:
p = figure(title="REP Forecast", plot_width=900, plot_height=300, tools=TOOLS, x_axis_label='date', x_axis_type='datetime', y_axis_label='volume', active_drag="xbox_select")
p.line(x=proj.index, y=y, line_width=2, line_alpha=0.6)
Tried converting to unixepoch in the SQL, same result.
Tried converting to unixepoch in the data, same result.
Tried using DateTimeTickFormatter, just shows all 5-6 years as one year (thinking it's just displaying the milliseconds as years rather than changing them from milliseconds to days.
I've looked here and in github, up and down, and tried different things but ultimately I can't find one working example where the source is a sql query not a csv.
None of these things have anything to do with SQL, Bokeh only cares about the data that you give it, not where it came from. You have specified that you want a datetime axis on the x-axis:
x_axis_type='datetime'
So, Bokeh will set up the plot with a ticker that picks "nice" values on a datetime scale, and with a tick formatter that displays tick locations as formatted dates. What is important, however, is that the data coordinates are in the appropriate units, which are floating point milliseconds since epoch.
You can provide x values directly in these units, but Bokeh will also automatically convert common datetime types (e.g. python stdlib, numpy, or pandas) to the right units automatically. So the easiest thing for you to do is pass a column of datetime values as the x values to line.
To be clear, this statement:
To render the line in Bokeh, it has to use the index
is incorrect. You can pass any dataframe column you like as the x-values, and I am suggesting you pass a column of datetimes.
I changed a line of the SQL to:
CASE WHEN df is null THEN strftime('%Y',ds) ELSE strftime('%Y',df) END AS 'projdate',
However, when I try expanding that specifier to %Y-%m-%d %H-%m-%s it just reads it as a string all over again.
And also by re-importing the data I was able to pass the date through here without using Index:
p.line(x=x, y=y, line_width=2, line_alpha=0.6)
But then I get this weird output: link.
So it's clear that it can read the year, but I need to pass through the full date to display the time series forecast. And it's still displaying the dates and y-values in the incorrect scale, regardless.
Going to noodle on this some more but if anyone has other suggestions, I'm thankful.
SOLVED the datetime problem. Added this after the SQL query:
proj['projdate'] = proj['projdate'].astype('datetime64[ns]')
Which in turn yields this:
Bokeh Plot
Still got a problem with the x-axis but since that's a straight numerical value, x_axis_type should fix it.
So far the working code looks like this (again, still iterating to add other controls but everything about the Bokeh plot itself works as intended):
# main.py
# created by: <>
# version: 0.1.2
# created date: 07-Aug-2018
# modified date: 09-Aug-2018
from os.path import dirname, join
import pandas as pd
import pandas.io.sql as psql
import numpy as np
import sqlite3
import os
from math import pi
from bokeh.plotting import figure, output_file, show
from bokeh.io import output_notebook, curdoc
from bokeh.models import ColumnDataSource, Div, DatetimeTickFormatter
from bokeh.models.widgets import Slider, Select, RadioButtonGroup
from bokeh.layouts import layout, widgetbox
import warnings
import datetime
warnings.filterwarnings('ignore')
## Set up the SQL Connection
conn = sqlite3.connect('/Users/<>/Documents/python_scripts/reptool/reptool_db')
c = conn.cursor()
## Run the SQL
proj = pd.read_sql(
"""
SELECT
CASE WHEN df is null THEN strftime('%Y-%m-%d',ds) ELSE strftime('%Y-%m-%d',df) END AS 'projdate',
CASE WHEN yhat is null THEN y ELSE yhat END AS 'projvol',
strftime('%Y',ds) as 'year'
FROM forecast
LEFT JOIN actuals
ON forecast.ds = actuals.df
""", con=conn)
proj['projdate'] = proj['projdate'].astype('datetime64[ns]')
# HTML index page and inline CSS stylesheet
desc = Div(text=open("/Users/<>/Documents/python_scripts/reptool/description.html").read(), width=800)
## Rename Columns and create list sets
proj.rename(columns={'projdate': 'x', 'projvol': 'y'}, inplace=True)
x=list(proj['x'])
y=list(proj['y'])
# Create input controls
radio_button_group = RadioButtonGroup(
labels=["Actuals", "Forecast","FY Projection"], active=0)
min_year = Slider(title="Period Start", start=2012, end=2018, value=2013, step=1)
max_year = Slider(title="Period End", start=2012, end=2018, value=2017, step=1)
## Declare systemic source
source = ColumnDataSource(data=dict(x=[], y=[], year=[]))
## Bokeh tools
TOOLS="pan,wheel_zoom,box_zoom,reset,xbox_select"
## Set up plot
p = figure(title="REP Forecast", plot_width=900, plot_height=300, tools=TOOLS, x_axis_label='date', x_axis_type='datetime', y_axis_label='volume', active_drag="xbox_select")
p.line(x=x, y=y, line_width=2, line_alpha=0.6)
p.xaxis.major_label_orientation = pi/4
# p.xaxis.formatter = DatetimeTickFormatter(seconds=["%Y:%M"],
# minutes=["%Y:%M"],
# minsec=["%Y:%M"],
# hours=["%Y:%M"])
# axis map
# definitions
def select_rep():
selected = proj[
(proj.year >= min_year.value) &
(proj.year >= max_year.value)
]
return selected
def update():
proj = select_rep()
source.data = dict(
year=proj["year"]
)
controls = [min_year, max_year]
for control in controls:
control.on_change('value', lambda attr, old, new: update())
sizing_mode = 'fixed' # 'scale_width' also looks nice with this example
## Build the html page and inline CSS
inputs = widgetbox(*controls)
l = layout([
[desc],
[p],
[inputs],
], )
# update()
curdoc().add_root(l)
curdoc().title = "REP"

Bokeh multiline plot

I am trying to plot RPI, CPI and CPIH on one chart with a HoverTool showing the value of each when you pan over a given area of the chart.
I initially tried adding each line separately using line() which kind of worked:
However, the HoverTool only works correctly when you scroll over the individual lines.
I have tried using multi_line() like:
combined_inflation_metrics = 'combined_inflation_metrics.csv'
df_combined_inflation_metrics = pd.read_csv(combined_inflation_metrics)
combined_source = ColumnDataSource(df_combined_inflation_metrics)
l.multi_line(xs=['Date','Date','Date'],ys=['RPI', 'CPI', 'CPIH'], source=combined_source)
#l.multi_line(xs=[['Date'],['Date'],['Date']],ys=[['RPI'], ['CPI'], ['CPIH']], source=combined_source)
show(l)
However, this is throwing the following:
RuntimeError:
Supplying a user-defined data source AND iterable values to glyph methods is
not possibe. Either:
Pass all data directly as literals:
p.circe(x=a_list, y=an_array, ...)
Or, put all data in a ColumnDataSource and pass column names:
source = ColumnDataSource(data=dict(x=a_list, y=an_array))
p.circe(x='x', y='y', source=source, ...)
But I am not too sure why this is?
Update:
I figured out a workaround by adding all of the values in each of the data sources. It works, but doesn't feel most efficient and would still like to know how to do this properly.
Edit - Code request:
from bokeh.plotting import figure, output_file, show
from bokeh.models import NumeralTickFormatter, DatetimeTickFormatter, ColumnDataSource, HoverTool, CrosshairTool, SaveTool, PanTool
import pandas as pd
import os
os.chdir(r'path')
#output_file('Inflation.html', title='Inflation')
RPI = 'RPI.csv'
CPI = 'CPI.csv'
CPIH = 'CPIH.csv'
df_RPI = pd.read_csv(RPI)
df_CPI = pd.read_csv(CPI)
df_CPIH = pd.read_csv(CPIH)
def to_date_time(data_frame, data_series):
data_frame[data_series] = data_frame[data_series].astype('datetime64[ns]')
to_date_time(df_RPI, 'Date')
to_date_time(df_CPI, 'Date')
to_date_time(df_CPIH, 'Date')
RPI_source = ColumnDataSource(df_RPI)
CPI_source = ColumnDataSource(df_CPI)
CPIH_source = ColumnDataSource(df_CPIH)
l = figure(title="Historic Inflaiton Metrics", logo=None)
l.plot_width = 1200
l.xaxis[0].formatter=DatetimeTickFormatter(
days=["%d %B %Y"],
months=["%d %B %Y"],
years=["%d %B %Y"],
)
glyph_1 = l.line('Date','RPI',source=RPI_source, legend='TYPE', color='red')
glyph_2 = l.line('Date','CPI',source=CPI_source, legend='TYPE', color='blue')
glyph_3 = l.line('Date','CPIH',source=CPIH_source, legend='TYPE', color='gold')
hover = HoverTool(renderers=[glyph_1],
tooltips=[ ("Date","#Date{%F}"),
("RPI","#RPI"),
("CPI","#CPI"),
("CPIH","#CPIH")],
formatters={"Date": "datetime"},
mode='vline'
)
l.tools = [SaveTool(), PanTool(), hover, CrosshairTool()]
show(l)
The hover tool looks up the data to show in the ColumnDataSource. Because you created a new ColumnDataSource for each line and restricted the hover tool to line1 it can only lookup data in the data source there.
The general solution is to only create one ColumnDataSource and reuse that in each line:
df_RPI = pd.read_csv(RPI)
df_CPI = pd.read_csv(CPI)
df_CPIH = pd.read_csv(CPIH)
df = df_RPI.merge(dfd_CPI, on="date")
df = df.merge(df_CPIH, on="date")
source = ColumnDataSource(df)
l = figure(title="Historic Inflation Metrics", logo=None)
glyph_1 = l.line('Date','RPI',source=source, legend='RPI', color='red')
l.line('Date','CPI',source=source, legend='CPI', color='blue')
l.line('Date','CPIH',source=source, legend='CPIH', color='gold')
hover = HoverTool(renderers=[glyph_1],
tooltips=[ ("Date","#Date{%F}"),
("RPI","#RPI"),
("CPI","#CPI"),
("CPIH","#CPIH")],
formatters={"Date": "datetime"},
mode='vline'
)
show(l)
This is of course only possible if you all your dataframes can be merged into one, i.e. the measurement timepoints are the same. If they are not besides resampling/interpolating I do not know a good method to do what you want.

Animate GMapPlot w/ Python/Bokeh

I'm a very nooby programmer and this is my first Stack Overflow question. :)
So I'm trying to animate a car's trip on google maps using Python. I used matplotlib at first and could get a dot animated over the path line... then I tried using bokeh and successfully got the path to overlay on google maps...
My problem is that I haven't found a good way to do both (animate plot over google maps).
My data is in the form of Lat/Long coordinates.
Any advice? Thanks in advance!
EDIT: Here's my code that does the gmapplot... I'd rather have this and no animation than animation with no GMAP. My goal is to animate that "car" dot.
import numpy as np
from bokeh.io import output_file, show, vform
from bokeh.models.widgets import Dropdown
from bokeh.models import (GMapPlot, GMapOptions, ColumnDataSource, Line, Circle,
DataRange1d, PanTool, WheelZoomTool, BoxSelectTool, HoverTool)
data = np.genfromtxt('Desktop\Temp Data for Python\test data 3.csv', delimiter=',',
names=True)
map_options = GMapOptions(lat=np.average(data['Latitude']),
lng=np.average(data['Longitude']), map_type="roadmap", zoom=13)
plot = GMapPlot(x_range=DataRange1d(), y_range=DataRange1d(), map_options=map_options,
title="My Drive")
source = ColumnDataSource(data=dict(lat=data['Latitude'], lon=data['Longitude'],
speed=data['GpsSpeed'],))
path = Line(x="lon", y="lat", line_width = 2, line_color='blue')
car = Circle(x=data['Longitude'][0], y=data['Latitude'][0], size=5, fill_color='red')
plot.add_glyph(source, path)
plot.add_glyph(source, car)
plot.add_tools(PanTool(), WheelZoomTool(), BoxSelectTool(),
HoverTool(tooltips=[("Speed", "#speed"),]))
output_file("gmap_plot.html")
show(plot)
This may not be exactly what you are looking for, but you could have a slider widget that controls the position of your car dot. The slider example found in the bokeh docs (or github repository, I can't remember) helped me when I started using sliders.
Just so you are aware, I was having problems with latlng points showing up in the correct locations. There is about a 10px offset. This is an open issue (github issue 2964).
The following code currently is just producing a generic bokeh Figure, but in theory, if you change it from a Figure to a GMapPlot it should work. I wasn't able to get this working with GMapPlots directly. I think this may be because of github issue 3737. I can't even run the Austin example from the bokeh docs.
Hopefully this is what you had in mind
from bokeh.plotting import Figure, ColumnDataSource, show, vplot
from bokeh.io import output_file
from bokeh.models import (Slider, CustomJS, GMapPlot,
GMapOptions, DataRange1d, Circle, Line)
import numpy as np
output_file("path.html")
# Create path around roundabout
r = 0.000192
x1 = np.linspace(-1,1,100)*r
x2 = np.linspace(1,-1,100)*r
x = np.hstack((x1,x2))
f = lambda x : np.sqrt(r**2 - x**2)
y1 = f(x1)
y2 = -f(x2)
y = np.hstack((y1,y2))
init_x = 40.233688
init_y = -111.646784
lon = init_x + x
lat = init_y + y
# Initialize data sources.
location = ColumnDataSource(data=dict(x=[lon[0]], y=[lat[0]]))
path = ColumnDataSource(data=dict(x=lon, y=lat))
# Initialize figure, path, and point
"""I haven't been able to verify that the GMapPlot code below works, but
this should be the right thing to do. The zoom may be totally wrong,
but my latlng points should be a path around a roundabout.
"""
##options = GMapOptions(lat=40.233681, lon=-111.646595, map_type="roadmap", zoom=15)
##fig = GMapPlot(x_range=DataRange1d(), y_range=DataRange1d(), map_options=options)
fig = Figure(plot_height=600, plot_width=600)
c = Circle(x='x', y='y', size=10)
p = Line(x='x', y='y')
fig.add_glyph(location, c)
fig.add_glyph(path, p)
# Slider callback
callback = CustomJS(args=dict(location=location, path=path), code="""
var loc = location.get('data');
var p = path.get('data');
t = cb_obj.get('value');
/* set the point location to the path location that
corresponds to the slider position */
loc['x'][0] = p['x'][t];
loc['y'][0] = p['y'][t];
location.trigger('change');
""")
# The way I have written this, 'start' has to be 0 and
# 'end' has to be the length of the array of path points.
slider = Slider(start=0, end=200, step=1, callback=callback)
show(vplot(fig, slider))

Categories