Content-Length: 36725 | pFad | http://github.com/fastplotlib/fastplotlib/pull/849.patch

thub.com From 18383fcfb89bd7dd56d7f6b4c37b52c8ab2ad86c Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Sat, 31 May 2025 20:19:17 -0400 Subject: [PATCH 01/10] basic prototype working :D --- examples/guis/imgui_decorator.py | 30 ++++++++++ fastplotlib/layouts/_imgui_figure.py | 85 +++++++++++++++++++++++----- fastplotlib/ui/_base.py | 24 ++++---- 3 files changed, 114 insertions(+), 25 deletions(-) create mode 100644 examples/guis/imgui_decorator.py diff --git a/examples/guis/imgui_decorator.py b/examples/guis/imgui_decorator.py new file mode 100644 index 00000000..ab1c8c26 --- /dev/null +++ b/examples/guis/imgui_decorator.py @@ -0,0 +1,30 @@ +""" +ImGUI Decorator +=============== + +Create imgui UIs quickly using a decorator! + +See the imgui docs for extensive examples on how to create all UI elements: https://pyimgui.readthedocs.io/en/latest/reference/imgui.core.html#imgui.core.begin_combo +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + + +import numpy as np +import fastplotlib as fpl +from imgui_bundle import imgui + +figure = fpl.Figure() +figure[0, 0].add_line(np.random.rand(100)) + + +@figure.add_gui(location="right", title="yay", size=100) +def gui(): + if imgui.button("reset data"): + figure[0, 0].graphics[0].data[:, 1] = np.random.rand(100) + + +figure.show(maintain_aspect=False) + +fpl.loop.run() diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index c5489023..cfb148a1 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -150,34 +150,91 @@ def _draw_imgui(self) -> imgui.ImDrawData: return imgui.get_draw_data() - def add_gui(self, gui: EdgeWindow): + def add_gui( + self, + gui: EdgeWindow = None, + location: Literal["right", "bottom"] = "right", + title="GUI Window", + size: int = 200, + window_flags: imgui.WindowFlags_ = imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_resize, + ): """ Add a GUI to the Figure. GUIs can be added to the left or bottom edge. + Can also be used as a decorator. + Parameters ---------- gui: EdgeWindow A GUI EdgeWindow instance + location: str, "right" | "bottom" + window location + + title: str + window title + + size: int + width or height of the window depending on location + + window_flags: imgui.WindowFlags_, default imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_resize, + imgui.WindowFlags_ enum + + Examples + -------- + + As a decorator:: + + import numpy as np + import fastplotlib as fpl + from imgui_bundle import imgui + + figure = fpl.Figure() + figure[0, 0].add_line(np.random.rand(100)) + + + @figure.add_gui(location="right", title="yay", size=100) + def gui(): + if imgui.button("reset data"): + figure[0, 0].graphics[0].data[:, 1] = np.random.rand(100) + + figure.show(maintain_aspect=False) + """ - if not isinstance(gui, EdgeWindow): - raise TypeError( - f"GUI must be of type: {EdgeWindow} you have passed a {type(gui)}" - ) - location = gui.location + def decorator(_gui_func): + if location not in GUI_EDGES: + raise ValueError( + f"GUI does not have a valid location, valid locations are: {GUI_EDGES}, you have passed: {location}" + ) - if location not in GUI_EDGES: - raise ValueError( - f"GUI does not have a valid location, valid locations are: {GUI_EDGES}, you have passed: {location}" - ) + if self.guis[location] is not None: + raise ValueError(f"GUI already exists in the desired location: {location}") + + if not isinstance(gui, EdgeWindow): + ew = EdgeWindow( + figure=self, + size=size, + location=location, + title=title, + update_call=_gui_func, + window_flags=window_flags + ) + window_location = location + else: + ew = _gui_func + window_location = location + + self.guis[window_location] = ew - if self.guis[location] is not None: - raise ValueError(f"GUI already exists in the desired location: {location}") + self._fpl_reset_layout() - self.guis[location] = gui + return _gui_func + + if not isinstance(gui, EdgeWindow): + return decorator - self._fpl_reset_layout() + decorator(gui) def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: """ diff --git a/fastplotlib/ui/_base.py b/fastplotlib/ui/_base.py index e31dd8d4..784eca4a 100644 --- a/fastplotlib/ui/_base.py +++ b/fastplotlib/ui/_base.py @@ -1,5 +1,5 @@ +from collections.abc import Callable from typing import Literal -import numpy as np from imgui_bundle import imgui @@ -31,7 +31,6 @@ def update(self): class Window(BaseGUI): """Base class for imgui windows drawn within Figures""" - pass @@ -42,8 +41,9 @@ def __init__( size: int, location: Literal["bottom", "right"], title: str, - window_flags: int = imgui.WindowFlags_.no_collapse + window_flags: imgui.WindowFlags_ = imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_resize, + update_call: Callable = None, *args, **kwargs, ): @@ -64,7 +64,7 @@ def __init__( title: str window title - window_flags: int + window_flags: imgui.WindowFlags_ window flag enum, valid flags are: .. code-block:: py @@ -94,10 +94,10 @@ def __init__( imgui.WindowFlags_.no_inputs *args - additional args for the GUI + additional args **kwargs - additional kwargs for teh GUI + additional kwargs """ super().__init__() @@ -115,6 +115,11 @@ def __init__( self._figure.canvas.add_event_handler(self._set_rect, "resize") + if update_call is None: + self._update_call = self.update + else: + self._update_call = update_call + @property def size(self) -> int | None: """width or height of the edge window""" @@ -185,11 +190,8 @@ def get_rect(self) -> tuple[int, int, int, int]: def draw_window(self): """helps simplify using imgui by managing window creation & position, and pushing/popping the ID""" # window position & size - x, y, w, h = self.get_rect() imgui.set_next_window_size((self.width, self.height)) imgui.set_next_window_pos((self.x, self.y)) - # imgui.set_next_window_pos((x, y)) - # imgui.set_next_window_size((w, h)) flags = self._window_flags # begin window @@ -198,8 +200,8 @@ def draw_window(self): # push ID to prevent conflict between multiple figs with same UI imgui.push_id(self._id_counter) - # draw stuff from subclass into window - self.update() + # draw imgui UI elements into window + self._update_call() # pop ID imgui.pop_id() From 8214849cf5ee0f2e5c0eb61d853b5313f54ea4d2 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Sun, 1 Jun 2025 00:21:35 -0400 Subject: [PATCH 02/10] add ChangeFlag --- fastplotlib/ui/utils.py | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 fastplotlib/ui/utils.py diff --git a/fastplotlib/ui/utils.py b/fastplotlib/ui/utils.py new file mode 100644 index 00000000..620e2e8d --- /dev/null +++ b/fastplotlib/ui/utils.py @@ -0,0 +1,46 @@ +class ChangeFlag: + """ + A flag that helps detect whether an imgui UI has been changed by the user. + Basically, once True, always True. + + Example:: + + changed = ChangeFlag(False) + + changed.value, bah = (False, False) + + print(changed.value) + + changed.value, bah = (True, False) + + print(changed.value) + + changed.value, bah = (False, False) + + print(changed.value) + + """ + def __init__(self, value: bool): + self._value = bool(value) + + @property + def value(self) -> bool: + return self._value + + @value.setter + def value(self, value: bool): + if value: + self._value = True + + def __bool__(self): + return self.value + + def __or__(self, other): + if other: + self._value = True + + def __eq__(self, other): + return self.value == other + + def force_value(self, value): + self._value = value From 3c998ee54cec527e8584a4c0d680c374336b0406 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Sun, 1 Jun 2025 00:21:54 -0400 Subject: [PATCH 03/10] docstring, comments --- fastplotlib/layouts/_imgui_figure.py | 61 +++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index cfb148a1..9d8e9d83 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -161,24 +161,30 @@ def add_gui( """ Add a GUI to the Figure. GUIs can be added to the left or bottom edge. - Can also be used as a decorator. + Can also be used as a decorator, see examples docstring and examples gallery. + + For a list of imgui elements see: https://pyimgui.readthedocs.io/en/latest/reference/imgui.core.html#imgui.core.begin_combo + + Note that the API docs for ``pyimgui`` do not match up exactly with ``imgui-bundle`` which we use in + fastplotlib. Unfortunately the API docs for imgui-bundle are nonexistent (as far as we know). See the + "imgui" section in the docs User Guide which includes tips on how to develop imgui UIs. Parameters ---------- gui: EdgeWindow - A GUI EdgeWindow instance + A GUI EdgeWindow instance, if not decorating location: str, "right" | "bottom" - window location + window location, ignored if not decorating title: str - window title + window title, ignored if not decorating size: int - width or height of the window depending on location + width or height of the window depending on location, ignored if not decorating window_flags: imgui.WindowFlags_, default imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_resize, - imgui.WindowFlags_ enum + imgui.WindowFlags_ enum, ignored if not decorating Examples -------- @@ -200,6 +206,33 @@ def gui(): figure.show(maintain_aspect=False) + Subclass EdgeWindow:: + + import numpy as np + import fastplotlib as fpl + from fastplotlib.ui import EdgeWindow + + figure = fpl.Figure() + figure[0, 0].add_line(np.sin(np.linspace(0, np.pi * 4, 0.1)), name="sine") + + class GUI(EdgeWindow): + def __init__(self, figure, location="right", size=200, title="My GUI", amplitude=1.0) + self._figure = figure + + self._amplitude = 1 + + def compute_data(self): + ampl = self._amplitude + new_data = ampl * np.sin(np.linspace(0, np.pi * 4, 0.1)) + self._figure[0, 0]["sine"].data[:, 1] = new_data + + def update(self): + # gui update function + changed, amplitude = imgui.slider_float("amplitude", v=self._amplitude, v_max=10, v_min=0.1) + if changed: + self._amplitude = amplitude + self.compute_data() + """ def decorator(_gui_func): @@ -212,7 +245,8 @@ def decorator(_gui_func): raise ValueError(f"GUI already exists in the desired location: {location}") if not isinstance(gui, EdgeWindow): - ew = EdgeWindow( + # being used as a decorator, create an EdgeWindow + edge_window = EdgeWindow( figure=self, size=size, location=location, @@ -220,20 +254,25 @@ def decorator(_gui_func): update_call=_gui_func, window_flags=window_flags ) - window_location = location + window_location = location # creating this reference is required else: - ew = _gui_func - window_location = location + edge_window = _gui_func # creating this reference is required + window_location = location # creating this reference is required - self.guis[window_location] = ew + # store the gui + self.guis[window_location] = edge_window + # redo the layout self._fpl_reset_layout() + # return function being decorated return _gui_func if not isinstance(gui, EdgeWindow): + # if decorating return decorator + # if not decorating decorator(gui) def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: From eae0fa07be17bf4d4bb85551e5f9dae20eaab9c7 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Sun, 1 Jun 2025 00:24:07 -0400 Subject: [PATCH 04/10] fix --- fastplotlib/ui/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fastplotlib/ui/utils.py b/fastplotlib/ui/utils.py index 620e2e8d..e4288b12 100644 --- a/fastplotlib/ui/utils.py +++ b/fastplotlib/ui/utils.py @@ -36,8 +36,7 @@ def __bool__(self): return self.value def __or__(self, other): - if other: - self._value = True + return self._value | other def __eq__(self, other): return self.value == other From 832f6fdeee295742aeaea0fb2ec36f367dcf8de4 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Sun, 1 Jun 2025 20:55:22 -0400 Subject: [PATCH 05/10] fig as argument to add_gui() decorated function, comments --- examples/guis/imgui_decorator.py | 14 +++--- fastplotlib/layouts/_imgui_figure.py | 50 +++++++++++++-------- fastplotlib/ui/_base.py | 1 + fastplotlib/{ui/utils.py => utils/imgui.py} | 1 + 4 files changed, 42 insertions(+), 24 deletions(-) rename fastplotlib/{ui/utils.py => utils/imgui.py} (99%) diff --git a/examples/guis/imgui_decorator.py b/examples/guis/imgui_decorator.py index ab1c8c26..f4efe995 100644 --- a/examples/guis/imgui_decorator.py +++ b/examples/guis/imgui_decorator.py @@ -15,16 +15,20 @@ import fastplotlib as fpl from imgui_bundle import imgui -figure = fpl.Figure() +figure = fpl.Figure(size=(700, 560)) figure[0, 0].add_line(np.random.rand(100)) -@figure.add_gui(location="right", title="yay", size=100) -def gui(): +@figure.add_gui(location="right", title="window", size=200) +def gui(fig_local): # figure is the only argument, so you can use it within the local scope of the GUI function if imgui.button("reset data"): - figure[0, 0].graphics[0].data[:, 1] = np.random.rand(100) + fig_local[0, 0].graphics[0].data[:, 1] = np.random.rand(100) figure.show(maintain_aspect=False) -fpl.loop.run() +# NOTE: fpl.loop.run() should not be used for interactive sessions +# See the "JupyterLab and IPython" section in the user guide +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index 9d8e9d83..5a96e729 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -1,3 +1,6 @@ +from __future__ import annotations +from collections.abc import Callable +from functools import partial from pathlib import Path from typing import Literal, Iterable @@ -151,12 +154,13 @@ def _draw_imgui(self) -> imgui.ImDrawData: return imgui.get_draw_data() def add_gui( - self, - gui: EdgeWindow = None, - location: Literal["right", "bottom"] = "right", - title="GUI Window", - size: int = 200, - window_flags: imgui.WindowFlags_ = imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_resize, + self, + gui: EdgeWindow = None, + location: Literal["right", "bottom"] = "right", + title="GUI Window", + size: int = 200, + window_flags: imgui.WindowFlags_ = imgui.WindowFlags_.no_collapse + | imgui.WindowFlags_.no_resize, ): """ Add a GUI to the Figure. GUIs can be added to the left or bottom edge. @@ -175,16 +179,16 @@ def add_gui( A GUI EdgeWindow instance, if not decorating location: str, "right" | "bottom" - window location, ignored if not decorating + window location, used if decorating title: str - window title, ignored if not decorating + window title, used if decorating size: int - width or height of the window depending on location, ignored if not decorating + width or height of the window depending on location, used if decorating window_flags: imgui.WindowFlags_, default imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_resize, - imgui.WindowFlags_ enum, ignored if not decorating + imgui.WindowFlags_ enum, used if decorating Examples -------- @@ -200,9 +204,9 @@ def add_gui( @figure.add_gui(location="right", title="yay", size=100) - def gui(): + def gui(fig): # figure is the only argument, so you can use it within the local scope of the GUI function if imgui.button("reset data"): - figure[0, 0].graphics[0].data[:, 1] = np.random.rand(100) + fig[0, 0].graphics[0].data[:, 1] = np.random.rand(100) figure.show(maintain_aspect=False) @@ -233,16 +237,22 @@ def update(self): self._amplitude = amplitude self.compute_data() + # create GUI instance and add to the figure + gui = GUI(figure) + figure.add_gui(gui) + """ - def decorator(_gui_func): + def decorator(_gui: EdgeWindow | Callable): if location not in GUI_EDGES: raise ValueError( f"GUI does not have a valid location, valid locations are: {GUI_EDGES}, you have passed: {location}" ) if self.guis[location] is not None: - raise ValueError(f"GUI already exists in the desired location: {location}") + raise ValueError( + f"GUI already exists in the desired location: {location}" + ) if not isinstance(gui, EdgeWindow): # being used as a decorator, create an EdgeWindow @@ -251,12 +261,14 @@ def decorator(_gui_func): size=size, location=location, title=title, - update_call=_gui_func, - window_flags=window_flags + update_call=partial( + _gui, self + ), # provide figure instance in scope of the gui function + window_flags=window_flags, ) window_location = location # creating this reference is required else: - edge_window = _gui_func # creating this reference is required + edge_window = _gui # creating this reference is required window_location = location # creating this reference is required # store the gui @@ -266,14 +278,14 @@ def decorator(_gui_func): self._fpl_reset_layout() # return function being decorated - return _gui_func + return _gui if not isinstance(gui, EdgeWindow): # if decorating return decorator # if not decorating - decorator(gui) + decorator(gui, self) def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: """ diff --git a/fastplotlib/ui/_base.py b/fastplotlib/ui/_base.py index 784eca4a..d37bda09 100644 --- a/fastplotlib/ui/_base.py +++ b/fastplotlib/ui/_base.py @@ -31,6 +31,7 @@ def update(self): class Window(BaseGUI): """Base class for imgui windows drawn within Figures""" + pass diff --git a/fastplotlib/ui/utils.py b/fastplotlib/utils/imgui.py similarity index 99% rename from fastplotlib/ui/utils.py rename to fastplotlib/utils/imgui.py index e4288b12..32c6f9f6 100644 --- a/fastplotlib/ui/utils.py +++ b/fastplotlib/utils/imgui.py @@ -20,6 +20,7 @@ class ChangeFlag: print(changed.value) """ + def __init__(self, value: bool): self._value = bool(value) From c7b92e161bf028e41a0d47af4f547dab3259f052 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Sun, 1 Jun 2025 21:10:29 -0400 Subject: [PATCH 06/10] update basic example to use changeflag --- examples/guis/imgui_basic.py | 36 +++++++++++++++------------- fastplotlib/layouts/_imgui_figure.py | 2 +- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/examples/guis/imgui_basic.py b/examples/guis/imgui_basic.py index eac39121..e803255e 100644 --- a/examples/guis/imgui_basic.py +++ b/examples/guis/imgui_basic.py @@ -15,6 +15,7 @@ # subclass from EdgeWindow to make a custom ImGUI Window to place inside the figure! from fastplotlib.ui import EdgeWindow +from fastplotlib.utils.imgui import ChangeFlag from imgui_bundle import imgui # make some initial data @@ -48,41 +49,44 @@ def __init__(self, figure, size, location, title): # sigma for gaussian noise self._sigma = 0.0 + # a flag that once True, always remains True + self._color_changed = ChangeFlag(False) + self._data_changed = ChangeFlag(False) + + def update(self): - # the UI will be used to modify the line - self._line = figure[0, 0]["sine-wave"] + # force flag values to reset + self._color_changed.force_value(False) + self._data_changed.force_value(False) # get the current line RGB values rgb_color = self._line.colors[:-1] # make color picker - changed_color, rgb = imgui.color_picker3("color", col=rgb_color) + self._color_changed.value, rgb = imgui.color_picker3("color", col=rgb_color) # get current line color alpha value alpha = self._line.colors[-1] # make float slider - changed_alpha, new_alpha = imgui.slider_float("alpha", v=alpha, v_min=0.0, v_max=1.0) + self._color_changed.value, new_alpha = imgui.slider_float("alpha", v=alpha, v_min=0.0, v_max=1.0) - # if RGB or alpha changed - if changed_color | changed_alpha: + # if RGB or alpha flag indicates a change + if self._color_changed: # set new color along with alpha self._line.colors = [*rgb, new_alpha] - # example of a slider, you can also use input_float - changed, amplitude = imgui.slider_float("amplitude", v=self._amplitude, v_max=10, v_min=0.1) - if changed: - # set y values - self._amplitude = amplitude - self._set_data() - # slider for thickness changed, thickness = imgui.slider_float("thickness", v=self._line.thickness, v_max=50.0, v_min=2.0) if changed: self._line.thickness = thickness + # example of a slider, you can also use input_float + self._data_changed.value, self._amplitude = imgui.slider_float("amplitude", v=self._amplitude, v_max=10, v_min=0.1) + # slider for gaussian noise - changed, sigma = imgui.slider_float("noise-sigma", v=self._sigma, v_max=1.0, v_min=0.0) - if changed: - self._sigma = sigma + self._data_changed.value, self._sigma = imgui.slider_float("noise-sigma", v=self._sigma, v_max=1.0, v_min=0.0) + + # data flag indicates change + if self._data_changed: self._set_data() # reset button diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index 5a96e729..4637a9c7 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -285,7 +285,7 @@ def decorator(_gui: EdgeWindow | Callable): return decorator # if not decorating - decorator(gui, self) + decorator(gui) def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: """ From 15baeb76c36621d4d56899257d3e3d9a78b3ee37 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Sun, 1 Jun 2025 21:43:03 -0400 Subject: [PATCH 07/10] add figure.remove_gui(), better handling in add_gui --- fastplotlib/layouts/_imgui_figure.py | 40 +++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index 4637a9c7..c787f4a6 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -244,6 +244,11 @@ def update(self): """ def decorator(_gui: EdgeWindow | Callable): + if not callable(_gui) and not isinstance(_gui, EdgeWindow): + raise TypeError( + "figure.add_gui() must be used as a decorator, or `gui` must be an `EdgeWindow` instance" + ) + if location not in GUI_EDGES: raise ValueError( f"GUI does not have a valid location, valid locations are: {GUI_EDGES}, you have passed: {location}" @@ -280,13 +285,42 @@ def decorator(_gui: EdgeWindow | Callable): # return function being decorated return _gui - if not isinstance(gui, EdgeWindow): - # if decorating + if gui is None: + # assume decorating return decorator - # if not decorating + # EdgeWindow instance passed decorator(gui) + def remove_gui(self, location: str) -> EdgeWindow: + """ + Remove an imgui UI + + Parameters + ---------- + location: str + "right" | "bottom" + + Returns + ------- + EdgeWindow | Callable + The removed EdgeWindow instance + + """ + + if location not in GUI_EDGES: + raise ValueError( + f"location not valid, valid locations are: {GUI_EDGES}, you have passed: {location}" + ) + + gui = self.guis.pop(location) + + # reset to None for this location + self.guis[location] = None + + # return EdgeWindow instance, it can be added again later + return gui + def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: """ Get rect for the portion of the canvas that the pygfx renderer draws to, From 3a93189abedddbbe0020b4cc7461fdaa67bbb194 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Sun, 1 Jun 2025 23:33:31 -0400 Subject: [PATCH 08/10] bugfix --- fastplotlib/layouts/_imgui_figure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index c787f4a6..e3809f20 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -274,7 +274,7 @@ def decorator(_gui: EdgeWindow | Callable): window_location = location # creating this reference is required else: edge_window = _gui # creating this reference is required - window_location = location # creating this reference is required + window_location = _gui.location # creating this reference is required # store the gui self.guis[window_location] = edge_window From 69053358b37129540589fb95384673415c8089be Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Sun, 29 Jun 2025 01:30:30 -0400 Subject: [PATCH 09/10] @Figure.append_gui decorator --- examples/guis/imgui_basic.py | 3 +- examples/guis/imgui_decorator.py | 53 +++++++++++++++++--- fastplotlib/layouts/_imgui_figure.py | 29 +++++++++++ fastplotlib/ui/__init__.py | 1 + fastplotlib/ui/_base.py | 6 ++- fastplotlib/{utils/imgui.py => ui/_utils.py} | 0 6 files changed, 83 insertions(+), 9 deletions(-) rename fastplotlib/{utils/imgui.py => ui/_utils.py} (100%) diff --git a/examples/guis/imgui_basic.py b/examples/guis/imgui_basic.py index e803255e..9511a1b0 100644 --- a/examples/guis/imgui_basic.py +++ b/examples/guis/imgui_basic.py @@ -14,8 +14,7 @@ import fastplotlib as fpl # subclass from EdgeWindow to make a custom ImGUI Window to place inside the figure! -from fastplotlib.ui import EdgeWindow -from fastplotlib.utils.imgui import ChangeFlag +from fastplotlib.ui import EdgeWindow, ChangeFlag from imgui_bundle import imgui # make some initial data diff --git a/examples/guis/imgui_decorator.py b/examples/guis/imgui_decorator.py index f4efe995..3118e137 100644 --- a/examples/guis/imgui_decorator.py +++ b/examples/guis/imgui_decorator.py @@ -12,20 +12,61 @@ import numpy as np +import imageio.v3 as iio import fastplotlib as fpl from imgui_bundle import imgui -figure = fpl.Figure(size=(700, 560)) -figure[0, 0].add_line(np.random.rand(100)) +img_data = iio.imread(f"imageio:camera.png").astype(np.float32) +xs = np.linspace(0, 2 * np.pi, 100) +ys = np.sin(xs) +line_data = np.column_stack([xs, ys]) -@figure.add_gui(location="right", title="window", size=200) +figure = fpl.Figure(shape=(2, 1), size=(700, 660)) +figure[0, 0].add_image(img_data, name="image") +figure[1, 0].add_line(line_data, name="sine") + +noise_sigma = 0.0 + +img_options = ["camera.png", "astronaut.png", "chelsea.png"] +img_index = 0 +@figure.add_gui(location="right", title="window", size=300) def gui(fig_local): # figure is the only argument, so you can use it within the local scope of the GUI function - if imgui.button("reset data"): - fig_local[0, 0].graphics[0].data[:, 1] = np.random.rand(100) + global img_data + global img_index + + global noise_sigma + + + clicked, img_index = imgui.combo("image", img_index, img_options) + if clicked: + fig_local[0, 0].delete_graphic(fig_local[0, 0]["image"]) + img_data = iio.imread(f"imageio:{img_options[img_index]}") + fig_local[0, 0].add_image(img_data, name="image") + + change, noise_sigma = imgui.slider_float("noise sigma", v=noise_sigma, v_min=0.0, v_max=100) + if change or clicked: + fig_local[0, 0]["image"].data = img_data + np.random.normal( + loc=0.0, + scale=noise_sigma, + size=img_data.size + ).reshape(img_data.shape) + + +# You can put all the GUI elements within on GUI function +# or split them across multiple functions and use the `append_gui` decorator +freq = 1.0 +@figure.append_gui(location="right") +def gui2(fig_local): + global freq + change, freq = imgui.slider_float("freq", v=freq, v_min=0.1, v_max=10) + if change: + ys = np.sin(xs * freq) + fig_local[1, 0]["sine"].data[:, 1] = ys -figure.show(maintain_aspect=False) +figure.show() +figure[1, 0].camera.maintain_aspect = False # NOTE: fpl.loop.run() should not be used for interactive sessions # See the "JupyterLab and IPython" section in the user guide diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index e3809f20..96adc197 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -292,6 +292,35 @@ def decorator(_gui: EdgeWindow | Callable): # EdgeWindow instance passed decorator(gui) + def append_gui(self, gui: Callable = None, location: str = None): + if location is None: + raise ValueError("Must provide GUI location to append to.") + + if location not in GUI_EDGES: + raise ValueError( + f"GUI does not have a valid location, valid locations are: {GUI_EDGES}, you have passed: {location}" + ) + + if self.guis[location] is None: + raise ValueError( + f"No GUI at given location to append to: {location}" + ) + + def decorator(_gui: Callable): + gui = self.guis[location] + if isinstance(gui._update_call, list): + gui._update_call.append(_gui) + else: + gui._update_call = [gui._update_call] + gui._update_call.append(partial(_gui, self)) + + return _gui + + if gui is None: + return decorator + + decorator(gui) + def remove_gui(self, location: str) -> EdgeWindow: """ Remove an imgui UI diff --git a/fastplotlib/ui/__init__.py b/fastplotlib/ui/__init__.py index a1e57a9c..0ff1d513 100644 --- a/fastplotlib/ui/__init__.py +++ b/fastplotlib/ui/__init__.py @@ -1,3 +1,4 @@ from ._base import BaseGUI, Window, EdgeWindow, Popup, GUI_EDGES from ._subplot_toolbar import SubplotToolbar from .right_click_menus import StandardRightClickMenu, ColormapPicker +from ._utils import ChangeFlag \ No newline at end of file diff --git a/fastplotlib/ui/_base.py b/fastplotlib/ui/_base.py index d37bda09..f6e43544 100644 --- a/fastplotlib/ui/_base.py +++ b/fastplotlib/ui/_base.py @@ -202,7 +202,11 @@ def draw_window(self): imgui.push_id(self._id_counter) # draw imgui UI elements into window - self._update_call() + if isinstance(self._update_call, list): + for update_call in self._update_call: + update_call() + else: + self._update_call() # pop ID imgui.pop_id() diff --git a/fastplotlib/utils/imgui.py b/fastplotlib/ui/_utils.py similarity index 100% rename from fastplotlib/utils/imgui.py rename to fastplotlib/ui/_utils.py From b3dda823de1ee9368e0bf71323d794462acbbad0 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Sun, 29 Jun 2025 02:04:43 -0400 Subject: [PATCH 10/10] docstring --- examples/guis/imgui_decorator.py | 1 - fastplotlib/layouts/_imgui_figure.py | 13 +++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/examples/guis/imgui_decorator.py b/examples/guis/imgui_decorator.py index 3118e137..8e51e9a0 100644 --- a/examples/guis/imgui_decorator.py +++ b/examples/guis/imgui_decorator.py @@ -37,7 +37,6 @@ def gui(fig_local): # figure is the only argument, so you can use it within the global noise_sigma - clicked, img_index = imgui.combo("image", img_index, img_options) if clicked: fig_local[0, 0].delete_graphic(fig_local[0, 0]["image"]) diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index 96adc197..406d1f0d 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -293,6 +293,19 @@ def decorator(_gui: EdgeWindow | Callable): decorator(gui) def append_gui(self, gui: Callable = None, location: str = None): + """ + Append to an existing GUI. Can also be used as a decorator. + + Parameters + ---------- + gui: Callable + function that creates imgui elements + + location: str, "right" or "bottom" + the existing GUI window to append more UI elements to + + """ + if location is None: raise ValueError("Must provide GUI location to append to.")








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/fastplotlib/fastplotlib/pull/849.patch

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy