Dynamic Plot Updates using Django and Bokeh Server

Hello!

Having searched the web and this forum extensively but without success for an answer, here is my challenge:

My goal is to plot a schedule of intervals (with attributes: start, end) that are stored in a database and accessed via Django’s (ORM) classes.

The tricky(?) part is that:
a) a line marker shall be drawn on the plot that indicates the current time (and be updated every 10 sec)
b) the plot shall initially display 3 days and load data only for this timespan. When user pans the plot, data shall be dynamically loaded and added to the plot (-> range update)

I have implemented this with using AjaxDataSource for the periodic time marker update and ColumDataSource with CustomJS for the x_range callback:

source = ColumnDataSource()

def callback(source=source, window=None):
def on_range_update():
xhr = window.XMLHttpRequest()
data = source.get(‘data’) # the data on the client
window_start = int(cb_obj.get(‘start’))
window_end = int(cb_obj.get(‘end’))
buffer_start = data[‘start’][0]
buffer_end = data[‘end’][-1]
url_data_right =
url_data_left =

    if window_start < buffer_start:
        # append data left
        url_data_left = "/schedule/api/passes?element_b=1&" + \
                        "start=" + window_start + "&" + \
                        "end=" + buffer_start
       
    if window_end > buffer_end:
        # append data right
        url_data_right = "/schedule/api/passes?element_b=1&" + \
                         "start=" + buffer_end + "&" + \
                         "end=" + window_end
   
    def onreadystatechange():
        if xhr.readyState == window.XMLHttpRequest.DONE:
            response = window.JSON.parse(xhr.responseText)
            if data['start'][-1] <= response['start'][0]:
                # append data right
                data['start'] = data['start'] + response['start']
                data['end'] = data['end'] + response['end']
                source.trigger('change')
            else:
                # append data left
                data['start'] = response['start'] + data['start']
                data['end'] = response['end'] + data['end']
                source.trigger('change')
           
    if url_data_right:
        xhr.onreadystatechange = onreadystatechange  
        xhr.open('GET', url_data_right, False)
        xhr.send()
       
    if url_data_left:
        xhr.onreadystatechange = onreadystatechange  
        xhr.open('GET', url_data_left, False)
        xhr.send()

if window.range_update_timeout != 0: # cancel the previous update request
    window.clearTimeout(window.range_update_timeout)
    window.range_update_timeout = 0

window.range_update_timeout = window.setTimeout(on_range_update, 500)

def passes_view(request):

now = datetime.utcnow()
start = now - timedelta(hours=24)
end = now + timedelta(hours=24)
       
p1 = figure(x_axis_type='datetime',
            tools='xwheel_zoom,xpan',
            active_scroll="xwheel_zoom",
            )

# ensure that plot shows the zoom region  
border_start = 1000 * calendar.timegm(start.utctimetuple())
border_end = 1000 * calendar.timegm(end.utctimetuple())
p1.x_range = Range1d(border_start, border_end)

# place current time marker
source_utc = AjaxDataSource(method='GET',
                            data_url=reverse('api', args=['utc_now']),
                            polling_interval=5000)  
utc_now = 1000 * calendar.timegm(datetime.utcnow().utctimetuple())
source_utc.data = dict(utc_now=[utc_now])
p1.quad(source=source_utc, color='red',
        top=1.5, bottom=-1.5, left='utc_now', right='utc_now')

# load passes from database
model = Pass    
qs = model.objects.all()
qs = qs.filter(end__gte=start)
qs = qs.filter(start__lte=end)
qs = qs.filter(element_b=1)
qs = qs.order_by('start')

qr = [[] for i in range(2)]
for i in qs:
    qr[0].append((i.start - datetime(1970,1,1))
                     / timedelta(milliseconds=1))
    qr[1].append((i.end - datetime(1970,1,1))
                     / timedelta(milliseconds=1))

source.data = dict(start=qr[0], end=qr[1])
print(source.data)
p1.quad(source=source, top=1, bottom=-1, left='start', right='end')
p1.x_range.callback = CustomJS.from_py_func(callback)

 
plot = p1

script, div = components(plot, INLINE)
context = {
    'bokeh_script' : script,
    'bokeh_div' : div,
    'js_resources' : INLINE.render_js(),
    'css_resources' : INLINE.render_css()
    }
return render(request, 'schedule/passes_view.html', context)

``

As you can see, the code is quite ugly and things become worse if more plots are involved. So, using a bokeh server from which my Django application can push to/pull from seems to me a solution to this.

However, I am missing a roadmap for this… The bokeh documentation provides examples for bokeh apps, and in the “hapiness” example there is a disclaimer that it is not working with periodic updates (https://github.com/bokeh/bokeh-demos/blob/master/happiness/Building%20happiness.ipynb)

Does someone has a boilerplate code for using bokeh server with django? I imagine that the bokeh server is started from script before starting django server, and then is accessible to all django apps.

Thanks
Artur

Hi Artur,

I cannot answer your question. But I can give you something like a

workaround/idea.

I use bokeh with django and I use the approach of CustomJS.

Use the callback function of your zoomtools.

In the zoom-callback you get the data range from the geometry of the

plot.

with the data range you can update the data of the datasource from

django with ajax calls.

Raphael
···

On 2016-09-15 15:17, Artur Scholz
wrote:

Hello!

    Having searched the web and this forum extensively but without

success for an answer, here is my challenge:

    My goal is to plot a schedule of intervals (with attributes:

start, end) that are stored in a database and accessed via
Django’s (ORM) classes.

    The tricky(?) part is that:

    a) a line marker shall be drawn on the plot that indicates the

current time (and be updated every 10 sec)

    b) the plot shall initially display 3 days and load data only

for this timespan. When user pans the plot, data shall be
dynamically loaded and added to the plot (-> range update)

    I have implemented this with using AjaxDataSource for the

periodic time marker update and ColumDataSource with CustomJS
for the x_range callback:

source = ColumnDataSource()

          def callback(source=source, window=None):  

                def on_range_update():

                    xhr = window.XMLHttpRequest()

                    data = source.get('data')                 # the

data on the client

                    window_start = int(cb_obj.get('start'))

                    window_end = int(cb_obj.get('end'))

                    buffer_start = data['start'][0]

                    buffer_end = data['end'][-1]    

                    url_data_right = []

                    url_data_left = []

                         

                    if window_start <
            buffer_start:

                        # append data left

                        url_data_left = "/schedule/api/passes?element_b=1&" + \

                                        "start=" +
            window_start + "&" + \

                                        "end=" +
            buffer_start

                       

                    if window_end >
            buffer_end:

                        # append data right

                        url_data_right = "/schedule/api/passes?element_b=1&" + \

                                         "start=" +
            buffer_end + "&" + \

                                         "end=" +
            window_end

                   

                    def onreadystatechange():

                        if xhr.              readyState

== window.XMLHttpRequest.DONE:

                            response = window.JSON.parse(xhr.responseText)

                            if data['start'][-1] <=
            response['start'][0]:

                                # append data right

                                data['start'] = data['start'] +
            response['start']

                                data['end'] = data['end'] +
            response['end']

                                source.trigger('change')

                            else:

                                # append data left

                                data['start'] =
            response['start'] + data['start']

                                data['end'] =
            response['end'] + data['end']

                                source.trigger('change')

                           

                    if url_data_right:

                        xhr.onreadystatechange =
            onreadystatechange  

                        xhr.open('GET',
            url_data_right, False)

                        xhr.send()

                       

                    if url_data_left:

                        xhr.onreadystatechange =
            onreadystatechange  

                        xhr.open('GET',
            url_data_left, False)

                        xhr.send()

               

                if window.              range_update_timeout

!= 0: # cancel
the previous update request

                    window.clearTimeout(window.range_update_timeout)

                    window.range_update_timeout = 0

               

                window.range_update_timeout = window.setTimeout(on_range_update, 500)





          def passes_view(request):

               

                now = datetime.utcnow()

                start = now -
            timedelta(hours=24)

                end = now +
            timedelta(hours=24)

                       

                p1 = figure(x_axis_type='datetime',

                            tools='xwheel_zoom,xpan',

                            active_scroll="xwheel_zoom",

                            )

               

                                # ensure that plot shows the

zoom region

                border_start = 1000 *
            calendar.timegm(start.utctimetuple())

                border_end = 1000 *
            calendar.timegm(end.utctimetuple())

                p1.x_range = Range1d(border_start,
            border_end)

               

                # place current time marker

                source_utc = AjaxDataSource(method='GET',

                                            data_url=reverse('api', args=['utc_now']),

                                            polling_interval=5000)  

                utc_now = 1000 *
            calendar.timegm(datetime.utcnow().utctimetuple())

                source_utc.data = dict(utc_now=[utc_now])

                p1.quad(source=source_utc, color='red',

                        top=1.5, bottom=-1.5, left='utc_now', right='utc_now')

               

                # load passes from database

                model = Pass    

                qs = model.objects.all()

                qs = qs.filter(end__gte=start)

                qs = qs.filter(start__lte=end)

                qs = qs.filter(element_b=1)

                qs = qs.order_by('start')

               

                qr = [[] for i in range(2)]

                for i in qs:

                    qr[0].append((i.start -
            datetime(1970,1,1))

                                     /
            timedelta(milliseconds=1))

                    qr[1].append((i.end -
            datetime(1970,1,1))

                                     /
            timedelta(milliseconds=1))

               

                source.data = dict(start=qr[0], end=qr[1])

                print(source.data)

                p1.quad(source=source, top=1, bottom=-1, left='start', right='end')

                p1.x_range.              callback

= CustomJS.from_py_func(callback)

                plot = p1



                script, div =
            components(plot, INLINE)

                context = {

                    'bokeh_script' : script,

                    'bokeh_div' : div,

                    'js_resources' : INLINE.render_js(),

                    'css_resources' : INLINE.render_css()

                    }

                return render(request, 'schedule/passes_view.html', context)

``

    As you can see, the code is quite ugly and things become worse

if more plots are involved. So, using a bokeh server from which
my Django application can push to/pull from seems to me a
solution to this.

    However, I am missing a roadmap for this.. The bokeh

documentation provides examples for bokeh apps, and in the
“hapiness” example there is a disclaimer that it is not working
with periodic updates
()
Does someone has a boilerplate code for using bokeh server with
django? I imagine that the bokeh server is started from script
before starting django server, and then is accessible to all
django apps.
Thanks
Artur

  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/cdddb619-4afb-4360-ac48-db44e52b84ad%40continuum.io](https://groups.google.com/a/continuum.io/d/msgid/bokeh/cdddb619-4afb-4360-ac48-db44e52b84ad%40continuum.io?utm_medium=email&utm_source=footer).

  For more options, visit [https://groups.google.com/a/continuum.io/d/optout](https://groups.google.com/a/continuum.io/d/optout).

https://github.com/bokeh/bokeh-demos/blob/master/happiness/Building%20happiness.ipynb

Thanks, but this is not what I am looking for. Indeed I solved all my needs already using CustomJS callbacks and Ajax polling.
What I need now is to know how to set up django with bokeh server.

I am confused about the concepts of bokeh document, curdoc, output_server, session, …
It should be as simple as:

  • a django view function that prepares the plot and sends it to bokeh server, which in turn is used by the client (via bokehJS) to plot it
  • some other python functions in the view namespace that are used as callbacks for this plot, to react upon range change and to
    receive periodic updates.

Note that preferably the plot creation should be done within the local view function instead of globally (as in all examples I saw), to avoid
having it running when in fact the website is not even visited.

Yet, I have no idea how to start this properly. Tried several things without success.

Artur

···

On Thursday, September 15, 2016 at 3:34:44 PM UTC+2, Raffs Walker wrote:

Hi Artur,

I cannot answer your question. But I can give you something like a

workaround/idea.

I use bokeh with django and I use the approach of CustomJS.



Use the callback function of your zoomtools.

In the zoom-callback you get the data range from the geometry of the

plot.

with the data range you can update the data of the datasource from

django with ajax calls.

Raphael


  On 2016-09-15 15:17, Artur Scholz > wrote:

Hello!

    Having searched the web and this forum extensively but without

success for an answer, here is my challenge:

    My goal is to plot a schedule of intervals (with attributes:

start, end) that are stored in a database and accessed via
Django’s (ORM) classes.

    The tricky(?) part is that:

    a) a line marker shall be drawn on the plot that indicates the

current time (and be updated every 10 sec)

    b) the plot shall initially display 3 days and load data only

for this timespan. When user pans the plot, data shall be
dynamically loaded and added to the plot (-> range update)

    I have implemented this with using AjaxDataSource for the

periodic time marker update and ColumDataSource with CustomJS
for the x_range callback:

source = ColumnDataSource()

          def callback(source=source, window=None):  

                def on_range_update():

                    xhr = window.XMLHttpRequest()

                    data = source.get('data')                 # the

data on the client

                    window_start = int(cb_obj.get('start'))

                    window_end = int(cb_obj.get('end'))

                    buffer_start = data['start'][0]

                    buffer_end = data['end'][-1]    

                    url_data_right = []

                    url_data_left = []

                         

                    if window_start <
            buffer_start:

                        # append data left

                        url_data_left = "/schedule/api/passes?element_b=1&" + \

                                        "start=" +
            window_start + "&" + \

                                        "end=" +
            buffer_start

                       

                    if window_end >
            buffer_end:

                        # append data right

                        url_data_right = "/schedule/api/passes?element_b=1&" + \

                                         "start=" +
            buffer_end + "&" + \

                                         "end=" +
            window_end

                   

                    def onreadystatechange():

                        if xhr.              readyState

== window.XMLHttpRequest.DONE:

                            response = window.JSON.parse(xhr.responseText)

                            if data['start'][-1] <=
            response['start'][0]:

                                # append data right

                                data['start'] = data['start'] +
            response['start']

                                data['end'] = data['end'] +
            response['end']

                                source.trigger('change')

                            else:

                                # append data left

                                data['start'] =
            response['start'] + data['start']

                                data['end'] =
            response['end'] + data['end']

                                source.trigger('change')

                           

                    if url_data_right:

                        xhr.onreadystatechange =
            onreadystatechange  

                        xhr.open('GET',
            url_data_right, False)

                        xhr.send()

                       

                    if url_data_left:

                        xhr.onreadystatechange =
            onreadystatechange  

                        xhr.open('GET',
            url_data_left, False)

                        xhr.send()

               

                if window.              range_update_timeout

!= 0: # cancel
the previous update request

                    window.clearTimeout(window.range_update_timeout)

                    window.range_update_timeout = 0

               

                window.range_update_timeout = window.setTimeout(on_range_update, 500)





          def passes_view(request):

               

                now = datetime.utcnow()

                start = now -
            timedelta(hours=24)

                end = now +
            timedelta(hours=24)

                       

                p1 = figure(x_axis_type='datetime',

                            tools='xwheel_zoom,xpan',

                            active_scroll="xwheel_zoom",

                            )

               

                                # ensure that plot shows the

zoom region

                border_start = 1000 *
            calendar.timegm(start.utctimetuple())

                border_end = 1000 *
            calendar.timegm(end.utctimetuple())

                p1.x_range = Range1d(border_start,
            border_end)

               

                # place current time marker

                source_utc = AjaxDataSource(method='GET',

                                            data_url=reverse('api', args=['utc_now']),

                                            polling_interval=5000)  

                utc_now = 1000 *
            calendar.timegm(datetime.utcnow().utctimetuple())

                source_utc.data = dict(utc_now=[utc_now])

                p1.quad(source=source_utc, color='red',

                        top=1.5, bottom=-1.5, left='utc_now', right='utc_now')

               

                # load passes from database

                model = Pass    

                qs = model.objects.all()

                qs = qs.filter(end__gte=start)

                qs = qs.filter(start__lte=end)

                qs = qs.filter(element_b=1)

                qs = qs.order_by('start')

               

                qr = [[] for i in range(2)]

                for i in qs:

                    qr[0].append((i.start -
            datetime(1970,1,1))

                                     /
            timedelta(milliseconds=1))

                    qr[1].append((i.end -
            datetime(1970,1,1))

                                     /
            timedelta(milliseconds=1))

               

                source.data = dict(start=qr[0], end=qr[1])

                print(source.data)

                p1.quad(source=source, top=1, bottom=-1, left='start', right='end')

                p1.x_range.              callback

= CustomJS.from_py_func(callback)

                plot = p1



                script, div =
            components(plot, INLINE)

                context = {

                    'bokeh_script' : script,

                    'bokeh_div' : div,

                    'js_resources' : INLINE.render_js(),

                    'css_resources' : INLINE.render_css()

                    }

                return render(request, 'schedule/passes_view.html', context)

``

    As you can see, the code is quite ugly and things become worse

if more plots are involved. So, using a bokeh server from which
my Django application can push to/pull from seems to me a
solution to this.

    However, I am missing a roadmap for this.. The bokeh

documentation provides examples for bokeh apps, and in the
“hapiness” example there is a disclaimer that it is not working
with periodic updates
(https://github.com/bokeh/bokeh-demos/blob/master/happiness/Building%20happiness.ipynb)

    Does someone has a boilerplate code for using bokeh server with

django? I imagine that the bokeh server is started from script
before starting django server, and then is accessible to all
django apps.

    Thanks

    Artur

  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/cdddb619-4afb-4360-ac48-db44e52b84ad%40continuum.io](https://groups.google.com/a/continuum.io/d/msgid/bokeh/cdddb619-4afb-4360-ac48-db44e52b84ad%40continuum.io?utm_medium=email&utm_source=footer).

  For more options, visit [https://groups.google.com/a/continuum.io/d/optout](https://groups.google.com/a/continuum.io/d/optout).

Hi,

Unfortunately I can't respond at length, but hopefully a few comments will clear some things up.

It is possible to "prepares the plot and sends it to bokeh server" using "bokeh.client" and "push_session". But I don't recommend this path. Previously the main reason to do this was to afford per-user-session customization. But as of 0.12.2 it is now possible to pass http request arguments to apps that are run directly on the bokeh server (i.e., via "bokeh server myapp.py")

*** I strongly encourage you to write apps in the "bokeh serve myapp.py" instead of using bokeh.client ***

Once you have written an app to run directly on the server, there are two options to "embed it" in a Djanog app:

* an iframe (works great, this is how demo.bokehplots.com works)

* template in the <div> returned by bokeh.embed.autoload_server

Either way, if you need per-session customizations, you can add HTTP request args to the URL for the iframe or for autoload_server, and then the user will see whatever customized version of the app you need to show them.

See:

  Bokeh server — Bokeh 3.3.2 Documentation

For more information about accessing HTTP request arguments.

Using "bokeh.client" has a number of intrinsic drawbacks compared to "bokeh serve myapp.py" style apps:

* double the networks traffic (server just becomes a middleman, relaying traffic between python client and browser)
* much harder to scale (need more capacity? just run more "bokeh serve myapp.py" behind a load balancer)
* actually requires more code to write
* client python process or thread must block permanently if there are callbacks to service (session.loop_until_closed)

Thanks,

Bryan

···

On Sep 15, 2016, at 11:10 AM, Artur Scholz <[email protected]> wrote:

Thanks, but this is not what I am looking for. Indeed I solved all my needs already using CustomJS callbacks and Ajax polling.
What I need now is to know how to set up django with bokeh server.

I am confused about the concepts of bokeh document, curdoc, output_server, session, ....
It should be as simple as:
- a django view function that prepares the plot and sends it to bokeh server, which in turn is used by the client (via bokehJS) to plot it
- some other python functions in the view namespace that are used as callbacks for this plot, to react upon range change and to
receive periodic updates.

Note that preferably the plot creation should be done within the local view function instead of globally (as in all examples I saw), to avoid
having it running when in fact the website is not even visited.

Yet, I have no idea how to start this properly. Tried several things without success.

Artur

On Thursday, September 15, 2016 at 3:34:44 PM UTC+2, Raffs Walker wrote:
Hi Artur,

I cannot answer your question. But I can give you something like a workaround/idea.
I use bokeh with django and I use the approach of CustomJS.

Use the callback function of your zoomtools.
In the zoom-callback you get the data range from the geometry of the plot.
with the data range you can update the data of the datasource from django with ajax calls.

Raphael

On 2016-09-15 15:17, Artur Scholz wrote:

Hello!

Having searched the web and this forum extensively but without success for an answer, here is my challenge:

My goal is to plot a schedule of intervals (with attributes: start, end) that are stored in a database and accessed via Django's (ORM) classes.

The tricky(?) part is that:
a) a line marker shall be drawn on the plot that indicates the current time (and be updated every 10 sec)
b) the plot shall initially display 3 days and load data only for this timespan. When user pans the plot, data shall be dynamically loaded and added to the plot (-> range update)

I have implemented this with using AjaxDataSource for the periodic time marker update and ColumDataSource with CustomJS for the x_range callback:

source = ColumnDataSource()
  
def callback(source=source, window=None):
    def on_range_update():
        xhr = window.XMLHttpRequest()
        data = source.get('data') # the data on the client
        window_start = int(cb_obj.get('start'))
        window_end = int(cb_obj.get('end'))
        buffer_start = data['start'][0]
        buffer_end = data['end'][-1]
        url_data_right =
        url_data_left =
             
        if window_start < buffer_start:
            # append data left
            url_data_left = "/schedule/api/passes?element_b=1&" + \
                            "start=" + window_start + "&" + \
                            "end=" + buffer_start
            
        if window_end > buffer_end:
            # append data right
            url_data_right = "/schedule/api/passes?element_b=1&" + \
                             "start=" + buffer_end + "&" + \
                             "end=" + window_end
        
        def onreadystatechange():
            if xhr.readyState == window.XMLHttpRequest.DONE:
                response = window.JSON.parse(xhr.responseText)
                if data['start'][-1] <= response['start'][0]:
                    # append data right
                    data['start'] = data['start'] + response['start']
                    data['end'] = data['end'] + response['end']
                    source.trigger('change')
                else:
                    # append data left
                    data['start'] = response['start'] + data['start']
                    data['end'] = response['end'] + data['end']
                    source.trigger('change')
                
        if url_data_right:
            xhr.onreadystatechange = onreadystatechange
            xhr.open('GET', url_data_right, False)
            xhr.send()
            
        if url_data_left:
            xhr.onreadystatechange = onreadystatechange
            xhr.open('GET', url_data_left, False)
            xhr.send()
    
    if window.range_update_timeout != 0: # cancel the previous update request
        window.clearTimeout(window.range_update_timeout)
        window.range_update_timeout = 0
    
    window.range_update_timeout = window.setTimeout(on_range_update, 500)

def passes_view(request):
    
    now = datetime.utcnow()
    start = now - timedelta(hours=24)
    end = now + timedelta(hours=24)
           
    p1 = figure(x_axis_type='datetime',
                tools='xwheel_zoom,xpan',
                active_scroll="xwheel_zoom",
                )
    
    # ensure that plot shows the zoom region
    border_start = 1000 * calendar.timegm(start.utctimetuple())
    border_end = 1000 * calendar.timegm(end.utctimetuple())
    p1.x_range = Range1d(border_start, border_end)
    
    # place current time marker
    source_utc = AjaxDataSource(method='GET',
                                data_url=reverse('api', args=['utc_now']),
                                polling_interval=5000)
    utc_now = 1000 * calendar.timegm(datetime.utcnow().utctimetuple())
    source_utc.data = dict(utc_now=[utc_now])
    p1.quad(source=source_utc, color='red',
            top=1.5, bottom=-1.5, left='utc_now', right='utc_now')
    
    # load passes from database
    model = Pass
    qs = model.objects.all()
    qs = qs.filter(end__gte=start)
    qs = qs.filter(start__lte=end)
    qs = qs.filter(element_b=1)
    qs = qs.order_by('start')
    
    qr = [ for i in range(2)]
    for i in qs:
        qr[0].append((i.start - datetime(1970,1,1))
                         / timedelta(milliseconds=1))
        qr[1].append((i.end - datetime(1970,1,1))
                         / timedelta(milliseconds=1))
    
    source.data = dict(start=qr[0], end=qr[1])
    print(source.data)
    p1.quad(source=source, top=1, bottom=-1, left='start', right='end')
    p1.x_range.callback = CustomJS.from_py_func(callback)

    plot = p1

    script, div = components(plot, INLINE)
    context = {
        'bokeh_script' : script,
        'bokeh_div' : div,
        'js_resources' : INLINE.render_js(),
        'css_resources' : INLINE.render_css()
        }
    return render(request, 'schedule/passes_view.html', context)

As you can see, the code is quite ugly and things become worse if more plots are involved. So, using a bokeh server from which my Django application can push to/pull from seems to me a solution to this.

However, I am missing a roadmap for this.. The bokeh documentation provides examples for bokeh apps, and in the "hapiness" example there is a disclaimer that it is not working with periodic updates (https://github.com/bokeh/bokeh-demos/blob/master/happiness/Building%20happiness.ipynb\)

Does someone has a boilerplate code for using bokeh server with django? I imagine that the bokeh server is started from script before starting django server, and then is accessible to all django apps.

Thanks
Artur

--
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 bokeh+un...@continuum.io.
To post to this group, send email to bo...@continuum.io.
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/cdddb619-4afb-4360-ac48-db44e52b84ad%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

--
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/8484708f-cb19-4b2e-afa5-be04959f6021%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

Ok, I am trying to use bokeh apps then. So I will put all code related to the plot into the app, including data loading and plot updates.

Now, do I need to manually start the bokeh server for this or can this be done from a django view function?

How about the following: User visits the web page (django framework), the view function starts the bokeh server if not already running,

and returns the HTTP response in form of HTML template filled with div & script from the bokeh server generated by the bokeh app?

···

On Thu, Sep 15, 2016 at 6:29 PM, Bryan Van de Ven [email protected] wrote:

Hi,

Unfortunately I can’t respond at length, but hopefully a few comments will clear some things up.

It is possible to “prepares the plot and sends it to bokeh server” using “bokeh.client” and “push_session”. But I don’t recommend this path. Previously the main reason to do this was to afford per-user-session customization. But as of 0.12.2 it is now possible to pass http request arguments to apps that are run directly on the bokeh server (i.e., via “bokeh server myapp.py”)

*** I strongly encourage you to write apps in the “bokeh serve myapp.py” instead of using bokeh.client ***

Once you have written an app to run directly on the server, there are two options to “embed it” in a Djanog app:

  • an iframe (works great, this is how demo.bokehplots.com works)

  • template in the

    returned by bokeh.embed.autoload_server

Either way, if you need per-session customizations, you can add HTTP request args to the URL for the iframe or for autoload_server, and then the user will see whatever customized version of the app you need to show them.

See:

    [http://bokeh.pydata.org/en/latest/docs/user_guide/server.html#accessing-the-http-request](http://bokeh.pydata.org/en/latest/docs/user_guide/server.html#accessing-the-http-request)

For more information about accessing HTTP request arguments.

Using “bokeh.client” has a number of intrinsic drawbacks compared to “bokeh serve myapp.py” style apps:

  • double the networks traffic (server just becomes a middleman, relaying traffic between python client and browser)

  • much harder to scale (need more capacity? just run more “bokeh serve myapp.py” behind a load balancer)

  • actually requires more code to write

  • client python process or thread must block permanently if there are callbacks to service (session.loop_until_closed)

Thanks,

Bryan

On Sep 15, 2016, at 11:10 AM, Artur Scholz [email protected] wrote:

Thanks, but this is not what I am looking for. Indeed I solved all my needs already using CustomJS callbacks and Ajax polling.

What I need now is to know how to set up django with bokeh server.

I am confused about the concepts of bokeh document, curdoc, output_server, session, …

It should be as simple as:

  • a django view function that prepares the plot and sends it to bokeh server, which in turn is used by the client (via bokehJS) to plot it
  • some other python functions in the view namespace that are used as callbacks for this plot, to react upon range change and to

receive periodic updates.

Note that preferably the plot creation should be done within the local view function instead of globally (as in all examples I saw), to avoid

having it running when in fact the website is not even visited.

Yet, I have no idea how to start this properly. Tried several things without success.

Artur

On Thursday, September 15, 2016 at 3:34:44 PM UTC+2, Raffs Walker wrote:

Hi Artur,

I cannot answer your question. But I can give you something like a workaround/idea.

I use bokeh with django and I use the approach of CustomJS.

Use the callback function of your zoomtools.

In the zoom-callback you get the data range from the geometry of the plot.

with the data range you can update the data of the datasource from django with ajax calls.

Raphael

On 2016-09-15 15:17, Artur Scholz wrote:

Hello!

Having searched the web and this forum extensively but without success for an answer, here is my challenge:

My goal is to plot a schedule of intervals (with attributes: start, end) that are stored in a database and accessed via Django’s (ORM) classes.

The tricky(?) part is that:

a) a line marker shall be drawn on the plot that indicates the current time (and be updated every 10 sec)

b) the plot shall initially display 3 days and load data only for this timespan. When user pans the plot, data shall be dynamically loaded and added to the plot (-> range update)

I have implemented this with using AjaxDataSource for the periodic time marker update and ColumDataSource with CustomJS for the x_range callback:

source = ColumnDataSource()

def callback(source=source, window=None):

def on_range_update():
    xhr = window.XMLHttpRequest()
    data = source.get('data') # the data on the client
    window_start = int(cb_obj.get('start'))
    window_end = int(cb_obj.get('end'))
    buffer_start = data['start'][0]
    buffer_end = data['end'][-1]
    url_data_right = []
    url_data_left = []
    if window_start < buffer_start:
        # append data left
        url_data_left = "/schedule/api/passes?element_b=1&" + \
                        "start=" + window_start + "&" + \
                        "end=" + buffer_start
    if window_end > buffer_end:
        # append data right
        url_data_right = "/schedule/api/passes?element_b=1&" + \
                         "start=" + buffer_end + "&" + \
                         "end=" + window_end
    def onreadystatechange():
        if xhr.readyState == window.XMLHttpRequest.DONE:
            response = window.JSON.parse(xhr.responseText)
            if data['start'][-1] <= response['start'][0]:
                # append data right
                data['start'] = data['start'] + response['start']
                data['end'] = data['end'] + response['end']
                source.trigger('change')
            else:
                # append data left
                data['start'] = response['start'] + data['start']
                data['end'] = response['end'] + data['end']
                source.trigger('change')
    if url_data_right:
        xhr.onreadystatechange = onreadystatechange
        xhr.open('GET', url_data_right, False)
        xhr.send()
    if url_data_left:
        xhr.onreadystatechange = onreadystatechange
        xhr.open('GET', url_data_left, False)
        xhr.send()
if window.range_update_timeout != 0: # cancel the previous update request
    window.clearTimeout(window.range_update_timeout)
    window.range_update_timeout = 0
window.range_update_timeout = window.setTimeout(on_range_update, 500)

def passes_view(request):

now = datetime.utcnow()
start = now - timedelta(hours=24)
end = now + timedelta(hours=24)
p1 = figure(x_axis_type='datetime',
            tools='xwheel_zoom,xpan',
            active_scroll="xwheel_zoom",
            )
# ensure that plot shows the zoom region
border_start = 1000 * calendar.timegm(start.utctimetuple())
border_end = 1000 * calendar.timegm(end.utctimetuple())
p1.x_range = Range1d(border_start, border_end)
# place current time marker
source_utc = AjaxDataSource(method='GET',
                            data_url=reverse('api', args=['utc_now']),
                            polling_interval=5000)
utc_now = 1000 * calendar.timegm(datetime.utcnow().utctimetuple())
source_utc.data = dict(utc_now=[utc_now])
p1.quad(source=source_utc, color='red',
        top=1.5, bottom=-1.5, left='utc_now', right='utc_now')
# load passes from database
model = Pass
qs = model.objects.all()
qs = qs.filter(end__gte=start)
qs = qs.filter(start__lte=end)
qs = qs.filter(element_b=1)
qs = qs.order_by('start')
qr = [[] for i in range(2)]
for i in qs:
    qr[0].append((i.start - datetime(1970,1,1))
                     / timedelta(milliseconds=1))
    qr[1].append((i.end - datetime(1970,1,1))
                     / timedelta(milliseconds=1))
source.data = dict(start=qr[0], end=qr[1])
print(source.data)
p1.quad(source=source, top=1, bottom=-1, left='start', right='end')
p1.x_range.callback = CustomJS.from_py_func(callback)
plot = p1
script, div = components(plot, INLINE)
context = {
    'bokeh_script' : script,
    'bokeh_div' : div,
    'js_resources' : INLINE.render_js(),
    'css_resources' : INLINE.render_css()
    }
return render(request, 'schedule/passes_view.html', context)

As you can see, the code is quite ugly and things become worse if more plots are involved. So, using a bokeh server from which my Django application can push to/pull from seems to me a solution to this.

However, I am missing a roadmap for this… The bokeh documentation provides examples for bokeh apps, and in the “hapiness” example there is a disclaimer that it is not working with periodic updates (https://github.com/bokeh/bokeh-demos/blob/master/happiness/Building%20happiness.ipynb)

Does someone has a boilerplate code for using bokeh server with django? I imagine that the bokeh server is started from script before starting django server, and then is accessible to all django apps.

Thanks

Artur

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/cdddb619-4afb-4360-ac48-db44e52b84ad%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

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/8484708f-cb19-4b2e-afa5-be04959f6021%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

You received this message because you are subscribed to a topic in the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this topic, visit https://groups.google.com/a/continuum.io/d/topic/bokeh/1baVw4t6FtY/unsubscribe.

To unsubscribe from this group and all its topics, 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/55ECEB35-50CF-4CA4-A045-E4FA69D352DA%40continuum.io.

It's possible for Django to start the Bokeh server, but this should only happen once. If there is some way to do that at startup rather than lazily in the view, that would be preferred I think. There was a recent thread on this mailing list about starting the server programmatically (i.e. not by using subprocess) that might be of interest to you.

Alternatively it's often the case that whatever starts your web server can also start a Bokeh server at the same time. I think this is the cleanest approach but I can understand why people might want to have Django (or whatever) "be in charge".

For server apps, it's not "div and script". Of the two methods I mentioned:

* iframe -- there is a single iframe configured with a URL to the bokeh server that goes in the template

* autoload_server -- this function takes the URL to app and returns a single JS script tag that goes in the template

Thanks,

Bryan

···

On Sep 15, 2016, at 3:13 PM, Artur Scholz <[email protected]> wrote:

Ok, I am trying to use bokeh apps then. So I will put all code related to the plot into the app, including data loading and plot updates.
Now, do I need to manually start the bokeh server for this or can this be done from a django view function?

How about the following: User visits the web page (django framework), the view function starts the bokeh server if not already running,
and returns the HTTP response in form of HTML template filled with div & script from the bokeh server generated by the bokeh app?

On Thu, Sep 15, 2016 at 6:29 PM, Bryan Van de Ven <[email protected]> wrote:
Hi,

Unfortunately I can't respond at length, but hopefully a few comments will clear some things up.

It is possible to "prepares the plot and sends it to bokeh server" using "bokeh.client" and "push_session". But I don't recommend this path. Previously the main reason to do this was to afford per-user-session customization. But as of 0.12.2 it is now possible to pass http request arguments to apps that are run directly on the bokeh server (i.e., via "bokeh server myapp.py")

*** I strongly encourage you to write apps in the "bokeh serve myapp.py" instead of using bokeh.client ***

Once you have written an app to run directly on the server, there are two options to "embed it" in a Djanog app:

* an iframe (works great, this is how demo.bokehplots.com works)

* template in the <div> returned by bokeh.embed.autoload_server

Either way, if you need per-session customizations, you can add HTTP request args to the URL for the iframe or for autoload_server, and then the user will see whatever customized version of the app you need to show them.

See:

        Bokeh server — Bokeh 3.3.2 Documentation

For more information about accessing HTTP request arguments.

Using "bokeh.client" has a number of intrinsic drawbacks compared to "bokeh serve myapp.py" style apps:

* double the networks traffic (server just becomes a middleman, relaying traffic between python client and browser)
* much harder to scale (need more capacity? just run more "bokeh serve myapp.py" behind a load balancer)
* actually requires more code to write
* client python process or thread must block permanently if there are callbacks to service (session.loop_until_closed)

Thanks,

Bryan

> On Sep 15, 2016, at 11:10 AM, Artur Scholz <[email protected]> wrote:
>
> Thanks, but this is not what I am looking for. Indeed I solved all my needs already using CustomJS callbacks and Ajax polling.
> What I need now is to know how to set up django with bokeh server.
>
> I am confused about the concepts of bokeh document, curdoc, output_server, session, ....
> It should be as simple as:
> - a django view function that prepares the plot and sends it to bokeh server, which in turn is used by the client (via bokehJS) to plot it
> - some other python functions in the view namespace that are used as callbacks for this plot, to react upon range change and to
> receive periodic updates.
>
> Note that preferably the plot creation should be done within the local view function instead of globally (as in all examples I saw), to avoid
> having it running when in fact the website is not even visited.
>
> Yet, I have no idea how to start this properly. Tried several things without success.
>
> Artur
>
> On Thursday, September 15, 2016 at 3:34:44 PM UTC+2, Raffs Walker wrote:
> Hi Artur,
>
> I cannot answer your question. But I can give you something like a workaround/idea.
> I use bokeh with django and I use the approach of CustomJS.
>
> Use the callback function of your zoomtools.
> In the zoom-callback you get the data range from the geometry of the plot.
> with the data range you can update the data of the datasource from django with ajax calls.
>
> Raphael
>
> On 2016-09-15 15:17, Artur Scholz wrote:
>> Hello!
>>
>> Having searched the web and this forum extensively but without success for an answer, here is my challenge:
>>
>> My goal is to plot a schedule of intervals (with attributes: start, end) that are stored in a database and accessed via Django's (ORM) classes.
>>
>> The tricky(?) part is that:
>> a) a line marker shall be drawn on the plot that indicates the current time (and be updated every 10 sec)
>> b) the plot shall initially display 3 days and load data only for this timespan. When user pans the plot, data shall be dynamically loaded and added to the plot (-> range update)
>>
>> I have implemented this with using AjaxDataSource for the periodic time marker update and ColumDataSource with CustomJS for the x_range callback:
>>
>> source = ColumnDataSource()
>>
>> def callback(source=source, window=None):
>> def on_range_update():
>> xhr = window.XMLHttpRequest()
>> data = source.get('data') # the data on the client
>> window_start = int(cb_obj.get('start'))
>> window_end = int(cb_obj.get('end'))
>> buffer_start = data['start'][0]
>> buffer_end = data['end'][-1]
>> url_data_right =
>> url_data_left =
>>
>> if window_start < buffer_start:
>> # append data left
>> url_data_left = "/schedule/api/passes?element_b=1&" + \
>> "start=" + window_start + "&" + \
>> "end=" + buffer_start
>>
>> if window_end > buffer_end:
>> # append data right
>> url_data_right = "/schedule/api/passes?element_b=1&" + \
>> "start=" + buffer_end + "&" + \
>> "end=" + window_end
>>
>> def onreadystatechange():
>> if xhr.readyState == window.XMLHttpRequest.DONE:
>> response = window.JSON.parse(xhr.responseText)
>> if data['start'][-1] <= response['start'][0]:
>> # append data right
>> data['start'] = data['start'] + response['start']
>> data['end'] = data['end'] + response['end']
>> source.trigger('change')
>> else:
>> # append data left
>> data['start'] = response['start'] + data['start']
>> data['end'] = response['end'] + data['end']
>> source.trigger('change')
>>
>> if url_data_right:
>> xhr.onreadystatechange = onreadystatechange
>> xhr.open('GET', url_data_right, False)
>> xhr.send()
>>
>> if url_data_left:
>> xhr.onreadystatechange = onreadystatechange
>> xhr.open('GET', url_data_left, False)
>> xhr.send()
>>
>> if window.range_update_timeout != 0: # cancel the previous update request
>> window.clearTimeout(window.range_update_timeout)
>> window.range_update_timeout = 0
>>
>> window.range_update_timeout = window.setTimeout(on_range_update, 500)
>>
>>
>> def passes_view(request):
>>
>> now = datetime.utcnow()
>> start = now - timedelta(hours=24)
>> end = now + timedelta(hours=24)
>>
>> p1 = figure(x_axis_type='datetime',
>> tools='xwheel_zoom,xpan',
>> active_scroll="xwheel_zoom",
>> )
>>
>> # ensure that plot shows the zoom region
>> border_start = 1000 * calendar.timegm(start.utctimetuple())
>> border_end = 1000 * calendar.timegm(end.utctimetuple())
>> p1.x_range = Range1d(border_start, border_end)
>>
>> # place current time marker
>> source_utc = AjaxDataSource(method='GET',
>> data_url=reverse('api', args=['utc_now']),
>> polling_interval=5000)
>> utc_now = 1000 * calendar.timegm(datetime.utcnow().utctimetuple())
>> source_utc.data = dict(utc_now=[utc_now])
>> p1.quad(source=source_utc, color='red',
>> top=1.5, bottom=-1.5, left='utc_now', right='utc_now')
>>
>> # load passes from database
>> model = Pass
>> qs = model.objects.all()
>> qs = qs.filter(end__gte=start)
>> qs = qs.filter(start__lte=end)
>> qs = qs.filter(element_b=1)
>> qs = qs.order_by('start')
>>
>> qr = [ for i in range(2)]
>> for i in qs:
>> qr[0].append((i.start - datetime(1970,1,1))
>> / timedelta(milliseconds=1))
>> qr[1].append((i.end - datetime(1970,1,1))
>> / timedelta(milliseconds=1))
>>
>> source.data = dict(start=qr[0], end=qr[1])
>> print(source.data)
>> p1.quad(source=source, top=1, bottom=-1, left='start', right='end')
>> p1.x_range.callback = CustomJS.from_py_func(callback)
>>
>>
>> plot = p1
>>
>> script, div = components(plot, INLINE)
>> context = {
>> 'bokeh_script' : script,
>> 'bokeh_div' : div,
>> 'js_resources' : INLINE.render_js(),
>> 'css_resources' : INLINE.render_css()
>> }
>> return render(request, 'schedule/passes_view.html', context)
>>
>>
>>
>> As you can see, the code is quite ugly and things become worse if more plots are involved. So, using a bokeh server from which my Django application can push to/pull from seems to me a solution to this.
>>
>> However, I am missing a roadmap for this.. The bokeh documentation provides examples for bokeh apps, and in the "hapiness" example there is a disclaimer that it is not working with periodic updates (https://github.com/bokeh/bokeh-demos/blob/master/happiness/Building%20happiness.ipynb\)
>>
>> Does someone has a boilerplate code for using bokeh server with django? I imagine that the bokeh server is started from script before starting django server, and then is accessible to all django apps.
>>
>> Thanks
>> Artur
>>
>>
>>
>> --
>> 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 bokeh+un...@continuum.io.
>> To post to this group, send email to bo...@continuum.io.
>> To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/cdddb619-4afb-4360-ac48-db44e52b84ad%40continuum.io\.
>> For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
>
>
> --
> 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/8484708f-cb19-4b2e-afa5-be04959f6021%40continuum.io\.
> For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

--
You received this message because you are subscribed to a topic in the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this topic, visit https://groups.google.com/a/continuum.io/d/topic/bokeh/1baVw4t6FtY/unsubscribe\.
To unsubscribe from this group and all its topics, 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/55ECEB35-50CF-4CA4-A045-E4FA69D352DA%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

--
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/CALqfExZZZk9VXg9JGf7WHCg2XgY2DtoLq1jb%3DLDBN3ivr6KyDA%40mail.gmail.com\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

I still miss to see the complete picture of how to work with bokeh server. Having read through the bokeh online documentation, the entire reference section, and much of the examples provided on GitHub, I wonder
why the following code is not working as expected.
My goal with this very, very simple example is to load an app into a running bokeh server.
First start bokeh server:
bokeh serve --host=localhost:5000 --host=localhost:5006
And then to run the application served from flask:
python app.py

Here is content of app.py:
from flask import Flask, render_template
from bokeh.embed import autoload_server
from bokeh.client.session import push_session
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.driving import count

plot configuration

source = ColumnDataSource(data=dict(x=[0], y=[0]))
plot = figure()
plot.circle(x=‘x’,y=‘y’, source=source)

@count()
def update(i):
new_data = dict()
new_data[‘x’] = source.data[‘x’] + [i]
new_data[‘y’] = source.data[‘y’] + [i]
source.data = new_data

server configuration

app = Flask(name)

@app.route(’/’)
def index():
curdoc().add_root(plot)
curdoc().add_periodic_callback(update, 1000)
session = push_session(curdoc())
script = autoload_server(model=None, session_id=session.id)
return render_template(‘index.html’, bokeh_script=script)

if name == “main”:
app.run(debug=True)

``

for sake of completeness, here is index.html:

Bokeh app test

{{ bokeh_script|safe }}

``

When visiting localhost:5000 the plot shows, but is not being updated. What am I missing here?

Artur

Hi Ari,

Unfortunately I am currently under very tight deadline in other work, so I can't expand at great length. It's clear that at some point when I am able, it would be good to add some additional docs, and in particular some diagrams that show visually how things are related. In the mean time I wrote up a fairly long answer on stack overflow that might be useful to you:

  http://stackoverflow.com/questions/39533452/bokeh-server-and-on-click-on-change-doing-nothing/39535545#39535545

The gist of it is this: when using push_session, callbacks don't happen in the Bokeh server, they happen in whatever python process calls push_session. But this also necessitates having to call session.loop_until_closed() to block and "service" the events the trigger callbacks in that python process. You aren't calling session.loop_until_closed(), so no callbacks are getting serviced, which is exactly what you are seeing. Of course, blocking in your Django app is almost certainly not what you want to do.

So what you do want to do, is to run the app "natively" (we are still looking for terminology) in the server, such as:

  bokeh server myapp.py

and not use push_session at all. You can still use autoload_server to embed an app in Django template (by providing a URL to the Bokeh server, instead of a session ID). If you need per-session customization, you can pass HTML request args in the URL, these are recently made available and inspectable by server apps.

There are currently case where push_session would be the *only* way to go, and if it is not necessary, peopel should alwasy start with "native" server apps first.

Thanks,

Bryan

···

On Sep 19, 2016, at 4:51 AM, Artur Scholz <[email protected]> wrote:

I still miss to see the complete picture of how to work with bokeh server. Having read through the bokeh online documentation, the entire reference section, and much of the examples provided on GitHub, I wonder why the following code is not working as expected.

My goal with this very, very simple example is to load an app into a running bokeh server.
First start bokeh server:
bokeh serve --host=localhost:5000 --host=localhost:5006
And then to run the application served from flask:
python app.py

Here is content of app.py:
from flask import Flask, render_template
from bokeh.embed import autoload_server
from bokeh.client.session import push_session
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.driving import count

# plot configuration
source = ColumnDataSource(data=dict(x=[0], y=[0]))
plot = figure()
plot.circle(x='x',y='y', source=source)

@count()
def update(i):
    new_data = dict()
    new_data['x'] = source.data['x'] + [i]
    new_data['y'] = source.data['y'] + [i]
    source.data = new_data

# server configuration
app = Flask(__name__)

@app.route('/')
def index():
    curdoc().add_root(plot)
    curdoc().add_periodic_callback(update, 1000)
    session = push_session(curdoc())
    script = autoload_server(model=None, session_id=session.id)
    return render_template('index.html', bokeh_script=script)

if __name__ == "__main__":
    app.run(debug=True)

for sake of completeness, here is index.html:
<!DOCTYPE html>
<html>
  <body>
    <h1>Bokeh app test</h1>
    <div class="bokeh_app">
      {{ bokeh_script|safe }}
    </div>
  </body>
</html>

When visiting localhost:5000 the plot shows, but is not being updated. What am I missing here?

Artur

--
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/dd3a1190-bfad-4cde-ba8f-24ddac0fbdd2%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

Sorry, that last sentence should read:

There are currently FEW cases where push_session would be the *only* way to go, and if it is not necessary, people should always start with "native" server apps first, before trying other things.

···

On Sep 19, 2016, at 8:57 AM, Bryan Van de Ven <[email protected]> wrote:

Hi Ari,

Unfortunately I am currently under very tight deadline in other work, so I can't expand at great length. It's clear that at some point when I am able, it would be good to add some additional docs, and in particular some diagrams that show visually how things are related. In the mean time I wrote up a fairly long answer on stack overflow that might be useful to you:

  http://stackoverflow.com/questions/39533452/bokeh-server-and-on-click-on-change-doing-nothing/39535545#39535545

The gist of it is this: when using push_session, callbacks don't happen in the Bokeh server, they happen in whatever python process calls push_session. But this also necessitates having to call session.loop_until_closed() to block and "service" the events the trigger callbacks in that python process. You aren't calling session.loop_until_closed(), so no callbacks are getting serviced, which is exactly what you are seeing. Of course, blocking in your Django app is almost certainly not what you want to do.

So what you do want to do, is to run the app "natively" (we are still looking for terminology) in the server, such as:

  bokeh server myapp.py

and not use push_session at all. You can still use autoload_server to embed an app in Django template (by providing a URL to the Bokeh server, instead of a session ID). If you need per-session customization, you can pass HTML request args in the URL, these are recently made available and inspectable by server apps.

There are currently case where push_session would be the *only* way to go, and if it is not necessary, peopel should alwasy start with "native" server apps first.

Thanks,

Bryan

On Sep 19, 2016, at 4:51 AM, Artur Scholz <[email protected]> wrote:

I still miss to see the complete picture of how to work with bokeh server. Having read through the bokeh online documentation, the entire reference section, and much of the examples provided on GitHub, I wonder why the following code is not working as expected.

My goal with this very, very simple example is to load an app into a running bokeh server.
First start bokeh server:
bokeh serve --host=localhost:5000 --host=localhost:5006
And then to run the application served from flask:
python app.py

Here is content of app.py:
from flask import Flask, render_template
from bokeh.embed import autoload_server
from bokeh.client.session import push_session
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.driving import count

# plot configuration
source = ColumnDataSource(data=dict(x=[0], y=[0]))
plot = figure()
plot.circle(x='x',y='y', source=source)

@count()
def update(i):
   new_data = dict()
   new_data['x'] = source.data['x'] + [i]
   new_data['y'] = source.data['y'] + [i]
   source.data = new_data

# server configuration
app = Flask(__name__)

@app.route('/')
def index():
   curdoc().add_root(plot)
   curdoc().add_periodic_callback(update, 1000)
   session = push_session(curdoc())
   script = autoload_server(model=None, session_id=session.id)
   return render_template('index.html', bokeh_script=script)

if __name__ == "__main__":
   app.run(debug=True)

for sake of completeness, here is index.html:
<!DOCTYPE html>
<html>
<body>
   <h1>Bokeh app test</h1>
   <div class="bokeh_app">
     {{ bokeh_script|safe }}
   </div>
</body>
</html>

When visiting localhost:5000 the plot shows, but is not being updated. What am I missing here?

Artur

--
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/dd3a1190-bfad-4cde-ba8f-24ddac0fbdd2%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

If running the app “natively” on the bokeh server is really the best approach for a django+bokeh web setup, is still questionably to me. As for instance demonstrated with the ‘happiness’ example, to have bokeh ‘only’ doing the plotting and django doing all the heavy work with data loading and preparation, seems to me a more suitable approach.

For those interested, to get my last posted script running, I simply moved the blocking session loop into a thread. Also, the script was extended to demonstrate the serving of different plots:

from flask import Flask, render_template
from threading import Thread
from bokeh.embed import autoload_server
from bokeh.client import push_session
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.driving import count
from bokeh.document import Document

app = Flask(name)

@app.route(‘/plot1’)
def plot1():
source = ColumnDataSource(data=dict(x=[0], y=[0]))
plot = figure()
plot.circle(x=‘x’,y=‘y’, source=source)

@count()
def update(i):
    new_data = dict()
    new_data['x'] = source.data['x'] + [i]
    new_data['y'] = source.data['y'] + [i]
    source.data = new_data

document = Document()
document.add_root(plot)  
document.add_periodic_callback(update, 1000)
session = push_session(document)
script = autoload_server(model=None, session_id=session.id)
thread = Thread(target = session.loop_until_closed)
thread.start()

return render_template('index.html', bokeh_script=script)

@app.route(‘/plot2’)
def plot2():
source = ColumnDataSource(data=dict(x=[0], y=[0]))
plot = figure()
plot.line(x=‘x’,y=‘y’, source=source)

@count()
def update(i):
    new_data = dict()
    new_data['x'] = source.data['x'] + [i]
    new_data['y'] = source.data['y'] + [i]
    source.data = new_data

document = Document()
document.add_root(plot)  
document.add_periodic_callback(update, 1000)
session = push_session(document)
script = autoload_server(model=None, session_id=session.id)
thread = Thread(target = session.loop_until_closed)
thread.start()

return render_template('index.html', bokeh_script=script)    

if name == “main”:
app.run(debug=True)

``

I think it is still a clean approach, as the bokeh server is started without app at the time of starting the web server and sessions get closed when clients disconnect.

If this approach will work as expected for my django web project (where multiple users can modify shared data) is yet to be determined.

Artur

···

On Monday, September 19, 2016 at 4:26:47 PM UTC+2, Bryan Van de ven wrote:

Sorry, that last sentence should read:

There are currently FEW cases where push_session would be the only way to go, and if it is not necessary, people should always start with “native” server apps first, before trying other things.

On Sep 19, 2016, at 8:57 AM, Bryan Van de Ven [email protected] wrote:

Hi Ari,

Unfortunately I am currently under very tight deadline in other work, so I can’t expand at great length. It’s clear that at some point when I am able, it would be good to add some additional docs, and in particular some diagrams that show visually how things are related. In the mean time I wrote up a fairly long answer on stack overflow that might be useful to you:

    [http://stackoverflow.com/questions/39533452/bokeh-server-and-on-click-on-change-doing-nothing/39535545#39535545](http://stackoverflow.com/questions/39533452/bokeh-server-and-on-click-on-change-doing-nothing/39535545#39535545)

The gist of it is this: when using push_session, callbacks don’t happen in the Bokeh server, they happen in whatever python process calls push_session. But this also necessitates having to call session.loop_until_closed() to block and “service” the events the trigger callbacks in that python process. You aren’t calling session.loop_until_closed(), so no callbacks are getting serviced, which is exactly what you are seeing. Of course, blocking in your Django app is almost certainly not what you want to do.

So what you do want to do, is to run the app “natively” (we are still looking for terminology) in the server, such as:

    bokeh server myapp.py

and not use push_session at all. You can still use autoload_server to embed an app in Django template (by providing a URL to the Bokeh server, instead of a session ID). If you need per-session customization, you can pass HTML request args in the URL, these are recently made available and inspectable by server apps.

There are currently case where push_session would be the only way to go, and if it is not necessary, peopel should alwasy start with “native” server apps first.

Thanks,

Bryan

On Sep 19, 2016, at 4:51 AM, Artur Scholz [email protected] wrote:

I still miss to see the complete picture of how to work with bokeh server. Having read through the bokeh online documentation, the entire reference section, and much of the examples provided on GitHub, I wonder why the following code is not working as expected.

My goal with this very, very simple example is to load an app into a running bokeh server.
First start bokeh server:

bokeh serve --host=localhost:5000 --host=localhost:5006

And then to run the application served from flask:

python app.py

Here is content of app.py:

from flask import Flask, render_template

from bokeh.embed import autoload_server

from bokeh.client.session import push_session

from bokeh.io import curdoc

from bokeh.models import ColumnDataSource

from bokeh.plotting import figure

from bokeh.driving import count

plot configuration

source = ColumnDataSource(data=dict(x=[0], y=[0]))

plot = figure()

plot.circle(x=‘x’,y=‘y’, source=source)

@count()

def update(i):

new_data = dict()

new_data[‘x’] = source.data[‘x’] + [i]

new_data[‘y’] = source.data[‘y’] + [i]

source.data = new_data

server configuration

app = Flask(name)

@app.route(‘/’)

def index():

curdoc().add_root(plot)

curdoc().add_periodic_callback(update, 1000)
session = push_session(curdoc())

script = autoload_server(model=None, session_id=session.id)

return render_template(‘index.html’, bokeh_script=script)

if name == “main”:

app.run(debug=True)

for sake of completeness, here is index.html:

Bokeh app test

 {{ bokeh_script|safe }}

When visiting localhost:5000 the plot shows, but is not being updated. What am I missing here?

Artur


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/dd3a1190-bfad-4cde-ba8f-24ddac0fbdd2%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Adding threads indefinitely and never cleaning up is a resource leak, eventually this approach will fall over.

I didn't claim anything about what work should be done where. What I claimed is that the best way to run bokeh apps, even if you want to embed them, is to run

  bokeh serve app.py

There are plenty of ways to do that, and also do the "heavy lifting" elsewhere. You just need to communicate the results/data/whatever the app needs in order to do its plotting, the the app. But that could be done any number of ways:

* apps can access HTML request args (new feature)
* apps can access cookies
* apps can access data in external databases (redis, mongo, etc.)
* apps can make AJAX calls to REST APIs
* ...

So sure, compute your results wherever you want, just put them some place a "native" server app can get to them when it needs them.

The reason this is much better is because:

* it is scalable -- if you have 10M users, just put more bokeh servers behind a load balancer. There's no good way to scale your current approach with a load balancer without sticky sessions (I guess you could pay for Nginx Pro)

* it is more robust -- as I mentioned your current approach has a huge resource leak, if this app is meant to run for a very long time, it won't.

Of course it also bears mentioning: Bokeh server apps support Jinja2 templates so if you are just creating a "single page app" it's possible the simplest thing to do is not use Django or anything else at all. The Bokeh server is based on Tornado, which a well established and very capable web app server in and of itself.

Thanks,

Bryan

···

On Sep 20, 2016, at 10:14 AM, Artur Scholz <[email protected]> wrote:

If running the app "natively" on the bokeh server is really the best approach for a django+bokeh web setup, is still questionably to me. As for instance demonstrated with the 'happiness' example, to have bokeh 'only' doing the plotting and django doing all the heavy work with data loading and preparation, seems to me a more suitable approach.

For those interested, to get my last posted script running, I simply moved the blocking session loop into a thread. Also, the script was extended to demonstrate the serving of different plots:

from flask import Flask, render_template
from threading import Thread
from bokeh.embed import autoload_server
from bokeh.client import push_session
from bokeh.io import curdoc
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.driving import count
from bokeh.document import Document

app = Flask(__name__)

@app.route('/plot1')
def plot1():
    source = ColumnDataSource(data=dict(x=[0], y=[0]))
    plot = figure()
    plot.circle(x='x',y='y', source=source)
    
    @count()
    def update(i):
        new_data = dict()
        new_data['x'] = source.data['x'] + [i]
        new_data['y'] = source.data['y'] + [i]
        source.data = new_data

    document = Document()
    document.add_root(plot)
    document.add_periodic_callback(update, 1000)
    session = push_session(document)
    script = autoload_server(model=None, session_id=session.id)
    thread = Thread(target = session.loop_until_closed)
    thread.start()

    return render_template('index.html', bokeh_script=script)

@app.route('/plot2')
def plot2():
    source = ColumnDataSource(data=dict(x=[0], y=[0]))
    plot = figure()
    plot.line(x='x',y='y', source=source)
    
    @count()
    def update(i):
        new_data = dict()
        new_data['x'] = source.data['x'] + [i]
        new_data['y'] = source.data['y'] + [i]
        source.data = new_data

    document = Document()
    document.add_root(plot)
    document.add_periodic_callback(update, 1000)
    session = push_session(document)
    script = autoload_server(model=None, session_id=session.id)
    thread = Thread(target = session.loop_until_closed)
    thread.start()

    return render_template('index.html', bokeh_script=script)
    
if __name__ == "__main__":
    app.run(debug=True)

I think it is still a clean approach, as the bokeh server is started without app at the time of starting the web server and sessions get closed when clients disconnect.

If this approach will work as expected for my django web project (where multiple users can modify shared data) is yet to be determined.

Artur

On Monday, September 19, 2016 at 4:26:47 PM UTC+2, Bryan Van de ven wrote:
Sorry, that last sentence should read:

There are currently FEW cases where push_session would be the *only* way to go, and if it is not necessary, people should always start with "native" server apps first, before trying other things.

> On Sep 19, 2016, at 8:57 AM, Bryan Van de Ven <[email protected]> wrote:
>
> Hi Ari,
>
> Unfortunately I am currently under very tight deadline in other work, so I can't expand at great length. It's clear that at some point when I am able, it would be good to add some additional docs, and in particular some diagrams that show visually how things are related. In the mean time I wrote up a fairly long answer on stack overflow that might be useful to you:
>
> http://stackoverflow.com/questions/39533452/bokeh-server-and-on-click-on-change-doing-nothing/39535545#39535545
>
> The gist of it is this: when using push_session, callbacks don't happen in the Bokeh server, they happen in whatever python process calls push_session. But this also necessitates having to call session.loop_until_closed() to block and "service" the events the trigger callbacks in that python process. You aren't calling session.loop_until_closed(), so no callbacks are getting serviced, which is exactly what you are seeing. Of course, blocking in your Django app is almost certainly not what you want to do.
>
> So what you do want to do, is to run the app "natively" (we are still looking for terminology) in the server, such as:
>
> bokeh server myapp.py
>
> and not use push_session at all. You can still use autoload_server to embed an app in Django template (by providing a URL to the Bokeh server, instead of a session ID). If you need per-session customization, you can pass HTML request args in the URL, these are recently made available and inspectable by server apps.
>
> There are currently case where push_session would be the *only* way to go, and if it is not necessary, peopel should alwasy start with "native" server apps first.
>
> Thanks,
>
> Bryan
>
>
>
>> On Sep 19, 2016, at 4:51 AM, Artur Scholz <[email protected]> wrote:
>>
>> I still miss to see the complete picture of how to work with bokeh server. Having read through the bokeh online documentation, the entire reference section, and much of the examples provided on GitHub, I wonder why the following code is not working as expected.
>>
>> My goal with this very, very simple example is to load an app into a running bokeh server.
>> First start bokeh server:
>> bokeh serve --host=localhost:5000 --host=localhost:5006
>> And then to run the application served from flask:
>> python app.py
>>
>> Here is content of app.py:
>> from flask import Flask, render_template
>> from bokeh.embed import autoload_server
>> from bokeh.client.session import push_session
>> from bokeh.io import curdoc
>> from bokeh.models import ColumnDataSource
>> from bokeh.plotting import figure
>> from bokeh.driving import count
>>
>> # plot configuration
>> source = ColumnDataSource(data=dict(x=[0], y=[0]))
>> plot = figure()
>> plot.circle(x='x',y='y', source=source)
>>
>> @count()
>> def update(i):
>> new_data = dict()
>> new_data['x'] = source.data['x'] + [i]
>> new_data['y'] = source.data['y'] + [i]
>> source.data = new_data
>>
>> # server configuration
>> app = Flask(__name__)
>>
>> @app.route('/')
>> def index():
>> curdoc().add_root(plot)
>> curdoc().add_periodic_callback(update, 1000)
>> session = push_session(curdoc())
>> script = autoload_server(model=None, session_id=session.id)
>> return render_template('index.html', bokeh_script=script)
>>
>> if __name__ == "__main__":
>> app.run(debug=True)
>>
>>
>> for sake of completeness, here is index.html:
>> <!DOCTYPE html>
>> <html>
>> <body>
>> <h1>Bokeh app test</h1>
>> <div class="bokeh_app">
>> {{ bokeh_script|safe }}
>> </div>
>> </body>
>> </html>
>>
>> When visiting localhost:5000 the plot shows, but is not being updated. What am I missing here?
>>
>> Artur
>>
>> --
>> 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 bokeh+un...@continuum.io.
>> To post to this group, send email to bo...@continuum.io.
>> To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/dd3a1190-bfad-4cde-ba8f-24ddac0fbdd2%40continuum.io\.
>> For more options, visit https://groups.google.com/a/continuum.io/d/optout\.
>

--
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/87af174c-4d39-4e1b-a0ef-6a2c65edaa42%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

How to add a (python) callback function for range update when using the bokeh server? It seems that only add_periodic_callback,
add_timeout_callback, and add_next_tick_callback are available. Does it mean I still need to use CustomJS for this?

Artur

···

On Thursday, September 15, 2016 at 3:17:56 PM UTC+2, Artur Scholz wrote:

Hello!

Having searched the web and this forum extensively but without success for an answer, here is my challenge:

My goal is to plot a schedule of intervals (with attributes: start, end) that are stored in a database and accessed via Django’s (ORM) classes.

The tricky(?) part is that:
a) a line marker shall be drawn on the plot that indicates the current time (and be updated every 10 sec)
b) the plot shall initially display 3 days and load data only for this timespan. When user pans the plot, data shall be dynamically loaded and added to the plot (-> range update)

I have implemented this with using AjaxDataSource for the periodic time marker update and ColumDataSource with CustomJS for the x_range callback:

source = ColumnDataSource()

def callback(source=source, window=None):
def on_range_update():
xhr = window.XMLHttpRequest()
data = source.get(‘data’) # the data on the client
window_start = int(cb_obj.get(‘start’))
window_end = int(cb_obj.get(‘end’))
buffer_start = data[‘start’][0]
buffer_end = data[‘end’][-1]
url_data_right =
url_data_left =

    if window_start < buffer_start:
        # append data left
        url_data_left = "/schedule/api/passes?element_b=1&" + \
                        "start=" + window_start + "&" + \
                        "end=" + buffer_start
       
    if window_end > buffer_end:
        # append data right
        url_data_right = "/schedule/api/passes?element_b=1&" + \
                         "start=" + buffer_end + "&" + \
                         "end=" + window_end
   
    def onreadystatechange():
        if xhr.readyState == window.XMLHttpRequest.DONE:
            response = window.JSON.parse(xhr.responseText)
            if data['start'][-1] <= response['start'][0]:
                # append data right
                data['start'] = data['start'] + response['start']
                data['end'] = data['end'] + response['end']
                source.trigger('change')
            else:
                # append data left
                data['start'] = response['start'] + data['start']
                data['end'] = response['end'] + data['end']
                source.trigger('change')
           
    if url_data_right:
        xhr.onreadystatechange = onreadystatechange  
        xhr.open('GET', url_data_right, False)
        xhr.send()
       
    if url_data_left:
        xhr.onreadystatechange = onreadystatechange  
        xhr.open('GET', url_data_left, False)
        xhr.send()

if window.range_update_timeout != 0: # cancel the previous update request
    window.clearTimeout(window.range_update_timeout)
    window.range_update_timeout = 0

window.range_update_timeout = window.setTimeout(on_range_update, 500)

def passes_view(request):

now = datetime.utcnow()
start = now - timedelta(hours=24)
end = now + timedelta(hours=24)
       
p1 = figure(x_axis_type='datetime',
            tools='xwheel_zoom,xpan',
            active_scroll="xwheel_zoom",
            )

# ensure that plot shows the zoom region  
border_start = 1000 * calendar.timegm(start.utctimetuple())
border_end = 1000 * calendar.timegm(end.utctimetuple())
p1.x_range = Range1d(border_start, border_end)

# place current time marker
source_utc = AjaxDataSource(method='GET',
                            data_url=reverse('api', args=['utc_now']),
                            polling_interval=5000)  
utc_now = 1000 * calendar.timegm(datetime.utcnow().utctimetuple())
source_utc.data = dict(utc_now=[utc_now])
p1.quad(source=source_utc, color='red',
        top=1.5, bottom=-1.5, left='utc_now', right='utc_now')

# load passes from database
model = Pass    
qs = model.objects.all()
qs = qs.filter(end__gte=start)
qs = qs.filter(start__lte=end)
qs = qs.filter(element_b=1)
qs = qs.order_by('start')

qr = [[] for i in range(2)]
for i in qs:
    qr[0].append((i.start - datetime(1970,1,1))
                     / timedelta(milliseconds=1))
    qr[1].append((i.end - datetime(1970,1,1))
                     / timedelta(milliseconds=1))

source.data = dict(start=qr[0], end=qr[1])
print(source.data)
p1.quad(source=source, top=1, bottom=-1, left='start', right='end')
p1.x_range.callback = CustomJS.from_py_func(callback)

 
plot = p1

script, div = components(plot, INLINE)
context = {
    'bokeh_script' : script,
    'bokeh_div' : div,
    'js_resources' : INLINE.render_js(),
    'css_resources' : INLINE.render_css()
    }
return render(request, 'schedule/passes_view.html', context)

``

As you can see, the code is quite ugly and things become worse if more plots are involved. So, using a bokeh server from which my Django application can push to/pull from seems to me a solution to this.

However, I am missing a roadmap for this… The bokeh documentation provides examples for bokeh apps, and in the “hapiness” example there is a disclaimer that it is not working with periodic updates (https://github.com/bokeh/bokeh-demos/blob/master/happiness/Building%20happiness.ipynb)

Does someone has a boilerplate code for using bokeh server with django? I imagine that the bokeh server is started from script before starting django server, and then is accessible to all django apps.

Thanks
Artur

Use

        def callback(attr, old, new):
      # stuff

  obj.on_change('attrname', callback)

This generally works on any property of any Bokeh model. See, e.g

  https://github.com/bokeh/bokeh/blob/master/examples/app/sliders.py#L48-L69

Thanks,

Bryan

···

On Sep 20, 2016, at 3:45 PM, Artur Scholz <[email protected]> wrote:

How to add a (python) callback function for range update when using the bokeh server? It seems that only add_periodic_callback, add_timeout_callback, and add_next_tick_callback are available. Does it mean I still need to use CustomJS for this?

Artur

On Thursday, September 15, 2016 at 3:17:56 PM UTC+2, Artur Scholz wrote:
Hello!

Having searched the web and this forum extensively but without success for an answer, here is my challenge:

My goal is to plot a schedule of intervals (with attributes: start, end) that are stored in a database and accessed via Django's (ORM) classes.

The tricky(?) part is that:
a) a line marker shall be drawn on the plot that indicates the current time (and be updated every 10 sec)
b) the plot shall initially display 3 days and load data only for this timespan. When user pans the plot, data shall be dynamically loaded and added to the plot (-> range update)

I have implemented this with using AjaxDataSource for the periodic time marker update and ColumDataSource with CustomJS for the x_range callback:

source = ColumnDataSource()
  
def callback(source=source, window=None):
    def on_range_update():
        xhr = window.XMLHttpRequest()
        data = source.get('data') # the data on the client
        window_start = int(cb_obj.get('start'))
        window_end = int(cb_obj.get('end'))
        buffer_start = data['start'][0]
        buffer_end = data['end'][-1]
        url_data_right =
        url_data_left =
             
        if window_start < buffer_start:
            # append data left
            url_data_left = "/schedule/api/passes?element_b=1&" + \
                            "start=" + window_start + "&" + \
                            "end=" + buffer_start
            
        if window_end > buffer_end:
            # append data right
            url_data_right = "/schedule/api/passes?element_b=1&" + \
                             "start=" + buffer_end + "&" + \
                             "end=" + window_end
        
        def onreadystatechange():
            if xhr.readyState == window.XMLHttpRequest.DONE:
                response = window.JSON.parse(xhr.responseText)
                if data['start'][-1] <= response['start'][0]:
                    # append data right
                    data['start'] = data['start'] + response['start']
                    data['end'] = data['end'] + response['end']
                    source.trigger('change')
                else:
                    # append data left
                    data['start'] = response['start'] + data['start']
                    data['end'] = response['end'] + data['end']
                    source.trigger('change')
                
        if url_data_right:
            xhr.onreadystatechange = onreadystatechange
            xhr.open('GET', url_data_right, False)
            xhr.send()
            
        if url_data_left:
            xhr.onreadystatechange = onreadystatechange
            xhr.open('GET', url_data_left, False)
            xhr.send()
    
    if window.range_update_timeout != 0: # cancel the previous update request
        window.clearTimeout(window.range_update_timeout)
        window.range_update_timeout = 0
    
    window.range_update_timeout = window.setTimeout(on_range_update, 500)

def passes_view(request):
    
    now = datetime.utcnow()
    start = now - timedelta(hours=24)
    end = now + timedelta(hours=24)
           
    p1 = figure(x_axis_type='datetime',
                tools='xwheel_zoom,xpan',
                active_scroll="xwheel_zoom",
                )
    
    # ensure that plot shows the zoom region
    border_start = 1000 * calendar.timegm(start.utctimetuple())
    border_end = 1000 * calendar.timegm(end.utctimetuple())
    p1.x_range = Range1d(border_start, border_end)
    
    # place current time marker
    source_utc = AjaxDataSource(method='GET',
                                data_url=reverse('api', args=['utc_now']),
                                polling_interval=5000)
    utc_now = 1000 * calendar.timegm(datetime.utcnow().utctimetuple())
    source_utc.data = dict(utc_now=[utc_now])
    p1.quad(source=source_utc, color='red',
            top=1.5, bottom=-1.5, left='utc_now', right='utc_now')
    
    # load passes from database
    model = Pass
    qs = model.objects.all()
    qs = qs.filter(end__gte=start)
    qs = qs.filter(start__lte=end)
    qs = qs.filter(element_b=1)
    qs = qs.order_by('start')
    
    qr = [ for i in range(2)]
    for i in qs:
        qr[0].append((i.start - datetime(1970,1,1))
                         / timedelta(milliseconds=1))
        qr[1].append((i.end - datetime(1970,1,1))
                         / timedelta(milliseconds=1))
    
    source.data = dict(start=qr[0], end=qr[1])
    print(source.data)
    p1.quad(source=source, top=1, bottom=-1, left='start', right='end')
    p1.x_range.callback = CustomJS.from_py_func(callback)

    plot = p1

    script, div = components(plot, INLINE)
    context = {
        'bokeh_script' : script,
        'bokeh_div' : div,
        'js_resources' : INLINE.render_js(),
        'css_resources' : INLINE.render_css()
        }
    return render(request, 'schedule/passes_view.html', context)

As you can see, the code is quite ugly and things become worse if more plots are involved. So, using a bokeh server from which my Django application can push to/pull from seems to me a solution to this.

However, I am missing a roadmap for this.. The bokeh documentation provides examples for bokeh apps, and in the "hapiness" example there is a disclaimer that it is not working with periodic updates (https://github.com/bokeh/bokeh-demos/blob/master/happiness/Building%20happiness.ipynb\)

Does someone has a boilerplate code for using bokeh server with django? I imagine that the bokeh server is started from script before starting django server, and then is accessible to all django apps.

Thanks
Artur

--
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/c0f6143e-fc60-4214-a8ac-cb5c1aaaff2d%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.