Using Bokeh with Bottle Web Framework with the Simple Templating Engine

Bokeh Bottle

Introduction

I created a small workout app that uses the Bottle, Nginx and uwsgi on a Raspberry Pi B+. I created the app partly out of necessity, but mostly as a learning activity. After a while, I wanted to be able to review my activity over time. While I’m comfortable enough running sql commands, I decided that it would be a good opportinity to use my newly acquired Bokeh skills.

The Problem

All of the documentation that I could find used either Flask or Django frameworks, but after some vigorous searching, I eventually found this Github repo that actually uses Bottle with Bokeh. The only problem, the author of the repo uses Jinja2 templating (and rightly so), and my app so far was using the simple templating engine that is standard. I had to figure out how to get it to work.

My Solution

After reading up on the documentation, and studying several examples, I finally dove in. My first thought (because I’m lazy) was to just create a static html file using bokeh.io.output_file, then creating a daily cron job to re-generate the file. That way, I could have a static route to the file and I would have to learn anything else. Instead, I decided to actually figure it out. Here is my example. I’m using a model-view-controller design pattern. As a controller, here is my chart.py:

import pandas as pd

from bokeh.charts.utils import cycle_colors
from bokeh.embed import components
from bokeh.models import DatetimeTickFormatter
from bokeh.plotting import figure
from bokeh.resources import CDN
from bottle import template
from project import app

import sqlite3

from datetime import datetime
from math import radians

DB_PATH = 'workout.db'


@app.route('/charts')
def dashboard():
    sql = """
    select w.amt, w.qty, w.xdate, wt.name as workout_type
    from workouts w
    join workout_types wt on w.workout_type = wt.id
    where w.xdate > date('now', '-30 days');
    """

# Get Data
    with sqlite3.connect(DB_PATH) as conn:
        data = pd.read_sql_query(sql, conn)

    wtypes = list(data.workout_type.unique())
    palette = cycle_colors(wtypes)

# Define chart
    f = figure(x_axis_type='datetime')

# Colorize each workout type, size by quantity
    for wtype, color in zip(wtypes, palette):
        f.circle(x=pd.to_datetime(data['xdate'][data["workout_type"] == wtype]),
                 y=data['amt'][data["workout_type"] == wtype],
                 fill_alpha=0.2,
                 size=data['qty'] * 4,
                 color=color,
                 legend=wtype)

# Stylize chart
    f.title.text = "Size = quantity"
    f.title.align = "center"
    f.xaxis.axis_label = "Date"
    f.xaxis.major_label_orientation = radians(90)
    f.xaxis.formatter = DatetimeTickFormatter(formats=dict(
        seconds=["%Y-%m-%d-%H-%M-%S"],
        minsec=["%Y-%m-%d-%H-%M-%S"],
        minutes=["%Y-%m-%d-%H-%M-%S"],
        hourmin=["%Y-%m-%d-%H-%M-%S"],
        hours=["%Y-%m-%d-%H-%M-%S"],
        days=["%Y-%m-%d"],
        months=["%Y-%m-%d"],
        years=["%Y-%m-%d"],
    ))

    f.yaxis.axis_label = "Amount"

# Serve chart to template
    js, div = components(f)
    cdn_js = CDN.js_files[0]
    cdn_css = CDN.css_files[0]
    return template('charts/home', js=js, div=div,
                    cdn_js=cdn_js, cdn_css=cdn_css)

You can infer some things about the data model from my SQL query.

Here is template base view:

<!doctype html5>
     <head>
         <link rel="stylesheet" type="text/css" href="/css/bootstrap.css">
         <link rel="stylesheet" type="text/css" href="{{ cdn_css }}"/>
         <title>{{ title }}</title>
         <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
     </head>
     <body class="container">
         <nav class="navbar navbar-default">
             <a class="btn btn-info" href="/">Home</a>
             <a class="btn btn-info" href="/admin">Admin</a>
         </nav>
         <div class="page">
             %include
        </div>
        <footer class="footer"><div class="navbar navbar-default navbar-fixed-bottom">
        <center class="text-muted">RY Workouts</center>
        </div></footer>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script src="{{ cdn_js }}"</script>
    <script src="/js/bootstrap.min.js"></script>
    </body>
</html>

Here is the content of the view:

<h1>Dashboard</h1>

<div class="panel">
    {{ !div }}
    {{ !js }}
</div>

<p>Chart shows last <em>30 days</em> of activity</p>
%rebase layout/charts title="Dashboard"

Summary

The key was to use bokeh.embed.components and bokeh.resources.CDN to embed both the chart and the javascript and css into the template. Then, I just had to call these items in the templates. Using this design pattern, it should be able to create many other charts. Below is what the resulting graph looks like.

Bokeh Plot

www.000webhost.com