我试图做一个简单的图形用户界面,我可以选择哪些数据加载和显示在一个Bokeh图。然后我可以删除这些数据。基本思路如下:
但是,我遇到了很多问题:
悬停工具不适用于添加的行
交互式图例不适用于添加的行
当我试图通过删除数据时plot.renderers.remove(删除线\u到\u), 整个bokeh情节变得疯狂(见下图2)
我很抱歉一下子就把这些问题抛诸脑后,但我已经花了好几天的时间试图解决所有这些问题,并且没有一个顺利的工作流程。考虑换到另一个图书馆,这会很糟糕,因为bokeh在美学上并不讨人喜欢。在
以下是我所有可编译的代码:
# general imports
from bokeh.client import push_session
import numpy as np
from scipy import signal
from timeit import default_timer as timer
import itertools
import ctypes
import copy
import pickle
import os
import os.path
#bokeh, plotting
from bokeh.io import show, output_notebook, output_file, save, reset_output, curdoc
from bokeh.layouts import row, column, widgetbox
from bokeh.models import ColumnDataSource, Select, MultiSelect
from bokeh.models.widgets import Slider, TextInput, Dropdown, Button
from bokeh.plotting import figure
from tornado.ioloop import IOLoop
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
from bokeh.server.server import Server
from bokeh.palettes import Dark2_5 as palette
from bokeh.palettes import Set1 as palettec #Set1, Category10
io_loop = IOLoop.current()
masterDir = 'C:\\Data\\BrownData\\'
_DEBUG_MODE = True
def modify_doc(doc):
TOOLS = "pan,wheel_zoom,box_zoom,reset,hover,save,resize"
SCREEN_WIDTH = ctypes.windll.user32.GetSystemMetrics(0)
SCREEN_HEIGHT = ctypes.windll.user32.GetSystemMetrics(1)
active_scroll = 'wheel_zoom'
colors2 = itertools.cycle(palettec)
colors = list(zip(range(10), colors2))
col = copy.deepcopy(palettec[7])
if not _DEBUG_MODE:
pass
else:
print('In debug mode')
Nsubdir, Nfiles, Nsigs = 2, 3, 4
dataInfo ={}
dataInfo['subDirNames'] = ['Subdir' + str(x) for x in range(Nsubdir)]
dataInfo['subDirFiles'] = [['File_' + str(y) + '_' + str(x) for x in range(Nfiles)] for y in range(Nsubdir)]
dataInfo['analogSignalNames'] = [
[['Sig_' + str(y) + str(x) + '_' + str(w) for w in range(Nsigs)] for x in range(Nfiles)] for y in range(Nsubdir)]
debugData = [np.array([0, 1, 0, 2, -1]).T * (i + 1) for i in range(20)]
debugData = [np.arange(1, 6, 1)] + debugData
subdir_dropdown = Select(title="Subdirectory", value=" ", options=[" "]+dataInfo['subDirNames'])
files_dropdown = Select(title="File", value=" ", options=[' '])
signal_dropdown = MultiSelect(title="Analog Signals:", value=[' '], options=[' '], size=7)
dataq={}
dataq['x'] = debugData[0]
dataq['y1'] = debugData[1]
dataq['y2'] = debugData[2]
source = ColumnDataSource(dataq)
# Set up layouts and add to document
inputs = widgetbox(subdir_dropdown, files_dropdown, signal_dropdown, width=int(SCREEN_WIDTH * .15))
#final = row(inputs, plt, plt_fft, width=int(SCREEN_WIDTH * .9))
mainLayout = row(row(inputs, name='Widgets'), name='mainLayout')
doc.add_root(mainLayout)
#session = push_session(doc)
plt_reset = 0
def getSignal(name, indx):
return debugData[indx]
def subdir_callback(attrname, old, new):
# first clear plot
rootLayout = doc.get_model_by_name('mainLayout')
listOfSubLayouts = rootLayout.children
plotToRemove = doc.get_model_by_name('sigplot')
if plotToRemove is not None:
listOfSubLayouts.remove(plotToRemove)
plt_reset, col = 1, palettec[7]
# change files menu options
indx = dataInfo['subDirNames'].index(subdir_dropdown.value)
files_dropdown.options = dataInfo['subDirFiles'][indx]
def files_callback(attrname, old, new):
# first clear plot
rootLayout = doc.get_model_by_name('mainLayout')
listOfSubLayouts = rootLayout.children
plotToRemove = doc.get_model_by_name('sigplot')
if plotToRemove is not None:
listOfSubLayouts.remove(plotToRemove)
plt_reset, col = 1, palettec[7]
# then start new plot
if not doc.get_model_by_name('sigplot'):
sigplot = figure(name='sigplot', tools=TOOLS)
plotToAdd = sigplot
r1 = sigplot.line(x=[1,2], y=[2,1], legend='test')
sigplot.legend.location = "top_left"
sigplot.legend.click_policy = "hide"
else:
plotToAdd = doc.get_model_by_name('sigplot')
listOfSubLayouts.append(plotToAdd)
# change menu options
sd_indx = dataInfo['subDirNames'].index(subdir_dropdown.value)
file_indx = dataInfo['subDirFiles'][sd_indx].index(files_dropdown.value)
signal_dropdown.options = dataInfo['analogSignalNames'][sd_indx][file_indx]
def analogSigs_callback(attrname, old, new):
nonlocal plt_reset, col
sigplot = doc.get_model_by_name('sigplot')
if old == [' ']:
old = []
if plt_reset == 1:
old = []
if len(new) > len(old): # add line
new_element = list(set(new) - set(old))
if len(new_element) > 1:
print('WARNING: somehow more than 1 new element chosen')
new_element = new_element[0]
sig_indx = signal_dropdown.options.index(new_element)
if new_element not in source.data:
source.add(getSignal(new_element, sig_indx), name=new_element)
else:
tmp_line = sigplot.select_one({'name': new_element})
#tmp_line.visible = True
sigplot.line(x='x', y=new_element, source=source, legend='ch' + new_element, line_width=1, color=col[0], name=new_element)
del col[0]
if len(new) < len(old): # remove line
removed_elements = list(set(old) - set(new))
for elem in removed_elements:
tmp_line = sigplot.select_one({'name': elem})
#tmp_line.visible = False
sigplot.renderers.remove(tmp_line)
if len(new) == len(old): # switch single line
tmp_line = sigplot.select_one({'name': old[0]})
#tmp_line.visible = False
sigplot.renderers.remove(tmp_line)
analogSigs_callback(attrname, [], new)
subdir_dropdown.on_change('value', subdir_callback)
files_dropdown.on_change('value', files_callback)
signal_dropdown.on_change('value', analogSigs_callback)
print('done')
bokeh_app = Application(FunctionHandler(modify_doc))
server = Server({'/': bokeh_app}, io_loop=io_loop, port=5006)
server.start()
if __name__ == '__main__':
#modify_doc(None)
print('Opening Bokeh application on http://localhost:5006/')
io_loop.add_callback(server.show, "/")
io_loop.start()
下面是一个工作示例
源可以改变长度。它不能同时有不同长度的列。因此,当加载新数据时,无论新的长度是什么,都要用新dict替换
source.data
。在示例:
如果您的轴类型改变(线性、日志、日期时间、因子/分类),那么它将变得更加复杂,我不确定每个转换都是可能的。在
相关问题 更多 >
编程相关推荐