Bokeh Server Slow to Init and Update with Large Layout

I’m working on a project to qualify some new devices for a hardware revision of a circuit board. I’m using Bokeh as a real-time front end to display the streaming data from these boards and allow our test engineers to monitor the device testing. I have a feeling my layout is causing Bokeh to go about things highly inefficiently, so I’m posting the Bokeh layout code to see if I can get a
confirmation on this.

Currently, each board has 12 plots with a combined 55 lines, and 3 tables with 11 columns apiece. Each board has its own tab, and we are running up to 8 boards at a time. Total number of figures is about 100. This layout works beautifully with one board, starts getting sluggish with 3 boards, and is basically unusable with 8. I’ve perused this forum and it appears that this may be on the larger side as Bokeh documents tend to go. I’m also relatively new to Python with about 3 months of experience; I’m actually a firmware guy. This is running on Python 2.7.15. The data is coming from discrete serial ports, and I read each port buffer in the periodic callback. Each board has its own ColumnDataStream and stream call in the callback. The stream call is (essentially):

def sampleBoards():

for board in boards:

    board.data.stream(dict_data, rollover=10)

I’m running the server with “bokeh serve --show my_script.py”. This
reads and displays basically 2,500 bytes per second, but I’m only using
Bokeh for live monitoring, hence the 10 sample rolling window. The Bokeh layout is as follows. This does not run, I sanitized the layout to illustrate how I’m putting it together. If something that will run would help, I can try to provide that.

from random import random

from functools import partial

import sys

from bokeh.layouts import column

from bokeh.models import ColumnDataSource, Range1d

from bokeh.models.widgets import Tabs, Panel, DataTable, TableColumn, HTMLTemplateFormatter

from bokeh.plotting import figure, curdoc

from device_read_script import sampleBoards, createBoards

def createDeviceType1Plot(board, device):

# create a plot and style its properties

plotWidth = 1400

plotHeight = 350

# define Bokeh tooltip

HOVER_TOOLTIP = [

    ('Attr', '$y{0,0.0}'),

    ('Packet Number', '$x{0,0}')

]

# Plot of Device Type 1

deviceType1Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='DeviceType1 '+device,

                  tooltips=HOVER_TOOLTIP,

                  tools="reset")

deviceType1Plot.title.text_font_size = '18px'

deviceType1Plot.toolbar.logo = None

deviceType1Plot.min_border_left = 30

deviceType1Plot.min_border_right = 80

deviceType1Plot.legend.location = 'top_left'

deviceType1Plot.legend.click_policy = 'hide'

# add a line renderer to our plot (no data yet)

deviceType1Plot.line(x='Packet Number', y=device + ' attr1', line_color='Black',

                     line_width=3, source=board.data, legend='attr1')

deviceType1Plot.line(x='Packet Number', y=device + ' attr2', line_color='Green',

                     line_width=3, source=board.data, legend='attr2')

deviceType1Plot.line(x='Packet Number', y=device + ' attr3', line_color='Blue',

                     line_width=3, source=board.data, legend='attr3')

deviceType1Plot.line(x='Packet Number', y=device + ' attr4', line_color='Red',

                     line_width=3, source=board.data, legend='attr4')

return deviceType1Plot

def createDeviceType2Plot(board, device):

# create a plot and style its properties

plotWidth = 1400

plotHeight = 350

# define Bokeh tooltip

HOVER_TOOLTIP = [

    ('Raw Value', '$y{0,0.0}'),

    ('Packet Number', '$x{0,0}')

]

# Plot of Device Type 2

deviceType2Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='DeviceType2 '+device,

                  tooltips=HOVER_TOOLTIP,

                  tools="reset")

deviceType2Plot.title.text_font_size = '18px'

deviceType2Plot.toolbar.logo = None

deviceType2Plot.min_border_left = 30

deviceType2Plot.min_border_right = 80

deviceType2Plot.legend.location = 'top_left'

deviceType2Plot.legend.click_policy = 'hide'

# add a line renderer to our plot (no data yet)

deviceType2Plot.line(x='Packet Number', y=device + ' attr1', line_color='Black',

                     line_width=3, source=board.data, legend='attr1')

deviceType2Plot.line(x='Packet Number', y=device + ' attr2', line_color='Green',

                     line_width=3, source=board.data, legend='attr2')

return deviceType2Plot

def createDeviceXPage(board):

dev1_plot = createDeviceType1Plot(board, 'Device1')

dev2_plot = createDeviceType1Plot(board, 'Device2')

dev3_plot = createDeviceType1Plot(board, 'Device3')

dev4_plot = createDeviceType1Plot(board, 'Device4')

dev5_plot = createDeviceType1Plot(board, 'Device5')

dev6_plot = createDeviceType1Plot(board, 'Device6')

dev7_plot = createDeviceType1Plot(board, 'Device7')

dev8_plot = createDeviceType1Plot(board, 'Device8')

dev9_plot = createDeviceType1Plot(board, 'Device9')

dev10_plot = createDeviceType2Plot(board, 'Device10')

dev11_plot = createDeviceType2Plot(board, 'Device11')

panel = column(dev1_plot, dev2_plot, dev3_plot,

               dev4_plot, dev5_plot, dev6_plot,

               dev7_plot, dev8_plot, dev9_plot,

               dev10_plot, dev11_plot)

return Panel(child=panel, title='Device X Data')

def createDeviceType3Page(board):

# create a plot and style its properties

plotWidth = 1400

plotHeight = 350

# Plot of Device Type 3

deviceType3Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='Device Type 3 Data',

                     tools="reset")

deviceType3Plot.title.text_font_size = '18px'

deviceType3Plot.toolbar.logo = None

deviceType3Plot.min_border_left = 10

deviceType3Plot.min_border_right = 10

deviceType3Plot.legend.location = 'top_left'

deviceType3Plot.legend.click_policy = 'hide'

# add a line renderer to our plot (no data yet)

deviceType3Plot.line(x='Packet Number', y='Device12', line_color='Black',

                     source=board.data, legend='Dev12')

deviceType3Plot.line(x='Packet Number', y='Device13', line_color='Black',

                source=board.data, legend='Dev13')

deviceType3Plot.line(x='Packet Number', y='Device14', line_color='Black',

                source=board.data, legend='Dev14')

deviceType3Plot.line(x='Packet Number', y='Device15', line_color='Black',

                source=board.data, legend='Dev15')

deviceType3Plot.line(x='Packet Number', y='Device16', line_color='Red',

                source=board.data, legend='Dev16')

deviceType3Plot.line(x='Packet Number', y='Device17', line_color='Red',

                source=board.data, legend='Dev17')

deviceType3Plot.line(x='Packet Number', y='Device18', line_color='Green',

                source=board.data, legend='Dev18')

deviceType3Plot.line(x='Packet Number', y='Device19', line_color='Green',

                source=board.data, legend='Dev19')

deviceType3Plot.line(x='Packet Number', y='Device20', line_color='Yellow',

                source=board.data, legend='Dev20')

deviceType3Plot.line(x='Packet Number', y='Device21', line_color='Blue',

                source=board.data, legend='Dev21')

deviceType3Plot.line(x='Packet Number', y='Device22', line_color='Blue',

                source=board.data, legend='Dev22')

tableColumns = [

    TableColumn(field='Device12', title='Dev12'),

    TableColumn(field='Device13', title='Dev13'),

    TableColumn(field='Device14', title='Dev14'),

    TableColumn(field='Device15', title='Dev15'),

    TableColumn(field='Device16', title='Dev16'),

    TableColumn(field='Device17', title='Dev17'),

    TableColumn(field='Device18', title='Dev18'),

    TableColumn(field='Device19', title='Dev19'),

    TableColumn(field='Device20', title='Dev20'),

    TableColumn(field='Device21', title='Dev21'),

    TableColumn(field='Device22', title='Dev22')

]

table = DataTable(source=board.data, columns=tableColumns, width=1400)

page = column(deviceType3Plot, table)

return Panel(child=page, title='Device Type3 Data')

def createDeviceType4Page(board):

attr1_tableColumns = [

    TableColumn(field='Device23_attr1', title='Dev23_attr1'),

    TableColumn(field='Device24_attr1', title='Dev24_attr1'),

    TableColumn(field='Device25_attr1', title='Dev25_attr1'),

    TableColumn(field='Device26_attr1', title='Dev26_attr1'),

    TableColumn(field='Device27_attr1', title='Dev27_attr1'),

    TableColumn(field='Device28_attr1', title='Dev28_attr1'),

    TableColumn(field='Device29_attr1', title='Dev29_attr1'),

    TableColumn(field='Device30_attr1', title='Dev30_attr1'),

    TableColumn(field='Device31_attr1', title='Dev31_attr1'),

    TableColumn(field='Device32_attr1', title='Dev32_attr1'),

    TableColumn(field='Device33_attr1', title='Dev33_attr1'),

    TableColumn(field='Device34_attr1', title='Dev34_attr1')

]

attr1Table = DataTable(source=board.data, columns=attr1_tableColumns, width=1400)

attr2_tableColumns = [

    TableColumn(field='Device23_attr2', title='Dev23_attr2'),

    TableColumn(field='Device24_attr2', title='Dev24_attr2'),

    TableColumn(field='Device25_attr2', title='Dev25_attr2'),

    TableColumn(field='Device26_attr2', title='Dev26_attr2'),

    TableColumn(field='Device27_attr2', title='Dev27_attr2'),

    TableColumn(field='Device28_attr2', title='Dev28_attr2'),

    TableColumn(field='Device29_attr2', title='Dev29_attr2'),

    TableColumn(field='Device30_attr2', title='Dev30_attr2'),

    TableColumn(field='Device31_attr2', title='Dev31_attr2'),

    TableColumn(field='Device32_attr2', title='Dev32_attr2'),

    TableColumn(field='Device33_attr2', title='Dev33_attr2'),

    TableColumn(field='Device34_attr2', title='Dev34_attr2')

]

attr2Table = DataTable(source=board.data, columns=attr2_tableColumns, width=1400)



page = column(attr1Table, attr2Table)

return Panel(child=page, title='Device Type 4 Data')

def createBoardPage(board):

devType1Page = createDeviceXPage(board)

devType3Page = createDeviceType3Page(board)

devType4Page = createDeviceType4Page(board)

tabs = Tabs(tabs=[devType1Page,

                  devType3Page,

                  devType4Page])

return Panel(child=tabs, title='Board Data (' +str(board.port.port) +')')

find COM ports and create board objects

boards = createBoards()

boardTabs =

for board in boards:

boardTabs.append(createBoardPage(board))

appTabs = Tabs(tabs=boardTabs)

curdoc().add_root(appTabs)

sampleBoards(boards)

bokehCallback = partial(sampleBoards, boards)

curdoc().add_periodic_callback(sampleBoards, 800)

Hi,

There was recently a very large PR merged to completely refactor how layout is done. As a result layout is also much more performant. This work is not yet in a full release, but can be obtained in dev builds. So first things first, can you try out your codebase with 1.1dev build? Instructions for install dev builds can be found here:

  https://bokeh.pydata.org/en/latest/docs/installation.html#developer-builds

Thanks,

Bryan

···

On Feb 5, 2019, at 10:55, [email protected] wrote:

I'm working on a project to qualify some new devices for a hardware revision of a circuit board. I'm using Bokeh as a real-time front end to display the streaming data from these boards and allow our test engineers to monitor the device testing. I have a feeling my layout is causing Bokeh to go about things highly inefficiently, so I'm posting the Bokeh layout code to see if I can get a confirmation on this.

Currently, each board has 12 plots with a combined 55 lines, and 3 tables with 11 columns apiece. Each board has its own tab, and we are running up to 8 boards at a time. Total number of figures is about 100. This layout works beautifully with one board, starts getting sluggish with 3 boards, and is basically unusable with 8. I've perused this forum and it appears that this may be on the larger side as Bokeh documents tend to go. I'm also relatively new to Python with about 3 months of experience; I'm actually a firmware guy. This is running on Python 2.7.15. The data is coming from discrete serial ports, and I read each port buffer in the periodic callback. Each board has its own ColumnDataStream and stream call in the callback. The stream call is (essentially):

def sampleBoards():
    for board in boards:
        board.data.stream(dict_data, rollover=10)

I'm running the server with "bokeh serve --show my_script.py". This reads and displays basically 2,500 bytes per second, but I'm only using Bokeh for live monitoring, hence the 10 sample rolling window. The Bokeh layout is as follows. This does not run, I sanitized the layout to illustrate how I'm putting it together. If something that will run would help, I can try to provide that.

from random import random
from functools import partial
import sys

from bokeh.layouts import column
from bokeh.models import ColumnDataSource, Range1d
from bokeh.models.widgets import Tabs, Panel, DataTable, TableColumn, HTMLTemplateFormatter
from bokeh.plotting import figure, curdoc

from device_read_script import sampleBoards, createBoards

def createDeviceType1Plot(board, device):
    # create a plot and style its properties
    plotWidth = 1400
    plotHeight = 350

    # define Bokeh tooltip
    HOVER_TOOLTIP = [
        ('Attr', '$y{0,0.0}'),
        ('Packet Number', '$x{0,0}')
    ]

    # Plot of Device Type 1
    deviceType1Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='DeviceType1 '+device,
                      tooltips=HOVER_TOOLTIP,
                      tools="reset")
    deviceType1Plot.title.text_font_size = '18px'
    deviceType1Plot.toolbar.logo = None
    deviceType1Plot.min_border_left = 30
    deviceType1Plot.min_border_right = 80
    deviceType1Plot.legend.location = 'top_left'
    deviceType1Plot.legend.click_policy = 'hide'

    # add a line renderer to our plot (no data yet)
    deviceType1Plot.line(x='Packet Number', y=device + ' attr1', line_color='Black',
                         line_width=3, source=board.data, legend='attr1')
    deviceType1Plot.line(x='Packet Number', y=device + ' attr2', line_color='Green',
                         line_width=3, source=board.data, legend='attr2')
    deviceType1Plot.line(x='Packet Number', y=device + ' attr3', line_color='Blue',
                         line_width=3, source=board.data, legend='attr3')
    deviceType1Plot.line(x='Packet Number', y=device + ' attr4', line_color='Red',
                         line_width=3, source=board.data, legend='attr4')

    return deviceType1Plot

def createDeviceType2Plot(board, device):
    # create a plot and style its properties
    plotWidth = 1400
    plotHeight = 350

    # define Bokeh tooltip
    HOVER_TOOLTIP = [
        ('Raw Value', '$y{0,0.0}'),
        ('Packet Number', '$x{0,0}')
    ]

    # Plot of Device Type 2
    deviceType2Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='DeviceType2 '+device,
                      tooltips=HOVER_TOOLTIP,
                      tools="reset")
    deviceType2Plot.title.text_font_size = '18px'
    deviceType2Plot.toolbar.logo = None
    deviceType2Plot.min_border_left = 30
    deviceType2Plot.min_border_right = 80
    deviceType2Plot.legend.location = 'top_left'
    deviceType2Plot.legend.click_policy = 'hide'

    # add a line renderer to our plot (no data yet)
    deviceType2Plot.line(x='Packet Number', y=device + ' attr1', line_color='Black',
                         line_width=3, source=board.data, legend='attr1')
    deviceType2Plot.line(x='Packet Number', y=device + ' attr2', line_color='Green',
                         line_width=3, source=board.data, legend='attr2')

    return deviceType2Plot

def createDeviceXPage(board):
    dev1_plot = createDeviceType1Plot(board, 'Device1')
    dev2_plot = createDeviceType1Plot(board, 'Device2')
    dev3_plot = createDeviceType1Plot(board, 'Device3')
    dev4_plot = createDeviceType1Plot(board, 'Device4')
    dev5_plot = createDeviceType1Plot(board, 'Device5')
    dev6_plot = createDeviceType1Plot(board, 'Device6')
    dev7_plot = createDeviceType1Plot(board, 'Device7')
    dev8_plot = createDeviceType1Plot(board, 'Device8')
    dev9_plot = createDeviceType1Plot(board, 'Device9')
    dev10_plot = createDeviceType2Plot(board, 'Device10')
    dev11_plot = createDeviceType2Plot(board, 'Device11')

    panel = column(dev1_plot, dev2_plot, dev3_plot,
                   dev4_plot, dev5_plot, dev6_plot,
                   dev7_plot, dev8_plot, dev9_plot,
                   dev10_plot, dev11_plot)
    return Panel(child=panel, title='Device X Data')

def createDeviceType3Page(board):
    # create a plot and style its properties
    plotWidth = 1400
    plotHeight = 350

    # Plot of Device Type 3
    deviceType3Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='Device Type 3 Data',
                         tools="reset")
    deviceType3Plot.title.text_font_size = '18px'
    deviceType3Plot.toolbar.logo = None
    deviceType3Plot.min_border_left = 10
    deviceType3Plot.min_border_right = 10
    deviceType3Plot.legend.location = 'top_left'
    deviceType3Plot.legend.click_policy = 'hide'

    # add a line renderer to our plot (no data yet)
    deviceType3Plot.line(x='Packet Number', y='Device12', line_color='Black',
                         source=board.data, legend='Dev12')
    deviceType3Plot.line(x='Packet Number', y='Device13', line_color='Black',
                    source=board.data, legend='Dev13')
    deviceType3Plot.line(x='Packet Number', y='Device14', line_color='Black',
                    source=board.data, legend='Dev14')
    deviceType3Plot.line(x='Packet Number', y='Device15', line_color='Black',
                    source=board.data, legend='Dev15')
    deviceType3Plot.line(x='Packet Number', y='Device16', line_color='Red',
                    source=board.data, legend='Dev16')
    deviceType3Plot.line(x='Packet Number', y='Device17', line_color='Red',
                    source=board.data, legend='Dev17')
    deviceType3Plot.line(x='Packet Number', y='Device18', line_color='Green',
                    source=board.data, legend='Dev18')
    deviceType3Plot.line(x='Packet Number', y='Device19', line_color='Green',
                    source=board.data, legend='Dev19')
    deviceType3Plot.line(x='Packet Number', y='Device20', line_color='Yellow',
                    source=board.data, legend='Dev20')
    deviceType3Plot.line(x='Packet Number', y='Device21', line_color='Blue',
                    source=board.data, legend='Dev21')
    deviceType3Plot.line(x='Packet Number', y='Device22', line_color='Blue',
                    source=board.data, legend='Dev22')

    tableColumns = [
        TableColumn(field='Device12', title='Dev12'),
        TableColumn(field='Device13', title='Dev13'),
        TableColumn(field='Device14', title='Dev14'),
        TableColumn(field='Device15', title='Dev15'),
        TableColumn(field='Device16', title='Dev16'),
        TableColumn(field='Device17', title='Dev17'),
        TableColumn(field='Device18', title='Dev18'),
        TableColumn(field='Device19', title='Dev19'),
        TableColumn(field='Device20', title='Dev20'),
        TableColumn(field='Device21', title='Dev21'),
        TableColumn(field='Device22', title='Dev22')
    ]

    table = DataTable(source=board.data, columns=tableColumns, width=1400)

    page = column(deviceType3Plot, table)

    return Panel(child=page, title='Device Type3 Data')

def createDeviceType4Page(board):
    attr1_tableColumns = [
        TableColumn(field='Device23_attr1', title='Dev23_attr1'),
        TableColumn(field='Device24_attr1', title='Dev24_attr1'),
        TableColumn(field='Device25_attr1', title='Dev25_attr1'),
        TableColumn(field='Device26_attr1', title='Dev26_attr1'),
        TableColumn(field='Device27_attr1', title='Dev27_attr1'),
        TableColumn(field='Device28_attr1', title='Dev28_attr1'),
        TableColumn(field='Device29_attr1', title='Dev29_attr1'),
        TableColumn(field='Device30_attr1', title='Dev30_attr1'),
        TableColumn(field='Device31_attr1', title='Dev31_attr1'),
        TableColumn(field='Device32_attr1', title='Dev32_attr1'),
        TableColumn(field='Device33_attr1', title='Dev33_attr1'),
        TableColumn(field='Device34_attr1', title='Dev34_attr1')
    ]

    attr1Table = DataTable(source=board.data, columns=attr1_tableColumns, width=1400)

    attr2_tableColumns = [
        TableColumn(field='Device23_attr2', title='Dev23_attr2'),
        TableColumn(field='Device24_attr2', title='Dev24_attr2'),
        TableColumn(field='Device25_attr2', title='Dev25_attr2'),
        TableColumn(field='Device26_attr2', title='Dev26_attr2'),
        TableColumn(field='Device27_attr2', title='Dev27_attr2'),
        TableColumn(field='Device28_attr2', title='Dev28_attr2'),
        TableColumn(field='Device29_attr2', title='Dev29_attr2'),
        TableColumn(field='Device30_attr2', title='Dev30_attr2'),
        TableColumn(field='Device31_attr2', title='Dev31_attr2'),
        TableColumn(field='Device32_attr2', title='Dev32_attr2'),
        TableColumn(field='Device33_attr2', title='Dev33_attr2'),
        TableColumn(field='Device34_attr2', title='Dev34_attr2')
    ]

    attr2Table = DataTable(source=board.data, columns=attr2_tableColumns, width=1400)
    
    page = column(attr1Table, attr2Table)

    return Panel(child=page, title='Device Type 4 Data')

def createBoardPage(board):
    devType1Page = createDeviceXPage(board)
    devType3Page = createDeviceType3Page(board)
    devType4Page = createDeviceType4Page(board)
    tabs = Tabs(tabs=[devType1Page,
                      devType3Page,
                      devType4Page])

    return Panel(child=tabs, title='Board Data (' +str(board.port.port) +')')

# find COM ports and create board objects
boards = createBoards()
boardTabs =
for board in boards:
    boardTabs.append(createBoardPage(board))

appTabs = Tabs(tabs=boardTabs)
curdoc().add_root(appTabs)

# sampleBoards(boards)
# bokehCallback = partial(sampleBoards, boards)

curdoc().add_periodic_callback(sampleBoards, 800)

--
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/555cf80a-e0c6-48f0-9ed4-342ce596a34a%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Thanks for the super quick response! I’m getting set up with conda, as the pip command just lists all requirements as already satisfied. I’ll let you know how it goes once I’m up and running.

···

On Tuesday, February 5, 2019 at 11:25:13 AM UTC-6, Bryan Van de ven wrote:

Hi,

There was recently a very large PR merged to completely refactor how layout is done. As a result layout is also much more performant. This work is not yet in a full release, but can be obtained in dev builds. So first things first, can you try out your codebase with 1.1dev build? Instructions for install dev builds can be found here:

    [https://bokeh.pydata.org/en/latest/docs/installation.html#developer-builds](https://bokeh.pydata.org/en/latest/docs/installation.html#developer-builds)

Thanks,

Bryan

On Feb 5, 2019, at 10:55, [email protected] wrote:

I’m working on a project to qualify some new devices for a hardware revision of a circuit board. I’m using Bokeh as a real-time front end to display the streaming data from these boards and allow our test engineers to monitor the device testing. I have a feeling my layout is causing Bokeh to go about things highly inefficiently, so I’m posting the Bokeh layout code to see if I can get a confirmation on this.

Currently, each board has 12 plots with a combined 55 lines, and 3 tables with 11 columns apiece. Each board has its own tab, and we are running up to 8 boards at a time. Total number of figures is about 100. This layout works beautifully with one board, starts getting sluggish with 3 boards, and is basically unusable with 8. I’ve perused this forum and it appears that this may be on the larger side as Bokeh documents tend to go. I’m also relatively new to Python with about 3 months of experience; I’m actually a firmware guy. This is running on Python 2.7.15. The data is coming from discrete serial ports, and I read each port buffer in the periodic callback. Each board has its own ColumnDataStream and stream call in the callback. The stream call is (essentially):

def sampleBoards():

for board in boards:
    board.data.stream(dict_data, rollover=10)

I’m running the server with “bokeh serve --show my_script.py”. This reads and displays basically 2,500 bytes per second, but I’m only using Bokeh for live monitoring, hence the 10 sample rolling window. The Bokeh layout is as follows. This does not run, I sanitized the layout to illustrate how I’m putting it together. If something that will run would help, I can try to provide that.

from random import random

from functools import partial

import sys

from bokeh.layouts import column

from bokeh.models import ColumnDataSource, Range1d

from bokeh.models.widgets import Tabs, Panel, DataTable, TableColumn, HTMLTemplateFormatter

from bokeh.plotting import figure, curdoc

from device_read_script import sampleBoards, createBoards

def createDeviceType1Plot(board, device):

# create a plot and style its properties
plotWidth = 1400
plotHeight = 350
# define Bokeh tooltip
HOVER_TOOLTIP = [
    ('Attr', '$y{0,0.0}'),
    ('Packet Number', '$x{0,0}')
]
# Plot of Device Type 1
deviceType1Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='DeviceType1 '+device,
                  tooltips=HOVER_TOOLTIP,
                  tools="reset")
deviceType1Plot.title.text_font_size = '18px'
deviceType1Plot.toolbar.logo = None
deviceType1Plot.min_border_left = 30
deviceType1Plot.min_border_right = 80
deviceType1Plot.legend.location = 'top_left'
deviceType1Plot.legend.click_policy = 'hide'
# add a line renderer to our plot (no data yet)
deviceType1Plot.line(x='Packet Number', y=device + ' attr1', line_color='Black',
                     line_width=3, source=board.data, legend='attr1')
deviceType1Plot.line(x='Packet Number', y=device + ' attr2', line_color='Green',
                     line_width=3, source=board.data, legend='attr2')
deviceType1Plot.line(x='Packet Number', y=device + ' attr3', line_color='Blue',
                     line_width=3, source=board.data, legend='attr3')
deviceType1Plot.line(x='Packet Number', y=device + ' attr4', line_color='Red',
                     line_width=3, source=board.data, legend='attr4')
return deviceType1Plot

def createDeviceType2Plot(board, device):

# create a plot and style its properties
plotWidth = 1400
plotHeight = 350
# define Bokeh tooltip
HOVER_TOOLTIP = [
    ('Raw Value', '$y{0,0.0}'),
    ('Packet Number', '$x{0,0}')
]
# Plot of Device Type 2
deviceType2Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='DeviceType2 '+device,
                  tooltips=HOVER_TOOLTIP,
                  tools="reset")
deviceType2Plot.title.text_font_size = '18px'
deviceType2Plot.toolbar.logo = None
deviceType2Plot.min_border_left = 30
deviceType2Plot.min_border_right = 80
deviceType2Plot.legend.location = 'top_left'
deviceType2Plot.legend.click_policy = 'hide'
# add a line renderer to our plot (no data yet)
deviceType2Plot.line(x='Packet Number', y=device + ' attr1', line_color='Black',
                     line_width=3, source=board.data, legend='attr1')
deviceType2Plot.line(x='Packet Number', y=device + ' attr2', line_color='Green',
                     line_width=3, source=board.data, legend='attr2')
return deviceType2Plot

def createDeviceXPage(board):

dev1_plot = createDeviceType1Plot(board, 'Device1')
dev2_plot = createDeviceType1Plot(board, 'Device2')
dev3_plot = createDeviceType1Plot(board, 'Device3')
dev4_plot = createDeviceType1Plot(board, 'Device4')
dev5_plot = createDeviceType1Plot(board, 'Device5')
dev6_plot = createDeviceType1Plot(board, 'Device6')
dev7_plot = createDeviceType1Plot(board, 'Device7')
dev8_plot = createDeviceType1Plot(board, 'Device8')
dev9_plot = createDeviceType1Plot(board, 'Device9')
dev10_plot = createDeviceType2Plot(board, 'Device10')
dev11_plot = createDeviceType2Plot(board, 'Device11')
panel = column(dev1_plot, dev2_plot, dev3_plot,
               dev4_plot, dev5_plot, dev6_plot,
               dev7_plot, dev8_plot, dev9_plot,
               dev10_plot, dev11_plot)
return Panel(child=panel, title='Device X Data')

def createDeviceType3Page(board):

# create a plot and style its properties
plotWidth = 1400
plotHeight = 350
# Plot of Device Type 3
deviceType3Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='Device Type 3 Data',
                     tools="reset")
deviceType3Plot.title.text_font_size = '18px'
deviceType3Plot.toolbar.logo = None
deviceType3Plot.min_border_left = 10
deviceType3Plot.min_border_right = 10
deviceType3Plot.legend.location = 'top_left'
deviceType3Plot.legend.click_policy = 'hide'
# add a line renderer to our plot (no data yet)
deviceType3Plot.line(x='Packet Number', y='Device12', line_color='Black',
                     source=board.data, legend='Dev12')
deviceType3Plot.line(x='Packet Number', y='Device13', line_color='Black',
                source=board.data, legend='Dev13')
deviceType3Plot.line(x='Packet Number', y='Device14', line_color='Black',
                source=board.data, legend='Dev14')
deviceType3Plot.line(x='Packet Number', y='Device15', line_color='Black',
                source=board.data, legend='Dev15')
deviceType3Plot.line(x='Packet Number', y='Device16', line_color='Red',
                source=board.data, legend='Dev16')
deviceType3Plot.line(x='Packet Number', y='Device17', line_color='Red',
                source=board.data, legend='Dev17')
deviceType3Plot.line(x='Packet Number', y='Device18', line_color='Green',
                source=board.data, legend='Dev18')
deviceType3Plot.line(x='Packet Number', y='Device19', line_color='Green',
                source=board.data, legend='Dev19')
deviceType3Plot.line(x='Packet Number', y='Device20', line_color='Yellow',
                source=board.data, legend='Dev20')
deviceType3Plot.line(x='Packet Number', y='Device21', line_color='Blue',
                source=board.data, legend='Dev21')
deviceType3Plot.line(x='Packet Number', y='Device22', line_color='Blue',
                source=board.data, legend='Dev22')
tableColumns = [
    TableColumn(field='Device12', title='Dev12'),
    TableColumn(field='Device13', title='Dev13'),
    TableColumn(field='Device14', title='Dev14'),
    TableColumn(field='Device15', title='Dev15'),
    TableColumn(field='Device16', title='Dev16'),
    TableColumn(field='Device17', title='Dev17'),
    TableColumn(field='Device18', title='Dev18'),
    TableColumn(field='Device19', title='Dev19'),
    TableColumn(field='Device20', title='Dev20'),
    TableColumn(field='Device21', title='Dev21'),
    TableColumn(field='Device22', title='Dev22')
]
table = DataTable(source=board.data, columns=tableColumns, width=1400)
page = column(deviceType3Plot, table)
return Panel(child=page, title='Device Type3 Data')

def createDeviceType4Page(board):

attr1_tableColumns = [
    TableColumn(field='Device23_attr1', title='Dev23_attr1'),
    TableColumn(field='Device24_attr1', title='Dev24_attr1'),
    TableColumn(field='Device25_attr1', title='Dev25_attr1'),
    TableColumn(field='Device26_attr1', title='Dev26_attr1'),
    TableColumn(field='Device27_attr1', title='Dev27_attr1'),
    TableColumn(field='Device28_attr1', title='Dev28_attr1'),
    TableColumn(field='Device29_attr1', title='Dev29_attr1'),
    TableColumn(field='Device30_attr1', title='Dev30_attr1'),
    TableColumn(field='Device31_attr1', title='Dev31_attr1'),
    TableColumn(field='Device32_attr1', title='Dev32_attr1'),
    TableColumn(field='Device33_attr1', title='Dev33_attr1'),
    TableColumn(field='Device34_attr1', title='Dev34_attr1')
]
attr1Table = DataTable(source=board.data, columns=attr1_tableColumns, width=1400)
attr2_tableColumns = [
    TableColumn(field='Device23_attr2', title='Dev23_attr2'),
    TableColumn(field='Device24_attr2', title='Dev24_attr2'),
    TableColumn(field='Device25_attr2', title='Dev25_attr2'),
    TableColumn(field='Device26_attr2', title='Dev26_attr2'),
    TableColumn(field='Device27_attr2', title='Dev27_attr2'),
    TableColumn(field='Device28_attr2', title='Dev28_attr2'),
    TableColumn(field='Device29_attr2', title='Dev29_attr2'),
    TableColumn(field='Device30_attr2', title='Dev30_attr2'),
    TableColumn(field='Device31_attr2', title='Dev31_attr2'),
    TableColumn(field='Device32_attr2', title='Dev32_attr2'),
    TableColumn(field='Device33_attr2', title='Dev33_attr2'),
    TableColumn(field='Device34_attr2', title='Dev34_attr2')
]
attr2Table = DataTable(source=board.data, columns=attr2_tableColumns, width=1400)
page = column(attr1Table, attr2Table)
return Panel(child=page, title='Device Type 4 Data')

def createBoardPage(board):

devType1Page = createDeviceXPage(board)
devType3Page = createDeviceType3Page(board)
devType4Page = createDeviceType4Page(board)
tabs = Tabs(tabs=[devType1Page,
                  devType3Page,
                  devType4Page])
return Panel(child=tabs, title='Board Data (' +str(board.port.port) +')')

find COM ports and create board objects

boards = createBoards()

boardTabs =

for board in boards:

boardTabs.append(createBoardPage(board))

appTabs = Tabs(tabs=boardTabs)

curdoc().add_root(appTabs)

sampleBoards(boards)

bokehCallback = partial(sampleBoards, boards)

curdoc().add_periodic_callback(sampleBoards, 800)


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/555cf80a-e0c6-48f0-9ed4-342ce596a34a%40continuum.io.

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

Ok, I tried running with Bokeh 1.1.0dev2 and there appears to be very little difference in performance if any. Is that the correct dev build?

I’m pretty certain I’m not setting things up very well, probably leading to Bokeh attempting to redraw everything all the time. I’m also using double nested tabs which I’ve seen can cause some issues. Only one tab ever needs to update at a time, is there a way to enforce that?

···

On Tuesday, February 5, 2019 at 12:59:46 PM UTC-6, Luke Ruatta wrote:

Thanks for the super quick response! I’m getting set up with conda, as the pip command just lists all requirements as already satisfied. I’ll let you know how it goes once I’m up and running.

On Tuesday, February 5, 2019 at 11:25:13 AM UTC-6, Bryan Van de ven wrote:

Hi,

There was recently a very large PR merged to completely refactor how layout is done. As a result layout is also much more performant. This work is not yet in a full release, but can be obtained in dev builds. So first things first, can you try out your codebase with 1.1dev build? Instructions for install dev builds can be found here:

    [https://bokeh.pydata.org/en/latest/docs/installation.html#developer-builds](https://bokeh.pydata.org/en/latest/docs/installation.html#developer-builds)

Thanks,

Bryan

On Feb 5, 2019, at 10:55, [email protected] wrote:

I’m working on a project to qualify some new devices for a hardware revision of a circuit board. I’m using Bokeh as a real-time front end to display the streaming data from these boards and allow our test engineers to monitor the device testing. I have a feeling my layout is causing Bokeh to go about things highly inefficiently, so I’m posting the Bokeh layout code to see if I can get a confirmation on this.

Currently, each board has 12 plots with a combined 55 lines, and 3 tables with 11 columns apiece. Each board has its own tab, and we are running up to 8 boards at a time. Total number of figures is about 100. This layout works beautifully with one board, starts getting sluggish with 3 boards, and is basically unusable with 8. I’ve perused this forum and it appears that this may be on the larger side as Bokeh documents tend to go. I’m also relatively new to Python with about 3 months of experience; I’m actually a firmware guy. This is running on Python 2.7.15. The data is coming from discrete serial ports, and I read each port buffer in the periodic callback. Each board has its own ColumnDataStream and stream call in the callback. The stream call is (essentially):

def sampleBoards():

for board in boards:
    board.data.stream(dict_data, rollover=10)

I’m running the server with “bokeh serve --show my_script.py”. This reads and displays basically 2,500 bytes per second, but I’m only using Bokeh for live monitoring, hence the 10 sample rolling window. The Bokeh layout is as follows. This does not run, I sanitized the layout to illustrate how I’m putting it together. If something that will run would help, I can try to provide that.

from random import random

from functools import partial

import sys

from bokeh.layouts import column

from bokeh.models import ColumnDataSource, Range1d

from bokeh.models.widgets import Tabs, Panel, DataTable, TableColumn, HTMLTemplateFormatter

from bokeh.plotting import figure, curdoc

from device_read_script import sampleBoards, createBoards

def createDeviceType1Plot(board, device):

# create a plot and style its properties
plotWidth = 1400
plotHeight = 350
# define Bokeh tooltip
HOVER_TOOLTIP = [
    ('Attr', '$y{0,0.0}'),
    ('Packet Number', '$x{0,0}')
]
# Plot of Device Type 1
deviceType1Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='DeviceType1 '+device,
                  tooltips=HOVER_TOOLTIP,
                  tools="reset")
deviceType1Plot.title.text_font_size = '18px'
deviceType1Plot.toolbar.logo = None
deviceType1Plot.min_border_left = 30
deviceType1Plot.min_border_right = 80
deviceType1Plot.legend.location = 'top_left'
deviceType1Plot.legend.click_policy = 'hide'
# add a line renderer to our plot (no data yet)
deviceType1Plot.line(x='Packet Number', y=device + ' attr1', line_color='Black',
                     line_width=3, source=board.data, legend='attr1')
deviceType1Plot.line(x='Packet Number', y=device + ' attr2', line_color='Green',
                     line_width=3, source=board.data, legend='attr2')
deviceType1Plot.line(x='Packet Number', y=device + ' attr3', line_color='Blue',
                     line_width=3, source=board.data, legend='attr3')
deviceType1Plot.line(x='Packet Number', y=device + ' attr4', line_color='Red',
                     line_width=3, source=board.data, legend='attr4')
return deviceType1Plot

def createDeviceType2Plot(board, device):

# create a plot and style its properties
plotWidth = 1400
plotHeight = 350
# define Bokeh tooltip
HOVER_TOOLTIP = [
    ('Raw Value', '$y{0,0.0}'),
    ('Packet Number', '$x{0,0}')
]
# Plot of Device Type 2
deviceType2Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='DeviceType2 '+device,
                  tooltips=HOVER_TOOLTIP,
                  tools="reset")
deviceType2Plot.title.text_font_size = '18px'
deviceType2Plot.toolbar.logo = None
deviceType2Plot.min_border_left = 30
deviceType2Plot.min_border_right = 80
deviceType2Plot.legend.location = 'top_left'
deviceType2Plot.legend.click_policy = 'hide'
# add a line renderer to our plot (no data yet)
deviceType2Plot.line(x='Packet Number', y=device + ' attr1', line_color='Black',
                     line_width=3, source=board.data, legend='attr1')
deviceType2Plot.line(x='Packet Number', y=device + ' attr2', line_color='Green',
                     line_width=3, source=board.data, legend='attr2')
return deviceType2Plot

def createDeviceXPage(board):

dev1_plot = createDeviceType1Plot(board, 'Device1')
dev2_plot = createDeviceType1Plot(board, 'Device2')
dev3_plot = createDeviceType1Plot(board, 'Device3')
dev4_plot = createDeviceType1Plot(board, 'Device4')
dev5_plot = createDeviceType1Plot(board, 'Device5')
dev6_plot = createDeviceType1Plot(board, 'Device6')
dev7_plot = createDeviceType1Plot(board, 'Device7')
dev8_plot = createDeviceType1Plot(board, 'Device8')
dev9_plot = createDeviceType1Plot(board, 'Device9')
dev10_plot = createDeviceType2Plot(board, 'Device10')
dev11_plot = createDeviceType2Plot(board, 'Device11')
panel = column(dev1_plot, dev2_plot, dev3_plot,
               dev4_plot, dev5_plot, dev6_plot,
               dev7_plot, dev8_plot, dev9_plot,
               dev10_plot, dev11_plot)
return Panel(child=panel, title='Device X Data')

def createDeviceType3Page(board):

# create a plot and style its properties
plotWidth = 1400
plotHeight = 350
# Plot of Device Type 3
deviceType3Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='Device Type 3 Data',
                     tools="reset")
deviceType3Plot.title.text_font_size = '18px'
deviceType3Plot.toolbar.logo = None
deviceType3Plot.min_border_left = 10
deviceType3Plot.min_border_right = 10
deviceType3Plot.legend.location = 'top_left'
deviceType3Plot.legend.click_policy = 'hide'
# add a line renderer to our plot (no data yet)
deviceType3Plot.line(x='Packet Number', y='Device12', line_color='Black',
                     source=board.data, legend='Dev12')
deviceType3Plot.line(x='Packet Number', y='Device13', line_color='Black',
                source=board.data, legend='Dev13')
deviceType3Plot.line(x='Packet Number', y='Device14', line_color='Black',
                source=board.data, legend='Dev14')
deviceType3Plot.line(x='Packet Number', y='Device15', line_color='Black',
                source=board.data, legend='Dev15')
deviceType3Plot.line(x='Packet Number', y='Device16', line_color='Red',
                source=board.data, legend='Dev16')
deviceType3Plot.line(x='Packet Number', y='Device17', line_color='Red',
                source=board.data, legend='Dev17')
deviceType3Plot.line(x='Packet Number', y='Device18', line_color='Green',
                source=board.data, legend='Dev18')
deviceType3Plot.line(x='Packet Number', y='Device19', line_color='Green',
                source=board.data, legend='Dev19')
deviceType3Plot.line(x='Packet Number', y='Device20', line_color='Yellow',
                source=board.data, legend='Dev20')
deviceType3Plot.line(x='Packet Number', y='Device21', line_color='Blue',
                source=board.data, legend='Dev21')
deviceType3Plot.line(x='Packet Number', y='Device22', line_color='Blue',
                source=board.data, legend='Dev22')
tableColumns = [
    TableColumn(field='Device12', title='Dev12'),
    TableColumn(field='Device13', title='Dev13'),
    TableColumn(field='Device14', title='Dev14'),
    TableColumn(field='Device15', title='Dev15'),
    TableColumn(field='Device16', title='Dev16'),
    TableColumn(field='Device17', title='Dev17'),
    TableColumn(field='Device18', title='Dev18'),
    TableColumn(field='Device19', title='Dev19'),
    TableColumn(field='Device20', title='Dev20'),
    TableColumn(field='Device21', title='Dev21'),
    TableColumn(field='Device22', title='Dev22')
]
table = DataTable(source=board.data, columns=tableColumns, width=1400)
page = column(deviceType3Plot, table)
return Panel(child=page, title='Device Type3 Data')

def createDeviceType4Page(board):

attr1_tableColumns = [
    TableColumn(field='Device23_attr1', title='Dev23_attr1'),
    TableColumn(field='Device24_attr1', title='Dev24_attr1'),
    TableColumn(field='Device25_attr1', title='Dev25_attr1'),
    TableColumn(field='Device26_attr1', title='Dev26_attr1'),
    TableColumn(field='Device27_attr1', title='Dev27_attr1'),
    TableColumn(field='Device28_attr1', title='Dev28_attr1'),
    TableColumn(field='Device29_attr1', title='Dev29_attr1'),
    TableColumn(field='Device30_attr1', title='Dev30_attr1'),
    TableColumn(field='Device31_attr1', title='Dev31_attr1'),
    TableColumn(field='Device32_attr1', title='Dev32_attr1'),
    TableColumn(field='Device33_attr1', title='Dev33_attr1'),
    TableColumn(field='Device34_attr1', title='Dev34_attr1')
]
attr1Table = DataTable(source=board.data, columns=attr1_tableColumns, width=1400)
attr2_tableColumns = [
    TableColumn(field='Device23_attr2', title='Dev23_attr2'),
    TableColumn(field='Device24_attr2', title='Dev24_attr2'),
    TableColumn(field='Device25_attr2', title='Dev25_attr2'),
    TableColumn(field='Device26_attr2', title='Dev26_attr2'),
    TableColumn(field='Device27_attr2', title='Dev27_attr2'),
    TableColumn(field='Device28_attr2', title='Dev28_attr2'),
    TableColumn(field='Device29_attr2', title='Dev29_attr2'),
    TableColumn(field='Device30_attr2', title='Dev30_attr2'),
    TableColumn(field='Device31_attr2', title='Dev31_attr2'),
    TableColumn(field='Device32_attr2', title='Dev32_attr2'),
    TableColumn(field='Device33_attr2', title='Dev33_attr2'),
    TableColumn(field='Device34_attr2', title='Dev34_attr2')
]
attr2Table = DataTable(source=board.data, columns=attr2_tableColumns, width=1400)
page = column(attr1Table, attr2Table)
return Panel(child=page, title='Device Type 4 Data')

def createBoardPage(board):

devType1Page = createDeviceXPage(board)
devType3Page = createDeviceType3Page(board)
devType4Page = createDeviceType4Page(board)
tabs = Tabs(tabs=[devType1Page,
                  devType3Page,
                  devType4Page])
return Panel(child=tabs, title='Board Data (' +str(board.port.port) +')')

find COM ports and create board objects

boards = createBoards()

boardTabs =

for board in boards:

boardTabs.append(createBoardPage(board))

appTabs = Tabs(tabs=boardTabs)

curdoc().add_root(appTabs)

sampleBoards(boards)

bokehCallback = partial(sampleBoards, boards)

curdoc().add_periodic_callback(sampleBoards, 800)


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/555cf80a-e0c6-48f0-9ed4-342ce596a34a%40continuum.io.

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

Hi,

It's certainly possible that the bottleneck is not layout (though you could double check that 1.1dev2 is the version displayed when the Bokeh server starts). Regarding updates, Bokeh is always going to redraw things whenever streaming data comes in. If you are trying to stream to 100 plots at once that simply may be out of scope for the project at present. I've certainly never tried that and have not heard of anyone else reporting on that kind of use case. It's really hard to say anything for sure without actually running code and profiling,. though. If you have the ability to look at the profiling in the browser dev tools that would be helpful.

Thanks,

Bryan

···

On Feb 5, 2019, at 14:59, [email protected] wrote:

Ok, I tried running with Bokeh 1.1.0dev2 and there appears to be very little difference in performance if any. Is that the correct dev build?

I'm pretty certain I'm not setting things up very well, probably leading to Bokeh attempting to redraw everything all the time. I'm also using double nested tabs which I've seen can cause some issues. Only one tab ever needs to update at a time, is there a way to enforce that?

On Tuesday, February 5, 2019 at 12:59:46 PM UTC-6, Luke Ruatta wrote:
Thanks for the super quick response! I'm getting set up with conda, as the pip command just lists all requirements as already satisfied. I'll let you know how it goes once I'm up and running.

On Tuesday, February 5, 2019 at 11:25:13 AM UTC-6, Bryan Van de ven wrote:
Hi,

There was recently a very large PR merged to completely refactor how layout is done. As a result layout is also much more performant. This work is not yet in a full release, but can be obtained in dev builds. So first things first, can you try out your codebase with 1.1dev build? Instructions for install dev builds can be found here:

        https://bokeh.pydata.org/en/latest/docs/installation.html#developer-builds

Thanks,

Bryan

> On Feb 5, 2019, at 10:55, [email protected] wrote:
>
> I'm working on a project to qualify some new devices for a hardware revision of a circuit board. I'm using Bokeh as a real-time front end to display the streaming data from these boards and allow our test engineers to monitor the device testing. I have a feeling my layout is causing Bokeh to go about things highly inefficiently, so I'm posting the Bokeh layout code to see if I can get a confirmation on this.
>
> Currently, each board has 12 plots with a combined 55 lines, and 3 tables with 11 columns apiece. Each board has its own tab, and we are running up to 8 boards at a time. Total number of figures is about 100. This layout works beautifully with one board, starts getting sluggish with 3 boards, and is basically unusable with 8. I've perused this forum and it appears that this may be on the larger side as Bokeh documents tend to go. I'm also relatively new to Python with about 3 months of experience; I'm actually a firmware guy. This is running on Python 2.7.15. The data is coming from discrete serial ports, and I read each port buffer in the periodic callback. Each board has its own ColumnDataStream and stream call in the callback. The stream call is (essentially):
>
> def sampleBoards():
> for board in boards:
> board.data.stream(dict_data, rollover=10)
>
>
> I'm running the server with "bokeh serve --show my_script.py". This reads and displays basically 2,500 bytes per second, but I'm only using Bokeh for live monitoring, hence the 10 sample rolling window. The Bokeh layout is as follows. This does not run, I sanitized the layout to illustrate how I'm putting it together. If something that will run would help, I can try to provide that.
>
> from random import random
> from functools import partial
> import sys
>
> from bokeh.layouts import column
> from bokeh.models import ColumnDataSource, Range1d
> from bokeh.models.widgets import Tabs, Panel, DataTable, TableColumn, HTMLTemplateFormatter
> from bokeh.plotting import figure, curdoc
>
> from device_read_script import sampleBoards, createBoards
>
> def createDeviceType1Plot(board, device):
> # create a plot and style its properties
> plotWidth = 1400
> plotHeight = 350
>
> # define Bokeh tooltip
> HOVER_TOOLTIP = [
> ('Attr', '$y{0,0.0}'),
> ('Packet Number', '$x{0,0}')
> ]
>
> # Plot of Device Type 1
> deviceType1Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='DeviceType1 '+device,
> tooltips=HOVER_TOOLTIP,
> tools="reset")
> deviceType1Plot.title.text_font_size = '18px'
> deviceType1Plot.toolbar.logo = None
> deviceType1Plot.min_border_left = 30
> deviceType1Plot.min_border_right = 80
> deviceType1Plot.legend.location = 'top_left'
> deviceType1Plot.legend.click_policy = 'hide'
>
> # add a line renderer to our plot (no data yet)
> deviceType1Plot.line(x='Packet Number', y=device + ' attr1', line_color='Black',
> line_width=3, source=board.data, legend='attr1')
> deviceType1Plot.line(x='Packet Number', y=device + ' attr2', line_color='Green',
> line_width=3, source=board.data, legend='attr2')
> deviceType1Plot.line(x='Packet Number', y=device + ' attr3', line_color='Blue',
> line_width=3, source=board.data, legend='attr3')
> deviceType1Plot.line(x='Packet Number', y=device + ' attr4', line_color='Red',
> line_width=3, source=board.data, legend='attr4')
>
> return deviceType1Plot
>
> def createDeviceType2Plot(board, device):
> # create a plot and style its properties
> plotWidth = 1400
> plotHeight = 350
>
> # define Bokeh tooltip
> HOVER_TOOLTIP = [
> ('Raw Value', '$y{0,0.0}'),
> ('Packet Number', '$x{0,0}')
> ]
>
> # Plot of Device Type 2
> deviceType2Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='DeviceType2 '+device,
> tooltips=HOVER_TOOLTIP,
> tools="reset")
> deviceType2Plot.title.text_font_size = '18px'
> deviceType2Plot.toolbar.logo = None
> deviceType2Plot.min_border_left = 30
> deviceType2Plot.min_border_right = 80
> deviceType2Plot.legend.location = 'top_left'
> deviceType2Plot.legend.click_policy = 'hide'
>
> # add a line renderer to our plot (no data yet)
> deviceType2Plot.line(x='Packet Number', y=device + ' attr1', line_color='Black',
> line_width=3, source=board.data, legend='attr1')
> deviceType2Plot.line(x='Packet Number', y=device + ' attr2', line_color='Green',
> line_width=3, source=board.data, legend='attr2')
>
> return deviceType2Plot
>
> def createDeviceXPage(board):
> dev1_plot = createDeviceType1Plot(board, 'Device1')
> dev2_plot = createDeviceType1Plot(board, 'Device2')
> dev3_plot = createDeviceType1Plot(board, 'Device3')
> dev4_plot = createDeviceType1Plot(board, 'Device4')
> dev5_plot = createDeviceType1Plot(board, 'Device5')
> dev6_plot = createDeviceType1Plot(board, 'Device6')
> dev7_plot = createDeviceType1Plot(board, 'Device7')
> dev8_plot = createDeviceType1Plot(board, 'Device8')
> dev9_plot = createDeviceType1Plot(board, 'Device9')
> dev10_plot = createDeviceType2Plot(board, 'Device10')
> dev11_plot = createDeviceType2Plot(board, 'Device11')
>
> panel = column(dev1_plot, dev2_plot, dev3_plot,
> dev4_plot, dev5_plot, dev6_plot,
> dev7_plot, dev8_plot, dev9_plot,
> dev10_plot, dev11_plot)
> return Panel(child=panel, title='Device X Data')
>
> def createDeviceType3Page(board):
> # create a plot and style its properties
> plotWidth = 1400
> plotHeight = 350
>
> # Plot of Device Type 3
> deviceType3Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='Device Type 3 Data',
> tools="reset")
> deviceType3Plot.title.text_font_size = '18px'
> deviceType3Plot.toolbar.logo = None
> deviceType3Plot.min_border_left = 10
> deviceType3Plot.min_border_right = 10
> deviceType3Plot.legend.location = 'top_left'
> deviceType3Plot.legend.click_policy = 'hide'
>
> # add a line renderer to our plot (no data yet)
> deviceType3Plot.line(x='Packet Number', y='Device12', line_color='Black',
> source=board.data, legend='Dev12')
> deviceType3Plot.line(x='Packet Number', y='Device13', line_color='Black',
> source=board.data, legend='Dev13')
> deviceType3Plot.line(x='Packet Number', y='Device14', line_color='Black',
> source=board.data, legend='Dev14')
> deviceType3Plot.line(x='Packet Number', y='Device15', line_color='Black',
> source=board.data, legend='Dev15')
> deviceType3Plot.line(x='Packet Number', y='Device16', line_color='Red',
> source=board.data, legend='Dev16')
> deviceType3Plot.line(x='Packet Number', y='Device17', line_color='Red',
> source=board.data, legend='Dev17')
> deviceType3Plot.line(x='Packet Number', y='Device18', line_color='Green',
> source=board.data, legend='Dev18')
> deviceType3Plot.line(x='Packet Number', y='Device19', line_color='Green',
> source=board.data, legend='Dev19')
> deviceType3Plot.line(x='Packet Number', y='Device20', line_color='Yellow',
> source=board.data, legend='Dev20')
> deviceType3Plot.line(x='Packet Number', y='Device21', line_color='Blue',
> source=board.data, legend='Dev21')
> deviceType3Plot.line(x='Packet Number', y='Device22', line_color='Blue',
> source=board.data, legend='Dev22')
>
> tableColumns = [
> TableColumn(field='Device12', title='Dev12'),
> TableColumn(field='Device13', title='Dev13'),
> TableColumn(field='Device14', title='Dev14'),
> TableColumn(field='Device15', title='Dev15'),
> TableColumn(field='Device16', title='Dev16'),
> TableColumn(field='Device17', title='Dev17'),
> TableColumn(field='Device18', title='Dev18'),
> TableColumn(field='Device19', title='Dev19'),
> TableColumn(field='Device20', title='Dev20'),
> TableColumn(field='Device21', title='Dev21'),
> TableColumn(field='Device22', title='Dev22')
> ]
>
> table = DataTable(source=board.data, columns=tableColumns, width=1400)
>
> page = column(deviceType3Plot, table)
>
> return Panel(child=page, title='Device Type3 Data')
>
> def createDeviceType4Page(board):
> attr1_tableColumns = [
> TableColumn(field='Device23_attr1', title='Dev23_attr1'),
> TableColumn(field='Device24_attr1', title='Dev24_attr1'),
> TableColumn(field='Device25_attr1', title='Dev25_attr1'),
> TableColumn(field='Device26_attr1', title='Dev26_attr1'),
> TableColumn(field='Device27_attr1', title='Dev27_attr1'),
> TableColumn(field='Device28_attr1', title='Dev28_attr1'),
> TableColumn(field='Device29_attr1', title='Dev29_attr1'),
> TableColumn(field='Device30_attr1', title='Dev30_attr1'),
> TableColumn(field='Device31_attr1', title='Dev31_attr1'),
> TableColumn(field='Device32_attr1', title='Dev32_attr1'),
> TableColumn(field='Device33_attr1', title='Dev33_attr1'),
> TableColumn(field='Device34_attr1', title='Dev34_attr1')
> ]
>
> attr1Table = DataTable(source=board.data, columns=attr1_tableColumns, width=1400)
>
> attr2_tableColumns = [
> TableColumn(field='Device23_attr2', title='Dev23_attr2'),
> TableColumn(field='Device24_attr2', title='Dev24_attr2'),
> TableColumn(field='Device25_attr2', title='Dev25_attr2'),
> TableColumn(field='Device26_attr2', title='Dev26_attr2'),
> TableColumn(field='Device27_attr2', title='Dev27_attr2'),
> TableColumn(field='Device28_attr2', title='Dev28_attr2'),
> TableColumn(field='Device29_attr2', title='Dev29_attr2'),
> TableColumn(field='Device30_attr2', title='Dev30_attr2'),
> TableColumn(field='Device31_attr2', title='Dev31_attr2'),
> TableColumn(field='Device32_attr2', title='Dev32_attr2'),
> TableColumn(field='Device33_attr2', title='Dev33_attr2'),
> TableColumn(field='Device34_attr2', title='Dev34_attr2')
> ]
>
> attr2Table = DataTable(source=board.data, columns=attr2_tableColumns, width=1400)
>
> page = column(attr1Table, attr2Table)
>
> return Panel(child=page, title='Device Type 4 Data')
>
> def createBoardPage(board):
> devType1Page = createDeviceXPage(board)
> devType3Page = createDeviceType3Page(board)
> devType4Page = createDeviceType4Page(board)
> tabs = Tabs(tabs=[devType1Page,
> devType3Page,
> devType4Page])
>
> return Panel(child=tabs, title='Board Data (' +str(board.port.port) +')')
>
> # find COM ports and create board objects
> boards = createBoards()
> boardTabs =
> for board in boards:
> boardTabs.append(createBoardPage(board))
>
> appTabs = Tabs(tabs=boardTabs)
> curdoc().add_root(appTabs)
>
> # sampleBoards(boards)
> # bokehCallback = partial(sampleBoards, boards)
>
> curdoc().add_periodic_callback(sampleBoards, 800)
>
>
>
> --
> 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/555cf80a-e0c6-48f0-9ed4-342ce596a34a%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/70e976ad-3b77-4df5-a0b4-5688d425a861%40continuum.io.
For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Bryan,

I agree that this is an extreme use case. I’m moving the project forward without Bokeh for now, but I may return here in the future to continue. Thanks again for your rapid input!

···

On Tuesday, February 5, 2019 at 3:51:46 PM UTC-6, Bryan Van de ven wrote:

Hi,

It’s certainly possible that the bottleneck is not layout (though you could double check that 1.1dev2 is the version displayed when the Bokeh server starts). Regarding updates, Bokeh is always going to redraw things whenever streaming data comes in. If you are trying to stream to 100 plots at once that simply may be out of scope for the project at present. I’ve certainly never tried that and have not heard of anyone else reporting on that kind of use case. It’s really hard to say anything for sure without actually running code and profiling,. though. If you have the ability to look at the profiling in the browser dev tools that would be helpful.

Thanks,

Bryan

On Feb 5, 2019, at 14:59, [email protected] wrote:

Ok, I tried running with Bokeh 1.1.0dev2 and there appears to be very little difference in performance if any. Is that the correct dev build?

I’m pretty certain I’m not setting things up very well, probably leading to Bokeh attempting to redraw everything all the time. I’m also using double nested tabs which I’ve seen can cause some issues. Only one tab ever needs to update at a time, is there a way to enforce that?

On Tuesday, February 5, 2019 at 12:59:46 PM UTC-6, Luke Ruatta wrote:

Thanks for the super quick response! I’m getting set up with conda, as the pip command just lists all requirements as already satisfied. I’ll let you know how it goes once I’m up and running.

On Tuesday, February 5, 2019 at 11:25:13 AM UTC-6, Bryan Van de ven wrote:

Hi,

There was recently a very large PR merged to completely refactor how layout is done. As a result layout is also much more performant. This work is not yet in a full release, but can be obtained in dev builds. So first things first, can you try out your codebase with 1.1dev build? Instructions for install dev builds can be found here:

    [https://bokeh.pydata.org/en/latest/docs/installation.html#developer-builds](https://bokeh.pydata.org/en/latest/docs/installation.html#developer-builds)

Thanks,

Bryan

On Feb 5, 2019, at 10:55, [email protected] wrote:

I’m working on a project to qualify some new devices for a hardware revision of a circuit board. I’m using Bokeh as a real-time front end to display the streaming data from these boards and allow our test engineers to monitor the device testing. I have a feeling my layout is causing Bokeh to go about things highly inefficiently, so I’m posting the Bokeh layout code to see if I can get a confirmation on this.

Currently, each board has 12 plots with a combined 55 lines, and 3 tables with 11 columns apiece. Each board has its own tab, and we are running up to 8 boards at a time. Total number of figures is about 100. This layout works beautifully with one board, starts getting sluggish with 3 boards, and is basically unusable with 8. I’ve perused this forum and it appears that this may be on the larger side as Bokeh documents tend to go. I’m also relatively new to Python with about 3 months of experience; I’m actually a firmware guy. This is running on Python 2.7.15. The data is coming from discrete serial ports, and I read each port buffer in the periodic callback. Each board has its own ColumnDataStream and stream call in the callback. The stream call is (essentially):

def sampleBoards():
for board in boards:
board.data.stream(dict_data, rollover=10)

I’m running the server with “bokeh serve --show my_script.py”. This reads and displays basically 2,500 bytes per second, but I’m only using Bokeh for live monitoring, hence the 10 sample rolling window. The Bokeh layout is as follows. This does not run, I sanitized the layout to illustrate how I’m putting it together. If something that will run would help, I can try to provide that.

from random import random
from functools import partial
import sys

from bokeh.layouts import column
from bokeh.models import ColumnDataSource, Range1d
from bokeh.models.widgets import Tabs, Panel, DataTable, TableColumn, HTMLTemplateFormatter
from bokeh.plotting import figure, curdoc

from device_read_script import sampleBoards, createBoards

def createDeviceType1Plot(board, device):
# create a plot and style its properties
plotWidth = 1400
plotHeight = 350

# define Bokeh tooltip
HOVER_TOOLTIP = [
    ('Attr', '$y{0,0.0}'),
    ('Packet Number', '$x{0,0}')
]

# Plot of Device Type 1
deviceType1Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='DeviceType1 '+device,
                  tooltips=HOVER_TOOLTIP,
                  tools="reset")
deviceType1Plot.title.text_font_size = '18px'
deviceType1Plot.toolbar.logo = None
deviceType1Plot.min_border_left = 30
deviceType1Plot.min_border_right = 80
deviceType1Plot.legend.location = 'top_left'
deviceType1Plot.legend.click_policy = 'hide'

# add a line renderer to our plot (no data yet)
deviceType1Plot.line(x='Packet Number', y=device + ' attr1', line_color='Black',
                     line_width=3, source=board.data, legend='attr1')
deviceType1Plot.line(x='Packet Number', y=device + ' attr2', line_color='Green',
                     line_width=3, source=board.data, legend='attr2')
deviceType1Plot.line(x='Packet Number', y=device + ' attr3', line_color='Blue',
                     line_width=3, source=board.data, legend='attr3')
deviceType1Plot.line(x='Packet Number', y=device + ' attr4', line_color='Red',
                     line_width=3, source=board.data, legend='attr4')

return deviceType1Plot

def createDeviceType2Plot(board, device):
# create a plot and style its properties
plotWidth = 1400
plotHeight = 350

# define Bokeh tooltip
HOVER_TOOLTIP = [
    ('Raw Value', '$y{0,0.0}'),
    ('Packet Number', '$x{0,0}')
]

# Plot of Device Type 2
deviceType2Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='DeviceType2 '+device,
                  tooltips=HOVER_TOOLTIP,
                  tools="reset")
deviceType2Plot.title.text_font_size = '18px'
deviceType2Plot.toolbar.logo = None
deviceType2Plot.min_border_left = 30
deviceType2Plot.min_border_right = 80
deviceType2Plot.legend.location = 'top_left'
deviceType2Plot.legend.click_policy = 'hide'

# add a line renderer to our plot (no data yet)
deviceType2Plot.line(x='Packet Number', y=device + ' attr1', line_color='Black',
                     line_width=3, source=board.data, legend='attr1')
deviceType2Plot.line(x='Packet Number', y=device + ' attr2', line_color='Green',
                     line_width=3, source=board.data, legend='attr2')

return deviceType2Plot

def createDeviceXPage(board):
dev1_plot = createDeviceType1Plot(board, ‘Device1’)
dev2_plot = createDeviceType1Plot(board, ‘Device2’)
dev3_plot = createDeviceType1Plot(board, ‘Device3’)
dev4_plot = createDeviceType1Plot(board, ‘Device4’)
dev5_plot = createDeviceType1Plot(board, ‘Device5’)
dev6_plot = createDeviceType1Plot(board, ‘Device6’)
dev7_plot = createDeviceType1Plot(board, ‘Device7’)
dev8_plot = createDeviceType1Plot(board, ‘Device8’)
dev9_plot = createDeviceType1Plot(board, ‘Device9’)
dev10_plot = createDeviceType2Plot(board, ‘Device10’)
dev11_plot = createDeviceType2Plot(board, ‘Device11’)

panel = column(dev1_plot, dev2_plot, dev3_plot,
               dev4_plot, dev5_plot, dev6_plot,
               dev7_plot, dev8_plot, dev9_plot,
               dev10_plot, dev11_plot)
return Panel(child=panel, title='Device X Data')

def createDeviceType3Page(board):
# create a plot and style its properties
plotWidth = 1400
plotHeight = 350

# Plot of Device Type 3
deviceType3Plot = figure(plot_height=plotHeight, plot_width=plotWidth, title='Device Type 3 Data',
                     tools="reset")
deviceType3Plot.title.text_font_size = '18px'
deviceType3Plot.toolbar.logo = None
deviceType3Plot.min_border_left = 10
deviceType3Plot.min_border_right = 10
deviceType3Plot.legend.location = 'top_left'
deviceType3Plot.legend.click_policy = 'hide'

# add a line renderer to our plot (no data yet)
deviceType3Plot.line(x='Packet Number', y='Device12', line_color='Black',
                     source=board.data, legend='Dev12')
deviceType3Plot.line(x='Packet Number', y='Device13', line_color='Black',
                source=board.data, legend='Dev13')
deviceType3Plot.line(x='Packet Number', y='Device14', line_color='Black',
                source=board.data, legend='Dev14')
deviceType3Plot.line(x='Packet Number', y='Device15', line_color='Black',
                source=board.data, legend='Dev15')
deviceType3Plot.line(x='Packet Number', y='Device16', line_color='Red',
                source=board.data, legend='Dev16')
deviceType3Plot.line(x='Packet Number', y='Device17', line_color='Red',
                source=board.data, legend='Dev17')
deviceType3Plot.line(x='Packet Number', y='Device18', line_color='Green',
                source=board.data, legend='Dev18')
deviceType3Plot.line(x='Packet Number', y='Device19', line_color='Green',
                source=board.data, legend='Dev19')
deviceType3Plot.line(x='Packet Number', y='Device20', line_color='Yellow',
                source=board.data, legend='Dev20')
deviceType3Plot.line(x='Packet Number', y='Device21', line_color='Blue',
                source=board.data, legend='Dev21')
deviceType3Plot.line(x='Packet Number', y='Device22', line_color='Blue',
                source=board.data, legend='Dev22')

tableColumns = [
    TableColumn(field='Device12', title='Dev12'),
    TableColumn(field='Device13', title='Dev13'),
    TableColumn(field='Device14', title='Dev14'),
    TableColumn(field='Device15', title='Dev15'),
    TableColumn(field='Device16', title='Dev16'),
    TableColumn(field='Device17', title='Dev17'),
    TableColumn(field='Device18', title='Dev18'),
    TableColumn(field='Device19', title='Dev19'),
    TableColumn(field='Device20', title='Dev20'),
    TableColumn(field='Device21', title='Dev21'),
    TableColumn(field='Device22', title='Dev22')
]

table = DataTable(source=board.data, columns=tableColumns, width=1400)

page = column(deviceType3Plot, table)

return Panel(child=page, title='Device Type3 Data')

def createDeviceType4Page(board):
attr1_tableColumns = [
TableColumn(field=‘Device23_attr1’, title=‘Dev23_attr1’),
TableColumn(field=‘Device24_attr1’, title=‘Dev24_attr1’),
TableColumn(field=‘Device25_attr1’, title=‘Dev25_attr1’),
TableColumn(field=‘Device26_attr1’, title=‘Dev26_attr1’),
TableColumn(field=‘Device27_attr1’, title=‘Dev27_attr1’),
TableColumn(field=‘Device28_attr1’, title=‘Dev28_attr1’),
TableColumn(field=‘Device29_attr1’, title=‘Dev29_attr1’),
TableColumn(field=‘Device30_attr1’, title=‘Dev30_attr1’),
TableColumn(field=‘Device31_attr1’, title=‘Dev31_attr1’),
TableColumn(field=‘Device32_attr1’, title=‘Dev32_attr1’),
TableColumn(field=‘Device33_attr1’, title=‘Dev33_attr1’),
TableColumn(field=‘Device34_attr1’, title=‘Dev34_attr1’)
]

attr1Table = DataTable(source=board.data, columns=attr1_tableColumns, width=1400)

attr2_tableColumns = [
    TableColumn(field='Device23_attr2', title='Dev23_attr2'),
    TableColumn(field='Device24_attr2', title='Dev24_attr2'),
    TableColumn(field='Device25_attr2', title='Dev25_attr2'),
    TableColumn(field='Device26_attr2', title='Dev26_attr2'),
    TableColumn(field='Device27_attr2', title='Dev27_attr2'),
    TableColumn(field='Device28_attr2', title='Dev28_attr2'),
    TableColumn(field='Device29_attr2', title='Dev29_attr2'),
    TableColumn(field='Device30_attr2', title='Dev30_attr2'),
    TableColumn(field='Device31_attr2', title='Dev31_attr2'),
    TableColumn(field='Device32_attr2', title='Dev32_attr2'),
    TableColumn(field='Device33_attr2', title='Dev33_attr2'),
    TableColumn(field='Device34_attr2', title='Dev34_attr2')
]

attr2Table = DataTable(source=board.data, columns=attr2_tableColumns, width=1400)

page = column(attr1Table, attr2Table)

return Panel(child=page, title='Device Type 4 Data')

def createBoardPage(board):
devType1Page = createDeviceXPage(board)
devType3Page = createDeviceType3Page(board)
devType4Page = createDeviceType4Page(board)
tabs = Tabs(tabs=[devType1Page,
devType3Page,
devType4Page])

return Panel(child=tabs, title='Board Data (' +str(board.port.port) +')')

find COM ports and create board objects

boards = createBoards()
boardTabs =
for board in boards:
boardTabs.append(createBoardPage(board))

appTabs = Tabs(tabs=boardTabs)
curdoc().add_root(appTabs)

sampleBoards(boards)

bokehCallback = partial(sampleBoards, boards)

curdoc().add_periodic_callback(sampleBoards, 800)


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/555cf80a-e0c6-48f0-9ed4-342ce596a34a%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/70e976ad-3b77-4df5-a0b4-5688d425a861%40continuum.io.

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