Running bokeh callbacks in separate web worker

I’m hoping this will come as a refreshing change because I’m approaching this forum with a solution rather than a problem. What do you all think of the idea I’m exploring below? I have adapted one example from the JavaScript Callbacks documentation page to pure javascript to execute callbacks in a separate web worker. The web worker message-passing API neatly leverages Bokeh’s protocol and acts as surrogate bokeh server. Comments and reactions welcome.

<html lang="en">
<head>
<meta charset="UTF-8">
<**link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh.min.css" **/>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh-widgets.min.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh-api.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh-widgets.js"></script>
</head>
<body>
<div class="bk-root" id="bokeh-root"></div>
<script type="text/js-worker">
{
// Stub out a few globals so importScripts doesn’t barf
self.setImmediate = (func, …params) => setTimeout(func, 0, …params);
self.document = {addEventListener() {}, createElement() {return {style: {}}}, createTextNode() {}};
self.window = {};
self.Image = {};
importScripts(
https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh.min.js’,
https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh-api.min.js’,
'https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh-widgets.min.js’);
const
doc = new Bokeh.Document(),
setter_id = ‘worker’;
doc.on_change(event => ‘worker’ === event.setter_id || postMessage(doc.create_json_patch([event])));
self.onmessage = event => doc.apply_json_patch(event.data, , ‘worker’)
}
</script>
<script>
{
**const
*****doc ***= **new **Bokeh.Document(),
***blob ***= new Blob([document.querySelector(“script[type=‘text/js-worker’]”).textContent], {type: ‘text/javascript’}),
***worker ***= **new **Worker(URL.createObjectURL(blob));

···
  • *doc.on_change(event => **‘main’ **=== event.setter_id || worker.postMessage(doc.create_json_patch([event])));
    worker.addEventListener(
    ‘message’
    , message => doc.apply_json_patch(message.data, , ‘main’));
    **function **cb(source, cb_obj) {
    *// make sure it only runs in Worker
    ***if (‘undefined’ **!== **typeof *****WorkerGlobalScope ***&& ***self *****instanceof **WorkerGlobalScope) {
    **const
    **x = source.data.x,
    f = cb_obj.value,
    data = {x, y: x.map(xi => Math.pow(xi, f))};
    source.setv({data}, {setter_id: ‘callback’})
    }
    }
    **const
    *****x ***= Bokeh.LinAlg.range(0, 200).map(x => x * .005),
    ***y ***= […x],
    ***source ***= **new **Bokeh.ColumnDataSource({data: {x, y}}),
    ***plot ***= Bokeh.Plotting.figure({plot_width: 400, plot_height: 400}),
    ***callback ***= **new **Bokeh.CustomJS({args: {source}, code: (**${*cb*.toString()}**)(source, cb_obj, cb_data)}),
    ***slider ***= **new **Bokeh.Widgets.Slider({start: .1, end: 4, value: 1, step: .1, title: ‘power’, deferred: true}),
    col = new **Bokeh.Column({children: [slider, plot]});
    *// install CustomJS so it gets serialized to worker
    *slider.js_property_callbacks[
    ‘change:value’
    ] = [callback];
    slider.finalize();
    plot.line({field: ‘x’}, {field: ‘y’}, {source, line_width: 3, line_alpha: 0.6});
    doc.add_root(col);
    Bokeh.embed.add_document_standalone(doc, document.querySelector(
    #bokeh-root
    ));
    }
    </script>
    </body>
    </html>

Hi Jan,

My guess is that this may be of general interest, my main comments immediately are:

* the BokehJS API is still very much a work-in-progress (we are resource constrained to work on it much)

* as a result, the BokehJS docs are under-developed

Also, I don't really have any experience with Web Workers personally, so I can't really comment at present on the technical side of this approach.

I think maybe the best thing for now, to make sure this does not get lost in the shuffle, would be to make a new GH issue discussion with this information, on the premise that it could get added to BokehJS docs in the future, when they get more developed. We may have some funding for someone to spend some time on some docs issues this summer, and if so, BokehJS docs is one of the possible tasks for them.

Thanks,

Bryan

···

On Jun 11, 2018, at 08:53, Jan Burgy <[email protected]> wrote:

I'm hoping this will come as a refreshing change because I'm approaching this forum with a solution rather than a problem. What do you all think of the idea I'm exploring below? I have adapted one example from the JavaScript Callbacks documentation page to pure javascript to execute callbacks in a separate web worker. The web worker message-passing API neatly leverages Bokeh's protocol and acts as surrogate bokeh server. Comments and reactions welcome.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">

    <link rel=“stylesheet” type=“text/css” href=“https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh.min.css” />
    <link rel=“stylesheet” type=“text/css” href=“https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh-widgets.min.css”/>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh-api.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh-widgets.js"></script>
</head>
<body>

<div class="bk-root" id="bokeh-root"></div>

<script type="text/js-worker">
{
    // Stub out a few globals so importScripts doesn't barf
    self.setImmediate = (func, ...params) => setTimeout(func, 0, ...params);
    self.document = {addEventListener() {}, createElement() {return {style: {}}}, createTextNode() {}};
    self.window = {};
    self.Image = {};
    importScripts(
        'https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh.min.js',
        'https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh-api.min.js',
        'https://cdnjs.cloudflare.com/ajax/libs/bokeh/0.12.16/bokeh-widgets.min.js');

    const
        doc = new Bokeh.Document(),
        setter_id = 'worker';

    doc.on_change(event => 'worker' === event.setter_id || postMessage(doc.create_json_patch([event])));
    self.onmessage = event => doc.apply_json_patch(event.data, , 'worker')
}
</script>

<script>
{
    const
        doc = new Bokeh.Document(),
        blob = new Blob([document.querySelector("script[type='text/js-worker']").textContent], {type: 'text/javascript'}),
        worker = new Worker(URL.createObjectURL(blob));

    doc.on_change(event => 'main' === event.setter_id || worker.postMessage(doc.create_json_patch([event])));
    worker.addEventListener('message', message => doc.apply_json_patch(message.data, , 'main'));

    function cb(source, cb_obj) {
        // make sure it only runs in Worker
        if ('undefined' !== typeof WorkerGlobalScope && self instanceof WorkerGlobalScope) {
            const
                x = source.data.x,
                f = cb_obj.value,
                data = {x, y: x.map(xi => Math.pow(xi, f))};
            source.setv({data}, {setter_id: 'callback'})
        }
    }

    const
        x = Bokeh.LinAlg.range(0, 200).map(x => x * .005),
        y = [...x],
        source = new Bokeh.ColumnDataSource({data: {x, y}}),
        plot = Bokeh.Plotting.figure({plot_width: 400, plot_height: 400}),
        callback = new Bokeh.CustomJS({args: {source}, code: `(${cb.toString()})(source, cb_obj, cb_data)`}),
        slider = new Bokeh.Widgets.Slider({start: .1, end: 4, value: 1, step: .1, title: 'power', __deferred__: true}),
        col = new Bokeh.Column({children: [slider, plot]});

    // install CustomJS so it gets serialized to worker
    slider.js_property_callbacks['change:value'] = [callback];
    slider.finalize();

    plot.line({field: 'x'}, {field: 'y'}, {source, line_width: 3, line_alpha: 0.6});

    doc.add_root(col);
    Bokeh.embed.add_document_standalone(doc, document.querySelector('#bokeh-root'));
}
</script>

</body>
</html>

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/e6ca806b-cfcb-4cd1-bcea-796dabf4a303%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Sounds good, I'll open an issue to discuss whether this belongs in the BokehJS documentation or should be killed with fire.