Ich versuche eine einfache GUI zu erstellen, wo ich auswählen kann, welche Daten in einem Bokeh-Plot geladen und angezeigt werden sollen. Ich kann diese Daten dann entfernen. Die Grundidee ist unten dargestellt: Bokeh GUI beim Hinzufügen/Entfernen von Zeilen
Allerdings habe ich in eine Vielzahl von Fragen führen:
Hover-Tool nicht mit zusätzlichen Linien arbeitet
Interactive Legende nicht mit zusätzlicher Arbeit Linien
Wenn ich versuche, Daten über plot.renderers.remove (line_to_remove) zu entfernen, das ganze Bokeh Grundstück verrückt (siehe 2. Abbildung unten)
- line_to_remove.visible = False funktioniert nicht
Es tut mir Leid auf einmal all diese Probleme fallen, aber ich habe diese Knicke verbracht Tagen versuchen, alle raus und einen reibungslosen Workflow Bokeh haben ohne Glück. Über den Wechsel zu einer anderen Bibliothek nachzudenken, die buckeln würde, ist nicht ästhetisch.
Hier ist mein Code, der übersetzbar ist:
# 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()
Wenn die Daten für die zwei Glyphen, die Sie zu pflegen scheinen, immer das gleiche Format haben (zB x: 'datetime' und y:' float'), müssen Sie wirklich nicht migrieren die Renderer auf diese Weise. Aktualisieren Sie einfach jede Glyphen-Datenquelle (es könnte auch eine gemeinsame Quelle sein, die verschiedene Spalten verwendet) und alle anderen Glyphen-bezogenen Informationen (Legende, Farben usw.), wenn sie nicht aus der Datenquelle stammen. Wenn das Format ist anders (Datum Zeit <-> kategorisch dann ist es möglich, aber nie konfrontiert, dass Fall) – Alex
Dank @Alex. Das Problem ist, dass ich keine Datenquellen mit unterschiedlichen Spaltenlängen haben kann, sollte ich also alle Dateisignale zu einer wirklich langen Spalte von NaNs initialisieren und sie dann verstecken? Das scheint zu viel Speicheraufwand zu verursachen – DankMasterDan