24 July 2017 / Luke Canavan
Web developers typically confine their business logic code into JS files, their layouts into HTML templates, and their styling into CSS files. This decoupling makes navigating and working on a code base easier.
In Bokeh, it’s just as valuable to decouple the styles of your Models from the business logic that generates them. In this post, I’ll share two ways I’ve found helpful for doing this.
Additionally, I’ve created a Monokai-inspired dark theme for Bokeh that you can use. It makes Bokeh visualizations look like this:
You can download the Theme file here and read below about how to use it.
One solution to separating styles from implementation is to create an adjacent python module (I generally call it “styles.py”) that contains dictionaries of Bokeh style properties for Models. To me, the distinction of whether something belongs in the styles dictionary or business logic is whether it’s based on data. If a Glyph color is computed based on some data value, it belongs in the business logic. Otherwise, it should go in a styles dictionary. Based on that rubric, I’ll even include some simple Bokeh Models like Tickers and Formatters in my styles dictionary.
Here’s an example styles module:
#### contents of styles.py from bokeh.models import BasicTicker, PrintfTickFormatter DARK_GRAY = "#282828" BROWN_GRAY = "#49483E" PLOT_OPTS = dict( background_fill_color=DARK_GRAY, border_fill_color=DARK_GRAY, outline_line_color=BROWN_GRAY, plot_height=600, plot_width=800 ) AXIS_OPTS = dict( axis_label_standoff=10, axis_label_text_font_size="15pt", axis_line_color=BROWN_GRAY, ticker=BasicTicker(num_minor_ticks=2), formatter=PrintfTickFormatter(format="%4.1e") )
Next, I’ll splat these style dictionaries into the appropriate Models when instantiating them. Here’s how it looks in practice:
from bokeh.models import Plot, Range1d, LinearAxis from .styles import PLOT_OPTS, AXIS_OPTS plot = Plot(x_range=Range1d(), y_range=Range1d(), **PLOT_OPTS) plot.add_layout( LinearAxis(**AXIS_OPTS), "left" ) plot.add_layout( LinearAxis(**AXIS_OPTS), "below" )
The benefit of using style dictionaries is their simplicity. They make visualization code much shorter and readable because they pull out all of your styling code into a separate module. I think styles dictionaries most useful for simpler visualization, perhaps embedding a single plot within a web app.
The alternative to using styling dictionaries is creating Bokeh Themes. Themes are a specification for creating custom defaults for Bokeh Models via a YAML file or a JSON. I think that Themes are a fantastic way to maintaining a consistent style across a larger set of visualizations because you don’t have to remember to explicitly add styles to individual models. Here’s an example of a YAML theme file:
#### contents of theme.yaml attrs: Plot: background_fill_color: "#282828" border_fill_color: "#282828" outline_line_color: "#49483E" Axis: axis_label_standoff: 10 axis_label_text_font_size="15pt" axis_line_color: "#49483E" BasicTicker: num_minor_ticks: 2 PrintfTickFormatter: format: "%4.1e"
When you set your Document’s
theme property, all of your custom styles
are applied to the appropriate models. You can read more in the Themes
Here, we’re attaching our Theme to our Bokeh Document:
from bokeh.theme import Theme theme = Theme(filename="./theme.yaml") doc = Document(theme=theme) #### or Document().theme = theme doc.add_root(plot)
Now the Axis styles in our yaml file are applied to all Axis Models within our Document!
The styling dictionary approach seems to work best when developing via the
bokeh.models API, where you’re explicitly creating and adding all of your
models. (You can read my previous
blog post about the
bokeh.models API). In the
bokeh.plotting API some plot components, like
axes for example, are implicitly created inside the
figure method. This is
where Themes may be better solution because they will automatically be applied
to all relevant Models.
What if you want different styles for the same model type? It’s possible to
have different style dictionaries for distinct model instances (i.e. having
Y_AXIS_OPTS dicts for a LinearAxis Model).
Alternatively, since Themes just change the model defaults, it’s possible to
override the new defaults however you normally would.
Finally, Themes are easier to share with others. You should be able to download or copy and paste the Theme file from here into a local file and attach it to any visualization you already have.
Unrelated to styling dictionaries or themes, I have a couple of styling pro-tips that I’d like to share:
Bokeh’s styling is very nice by default. However, extending Bokeh with your own custom styles can add an impressive level of polish to your visualizations. Now that you’re an expert in styling, here are some things to do next: