-
Notifications
You must be signed in to change notification settings - Fork 3
Rendering Bokeh plots in Flask
One thing I really wanted to do while working with Bokeh is to render my own plots and embed them in a template served by Flask. I've figured out how to do this, to a degree, so I've decided to create a tutorial on embedding Bokeh plots into HTML via Flask and its templating system. This tutorial assumes you know the basics of both Flask and Bokeh - not that you can't follow it otherwise, but once you're familiar with them the details make much more sense.
First of all, let's define the absolute basics you need for serving a Bokeh plot on a webpage:
- A plotting function that returns an HTML snippet.
- A basic Flask app that will render this HTML snippet.
With this, we should be able to serve a plot on a webpage without the need of bokeh-server. This is extremely useful if you want to integrate these plots into an app deployed onto Heroku, for example.
Note: I highly suggest that you type out the code snippets I share with you instead of copying them. I feel that simply pasting the code into a text editor doesn't let you understand how and why you are building the code.
First, we'll have to create a basic plot in Bokeh, with a few differences.
Our initial plot will just be a line plot. We will use output_file() for our output mode. For the data, I used Numpy to generate an array of 100 random integers between 0 and 100. (This isn't strictly required, but I've found that Bokeh works best with an array.)
plots.py
:
from bokeh.plotting import *
import numpy as np
# Define a function that will return an HTML snippet.
def build_plot():
# Set the output for our plot.
output_file('plot.html', title='Plot')
# Create some data for our plot.
x_data = np.arange(1, 101)
y_data = np.random.randint(0, 101, 100)
# Create a line plot from our data.
line(x_data, y_data)
# Create an HTML snippet of our plot.
snippet = curplot().create_html_snippet(embed_base_url='../static/js/', embed_save_loc='./static/js')
# Return the snippet we want to place in our page.
return snippet
Notice that we've done something interesting with our plot. Instead of calling save() or show() to create an HTML page of our plot, we've used create_html_snippet() on our plot and had our function return that HTML snippet. An HTML snippet looks something like this:
<script src="../static/js/b8df54f4-ce27-49ec-a5be-559815841d37.embed.js" bokeh_plottype="embeddata"
bokeh_modelid="b8df54f4-ce27-49ec-a5be-559815841d37" bokeh_modeltype="Plot" async="true"></script>
There's a lot of complicated-looking attributes there, but the gist of it is that it's a script tag that references a Javascript file. Essentially, it's a piece of HTML. We can inject this snippet into a webpage and it will render a plot wherever it is placed.
Now, we need a Flask application. Each Flask app needs at the very minimum one Python file:
views.py
:
from flask import Flask
app = Flask(__name__)
# Define our URLs and pages.
@app.route('/') # The base URL for the home page.
def hello_world():
return 'hello world'
if __name__ = "__main__":
app.run(debug=True)
That's it. That's an extremely minimal app in Flask that renders plain text on a webpage. Congratulations, you made a website!
If we want to render a plot, however, things will be a bit more complicated. This approach won't be enough to render a plot snippet. Returning our snippet like this...
@app.route('/plot')
def render_plot():
plot_snippet = build_plot()
return plot_snippet
...will get us a blank page. The page's HTML will just be the script tag and nothing else, which won't do anything. Trying to output an entire HTML tree in Python is cumbersome and also extremely vulnerable, so that's out of the question as well. What we are going to need is a template.
An HTML template is a basic HTML page that Flask can read and dynamically change based on what you pass into it. You can read more about Flask templates here. Let's cut to the chase, though, and create a template:
plots.html
:
<!DOCTYPE html>
<html>
<head>
<title>Plots</title>
</head>
<body>
<div id="plot">
{{ snippet|safe }}
</div>
</body>
</html>
For our purposes, this is more than enough. The gist of it is that within our body tag, we created a div with the id "plot". Within that div, however, there's something new - "{{ snippet|safe }}".
This is a template variable. The braces ("{{ }}") denote what's inside as a variable to be passed to the template. Whatever that variable is will replace the entire block of braces. For example, if snippet = "hello world"
, the template will render the plain text "hello world" inside that div element. The |safe
tacked onto that variable means that the variable will be rendered literally upon the page, which we need in order to tell the page to render our HTML snippet.
Before we can use our template, however, we need to place it in a special directory. In the same directory as plots.py
and views.py
, create a new folder called templates
. Place your plots.html
file in that directory.
While we're creating our templates
directory, we have some more folders and files to make. The Bokeh plot depends on BokehJS itself to render, so we need to include that and Bokeh's CSS files for our plot to display correctly. Next to your templates
directory, create a static
directory. In this directory, make two directories: css
, and js
.
In css
, add these files:
bootstrap-bokeh-2.0.4.css
bokeh.css
continuum.css
In js
, add this file:
bokeh.js
(Remember that our HTML snippet wants to save our embed.js to js
- it has to exist for this to work. This is also where our plot's JS file will be saved.)
Next, add this block after the title tag in plots.html
:
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-bokeh-2.0.4.css') }}" type="text/css" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/bokeh.css') }}" type="text/css" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/continuum.css') }}" type="text/css" />
<script type="text/javascript" src="{{ url_for('static', filename='js/bokeh.js') }}"></script>
This will point the page to our CSS and JS files, and load them so we can use them.
There's a problem here. Where do we get the bokeh.js, continuum.css, etc. files? I got mine from the bokeh-server examples, but that's not an idea solution. Is there an online repository for these files ala jQuery? Otherwise, where can you download them?
Now, let's return to views.py
. First, we need to import the Flask function that lets us render a template. Make sure you have this at the top of your views.py
file:
from flask import Flask, render_template
This imports everything you will need to render our plot. However, we still need the function to create the plot. We can grab the function from plots.py
like this:
from plots import build_plot
Next, replace hello_world()
with this:
@app.route('/')
def render_plot():
plot_snippet = build_plot()
return render_template('plots.html', snippet=plot_snippet)
Everything should now be in place. Run python views.py
in your app's directory, navigate to localhost:5000
, and the plot should show up! You've now embedded a plot in a Flask webpage.
You should be able to add your own CSS, JS, HTML, etc. on top of this.
I've uploaded the entire set of files to a Github repo.
===
Issues:
-
The downside to this is that a unique embed.js file is created every time you access the page. Naturally, this has the danger of running up a whole lot of space.
-
I'm trying to create a JSFiddle of this as an example. However, JSFiddle doesn't seem to let me upload the CSS/JS files from my computer. Since I don't have a link to these files, I can't upload them to JSFiddle...unless...