Bokeh Rich Dashboards

As Bokeh moves towards a 1.0 version, the number of features and shortcuts that users can use to build rich apps and dashboards increases with every new release. Some of the hot questions recently being asked are:

  • Do I need bokeh server to build a dashboard?
  • Is it ready for production?
  • I have a large custom dataset, can I use Bokeh to visualize my data?

Well, as in most cases the answer is YES .. and NO! Those are very generic questions (more or less like when someone asks “is Python a good programming language?”) and the right answer is different in every single specific case. Although valid, the previous questions are not really useful without the needs, the context, and a solid understanding of the technology being discussed.

So my advice before starting is:

  • understand the user requirements (expressed in features and performance), the system specifications, constraints, deadlines, etc..
  • collect information about the Bokeh project, read its documentation, try the examples, etc…

With that in mind, the intention of the this blog post is to show a combination of some of the recent additions to Bokeh that are probably less known then the bokeh-server but can be a very helpful alternative to create custom rich interactive dashboards.

For this post we are going to be looking at the Stocks Resampling Demo from the bokeh-demos repository, where you can download the code and try it yourself. To summarize, the demo implements a dashboard that shows stocks prices over the years and supports a downsampling logic to recreate the main plot when an user selects a region on the upper selection plot.

Instead of explaining line by line I’ll talk about the main Bokeh objects acting together in this demo and how they are being used.

Bokeh Ingredients:

  1. plotting API

  2. AjaxDataSource

  3. CustomJS (previously known as Callback)

  4. bokeh.embed.components

Extra Ingredients:

  • a web service to serve your plot data (in our demo we are using a flask app)

To see how those ingredients can be used to build a dashboard take a look at the custom_stocks_panel.py file that can be found at the repo mentioned earlier.

Here’s a brief explanation of each of those ingredients and how they work together on that example:

1. Plotting API

Bokeh plotting API is the “mid level” interface that can be used to create plots and widgets on your page. It has been part of the library for quite a while and probably is the ingredient of our demo that needs less presentation. But before going ahead there is one aspect that is good to remark and that is useful to understand: every glyph draw on a plot figure created by the plotting API is connected to a “data source”, usually a ColumnDataSource. I encourage the reader to look at the documentation for a better understanding of what a ColumnDataSource is but to simplify let’s consider it as just a bag of data that the figure renderers use to retrieve the data they need to create the glyphs when drawing the figure on the browser.

The functions create_main_plot and create_selection_plot are 2 examples of of the plotting API in action.

2. AjaxDataSource

While the name may sound fancy or complicated for this different type of data source, it’s not. Trying to keep things simple, if we defined a ColumnDataSource as “just a bag of data” we can simplify the explanation of the AjaxDataSource by saying that it’s like a ColumnDataSource but instead of keeping this bag of data locally it keeps being synchronized with a remote service that returns the data when asked.

In the demo the data source is defined as:

source = AjaxDataSource(data_url='http://localhost:5000/data', polling_interval=1000)

 

Python

 

It means that Bokeh is going to send a get request to ‘http://localhost:5000/data’ every second to get updated data (if there’s any). It’s a simple code change but very powerful!

3. CustomJS

CustomJS (formerly called Callback) is another very powerful feature of Bokeh added in recent releases. It makes possible to create small snippets of javascript code (let’s call them “event handlers”) from your python code that are going to be executed upon some user actions on the browser. Those event handlers can be added to a considerable set of bokeh objects (like widgets, tools, data sources, etc..). For instance it’s possible to execute custom javascript code when an user selects a value on a slider widget or when hovers the mouse over a plot or selects subset of elements on a plot. It’s also possible to pass the Bokeh objects available on your python code to your javascript code using an environment dictionary where you map javascript object names (that are going to be available in your javascript function namespace) to python objects (available in your python code). The following lines taken from that demo show a use case applied to a ColumnDataSource:

select_tool = selection_plot.select(dict(type=BoxSelectTool))
   select_tool.dimensions = ['width']
 
   code = """
       if (window.xrange_base_start == undefined){
           window.xrange_base_start = main_plot.get('x_range').get('start');
       }
       if (window.xrange_base_end == undefined){
           window.xrange_base_end = main_plot.get('x_range').get('end');
       }
 
       data = source.get('data');
       sel = source.get('selected')['1d']['indices'];
       var mi = 1000000000;
       var ma = -100000;
       if (sel.length == 0){
          var url = "http://127.0.0.1:5000/alldata";
          source_data = selection_source.get('data');
          source_data.bottom = []
          source_data.values = [];
          source_data.start = [];
          source_data.end = [];
 
           // reset main plot ranges
           main_range.set('start', window.xrange_base_start);
           main_range.set('end', window.xrange_base_end);
       }else{
          for (i=0; i<sel.length; i++){
           if (mi>sel[i]){
               mi = sel[i];
           }
           if (ma<sel[i]){
               ma = sel[i];
           }
          }
          var url = "http://127.0.0.1:5000/subsample/"+data.Date[mi]+"/"+data.Date[ma];
          source_data = selection_source.get('data');
          source_data.bottom = [0]
          source_data.values = [700];
          source_data.start = [data.Date[mi]];
          source_data.end = [data.Date[ma]];
 
          main_range = main_plot.get('x_range');
          main_range.set('start', data.Date[mi]);
          main_range.set('end', data.Date[ma]);
       }
 
       xmlhttp = new XMLHttpRequest();
       xmlhttp.open("GET", url, true);
       xmlhttp.send();
 
       selection_source.trigger('change');
 
       if (sel.length==0){
           $("#details_panel").addClass("hidden");
           $("#details_panel").html("");
       }else{
 
           var url = "http://127.0.0.1:5000/details";
           xhr = $.ajax({
               type: 'GET',
               url: url,
               contentType: "application/json",
               // data: jsondata,
               header: {
                 client: "javascript"
               }
           });
 
           xhr.done(function(details) {
               $("#details_panel").removeClass("hidden");
               $("#details_panel").html("<h3>Selected Region Report</h3>");
               $("#details_panel").append("<div>From " + details.start + " to " + details.end + "</div>");
               $("#details_panel").append("<div>Number of original samples " + details.original_samples_no + "</div>");
               $("#details_panel").append("<div>Number of samples " + details.samples_no + "</div>");
               $("#details_panel").append("<div>Factor " + details.factor + "</div>");
           });
       }
 
   """
 
   callback = Callback(
          args={'source': static_source,
                'selection_source': selection_source,
                'main_plot': main_plot},
          code=code)
   static_source.callback = callback

 

Python

 

Whenever the data of the static_source ColumnDataSource is selected the javascript is executed. It’s worth noticing how bokeh python objects are “passed” to the javascript code by using the “args” dictionary argument as a namespace extension.

4. bokeh.embed.components

Using components is not really mandatory and we could have just used Bokeh layout objects such as HBox and VBox but at the moment using components give us a much higher level of style customization and application extendability. What it does is to return the individual Bokeh components for a inline embedding in your own web page. The components() function takes either a single PlotObject, a list/tuple of PlotObjects, or a dictionary of keys and PlotObjects. Each returns a corresponding data structure of script and div pairs (please refer to the related documentation section for more details).

It’s possible to see it being used on the demo newapplet view function to retrieve the components and pass them to a custom template named “stocks_custom.html” that can be found inside the templates folder.

It’s also worth mentioning that it’s possible to use this feature to create customized styles. In the case of the demo a shared style variable is being used by the python code to style the bokeh objects (see the style_axis function) and by the main view serving the page to select the specific theme css file (see the template file at templates/stocks_custom.html)

EXTRA. Using a custom flask server

If you have an external web service that you can communicate with to control your data and your app behaviour then you can actually achieve very powerful functionalities. In the specific case of this demo the custom flask service is the final component that interacts with the previous Bokeh features listed before to provide a live downsampling functionality.

The flask server code implemented by the flask_server_minutes.py file that can be found in the bokeh-demos repository mentioned above. The interesting parts are the views that are used by the AjaxDataSource to retrieve the data (see view function get_data) and the views exposed to ask the server to resample the data on a different selected interval (see the view function subsample).

Final Thoughts

The demo that we have been considering in this post is an example of how different components of bokeh can be put together to build a rich featured customized dashboard without much code.

We hope you find this post useful.


About the Author

Q. What is your superpower(s)?

A. Developer

Q. What is your technical specialty or area of research?

A. Currently Bokeh core dev.

Before joining Continuum have been usin …

Read more

Join the Disucssion