Skip to content

Commit d7af1b0

Browse files
committed
new blog post on bokeh
1 parent 2c4e552 commit d7af1b0

File tree

9 files changed

+255
-22
lines changed

9 files changed

+255
-22
lines changed

content/posts/170526-bar-charts-bokeh.markdown

Lines changed: 253 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ developers to generate JavaScript data visualizations for their web
1313
applications *without writing any JavaScript*. While learning a
1414
JavaScript-based data visualization library like [d3.js](https://d3js.org/)
1515
can be useful, it's often far easier to knock out a few lines of Python
16-
code that get the job done.
16+
code to get the job done.
1717

18-
With Bokeh we can create incredibly detailed interactive visualizations,
18+
With Bokeh, we can create incredibly detailed interactive visualizations,
1919
or just traditional ones like the following bar chart.
2020

21-
<img src="/img/170526-bar-charts-bokeh-flask/chart-example-64.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive bar chart with 64 bars.">
22-
21+
<img src="/img/170526-bar-charts-bokeh-flask/chart-example-64.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 64 bars.">
2322

2423
Let's use the
2524
[Flask](/flask.html) [web framework](/web-frameworks.html) with Bokeh to
@@ -28,8 +27,10 @@ create custom bar charts in a Python web app.
2827

2928
## Our Tools
3029
This tutorial works with either [Python 2 or 3](/python-2-or-3.html),
31-
but Python 3 is strongly recommended for new applications. In addition
32-
to Python throughout this tutorial we will also use the following
30+
but Python 3 is strongly recommended for new applications. I used
31+
[Python 3.6.1](https://www.python.org/downloads/release/python-361/) while
32+
writing this post. In addition to Python throughout this tutorial we
33+
will also use the following
3334
[application dependencies](/application-dependencies.html):
3435

3536
* [Flask](/flask.html) web framework,
@@ -52,7 +53,7 @@ before running this code, take a look at
5253
All code in this blog post is available open source under the MIT license
5354
on GitHub under the
5455
[bar-charts-bokeh-flask-python-3 directory of the blog-code-examples repository](https://github.com/fullstackpython/blog-code-examples).
55-
Use the source code and abuse it as you like for your own applications.
56+
Use and abuse the source code as you like for your own applications.
5657

5758

5859
## Installing Bokeh and Flask
@@ -76,7 +77,7 @@ The command prompt will change after activating the virtualenv:
7677
<img src="/img/170526-bar-charts-bokeh-flask/activate-virtualenv.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Activating our Python virtual environment on the command line.">
7778

7879
Keep in mind that you need to activate the virtualenv in every new terminal
79-
window that you want this virtualenv to be used for your project.
80+
window where you want to use the virtualenv to run the project.
8081

8182
Bokeh and Flask are installable into the now-activated virtualenv
8283
using pip. Run this command to get the appropriate Bokeh and Flask
@@ -107,8 +108,8 @@ Now we can start building our web application.
107108
We are going to first code a basic Flask application then add our bar
108109
chart to the rendered page.
109110

110-
111-
`app.py`:
111+
Create a folder for your project then within it create a file named
112+
`app.py` with the following initial contents:
112113

113114
```python
114115
from flask import Flask, render_template
@@ -128,11 +129,25 @@ if __name__ == "__main__":
128129
app.run(debug=True)
129130
```
130131

131-
...explain code...
132-
133-
`templates/chart.html`:
134-
135-
```
132+
The above code is a short one-route [Flask](/flask.html) application
133+
that defines the `chart` function. `chart` takes in an arbitrary integer
134+
as input which will later be used to define how much data we want in our
135+
bar chart. The `render_template` function within `chart` will use a template
136+
from Flask's default [template engine](/template-engines.html) named
137+
[Jinja2](/jinja2.html) to output HTML.
138+
139+
The last two lines in the allow us to run the Flask application from the
140+
command line on port 5000 in debug mode. Never use debug mode for production,
141+
that's what [WSGI servers](/wsgi-servers.html) like
142+
[Gunicorn](/green-unicorn-gunicorn.html) are built for.
143+
144+
Create a subdirectory within your project folder named `templates`. Within
145+
`templates` create a file name `chart.html`. `chart.html` was referenced in
146+
the `chart` function of our `app.py` file so we need to create it before our
147+
app will run properly. Populate `chart.html` with the following
148+
[Jinja2](/jinja2.html) markup.
149+
150+
```jinja2
136151
<!DOCTYPE html>
137152
<html>
138153
<head>
@@ -144,25 +159,242 @@ if __name__ == "__main__":
144159
</html>
145160
```
146161

147-
Short explanation of above HTML.
162+
`chart.html`'s boilerplate displays the number of bars passed into the
163+
`chart` function via the URL.
164+
165+
The `<h1>` tag's message on the number of bugs found goes along with our
166+
sample app's theme. We will pretend to be charting the number of bugs
167+
found by automated tests run each day.
168+
169+
We can test our application out now.
170+
171+
Make sure your virtualenv is still activated and that you are in the
172+
base directory of your project where `app.py` is located. Run `app.py`
173+
using the `python` command.
148174

149175
```
150176
$(barchart) python app.py
151177
```
152178

153-
Go to "localhost:5000" in your web browser.
179+
Go to [localhost:5000/16/](http://localhost:5000/16/) in your web browser.
180+
You should see a large message that changes when you modify the URL.
154181

155-
...Screenshots of it working...
182+
<img src="/img/170526-bar-charts-bokeh-flask/basic-app-no-chart.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Simple Flask app without bar chart">
183+
184+
Our simple Flask route is in place but that's not very exciting. Time
185+
to add our bar chart.
156186

157187

158188
## Generating the Bar Chart
189+
We can build on the basic Flask app foundation that we just wrote with
190+
some new Python code that uses [Bokeh](/bokeh.html).
191+
192+
Open `app.py` back up and change the top of the file to include the
193+
following imports.
194+
195+
196+
```python
197+
import random
198+
from bokeh.models import (HoverTool, FactorRange, Plot, LinearAxis, Grid,
199+
Range1d)
200+
from bokeh.models.glyphs import VBar
201+
from bokeh.plotting import figure
202+
from bokeh.charts import Bar
203+
from bokeh.embed import components
204+
from bokeh.models.sources import ColumnDataSource
205+
from flask import Flask, render_template
206+
```
207+
208+
Throughout the rest of the file we will need these Bokeh imports along
209+
with the `random` module to generate data and our bar chart.
210+
211+
Our bar chart will use "software bugs found" as a theme. The data will
212+
be randomly generated each time the page is refreshed. In a real application
213+
you'd have a more stable and useful data source!
214+
215+
Continue modifying `app.py` so the section after the imports looks like
216+
the following code.
217+
218+
```python
219+
app = Flask(__name__)
220+
221+
222+
@app.route("/<int:bars_count>/")
223+
def chart(bars_count):
224+
if bars_count <= 0:
225+
bars_count = 1
226+
227+
data = {"days": [], "bugs": [], "costs": []}
228+
for i in range(1, bars_count + 1):
229+
data['days'].append(i)
230+
data['bugs'].append(random.randint(1,100))
231+
data['costs'].append(random.uniform(1.00, 1000.00))
232+
233+
hover = create_hover_tool()
234+
plot = create_bar_chart(data, "Bugs found per day", "days",
235+
"bugs", hover)
236+
script, div = components(plot)
237+
238+
return render_template("chart.html", bars_count=bars_count,
239+
the_div=div, the_script=script)
240+
```
241+
242+
The `chart` function gains three new lists that are randomly generated
243+
by
244+
[Python 3's super-handy random module](https://docs.python.org/3/library/random.html).
245+
246+
`chart` calls two functions, `create_hover_tool` and `create_bar_chart`.
247+
We haven't written those functions yet so continue adding code below `chart`:
248+
249+
```python
250+
def create_hover_tool():
251+
# we'll code this function in a moment
252+
return None
253+
254+
255+
def create_bar_chart(data, title, x_name, y_name, hover_tool=None,
256+
width=1200, height=300):
257+
"""Creates a bar chart plot with the exact styling for the centcom
258+
dashboard. Pass in data as a dictionary, desired plot title,
259+
name of x axis, y axis and the hover tool HTML.
260+
"""
261+
source = ColumnDataSource(data)
262+
xdr = FactorRange(factors=data[x_name])
263+
ydr = Range1d(start=0,end=max(data[y_name])*1.5)
264+
265+
tools = []
266+
if hover_tool:
267+
tools = [hover_tool,]
268+
269+
plot = figure(title=title, x_range=xdr, y_range=ydr, plot_width=width,
270+
plot_height=height, h_symmetry=False, v_symmetry=False,
271+
min_border=0, toolbar_location="above", tools=tools,
272+
responsive=True, outline_line_color="#666666")
273+
274+
glyph = VBar(x=x_name, top=y_name, bottom=0, width=.8,
275+
fill_color="#e12127")
276+
plot.add_glyph(source, glyph)
277+
278+
xaxis = LinearAxis()
279+
yaxis = LinearAxis()
280+
281+
plot.add_layout(Grid(dimension=0, ticker=xaxis.ticker))
282+
plot.add_layout(Grid(dimension=1, ticker=yaxis.ticker))
283+
plot.toolbar.logo = None
284+
plot.min_border_top = 0
285+
plot.xgrid.grid_line_color = None
286+
plot.ygrid.grid_line_color = "#999999"
287+
plot.yaxis.axis_label = "Bugs found"
288+
plot.ygrid.grid_line_alpha = 0.1
289+
plot.xaxis.axis_label = "Days after app deployment"
290+
plot.xaxis.major_label_orientation = 1
291+
return plot
292+
```
293+
294+
There is a whole lot of new code above so let's break it down. The
295+
`create_hover_tool` function does not do anything yet, it simply
296+
returns `None`, which we can use if we do not want a hover tool. The hover
297+
tool is an overlay that appears when we move our mouse cursor over one of
298+
the bars or touch a bar on a touchscreen so we can see more data about the
299+
bar.
300+
301+
Within the `create_bar_chart` function we take in our generated data source
302+
and convert it into a `ColumnDataSource` object that is one type of input
303+
object we can pass to Bokeh functions. We specify two ranges for the chart's
304+
x and y axes.
305+
306+
Since we do not yet have a hover tool the `tools` list will remain empty.
307+
The line where we create `plot` using the `figure` function is where a lot of
308+
the magic happens. We specify all the parameters we want our graph to have
309+
such as the size, toolbar, borders and whether or not the graph should be
310+
responsive upon changing the web browser size.
311+
312+
We create vertical bars with the `VBar` object and add them to the plot using
313+
the `add_glyph` function that combines our source data with the `VBar`
314+
specification.
315+
316+
The last lines of the function modify the look and feel of the graph. For
317+
example I took away the `Bokeh` logo by specifying `plot.toolbar.logo = None`
318+
and added labels to both axes. I recommend keeping the
319+
[bokeh.plottin](http://bokeh.pydata.org/en/latest/docs/reference/plotting.html#bokeh-plotting)
320+
documentation open to know what your options are for customizing your
321+
visualizations.
322+
323+
Alright, let's give our app a try with a simple chart of 4 bars. The
324+
Flask app should automatically reload when you save `app.py` with the new
325+
code but if you shut down the development server fire it back up with the
326+
`python app.py` command.
327+
328+
Open your browser to [localhost:5000/4/](localhost:5000/4/).
329+
330+
<img src="/img/170526-bar-charts-bokeh-flask/chart-example-4.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 4 bars.">
331+
332+
That one looks a bit sparse, so we can crank it up by 4x to 16 bars
333+
by going to [localhost:5000/16/](localhost:5000/16/).
334+
335+
<img src="/img/170526-bar-charts-bokeh-flask/chart-example-16.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 16 bars.">
336+
337+
Now another 4x to 128 bars with [localhost:5000/128/](localhost:5000/128/)...
338+
339+
<img src="/img/170526-bar-charts-bokeh-flask/chart-example-128.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 128 bars.">
340+
341+
Looking good so far. But what about that hover tool to drill down into each
342+
bar for more data? We can add the hover with just a few lines of code
343+
in the `create_hover_tool` function.
344+
345+
346+
## Adding a Hover Tool
347+
Within `app.py` modify the `create_hover`_tool to match the following
348+
code.
349+
350+
```python
351+
def create_hover_tool():
352+
"""Generates the HTML for the Bokeh's hover data tool on our graph."""
353+
hover_html = """
354+
<div>
355+
<span class="hover-tooltip">$x</span>
356+
</div>
357+
<div>
358+
<span class="hover-tooltip">@bugs bugs</span>
359+
</div>
360+
<div>
361+
<span class="hover-tooltip">$@costs{0.00}</span>
362+
</div>
363+
"""
364+
return HoverTool(tooltips=hover_html)
365+
```
366+
367+
It may look really odd to have HTML embedded within your Python application,
368+
but that's how we specify what the hover tool should display. We use
369+
`$x` to show the bar's x axis, `@bugs` to show the "bugs" field from our
370+
data source, and `$@costs{0.00}` to show the "costs" field formatted as
371+
a dollar amount with exactly 2 decimal places.
372+
373+
Make sure you changed `return None` to `return HoverTool(tooltips=hover_html)`
374+
so we can see the results of our new function in the graph.
375+
376+
Head back to the browser and reload the
377+
[localhost:5000/128/](http://localhost:5000/128) page.
378+
379+
<img src="/img/170526-bar-charts-bokeh-flask/chart-example-128-hover-tool.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 128 bars and showing the hover tool.">
380+
381+
Nice work! Try playing around with the number of bars in the URL and the
382+
window size to see what the graph looks like under different conditions.
383+
384+
The chart gets crowded with more than 100 or so bars, but you can give
385+
it a try with whatever number of bars you want. Here is what an
386+
impractical amount of 50,000 bars looks like just for the heck of it:
387+
388+
<img src="/img/170526-bar-charts-bokeh-flask/chart-example-50000.png" width="100%" class="technical-diagram img-rounded" style="border:1px solid #ccc" alt="Responsive Bokeh bar chart with 50000 bars.">
159389

390+
Yea, we may need to do some additional work to display more than a few
391+
hundred bars at a time.
160392

161393

162394
## What's next?
163-
Nice work creating a nifty configurable bar chart in Bokeh! Next
164-
up you can modify the color scheme, change the input data source or try
165-
to create other types of charts.
395+
You just created a nifty configurable bar chart in Bokeh. Next you can
396+
modify the color scheme, change the input data source, try to create other
397+
types of charts or solve how to display very large numbers of bars.
166398

167399
There is a lot more than Bokeh can do, so be sure to check out the
168400
[official project documentation](http://bokeh.pydata.org/en/latest/) ,
37.6 KB
Loading
208 KB
Loading
183 KB
Loading
95.4 KB
Loading
82.1 KB
Loading
140 KB
Loading

theme/templates/blog/responsive-bar-charts-bokeh-flask-python-3.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
<a href="/wsgi-servers.html" class="list-group-item smaller-item">WSGI Servers</a>
77
<a href="/green-unicorn-gunicorn.html" class="list-group-item smaller-item">Green Unicorn (Gunicorn)</a>
88
<a href="http://bokeh.pydata.org/en/latest/" class="list-group-item smaller-item">Official Bokeh Docs {% include "blog/external-link.html" %}</a>
9+
<a href="https://github.com/fullstackpython/blog-code-examples/tree/master/bar-charts-bokeh-flask-python-3" class="list-group-item smaller-item">Code for this post {% include "blog/external-link.html" %}</a>

theme/templates/table-of-contents.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ <h4 class="toc-subsection">8.2 <a href="/platform-as-a-service.html">Platform-as
181181
<h4 class="toc-subsection">8.3 <a href="/operating-systems.html">Operating systems</a></h4>
182182
<div class="toc"><a href="/ubuntu.html">Ubuntu Linux</a></div>
183183
<div class="toc soon">Mint Linux</div>
184-
<div class="toc soon">FreeBSD</div>
185184
<div class="toc soon">Mac OS X</div>
186185
<div class="toc soon">Windows</div>
187186

@@ -211,6 +210,7 @@ <h4 class="toc-subsection">8.8 <span class="soon">Containers</span></h4>
211210

212211
<h4 class="toc-subsection">8.9 <a href="/serverless.html">Serverless Architectures</a></h4>
213212
<div class="toc"><a href="/aws-lambda.html">AWS Lambda</a></div>
213+
<div class="toc soon">Twilio Functions</div>
214214
<div class="toc soon">Azure Functions</div>
215215
<div class="toc"><a href="/google-cloud-functions.html">Google Cloud Functions</a></div>
216216
<div class="toc soon">Bluemix OpenWhisk</div>

0 commit comments

Comments
 (0)