diff --git a/docs/source/user_guide/guide.rst b/docs/source/user_guide/guide.rst index 1bdb3377c..073fa806c 100644 --- a/docs/source/user_guide/guide.rst +++ b/docs/source/user_guide/guide.rst @@ -549,10 +549,15 @@ between imgui and ipywidgets, Qt, and wx is the imgui UI can be rendered directl i.e. it will work in jupyter, Qt, glfw and wx windows! The programming model is different from Qt and ipywidgets, there are no callbacks, but it is easy to learn if you see a few examples. +.. image:: ../_static/guide_imgui.png + We specifically use `imgui-bundle `_ for the python bindings in fastplotlib. There is large community and many resources out there on building UIs using imgui. -For examples on integrating imgui with a fastplotlib Figure please see the examples gallery. +To install ``fastplotlib`` with ``imgui`` use the ``imgui`` extras option, i.e. ``pip install fastplotlib[imgui]``, or ``pip install imgui_bundle`` if you've already installed fastplotlib. + +Fastplotlib comes built-in with imgui UIs for subplot toolbars and a standard right-click menu with a number of options. +You can also make custom GUIs and embed them within the canvas, see the examples gallery for detailed examples. **Some tips:** @@ -662,21 +667,6 @@ There are several spaces to consider when using ``fastplotlib``: For more information on the various spaces used by rendering engines please see this `article `_ -Imgui ------ - -Fastplotlib uses `imgui_bundle `_ to provide within-canvas UI elemenents if you -installed ``fastplotlib`` using the ``imgui`` toggle, i.e. ``fastplotlib[imgui]``, or installed ``imgui_bundle`` afterwards. - -Fastplotlib comes built-in with imgui UIs for subplot toolbars and a standard right-click menu with a number of options. -You can also make custom GUIs and embed them within the canvas, see the examples gallery for detailed examples. - -.. note:: - Imgui is optional, you can use other GUI frameworks such at Qt or ipywidgets with fastplotlib. You can also of course - use imgui and Qt or ipywidgets. - -.. image:: ../_static/guide_imgui.png - Using ``fastplotlib`` in an interactive shell --------------------------------------------- diff --git a/examples/guis/sine_cosine_funcs.py b/examples/guis/sine_cosine_funcs.py new file mode 100644 index 000000000..c91a3b2e8 --- /dev/null +++ b/examples/guis/sine_cosine_funcs.py @@ -0,0 +1,186 @@ +""" +Sine and Cosine functions +========================= + +Identical to the Unit Circle example but you can change the angular frequencies using a UI + +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import glfw +import numpy as np +import fastplotlib as fpl +from fastplotlib.ui import EdgeWindow +from imgui_bundle import imgui + + +# initial frequency coefficients for sine and cosine functions +P = 1 +Q = 1 + + +# helper function to make a circle +def make_circle(center, radius: float, p, q, n_points: int) -> np.ndarray: + theta = np.linspace(0, 2 * np.pi, n_points) + xs = radius * np.cos(theta * p) + ys = radius * np.sin(theta * q) + + return np.column_stack([xs, ys]) + center + + +# we can define this layout using "extents", i.e. min and max ranges on the canvas +# (x_min, x_max, y_min, y_max) +# extents can be defined as fractions as shown here +extents = [ + (0, 0.5, 0, 1), # circle subplot + (0.5, 1, 0, 0.5), # sine subplot + (0.5, 1, 0.5, 1), # cosine subplot +] + +# create a figure with 3 subplots +figure = fpl.Figure( + extents=extents, + names=["circle", "sin", "cos"], + size=(700, 560) +) + +# set more descriptive figure titles +figure["circle"].title = "sin(x*p) over cos(x*q)" +figure["sin"].title = "sin(x * p)" +figure["cos"].title = "cos(x * q)" + +# set the axes to intersect at (0, 0, 0) to better illustrate the unit circle +for subplot in figure: + subplot.axes.intersection = (0, 0, 0) + subplot.toolbar = False # reduce clutter + +figure["sin"].camera.maintain_aspect = False +figure["cos"].camera.maintain_aspect = False + +# create sine and cosine data +xs = np.linspace(0, 2 * np.pi, 360) +sine = np.sin(xs * P) +cosine = np.cos(xs * Q) + +# circle data +circle_data = make_circle(center=(0, 0), p=P, q=Q, radius=1, n_points=360) + +# make the circle line graphic, set the cmap transform using the sine function +circle_graphic = figure["circle"].add_line( + circle_data, thickness=4, cmap="bwr", cmap_transform=sine +) + +# line to show the circle radius +# use it to indicate the current position of the sine and cosine selctors (below) +radius_data = np.array([[0, 0, 0], [*circle_data[0], 0]]) +circle_radius_graphic = figure["circle"].add_line( + radius_data, thickness=6, colors="magenta" +) + +# sine line graphic, cmap transform set from the sine function +sine_graphic = figure["sin"].add_line( + sine, thickness=10, cmap="bwr", cmap_transform=sine +) + +# cosine line graphic, cmap transform set from the sine function +# illustrates the sine function values on the cosine graphic +cosine_graphic = figure["cos"].add_line( + cosine, thickness=10, cmap="bwr", cmap_transform=sine +) + +# add linear selectors to the sine and cosine line graphics +sine_selector = sine_graphic.add_linear_selector() +cosine_selector = cosine_graphic.add_linear_selector() + + +def set_circle_cmap(ev): + # sets the cmap transforms + + cmap_transform = ev.graphic.data[:, 1] # y-val data of the sine or cosine graphic + for g in [sine_graphic, cosine_graphic]: + g.cmap.transform = cmap_transform + + # set circle cmap transform + circle_graphic.cmap.transform = cmap_transform + +# when the sine or cosine graphic is clicked, the cmap_transform +# of the sine, cosine and circle line graphics are all set from +# the y-values of the clicked line +sine_graphic.add_event_handler(set_circle_cmap, "click") +cosine_graphic.add_event_handler(set_circle_cmap, "click") + + +def set_x_val(ev): + # used to sync the two selectors + value = ev.info["value"] + index = ev.get_selected_index() + + sine_selector.selection = value + cosine_selector.selection = value + + circle_radius_graphic.data[1, :-1] = circle_data[index] + +# add same event handler to both graphics +sine_selector.add_event_handler(set_x_val, "selection") +cosine_selector.add_event_handler(set_x_val, "selection") + +# initial selection value +sine_selector.selection = 50 + + +class GUIWindow(EdgeWindow): + def __init__(self, figure, size, location, title): + super().__init__(figure=figure, size=size, location=location, title=title) + + self._p = 1 + self._q = 1 + + def _set_data(self): + global sine_graphic, cosine_graphic, circle_graphic, circle_radius_graphic, circle_data + + # make new data + sine = np.sin(xs * self._p) + cosine = np.cos(xs * self._q) + circle_data = make_circle(center=(0, 0), p=self._p, q=self._q, radius=1, n_points=360) + + + # set the graphics + sine_graphic.data[:, 1] = sine + cosine_graphic.data[:, 1] = cosine + circle_graphic.data[:, :2] = circle_data + circle_radius_graphic.data[1, :-1] = circle_data[sine_selector.get_selected_index()] + + def update(self): + flag_set_data = False + + changed, self._p = imgui.input_int("P", v=self._p, step_fast=2) + if changed: + flag_set_data = True + + changed, self._q = imgui.input_int("Q", v=self._q, step_fast=2) + if changed: + flag_set_data = True + + if flag_set_data: + self._set_data() + + +gui = GUIWindow( + figure=figure, + size=100, + location="right", + title="Freq. coeffs" +) + +figure.add_gui(gui) + +figure.show() + + +# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively +# please see our docs for using fastplotlib interactively in ipython and jupyter +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index 40145fe50..b0267dc75 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -150,7 +150,7 @@ def _draw_imgui(self) -> imgui.ImDrawData: def add_gui(self, gui: EdgeWindow): """ - Add a GUI to the Figure. GUIs can be added to the top, bottom, left or right edge. + Add a GUI to the Figure. GUIs can be added to the left or bottom edge. Parameters ---------- @@ -191,25 +191,15 @@ def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: width, height = self.canvas.get_logical_size() - for edge in ["left", "right"]: + for edge in ["right"]: if self.guis[edge]: width -= self._guis[edge].size - for edge in ["top", "bottom"]: + for edge in ["bottom"]: if self.guis[edge]: height -= self._guis[edge].size - if self.guis["left"]: - xpos = self.guis["left"].size - else: - xpos = 0 - - if self.guis["top"]: - ypos = self.guis["top"].size - else: - ypos = 0 - - return xpos, ypos, max(1, width), max(1, height) + return 0, 0, max(1, width), max(1, height) def register_popup(self, popup: Popup.__class__): """ diff --git a/fastplotlib/ui/_base.py b/fastplotlib/ui/_base.py index 6c134d415..e31dd8d4a 100644 --- a/fastplotlib/ui/_base.py +++ b/fastplotlib/ui/_base.py @@ -6,7 +6,7 @@ from ..layouts._figure import Figure -GUI_EDGES = ["top", "right", "bottom", "left"] +GUI_EDGES = ["right", "bottom"] class BaseGUI: @@ -40,7 +40,7 @@ def __init__( self, figure: Figure, size: int, - location: Literal["top", "bottom", "left", "right"], + location: Literal["bottom", "right"], title: str, window_flags: int = imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_resize, @@ -48,7 +48,7 @@ def __init__( **kwargs, ): """ - A base class for imgui windows displayed at one of the four edges of a Figure + A base class for imgui windows displayed at the bottom or top edge of a Figure Parameters ---------- @@ -58,7 +58,7 @@ def __init__( size: int width or height of the window, depending on its location - location: str, "top" | "bottom" | "left" | "right" + location: str, "bottom" | "right" location of the window title: str @@ -168,10 +168,6 @@ def get_rect(self) -> tuple[int, int, int, int]: width_canvas, height_canvas = self._figure.canvas.get_logical_size() match self._location: - case "top": - x_pos, y_pos = (0, 0) - width, height = (width_canvas, self.size) - case "bottom": x_pos = 0 y_pos = height_canvas - self.size @@ -179,22 +175,8 @@ def get_rect(self) -> tuple[int, int, int, int]: case "right": x_pos, y_pos = (width_canvas - self.size, 0) - - if self._figure.guis["top"]: - # if there is a GUI in the top edge, make this one below - y_pos += self._figure.guis["top"].size - width, height = (self.size, height_canvas) - if self._figure.guis["bottom"] is not None: - height -= self._figure.guis["bottom"].size - case "left": - x_pos, y_pos = (0, 0) - if self._figure.guis["top"]: - # if there is a GUI in the top edge, make this one below - y_pos += self._figure.guis["top"].size - - width, height = (self.size, height_canvas) if self._figure.guis["bottom"] is not None: height -= self._figure.guis["bottom"].size @@ -203,8 +185,11 @@ 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 diff --git a/fastplotlib/ui/right_click_menus/_standard_menu.py b/fastplotlib/ui/right_click_menus/_standard_menu.py index 53a553f34..4bb59c51d 100644 --- a/fastplotlib/ui/right_click_menus/_standard_menu.py +++ b/fastplotlib/ui/right_click_menus/_standard_menu.py @@ -31,7 +31,7 @@ def __init__(self, figure, fa_icons): # whether the right click menu is currently open or not self.is_open: bool = False - def get_subplot(self) -> PlotArea | bool: + def get_subplot(self) -> PlotArea | bool | None: """get the subplot that a click occurred in""" if self._last_right_click_pos is None: return False @@ -40,6 +40,9 @@ def get_subplot(self) -> PlotArea | bool: if subplot.viewport.is_inside(*self._last_right_click_pos): return subplot + # not inside a subplot + return False + def cleanup(self): """called when the popup disappears""" self.is_open = False pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy