From 67dfc77e4616aac119e4bc796b2185e0444b5fdf Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Wed, 2 Jun 2021 15:02:14 +0200 Subject: [PATCH] first try on wrapper classes (Object, Container (Column, Row)) that simplify the append/remove logic --- actor.py | 2 +- buki.py | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++ director.py | 17 +++++----- frame.py | 10 +++--- plot0d.py | 7 ++-- plot1d.py | 7 ++-- plot2d.py | 7 ++-- 7 files changed, 124 insertions(+), 19 deletions(-) create mode 100644 buki.py diff --git a/actor.py b/actor.py index 2d197f6..6e7aef3 100644 --- a/actor.py +++ b/actor.py @@ -44,7 +44,7 @@ def decide_src_plt_cache_update(pvname): update = update_latest plt = plt() - plt.name = plt.fig.name = pvname #TODO this needs to be done in Plot* + plt.name = plt.layout.name = pvname #TODO this needs to be done in Plot* cache = cacher.add_source(src, size=cache_size) diff --git a/buki.py b/buki.py new file mode 100644 index 0000000..02ef39a --- /dev/null +++ b/buki.py @@ -0,0 +1,93 @@ +from weakref import WeakSet #TODO: for debugging + +from bokeh.layouts import column, row + + +class Object: + + instances = WeakSet() #TODO: for debugging + + def __init__(self, layout, parent=None): + self.layout = layout + self.parent = parent + self.instances.add(self) #TODO: for debugging + + def delete(self): + if self.parent is not None: + self.parent.remove(self) + + def __repr__(self): + return repr(self.layout) + + + +class Container(Object): + + factory = None #TODO: abc + + def __init__(self, *children, parent=None, **kwargs): + self.children = children = [ensure_Object(obj) for obj in children] + + layouts = [] + for obj in children: + layouts.append(obj.layout) + obj.parent = self + + factory = self.factory.__func__ # use function and not method (i.e., no self in args) + layout = factory(*layouts, **kwargs) + super().__init__(layout, parent=parent) + + + def append(self, obj): + obj = ensure_Object(obj) + obj.parent = self + self.children.append(obj) + self.layout.children.append(obj.layout) + + def prepend(self, obj): + obj = ensure_Object(obj) + obj.parent = self + self.children.insert(0, obj) + self.layout.children.insert(0, obj.layout) + + def remove(self, obj): + obj = ensure_Object(obj) + obj.parent = None + self.children.remove(obj) + self.layout.children.remove(obj.layout) +# if not self.children: +# print("Delete emptied container") +# self.delete() + + def delete(self): + for obj in self.children: + obj.parent = None +# self.children = [] + super().delete() + + def __bool__(self): + return bool(self.children) + + def __iter__(self): + return iter(self.children) + + def __repr__(self): + return f"{self.layout}: {self.children}" + + + +def ensure_Object(obj): + if isinstance(obj, Object): + return obj + return Object(obj) + + + +class Column(Container): + factory = column + +class Row(Container): + factory = row + + + diff --git a/director.py b/director.py index 486ace3..c6712fc 100644 --- a/director.py +++ b/director.py @@ -5,6 +5,7 @@ from bokeh.models import Button, Div, Spacer, TextInput from bokeh.plotting import curdoc from actor import Actor +from buki import Column, Row class Director: @@ -16,8 +17,8 @@ class Director: self.ti_add_pvs = ti_add_pvs = TextInput(value="", title="Add PV:") ti_add_pvs.on_change("value", self.do_add_pvs) - self.plot_container = plot_container = column() - root_container = column(ti_add_pvs, Spacer(height=15), plot_container) + self.plot_container = plot_container = Column() + root_container = column(ti_add_pvs, Spacer(height=15), plot_container.layout) doc.add_root(root_container) self.updates = [] @@ -43,11 +44,11 @@ class Director: pvs_row = self.make_pvs_row(pvnames) if not pvs_row.children: continue # do not insert empty rows - self.plot_container.children.insert(0, pvs_row) + self.plot_container.prepend(pvs_row) def make_pvs_row(self, pvnames): - container = row() + container = Row() for n in pvnames: print("Add PV:", n) try: @@ -65,7 +66,7 @@ class Director: print("Connect close button") a.plt.on_click_close(lambda: self.remove_actor(a)) print("Add plot") - a.container.children.append(a.plt.fig) + a.container.append(a.plt) print("Add update") self.updates.append(a.update) @@ -75,10 +76,10 @@ class Director: print("Remove update") self.updates.remove(a.update) print("Remove plot") - a.container.children.remove(a.plt.fig) - if not a.container.children: + a.container.remove(a.plt) + if not a.container: print("Remove emptied container") - self.plot_container.children.remove(a.container) + self.plot_container.remove(a.container) diff --git a/frame.py b/frame.py index fc229ba..0670731 100644 --- a/frame.py +++ b/frame.py @@ -1,19 +1,21 @@ from bokeh.layouts import column, row from bokeh.models import Button, Div, Spacer, TextInput +from buki import Column, Row + CROSS = "🗙" GEAR = "⚙" -class Frame: +class Frame(Column): def __init__(self, plt): self.plt = plt self.set = plt.set # make frame act like contained plot name = plt.name - inner_fig = plt.fig + inner_fig = plt.layout n_btns = 2 btn_width = 35 @@ -39,7 +41,7 @@ class Frame: self.inner = inner = row(inner_fig, cfg_dialog) size_maintainer = row(inner, min_width=inner_fig.width, min_height=inner_fig.height) #TODO: why min_* ? - self.fig = column(header, size_maintainer, Spacer(height=35)) + super().__init__(header, size_maintainer, Spacer(height=35)) def on_click_close(self, *args, **kwargs): @@ -49,7 +51,7 @@ class Frame: toggle_button_state(self.btn_close) # do not close plot from cfg dialog toggle_button_type(self.btn_cfg, "default", "warning") self.inner.visible = False - switch_visibility(self.plt.fig, self.cfg_dialog) + switch_visibility(self.plt.layout, self.cfg_dialog) self.inner.visible = True diff --git a/plot0d.py b/plot0d.py index 249dba7..688b617 100644 --- a/plot0d.py +++ b/plot0d.py @@ -4,8 +4,10 @@ from bokeh.plotting import figure from bokeh.layouts import row from bokeh.models import ColumnDataSource +from buki import Object -class Plot0D: + +class Plot0D(Object): def __init__(self): self.timeseries = timeseries = TimeSeries() @@ -13,7 +15,8 @@ class Plot0D: #TODO: if not set, width and height are None width = timeseries.fig.width + histo.fig.width height = max(timeseries.fig.height, histo.fig.height) - self.fig = row(timeseries.fig, histo.fig, width=width, height=height) + fig = row(timeseries.fig, histo.fig, width=width, height=height) + super().__init__(fig) def set(self, times, values): self.timeseries.set(times, values) diff --git a/plot1d.py b/plot1d.py index efbb2c1..ad54c9b 100644 --- a/plot1d.py +++ b/plot1d.py @@ -3,8 +3,10 @@ import numpy as np from bokeh.plotting import figure from bokeh.models import ColumnDataSource +from buki import Object -class Plot1D: + +class Plot1D(Object): def __init__(self): data = { @@ -14,9 +16,10 @@ class Plot1D: self.source = source = ColumnDataSource(data=data) - self.fig = fig = figure() + fig = figure() fig.line(x="x", y="y", source=source) fig.circle(x="x", y="y", source=source) + super().__init__(fig) def set(self, y): diff --git a/plot2d.py b/plot2d.py index f4adef9..2714d41 100644 --- a/plot2d.py +++ b/plot2d.py @@ -3,8 +3,10 @@ import numpy as np from bokeh.plotting import figure from bokeh.models import ColumnDataSource +from buki import Object -class Plot2D: + +class Plot2D(Object): def __init__(self, *args, **kwargs): data = { @@ -13,8 +15,9 @@ class Plot2D: self.source = source = ColumnDataSource(data=data) - self.fig = fig = figure() + fig = figure() fig.image(source=source, x=0, y=0, dw=1, dh=1, palette="Spectral11") + super().__init__(fig) def set(self, image):