From 3a2c86dc97c36fb8054fe2b7bec90566aba85966 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Mon, 24 Feb 2025 04:10:44 -0500 Subject: [PATCH 1/4] Graphic.add_text --- fastplotlib/graphics/_base.py | 66 +++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index a25bc7176..03af17826 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -361,6 +361,72 @@ def my_handler(event): feature = getattr(self, f"_{t}") feature.remove_event_handler(wrapper) + def add_text(self, text: str, location: str = "center", location_z: str = "front", **kwargs): + """ + Add a TextGraphic at one of the corners or center of this graphic. + + Note 1: + Note 2: Uses axis aligned bounding box to get corners, does not account for any rotation set on the graphic. + + Parameters + ---------- + text: str + text to display in the ``TextGraphic`` + + location: str, default "center" + one of "center", "top-left", "top-right", "bottom-right", or "bottom-left" + + location_z: str, default "front" + one of "front" or "back" + + spacing: float, default 2.0 + number of pixels from the corner of the graphic to the TextGraphic position, ignored if location = "center" + + kwargs + passed to ``TextGraphic`` + + Returns + ------- + TextGraphic + + """ + valid = ["center", "top-left", "top-right", "bottom-right", "bottom-left"] + if not isinstance(location, str): + raise TypeError("`location` must be of type ") + if location not in valid: + raise ValueError(f"`location` must be one of : {valid}, you have passed: {location}") + + if location == "center": + x, y, z, r = self.world_object.get_world_bounding_sphere() + + return self._plot_area.add_text(text, offset=(x, y, z), **kwargs) + + bbox = self.world_object.get_world_bounding_box() + + for i, axis in enumerate(["x", "y", "z"]): + if getattr(self._plot_area.camera.local, f"scale_{axis}") < 0: + # swap boundary values for this axis + bbox[0, i], bbox[1, i] = bbox[1, i], bbox[0, i] + print(f"flipped: {i}") + + [[x1, y1, z1], [x2, y2, z2]] = bbox + + # set (x, y, z) location of text based on user str + if "top" in location: + y = y2 + elif "bottom" in location: + y = y1 + if "left" in location: + x = x1 + elif "right" in location: + x = x2 + if location_z == "front": + z = z1 + elif location_z == "back": + z = z2 + + return self._plot_area.add_text(text, offset=(x, y, z), **kwargs) + def _fpl_add_plot_area_hook(self, plot_area): self._plot_area = plot_area From 8ec898d8192b76f240b1a354eb0a0b2fe1a2f89a Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Mon, 24 Feb 2025 04:14:07 -0500 Subject: [PATCH 2/4] comment, black --- fastplotlib/graphics/_base.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index 03af17826..8af8e6708 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -361,11 +361,13 @@ def my_handler(event): feature = getattr(self, f"_{t}") feature.remove_event_handler(wrapper) - def add_text(self, text: str, location: str = "center", location_z: str = "front", **kwargs): + def add_text( + self, text: str, location: str = "center", location_z: str = "front", **kwargs + ): """ Add a TextGraphic at one of the corners or center of this graphic. - Note 1: + Note 1: If an axis is flipped, location is relative to the rendered view not the location in world space Note 2: Uses axis aligned bounding box to get corners, does not account for any rotation set on the graphic. Parameters @@ -394,7 +396,9 @@ def add_text(self, text: str, location: str = "center", location_z: str = "front if not isinstance(location, str): raise TypeError("`location` must be of type ") if location not in valid: - raise ValueError(f"`location` must be one of : {valid}, you have passed: {location}") + raise ValueError( + f"`location` must be one of : {valid}, you have passed: {location}" + ) if location == "center": x, y, z, r = self.world_object.get_world_bounding_sphere() From c32a4bc66a918fed0fd3835d4f1fe3933510aa04 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Mon, 24 Feb 2025 04:31:42 -0500 Subject: [PATCH 3/4] move if offset or data changes, WIP --- fastplotlib/graphics/_base.py | 92 +++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index 8af8e6708..2081b292f 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -361,6 +361,47 @@ def my_handler(event): feature = getattr(self, f"_{t}") feature.remove_event_handler(wrapper) + def _get_corner(self, location: str, location_z) -> tuple[float, float, float]: + valid = ["center", "top-left", "top-right", "bottom-right", "bottom-left"] + if not isinstance(location, str): + raise TypeError("`location` must be of type ") + if location not in valid: + raise ValueError( + f"`location` must be one of : {valid}, you have passed: {location}" + ) + + if location == "center": + x, y, z, r = self.world_object.get_world_bounding_sphere() + + return (x, y, z) + + else: + bbox = self.world_object.get_world_bounding_box() + + for i, axis in enumerate(["x", "y", "z"]): + if getattr(self._plot_area.camera.local, f"scale_{axis}") < 0: + # swap boundary values for this axis + bbox[0, i], bbox[1, i] = bbox[1, i], bbox[0, i] + print(f"flipped: {i}") + + [[x1, y1, z1], [x2, y2, z2]] = bbox + + # set (x, y, z) location of text based on user str + if "top" in location: + y = y2 + elif "bottom" in location: + y = y1 + if "left" in location: + x = x1 + elif "right" in location: + x = x2 + if location_z == "front": + z = z1 + elif location_z == "back": + z = z2 + + return (x, y, z) + def add_text( self, text: str, location: str = "center", location_z: str = "front", **kwargs ): @@ -392,44 +433,23 @@ def add_text( TextGraphic """ - valid = ["center", "top-left", "top-right", "bottom-right", "bottom-left"] - if not isinstance(location, str): - raise TypeError("`location` must be of type ") - if location not in valid: - raise ValueError( - f"`location` must be one of : {valid}, you have passed: {location}" - ) + offset = self._get_corner(location, location_z) - if location == "center": - x, y, z, r = self.world_object.get_world_bounding_sphere() + def update_offset(ev): + new_offset = self._get_corner(*ev.graphic._location) + ev.graphic.offset = new_offset + + text_graphic = self._plot_area.add_text(text, offset=offset, metadata=(location, location_z), **kwargs) + text_graphic._location = (location, location_z) + + text_graphic.add_event_handler(update_offset, "offset") + + class_name = self.__class__.__name__ + + if "Line" in class_name or "Scatter" in class_name: + text_graphic.add_event_handler(update_offset, "data") - return self._plot_area.add_text(text, offset=(x, y, z), **kwargs) - - bbox = self.world_object.get_world_bounding_box() - - for i, axis in enumerate(["x", "y", "z"]): - if getattr(self._plot_area.camera.local, f"scale_{axis}") < 0: - # swap boundary values for this axis - bbox[0, i], bbox[1, i] = bbox[1, i], bbox[0, i] - print(f"flipped: {i}") - - [[x1, y1, z1], [x2, y2, z2]] = bbox - - # set (x, y, z) location of text based on user str - if "top" in location: - y = y2 - elif "bottom" in location: - y = y1 - if "left" in location: - x = x1 - elif "right" in location: - x = x2 - if location_z == "front": - z = z1 - elif location_z == "back": - z = z2 - - return self._plot_area.add_text(text, offset=(x, y, z), **kwargs) + return text_graphic def _fpl_add_plot_area_hook(self, plot_area): self._plot_area = plot_area From 9722aacb9ccd136db653a195520e0e43b9c171c1 Mon Sep 17 00:00:00 2001 From: kushalkolar Date: Mon, 24 Feb 2025 04:43:29 -0500 Subject: [PATCH 4/4] text moves if graphic offset or data change --- fastplotlib/graphics/_base.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index 2081b292f..c075ad048 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -49,6 +49,12 @@ ] +def _update_text_offset(parent, tg, ev): + # helper function + new_offset = parent._fpl_get_corner(*tg._location) + tg.offset = new_offset + + class Graphic: _features: set[str] = {} @@ -361,7 +367,8 @@ def my_handler(event): feature = getattr(self, f"_{t}") feature.remove_event_handler(wrapper) - def _get_corner(self, location: str, location_z) -> tuple[float, float, float]: + def _fpl_get_corner(self, location: str, location_z) -> tuple[float, float, float]: + """Get the (x, y, z) corner of a graphic relative to the rendered view""" valid = ["center", "top-left", "top-right", "bottom-right", "bottom-left"] if not isinstance(location, str): raise TypeError("`location` must be of type ") @@ -382,7 +389,6 @@ def _get_corner(self, location: str, location_z) -> tuple[float, float, float]: if getattr(self._plot_area.camera.local, f"scale_{axis}") < 0: # swap boundary values for this axis bbox[0, i], bbox[1, i] = bbox[1, i], bbox[0, i] - print(f"flipped: {i}") [[x1, y1, z1], [x2, y2, z2]] = bbox @@ -433,21 +439,19 @@ def add_text( TextGraphic """ - offset = self._get_corner(location, location_z) - - def update_offset(ev): - new_offset = self._get_corner(*ev.graphic._location) - ev.graphic.offset = new_offset + offset = self._fpl_get_corner(location, location_z) text_graphic = self._plot_area.add_text(text, offset=offset, metadata=(location, location_z), **kwargs) text_graphic._location = (location, location_z) - text_graphic.add_event_handler(update_offset, "offset") + events = ["offset"] class_name = self.__class__.__name__ if "Line" in class_name or "Scatter" in class_name: - text_graphic.add_event_handler(update_offset, "data") + events.append("data") + + self.add_event_handler(partial(_update_text_offset, self, text_graphic), *events) return text_graphic