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)