first try on dynamic adding/removing plots
This commit is contained in:
63
actor.py
Normal file
63
actor.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
from cacher import cacher
|
||||||
|
from plots import Plot0D, Plot1D, Plot2D
|
||||||
|
from sources import Camera, Source
|
||||||
|
|
||||||
|
|
||||||
|
def update_all(plt, cache):
|
||||||
|
plt.set(*cache.xy)
|
||||||
|
|
||||||
|
def update_latest(plt, cache):
|
||||||
|
plt.set(cache.latest)
|
||||||
|
|
||||||
|
|
||||||
|
def decide_src_plt_cache(pvname):
|
||||||
|
pvname = pvname.upper()
|
||||||
|
|
||||||
|
if pvname.endswith(":FPICTURE"):
|
||||||
|
print("Camera", pvname)
|
||||||
|
src = Camera(pvname)
|
||||||
|
plt = Plot2D
|
||||||
|
cache_size = 1
|
||||||
|
update = update_latest
|
||||||
|
if not src.is_connected:
|
||||||
|
raise RuntimeError(f"{pvname} is not connected")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Source", pvname)
|
||||||
|
src = Source(pvname)
|
||||||
|
if not src.is_connected:
|
||||||
|
raise RuntimeError(f"{pvname} is not connected")
|
||||||
|
print(src.nelm)
|
||||||
|
if src.nelm == 0:
|
||||||
|
raise RuntimeError(f"{src}: {src.value}")
|
||||||
|
|
||||||
|
if src.nelm == 1:
|
||||||
|
print("Scalar")
|
||||||
|
plt = Plot0D
|
||||||
|
cache_size = 100
|
||||||
|
update = update_all
|
||||||
|
else:
|
||||||
|
print("Curve")
|
||||||
|
plt = Plot1D
|
||||||
|
cache_size = 1
|
||||||
|
update = update_latest
|
||||||
|
|
||||||
|
plt = plt()
|
||||||
|
plt.name = plt.fig.name = pvname #TODO this needs to be done in Plot*
|
||||||
|
|
||||||
|
cache = cacher.add_source(src, size=cache_size)
|
||||||
|
|
||||||
|
return src, plt, cache, update
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Actor:
|
||||||
|
|
||||||
|
def __init__(self, pvname, cache_size=100):
|
||||||
|
self.src, self.plt, self.cache, self._update = decide_src_plt_cache(pvname)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self._update(self.plt, self.cache)
|
||||||
|
|
||||||
|
|
||||||
|
|
61
director.py
Normal file
61
director.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
from random import choice
|
||||||
|
|
||||||
|
from bokeh.layouts import column, row
|
||||||
|
from bokeh.models import Button, Div, Spacer, TextInput
|
||||||
|
from bokeh.plotting import curdoc
|
||||||
|
|
||||||
|
from actor import Actor
|
||||||
|
|
||||||
|
|
||||||
|
class Director:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.doc = doc = curdoc()
|
||||||
|
doc.title = choice("👺👹") + " kabuki"
|
||||||
|
|
||||||
|
ti_add_pv = TextInput(value="", title="Add PV:")
|
||||||
|
ti_add_pv.on_change("value", self.on_add_pv)
|
||||||
|
|
||||||
|
self.layout = layout = column(ti_add_pv)
|
||||||
|
doc.add_root(layout)
|
||||||
|
|
||||||
|
|
||||||
|
def add_pvs(self, *pvnames):
|
||||||
|
for n in pvnames:
|
||||||
|
self.add_pv(n)
|
||||||
|
|
||||||
|
|
||||||
|
def on_add_pv(self, attr, old, new):
|
||||||
|
print("CB Add PV:", attr, old, new)
|
||||||
|
self.add_pv(new)
|
||||||
|
|
||||||
|
|
||||||
|
def add_pv(self, pvname):
|
||||||
|
print("Add PV:", pvname)
|
||||||
|
|
||||||
|
try:
|
||||||
|
a = Actor(pvname)
|
||||||
|
except Exception as e:
|
||||||
|
print("caught:", type(e), e)
|
||||||
|
return
|
||||||
|
|
||||||
|
def add_header_widgets(fig):
|
||||||
|
btn = Button(label="🗙", button_type="light", width=35)
|
||||||
|
lbl = Div(text=fig.name, align="center", style={"font-size": "150%"})
|
||||||
|
res = column(row(btn, lbl), fig, Spacer(height=35))
|
||||||
|
btn.on_click(lambda: self.remove_plot(res))
|
||||||
|
return res
|
||||||
|
|
||||||
|
fig = a.plt.fig
|
||||||
|
fig = add_header_widgets(fig)
|
||||||
|
# self.layout.children.append(fig)
|
||||||
|
self.layout.children.insert(1, fig) # 1 to skip TextInput
|
||||||
|
self.doc.add_periodic_callback(a.update, 1000)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_plot(self, which):
|
||||||
|
print("Remove plot:", which)
|
||||||
|
self.layout.children.remove(which)
|
||||||
|
|
||||||
|
|
||||||
|
|
42
kabuki.py
42
kabuki.py
@ -1,44 +1,10 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
from random import choice
|
|
||||||
|
|
||||||
from bokeh.plotting import curdoc
|
|
||||||
from bokeh.layouts import column
|
|
||||||
|
|
||||||
from cacher import cacher
|
from cacher import cacher
|
||||||
from plots import Plot0D, Plot1D, Plot2D
|
from director import Director
|
||||||
from sources import Camera, Source
|
|
||||||
|
|
||||||
|
|
||||||
src0 = Source("MTEST:RAND0")
|
app = Director()
|
||||||
src1 = Source("MTEST:ARR")
|
app.doc.add_periodic_callback(lambda: print(cacher), 10000)
|
||||||
src2 = Camera("MTEST:CHAN-IMAGE:FPICTURE")
|
app.add_pvs("MTEST:RAND0", "MTEST:ARR", "MTEST:CHAN-IMAGE:FPICTURE")
|
||||||
|
|
||||||
cache0 = cacher.add_source(src0)
|
|
||||||
cache1 = cacher.add_source(src1, size=1) #TODO: statistics on the curves would need larger cache
|
|
||||||
cache2 = cacher.add_source(src2, size=1)
|
|
||||||
|
|
||||||
p0 = Plot0D()
|
|
||||||
p1 = Plot1D()
|
|
||||||
p2 = Plot2D()
|
|
||||||
|
|
||||||
def update():
|
|
||||||
p0.set(*cache0.xy)
|
|
||||||
p1.set(cache1.latest)
|
|
||||||
p2.set(cache2.latest)
|
|
||||||
|
|
||||||
|
|
||||||
doc = curdoc()
|
|
||||||
doc.title = choice("👺👹") + " kabuki"
|
|
||||||
doc.add_periodic_callback(update, 1000)
|
|
||||||
|
|
||||||
plt = column(
|
|
||||||
p0.fig,
|
|
||||||
p1.fig,
|
|
||||||
p2.fig
|
|
||||||
)
|
|
||||||
|
|
||||||
doc.add_root(plt)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user