I have a Bokeh Server that does some analysis on my local crime dataset. The server has 3 tabs
-
A bar chart with an interactive slider you can use to scroll through single months of data
-
A crime trend multi-line plot with an Interactive Legend
-
A gmap plot that shows all the places where the crimes happened
I want to embed this in a Flash app now so i can use jinga2 templates… the end game is have lots of Bokeh servers and possibly some Plotly plots for a portfolio for the uk.gov datasets
i call - bokeh serve --show top_level.py --allow-websocket-origin=127.0.0.1:8080 on a script which looks like this
from bokeh.plotting import curdoc
from bokeh.models.widgets import Tabs
from barch import BarChart
from next import CrimeTrends
from mapping_geo_data import MapGeoData
bar_chart = BarChart()
crime_count_tab = bar_chart.get_panel()
crime_trend = CrimeTrends()
crime_trend_tab = crime_trend.get_panel()
map_geo = MapGeoData()
geo_data_tab = map_geo.get_panel()
all_tabs = Tabs(tabs=[crime_count_tab, crime_trend_tab, geo_data_tab])
curdoc().add_root(all_tabs)
I have a BarChart class which looks like this
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.palettes import d3
from bokeh.models.widgets import Panel, Slider
from bokeh.layouts import widgetbox, row
from second import Datasets
class BarChart:
def __init__(self):
self.datasets = Datasets()
self.datasets.get_data()
# pre initialisation then its all event driven from here on.
self.crimes = self.datasets.meta_common_crimes('2017-02')
self.data_source = ColumnDataSource(dict(crimes=list(self.crimes.keys()),
counts=list(self.crimes.values()),
color=d3['Category20'][14]))
self.slider = Slider(title="Months between 2017-02 / 2018-01", start=1, end=12, step=1,
value=1)
def get_panel(self):
plot = figure(x_range=list(self.crimes.keys()), y_range=(0, 6500), plot_height=600, plot_width=1000,
title="Crime Counts", toolbar_location=None, tools="")
plot.vbar(x='crimes', top='counts', width=0.9, color='color', source=self.data_source)
plot.xgrid.grid_line_color = None
plot.xaxis.major_label_orientation = 1
plot.yaxis.axis_label = "Crime Counts"
plot.xaxis.axis_label = "Crime type"
self.slider.on_change('value', self.callback)
widget_box = widgetbox(self.slider)
return Panel(child=row([widget_box, plot]), title="Crime Counts")
def callback(self, attr, old, new):
month = int(self.slider.value)
if month > 11:
year = "2018-"
month -= 11
else:
year = "2017-"
month += 1
# pad the single interger months with zeros
month = str(month).zfill(2)
# get crimes for the current year
new_crimes = self.datasets.meta_common_crimes(year + month)
# update the data source with the new values
self.data_source.data = dict(
crimes=list(new_crimes.keys()),
counts=list(new_crimes.values()),
color=d3['Category20'][14]
)
The Multi-Line plot looks like this
from bokeh.plotting import figure
from bokeh.palettes import d3
from bokeh.models.widgets import Panel
from bokeh.layouts import row
from second import Datasets
class CrimeTrends:
def __init__(self):
self.datasets = Datasets()
self.datasets.get_data()
self.df = self.datasets.new_crime_trend_df()
def get_panel(self):
plot = figure(x_axis_type='datetime', width=1000, height=600, title="Crime trends over months")
for data, name, color in zip(self.df.columns, list(self.df.columns), d3['Category20'][14]):
plot.line(x=self.df.index.values, y=self.df[data], line_color=color, line_width=2, legend=name)
plot.legend.border_line_alpha = 0.1
plot.xaxis[0].ticker.desired_num_ticks = 12
plot.yaxis.axis_label = "Crime Counts"
plot.xaxis.axis_label = "Months"
plot.legend.location = "top_left"
plot.legend.click_policy = "hide"
return Panel(child=row(plot), title="Crime Trends")
and the gmap plot looks like
from bokeh.models import ColumnDataSource, GMapOptions, GMapPlot
from bokeh.plotting import gmap
from bokeh.models.widgets import Panel, CheckboxGroup
from bokeh.layouts import row, widgetbox, column
from second import Datasets
class MapGeoData:
def __init__(self):
self.datasets = Datasets()
self.datasets.get_data()
self.df = self.datasets.one_big_dframe()
# this gets updated based on changes in the UI and fed to the google maps API
self.source = ColumnDataSource(
data=dict(lat=self.df['Latitude'],
lon=self.df['Longitude'])
)
self.months_cb_group = CheckboxGroup(labels=["2017-02", "2017-03", "2017-04", "2017-05", "2017-06", "2017-07",
"2017-08", "2017-09", "2017-10", "2017-11", "2017-12", "2018-01"],
active=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
self.crime_type_cb_group = CheckboxGroup(labels=['Anti-social behaviour', 'Bicycle theft', 'Burglary',
'Criminal damage and arson', 'Drugs', 'Other theft',
'Possession of weapons', 'Public order', 'Robbery',
'Shoplifting', 'Theft from the person', 'Vehicle crime',
'Violence and sexual offences', 'Other crime'],
active=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])
def callback(self, _):
changed = self.datasets.map_location_data(self.months_cb_group.active, self.crime_type_cb_group.active)
self.source.data = dict(lat=changed['Latitude'], lon=changed['Longitude'])
def get_panel(self):
map_options = GMapOptions(lat=54.633997, lng=-6.796974, map_type="roadmap", zoom=8)
# For GMaps to function, Google requires you obtain and enable an API key:
<details class='elided'>
<summary title='Show trimmed content'>···</summary>
#
# https://developers.google.com/maps/documentation/javascript/get-api-key
API_KEY = "MY_API_KEY"
p = gmap(google_api_key=API_KEY, map_options=map_options,
title=" Crime Data")
p.circle(x="lon", y="lat", size=3, fill_color="blue", fill_alpha=0.8, source=self.source)
self.months_cb_group.on_click(self.callback)
self.crime_type_cb_group.on_click(self.callback)
return Panel(child=row(column(widgetbox(self.months_cb_group)), column(widgetbox(self.crime_type_cb_group)), p),
title="Mapping Geo Data")
then i have my flask app
from flask import Flask, render_template
from bokeh.client import pull_session
from bokeh.embed import server_session
app = Flask(__name__, template_folder="../templates")
@app.route('/', methods=['GET'])
def bkapp_page():
session = pull_session(url="http://localhost:5006/top_level")
# generate a script to load the customized session
script = server_session(None, session_id=session.id, url='http://localhost:5006/top_level')
# use the script in the rendered page
return render_template("embed.html", script=script, template="Flask")
if __name__ == '__main__':
app.run(port=8080)
If I take out the gmap plot this all works fine but when i include the gmap plot in as one of the tabs i get this error from flask
(crime) λ python flask_thing.py
-
Serving Flask app “flask_thing” (lazy loading)
-
Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
-
Debug mode: on
-
Restarting with stat
-
Debugger is active!
-
Debugger PIN: 997-466-280
-
Running on http://127.0.0.1:8080/ (Press CTRL+C to quit)
127.0.0.1 - - [09/Jul/2018 14:15:09] “GET / HTTP/1.1” 500 -
INFO:werkzeug:127.0.0.1 - - [09/Jul/2018 14:15:09] “GET / HTTP/1.1” 500 -
Traceback (most recent call last):
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\flask\app.py”, line 2309, in call
return self.wsgi_app(environ, start_response)
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\flask\app.py”, line 2295, in wsgi_app
response = self.handle_exception(e)
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\flask\app.py”, line 1741, in handle_exception
reraise(exc_type, exc_value, tb)
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\flask_compat.py”, line 35, in reraise
raise value
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\flask\app.py”, line 2292, in wsgi_app
response = self.full_dispatch_request()
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\flask\app.py”, line 1815, in full_dispatch_request
rv = self.handle_user_exception(e)
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\flask\app.py”, line 1718, in handle_user_exception
reraise(exc_type, exc_value, tb)
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\flask_compat.py”, line 35, in reraise
raise value
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\flask\app.py”, line 1813, in full_dispatch_request
rv = self.dispatch_request()
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\flask\app.py”, line 1799, in dispatch_request
return self.view_functionsrule.endpoint
File “C:\Users\mmcdaid\Documents\crime-data-bokeh\src\flask_thing.py”, line 11, in bkapp_page
session = pull_session(url=“http://localhost:5006/top_level”)
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\bokeh\client\session.py”, line 139, in pull_session
session.pull()
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\bokeh\client\session.py”, line 266, in pull
self._connection.pull_doc(doc)
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\bokeh\client_connection.py”, line 250, in pull_doc
reply.push_to_document(document)
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\bokeh\protocol\messages\pull_doc_reply.py”, line 57, in push_to_document
doc.replace_with_json(self.content[‘doc’])
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\bokeh\document.py”, line 672, in replace_with_json
replacement = self.from_json(json)
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\bokeh\document.py”, line 512, in from_json
references = cls._instantiate_references_json(references_json)
File “C:\Users\mmcdaid\AppData\Local\conda\conda\envs\crime\lib\site-packages\bokeh\document.py”, line 1024, in _instantiate_references_json
instance = cls(id=obj_id, _block_events=True)
TypeError: init() missing 2 required positional arguments: ‘google_api_key’ and ‘map_options’
127.0.0.1 - - [09/Jul/2018 14:15:09] “GET /?debugger=yes&cmd=resource&f=style.css HTTP/1.1” 200 -
INFO:werkzeug:127.0.0.1 - - [09/Jul/2018 14:15:09] “GET /?debugger=yes&cmd=resource&f=style.css HTTP/1.1” 200 -
127.0.0.1 - - [09/Jul/2018 14:15:09] “GET /?debugger=yes&cmd=resource&f=jquery.js HTTP/1.1” 200 -
INFO:werkzeug:127.0.0.1 - - [09/Jul/2018 14:15:09] “GET /?debugger=yes&cmd=resource&f=jquery.js HTTP/1.1” 200 -
127.0.0.1 - - [09/Jul/2018 14:15:09] “GET /?debugger=yes&cmd=resource&f=debugger.js HTTP/1.1” 200 -
INFO:werkzeug:127.0.0.1 - - [09/Jul/2018 14:15:09] “GET /?debugger=yes&cmd=resource&f=debugger.js HTTP/1.1” 200 -
127.0.0.1 - - [09/Jul/2018 14:15:09] “GET /?debugger=yes&cmd=resource&f=ubuntu.ttf HTTP/1.1” 200 -
INFO:werkzeug:127.0.0.1 - - [09/Jul/2018 14:15:09] “GET /?debugger=yes&cmd=resource&f=ubuntu.ttf HTTP/1.1” 200 -
127.0.0.1 - - [09/Jul/2018 14:15:09] “GET /?debugger=yes&cmd=resource&f=console.png HTTP/1.1” 200 -
INFO:werkzeug:127.0.0.1 - - [09/Jul/2018 14:15:09] “GET /?debugger=yes&cmd=resource&f=console.png HTTP/1.1” 200 -
127.0.0.1 - - [09/Jul/2018 14:15:09] “GET /?debugger=yes&cmd=resource&f=console.png HTTP/1.1” 200 -
INFO:werkzeug:127.0.0.1 - - [09/Jul/2018 14:15:09] “GET /?debugger=yes&cmd=resource&f=console.png HTTP/1.1” 200 -
How is the API key missing … when i call pull_session(url=“https:// localhost:5006/top_level”) does this get the API key ? If not how can i pass it… I don’t even know how i would go about debugging this. Any help at all would be appreciated.