From 5ad0a064881b7d6754c7826277846286980b7d65 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Tue, 20 May 2025 03:45:57 -0400 Subject: [PATCH 01/15] add overlay render pass --- fastplotlib/layouts/_engine.py | 2 +- fastplotlib/layouts/_figure.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/fastplotlib/layouts/_engine.py b/fastplotlib/layouts/_engine.py index 877a7fbab..bf73d5f0d 100644 --- a/fastplotlib/layouts/_engine.py +++ b/fastplotlib/layouts/_engine.py @@ -7,7 +7,7 @@ from ._rect import RectManager -class UnderlayCamera(pygfx.Camera): +class ScreenSpaceCamera(pygfx.Camera): """ Same as pygfx.ScreenCoordsCamera but y-axis is inverted. diff --git a/fastplotlib/layouts/_figure.py b/fastplotlib/layouts/_figure.py index a1bae965e..c7185b37e 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -19,7 +19,7 @@ ) from ._utils import controller_types as valid_controller_types from ._subplot import Subplot -from ._engine import GridLayout, WindowLayout, UnderlayCamera +from ._engine import GridLayout, WindowLayout, ScreenSpaceCamera from .. import ImageGraphic @@ -409,10 +409,14 @@ def __init__( canvas_rect=self.get_pygfx_render_area(), ) - self._underlay_camera = UnderlayCamera() - + # underlay render pass + self._underlay_camera = ScreenSpaceCamera() self._underlay_scene = pygfx.Scene() + # overlay render pass + self._overlay_camera = ScreenSpaceCamera() + self._overlay_scene = pygfx.Scene() + for subplot in self._subplots.ravel(): self._underlay_scene.add(subplot.frame._world_object) @@ -492,6 +496,9 @@ def _render(self, draw=True): for subplot in self: subplot._render() + # overlay render pass + self.renderer.render(self._overlay_scene, self._overlay_camera, flush=False) + self.renderer.flush() # call post-render animate functions From 4689f5b127b5e0d878fcd102b6fb6afca6d27df6 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Tue, 20 May 2025 03:46:32 -0400 Subject: [PATCH 02/15] Graphic accessible in graphics --- fastplotlib/graphics/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fastplotlib/graphics/__init__.py b/fastplotlib/graphics/__init__.py index 03f361502..b458a8c48 100644 --- a/fastplotlib/graphics/__init__.py +++ b/fastplotlib/graphics/__init__.py @@ -7,6 +7,7 @@ __all__ = [ + "Graphic", "LineGraphic", "ScatterGraphic", "ImageGraphic", From e009db01b532545f3dbab2989ed89995b7160d1b Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Tue, 20 May 2025 03:47:17 -0400 Subject: [PATCH 03/15] tooltip prototype --- fastplotlib/tools/__init__.py | 1 + fastplotlib/tools/_tooltip.py | 177 ++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 fastplotlib/tools/_tooltip.py diff --git a/fastplotlib/tools/__init__.py b/fastplotlib/tools/__init__.py index 80396c98d..84e0238a7 100644 --- a/fastplotlib/tools/__init__.py +++ b/fastplotlib/tools/__init__.py @@ -1 +1,2 @@ from ._histogram_lut import HistogramLUTTool +from ._tooltip import Tooltip diff --git a/fastplotlib/tools/_tooltip.py b/fastplotlib/tools/_tooltip.py new file mode 100644 index 000000000..ab6cf435a --- /dev/null +++ b/fastplotlib/tools/_tooltip.py @@ -0,0 +1,177 @@ +from functools import partial + +import numpy as np +import pygfx +from .. import ScatterGraphic + +from ..graphics import LineGraphic, ImageGraphic, TextGraphic + + +class MeshMasks: + """Used set the x0, x1, y0, y1 positions of the plane mesh""" + + x0 = np.array( + [ + [False, False, False], + [True, False, False], + [False, False, False], + [True, False, False], + ] + ) + + x1 = np.array( + [ + [True, False, False], + [False, False, False], + [True, False, False], + [False, False, False], + ] + ) + + y0 = np.array( + [ + [False, True, False], + [False, True, False], + [False, False, False], + [False, False, False], + ] + ) + + y1 = np.array( + [ + [False, False, False], + [False, False, False], + [False, True, False], + [False, True, False], + ] + ) + + +masks = MeshMasks + + +class Tooltip: + def __init__(self): + self._text = pygfx.Text( + text="", + font_size=12, + screen_space=False, + anchor="bottom-left", + material=pygfx.TextMaterial( + color="w", + outline_color="w", + outline_thickness=0.0, + pick_write=False, + ), + ) + + geometry = pygfx.plane_geometry(1, 1) + material = pygfx.MeshBasicMaterial(color=(0.1, 0.1, 0.3, 1.)) + self._plane = pygfx.Mesh(geometry, material) + # else text not visible + self._plane.world.z = 0.5 + + # line to outline the mesh + self._line = pygfx.Line( + geometry=pygfx.Geometry( + positions=np.array([ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ], dtype=np.float32) + ), + material=pygfx.LineThinMaterial( + thickness=1.0, color=(0.8, 0.8, 1.0, 1.0) + ) + ) + + + self._world_object = pygfx.Group() + self._world_object.add(self._plane, self._text, self._line) + + # padded to bbox so the background box behind the text extends a bit further + # making the text easier to read + self._padding = np.array( + [[5, 5, 0], + [-5, -5, 0]], + dtype=np.float32 + + ) + + def _set_position(self, pos: tuple[float, float]): + """ + Set the position of the tooltip + + Parameters + ---------- + pos: [float, float] + position in screen space + + """ + # need to flip due to inverted y + x, y = pos[0], pos[1] + + self._text.world.position = (x, -y, 0) + + bbox = self._text.get_world_bounding_box() - self._padding + [[x0, y0, _], [x1, y1, _]] = bbox + + self._plane.geometry.positions.data[masks.x0] = x0 + self._plane.geometry.positions.data[masks.x1] = x1 + self._plane.geometry.positions.data[masks.y0] = y0 + self._plane.geometry.positions.data[masks.y1] = y1 + + self._plane.geometry.positions.update_range() + + # line points + pts = [ + [x0, y0], + [x0, y1], + [x1, y1], + [x1, y0], + [x0, y0] + ] + + self._line.geometry.positions.data[:, :2] = pts + self._line.geometry.positions.update_range() + + def _event_handler(self, display_property, ev: pygfx.PointerEvent): + if isinstance(ev.graphic, ImageGraphic): + col, row = ev.pick_info["index"] + info = ev.graphic.data[row, col] + self._text.set_text(str(info)) + + elif isinstance(ev.graphic, (LineGraphic, ScatterGraphic)): + index = ev.pick_info["vertex_index"] + info = ev.graphic.data[index] + self._text.set_text(str(info)) + + self._set_position((ev.x, ev.y)) + + def _clear(self, ev): + self._text.set_text("") + + self._text.world.position = (-1, -1, 0) + + self._plane.geometry.positions.data[masks.x0] = -1 + self._plane.geometry.positions.data[masks.x1] = -1 + self._plane.geometry.positions.data[masks.y0] = -1 + self._plane.geometry.positions.data[masks.y1] = -1 + + self._plane.geometry.positions.update_range() + + # line points + self._line.geometry.positions.data[:, :2] = -1 + self._line.geometry.positions.update_range() + + def register_graphic( + self, + graphic, + event_type: str = "pointer_move", + display_property: str = "data", + formatter: callable = None, + ): + graphic.add_event_handler(partial(self._event_handler, display_property), event_type) + graphic.add_event_handler(self._clear, "pointer_leave") From 86e238a8143435ebacf0e4f6445b3208bd2203f7 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Tue, 20 May 2025 16:33:12 -0400 Subject: [PATCH 04/15] basic tooltips work --- examples/misc/tooltips.py | 50 ++++++++++ fastplotlib/layouts/_figure.py | 14 ++- fastplotlib/tools/_tooltip.py | 172 +++++++++++++++++++++++++++------ 3 files changed, 204 insertions(+), 32 deletions(-) create mode 100644 examples/misc/tooltips.py diff --git a/examples/misc/tooltips.py b/examples/misc/tooltips.py new file mode 100644 index 000000000..aad3a7c4f --- /dev/null +++ b/examples/misc/tooltips.py @@ -0,0 +1,50 @@ +""" +Tooltips +======== + +Register graphics to the Figure's tooltip manager +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import imageio.v3 as iio +import fastplotlib as fpl + + +# get some data +scatter_data = np.random.rand(1_000, 3) + +xs = np.linspace(0, 2 * np.pi, 100) +ys = np.sin(xs) + +gray = iio.imread("imageio:camera.png") +rgb = iio.imread("imageio:astronaut.png") + +# create a figure +figure = fpl.Figure( + cameras=["3d", "2d", "2d", "2d"], + controller_types=["orbit", "panzoom", "panzoom", "panzoom"], + size=(700, 560), + shape=(2, 2) +) + +# create graphics +scatter = figure[0, 0].add_scatter(scatter_data, sizes=3, colors="r") +line = figure[0, 1].add_line(np.column_stack([xs, ys])) +image = figure[1, 0].add_image(gray) +image_rgb = figure[1, 1].add_image(rgb) + +# register graphics to the tooltip +for g in [scatter, line, image, image_rgb]: + figure.tooltip.register(g) + +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/_figure.py b/fastplotlib/layouts/_figure.py index c7185b37e..ce24e40a9 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -21,6 +21,7 @@ from ._subplot import Subplot from ._engine import GridLayout, WindowLayout, ScreenSpaceCamera from .. import ImageGraphic +from ..tools import Tooltip class Figure: @@ -413,12 +414,16 @@ def __init__( self._underlay_camera = ScreenSpaceCamera() self._underlay_scene = pygfx.Scene() + for subplot in self._subplots.ravel(): + self._underlay_scene.add(subplot.frame._world_object) + # overlay render pass self._overlay_camera = ScreenSpaceCamera() self._overlay_scene = pygfx.Scene() - for subplot in self._subplots.ravel(): - self._underlay_scene.add(subplot.frame._world_object) + # tooltip in overlay render pass + self._tooltip = Tooltip() + self._overlay_scene.add(self._tooltip.world_object) self._animate_funcs_pre: list[callable] = list() self._animate_funcs_post: list[callable] = list() @@ -487,6 +492,11 @@ def names(self) -> np.ndarray[str]: names.flags.writeable = False return names + @property + def tooltip(self) -> Tooltip: + """manage tooltips""" + return self._tooltip + def _render(self, draw=True): # draw the underlay planes self.renderer.render(self._underlay_scene, self._underlay_camera, flush=False) diff --git a/fastplotlib/tools/_tooltip.py b/fastplotlib/tools/_tooltip.py index ab6cf435a..e2386fa80 100644 --- a/fastplotlib/tools/_tooltip.py +++ b/fastplotlib/tools/_tooltip.py @@ -2,9 +2,9 @@ import numpy as np import pygfx -from .. import ScatterGraphic -from ..graphics import LineGraphic, ImageGraphic, TextGraphic +from ..graphics import LineGraphic, ImageGraphic, ScatterGraphic +from ..graphics.features import GraphicFeatureEvent class MeshMasks: @@ -52,6 +52,7 @@ class MeshMasks: class Tooltip: def __init__(self): + # text object self._text = pygfx.Text( text="", font_size=12, @@ -65,13 +66,14 @@ def __init__(self): ), ) + # plane for the background of the text object geometry = pygfx.plane_geometry(1, 1) - material = pygfx.MeshBasicMaterial(color=(0.1, 0.1, 0.3, 1.)) + material = pygfx.MeshBasicMaterial(color=(0.1, 0.1, 0.3, 0.9)) self._plane = pygfx.Mesh(geometry, material) # else text not visible self._plane.world.z = 0.5 - # line to outline the mesh + # line to outline the plane mesh self._line = pygfx.Line( geometry=pygfx.Geometry( positions=np.array([ @@ -87,7 +89,6 @@ def __init__(self): ) ) - self._world_object = pygfx.Group() self._world_object.add(self._plane, self._text, self._line) @@ -97,9 +98,65 @@ def __init__(self): [[5, 5, 0], [-5, -5, 0]], dtype=np.float32 - ) + self._registered_graphics = dict() + + @property + def world_object(self) -> pygfx.Group: + return self._world_object + + @property + def font_size(self): + """Get or set font size""" + return self._text.font_size + + @font_size.setter + def font_size(self, size: float): + self._text.font_size = size + + @property + def text_color(self): + """Get or set text color using a str or RGB(A) array""" + return self._text.material.color + + @text_color.setter + def text_color(self, color: str | tuple | list | np.ndarray): + self._text.material.color = color + + @property + def background_color(self): + """Get or set background color using a str or RGB(A) array""" + return self._plane.material.color + + @background_color.setter + def background_color(self, color: str | tuple | list | np.ndarray): + self._plane.material.color = color + + @property + def outline_color(self): + """Get or set outline color using a str or RGB(A) array""" + return self._line.material.color + + @outline_color.setter + def outline_color(self, color: str | tuple | list | np.ndarray): + """Get or set outline color using a str or RGB(A) array""" + self._line.material.color = color + + @property + def padding(self) -> np.ndarray: + """ + Get or set the background padding in number of pixels. + The padding defines the number of pixels around the tooltip text that the background is extended by. + """ + + return self.padding[0, :2].copy() + + @padding.setter + def padding(self, padding_xy: tuple[float, float]): + self._padding[0, :2] = padding_xy + self._padding[1, :2] = -np.asarray(padding_xy) + def _set_position(self, pos: tuple[float, float]): """ Set the position of the tooltip @@ -113,6 +170,10 @@ def _set_position(self, pos: tuple[float, float]): # need to flip due to inverted y x, y = pos[0], pos[1] + # put the tooltip slightly to the top right of the cursor positoin + x += 8 + y -= 8 + self._text.world.position = (x, -y, 0) bbox = self._text.get_world_bounding_box() - self._padding @@ -137,41 +198,92 @@ def _set_position(self, pos: tuple[float, float]): self._line.geometry.positions.data[:, :2] = pts self._line.geometry.positions.update_range() - def _event_handler(self, display_property, ev: pygfx.PointerEvent): - if isinstance(ev.graphic, ImageGraphic): + def _event_handler(self, custom_tooltip: callable, ev: pygfx.PointerEvent): + """handles the tooltip appear event, determines the text to be set in the tooltip""" + if custom_tooltip is not None: + info = custom_tooltip(ev) + + elif isinstance(ev.graphic, ImageGraphic): col, row = ev.pick_info["index"] - info = ev.graphic.data[row, col] - self._text.set_text(str(info)) + if ev.graphic.data.value.ndim == 2: + info = str(ev.graphic.data[row, col]) + else: + info = "\n".join(f"{channel}: {val}" for channel, val in zip("rgba", ev.graphic.data[row, col])) elif isinstance(ev.graphic, (LineGraphic, ScatterGraphic)): index = ev.pick_info["vertex_index"] - info = ev.graphic.data[index] - self._text.set_text(str(info)) + info = "\n".join(f"{dim}: {val}" for dim, val in zip("xyz", ev.graphic.data[index])) + else: + raise TypeError("Unsupported graphic") + + # make the tooltip object visible + self.world_object.visible = True + # set the text and top left position of the tooltip + self._text.set_text(info) self._set_position((ev.x, ev.y)) def _clear(self, ev): self._text.set_text("") + self.world_object.visible = False + + def register( + self, + graphic, + appear_event: str = "pointer_move", + disappear_event: str = "pointer_leave", + custom_tooltip: callable = None, + ): + """ + Register a Graphic to display tooltips - self._text.world.position = (-1, -1, 0) + Parameters + ---------- + graphic: Graphic + Graphic to register - self._plane.geometry.positions.data[masks.x0] = -1 - self._plane.geometry.positions.data[masks.x1] = -1 - self._plane.geometry.positions.data[masks.y0] = -1 - self._plane.geometry.positions.data[masks.y1] = -1 + appear_event: str, default "pointer_move" + the pointer that triggers the tooltip to appear. Usually one of "pointer_move" | "click" | "double_click" - self._plane.geometry.positions.update_range() + disappear_event: str, default "pointer_leave" + the event that triggers the tooltip to disappear, does not have to be a pointer event. - # line points - self._line.geometry.positions.data[:, :2] = -1 - self._line.geometry.positions.update_range() + custom_tooltip: callable, default None + a custom function that takes the pointer event defined as the `appear_event` and returns the text + to display in the tooltip - def register_graphic( - self, - graphic, - event_type: str = "pointer_move", - display_property: str = "data", - formatter: callable = None, - ): - graphic.add_event_handler(partial(self._event_handler, display_property), event_type) - graphic.add_event_handler(self._clear, "pointer_leave") + """ + + pfunc = partial(self._event_handler, custom_tooltip) + graphic.add_event_handler(pfunc, appear_event) + graphic.add_event_handler(self._clear, disappear_event) + + self._registered_graphics[graphic] = (pfunc, appear_event, disappear_event) + + # automatically unregister when graphic is deleted + graphic.add_event_handler(self.unregister, "deleted") + + def unregister(self, graphic): + """ + Unregister a Graphic to no longer display tooltips for this graphic. + + Parameters + ---------- + graphic: Graphic + Graphic to unregister + + """ + + if isinstance(graphic, GraphicFeatureEvent): + # this happens when the deleted event is triggered + graphic = graphic.graphic + + if graphic not in self._registered_graphics: + raise KeyError(f"Given graphic: {graphic} is not registered") + + # get pfunc and event names + pfunc, appear_event, disappear_event = self._registered_graphics.pop(graphic) + + # remove handlers from graphic + graphic.remove_event_handler(pfunc, appear_event) + graphic.remove_event_handler(self._clear, disappear_event) From 2784ab0570a24a30a3ebeff1d4dc8edc301c889a Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Tue, 20 May 2025 16:44:23 -0400 Subject: [PATCH 05/15] custom tooltip example --- examples/misc/tooltips_custom.py | 52 ++++++++++++++++++++++++++++++++ fastplotlib/tools/_tooltip.py | 6 ++-- 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 examples/misc/tooltips_custom.py diff --git a/examples/misc/tooltips_custom.py b/examples/misc/tooltips_custom.py new file mode 100644 index 000000000..99f2ae2d9 --- /dev/null +++ b/examples/misc/tooltips_custom.py @@ -0,0 +1,52 @@ +""" +Tooltips Customization +====================== + +Customize the information displayed in a tooltip +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + + +import fastplotlib as fpl +from sklearn.cluster import AgglomerativeClustering +from sklearn import datasets + + +figure = fpl.Figure(size=(700, 560)) + +dataset = datasets.load_iris() +data = dataset["data"] + +agg = AgglomerativeClustering(n_clusters=3) +agg.fit_predict(data) + +scatter_graphic = figure[0, 0].add_scatter( + data=data[:, :-1], # use only xy data + sizes=15, + cmap="Set1", + cmap_transform=agg.labels_ # use the labels as a transform to map colors from the colormap +) + + +def tooltip_info(ev) -> str: + # get index of the scatter point that is being hovered + index = ev.pick_info["vertex_index"] + + # get the species name + target = dataset["target"][index] + info = dataset["target_names"][target] + + # return this string to display it in the tooltip + return info + + +figure.tooltip.register(scatter_graphic, custom_info=tooltip_info) + +figure.show() + + +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/fastplotlib/tools/_tooltip.py b/fastplotlib/tools/_tooltip.py index e2386fa80..4c213d3b1 100644 --- a/fastplotlib/tools/_tooltip.py +++ b/fastplotlib/tools/_tooltip.py @@ -232,7 +232,7 @@ def register( graphic, appear_event: str = "pointer_move", disappear_event: str = "pointer_leave", - custom_tooltip: callable = None, + custom_info: callable = None, ): """ Register a Graphic to display tooltips @@ -248,13 +248,13 @@ def register( disappear_event: str, default "pointer_leave" the event that triggers the tooltip to disappear, does not have to be a pointer event. - custom_tooltip: callable, default None + custom_info: callable, default None a custom function that takes the pointer event defined as the `appear_event` and returns the text to display in the tooltip """ - pfunc = partial(self._event_handler, custom_tooltip) + pfunc = partial(self._event_handler, custom_info) graphic.add_event_handler(pfunc, appear_event) graphic.add_event_handler(self._clear, disappear_event) From c22630da527b95080ca7f5dc198c6d83623b05e5 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Wed, 21 May 2025 00:54:55 -0400 Subject: [PATCH 06/15] auto tooltips --- examples/line_collection/line_stack.py | 25 +++++++++++++++++++- examples/misc/tooltips.py | 8 +++---- examples/misc/tooltips_custom.py | 2 +- fastplotlib/layouts/_figure.py | 32 ++++++++++++++++++++++---- fastplotlib/layouts/_imgui_figure.py | 2 ++ fastplotlib/layouts/_plot_area.py | 5 +++- fastplotlib/tools/_tooltip.py | 10 +++++++- 7 files changed, 71 insertions(+), 13 deletions(-) diff --git a/examples/line_collection/line_stack.py b/examples/line_collection/line_stack.py index 95b681b76..4867d72bb 100644 --- a/examples/line_collection/line_stack.py +++ b/examples/line_collection/line_stack.py @@ -19,7 +19,10 @@ data = np.column_stack([xs, ys]) multi_data = np.stack([data] * 10) -figure = fpl.Figure(size=(700, 560)) +figure = fpl.Figure( + size=(700, 560), + show_tooltips=True +) line_stack = figure[0, 0].add_line_stack( multi_data, # shape: (10, 100, 2), i.e. [n_lines, n_points, xy] @@ -28,6 +31,26 @@ separation=1, # spacing between lines along the separation axis, default separation along "y" axis ) + +def tooltip_info(ev): + """a custom function to display the index of the graphic within the collection""" + index = ev.pick_info["vertex_index"] # index of the line datapoint being hovered + + # get index of the hovered line within the line stack + line_index = np.where(line_stack.graphics == ev.graphic)[0].item() + info = f"line index: {line_index}\n" + + # append data value info + info += "\n".join(f"{dim}: {val}" for dim, val in zip("xyz", ev.graphic.data[index])) + + # return str to display in tooltip + return info + +# register the line stack with the custom tooltip function +figure.tooltip_manager.register( + line_stack, custom_info=tooltip_info +) + figure.show(maintain_aspect=False) diff --git a/examples/misc/tooltips.py b/examples/misc/tooltips.py index aad3a7c4f..38d3ea537 100644 --- a/examples/misc/tooltips.py +++ b/examples/misc/tooltips.py @@ -2,7 +2,7 @@ Tooltips ======== -Register graphics to the Figure's tooltip manager +Show tooltips on all graphics """ # test_example = false @@ -27,7 +27,8 @@ cameras=["3d", "2d", "2d", "2d"], controller_types=["orbit", "panzoom", "panzoom", "panzoom"], size=(700, 560), - shape=(2, 2) + shape=(2, 2), + show_tooltips=True, ) # create graphics @@ -36,9 +37,6 @@ image = figure[1, 0].add_image(gray) image_rgb = figure[1, 1].add_image(rgb) -# register graphics to the tooltip -for g in [scatter, line, image, image_rgb]: - figure.tooltip.register(g) figure.show() diff --git a/examples/misc/tooltips_custom.py b/examples/misc/tooltips_custom.py index 99f2ae2d9..97d688305 100644 --- a/examples/misc/tooltips_custom.py +++ b/examples/misc/tooltips_custom.py @@ -42,7 +42,7 @@ def tooltip_info(ev) -> str: return info -figure.tooltip.register(scatter_graphic, custom_info=tooltip_info) +figure.tooltip_manager.register(scatter_graphic, custom_info=tooltip_info) figure.show() diff --git a/fastplotlib/layouts/_figure.py b/fastplotlib/layouts/_figure.py index ce24e40a9..fb9e8e279 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -52,6 +52,7 @@ def __init__( canvas_kwargs: dict = None, size: tuple[int, int] = (500, 300), names: list | np.ndarray = None, + show_tooltips: bool = False, ): """ Create a Figure containing Subplots. @@ -122,6 +123,9 @@ def __init__( names: list or array of str, optional subplot names + show_tooltips: bool, default False + show tooltips on graphics + """ if rects is not None: @@ -422,8 +426,10 @@ def __init__( self._overlay_scene = pygfx.Scene() # tooltip in overlay render pass - self._tooltip = Tooltip() - self._overlay_scene.add(self._tooltip.world_object) + self._tooltip_manager = Tooltip() + self._overlay_scene.add(self._tooltip_manager.world_object) + + self._show_tooltips = show_tooltips self._animate_funcs_pre: list[callable] = list() self._animate_funcs_post: list[callable] = list() @@ -493,9 +499,27 @@ def names(self) -> np.ndarray[str]: return names @property - def tooltip(self) -> Tooltip: + def tooltip_manager(self) -> Tooltip: """manage tooltips""" - return self._tooltip + return self._tooltip_manager + + @property + def show_tooltips(self) -> bool: + """show/hide tooltips for all graphics""" + return self._show_tooltips + + @show_tooltips.setter + def show_tooltips(self, val: bool): + self._show_tooltips = val + + if val: + # register all graphics + for subplot in self: + for graphic in subplot.graphics: + self._tooltip_manager.register(graphic) + + elif not val: + self._tooltip_manager.unregister_all() def _render(self, draw=True): # draw the underlay planes diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index b0267dc75..c54890239 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -44,6 +44,7 @@ def __init__( canvas_kwargs: dict = None, size: tuple[int, int] = (500, 300), names: list | np.ndarray = None, + show_tooltips: bool = False, ): self._guis: dict[str, EdgeWindow] = {k: None for k in GUI_EDGES} @@ -60,6 +61,7 @@ def __init__( canvas_kwargs=canvas_kwargs, size=size, names=names, + show_tooltips=show_tooltips, ) self._imgui_renderer = ImguiRenderer(self.renderer.device, self.canvas) diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index 2934e0589..2542fc215 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -491,6 +491,10 @@ def _add_or_insert_graphic( obj_list = self._graphics self._fpl_graphics_scene.add(graphic.world_object) + # add to tooltip registry + if self.get_figure().show_tooltips: + self.get_figure().tooltip_manager.register(graphic) + else: raise TypeError("graphic must be of type Graphic | BaseSelector | Legend") @@ -504,7 +508,6 @@ def _add_or_insert_graphic( if center: self.center_graphic(graphic) - # if we don't use the weakref above, then the object lingers if a plot hook is used! graphic._fpl_add_plot_area_hook(self) def _check_graphic_name_exists(self, name): diff --git a/fastplotlib/tools/_tooltip.py b/fastplotlib/tools/_tooltip.py index 4c213d3b1..b4804f419 100644 --- a/fastplotlib/tools/_tooltip.py +++ b/fastplotlib/tools/_tooltip.py @@ -68,7 +68,7 @@ def __init__(self): # plane for the background of the text object geometry = pygfx.plane_geometry(1, 1) - material = pygfx.MeshBasicMaterial(color=(0.1, 0.1, 0.3, 0.9)) + material = pygfx.MeshBasicMaterial(color=(0.1, 0.1, 0.3, 0.95)) self._plane = pygfx.Mesh(geometry, material) # else text not visible self._plane.world.z = 0.5 @@ -253,6 +253,9 @@ def register( to display in the tooltip """ + if graphic in list(self._registered_graphics.keys()): + # unregister first and then re-register + self.unregister(graphic) pfunc = partial(self._event_handler, custom_info) graphic.add_event_handler(pfunc, appear_event) @@ -287,3 +290,8 @@ def unregister(self, graphic): # remove handlers from graphic graphic.remove_event_handler(pfunc, appear_event) graphic.remove_event_handler(self._clear, disappear_event) + + def unregister_all(self): + """unregister all graphics""" + for graphic in self._registered_graphics.keys(): + self.unregister(graphic) From 32cf5fa5e81c8bf053ff3d429fce984a4a476ac0 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Wed, 21 May 2025 00:55:39 -0400 Subject: [PATCH 07/15] black --- fastplotlib/tools/_tooltip.py | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/fastplotlib/tools/_tooltip.py b/fastplotlib/tools/_tooltip.py index b4804f419..563b5fd4d 100644 --- a/fastplotlib/tools/_tooltip.py +++ b/fastplotlib/tools/_tooltip.py @@ -76,17 +76,18 @@ def __init__(self): # line to outline the plane mesh self._line = pygfx.Line( geometry=pygfx.Geometry( - positions=np.array([ + positions=np.array( + [ [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], - ], dtype=np.float32) + ], + dtype=np.float32, + ) ), - material=pygfx.LineThinMaterial( - thickness=1.0, color=(0.8, 0.8, 1.0, 1.0) - ) + material=pygfx.LineThinMaterial(thickness=1.0, color=(0.8, 0.8, 1.0, 1.0)), ) self._world_object = pygfx.Group() @@ -94,11 +95,7 @@ def __init__(self): # padded to bbox so the background box behind the text extends a bit further # making the text easier to read - self._padding = np.array( - [[5, 5, 0], - [-5, -5, 0]], - dtype=np.float32 - ) + self._padding = np.array([[5, 5, 0], [-5, -5, 0]], dtype=np.float32) self._registered_graphics = dict() @@ -187,13 +184,7 @@ def _set_position(self, pos: tuple[float, float]): self._plane.geometry.positions.update_range() # line points - pts = [ - [x0, y0], - [x0, y1], - [x1, y1], - [x1, y0], - [x0, y0] - ] + pts = [[x0, y0], [x0, y1], [x1, y1], [x1, y0], [x0, y0]] self._line.geometry.positions.data[:, :2] = pts self._line.geometry.positions.update_range() @@ -208,11 +199,16 @@ def _event_handler(self, custom_tooltip: callable, ev: pygfx.PointerEvent): if ev.graphic.data.value.ndim == 2: info = str(ev.graphic.data[row, col]) else: - info = "\n".join(f"{channel}: {val}" for channel, val in zip("rgba", ev.graphic.data[row, col])) + info = "\n".join( + f"{channel}: {val}" + for channel, val in zip("rgba", ev.graphic.data[row, col]) + ) elif isinstance(ev.graphic, (LineGraphic, ScatterGraphic)): index = ev.pick_info["vertex_index"] - info = "\n".join(f"{dim}: {val}" for dim, val in zip("xyz", ev.graphic.data[index])) + info = "\n".join( + f"{dim}: {val}" for dim, val in zip("xyz", ev.graphic.data[index]) + ) else: raise TypeError("Unsupported graphic") From 1388b58be4c2233d2f60baf09a5dd626647ec72d Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Wed, 21 May 2025 01:05:31 -0400 Subject: [PATCH 08/15] update iris example, add to __init__ --- docs/source/generate_api.py | 39 ++++++++++++++++++++++++++++---- examples/misc/tooltips_custom.py | 6 +++-- fastplotlib/tools/__init__.py | 5 ++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/docs/source/generate_api.py b/docs/source/generate_api.py index 512826b5e..854e3f75f 100644 --- a/docs/source/generate_api.py +++ b/docs/source/generate_api.py @@ -9,6 +9,7 @@ from fastplotlib.layouts import Subplot from fastplotlib import graphics from fastplotlib.graphics import features, selectors +from fastplotlib import tools from fastplotlib import widgets from fastplotlib import utils from fastplotlib import ui @@ -21,6 +22,7 @@ GRAPHICS_DIR = API_DIR.joinpath("graphics") GRAPHIC_FEATURES_DIR = API_DIR.joinpath("graphic_features") SELECTORS_DIR = API_DIR.joinpath("selectors") +TOOLS_DIR = API_DIR.joinpath("tools") WIDGETS_DIR = API_DIR.joinpath("widgets") UI_DIR = API_DIR.joinpath("ui") GUIDE_DIR = current_dir.joinpath("user_guide") @@ -31,6 +33,7 @@ GRAPHICS_DIR, GRAPHIC_FEATURES_DIR, SELECTORS_DIR, + TOOLS_DIR, WIDGETS_DIR, UI_DIR, ] @@ -264,7 +267,8 @@ def main(): ) # the rest of this is a mess and can be refactored later - + ############################################################################## + # ** Graphic classes ** # graphic_classes = [getattr(graphics, g) for g in graphics.__all__] graphic_class_names = [g.__name__ for g in graphic_classes] @@ -290,7 +294,7 @@ def main(): source_path=GRAPHICS_DIR.joinpath(f"{graphic_cls.__name__}.rst"), ) ############################################################################## - + # ** GraphicFeature classes ** # feature_classes = [getattr(features, f) for f in features.__all__] feature_class_names = [f.__name__ for f in feature_classes] @@ -315,7 +319,7 @@ def main(): source_path=GRAPHIC_FEATURES_DIR.joinpath(f"{feature_cls.__name__}.rst"), ) ############################################################################## - + # ** Selector classes ** # selector_classes = [getattr(selectors, s) for s in selectors.__all__] selector_class_names = [s.__name__ for s in selector_classes] @@ -339,8 +343,35 @@ def main(): modules=["fastplotlib"], source_path=SELECTORS_DIR.joinpath(f"{selector_cls.__name__}.rst"), ) + ############################################################################## + # ** Tools classes ** # + tools_classes = [getattr(tools, t) for t in tools.__all__] + tools_class_names = [t.__name__ for t in tools_classes] + + tools_class_names_str = "\n ".join([""] + tools_class_names) + + with open(TOOLS_DIR.joinpath("index.rst"), "w") as f: + f.write( + f"Tools\n" + f"*****\n" + f"\n" + f".. toctree::\n" + f" :maxdepth: 1\n" + f"{tools_class_names_str}\n" + ) + + for tool_cls in tools_classes: + generate_page( + page_name=tool_cls.__name__, + classes=[tool_cls], + modules=["fastplotlib"], + source_path=TOOLS_DIR.joinpath(f"{tool_cls.__name__}.rst"), + ) + + ############################################################################## + # ** Widget classes ** # widget_classes = [getattr(widgets, w) for w in widgets.__all__] widget_class_names = [w.__name__ for w in widget_classes] @@ -365,7 +396,7 @@ def main(): source_path=WIDGETS_DIR.joinpath(f"{widget_cls.__name__}.rst"), ) ############################################################################## - + # ** UI classes ** # ui_classes = [ui.BaseGUI, ui.Window, ui.EdgeWindow, ui.Popup] ui_class_names = [cls.__name__ for cls in ui_classes] diff --git a/examples/misc/tooltips_custom.py b/examples/misc/tooltips_custom.py index 97d688305..a62190906 100644 --- a/examples/misc/tooltips_custom.py +++ b/examples/misc/tooltips_custom.py @@ -2,7 +2,8 @@ Tooltips Customization ====================== -Customize the information displayed in a tooltip +Customize the information displayed in a tooltip. This example uses the Iris dataset and sets the tooltip to display +the species and cluster label of the point that is being hovered by the mouse pointer. """ # test_example = false @@ -36,7 +37,8 @@ def tooltip_info(ev) -> str: # get the species name target = dataset["target"][index] - info = dataset["target_names"][target] + cluster = agg.labels_[index] + info = f"species: {dataset['target_names'][target]}\ncluster: {cluster}" # return this string to display it in the tooltip return info diff --git a/fastplotlib/tools/__init__.py b/fastplotlib/tools/__init__.py index 84e0238a7..df129a369 100644 --- a/fastplotlib/tools/__init__.py +++ b/fastplotlib/tools/__init__.py @@ -1,2 +1,7 @@ from ._histogram_lut import HistogramLUTTool from ._tooltip import Tooltip + +__all__ = [ + "HistogramLUTTool", + "Tooltip", +] From 13859ec07c2c7611c83c4dff4971d33b21c983e6 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Wed, 21 May 2025 01:13:53 -0400 Subject: [PATCH 09/15] update docs --- docs/source/api/graphics/Graphic.rst | 47 +++++++++++++++++++ docs/source/api/graphics/index.rst | 1 + docs/source/api/layouts/figure.rst | 2 + docs/source/api/layouts/imgui_figure.rst | 2 + docs/source/api/tools/HistogramLUTTool.rst | 53 ++++++++++++++++++++++ docs/source/api/tools/Tooltip.rst | 38 ++++++++++++++++ docs/source/api/tools/index.rst | 8 ++++ docs/source/generate_api.py | 3 ++ 8 files changed, 154 insertions(+) create mode 100644 docs/source/api/graphics/Graphic.rst create mode 100644 docs/source/api/tools/HistogramLUTTool.rst create mode 100644 docs/source/api/tools/Tooltip.rst create mode 100644 docs/source/api/tools/index.rst diff --git a/docs/source/api/graphics/Graphic.rst b/docs/source/api/graphics/Graphic.rst new file mode 100644 index 000000000..08ab0404b --- /dev/null +++ b/docs/source/api/graphics/Graphic.rst @@ -0,0 +1,47 @@ +.. _api.Graphic: + +Graphic +******* + +======= +Graphic +======= +.. currentmodule:: fastplotlib + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: Graphic_api + + Graphic + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: Graphic_api + + Graphic.axes + Graphic.block_events + Graphic.deleted + Graphic.event_handlers + Graphic.name + Graphic.offset + Graphic.right_click_menu + Graphic.rotation + Graphic.supported_events + Graphic.visible + Graphic.world_object + +Methods +~~~~~~~ +.. autosummary:: + :toctree: Graphic_api + + Graphic.add_axes + Graphic.add_event_handler + Graphic.clear_event_handlers + Graphic.remove_event_handler + Graphic.rotate + Graphic.share_property + Graphic.unshare_property + diff --git a/docs/source/api/graphics/index.rst b/docs/source/api/graphics/index.rst index a2addb7bf..491013dff 100644 --- a/docs/source/api/graphics/index.rst +++ b/docs/source/api/graphics/index.rst @@ -4,6 +4,7 @@ Graphics .. toctree:: :maxdepth: 1 + Graphic LineGraphic ScatterGraphic ImageGraphic diff --git a/docs/source/api/layouts/figure.rst b/docs/source/api/layouts/figure.rst index b5cbbd2bb..d191fe8ce 100644 --- a/docs/source/api/layouts/figure.rst +++ b/docs/source/api/layouts/figure.rst @@ -27,6 +27,8 @@ Properties Figure.names Figure.renderer Figure.shape + Figure.show_tooltips + Figure.tooltip_manager Methods ~~~~~~~ diff --git a/docs/source/api/layouts/imgui_figure.rst b/docs/source/api/layouts/imgui_figure.rst index a338afe96..0abfcc067 100644 --- a/docs/source/api/layouts/imgui_figure.rst +++ b/docs/source/api/layouts/imgui_figure.rst @@ -29,6 +29,8 @@ Properties ImguiFigure.names ImguiFigure.renderer ImguiFigure.shape + ImguiFigure.show_tooltips + ImguiFigure.tooltip_manager Methods ~~~~~~~ diff --git a/docs/source/api/tools/HistogramLUTTool.rst b/docs/source/api/tools/HistogramLUTTool.rst new file mode 100644 index 000000000..d134eb1ce --- /dev/null +++ b/docs/source/api/tools/HistogramLUTTool.rst @@ -0,0 +1,53 @@ +.. _api.HistogramLUTTool: + +HistogramLUTTool +**************** + +================ +HistogramLUTTool +================ +.. currentmodule:: fastplotlib + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: HistogramLUTTool_api + + HistogramLUTTool + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: HistogramLUTTool_api + + HistogramLUTTool.axes + HistogramLUTTool.block_events + HistogramLUTTool.cmap + HistogramLUTTool.deleted + HistogramLUTTool.event_handlers + HistogramLUTTool.image_graphic + HistogramLUTTool.name + HistogramLUTTool.offset + HistogramLUTTool.right_click_menu + HistogramLUTTool.rotation + HistogramLUTTool.supported_events + HistogramLUTTool.visible + HistogramLUTTool.vmax + HistogramLUTTool.vmin + HistogramLUTTool.world_object + +Methods +~~~~~~~ +.. autosummary:: + :toctree: HistogramLUTTool_api + + HistogramLUTTool.add_axes + HistogramLUTTool.add_event_handler + HistogramLUTTool.clear_event_handlers + HistogramLUTTool.disconnect_image_graphic + HistogramLUTTool.remove_event_handler + HistogramLUTTool.rotate + HistogramLUTTool.set_data + HistogramLUTTool.share_property + HistogramLUTTool.unshare_property + diff --git a/docs/source/api/tools/Tooltip.rst b/docs/source/api/tools/Tooltip.rst new file mode 100644 index 000000000..71607bf20 --- /dev/null +++ b/docs/source/api/tools/Tooltip.rst @@ -0,0 +1,38 @@ +.. _api.Tooltip: + +Tooltip +******* + +======= +Tooltip +======= +.. currentmodule:: fastplotlib + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: Tooltip_api + + Tooltip + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: Tooltip_api + + Tooltip.background_color + Tooltip.font_size + Tooltip.outline_color + Tooltip.padding + Tooltip.text_color + Tooltip.world_object + +Methods +~~~~~~~ +.. autosummary:: + :toctree: Tooltip_api + + Tooltip.register + Tooltip.unregister + Tooltip.unregister_all + diff --git a/docs/source/api/tools/index.rst b/docs/source/api/tools/index.rst new file mode 100644 index 000000000..c2666ed28 --- /dev/null +++ b/docs/source/api/tools/index.rst @@ -0,0 +1,8 @@ +Tools +***** + +.. toctree:: + :maxdepth: 1 + + HistogramLUTTool + Tooltip diff --git a/docs/source/generate_api.py b/docs/source/generate_api.py index 854e3f75f..545aea3a6 100644 --- a/docs/source/generate_api.py +++ b/docs/source/generate_api.py @@ -469,6 +469,9 @@ def write_table(name, feature_cls): f.write("============\n\n") for graphic_cls in [*graphic_classes, *selector_classes]: + if graphic_cls is graphics.Graphic: + # skip Graphic base class + continue f.write(f"{graphic_cls.__name__}\n") f.write("-" * len(graphic_cls.__name__) + "\n\n") for name, type_ in graphic_cls._features.items(): From 8b69cb6c9b2d7062c2b7e0df880aa084e56a1ad4 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Wed, 21 May 2025 01:14:05 -0400 Subject: [PATCH 10/15] type --- fastplotlib/tools/_tooltip.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fastplotlib/tools/_tooltip.py b/fastplotlib/tools/_tooltip.py index 563b5fd4d..279cc950d 100644 --- a/fastplotlib/tools/_tooltip.py +++ b/fastplotlib/tools/_tooltip.py @@ -3,7 +3,7 @@ import numpy as np import pygfx -from ..graphics import LineGraphic, ImageGraphic, ScatterGraphic +from ..graphics import LineGraphic, ImageGraphic, ScatterGraphic, Graphic from ..graphics.features import GraphicFeatureEvent @@ -225,7 +225,7 @@ def _clear(self, ev): def register( self, - graphic, + graphic: Graphic, appear_event: str = "pointer_move", disappear_event: str = "pointer_leave", custom_info: callable = None, @@ -262,7 +262,7 @@ def register( # automatically unregister when graphic is deleted graphic.add_event_handler(self.unregister, "deleted") - def unregister(self, graphic): + def unregister(self, graphic: Graphic): """ Unregister a Graphic to no longer display tooltips for this graphic. From fab75b8fa52a842eb33c6bc819f5a471639b9a63 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Wed, 21 May 2025 01:25:02 -0400 Subject: [PATCH 11/15] comments --- examples/misc/tooltips.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/misc/tooltips.py b/examples/misc/tooltips.py index 38d3ea537..4fdae1482 100644 --- a/examples/misc/tooltips.py +++ b/examples/misc/tooltips.py @@ -28,7 +28,7 @@ controller_types=["orbit", "panzoom", "panzoom", "panzoom"], size=(700, 560), shape=(2, 2), - show_tooltips=True, + show_tooltips=True, # tooltip will display data value info for all graphics ) # create graphics @@ -40,6 +40,12 @@ figure.show() +# to hide tooltips for all graphics in an existing Figure +# figure.show_tooltips = False + +# to show tooltips for all graphics in an existing Figure +# figure.show_tooltips = True + # NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively # please see our docs for using fastplotlib interactively in ipython and jupyter From 97e681e3377fd2f7eedf525e230d5dfb45983415 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Wed, 21 May 2025 01:28:40 -0400 Subject: [PATCH 12/15] docstring --- fastplotlib/tools/_tooltip.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/fastplotlib/tools/_tooltip.py b/fastplotlib/tools/_tooltip.py index 279cc950d..083e607e8 100644 --- a/fastplotlib/tools/_tooltip.py +++ b/fastplotlib/tools/_tooltip.py @@ -231,7 +231,10 @@ def register( custom_info: callable = None, ): """ - Register a Graphic to display tooltips + Register a Graphic to display tooltips. + + **Note:** if the passed graphic is already registered then it first unregistered + and then re-registered using the given arguments. Parameters ---------- @@ -266,6 +269,8 @@ def unregister(self, graphic: Graphic): """ Unregister a Graphic to no longer display tooltips for this graphic. + **Note:** if the passed graphic is not registered then it is just ignored without raising any exception. + Parameters ---------- graphic: Graphic @@ -278,7 +283,7 @@ def unregister(self, graphic: Graphic): graphic = graphic.graphic if graphic not in self._registered_graphics: - raise KeyError(f"Given graphic: {graphic} is not registered") + return # get pfunc and event names pfunc, appear_event, disappear_event = self._registered_graphics.pop(graphic) From 3a87a1c3b4c6664b861b2eb7a19d81e276512998 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Wed, 21 May 2025 01:43:12 -0400 Subject: [PATCH 13/15] add tools dir to api root toctree --- docs/source/generate_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/generate_api.py b/docs/source/generate_api.py index 545aea3a6..0be967a36 100644 --- a/docs/source/generate_api.py +++ b/docs/source/generate_api.py @@ -441,6 +441,7 @@ def main(): " graphics/index\n" " graphic_features/index\n" " selectors/index\n" + " tools/index\n" " ui/index\n" " widgets/index\n" " fastplotlib\n" From 49de4a778f3104772a926b57b67d8aabc5794577 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Wed, 21 May 2025 02:00:34 -0400 Subject: [PATCH 14/15] forgot to regenerate --- docs/source/api/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst index 87c134782..3a1184e6c 100644 --- a/docs/source/api/index.rst +++ b/docs/source/api/index.rst @@ -9,6 +9,7 @@ API Reference graphics/index graphic_features/index selectors/index + tools/index ui/index widgets/index fastplotlib From 0da5e386d74d9871acfd66c3fbb528529a52356b Mon Sep 17 00:00:00 2001 From: Kushal Kolar Date: Wed, 21 May 2025 20:03:04 -0400 Subject: [PATCH 15/15] Apply suggestions from code review Co-authored-by: Caitlin Lewis <69729525+clewis7@users.noreply.github.com> --- examples/line_collection/line_stack.py | 2 +- fastplotlib/tools/_tooltip.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/line_collection/line_stack.py b/examples/line_collection/line_stack.py index 4867d72bb..4f0c6037d 100644 --- a/examples/line_collection/line_stack.py +++ b/examples/line_collection/line_stack.py @@ -33,7 +33,7 @@ def tooltip_info(ev): - """a custom function to display the index of the graphic within the collection""" + """A custom function to display the index of the graphic within the collection.""" index = ev.pick_info["vertex_index"] # index of the line datapoint being hovered # get index of the hovered line within the line stack diff --git a/fastplotlib/tools/_tooltip.py b/fastplotlib/tools/_tooltip.py index 083e607e8..2fbdfcec2 100644 --- a/fastplotlib/tools/_tooltip.py +++ b/fastplotlib/tools/_tooltip.py @@ -137,7 +137,6 @@ def outline_color(self): @outline_color.setter def outline_color(self, color: str | tuple | list | np.ndarray): - """Get or set outline color using a str or RGB(A) array""" self._line.material.color = color @property @@ -190,7 +189,7 @@ def _set_position(self, pos: tuple[float, float]): self._line.geometry.positions.update_range() def _event_handler(self, custom_tooltip: callable, ev: pygfx.PointerEvent): - """handles the tooltip appear event, determines the text to be set in the tooltip""" + """Handles the tooltip appear event, determines the text to be set in the tooltip""" if custom_tooltip is not None: info = custom_tooltip(ev)