Skip to content

Reactive event driven Bokeh Charts

Johannes Eckstein edited this page Feb 6, 2020 · 2 revisions

Reactive event and Python backend driven Bokeh charts in Angular

How you can overcome the drawbacks of ajax calls and make bokeh charts more reactive.

Why data polling via Ajax Methods is a dead end

Ajax calls from web-frontend side seem to be the quasi standard when it comes to regularly updated data views. But Ajax calls under the hood use the polling technique. This means the client asks every time-period on server side for new data via a http call which in fact has several drawbacks:

  • slows down the overall reactiveness of the view, especially when it comes to polling time below 50 ms.
  • the polling time is crucial when you have to decide before hand, which delay time you want to admit. If you want to have short time delay upon a data change event, polling is definately not the best option.
  • even if data is not changing at all or stays inside non-critical bounds, the polling will go on forever, which is a extra load on the network and on the server especially when the data platform needs to scale up with the amount of data and view requests.

These are in fact also the reasons why an event-driven update technology will always outperform polling techniques. We will show you how this can be reached within an angular-bokeh setup.

Events in Angular

Angular has implemented and in fact is build upon an inherent event driven mechanism, that's why it is evidently very attractive to combine Python's async paradigm with Angular event detection.

On Python side the most mature web server which deals with async is aiohttp. It offers also a simple possibility to open up a websocket server onto which the Angular Client can connect to.

In our last article we published the combination of Angular with Bokeh charts, we send the chart in json format via the websocket connection to angular. But we had to rely on the ajax calls in order to update the charts since the BokehJS API offers mainly the black boxed embed method for embedding charts. After investigating the API a little bit deeper it seems very viable not to use the embed method but to go through the embedding step by step. Thus we have been able to extract the bokeh Document object and from the this object we got access to the data source.

Since there are very view examples available on the internet we decided to give a little bit of insight into what you can do with the source object and how we can in fact use it to change the update paradigm to event driven.

Source object update

We are now focusing on the Angular side. After receiving the chart-item produced in Python in json format in item.doc we may produce a bokeh document object with

const doc = Bokeh.Document.from_json(item.doc);

The source object can be extracted from the Document by the call

const source = doc.get_model_by_name('my-tiles').data_source

where it is important to use the same name 'my-tiles' also in the backend for the data model. The embedding now works like

const roots: Roots = {[item.root_id]: el};
Bokeh.embed.add_document_standalone(doc, el, roots, false);

where the element is the Http-Element, which usually available by its id.

const el = document.getElementById(id);

The document in the line before has nothing to do with the bokeh document, it's just the global app document hook. Now that we have access to the source it is not so difficult to proceed with our usual event driven update.

    public getNumberChartData(source: any) {
        const callbackId = 'numberChartData';
        this.messageService.awaitMessage().pipe(filter(msg => msg.name === callbackId))
            .subscribe(
            msg => {
                source.data.label[0] = msg.args.number;
                source.data.color = msg.args.color;
                source.change.emit();
            });
    }

Whenever a relevant websocket message arrives in the MessageService, the source change event is called, which causes an inherent chart update. This subscription must be done only once for any new source and it should be made sure, that when the chart component is removed the subscription is unsubscriped.

Python side: observer and observables

When it comes to async programming, it seems that the best-practice examples must still be searched for. The async paradigm is confusing because it kind of breaks with simple futures and reduces the asynchronous programming effectively down to 3 things: async, await and somewhere the loop floating around. Since most examples tend to be singular and small it seems in the first place very difficult to setup a whole project to async especially if one tries to combine async and non-async functions. Sometimes it is even not possible to introduce async, e.g. for the often used __init__ function. It simply does not accept an async in front.

Also it seems staggering, that the loop mainly serves as a long-lasting blocking call and everthing what is inside the loop is to be concerned of and everthing what is outside at that moment is out forever. Well that is not really true but according to our experience it is best practice if everthing is inside the loop, when it comes to run_until_complete.

To switch to a fully event-driven paradigm, it will be much easier to setup if we introduce an observer-observable paradigm within the module nucosObs. Therefore we build a simple yet powerful observer mechanism, which allows to avoid distributing each method reference onto that place we want to call it. On the other hand we now have to distribute the observables which kind of carry the information for any observer that is listening on that observable. Also the way we react onto data inside the observables may be adjusted very easily afterwards and individually.

All observable are fed with data in so called interfaces.This can be for example a text input on the console or a websocket server. On the interface we may focus only how to get data from the source and feed it into the observable. This task then is totally seperated from the task, what to do with data. In the legacy context usually a callback is connected to data, but even that is not necessary here.

The observers on the other hand are setup before starting the main loop call. But they include all kind of reactions inherently. They listen on the observable and parse that data for any purpose you want, even by calling subsequent methods. The parse function may be indvidualized very easily.

As a bonus the observers may be scheduled very easily for single or regular tasks.