diff --git a/docs/source/_static/guide_ipywidgets.webp b/docs/source/_static/guide_ipywidgets.webp new file mode 100644 index 000000000..9a7963381 Binary files /dev/null and b/docs/source/_static/guide_ipywidgets.webp differ diff --git a/docs/source/_static/switcher.json b/docs/source/_static/switcher.json index 67f723e2f..9f792b252 100644 --- a/docs/source/_static/switcher.json +++ b/docs/source/_static/switcher.json @@ -1,7 +1,22 @@ [ + { + "name": "release", + "version": "v0.4.0", + "url": "http://www.fastplotlib.org/" + }, { "name": "dev/main", "version": "dev", - "url": "http://www.fastplotlib.org/versions/dev" + "url": "http://www.fastplotlib.org/ver/dev" + }, + { + "name": "v0.3.0", + "version": "v0.3.0", + "url": "http://www.fastplotlib.org/ver/0.3.0" + }, + { + "name": "v0.4.0", + "version": "v0.4.0", + "url": "http://www.fastplotlib.org/ver/0.4.0" } ] diff --git a/docs/source/api/graphic_features/Deleted.rst b/docs/source/api/graphic_features/Deleted.rst index 09131c4a7..ffc704917 100644 --- a/docs/source/api/graphic_features/Deleted.rst +++ b/docs/source/api/graphic_features/Deleted.rst @@ -6,7 +6,7 @@ Deleted ======= Deleted ======= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/FontSize.rst b/docs/source/api/graphic_features/FontSize.rst index 4b8df9826..5e34c6038 100644 --- a/docs/source/api/graphic_features/FontSize.rst +++ b/docs/source/api/graphic_features/FontSize.rst @@ -6,7 +6,7 @@ FontSize ======== FontSize ======== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/GraphicFeatureEvent.rst b/docs/source/api/graphic_features/GraphicFeatureEvent.rst new file mode 100644 index 000000000..233462052 --- /dev/null +++ b/docs/source/api/graphic_features/GraphicFeatureEvent.rst @@ -0,0 +1,38 @@ +.. _api.GraphicFeatureEvent: + +GraphicFeatureEvent +******************* + +=================== +GraphicFeatureEvent +=================== +.. currentmodule:: fastplotlib.graphics.features + +Constructor +~~~~~~~~~~~ +.. autosummary:: + :toctree: GraphicFeatureEvent_api + + GraphicFeatureEvent + +Properties +~~~~~~~~~~ +.. autosummary:: + :toctree: GraphicFeatureEvent_api + + GraphicFeatureEvent.bubbles + GraphicFeatureEvent.cancelled + GraphicFeatureEvent.current_target + GraphicFeatureEvent.root + GraphicFeatureEvent.target + GraphicFeatureEvent.time_stamp + GraphicFeatureEvent.type + +Methods +~~~~~~~ +.. autosummary:: + :toctree: GraphicFeatureEvent_api + + GraphicFeatureEvent.cancel + GraphicFeatureEvent.stop_propagation + diff --git a/docs/source/api/graphic_features/ImageCmap.rst b/docs/source/api/graphic_features/ImageCmap.rst index 23d16a4a2..2c23a3406 100644 --- a/docs/source/api/graphic_features/ImageCmap.rst +++ b/docs/source/api/graphic_features/ImageCmap.rst @@ -6,7 +6,7 @@ ImageCmap ========= ImageCmap ========= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/ImageCmapInterpolation.rst b/docs/source/api/graphic_features/ImageCmapInterpolation.rst index 7e04ec788..0577f2d70 100644 --- a/docs/source/api/graphic_features/ImageCmapInterpolation.rst +++ b/docs/source/api/graphic_features/ImageCmapInterpolation.rst @@ -6,7 +6,7 @@ ImageCmapInterpolation ====================== ImageCmapInterpolation ====================== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/ImageInterpolation.rst b/docs/source/api/graphic_features/ImageInterpolation.rst index 866e76333..ebf69c279 100644 --- a/docs/source/api/graphic_features/ImageInterpolation.rst +++ b/docs/source/api/graphic_features/ImageInterpolation.rst @@ -6,7 +6,7 @@ ImageInterpolation ================== ImageInterpolation ================== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/ImageVmax.rst b/docs/source/api/graphic_features/ImageVmax.rst index b7dfe7e2d..aa8d6526a 100644 --- a/docs/source/api/graphic_features/ImageVmax.rst +++ b/docs/source/api/graphic_features/ImageVmax.rst @@ -6,7 +6,7 @@ ImageVmax ========= ImageVmax ========= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/ImageVmin.rst b/docs/source/api/graphic_features/ImageVmin.rst index 0d4634894..361cc5838 100644 --- a/docs/source/api/graphic_features/ImageVmin.rst +++ b/docs/source/api/graphic_features/ImageVmin.rst @@ -6,7 +6,7 @@ ImageVmin ========= ImageVmin ========= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/LinearRegionSelectionFeature.rst b/docs/source/api/graphic_features/LinearRegionSelectionFeature.rst index b8958c86b..9f06f2682 100644 --- a/docs/source/api/graphic_features/LinearRegionSelectionFeature.rst +++ b/docs/source/api/graphic_features/LinearRegionSelectionFeature.rst @@ -6,7 +6,7 @@ LinearRegionSelectionFeature ============================ LinearRegionSelectionFeature ============================ -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/LinearSelectionFeature.rst b/docs/source/api/graphic_features/LinearSelectionFeature.rst index ad7b8645a..b9e71cd7b 100644 --- a/docs/source/api/graphic_features/LinearSelectionFeature.rst +++ b/docs/source/api/graphic_features/LinearSelectionFeature.rst @@ -6,7 +6,7 @@ LinearSelectionFeature ====================== LinearSelectionFeature ====================== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/Name.rst b/docs/source/api/graphic_features/Name.rst index 288fcfc22..f5a5235d8 100644 --- a/docs/source/api/graphic_features/Name.rst +++ b/docs/source/api/graphic_features/Name.rst @@ -6,7 +6,7 @@ Name ==== Name ==== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/Offset.rst b/docs/source/api/graphic_features/Offset.rst index 683aaf763..fdb2af66a 100644 --- a/docs/source/api/graphic_features/Offset.rst +++ b/docs/source/api/graphic_features/Offset.rst @@ -6,7 +6,7 @@ Offset ====== Offset ====== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/PointsSizesFeature.rst b/docs/source/api/graphic_features/PointsSizesFeature.rst index 3dcc4eeb2..f3f78b74b 100644 --- a/docs/source/api/graphic_features/PointsSizesFeature.rst +++ b/docs/source/api/graphic_features/PointsSizesFeature.rst @@ -6,7 +6,7 @@ PointsSizesFeature ================== PointsSizesFeature ================== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/RectangleSelectionFeature.rst b/docs/source/api/graphic_features/RectangleSelectionFeature.rst index d35752a24..cdfd1ad3f 100644 --- a/docs/source/api/graphic_features/RectangleSelectionFeature.rst +++ b/docs/source/api/graphic_features/RectangleSelectionFeature.rst @@ -6,7 +6,7 @@ RectangleSelectionFeature ========================= RectangleSelectionFeature ========================= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/Rotation.rst b/docs/source/api/graphic_features/Rotation.rst index f8963b0fd..b7729c7a4 100644 --- a/docs/source/api/graphic_features/Rotation.rst +++ b/docs/source/api/graphic_features/Rotation.rst @@ -6,7 +6,7 @@ Rotation ======== Rotation ======== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/SizeSpace.rst b/docs/source/api/graphic_features/SizeSpace.rst index 0bca1ecc8..e7c8e30be 100644 --- a/docs/source/api/graphic_features/SizeSpace.rst +++ b/docs/source/api/graphic_features/SizeSpace.rst @@ -6,7 +6,7 @@ SizeSpace ========= SizeSpace ========= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/TextData.rst b/docs/source/api/graphic_features/TextData.rst index 1c27b6e48..bf08b08d6 100644 --- a/docs/source/api/graphic_features/TextData.rst +++ b/docs/source/api/graphic_features/TextData.rst @@ -6,7 +6,7 @@ TextData ======== TextData ======== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/TextFaceColor.rst b/docs/source/api/graphic_features/TextFaceColor.rst index 5dae54192..5ab01b04b 100644 --- a/docs/source/api/graphic_features/TextFaceColor.rst +++ b/docs/source/api/graphic_features/TextFaceColor.rst @@ -6,7 +6,7 @@ TextFaceColor ============= TextFaceColor ============= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/TextOutlineColor.rst b/docs/source/api/graphic_features/TextOutlineColor.rst index f7831b0df..571261625 100644 --- a/docs/source/api/graphic_features/TextOutlineColor.rst +++ b/docs/source/api/graphic_features/TextOutlineColor.rst @@ -6,7 +6,7 @@ TextOutlineColor ================ TextOutlineColor ================ -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/TextOutlineThickness.rst b/docs/source/api/graphic_features/TextOutlineThickness.rst index 75d485781..450ae54c9 100644 --- a/docs/source/api/graphic_features/TextOutlineThickness.rst +++ b/docs/source/api/graphic_features/TextOutlineThickness.rst @@ -6,7 +6,7 @@ TextOutlineThickness ==================== TextOutlineThickness ==================== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/TextureArray.rst b/docs/source/api/graphic_features/TextureArray.rst index 79707c453..73facc5bf 100644 --- a/docs/source/api/graphic_features/TextureArray.rst +++ b/docs/source/api/graphic_features/TextureArray.rst @@ -6,7 +6,7 @@ TextureArray ============ TextureArray ============ -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/Thickness.rst b/docs/source/api/graphic_features/Thickness.rst index 061f96fe8..dc4c5888f 100644 --- a/docs/source/api/graphic_features/Thickness.rst +++ b/docs/source/api/graphic_features/Thickness.rst @@ -6,7 +6,7 @@ Thickness ========= Thickness ========= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/UniformColor.rst b/docs/source/api/graphic_features/UniformColor.rst index 7370589b7..8e9d56eae 100644 --- a/docs/source/api/graphic_features/UniformColor.rst +++ b/docs/source/api/graphic_features/UniformColor.rst @@ -6,7 +6,7 @@ UniformColor ============ UniformColor ============ -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/UniformSize.rst b/docs/source/api/graphic_features/UniformSize.rst index e342d6a70..e4727dcb9 100644 --- a/docs/source/api/graphic_features/UniformSize.rst +++ b/docs/source/api/graphic_features/UniformSize.rst @@ -6,7 +6,7 @@ UniformSize =========== UniformSize =========== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/VertexCmap.rst b/docs/source/api/graphic_features/VertexCmap.rst index a3311d6e6..77d96aaf6 100644 --- a/docs/source/api/graphic_features/VertexCmap.rst +++ b/docs/source/api/graphic_features/VertexCmap.rst @@ -6,7 +6,7 @@ VertexCmap ========== VertexCmap ========== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/VertexColors.rst b/docs/source/api/graphic_features/VertexColors.rst index 3c2089a78..d09da7a18 100644 --- a/docs/source/api/graphic_features/VertexColors.rst +++ b/docs/source/api/graphic_features/VertexColors.rst @@ -6,7 +6,7 @@ VertexColors ============ VertexColors ============ -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/VertexPositions.rst b/docs/source/api/graphic_features/VertexPositions.rst index 9669ab6d5..d181f07b9 100644 --- a/docs/source/api/graphic_features/VertexPositions.rst +++ b/docs/source/api/graphic_features/VertexPositions.rst @@ -6,7 +6,7 @@ VertexPositions =============== VertexPositions =============== -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/Visible.rst b/docs/source/api/graphic_features/Visible.rst index 957b4433a..06bfd2278 100644 --- a/docs/source/api/graphic_features/Visible.rst +++ b/docs/source/api/graphic_features/Visible.rst @@ -6,7 +6,7 @@ Visible ======= Visible ======= -.. currentmodule:: fastplotlib.graphics._features +.. currentmodule:: fastplotlib.graphics.features Constructor ~~~~~~~~~~~ diff --git a/docs/source/api/graphic_features/index.rst b/docs/source/api/graphic_features/index.rst index dc88e97d6..90a58fe8e 100644 --- a/docs/source/api/graphic_features/index.rst +++ b/docs/source/api/graphic_features/index.rst @@ -31,3 +31,4 @@ Graphic Features Rotation Visible Deleted + GraphicFeatureEvent diff --git a/docs/source/api/graphics/index.rst b/docs/source/api/graphics/index.rst index b64ac53c0..a2addb7bf 100644 --- a/docs/source/api/graphics/index.rst +++ b/docs/source/api/graphics/index.rst @@ -5,8 +5,8 @@ Graphics :maxdepth: 1 LineGraphic - ImageGraphic ScatterGraphic + ImageGraphic TextGraphic LineCollection LineStack diff --git a/docs/source/api/utils.rst b/docs/source/api/utils.rst index 6222e22c6..be7b1a049 100644 --- a/docs/source/api/utils.rst +++ b/docs/source/api/utils.rst @@ -4,3 +4,7 @@ fastplotlib.utils .. currentmodule:: fastplotlib.utils .. automodule:: fastplotlib.utils.functions :members: + +.. currentmodule:: fastplotlib.utils +.. automodule:: fastplotlib.utils._plot_helpers + :members: diff --git a/docs/source/conf.py b/docs/source/conf.py index 865c462a6..8d17c97ae 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -60,12 +60,16 @@ "../../examples/image_widget", "../../examples/gridplot", "../../examples/window_layouts", + "../../examples/controllers", "../../examples/line", "../../examples/line_collection", "../../examples/scatter", + "../../examples/text", + "../../examples/events", "../../examples/selection_tools", "../../examples/machine_learning", "../../examples/guis", + "../../examples/ipywidgets", "../../examples/misc", "../../examples/qt", ] diff --git a/docs/source/generate_api.py b/docs/source/generate_api.py index 6887566cb..512826b5e 100644 --- a/docs/source/generate_api.py +++ b/docs/source/generate_api.py @@ -1,12 +1,14 @@ -from typing import * +from collections import defaultdict import inspect -from pathlib import Path +from io import StringIO import os +from pathlib import Path +from typing import * import fastplotlib -from fastplotlib.layouts._subplot import Subplot +from fastplotlib.layouts import Subplot from fastplotlib import graphics -from fastplotlib.graphics import _features, selectors +from fastplotlib.graphics import features, selectors from fastplotlib import widgets from fastplotlib import utils from fastplotlib import ui @@ -21,6 +23,7 @@ SELECTORS_DIR = API_DIR.joinpath("selectors") WIDGETS_DIR = API_DIR.joinpath("widgets") UI_DIR = API_DIR.joinpath("ui") +GUIDE_DIR = current_dir.joinpath("user_guide") doc_sources = [ API_DIR, @@ -56,16 +59,6 @@ "See the rendercanvas docs: https://rendercanvas.readthedocs.io/stable/api.html#rendercanvas.BaseLoop " ) -with open(API_DIR.joinpath("utils.rst"), "w") as f: - f.write( - "fastplotlib.utils\n" - "*****************\n\n" - - "..currentmodule:: fastplotlib.utils\n" - "..automodule:: fastplotlib.utils.functions\n" - " : members:\n" - ) - def get_public_members(cls) -> Tuple[List[str], List[str]]: """ @@ -139,12 +132,18 @@ def generate_class( return out -def generate_functions_module(module, name: str): +def generate_functions_module(module, name: str, generate_header: bool = True): underline = "*" * len(name) + if generate_header: + header = ( + f"{name}\n" + f"{underline}\n" + f"\n" + ) + else: + header = "\n" out = ( - f"{name}\n" - f"{underline}\n" - f"\n" + f"{header}" f".. currentmodule:: {name}\n" f".. automodule:: {module.__name__}\n" f" :members:\n" @@ -173,6 +172,60 @@ def generate_page( to_write = generate_class(cls, module) f.write(to_write) +####################################################### +# Used for GraphicFeature class event table +# copy-pasted from https://pablofernandez.tech/2019/03/21/turning-a-list-of-dicts-into-a-restructured-text-table/ + +def _generate_header(field_names, column_widths): + with StringIO() as output: + for field_name in field_names: + output.write(f"+-{'-' * column_widths[field_name]}-") + output.write("+\n") + for field_name in field_names: + output.write(f"| {field_name} {' ' * (column_widths[field_name] - len(field_name))}") + output.write("|\n") + for field_name in field_names: + output.write(f"+={'=' * column_widths[field_name]}=") + output.write("+\n") + return output.getvalue() + + +def _generate_row(row, field_names, column_widths): + with StringIO() as output: + for field_name in field_names: + output.write(f"| {row[field_name]}{' ' * (column_widths[field_name] - len(str(row[field_name])))} ") + output.write("|\n") + for field_name in field_names: + output.write(f"+-{'-' * column_widths[field_name]}-") + output.write("+\n") + return output.getvalue() + + +def _get_fields(data): + field_names = [] + column_widths = defaultdict(lambda: 0) + for row in data: + for field_name in row: + if field_name not in field_names: + field_names.append(field_name) + column_widths[field_name] = max(column_widths[field_name], len(field_name), len(str(row[field_name]))) + return field_names, column_widths + + +def dict_to_rst_table(data): + """convert a list of dicts to an RST table""" + field_names, column_widths = _get_fields(data) + with StringIO() as output: + output.write(_generate_header(field_names, column_widths)) + for row in data: + output.write(_generate_row(row, field_names, column_widths)) + + output.write("\n") + + return output.getvalue() + +####################################################### + def main(): generate_page( @@ -238,7 +291,7 @@ def main(): ) ############################################################################## - feature_classes = [getattr(_features, f) for f in _features.__all__] + feature_classes = [getattr(features, f) for f in features.__all__] feature_class_names = [f.__name__ for f in feature_classes] @@ -258,7 +311,7 @@ def main(): generate_page( page_name=feature_cls.__name__, classes=[feature_cls], - modules=["fastplotlib.graphics._features"], + modules=["fastplotlib.graphics.features"], source_path=GRAPHIC_FEATURES_DIR.joinpath(f"{feature_cls.__name__}.rst"), ) ############################################################################## @@ -340,11 +393,12 @@ def main(): ############################################################################## utils_str = generate_functions_module(utils.functions, "fastplotlib.utils") + utils_str += generate_functions_module(utils._plot_helpers, "fastplotlib.utils", generate_header=False) with open(API_DIR.joinpath("utils.rst"), "w") as f: f.write(utils_str) - # nake API index file + # make API index file with open(API_DIR.joinpath("index.rst"), "w") as f: f.write( "API Reference\n" @@ -362,5 +416,39 @@ def main(): " utils\n" ) + ############################################################################## + # graphic feature event tables + + def write_table(name, feature_cls): + s = f"{name}\n" + s += "^" * len(name) + "\n\n" + + if hasattr(feature_cls, "event_extra_attrs"): + s += "**extra attributes**\n\n" + s += dict_to_rst_table(feature_cls.event_extra_attrs) + + s += "**event info dict**\n\n" + s += dict_to_rst_table(feature_cls.event_info_spec) + + return s + + with open(GUIDE_DIR.joinpath("event_tables.rst"), "w") as f: + f.write(".. _event_tables:\n\n") + f.write("Event Tables\n") + f.write("============\n\n") + + for graphic_cls in [*graphic_classes, *selector_classes]: + f.write(f"{graphic_cls.__name__}\n") + f.write("-" * len(graphic_cls.__name__) + "\n\n") + for name, type_ in graphic_cls._features.items(): + if isinstance(type_, tuple): + for t in type_: + if t is None: + continue + f.write(write_table(name, t)) + else: + f.write(write_table(name, type_)) + + if __name__ == "__main__": main() diff --git a/docs/source/user_guide/event_tables.rst b/docs/source/user_guide/event_tables.rst new file mode 100644 index 000000000..1b9b2f7ec --- /dev/null +++ b/docs/source/user_guide/event_tables.rst @@ -0,0 +1,1020 @@ +.. _event_tables: + +Event Tables +============ + +LineGraphic +----------- + +data +^^^^ + +**event info dict** + ++----------+----------------------------------------------+--------------------------------------------------------+ +| dict key | type | description | ++==========+==============================================+========================================================+ +| key | slice, index (int) or numpy-like fancy index | key at which vertex positions data were indexed/sliced | ++----------+----------------------------------------------+--------------------------------------------------------+ +| value | int | float | array-like | new data values for points that were changed | ++----------+----------------------------------------------+--------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++------------+--------------------------------------+------------------------------------------------------+ +| dict key | type | description | ++============+======================================+======================================================+ +| key | slice, index, numpy-like fancy index | index/slice at which colors were indexed/sliced | ++------------+--------------------------------------+------------------------------------------------------+ +| value | np.ndarray [n_points_changed, RGBA] | new color values for points that were changed | ++------------+--------------------------------------+------------------------------------------------------+ +| user_value | str or array-like | user input value that was parsed into the RGBA array | ++------------+--------------------------------------+------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++----------+-------------------+-----------------+ +| dict key | type | description | ++==========+===================+=================+ +| value | np.ndarray [RGBA] | new color value | ++----------+-------------------+-----------------+ + +cmap +^^^^ + +**event info dict** + ++----------+-------+--------------------------------+ +| dict key | type | description | ++==========+=======+================================+ +| key | slice | key at cmap colors were sliced | ++----------+-------+--------------------------------+ +| value | str | new cmap to set at given slice | ++----------+-------+--------------------------------+ + +thickness +^^^^^^^^^ + +**event info dict** + ++----------+-------+---------------------+ +| dict key | type | description | ++==========+=======+=====================+ +| value | float | new thickness value | ++----------+-------+---------------------+ + +size_space +^^^^^^^^^^ + +**event info dict** + ++----------+------+------------------------------+ +| dict key | type | description | ++==========+======+==============================+ +| value | str | 'screen' | 'world' | 'model' | ++----------+------+------------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +ScatterGraphic +-------------- + +data +^^^^ + +**event info dict** + ++----------+----------------------------------------------+--------------------------------------------------------+ +| dict key | type | description | ++==========+==============================================+========================================================+ +| key | slice, index (int) or numpy-like fancy index | key at which vertex positions data were indexed/sliced | ++----------+----------------------------------------------+--------------------------------------------------------+ +| value | int | float | array-like | new data values for points that were changed | ++----------+----------------------------------------------+--------------------------------------------------------+ + +sizes +^^^^^ + +**event info dict** + ++----------+----------------------------------------------+----------------------------------------------+ +| dict key | type | description | ++==========+==============================================+==============================================+ +| key | slice, index (int) or numpy-like fancy index | key at which point sizes were indexed/sliced | ++----------+----------------------------------------------+----------------------------------------------+ +| value | int | float | array-like | new size values for points that were changed | ++----------+----------------------------------------------+----------------------------------------------+ + +sizes +^^^^^ + +**event info dict** + ++----------+-------+----------------+ +| dict key | type | description | ++==========+=======+================+ +| value | float | new size value | ++----------+-------+----------------+ + +colors +^^^^^^ + +**event info dict** + ++------------+--------------------------------------+------------------------------------------------------+ +| dict key | type | description | ++============+======================================+======================================================+ +| key | slice, index, numpy-like fancy index | index/slice at which colors were indexed/sliced | ++------------+--------------------------------------+------------------------------------------------------+ +| value | np.ndarray [n_points_changed, RGBA] | new color values for points that were changed | ++------------+--------------------------------------+------------------------------------------------------+ +| user_value | str or array-like | user input value that was parsed into the RGBA array | ++------------+--------------------------------------+------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++----------+-------------------+-----------------+ +| dict key | type | description | ++==========+===================+=================+ +| value | np.ndarray [RGBA] | new color value | ++----------+-------------------+-----------------+ + +cmap +^^^^ + +**event info dict** + ++----------+-------+--------------------------------+ +| dict key | type | description | ++==========+=======+================================+ +| key | slice | key at cmap colors were sliced | ++----------+-------+--------------------------------+ +| value | str | new cmap to set at given slice | ++----------+-------+--------------------------------+ + +size_space +^^^^^^^^^^ + +**event info dict** + ++----------+------+------------------------------+ +| dict key | type | description | ++==========+======+==============================+ +| value | str | 'screen' | 'world' | 'model' | ++----------+------+------------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +ImageGraphic +------------ + +data +^^^^ + +**event info dict** + ++----------+--------------------------------------+--------------------------------------------------+ +| dict key | type | description | ++==========+======================================+==================================================+ +| key | slice, index, numpy-like fancy index | key at which image data was sliced/fancy indexed | ++----------+--------------------------------------+--------------------------------------------------+ +| value | np.ndarray | float | new data values | ++----------+--------------------------------------+--------------------------------------------------+ + +cmap +^^^^ + +**event info dict** + ++----------+------+---------------+ +| dict key | type | description | ++==========+======+===============+ +| value | str | new cmap name | ++----------+------+---------------+ + +vmin +^^^^ + +**event info dict** + ++----------+-------+----------------+ +| dict key | type | description | ++==========+=======+================+ +| value | float | new vmin value | ++----------+-------+----------------+ + +vmax +^^^^ + +**event info dict** + ++----------+-------+----------------+ +| dict key | type | description | ++==========+=======+================+ +| value | float | new vmax value | ++----------+-------+----------------+ + +interpolation +^^^^^^^^^^^^^ + +**event info dict** + ++----------+------+--------------------------------------------+ +| dict key | type | description | ++==========+======+============================================+ +| value | str | new interpolation method, nearest | linear | ++----------+------+--------------------------------------------+ + +cmap_interpolation +^^^^^^^^^^^^^^^^^^ + +**event info dict** + ++----------+------+------------------------------------------------+ +| dict key | type | description | ++==========+======+================================================+ +| value | str | new cmap interpolatio method, nearest | linear | ++----------+------+------------------------------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +TextGraphic +----------- + +text +^^^^ + +**event info dict** + ++----------+------+---------------+ +| dict key | type | description | ++==========+======+===============+ +| value | str | new text data | ++----------+------+---------------+ + +font_size +^^^^^^^^^ + +**event info dict** + ++----------+-------------+---------------+ +| dict key | type | description | ++==========+=============+===============+ +| value | float | int | new font size | ++----------+-------------+---------------+ + +face_color +^^^^^^^^^^ + +**event info dict** + ++----------+------------------+----------------+ +| dict key | type | description | ++==========+==================+================+ +| value | str | np.ndarray | new text color | ++----------+------------------+----------------+ + +outline_color +^^^^^^^^^^^^^ + +**event info dict** + ++----------+------------------+-------------------+ +| dict key | type | description | ++==========+==================+===================+ +| value | str | np.ndarray | new outline color | ++----------+------------------+-------------------+ + +outline_thickness +^^^^^^^^^^^^^^^^^ + +**event info dict** + ++----------+-------+----------------------------+ +| dict key | type | description | ++==========+=======+============================+ +| value | float | new text outline thickness | ++----------+-------+----------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +LineCollection +-------------- + +data +^^^^ + +**event info dict** + ++----------+----------------------------------------------+--------------------------------------------------------+ +| dict key | type | description | ++==========+==============================================+========================================================+ +| key | slice, index (int) or numpy-like fancy index | key at which vertex positions data were indexed/sliced | ++----------+----------------------------------------------+--------------------------------------------------------+ +| value | int | float | array-like | new data values for points that were changed | ++----------+----------------------------------------------+--------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++------------+--------------------------------------+------------------------------------------------------+ +| dict key | type | description | ++============+======================================+======================================================+ +| key | slice, index, numpy-like fancy index | index/slice at which colors were indexed/sliced | ++------------+--------------------------------------+------------------------------------------------------+ +| value | np.ndarray [n_points_changed, RGBA] | new color values for points that were changed | ++------------+--------------------------------------+------------------------------------------------------+ +| user_value | str or array-like | user input value that was parsed into the RGBA array | ++------------+--------------------------------------+------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++----------+-------------------+-----------------+ +| dict key | type | description | ++==========+===================+=================+ +| value | np.ndarray [RGBA] | new color value | ++----------+-------------------+-----------------+ + +cmap +^^^^ + +**event info dict** + ++----------+-------+--------------------------------+ +| dict key | type | description | ++==========+=======+================================+ +| key | slice | key at cmap colors were sliced | ++----------+-------+--------------------------------+ +| value | str | new cmap to set at given slice | ++----------+-------+--------------------------------+ + +thickness +^^^^^^^^^ + +**event info dict** + ++----------+-------+---------------------+ +| dict key | type | description | ++==========+=======+=====================+ +| value | float | new thickness value | ++----------+-------+---------------------+ + +size_space +^^^^^^^^^^ + +**event info dict** + ++----------+------+------------------------------+ +| dict key | type | description | ++==========+======+==============================+ +| value | str | 'screen' | 'world' | 'model' | ++----------+------+------------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +LineStack +--------- + +data +^^^^ + +**event info dict** + ++----------+----------------------------------------------+--------------------------------------------------------+ +| dict key | type | description | ++==========+==============================================+========================================================+ +| key | slice, index (int) or numpy-like fancy index | key at which vertex positions data were indexed/sliced | ++----------+----------------------------------------------+--------------------------------------------------------+ +| value | int | float | array-like | new data values for points that were changed | ++----------+----------------------------------------------+--------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++------------+--------------------------------------+------------------------------------------------------+ +| dict key | type | description | ++============+======================================+======================================================+ +| key | slice, index, numpy-like fancy index | index/slice at which colors were indexed/sliced | ++------------+--------------------------------------+------------------------------------------------------+ +| value | np.ndarray [n_points_changed, RGBA] | new color values for points that were changed | ++------------+--------------------------------------+------------------------------------------------------+ +| user_value | str or array-like | user input value that was parsed into the RGBA array | ++------------+--------------------------------------+------------------------------------------------------+ + +colors +^^^^^^ + +**event info dict** + ++----------+-------------------+-----------------+ +| dict key | type | description | ++==========+===================+=================+ +| value | np.ndarray [RGBA] | new color value | ++----------+-------------------+-----------------+ + +cmap +^^^^ + +**event info dict** + ++----------+-------+--------------------------------+ +| dict key | type | description | ++==========+=======+================================+ +| key | slice | key at cmap colors were sliced | ++----------+-------+--------------------------------+ +| value | str | new cmap to set at given slice | ++----------+-------+--------------------------------+ + +thickness +^^^^^^^^^ + +**event info dict** + ++----------+-------+---------------------+ +| dict key | type | description | ++==========+=======+=====================+ +| value | float | new thickness value | ++----------+-------+---------------------+ + +size_space +^^^^^^^^^^ + +**event info dict** + ++----------+------+------------------------------+ +| dict key | type | description | ++==========+======+==============================+ +| value | str | 'screen' | 'world' | 'model' | ++----------+------+------------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +LinearSelector +-------------- + +selection +^^^^^^^^^ + +**extra attributes** + ++--------------------+----------+----------------------------------+ +| attribute | type | description | ++====================+==========+==================================+ +| get_selected_index | callable | returns index under the selector | ++--------------------+----------+----------------------------------+ + +**event info dict** + ++----------+-------+-------------------------------+ +| dict key | type | description | ++==========+=======+===============================+ +| value | float | new x or y value of selection | ++----------+-------+-------------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +LinearRegionSelector +-------------------- + +selection +^^^^^^^^^ + +**extra attributes** + ++----------------------+----------+------------------------------------+ +| attribute | type | description | ++======================+==========+====================================+ +| get_selected_indices | callable | returns indices under the selector | ++----------------------+----------+------------------------------------+ +| get_selected_data | callable | returns data under the selector | ++----------------------+----------+------------------------------------+ + +**event info dict** + ++----------+------------+-----------------------------+ +| dict key | type | description | ++==========+============+=============================+ +| value | np.ndarray | new [min, max] of selection | ++----------+------------+-----------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + +RectangleSelector +----------------- + +selection +^^^^^^^^^ + +**extra attributes** + ++----------------------+----------+------------------------------------+ +| attribute | type | description | ++======================+==========+====================================+ +| get_selected_indices | callable | returns indices under the selector | ++----------------------+----------+------------------------------------+ +| get_selected_data | callable | returns data under the selector | ++----------------------+----------+------------------------------------+ + +**event info dict** + ++----------+------------+-------------------------------------------+ +| dict key | type | description | ++==========+============+===========================================+ +| value | np.ndarray | new [xmin, xmax, ymin, ymax] of selection | ++----------+------------+-------------------------------------------+ + +name +^^^^ + +**event info dict** + ++----------+------+--------------------+ +| dict key | type | description | ++==========+======+====================+ +| value | str | user provided name | ++----------+------+--------------------+ + +offset +^^^^^^ + +**event info dict** + ++----------+---------------------------------+----------------------+ +| dict key | type | description | ++==========+=================================+======================+ +| value | np.ndarray[float, float, float] | new offset (x, y, z) | ++----------+---------------------------------+----------------------+ + +rotation +^^^^^^^^ + +**event info dict** + ++----------+----------------------------------------+-------------------------+ +| dict key | type | description | ++==========+========================================+=========================+ +| value | np.ndarray[float, float, float, float] | new rotation quaternion | ++----------+----------------------------------------+-------------------------+ + +visible +^^^^^^^ + +**event info dict** + ++----------+------+---------------------+ +| dict key | type | description | ++==========+======+=====================+ +| value | bool | new visibility bool | ++----------+------+---------------------+ + +deleted +^^^^^^^ + +**event info dict** + ++----------+------+-------------------------------+ +| dict key | type | description | ++==========+======+===============================+ +| value | bool | True when graphic was deleted | ++----------+------+-------------------------------+ + diff --git a/docs/source/user_guide/faq.rst b/docs/source/user_guide/faq.rst index 0061a04d4..5985efae1 100644 --- a/docs/source/user_guide/faq.rst +++ b/docs/source/user_guide/faq.rst @@ -56,6 +56,8 @@ Should I use ``fastplotlib`` for making publication figures? While `fastplotlib` figures can be exported to PNG using ``figure.export()``, `fastplotlib` is not intended for creating *static* publication figures. There are many other libraries that are well-suited for this task. + The rendering engine pygfx has a starting point for an svg renderer, you may try and expand upon it: https://github.com/pygfx/pygfx/tree/main/pygfx/renderers/svg + How does ``fastplotlib`` handle data loading? --------------------------------------------- diff --git a/docs/source/user_guide/guide.rst b/docs/source/user_guide/guide.rst index d544c42a3..1bdb3377c 100644 --- a/docs/source/user_guide/guide.rst +++ b/docs/source/user_guide/guide.rst @@ -41,8 +41,8 @@ The fundamental goal of ``fastplotlib`` is to provide a high-level, expressive A make it easy and intuitive to produce interactive visualizations that are as performant and vibrant as a modern video game 😄 -How to use ``fastplotlib`` --------------------------- +``fastplotlib`` basics +---------------------- Before giving a detailed overview of the library, here is a minimal example:: @@ -71,16 +71,22 @@ This is just a simple example of how the ``fastplotlib`` API works to create a p However, we are just scratching the surface of what is possible with ``fastplotlib``. Next, let's take a look at the building blocks of ``fastplotlib`` and how they can be used to create more complex visualizations. +Aside from this user guide, the Examples Gallery is the best place to learn specific things in fastplotlib. +If you still need help don't hesitate to post an issue or discussion post! + Figure ------ -The starting point for creating any visualization in ``fastplotlib`` is a ``Figure`` object. This can be a single plot or a grid of subplots. +The starting point for creating any visualization in ``fastplotlib`` is a ``Figure`` object. This can be a single subplot or many subplots. The ``Figure`` object houses and takes care of the underlying rendering components such as the camera, controller, renderer, and canvas. Most users won't need to use these directly; however, the ability to directly interact with the rendering engine is still available if needed. -By default, if no ``shape`` argument is provided when creating a ``Figure``, there will be a single subplot. All subplots in a ``Figure`` can be accessed using -indexing (i.e. ``fig_object[i ,j]``). +By default, if no ``shape`` argument is provided when creating a ``Figure``, there will be a single ``Subplot``. + +If a shape argument is provided, all subplots in a ``Figure`` can be accessed by indexing (i.e. ``fig_object[i ,j]``). A "window layout" +with customizable subplot positions and sizes can also be set by providing a ``rects`` or ``extents`` argument. The Examples Gallery +has a few examples that show how to create a "Window Layout". After defining a ``Figure``, we can begin to add ``Graphic`` objects. @@ -99,18 +105,22 @@ to be easily accessed from figures:: add image graphic image_graphic = fig[0, 0].add_image(data=data, name="astronaut") - # show plot + # show figure fig.show() - # index plot to get graphic + # index subplot to get graphic fig[0, 0]["astronaut"] + # another way to index graphics in a subplot + fig[0, 0].graphics[0] is fig[0, 0]["astronaut"] # will return `True` + .. See the examples gallery for examples on how to create and interactive with all the various types of graphics. -Graphics also have mutable properties that can be linked to events. Some of these properties, such as the ``data`` or ``colors`` of a line can even be indexed, -allowing for the creation of very powerful visualizations. +Graphics also have mutable properties. Some of these properties, such as the ``data`` or ``colors`` of a line can even be sliced, +allowing for the creation of very powerful visualizations. Event handlers can be added to a graphic to capture changes to +any of these properties. (1) Common properties that all graphics have @@ -132,17 +142,17 @@ allowing for the creation of very powerful visualizations. (a) ``ImageGraphic`` - +------------------------+------------------------------------+ - | Feature Name | Description | - +========================+====================================+ - | data | Underlying image data | - +------------------------+------------------------------------+ - | vmin | Lower contrast limit of an image | - +------------------------+------------------------------------+ - | vmax | Upper contrast limit of an image | - +------------------------+------------------------------------+ - | cmap | Colormap of an image | - +------------------------+------------------------------------+ + +------------------------+---------------------------------------------------+ + | Feature Name | Description | + +========================+===================================================+ + | data | Underlying image data | + +------------------------+---------------------------------------------------+ + | vmin | Lower contrast limit of an image | + +------------------------+---------------------------------------------------+ + | vmax | Upper contrast limit of an image | + +------------------------+---------------------------------------------------+ + | cmap | Colormap for a grayscale image, ignored if RGB(A) | + +------------------------+---------------------------------------------------+ (b) ``LineGraphic``, ``LineCollection``, ``LineStack`` @@ -244,14 +254,13 @@ your data, you are able to select an entire region. See the examples gallery for more in-depth examples with selector tools. Now we have the basics of creating a ``Figure``, adding ``Graphics`` to a ``Figure``, and working with ``Graphic`` properties to dynamically change or alter them. -Let's take a look at how we can define events to link ``Graphics`` and their properties together. Events ------ -Events can be a multitude of things: traditional events such as mouse or keyboard events, or events related to ``Graphic`` properties. +Events can be a multitude of things: canvas events such as mouse or keyboard events, or events related to ``Graphic`` properties. -There are two ways to add events in ``fastplotlib``. +There are two ways to add events to a graphic: 1) Use the method `add_event_handler()` :: @@ -272,24 +281,24 @@ There are two ways to add events in ``fastplotlib``. .. -The ``event_handler`` is a user-defined function that accepts an event instance as the first and only positional argument. +The ``event_handler`` is a user-defined callback function that accepts an event instance as the first and only positional argument. Information about the structure of event instances are described below. The ``"event_type"`` -is a string that identifies the type of event; this can be either a ``pygfx.Event`` or a ``Graphic`` property event. +is a string that identifies the type of event. ``graphic.supported_events`` will return a tuple of all ``event_type`` strings that this graphic supports. When an event occurs, the user-defined event handler will receive an event object. Depending on the type of event, the -event object will have relevant information that can be used in the callback. See below for event tables. +event object will have relevant information that can be used in the callback. See the next section for details. Graphic property events ^^^^^^^^^^^^^^^^^^^^^^^ -All ``Graphic`` events have the following attributes: +All ``Graphic`` events are instances of ``fastplotlib.GraphicFeatureEvent`` and have the following attributes: +------------+-------------+-----------------------------------------------+ | attribute | type | description | +============+=============+===============================================+ - | type | str | "colors" - name of the event | + | type | str | name of the event type | +------------+-------------+-----------------------------------------------+ | graphic | Graphic | graphic instance that the event is from | +------------+-------------+-----------------------------------------------+ @@ -300,144 +309,80 @@ All ``Graphic`` events have the following attributes: | time_stamp | float | time when the event occurred, in ms | +------------+-------------+-----------------------------------------------+ -The ``info`` attribute will house additional information for different ``Graphic`` property events: - -event_type: "colors" - - Vertex Colors - - **info dict** - - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | dict key | value type | value description | - +============+===========================================================+==================================================================================+ - | key | int | slice | np.ndarray[int | bool] | tuple[slice, ...] | key at which colors were indexed/sliced | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | value | np.ndarray | new color values for points that were changed, shape is [n_points_changed, RGBA] | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | user_value | str | np.ndarray | tuple[float] | list[float] | list[str] | user input value that was parsed into the RGBA array | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - - Uniform Colors - - **info dict** +Selectors have one event called ``selection`` which has extra attributes in addition to those listed in the table above. +The selection event section covers these. - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | dict key | value type | value description | - +============+===========================================================+==================================================================================+ - | value | np.ndarray | new color values for points that were changed, shape is [n_points_changed, RGBA] | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ +The ``info`` attribute for most graphic property events will have one key, ``"value"``, which is the new value +of the graphic property. Events for graphic properties that represent arrays, such the ``data`` properties for +images, lines, and scatters will contain more entries. Here are a list of all graphic properties that have such +additional entries: -event_type: "sizes" +* ``ImageGraphic`` + * data - **info dict** +* ``LineGraphic`` + * data, colors, cmap - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - | dict key | value type | value description | - +==========+==========================================================+==========================================================================================+ - | key | int | slice | np.ndarray[int | bool] | tuple[slice, ...] | key at which vertex positions data were indexed/sliced | - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - | value | np.ndarray | float | list[float] | new data values for points that were changed, shape depends on the indices that were set | - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ +* ``ScatterGraphic`` + * data, colors, cmap, sizes -event_type: "data" +You can understand an event's attributes by adding a simple event handler:: - **info dict** - - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - | dict key | value type | value description | - +==========+==========================================================+==========================================================================================+ - | key | int | slice | np.ndarray[int | bool] | tuple[slice, ...] | key at which vertex positions data were indexed/sliced | - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - | value | np.ndarray | float | list[float] | new data values for points that were changed, shape depends on the indices that were set | - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - -event_type: "thickness" - - **info dict** - - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | dict key | value type | value description | - +============+===========================================================+==================================================================================+ - | value | float | new thickness value | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - -event_type: "cmap" - - **info dict** - - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | dict key | value type | value description | - +============+===========================================================+==================================================================================+ - | value | string | new colormap value | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ + @graphic.add_event_handler("event_type") + def handler(ev): + print(ev.type) + print(ev.graphic) + print(ev.info) -event_type: "selection" + # trigger the event + graphic.event_type = - ``LinearSelector`` + # direct example + @image_graphic.add_event_handler("cmap") + def cmap_changed(ev): + print(ev.type) + print(ev.info) - **additional event attributes:** + image_graphic.cmap = "viridis" + # this will trigger the cmap event and print the following: + # 'cmap' + # {"value": "viridis"} - +--------------------+----------+------------------------------------+ - | attribute | type | description | - +====================+==========+====================================+ - | get_selected_index | callable | returns indices under the selector | - +--------------------+----------+------------------------------------+ +.. - **info dict:** +The :ref:`event_tables` provide a description of the event info dicts for all Graphic Feature Events. - +----------+------------+-------------------------------+ - | dict key | value type | value description | - +==========+============+===============================+ - | value | np.ndarray | new x or y value of selection | - +----------+------------+-------------------------------+ +Selection event +~~~~~~~~~~~~~~~ - ``LinearRegionSelector`` +The ``selection`` event for selectors has additional attributes, mostly ``callable`` methods, that aid in using the +selector tool, such as getting the indices or data under the selection. The ``info`` dict will contain one entry ``value`` +which is the new selection value. - **additional event attributes:** +The :ref:`event_tables` provide a description of the additional attributes as well as the event info dicts for selector events. - +----------------------+----------+------------------------------------+ - | attribute | type | description | - +======================+==========+====================================+ - | get_selected_indices | callable | returns indices under the selector | - +----------------------+----------+------------------------------------+ - | get_selected_data | callable | returns data under the selector | - +----------------------+----------+------------------------------------+ +Canvas Events +^^^^^^^^^^^^^ - **info dict:** +Canvas events can be added to a graphic or to a Figure (see next section). +Here is a description of all canvas events and their attributes. - +----------+------------+-----------------------------+ - | dict key | value type | value description | - +==========+============+=============================+ - | value | np.ndarray | new [min, max] of selection | - +----------+------------+-----------------------------+ +The examples gallery provides several examples using pointer and key events. -Rendering engine events from a Graphic -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Pointer events +~~~~~~~~~~~~~~ -Rendering engine event handlers can be added to a graphic or to a Figure (see next section). -Here is a description of all rendering engine events and their attributes. +**List of pointer events:** * **pointer_down**: emitted when the user interacts with mouse, - touch or other pointer devices, by pressing it down. - - * *x*: horizontal position of the pointer within the widget. - * *y*: vertical position of the pointer within the widget. - * *button*: the button to which this event applies. See "Mouse buttons" section below for details. - * *buttons*: a tuple of buttons being pressed down. - * *modifiers*: a tuple of modifier keys being pressed down. See section below for details. - * *ntouches*: the number of simultaneous pointers being down. - * *touches*: a dict with int keys (pointer id's), and values that are dicts - that contain "x", "y", and "pressure". - * *time_stamp*: a timestamp in seconds. * **pointer_up**: emitted when the user releases a pointer. - This event has the same keys as the pointer down event. * **pointer_move**: emitted when the user moves a pointer. - This event has the same keys as the pointer down event. This event is throttled. +* **click**: emmitted when a mouse button is clicked. + * **double_click**: emitted on a double-click. This event looks like a pointer event, but without the touches. @@ -465,25 +410,19 @@ Here is a description of all rendering engine events and their attributes. * *modifiers*: a tuple of modifier keys being pressed down. * *time_stamp*: a timestamp in seconds. -* **key_down**: emitted when a key is pressed down. - - * *key*: the key being pressed as a string. See section below for details. - * *modifiers*: a tuple of modifier keys being pressed down. - * *time_stamp*: a timestamp in seconds. - -* **key_up**: emitted when a key is released. - This event has the same keys as the key down event. - +All pointer events have the following attributes: -Time stamps -~~~~~~~~~~~ +* *x*: horizontal position of the pointer within the widget. +* *y*: vertical position of the pointer within the widget. +* *button*: the button to which this event applies. See "Mouse buttons" section below for details. +* *buttons*: a tuple of buttons being pressed down (see below) +* *modifiers*: a tuple of modifier keys being pressed down. See section below for details. +* *ntouches*: the number of simultaneous pointers being down. +* *touches*: a dict with int keys (pointer id's), and values that are dicts + that contain "x", "y", and "pressure". +* *time_stamp*: a timestamp in seconds. -Since the time origin of ``time_stamp`` values is undefined, -time stamp values only make sense in relation to other time stamps. - - -Mouse buttons -~~~~~~~~~~~~~ +**Mouse buttons:** * 0: No button. * 1: Left button. @@ -491,9 +430,20 @@ Mouse buttons * 3: Middle button * 4-9: etc. +Key events +~~~~~~~~~~ + +**List of key (keyboard keys) events:** + +* **key_down**: emitted when a key is pressed down. + +* **key_up**: emitted when a key is released. + +Key events have the following attributes: -Keys -~~~~ +* *key*: the key being pressed as a string. See section below for details. +* *modifiers*: a tuple of modifier keys being pressed down. +* *time_stamp*: a timestamp in seconds. The key names follow the `browser spec `_. @@ -504,13 +454,18 @@ The key names follow the `browser spec `_ library is great for rapidly building UIs for prototyping +in jupyter. It is particularly useful for scientific and engineering applications since we can rapidly create a UI to +interact with our ``fastplotlib`` visualization. The main downside is that it only works in jupyter. + +.. image:: ../_static/guide_ipywidgets.webp + +For examples please see the examples gallery. + +Qt +^^ + +Qt is a very popular UI library written in C++, ``PyQt6`` and ``PySide6`` provide python bindings. There are countless +tutorials on how to build a UI using Qt which you can easily find if you google ``PyQt``. You can embed a ``Figure`` as +a Qt widget within a Qt application. + +For examples please see the examples gallery. + +imgui +^^^^^ + +`Imgui `_ is also a very popular library used for building UIs. The difference +between imgui and ipywidgets, Qt, and wx is the imgui UI can be rendered directly on the same canvas as a fastplotlib +``Figure``. This is hugely advantageous, it means that you can write an imgui UI and it will run on any GUI backend, +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. + +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. + +**Some tips:** + +The ``imgui-bundle`` docs as of March 2025 don't have a nice API list (as far as I know), here is how we go about developing UIs with imgui: + +1. Use the ``pyimgui`` API docs to locate the type of UI element we want, for example if we want a ``slider_int``: https://pyimgui.readthedocs.io/en/latest/reference/imgui.core.html#imgui.core.slider_int + +2. Look at the function signature in the ``imgui-bundle`` sources. You can usually access this easily with your IDE: https://github.com/pthom/imgui_bundle/blob/a5e7d46555832c40e9be277d4747eac5a303dbfc/bindings/imgui_bundle/imgui/__init__.pyi#L1693-L1696 + +3. ``pyimgui`` and ``imgui-bundle`` sometimes don't have the same function signature, so we use a combination of the pyimgui docs and +imgui-bundle function signature to understand and implement the UI element. + ImageWidget ----------- @@ -572,12 +579,9 @@ Let's look at an example: :: movie = iio.imread("imageio:cockatoo.mp4") - # convert RGB movie to grayscale - gray_movie = np.dot(movie[..., :3], [0.299, 0.587, 0.114]) - iw_movie = ImageWidget( - data=gray_movie, - cmap="gray" + data=movie, + rgb=True ) iw_movie.show() diff --git a/docs/source/user_guide/index.rst b/docs/source/user_guide/index.rst index 59189be22..92f0da98c 100644 --- a/docs/source/user_guide/index.rst +++ b/docs/source/user_guide/index.rst @@ -6,5 +6,6 @@ User Guide :maxdepth: 2 guide + event_tables gpu faq diff --git a/examples/controllers/README.rst b/examples/controllers/README.rst new file mode 100644 index 000000000..824087ce3 --- /dev/null +++ b/examples/controllers/README.rst @@ -0,0 +1,2 @@ +Controller examples +=================== diff --git a/examples/controllers/specify_integers.py b/examples/controllers/specify_integers.py new file mode 100644 index 000000000..128f240ad --- /dev/null +++ b/examples/controllers/specify_integers.py @@ -0,0 +1,50 @@ +""" +Specify IDs with integers +========================= + +Specify controllers to sync subplots using integer IDs +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + + +xs = np.linspace(0, 2 * np.pi, 100) +sine = np.sin(xs) +cosine = np.cos(xs) + +# controller IDs +# one controller is created for each unique ID +# if the IDs are the same, those subplots will be synced +ids = [ + [0, 1], + [2, 0], +] + +names = [f"contr. id: {i}" for i in np.asarray(ids).ravel()] + +figure = fpl.Figure( + shape=(2, 2), + controller_ids=ids, + names=names, + size=(700, 560), +) + +figure[0, 0].add_line(np.column_stack([xs, sine])) + +figure[0, 1].add_line(np.random.rand(100)) +figure[1, 0].add_line(np.random.rand(100)) + +figure[1, 1].add_line(np.column_stack([xs, cosine])) + +figure.show(maintain_aspect=False) + + +# 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/examples/controllers/specify_names.py b/examples/controllers/specify_names.py new file mode 100644 index 000000000..fb0539c4a --- /dev/null +++ b/examples/controllers/specify_names.py @@ -0,0 +1,47 @@ +""" +Specify IDs with subplot names +============================== + +Provide a list of tuples where each tuple has subplot names. The same controller will be used for the subplots +indicated by each of these tuples +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + + +xs = np.linspace(0, 2 * np.pi, 100) +ys = np.sin(xs) + +# create some subplots names +names = ["subplot_0", "subplot_1", "subplot_2", "subplot_3", "subplot_4", "subplot_5"] + +# list of tuples of subplot names +# subplots within each tuple will use the same controller. +ids = [ + ("subplot_0", "subplot_3"), + ("subplot_1", "subplot_2", "subplot_4"), +] + + +figure = fpl.Figure( + shape=(2, 3), + controller_ids=ids, + names=names, + size=(700, 560), +) + +for subplot in figure: + subplot.add_line(np.column_stack([xs, ys + np.random.normal(scale=0.1, size=100)])) + +figure.show(maintain_aspect=False) + + +# 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/examples/controllers/sync_all.py b/examples/controllers/sync_all.py new file mode 100644 index 000000000..0683a8827 --- /dev/null +++ b/examples/controllers/sync_all.py @@ -0,0 +1,30 @@ +""" +Sync subplots +============= + +Use one controller for all subplots. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl + + +xs = np.linspace(0, 2 * np.pi, 100) +ys = np.sin(xs) + +figure = fpl.Figure(shape=(2, 2), controller_ids="sync", size=(700, 560)) + +for subplot in figure: + subplot.add_line(np.column_stack([xs, ys + np.random.normal(scale=0.5, size=100)])) + +figure.show(maintain_aspect=False) + + +# 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/examples/events/README.rst b/examples/events/README.rst new file mode 100644 index 000000000..8e2deca4b --- /dev/null +++ b/examples/events/README.rst @@ -0,0 +1,4 @@ +Events +====== + +Several examples using events \ No newline at end of file diff --git a/examples/events/cmap_event.py b/examples/events/cmap_event.py new file mode 100644 index 000000000..6cd68f333 --- /dev/null +++ b/examples/events/cmap_event.py @@ -0,0 +1,75 @@ +""" +cmap event +========== + +Add a cmap event handler to multiple graphics. When any one graphic changes the cmap, the cmap of all other graphics +is also changed. + +This also shows how bidirectional events are supported. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import imageio.v3 as iio + +# load images +img1 = iio.imread("imageio:camera.png") +img2 = iio.imread("imageio:moon.png") + +# Create a figure +figure = fpl.Figure( + shape=(2, 2), + size=(700, 560), + names=["camera", "moon", "sine", "cloud"], +) + +# create graphics +figure["camera"].add_image(img1) +figure["moon"].add_image(img2) + +# sine wave +xs = np.linspace(0, 4 * np.pi, 100) +ys = np.sin(xs) + +figure["sine"].add_line(np.column_stack([xs, ys])) + +# make a 2D gaussian cloud +cloud_data = np.random.normal(0, scale=3, size=1000).reshape(500, 2) +figure["cloud"].add_scatter( + cloud_data, + sizes=3, + cmap="plasma", + cmap_transform=np.linalg.norm(cloud_data, axis=1) # cmap transform using distance from origin +) +figure["cloud"].axes.intersection = (0, 0, 0) + +# show the plot +figure.show() + + +# event handler to change the cmap of all graphics when the cmap of any one graphic changes +def cmap_changed(ev: fpl.GraphicFeatureEvent): + # get the new cmap + new_cmap = ev.info["value"] + + # set cmap of the graphics in the other subplots + for subplot in figure: + subplot.graphics[0].cmap = new_cmap + + +for subplot in figure: + # add event handler to the graphic added to each subplot + subplot.graphics[0].add_event_handler(cmap_changed, "cmap") + + +# change the cmap of graphic image, triggers all other graphics to set the cmap +figure["camera"].graphics[0].cmap = "jet" + +# 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/examples/events/drag_points.py b/examples/events/drag_points.py new file mode 100644 index 000000000..9a91779d4 --- /dev/null +++ b/examples/events/drag_points.py @@ -0,0 +1,99 @@ +""" +Drag points +=========== + +Example where you can drag scatter points on a line. This example also demonstrates how you can use a shared buffer +between two graphics to represent the same data using different graphics. When you update the data of one graphic the +data of the other graphic is also changed simultaneously since they use the same underlying buffer on the GPU. + +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import pygfx + +xs = np.linspace(0, 2 * np.pi, 10) +ys = np.sin(xs) + +data = np.column_stack([xs, ys]) + +figure = fpl.Figure(size=(700, 560)) + +# add a line +line_graphic = figure[0, 0].add_line(data) + +# add a scatter, share the line graphic buffer! +scatter_graphic = figure[0, 0].add_scatter(data=line_graphic.data, sizes=25, colors="r") + +is_moving = False +vertex_index = None + + +@scatter_graphic.add_event_handler("pointer_down") +def start_drag(ev: pygfx.PointerEvent): + global is_moving + global vertex_index + + if ev.button != 1: + return + + is_moving = True + vertex_index = ev.pick_info["vertex_index"] + scatter_graphic.colors[vertex_index] = "cyan" + + +@figure.renderer.add_event_handler("pointer_move") +def move_point(ev): + global is_moving + global vertex_index + + # if not moving, return + if not is_moving: + return + + # disable controller + figure[0, 0].controller.enabled = False + + # map x, y from screen space to world space + pos = figure[0, 0].map_screen_to_world(ev) + + if pos is None: + # end movement + is_moving = False + scatter_graphic.colors[vertex_index] = "r" # reset color + vertex_index = None + return + + # change scatter data + # since we are sharing the buffer, the line data will also change + scatter_graphic.data[vertex_index, :-1] = pos[:-1] + + # re-enable controller + figure[0, 0].controller.enabled = True + + +@figure.renderer.add_event_handler("pointer_up") +def end_drag(ev: pygfx.PointerEvent): + global is_moving + global vertex_index + + # end movement + if is_moving: + # reset color + scatter_graphic.colors[vertex_index] = "r" + + is_moving = False + vertex_index = None + + +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/examples/misc/simple_event.py b/examples/events/image_click.py similarity index 58% rename from examples/misc/simple_event.py rename to examples/events/image_click.py index e382f04b5..acb6cde37 100644 --- a/examples/misc/simple_event.py +++ b/examples/events/image_click.py @@ -1,14 +1,15 @@ """ -Simple Event -============ +Image click event +================= -Example showing how to add a simple callback event. +Example showing how to use a click event on an image. """ # test_example = false # sphinx_gallery_pygfx_docs = 'screenshot' import fastplotlib as fpl +import pygfx import imageio.v3 as iio data = iio.imread("imageio:camera.png") @@ -16,32 +17,21 @@ # Create a figure figure = fpl.Figure(size=(700, 560)) -# plot sine wave, use a single color -image_graphic = figure[0,0].add_image(data=data) +# create image graphic +image_graphic = figure[0, 0].add_image(data=data) # show the plot figure.show() -# define callback function to print the event data -def callback_func(event_data): - print(event_data.info) - - -# Will print event data when the color changes -image_graphic.add_event_handler(callback_func, "cmap") - -image_graphic.cmap = "viridis" - - # adding a click event, we can also use decorators to add event handlers @image_graphic.add_event_handler("click") -def click_event(event_data): +def click_event(ev: pygfx.PointerEvent): # get the click location in screen coordinates - xy = (event_data.x, event_data.y) + xy = (ev.x, ev.y) # map the screen coordinates to world coordinates - xy = figure[0,0].map_screen_to_world(xy)[:-1] + xy = figure[0, 0].map_screen_to_world(xy)[:-1] # print the click location print(xy) diff --git a/examples/events/image_data_event.py b/examples/events/image_data_event.py new file mode 100644 index 000000000..32f78996c --- /dev/null +++ b/examples/events/image_data_event.py @@ -0,0 +1,56 @@ +""" +Image data event +================ + +Example showing how to add an event handler to an ImageGraphic to capture when the data changes. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import fastplotlib as fpl +import imageio.v3 as iio +from scipy.ndimage import gaussian_filter + +rgb_weights = [0.299, 0.587, 0.114] + +# load images, convert to grayscale +img1 = iio.imread("imageio:wikkie.png") @ rgb_weights +img2 = iio.imread("imageio:astronaut.png") @ rgb_weights + +# Create a figure +figure = fpl.Figure( + shape=(1, 2), + size=(700, 560), + names=["image", "gaussian filtered image"] +) + +# create image graphics +image_raw = figure[0, 0].add_image(img1) +image_filt = figure[0, 1].add_image(gaussian_filter(img1, sigma=5)) + +# show the plot +figure.show() + + +# add event handler +@image_raw.add_event_handler("data") +def data_changed(ev: fpl.GraphicFeatureEvent): + # get the new image data + new_img = ev.info["value"] + + # set the filtered image graphic + image_filt.data = gaussian_filter(new_img, sigma=5) + + +# set the data on the first image graphic +# this will trigger the `data_changed()` handler to be called +image_raw.data = img2 + + +# 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/examples/events/key_events.py b/examples/events/key_events.py new file mode 100644 index 000000000..6979d44d7 --- /dev/null +++ b/examples/events/key_events.py @@ -0,0 +1,85 @@ +""" +Key Events +========== + +Move an image around using and change some of its properties using keyboard events. + +- Use the arrows keys to move the image by changing its offset + +- Press "v", "g", "p" to change the colormaps (viridis, grey, plasma). + +- Press "r" to rotate the image +18 degrees (pi / 10 radians) +- Press "Shift + R" to rotate the image -18 degrees +- Axis of rotation is the origin + +- Press "-", "=" to decrease/increase the vmin +- Press "_", "+" to decrease/increase the vmax + +We use the ImageWidget here because the histogram LUT tool makes it easy to see the changes in vmin and vmax. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import pygfx +import imageio.v3 as iio + +data = iio.imread("imageio:camera.png") + +iw = fpl.ImageWidget(data, figure_kwargs={"size": (700, 560)}) + +image = iw.managed_graphics[0] + + +@iw.figure.renderer.add_event_handler("key_down") +def handle_event(ev: pygfx.KeyboardEvent): + match ev.key: + # change the cmap + case "v": + image.cmap = "viridis" + case "g": + image.cmap = "grey" + case "p": + image.cmap = "plasma" + + # keys to change vmin/vmax + case "-": + image.vmin -= 1 + case "=": + image.vmin += 1 + case "_": + image.vmax -= 1 + case "+": + image.vmax += 1 + + # rotate + case "r": + image.rotate(np.pi / 10, axis="z") + case "R": + image.rotate(-np.pi / 10, axis="z") + + # arrow key events to move the image + case "ArrowUp": + image.offset = image.offset + [0, -10, 0] # remember y-axis is flipped for images + case "ArrowDown": + image.offset = image.offset + [0, 10, 0] + case "ArrowLeft": + image.offset = image.offset + [-10, 0, 0] + case "ArrowRight": + image.offset = image.offset + [10, 0, 0] + + +iw.show() + + +figure = iw.figure # ignore, this is just so the docs gallery scraper picks up the figure + + +# 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/examples/events/line_data_thickness_event.py b/examples/events/line_data_thickness_event.py new file mode 100644 index 000000000..4baaba42c --- /dev/null +++ b/examples/events/line_data_thickness_event.py @@ -0,0 +1,79 @@ +""" +Events line data thickness +========================== + +Simple example of adding event handlers for line data and thickness. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import fastplotlib as fpl +import numpy as np + +figure = fpl.Figure(size=(700, 560)) + +xs = np.linspace(0, 4 * np.pi, 100) +# sine wave +ys = np.sin(xs) +sine = np.column_stack([xs, ys]) + +# cosine wave +ys = np.cos(xs) +cosine = np.column_stack([xs, ys]) + +# create line graphics +sine_graphic = figure[0, 0].add_line(data=sine) +cosine_graphic = figure[0, 0].add_line(data=cosine, offset=(0, 4, 0)) + +# make a list of the line graphics for convenience +lines = [sine_graphic, cosine_graphic] + + +def change_thickness(ev: fpl.GraphicFeatureEvent): + # sets thickness of all the lines + new_value = ev.info["value"] + + for g in lines: + g.thickness = new_value + + +def change_data(ev: fpl.GraphicFeatureEvent): + # sets data of all the lines using the given event and value from the event + + # the user's slice/index + # This can be a single int index, a slice, + # or even a numpy array of int or bool for fancy indexing! + indices = ev.info["key"] + + # the new values to set at the given indices + new_values = ev.info["value"] + + # set the data for all the lines + for g in lines: + g.data[indices] = new_values + + +# add the event handlers to the line graphics +for g in lines: + g.add_event_handler(change_thickness, "thickness") + g.add_event_handler(change_data, "data") + + +figure.show() +figure[0, 0].axes.intersection = (0, 0, 0) + +# set the y-value of the middle 40 points of the sine graphic to 1 +# after the sine_graphic sets its data, the event handlers will be called +# and therefore the cosine graphic will also set its data using the event data +sine_graphic.data[30:70, 1] = np.ones(40) + +# set the thickness of the cosine graphic, this will trigger an event +# that causes the sine graphic's thickness to also be set from this value +cosine_graphic.thickness = 10 + +# 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/examples/events/lines_mouse_nearest.py b/examples/events/lines_mouse_nearest.py new file mode 100644 index 000000000..8c9601de6 --- /dev/null +++ b/examples/events/lines_mouse_nearest.py @@ -0,0 +1,62 @@ +""" +Highlight nearest circle +======================== + +Shows how to use the "pointer_move" event to get the nearest circle and highlight it. + +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +from itertools import product +import numpy as np +import fastplotlib as fpl +import pygfx + + +def make_circle(center, radius: float, n_points: int) -> np.ndarray: + theta = np.linspace(0, 2 * np.pi, n_points) + xs = radius * np.cos(theta) + ys = radius * np.sin(theta) + + return np.column_stack([xs, ys]) + center + +spatial_dims = (100, 100) + +circles = list() +for center in product(range(0, spatial_dims[0], 15), range(0, spatial_dims[1], 15)): + circles.append(make_circle(center, 5, n_points=75)) + +pos_xy = np.vstack(circles) + +figure = fpl.Figure(size=(700, 560)) + +line_collection = figure[0, 0].add_line_collection(circles, colors="w", thickness=5) + + +@figure.renderer.add_event_handler("pointer_move") +def highlight_nearest(ev: pygfx.PointerEvent): + line_collection.colors = "w" + + pos = figure[0, 0].map_screen_to_world(ev) + if pos is None: + return + + # get_nearest_graphics() is a helper function + # sorted the passed array or collection of graphics from nearest to furthest from the passed `pos` + nearest = fpl.utils.get_nearest_graphics(pos, line_collection)[0] + + nearest.colors = "r" + + +# remove clutter +figure[0, 0].axes.visible = False + +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/examples/events/paint_image.py b/examples/events/paint_image.py new file mode 100644 index 000000000..cfc2eda11 --- /dev/null +++ b/examples/events/paint_image.py @@ -0,0 +1,71 @@ +""" +Paint an Image +============== + +Click and drag the mouse to paint in the image +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import pygfx + +figure = fpl.Figure(size=(700, 560)) + +# add a blank image +image = figure[0, 0].add_image(np.zeros((100, 100)), vmin=0, vmax=255) + +painting = False # use to toggle painting state + + +@image.add_event_handler("pointer_down") +def on_pointer_down(ev: pygfx.PointerEvent): + # start painting when mouse button is down + global painting + + # get image element index, (x, y) pos corresponds to array (column, row) + col, row = ev.pick_info["index"] + + # increase value of this image element + image.data[row, col] = np.clip(image.data[row, col] + 50, 0, 255) + + # toggle on painting state + painting = True + + # disable controller until painting stops when mouse button is un-clicked + figure[0, 0].controller.enabled = False + + +@image.add_event_handler("pointer_move") +def on_pointer_move(ev: pygfx.PointerEvent): + # continue painting when mouse pointer is moved + global painting + + if not painting: + return + + # get image element index, (x, y) pos corresponds to array (column, row) + col, row = ev.pick_info["index"] + + image.data[row, col] = np.clip(image.data[row, col] + 50, 0, 255) + + +@figure.renderer.add_event_handler("pointer_up") +def on_pointer_up(ev: pygfx.PointerEvent): + # toggle off painting state + global painting + painting = False + + # re-enable controller + figure[0, 0].controller.enabled = True + + +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/examples/events/scatter_click.py b/examples/events/scatter_click.py new file mode 100644 index 000000000..e56dca743 --- /dev/null +++ b/examples/events/scatter_click.py @@ -0,0 +1,66 @@ +""" +Scatter click +============= + +Add an event handler to click on scatter points and highlight them, i.e. change the color and size of the clicked point. +Fly around the 3D scatter using WASD keys and click on points to highlight them +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import pygfx + +# make a gaussian cloud +data = np.random.normal(loc=0, scale=3, size=1500).reshape(500, 3) + +figure = fpl.Figure(cameras="3d", size=(700, 560)) + +scatter = figure[0, 0].add_scatter( + data, # the gaussian cloud + sizes=10, # some big points that are easy to click + cmap="viridis", + cmap_transform=np.linalg.norm(data, axis=1) # color points using distance from origin +) + +# simple dict to restore the original scatter color and size +# of the previously clicked point upon clicking a new point +old_props = {"index": None, "size": None, "color": None} + + +@scatter.add_event_handler("click") +def highlight_point(ev: pygfx.PointerEvent): + global old_props + + # the index of the point that was just clicked + new_index = ev.pick_info["vertex_index"] + + # restore old point's properties + if old_props["index"] is not None: + old_index = old_props["index"] + if new_index == old_index: + # same point was clicked, ignore + return + scatter.colors[old_index] = old_props["color"] + scatter.sizes[old_index] = old_props["size"] + + # store the current property values of this new point + old_props["index"] = new_index + old_props["color"] = scatter.colors[new_index].copy() # if you do not copy you will just get a view of the array! + old_props["size"] = scatter.sizes[new_index] + + # highlight this new point + scatter.colors[new_index] = "magenta" + scatter.sizes[new_index] = 20 + + +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/examples/events/scatter_hover.py b/examples/events/scatter_hover.py new file mode 100644 index 000000000..9d69dc24c --- /dev/null +++ b/examples/events/scatter_hover.py @@ -0,0 +1,69 @@ +""" +Scatter hover +============= + +Add an event handler to hover on scatter points and highlight them, i.e. change the color and size of the clicked point. +Fly around the 3D scatter using WASD keys and click on points to highlight them. + +There is no "hover" event, you can create a hover effect by using "pointer_move" events. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import fastplotlib as fpl +import pygfx + +# make a gaussian cloud +data = np.random.normal(loc=0, scale=3, size=1500).reshape(500, 3) + +figure = fpl.Figure(cameras="3d", size=(700, 560)) + +scatter = figure[0, 0].add_scatter( + data, # the gaussian cloud + sizes=10, # some big points that are easy to click + cmap="viridis", + cmap_transform=np.linalg.norm(data, axis=1) # color points using distance from origin +) + +# simple dict to restore the original scatter color and size +# of the previously clicked point upon clicking a new point +old_props = {"index": None, "size": None, "color": None} + + +@scatter.add_event_handler("pointer_move") +def highlight_point(ev: pygfx.PointerEvent): + global old_props + + # the index of the point that was just entered + new_index = ev.pick_info["vertex_index"] + + # if a new point has been entered, but we have not yet had + # a leave event for the previous point, then reset this old point + if old_props["index"] is not None: + old_index = old_props["index"] + if new_index == old_index: + # same point, ignore + return + scatter.colors[old_index] = old_props["color"] + scatter.sizes[old_index] = old_props["size"] + + # store the current property values of this new point + old_props["index"] = new_index + old_props["color"] = scatter.colors[new_index].copy() # if you do not copy you will just get a view of the array! + old_props["size"] = scatter.sizes[new_index] + + # highlight this new point + scatter.colors[new_index] = "magenta" + scatter.sizes[new_index] = 20 + + +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/examples/events/scatter_hover_transforms.py b/examples/events/scatter_hover_transforms.py new file mode 100644 index 000000000..7f9fbb9ff --- /dev/null +++ b/examples/events/scatter_hover_transforms.py @@ -0,0 +1,126 @@ +""" +Scatter data explore scalers +============================ + +Based on the sklearn preprocessing scalers example. Hover points to highlight the corresponding point of the dataset +transformed by the various scalers. + +See: https://scikit-learn.org/stable/auto_examples/preprocessing/plot_all_scaling.html + +This is another example that uses bi-directional events. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'screenshot' + +from sklearn.datasets import fetch_california_housing +from sklearn.preprocessing import ( + Normalizer, + QuantileTransformer, + PowerTransformer, +) + +import fastplotlib as fpl +import pygfx + +# get the dataset +dataset = fetch_california_housing() +X_full, y = dataset.data, dataset.target +feature_names = dataset.feature_names + +feature_mapping = { + "MedInc": "Median income in block", + "HouseAge": "Median house age in block", + "AveRooms": "Average number of rooms", + "AveBedrms": "Average number of bedrooms", + "Population": "Block population", + "AveOccup": "Average house occupancy", + "Latitude": "House block latitude", + "Longitude": "House block longitude", +} + +# Take only 2 features to make visualization easier +# Feature MedInc has a long tail distribution. +# Feature AveOccup has a few but very large outliers. +features = ["MedInc", "AveOccup"] +features_idx = [feature_names.index(feature) for feature in features] +X = X_full[:, features_idx] + +# list of our scalers and their names as strings +scalers = [PowerTransformer, QuantileTransformer, Normalizer] +names = ["Original Data", *[s.__name__ for s in scalers]] + +# fastplotlib code starts here, make a figure +figure = fpl.Figure( + shape=(2, 2), + names=names, + size=(700, 780), +) + +scatters = list() # list to store our 4 scatter graphics for convenience + +# add a scatter of the original data +s = figure["Original Data"].add_scatter( + data=X, + cmap="viridis", + cmap_transform=y, + sizes=3, +) + +# append to list of scatters +scatters.append(s) + +# add the scaled data as scatter graphics +for scaler in scalers: + name = scaler.__name__ + s = figure[name].add_scatter(scaler().fit_transform(X), cmap="viridis", cmap_transform=y, sizes=3) + scatters.append(s) + + +# simple dict to restore the original scatter color and size +# of the previously clicked point upon clicking a new point +old_props = {"index": None, "size": None, "color": None} + + +def highlight_point(ev: pygfx.PointerEvent): + # event handler to highlight the point when the mouse moves over it + global old_props + + # the index of the point that was just clicked + new_index = ev.pick_info["vertex_index"] + + # restore old point's properties + if old_props["index"] is not None: + old_index = old_props["index"] + if new_index == old_index: + # same point was clicked, ignore + return + for s in scatters: + s.colors[old_index] = old_props["color"] + s.sizes[old_index] = old_props["size"] + + # store the current property values of this new point + old_props["index"] = new_index + # all the scatters have the same colors and size for the corresponding index + # so we can just use the first scatter's original color and size + old_props["color"] = scatters[0].colors[new_index].copy() # if you do not copy you will just get a view of the array! + old_props["size"] = scatters[0].sizes[new_index] + + # highlight this new point + for s in scatters: + s.colors[new_index] = "magenta" + s.sizes[new_index] = 15 + + +# add the event handler to all the scatter graphics +for s in scatters: + s.add_event_handler(highlight_point, "pointer_move") + + +figure.show(maintain_aspect=False) + +# 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/examples/image_widget/image_widget_single_video.py b/examples/image_widget/image_widget_single_video.py index 3a0e94fca..aa601d3c1 100644 --- a/examples/image_widget/image_widget_single_video.py +++ b/examples/image_widget/image_widget_single_video.py @@ -20,7 +20,7 @@ movie_sub = movie[:15, ::12, ::12].copy() del movie -iw = fpl.ImageWidget(movie_sub, rgb=[True], figure_kwargs={"size": (700, 560)}) +iw = fpl.ImageWidget(movie_sub, rgb=True, figure_kwargs={"size": (700, 560)}) # ImageWidget supports setting window functions the `time` "t" or `volume` "z" dimension # These can also be given as kwargs to `ImageWidget` during instantiation diff --git a/examples/ipywidgets/README.rst b/examples/ipywidgets/README.rst new file mode 100644 index 000000000..3f6ae9d5f --- /dev/null +++ b/examples/ipywidgets/README.rst @@ -0,0 +1,2 @@ +Using with ipywidgets +===================== diff --git a/examples/ipywidgets/ipywidgets_modify_image.py b/examples/ipywidgets/ipywidgets_modify_image.py new file mode 100644 index 000000000..c0206e945 --- /dev/null +++ b/examples/ipywidgets/ipywidgets_modify_image.py @@ -0,0 +1,69 @@ +""" +ipwidgets modify an ImageGraphic +================================ + +Use ipywidgets to modify some features of an ImageGraphic. Run in jupyterlab. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'code' + +import fastplotlib as fpl +from scipy.ndimage import gaussian_filter +import imageio.v3 as iio +from ipywidgets import FloatRangeSlider, FloatSlider, Select, VBox + +data = iio.imread("imageio:moon.png") + +iw = fpl.ImageWidget(data, figure_kwargs={"size": (700, 560)}) + +# get the ImageGraphic from the image widget +image = iw.managed_graphics[0] + +min_v, max_v = fpl.utils.quick_min_max(data) + +# slider to adjust vmin, vmax of the image +vmin_vmax_slider = FloatRangeSlider(value=(image.vmin, image.vmax), min=min_v, max=max_v, description="vmin, vmax:") + +# slider to adjust sigma of a gaussian kernel used to filter the image (i.e. gaussian blur) +slider_sigma = FloatSlider(min=0.0, max=10.0, value=0.0, description="σ: ") + +# select box to choose the sample image shown in the ImageWidget +select_image = Select(options=["moon.png", "camera.png", "checkerboard.png"], description="image: ") + + +def update_vmin_vmax(change): + vmin, vmax = change["new"] + + image = iw.managed_graphics[0] + image.vmin, image.vmax = vmin, vmax + + +def update_sigma(change): + sigma = change["new"] + + # set a "frame apply" dict onto the ImageWidget + # this maps {image_index: function} + # the function is applied to the image data at the image index given by the key + iw.frame_apply = {0: lambda image_data: gaussian_filter(image_data, sigma=sigma)} + + +def update_image(change): + filename = change["new"] + data = iio.imread(f"imageio:{filename}") + + iw.set_data(data) + + # set vmin, vmax sliders w.r.t. this new image + image = iw.managed_graphics[0] + vmin_vmax_slider.value = image.vmin, image.vmax + vmin_vmax_slider.min, vmin_vmax_slider.max = fpl.utils.quick_min_max(data) + + +# connect the ipywidgets to the handler functions +vmin_vmax_slider.observe(update_vmin_vmax, "value") +slider_sigma.observe(update_sigma, "value") +select_image.observe(update_image, "value") + +# display in a vbox +VBox([iw.show(), vmin_vmax_slider, slider_sigma, select_image]) diff --git a/examples/ipywidgets/ipywidgets_sliders_line.py b/examples/ipywidgets/ipywidgets_sliders_line.py new file mode 100644 index 000000000..8288e5719 --- /dev/null +++ b/examples/ipywidgets/ipywidgets_sliders_line.py @@ -0,0 +1,91 @@ +""" +ipywidget sliders to modify a sine wave +======================================= + +Example with ipywidgets sliders to change a sine wave and view the frequency spectra. You can run this in jupyterlab +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'code' + +import numpy as np +import fastplotlib as fpl +from ipywidgets import FloatSlider, Checkbox, VBox + + +def generate_data(freq, duration, sampling_rate, ampl, noise_sigma): + # generate a sine wave using given params + xs = np.linspace(0, duration, sampling_rate * duration) + ys = np.sin((2 * np.pi) * freq * xs) * ampl + + noise = np.random.normal(scale=noise_sigma, size=sampling_rate * duration) + + signal = np.column_stack([xs, ys + noise]) + fft_mag = np.abs(np.fft.rfft(signal[:, 1])) + fft_freqs = np.linspace(0, sampling_rate / 2, num=fft_mag.shape[0]) + + return np.column_stack([xs, ys + noise]), np.column_stack([fft_freqs, fft_mag]) + + +signal, fft = generate_data( + freq=1, + duration=10, + sampling_rate=50, + ampl=1, + noise_sigma=0.05 +) + +# create a figure +figure = fpl.Figure(shape=(2, 1), names=["signal", "fft"], size=(700, 560)) + +# line graphic for the signal +signal_line = figure[0, 0].add_line(signal, thickness=1) + +# easier to understand the frequency of the sine wave if the +# axes go through the middle of the sine wave +figure[0, 0].axes.intersection = (0, 0, 0) + +# line graphic for fft +fft_line = figure[1, 0].add_line(fft) + +# do not maintain the aspect ratio of the fft subplot +figure[1, 0].camera.maintain_aspect = False + +# create ipywidget sliders +slider_freq = FloatSlider(min=0.1, max=10, value=1.0, step=0.1, description="freq: ") +slider_ampl = FloatSlider(min=0.0, max=10, value=1.0, step=0.5, description="ampl: ") +slider_noise = FloatSlider(min=0, max=1, value=0.05, step=0.05, description="noise: ") + +# checkbox +checkbox_autoscale = Checkbox(value=False, description="autoscale: ") + + +def update(*args): + # update whenever a slider changes + freq = slider_freq.value + ampl = slider_ampl.value + noise = slider_noise.value + + signal, fft = generate_data( + freq=freq, + duration=10, + sampling_rate=50, + ampl=ampl, + noise_sigma=noise, + ) + + signal_line.data[:, :-1] = signal + fft_line.data[:, :-1] = fft + + if checkbox_autoscale.value: + for subplot in figure: + subplot.auto_scale(maintain_aspect=False) + + +# when any one slider changes, it calls update +for slider in [slider_freq, slider_ampl, slider_noise]: + slider.observe(update, "value") + +# display the fastplotlib figure and ipywidgets in a VBox (vertically stacked) +# figure.show() just returns an ipywidget object +VBox([figure.show(), slider_freq, slider_ampl, slider_noise, checkbox_autoscale]) diff --git a/examples/screenshots/linear_region_selectors_match_offsets.png b/examples/screenshots/linear_region_selectors_match_offsets.png index 327f14e72..e6fab4c4d 100644 --- a/examples/screenshots/linear_region_selectors_match_offsets.png +++ b/examples/screenshots/linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8fac4f439b34a5464792588b77856f08c127c0ee06fa77722818f8d6b48dd64c -size 95433 +oid sha256:f2eac8ffeb8cd35a0c65d51b0952defea61928abb53c865e681fa72af4ac4347 +size 95750 diff --git a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png index 809908432..d82efa849 100644 --- a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png +++ b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:303d562f1a16f6a704415072d43ca08a51e12a702292b522e0f17f397b1aee60 -size 96668 +oid sha256:1b22ee4506bc532344cfcbd5daa0c4e90d9a831d59f1d916bd28534786947771 +size 97036 diff --git a/examples/selection_tools/linear_region_selector.py b/examples/selection_tools/linear_region_selector.py index 6fa17db38..bfbf27811 100644 --- a/examples/selection_tools/linear_region_selector.py +++ b/examples/selection_tools/linear_region_selector.py @@ -29,15 +29,15 @@ names=names, ) -# preallocated size for zoomed data -zoomed_prealloc = 1_000 +# preallocated number of datapoints for zoomed data +zoomed_prealloc = 5_000 # data to plot -xs = np.linspace(0, 10 * np.pi, 1_000) -ys = np.sin(xs) # y = sine(x) +xs = np.linspace(0, 200 * np.pi, 10_000) +ys = np.sin(xs) + np.random.normal(scale=0.2, size=10000) # make sine along x axis -sine_graphic_x = figure[0, 0].add_line(np.column_stack([xs, ys])) +sine_graphic_x = figure[0, 0].add_line(np.column_stack([xs, ys]), thickness=1) # x = sine(y), sine(y) > 0 = 0 sine_y = ys @@ -51,7 +51,7 @@ sine_graphic_y.position_y = 50 # add linear selectors -selector_x = sine_graphic_x.add_linear_region_selector() # default axis is "x" +selector_x = sine_graphic_x.add_linear_region_selector((0, 100)) # default axis is "x" selector_y = sine_graphic_y.add_linear_region_selector(axis="y") # preallocate array for storing zoomed in data @@ -102,8 +102,8 @@ def set_zoom_y(ev): selector_y.add_event_handler(set_zoom_y, "selection") # set initial selection -selector_x.selection = selector_y.selection = (0, 4 * np.pi) - +selector_x.selection = (0, 150) +selector_y.selection = (0, 150) figure.show(maintain_aspect=False) diff --git a/examples/tests/testutils.py b/examples/tests/testutils.py index d6fce52fe..4c23b3481 100644 --- a/examples/tests/testutils.py +++ b/examples/tests/testutils.py @@ -25,8 +25,9 @@ "line_collection/*.py", "gridplot/*.py", "window_layouts/*.py", - "misc/*.py", + "events/*.py", "selection_tools/*.py", + "misc/*.py", "guis/*.py", ] diff --git a/examples/text/README.rst b/examples/text/README.rst new file mode 100644 index 000000000..01466a39f --- /dev/null +++ b/examples/text/README.rst @@ -0,0 +1,2 @@ +Text Examples +============= diff --git a/examples/text/moving_label.py b/examples/text/moving_label.py new file mode 100644 index 000000000..45d2439ee --- /dev/null +++ b/examples/text/moving_label.py @@ -0,0 +1,84 @@ +""" +Moving TextGraphic label +======================== + +A TextGraphic that labels a point on a line and another TextGraphic that moves along the line on every draw. +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'animate 10s' + +import numpy as np +import fastplotlib as fpl + +# create a sinc wave +xs = np.linspace(-2 * np.pi, 2 * np.pi, 200) +ys = np.sinc(xs) + +data = np.column_stack([xs, ys]) + +# create a figure +figure = fpl.Figure(size=(700, 450)) + +# sinc wave +line = figure[0, 0].add_line(data, thickness=2) + +# position for the text label on the peak +pos = (0, max(ys), 0) + +# create label for the peak +text_peak = figure[0, 0].add_text( + f"peak ", + font_size=20, + anchor="bottom-right", + offset=pos +) + +# add a point on the peak +point_peak = figure[0, 0].add_scatter(np.asarray([pos]), sizes=10, colors="r") + +# create a text that will move along the line +text_moving = figure[0, 0].add_text( + f"({xs[0]:.2f}, {ys[0]:.2f}) ", + font_size=16, + outline_color="k", + outline_thickness=1, + anchor="top-center", + offset=(*data[0], 0) +) +# a point that will move on the line +point_moving = figure[0, 0].add_scatter(np.asarray([data[0]]), sizes=10, colors="magenta") + + +index = 0 +def update(): + # moves the text and point before every draw + global index + # get the new position + new_pos = (*data[index], 0) + + # move the text and point to the new position + text_moving.offset = new_pos + point_moving.data[0] = new_pos + + # set the text to the new position + text_moving.text = f"({new_pos[0]:.2f}, {new_pos[1]:.2f})" + + # increment index + index += 1 + if index == data.shape[0]: + index = 0 + + +# add update as an animation functions +figure.add_animations(update) + +figure[0, 0].axes.visible = False +figure.show(maintain_aspect=False) + + +# 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/__init__.py b/fastplotlib/__init__.py index 7eb9554e8..f1ec5daa8 100644 --- a/fastplotlib/__init__.py +++ b/fastplotlib/__init__.py @@ -3,6 +3,7 @@ # this must be the first import for auto-canvas detection from .utils import loop # noqa from .graphics import * +from .graphics.features import GraphicFeatureEvent from .graphics.selectors import * from .graphics.utils import pause_events from .legends import * diff --git a/fastplotlib/graphics/__init__.py b/fastplotlib/graphics/__init__.py index ff96baa4c..03f361502 100644 --- a/fastplotlib/graphics/__init__.py +++ b/fastplotlib/graphics/__init__.py @@ -1,3 +1,4 @@ +from ._base import Graphic from .line import LineGraphic from .scatter import ScatterGraphic from .image import ImageGraphic @@ -7,8 +8,8 @@ __all__ = [ "LineGraphic", - "ImageGraphic", "ScatterGraphic", + "ImageGraphic", "TextGraphic", "LineCollection", "LineStack", diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index 61ad291ee..e115107b0 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -16,7 +16,7 @@ import pygfx -from ._features import ( +from .features import ( BufferManager, Deleted, Name, @@ -50,7 +50,7 @@ class Graphic: - _features: set[str] = {} + _features: dict[str, type] = dict() def __init_subclass__(cls, **kwargs): # set the type of the graphic in lower case like "image", "line_collection", etc. @@ -63,12 +63,12 @@ def __init_subclass__(cls, **kwargs): # set of all features cls._features = { - *cls._features, - "name", - "offset", - "rotation", - "visible", - "deleted", + **cls._features, + "name": Name, + "offset": Offset, + "rotation": Rotation, + "visible": Visible, + "deleted": Deleted, } super().__init_subclass__(**kwargs) @@ -129,7 +129,7 @@ def __init__( @property def supported_events(self) -> tuple[str]: """events supported by this graphic""" - return (*tuple(self._features), *PYGFX_EVENTS) + return (*tuple(self._features.keys()), *PYGFX_EVENTS) @property def name(self) -> str | None: @@ -273,7 +273,7 @@ def decorator(_callback): # add to our record self._event_handlers[t].add(_callback) - if t in self._features: + if t in self._features.keys(): # fpl feature event feature = getattr(self, f"_{t}") feature.add_event_handler(_callback_wrapper) diff --git a/fastplotlib/graphics/_positions_base.py b/fastplotlib/graphics/_positions_base.py index 565a4cd98..5d98d16d1 100644 --- a/fastplotlib/graphics/_positions_base.py +++ b/fastplotlib/graphics/_positions_base.py @@ -4,7 +4,7 @@ import pygfx from ._base import Graphic -from ._features import ( +from .features import ( VertexPositions, VertexColors, UniformColor, @@ -58,7 +58,7 @@ def cmap(self, name: str): @property def size_space(self): """ - The coordinate space in which the size is expressed (‘screen’, ‘world’, ‘model’) + The coordinate space in which the size is expressed ('screen', 'world', 'model') See https://docs.pygfx.org/stable/_autosummary/utils/utils/enums/pygfx.utils.enums.CoordSpace.html#pygfx.utils.enums.CoordSpace for available options. """ diff --git a/fastplotlib/graphics/_features/__init__.py b/fastplotlib/graphics/features/__init__.py similarity index 96% rename from fastplotlib/graphics/_features/__init__.py rename to fastplotlib/graphics/features/__init__.py index a1915bbe9..18bcf5187 100644 --- a/fastplotlib/graphics/_features/__init__.py +++ b/fastplotlib/graphics/features/__init__.py @@ -19,7 +19,7 @@ from ._base import ( GraphicFeature, BufferManager, - FeatureEvent, + GraphicFeatureEvent, to_gpu_supported_dtype, ) @@ -67,4 +67,5 @@ "Rotation", "Visible", "Deleted", + "GraphicFeatureEvent", ] diff --git a/fastplotlib/graphics/_features/_base.py b/fastplotlib/graphics/features/_base.py similarity index 96% rename from fastplotlib/graphics/_features/_base.py rename to fastplotlib/graphics/features/_base.py index 1088dc005..d32904ae5 100644 --- a/fastplotlib/graphics/_features/_base.py +++ b/fastplotlib/graphics/features/_base.py @@ -1,5 +1,5 @@ from warnings import warn -from typing import Any, Literal +from typing import Literal import numpy as np from numpy.typing import NDArray @@ -23,7 +23,7 @@ def to_gpu_supported_dtype(array): return np.asarray(array).astype(np.float32) -class FeatureEvent(pygfx.Event): +class GraphicFeatureEvent(pygfx.Event): """ **All event instances have the following attributes** @@ -34,11 +34,11 @@ class FeatureEvent(pygfx.Event): +------------+-------------+-----------------------------------------------+ | graphic | Graphic | graphic instance that the event is from | +------------+-------------+-----------------------------------------------+ - | info | dict | event info dictionary (see below) | + | info | dict | event info dictionary | +------------+-------------+-----------------------------------------------+ | target | WorldObject | pygfx rendering engine object for the graphic | +------------+-------------+-----------------------------------------------+ - | time_stamp | float | time when the event occured, in ms | + | time_stamp | float | time when the event occurred, in ms | +------------+-------------+-----------------------------------------------+ """ @@ -57,7 +57,7 @@ def __init__(self, **kwargs): self._reentrant_block: bool = False @property - def value(self) -> Any: + def value(self): """Graphic Feature value, must be implemented in subclass""" raise NotImplemented @@ -120,7 +120,7 @@ def clear_event_handlers(self): """Clear all event handlers""" self._event_handlers.clear() - def _call_event_handlers(self, event_data: FeatureEvent): + def _call_event_handlers(self, event_data: GraphicFeatureEvent): if self._block_events: return @@ -310,7 +310,7 @@ def _emit_event(self, type: str, key, value): "key": key, "value": value, } - event = FeatureEvent(type, info=event_info) + event = GraphicFeatureEvent(type, info=event_info) self._call_event_handlers(event) diff --git a/fastplotlib/graphics/_features/_common.py b/fastplotlib/graphics/features/_common.py similarity index 53% rename from fastplotlib/graphics/_features/_common.py rename to fastplotlib/graphics/features/_common.py index e9c49a475..71e979f77 100644 --- a/fastplotlib/graphics/_features/_common.py +++ b/fastplotlib/graphics/features/_common.py @@ -1,12 +1,17 @@ import numpy as np -from ._base import GraphicFeature, FeatureEvent, block_reentrance +from ._base import GraphicFeature, GraphicFeatureEvent, block_reentrance class Name(GraphicFeature): - """Graphic name""" + property_name = "name" + event_info_spec = [ + {"dict key": "value", "type": "str", "description": "user provided name"}, + ] def __init__(self, value: str): + """Graphic name""" + self._value = value super().__init__() @@ -24,17 +29,29 @@ def set_value(self, graphic, value: str): self._value = value - event = FeatureEvent(type="name", info={"value": value}) + event = GraphicFeatureEvent(type="name", info={"value": value}) self._call_event_handlers(event) class Offset(GraphicFeature): - """Offset position of the graphic, [x, y, z]""" + property_name = "offset" + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray[float, float, float]", + "description": "new offset (x, y, z)", + }, + ] def __init__(self, value: np.ndarray | list | tuple): + """Offset position of the graphic, [x, y, z]""" + self._validate(value) - self._value = np.array(value) - self._value.flags.writeable = False + # initialize zeros array + self._value = np.zeros(3) + + # set values + self._value[:] = value super().__init__() def _validate(self, value): @@ -48,22 +65,38 @@ def value(self) -> np.ndarray: @block_reentrance def set_value(self, graphic, value: np.ndarray | list | tuple): self._validate(value) + value = np.asarray(value) graphic.world_object.world.position = value - self._value = graphic.world_object.world.position.copy() - self._value.flags.writeable = False - event = FeatureEvent(type="offset", info={"value": value}) + # sometimes there are transforms so get the final position value like this + value = graphic.world_object.world.position.copy() + + # set value of existing feature value array + self._value[:] = value + + event = GraphicFeatureEvent(type="offset", info={"value": value}) self._call_event_handlers(event) class Rotation(GraphicFeature): - """Graphic rotation quaternion""" + property_name = "offset" + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray[float, float, float, float]", + "description": "new rotation quaternion", + }, + ] def __init__(self, value: np.ndarray | list | tuple): + """Graphic rotation quaternion""" + self._validate(value) - self._value = np.array(value) - self._value.flags.writeable = False + # create zeros array + self._value = np.zeros(4) + + self._value[:] = value super().__init__() def _validate(self, value): @@ -79,18 +112,29 @@ def value(self) -> np.ndarray: @block_reentrance def set_value(self, graphic, value: np.ndarray | list | tuple): self._validate(value) + value = np.asarray(value) graphic.world_object.world.rotation = value - self._value = graphic.world_object.world.rotation.copy() - self._value.flags.writeable = False - event = FeatureEvent(type="rotation", info={"value": value}) + # get the actual final quaternion value, pygfx adjusts to make sure || q ||_2 == 1 + # i.e. pygfx checks to make sure norm 1 and other transforms + value = graphic.world_object.world.rotation.copy() + + # set value of existing feature value array + self._value[:] = value + + event = GraphicFeatureEvent(type="rotation", info={"value": value}) self._call_event_handlers(event) class Visible(GraphicFeature): """Access or change the visibility.""" + property_name = "offset" + event_info_spec = [ + {"dict key": "value", "type": "bool", "description": "new visibility bool"}, + ] + def __init__(self, value: bool): self._value = value super().__init__() @@ -104,7 +148,7 @@ def set_value(self, graphic, value: bool): graphic.world_object.visible = value self._value = value - event = FeatureEvent(type="visible", info={"value": value}) + event = GraphicFeatureEvent(type="visible", info={"value": value}) self._call_event_handlers(event) @@ -113,6 +157,15 @@ class Deleted(GraphicFeature): Used when a graphic is deleted, triggers events that can be useful to indicate this graphic has been deleted """ + property_name = "deleted" + event_info_spec = [ + { + "dict key": "value", + "type": "bool", + "description": "True when graphic was deleted", + }, + ] + def __init__(self, value: bool): self._value = value super().__init__() @@ -124,5 +177,5 @@ def value(self) -> bool: @block_reentrance def set_value(self, graphic, value: bool): self._value = value - event = FeatureEvent(type="deleted", info={"value": value}) + event = GraphicFeatureEvent(type="deleted", info={"value": value}) self._call_event_handlers(event) diff --git a/fastplotlib/graphics/_features/_image.py b/fastplotlib/graphics/features/_image.py similarity index 81% rename from fastplotlib/graphics/_features/_image.py rename to fastplotlib/graphics/features/_image.py index c0e2b28d2..c47a26e6a 100644 --- a/fastplotlib/graphics/_features/_image.py +++ b/fastplotlib/graphics/features/_image.py @@ -5,7 +5,7 @@ import numpy as np import pygfx -from ._base import GraphicFeature, FeatureEvent, block_reentrance +from ._base import GraphicFeature, GraphicFeatureEvent, block_reentrance from ...utils import ( make_colors, @@ -15,6 +15,19 @@ # manages an array of 8192x8192 Textures representing chunks of an image class TextureArray(GraphicFeature): + event_info_spec = [ + { + "dict key": "key", + "type": "slice, index, numpy-like fancy index", + "description": "key at which image data was sliced/fancy indexed", + }, + { + "dict key": "value", + "type": "np.ndarray | float", + "description": "new data values", + }, + ] + def __init__(self, data, isolated_buffer: bool = True): super().__init__() @@ -142,7 +155,7 @@ def __setitem__(self, key, value): for texture in self.buffer.ravel(): texture.update_range((0, 0, 0), texture.size) - event = FeatureEvent("data", info={"key": key, "value": value}) + event = GraphicFeatureEvent("data", info={"key": key, "value": value}) self._call_event_handlers(event) def __len__(self): @@ -152,6 +165,14 @@ def __len__(self): class ImageVmin(GraphicFeature): """lower contrast limit""" + event_info_spec = [ + { + "dict key": "value", + "type": "float", + "description": "new vmin value", + }, + ] + def __init__(self, value: float): self._value = value super().__init__() @@ -166,13 +187,21 @@ def set_value(self, graphic, value: float): graphic._material.clim = (value, vmax) self._value = value - event = FeatureEvent(type="vmin", info={"value": value}) + event = GraphicFeatureEvent(type="vmin", info={"value": value}) self._call_event_handlers(event) class ImageVmax(GraphicFeature): """upper contrast limit""" + event_info_spec = [ + { + "dict key": "value", + "type": "float", + "description": "new vmax value", + }, + ] + def __init__(self, value: float): self._value = value super().__init__() @@ -187,13 +216,21 @@ def set_value(self, graphic, value: float): graphic._material.clim = (vmin, value) self._value = value - event = FeatureEvent(type="vmax", info={"value": value}) + event = GraphicFeatureEvent(type="vmax", info={"value": value}) self._call_event_handlers(event) class ImageCmap(GraphicFeature): """colormap for texture""" + event_info_spec = [ + { + "dict key": "value", + "type": "str", + "description": "new cmap name", + }, + ] + def __init__(self, value: str): self._value = value self.texture = get_cmap_texture(value) @@ -210,13 +247,21 @@ def set_value(self, graphic, value: str): graphic._material.map.texture.update_range((0, 0, 0), size=(256, 1, 1)) self._value = value - event = FeatureEvent(type="cmap", info={"value": value}) + event = GraphicFeatureEvent(type="cmap", info={"value": value}) self._call_event_handlers(event) class ImageInterpolation(GraphicFeature): """Image interpolation method""" + event_info_spec = [ + { + "dict key": "value", + "type": "str", + "description": "new interpolation method, nearest | linear", + }, + ] + def __init__(self, value: str): self._validate(value) self._value = value @@ -237,13 +282,21 @@ def set_value(self, graphic, value: str): graphic._material.interpolation = value self._value = value - event = FeatureEvent(type="interpolation", info={"value": value}) + event = GraphicFeatureEvent(type="interpolation", info={"value": value}) self._call_event_handlers(event) class ImageCmapInterpolation(GraphicFeature): """Image cmap interpolation method""" + event_info_spec = [ + { + "dict key": "value", + "type": "str", + "description": "new cmap interpolatio method, nearest | linear", + }, + ] + def __init__(self, value: str): self._validate(value) self._value = value @@ -268,5 +321,5 @@ def set_value(self, graphic, value: str): graphic._material.map.mag_filter = value self._value = value - event = FeatureEvent(type="cmap_interpolation", info={"value": value}) + event = GraphicFeatureEvent(type="cmap_interpolation", info={"value": value}) self._call_event_handlers(event) diff --git a/fastplotlib/graphics/_features/_positions_graphics.py b/fastplotlib/graphics/features/_positions_graphics.py similarity index 75% rename from fastplotlib/graphics/_features/_positions_graphics.py rename to fastplotlib/graphics/features/_positions_graphics.py index 78e53f545..868701079 100644 --- a/fastplotlib/graphics/_features/_positions_graphics.py +++ b/fastplotlib/graphics/features/_positions_graphics.py @@ -9,7 +9,7 @@ from ._base import ( GraphicFeature, BufferManager, - FeatureEvent, + GraphicFeatureEvent, to_gpu_supported_dtype, block_reentrance, ) @@ -17,20 +17,24 @@ class VertexColors(BufferManager): - """ - - **info dict** - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | dict key | value type | value description | - +============+===========================================================+==================================================================================+ - | key | int | slice | np.ndarray[int | bool] | tuple[slice, ...] | key at which colors were indexed/sliced | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | value | np.ndarray | new color values for points that were changed, shape is [n_points_changed, RGBA] | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - | user_value | str | np.ndarray | tuple[float] | list[float] | list[str] | user input value that was parsed into the RGBA array | - +------------+-----------------------------------------------------------+----------------------------------------------------------------------------------+ - - """ + property_name = "colors" + event_info_spec = [ + { + "dict key": "key", + "type": "slice, index, numpy-like fancy index", + "description": "index/slice at which colors were indexed/sliced", + }, + { + "dict key": "value", + "type": "np.ndarray [n_points_changed, RGBA]", + "description": "new color values for points that were changed", + }, + { + "dict key": "user_value", + "type": "str or array-like", + "description": "user input value that was parsed into the RGBA array", + }, + ] def __init__( self, @@ -137,18 +141,28 @@ def __setitem__( "user_value": user_value, } - event = FeatureEvent("colors", info=event_info) + event = GraphicFeatureEvent("colors", info=event_info) self._call_event_handlers(event) def __len__(self): return len(self.buffer.data) -# Manages uniform color for line or scatter material class UniformColor(GraphicFeature): + property_name = "colors" + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray [RGBA]", + "description": "new color value", + }, + ] + def __init__( self, value: str | np.ndarray | tuple | list | pygfx.Color, alpha: float = 1.0 ): + """Manages uniform color for line or scatter material""" + v = (*tuple(pygfx.Color(value))[:-1], alpha) # apply alpha self._value = pygfx.Color(v) super().__init__() @@ -163,13 +177,19 @@ def set_value(self, graphic, value: str | np.ndarray | tuple | list | pygfx.Colo graphic.world_object.material.color = value self._value = value - event = FeatureEvent(type="colors", info={"value": value}) + event = GraphicFeatureEvent(type="colors", info={"value": value}) self._call_event_handlers(event) -# manages uniform size for scatter material class UniformSize(GraphicFeature): + property_name = "sizes" + event_info_spec = [ + {"dict key": "value", "type": "float", "description": "new size value"}, + ] + def __init__(self, value: int | float): + """Manages uniform size for scatter material""" + self._value = float(value) super().__init__() @@ -179,16 +199,27 @@ def value(self) -> float: @block_reentrance def set_value(self, graphic, value: float | int): - graphic.world_object.material.size = float(value) + value = float(value) + graphic.world_object.material.size = value self._value = value - event = FeatureEvent(type="sizes", info={"value": value}) + event = GraphicFeatureEvent(type="sizes", info={"value": value}) self._call_event_handlers(event) -# manages the coordinate space for scatter/line class SizeSpace(GraphicFeature): + property_name = "size_space" + event_info_spec = [ + { + "dict key": "value", + "type": "str", + "description": "'screen' | 'world' | 'model'", + }, + ] + def __init__(self, value: str): + """Manages the coordinate space for scatter/line graphic""" + self._value = value super().__init__() @@ -198,27 +229,35 @@ def value(self) -> str: @block_reentrance def set_value(self, graphic, value: str): + if value not in ["screen", "world", "model"]: + raise ValueError( + f"`size_space` must be one of: {['screen', 'world', 'model']}" + ) + if "Line" in graphic.world_object.material.__class__.__name__: graphic.world_object.material.thickness_space = value else: graphic.world_object.material.size_space = value self._value = value - event = FeatureEvent(type="size_space", info={"value": value}) + event = GraphicFeatureEvent(type="size_space", info={"value": value}) self._call_event_handlers(event) class VertexPositions(BufferManager): - """ - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - | dict key | value type | value description | - +==========+==========================================================+==========================================================================================+ - | key | int | slice | np.ndarray[int | bool] | tuple[slice, ...] | key at which vertex positions data were indexed/sliced | - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - | value | np.ndarray | float | list[float] | new data values for points that were changed, shape depends on the indices that were set | - +----------+----------------------------------------------------------+------------------------------------------------------------------------------------------+ - - """ + property_name = "data" + event_info_spec = [ + { + "dict key": "key", + "type": "slice, index (int) or numpy-like fancy index", + "description": "key at which vertex positions data were indexed/sliced", + }, + { + "dict key": "value", + "type": "int | float | array-like", + "description": "new data values for points that were changed", + }, + ] def __init__(self, data: Any, isolated_buffer: bool = True): """ @@ -268,15 +307,19 @@ def __len__(self): class PointsSizesFeature(BufferManager): - """ - +----------+-------------------------------------------------------------------+----------------------------------------------+ - | dict key | value type | value description | - +==========+===================================================================+==============================================+ - | key | int | slice | np.ndarray[int | bool] | list[int | bool] | key at which point sizes indexed/sliced | - +----------+-------------------------------------------------------------------+----------------------------------------------+ - | value | int | float | np.ndarray | list[int | float] | tuple[int | float] | new size values for points that were changed | - +----------+-------------------------------------------------------------------+----------------------------------------------+ - """ + property_name = "sizes" + event_info_spec = [ + { + "dict key": "key", + "type": "slice, index (int) or numpy-like fancy index", + "description": "key at which point sizes were indexed/sliced", + }, + { + "dict key": "value", + "type": "int | float | array-like", + "description": "new size values for points that were changed", + }, + ] def __init__( self, @@ -341,7 +384,10 @@ def __len__(self): class Thickness(GraphicFeature): - """line thickness""" + property_name = "thickness" + event_info_spec = [ + {"dict key": "value", "type": "float", "description": "new thickness value"}, + ] def __init__(self, value: float): self._value = value @@ -353,18 +399,28 @@ def value(self) -> float: @block_reentrance def set_value(self, graphic, value: float): + value = float(value) graphic.world_object.material.thickness = value self._value = value - event = FeatureEvent(type="thickness", info={"value": value}) + event = GraphicFeatureEvent(type="thickness", info={"value": value}) self._call_event_handlers(event) class VertexCmap(BufferManager): - """ - Sliceable colormap feature, manages a VertexColors instance and - provides a way to set colormaps with arbitrary transforms - """ + property_name = "cmap" + event_info_spec = [ + { + "dict key": "key", + "type": "slice", + "description": "key at cmap colors were sliced", + }, + { + "dict key": "value", + "type": "str", + "description": "new cmap to set at given slice", + }, + ] def __init__( self, @@ -373,6 +429,11 @@ def __init__( transform: np.ndarray | None, alpha: float = 1.0, ): + """ + Sliceable colormap feature, manages a VertexColors instance and + provides a way to set colormaps with arbitrary transforms + """ + super().__init__(data=vertex_colors.buffer) self._vertex_colors = vertex_colors @@ -405,12 +466,12 @@ def __setitem__(self, key: slice, cmap_name): if not isinstance(key, slice): raise TypeError( "fancy indexing not supported for VertexCmap, only slices " - "of a continuous are supported for apply a cmap" + "of a continuous range are supported for applying a cmap" ) if key.step is not None: raise TypeError( "step sized indexing not currently supported for setting VertexCmap, " - "slices must be a continuous region" + "slices must be a continuous range" ) # parse slice diff --git a/fastplotlib/graphics/_features/_selection_features.py b/fastplotlib/graphics/features/_selection_features.py similarity index 74% rename from fastplotlib/graphics/_features/_selection_features.py rename to fastplotlib/graphics/features/_selection_features.py index c157023b4..233353401 100644 --- a/fastplotlib/graphics/_features/_selection_features.py +++ b/fastplotlib/graphics/features/_selection_features.py @@ -3,28 +3,25 @@ import numpy as np from ...utils import mesh_masks -from ._base import GraphicFeature, FeatureEvent, block_reentrance +from ._base import GraphicFeature, GraphicFeatureEvent, block_reentrance class LinearSelectionFeature(GraphicFeature): - """ - **additional event attributes:** - - +--------------------+----------+------------------------------------+ - | attribute | type | description | - +====================+==========+====================================+ - | get_selected_index | callable | returns indices under the selector | - +--------------------+----------+------------------------------------+ - - **info dict:** - - +----------+------------+-------------------------------+ - | dict key | value type | value description | - +==========+============+===============================+ - | value | np.ndarray | new x or y value of selection | - +----------+------------+-------------------------------+ - - """ + event_info_spec = [ + { + "dict key": "value", + "type": "float", + "description": "new x or y value of selection", + }, + ] + + event_extra_attrs = [ + { + "attribute": "get_selected_index", + "type": "callable", + "description": "returns index under the selector", + } + ] def __init__(self, axis: str, value: float, limits: tuple[float, float]): """ @@ -71,33 +68,33 @@ def set_value(self, selector, value: float): self._value = value - event = FeatureEvent("selection", {"value": value}) + event = GraphicFeatureEvent("selection", {"value": value}) event.get_selected_index = selector.get_selected_index self._call_event_handlers(event) class LinearRegionSelectionFeature(GraphicFeature): - """ - **additional event attributes:** - - +----------------------+----------+------------------------------------+ - | attribute | type | description | - +======================+==========+====================================+ - | get_selected_indices | callable | returns indices under the selector | - +----------------------+----------+------------------------------------+ - | get_selected_data | callable | returns data under the selector | - +----------------------+----------+------------------------------------+ - - **info dict:** - - +----------+------------+-----------------------------+ - | dict key | value type | value description | - +==========+============+=============================+ - | value | np.ndarray | new [min, max] of selection | - +----------+------------+-----------------------------+ - - """ + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray", + "description": "new [min, max] of selection", + }, + ] + + event_extra_attrs = [ + { + "attribute": "get_selected_indices", + "type": "callable", + "description": "returns indices under the selector", + }, + { + "attribute": "get_selected_data", + "type": "callable", + "description": "returns data under the selector", + }, + ] def __init__(self, value: tuple[int, int], axis: str, limits: tuple[float, float]): super().__init__() @@ -183,7 +180,7 @@ def set_value(self, selector, value: Sequence[float]): if len(self._event_handlers) < 1: return - event = FeatureEvent("selection", {"value": self.value}) + event = GraphicFeatureEvent("selection", {"value": self.value}) event.get_selected_indices = selector.get_selected_indices event.get_selected_data = selector.get_selected_data @@ -195,26 +192,26 @@ def set_value(self, selector, value: Sequence[float]): class RectangleSelectionFeature(GraphicFeature): - """ - **additional event attributes:** - - +----------------------+----------+------------------------------------+ - | attribute | type | description | - +======================+==========+====================================+ - | get_selected_indices | callable | returns indices under the selector | - +----------------------+----------+------------------------------------+ - | get_selected_data | callable | returns data under the selector | - +----------------------+----------+------------------------------------+ - - **info dict:** - - +----------+------------+-------------------------------------------+ - | dict key | value type | value description | - +==========+============+===========================================+ - | value | np.ndarray | new [xmin, xmax, ymin, ymax] of selection | - +----------+------------+-------------------------------------------+ - - """ + event_info_spec = [ + { + "dict key": "value", + "type": "np.ndarray", + "description": "new [xmin, xmax, ymin, ymax] of selection", + }, + ] + + event_extra_attrs = [ + { + "attribute": "get_selected_indices", + "type": "callable", + "description": "returns indices under the selector", + }, + { + "attribute": "get_selected_data", + "type": "callable", + "description": "returns data under the selector", + }, + ] def __init__( self, @@ -336,7 +333,7 @@ def set_value(self, selector, value: Sequence[float]): if len(self._event_handlers) < 1: return - event = FeatureEvent("selection", {"value": self.value}) + event = GraphicFeatureEvent("selection", {"value": self.value}) event.get_selected_indices = selector.get_selected_indices event.get_selected_data = selector.get_selected_data diff --git a/fastplotlib/graphics/_features/_text.py b/fastplotlib/graphics/features/_text.py similarity index 65% rename from fastplotlib/graphics/_features/_text.py rename to fastplotlib/graphics/features/_text.py index a95fe256c..d8e5e95e8 100644 --- a/fastplotlib/graphics/_features/_text.py +++ b/fastplotlib/graphics/features/_text.py @@ -2,10 +2,18 @@ import pygfx -from ._base import GraphicFeature, FeatureEvent, block_reentrance +from ._base import GraphicFeature, GraphicFeatureEvent, block_reentrance class TextData(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "str", + "description": "new text data", + }, + ] + def __init__(self, value: str): self._value = value super().__init__() @@ -19,11 +27,19 @@ def set_value(self, graphic, value: str): graphic.world_object.set_text(value) self._value = value - event = FeatureEvent(type="text", info={"value": value}) + event = GraphicFeatureEvent(type="text", info={"value": value}) self._call_event_handlers(event) class FontSize(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "float | int", + "description": "new font size", + }, + ] + def __init__(self, value: float | int): self._value = value super().__init__() @@ -37,11 +53,19 @@ def set_value(self, graphic, value: float | int): graphic.world_object.font_size = value self._value = graphic.world_object.font_size - event = FeatureEvent(type="font_size", info={"value": value}) + event = GraphicFeatureEvent(type="font_size", info={"value": value}) self._call_event_handlers(event) class TextFaceColor(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "str | np.ndarray", + "description": "new text color", + }, + ] + def __init__(self, value: str | np.ndarray | list[float] | tuple[float]): self._value = pygfx.Color(value) super().__init__() @@ -56,11 +80,19 @@ def set_value(self, graphic, value: str | np.ndarray | list[float] | tuple[float graphic.world_object.material.color = value self._value = graphic.world_object.material.color - event = FeatureEvent(type="face_color", info={"value": value}) + event = GraphicFeatureEvent(type="face_color", info={"value": value}) self._call_event_handlers(event) class TextOutlineColor(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "str | np.ndarray", + "description": "new outline color", + }, + ] + def __init__(self, value: str | np.ndarray | list[float] | tuple[float]): self._value = pygfx.Color(value) super().__init__() @@ -75,11 +107,19 @@ def set_value(self, graphic, value: str | np.ndarray | list[float] | tuple[float graphic.world_object.material.outline_color = value self._value = graphic.world_object.material.outline_color - event = FeatureEvent(type="outline_color", info={"value": value}) + event = GraphicFeatureEvent(type="outline_color", info={"value": value}) self._call_event_handlers(event) class TextOutlineThickness(GraphicFeature): + event_info_spec = [ + { + "dict key": "value", + "type": "float", + "description": "new text outline thickness", + }, + ] + def __init__(self, value: float): self._value = value super().__init__() @@ -93,5 +133,5 @@ def set_value(self, graphic, value: float): graphic.world_object.material.outline_thickness = value self._value = graphic.world_object.material.outline_thickness - event = FeatureEvent(type="outline_thickness", info={"value": value}) + event = GraphicFeatureEvent(type="outline_thickness", info={"value": value}) self._call_event_handlers(event) diff --git a/fastplotlib/graphics/_features/utils.py b/fastplotlib/graphics/features/utils.py similarity index 100% rename from fastplotlib/graphics/_features/utils.py rename to fastplotlib/graphics/features/utils.py diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py index 8b937023b..5f198c84f 100644 --- a/fastplotlib/graphics/image.py +++ b/fastplotlib/graphics/image.py @@ -6,7 +6,7 @@ from ..utils import quick_min_max from ._base import Graphic from .selectors import LinearSelector, LinearRegionSelector, RectangleSelector -from ._features import ( +from .features import ( TextureArray, ImageCmap, ImageVmin, @@ -71,7 +71,14 @@ def chunk_index(self) -> tuple[int, int]: class ImageGraphic(Graphic): - _features = {"data", "cmap", "vmin", "vmax", "interpolation", "cmap_interpolation"} + _features = { + "data": TextureArray, + "cmap": ImageCmap, + "vmin": ImageVmin, + "vmax": ImageVmax, + "interpolation": ImageInterpolation, + "cmap_interpolation": ImageCmapInterpolation, + } def __init__( self, diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py index 489c64930..d02297c64 100644 --- a/fastplotlib/graphics/line.py +++ b/fastplotlib/graphics/line.py @@ -6,11 +6,25 @@ from ._positions_base import PositionsGraphic from .selectors import LinearRegionSelector, LinearSelector, RectangleSelector -from ._features import Thickness, SizeSpace +from .features import ( + Thickness, + VertexPositions, + VertexColors, + UniformColor, + VertexCmap, + SizeSpace, +) +from ..utils import quick_min_max class LineGraphic(PositionsGraphic): - _features = {"data", "colors", "cmap", "thickness", "size_space"} + _features = { + "data": VertexPositions, + "colors": (VertexColors, UniformColor), + "cmap": (VertexCmap, None), # none if UniformColor + "thickness": Thickness, + "size_space": SizeSpace, + } def __init__( self, @@ -298,6 +312,6 @@ def _get_linear_selector_init_args( size = int(np.ptp(magn_vals) * 1.5 + padding) # center of selector along the other axis - center = np.nanmean(magn_vals) + center = sum(quick_min_max(magn_vals)) / 2 return bounds_init, limits, size, center diff --git a/fastplotlib/graphics/scatter.py b/fastplotlib/graphics/scatter.py index 189af4844..a8479bbf6 100644 --- a/fastplotlib/graphics/scatter.py +++ b/fastplotlib/graphics/scatter.py @@ -4,11 +4,25 @@ import pygfx from ._positions_base import PositionsGraphic -from ._features import PointsSizesFeature, UniformSize, SizeSpace +from .features import ( + PointsSizesFeature, + UniformSize, + SizeSpace, + VertexPositions, + VertexColors, + UniformColor, + VertexCmap, +) class ScatterGraphic(PositionsGraphic): - _features = {"data", "sizes", "colors", "cmap", "size_space"} + _features = { + "data": VertexPositions, + "sizes": (PointsSizesFeature, UniformSize), + "colors": (VertexColors, UniformColor), + "cmap": (VertexCmap, None), + "size_space": SizeSpace, + } def __init__( self, diff --git a/fastplotlib/graphics/selectors/_base_selector.py b/fastplotlib/graphics/selectors/_base_selector.py index 5158a9239..629c063bc 100644 --- a/fastplotlib/graphics/selectors/_base_selector.py +++ b/fastplotlib/graphics/selectors/_base_selector.py @@ -35,8 +35,6 @@ class MoveInfo: # Selector base class class BaseSelector(Graphic): - _features = {"selection"} - @property def axis(self) -> str: return self._axis diff --git a/fastplotlib/graphics/selectors/_linear.py b/fastplotlib/graphics/selectors/_linear.py index fe57036a3..7ac0fc761 100644 --- a/fastplotlib/graphics/selectors/_linear.py +++ b/fastplotlib/graphics/selectors/_linear.py @@ -7,11 +7,13 @@ from .._base import Graphic from .._collection_base import GraphicCollection -from .._features._selection_features import LinearSelectionFeature +from ..features._selection_features import LinearSelectionFeature from ._base_selector import BaseSelector class LinearSelector(BaseSelector): + _features = {"selection": LinearSelectionFeature} + @property def parent(self) -> Graphic: return self._parent diff --git a/fastplotlib/graphics/selectors/_linear_region.py b/fastplotlib/graphics/selectors/_linear_region.py index c1e6095f8..1bc3efc2c 100644 --- a/fastplotlib/graphics/selectors/_linear_region.py +++ b/fastplotlib/graphics/selectors/_linear_region.py @@ -6,11 +6,13 @@ from .._base import Graphic from .._collection_base import GraphicCollection -from .._features._selection_features import LinearRegionSelectionFeature +from ..features._selection_features import LinearRegionSelectionFeature from ._base_selector import BaseSelector class LinearRegionSelector(BaseSelector): + _features = {"selection": LinearRegionSelectionFeature} + @property def parent(self) -> Graphic | None: """graphic that the selector is associated with""" diff --git a/fastplotlib/graphics/selectors/_rectangle.py b/fastplotlib/graphics/selectors/_rectangle.py index 51c3209b1..8d0af8e88 100644 --- a/fastplotlib/graphics/selectors/_rectangle.py +++ b/fastplotlib/graphics/selectors/_rectangle.py @@ -7,11 +7,13 @@ from .._collection_base import GraphicCollection from .._base import Graphic -from .._features import RectangleSelectionFeature +from ..features import RectangleSelectionFeature from ._base_selector import BaseSelector class RectangleSelector(BaseSelector): + _features = {"selection": RectangleSelectionFeature} + @property def parent(self) -> Graphic | None: """Graphic that selector is associated with.""" diff --git a/fastplotlib/graphics/text.py b/fastplotlib/graphics/text.py index e3794743a..70f9b2a43 100644 --- a/fastplotlib/graphics/text.py +++ b/fastplotlib/graphics/text.py @@ -2,7 +2,7 @@ import numpy as np from ._base import Graphic -from ._features import ( +from .features import ( TextData, FontSize, TextFaceColor, @@ -13,11 +13,11 @@ class TextGraphic(Graphic): _features = { - "text", - "font_size", - "face_color", - "outline_color", - "outline_thickness", + "text": TextData, + "font_size": FontSize, + "face_color": TextFaceColor, + "outline_color": TextOutlineColor, + "outline_thickness": TextOutlineThickness, } def __init__( diff --git a/fastplotlib/layouts/__init__.py b/fastplotlib/layouts/__init__.py index 8fb1d54d8..23839586c 100644 --- a/fastplotlib/layouts/__init__.py +++ b/fastplotlib/layouts/__init__.py @@ -1,4 +1,5 @@ from ._figure import Figure +from ._subplot import Subplot from ._utils import IMGUI if IMGUI: diff --git a/fastplotlib/legends/legend.py b/fastplotlib/legends/legend.py index df78d5662..69a556109 100644 --- a/fastplotlib/legends/legend.py +++ b/fastplotlib/legends/legend.py @@ -5,8 +5,8 @@ import numpy as np import pygfx -from ..graphics._base import Graphic -from ..graphics._features._base import FeatureEvent +from ..graphics import Graphic +from ..graphics.features import GraphicFeatureEvent from ..graphics import LineGraphic, ScatterGraphic, ImageGraphic from ..utils import mesh_masks @@ -116,7 +116,7 @@ def label(self, text: str): self._parent._check_label_unique(text) self._label_world_object.geometry.set_text(text) - def _update_color(self, ev: FeatureEvent): + def _update_color(self, ev: GraphicFeatureEvent): new_color = ev.info["value"] if np.unique(new_color, axis=0).shape[0] > 1: raise ValueError( diff --git a/fastplotlib/utils/_plot_helpers.py b/fastplotlib/utils/_plot_helpers.py index 5a39b76d0..12afe1cb2 100644 --- a/fastplotlib/utils/_plot_helpers.py +++ b/fastplotlib/utils/_plot_helpers.py @@ -36,10 +36,12 @@ def get_nearest_graphics_indices( if not all(isinstance(g, Graphic) for g in graphics): raise TypeError("all elements of `graphics` must be Graphic objects") - pos = np.asarray(pos) + pos = np.asarray(pos).ravel() - if pos.shape != (2,) or not pos.shape != (3,): - raise TypeError + if pos.shape != (2,) and pos.shape != (3,): + raise TypeError( + f"pos.shape must be (2,) or (3,), the shape of pos you have passed is: {pos.shape}" + ) # get centers centers = np.empty(shape=(len(graphics), len(pos))) diff --git a/tests/events.py b/tests/events.py index ea160dec3..e9b212adb 100644 --- a/tests/events.py +++ b/tests/events.py @@ -5,7 +5,7 @@ import pygfx import fastplotlib as fpl -from fastplotlib.graphics._features import FeatureEvent +from fastplotlib.graphics.features import GraphicFeatureEvent def make_positions_data() -> np.ndarray: @@ -22,7 +22,7 @@ def make_scatter_graphic() -> fpl.ScatterGraphic: return fpl.ScatterGraphic(make_positions_data()) -event_instance: FeatureEvent = None +event_instance: GraphicFeatureEvent = None def event_handler(event): @@ -30,7 +30,7 @@ def event_handler(event): event_instance = event -decorated_event_instance: FeatureEvent = None +decorated_event_instance: GraphicFeatureEvent = None @pytest.mark.parametrize("graphic", [make_line_graphic(), make_scatter_graphic()]) @@ -42,7 +42,7 @@ def test_positions_data_event(graphic: fpl.LineGraphic | fpl.ScatterGraphic): info = {"key": (slice(3, 8, None), 1), "value": value} - expected = FeatureEvent(type="data", info=info) + expected = GraphicFeatureEvent(type="data", info=info) def validate(graphic, handler, expected_feature_event, event_to_test): assert expected_feature_event.type == event_to_test.type diff --git a/tests/test_colors_buffer_manager.py b/tests/test_colors_buffer_manager.py index 8a6c5700f..7b1aef16a 100644 --- a/tests/test_colors_buffer_manager.py +++ b/tests/test_colors_buffer_manager.py @@ -5,7 +5,7 @@ import pygfx import fastplotlib as fpl -from fastplotlib.graphics._features import VertexColors, FeatureEvent +from fastplotlib.graphics.features import VertexColors, GraphicFeatureEvent from .utils import ( generate_slice_indices, generate_color_inputs, @@ -18,7 +18,7 @@ def make_colors_buffer() -> VertexColors: return colors -EVENT_RETURN_VALUE: FeatureEvent = None +EVENT_RETURN_VALUE: GraphicFeatureEvent = None def event_handler(ev): @@ -65,7 +65,7 @@ def test_int(test_graphic): if test_graphic: # test event - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object assert EVENT_RETURN_VALUE.info["key"] == 3 @@ -120,7 +120,7 @@ def test_tuple(test_graphic, slice_method): if test_graphic: # test event - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object assert EVENT_RETURN_VALUE.info["key"] == (s, slice(None)) @@ -142,7 +142,7 @@ def test_tuple(test_graphic, slice_method): if test_graphic: # test event - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object assert EVENT_RETURN_VALUE.info["key"] == slice(None) @@ -218,7 +218,7 @@ def test_slice(color_input, slice_method: dict, test_graphic: bool): if test_graphic: global EVENT_RETURN_VALUE - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object if isinstance(s, slice): diff --git a/tests/test_common_features.py b/tests/test_common_features.py index 332ac71ae..5671478a7 100644 --- a/tests/test_common_features.py +++ b/tests/test_common_features.py @@ -4,7 +4,7 @@ import pytest import fastplotlib as fpl -from fastplotlib.graphics._features import FeatureEvent, Name, Offset, Rotation, Visible +from fastplotlib.graphics.features import GraphicFeatureEvent, Name, Offset, Rotation, Visible def make_graphic(kind: str, **kwargs): @@ -29,11 +29,11 @@ def make_graphic(kind: str, **kwargs): ] -RETURN_EVENT_VALUE: FeatureEvent = None -DECORATED_EVENT_VALUE: FeatureEvent = None +RETURN_EVENT_VALUE: GraphicFeatureEvent = None +DECORATED_EVENT_VALUE: GraphicFeatureEvent = None -def return_event(ev: FeatureEvent): +def return_event(ev: GraphicFeatureEvent): global RETURN_EVENT_VALUE RETURN_EVENT_VALUE = ev @@ -138,7 +138,7 @@ def decorated_handler(ev): assert DECORATED_EVENT_VALUE.type == "offset" assert DECORATED_EVENT_VALUE.graphic is graphic assert DECORATED_EVENT_VALUE.target is graphic.world_object - assert DECORATED_EVENT_VALUE.info["value"] == (7.0, 8.0, 9.0) + npt.assert_almost_equal(DECORATED_EVENT_VALUE.info["value"], (7.0, 8.0, 9.0)) @pytest.mark.parametrize( @@ -202,7 +202,7 @@ def decorated_handler(ev): assert DECORATED_EVENT_VALUE.type == "rotation" assert DECORATED_EVENT_VALUE.graphic is graphic assert DECORATED_EVENT_VALUE.target is graphic.world_object - assert DECORATED_EVENT_VALUE.info["value"] == (0, 0, 0.6, 0.8) + npt.assert_almost_equal(DECORATED_EVENT_VALUE.info["value"], (0, 0, 0.6, 0.8)) @pytest.mark.parametrize( diff --git a/tests/test_image_graphic.py b/tests/test_image_graphic.py index 02b982d80..f2d87860b 100644 --- a/tests/test_image_graphic.py +++ b/tests/test_image_graphic.py @@ -5,7 +5,7 @@ import pygfx import fastplotlib as fpl -from fastplotlib.graphics._features import FeatureEvent +from fastplotlib.graphics.features import GraphicFeatureEvent from fastplotlib.utils import make_colors GRAY_IMAGE = iio.imread("imageio:camera.png") @@ -18,7 +18,7 @@ # new screenshot tests too for these when in graphics -EVENT_RETURN_VALUE: FeatureEvent = None +EVENT_RETURN_VALUE: GraphicFeatureEvent = None def event_handler(ev): @@ -28,7 +28,7 @@ def event_handler(ev): def check_event(graphic, feature, value): global EVENT_RETURN_VALUE - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.type == feature assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target == graphic.world_object @@ -58,7 +58,7 @@ def check_set_slice( npt.assert_almost_equal(data_values[:, col_slice.stop :], data[:, col_slice.stop :]) global EVENT_RETURN_VALUE - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.type == "data" assert EVENT_RETURN_VALUE.graphic == image_graphic assert EVENT_RETURN_VALUE.target == image_graphic.world_object diff --git a/tests/test_positions_data_buffer_manager.py b/tests/test_positions_data_buffer_manager.py index 77d049ab5..18a7b36e8 100644 --- a/tests/test_positions_data_buffer_manager.py +++ b/tests/test_positions_data_buffer_manager.py @@ -3,14 +3,14 @@ import pytest import fastplotlib as fpl -from fastplotlib.graphics._features import VertexPositions, FeatureEvent +from fastplotlib.graphics.features import VertexPositions, GraphicFeatureEvent from .utils import ( generate_slice_indices, generate_positions_spiral_data, ) -EVENT_RETURN_VALUE: FeatureEvent = None +EVENT_RETURN_VALUE: GraphicFeatureEvent = None def event_handler(ev): @@ -72,7 +72,7 @@ def test_int(test_graphic): # check event if test_graphic: - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object assert EVENT_RETURN_VALUE.info["key"] == 2 @@ -87,7 +87,7 @@ def test_int(test_graphic): # check event if test_graphic: - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object assert EVENT_RETURN_VALUE.info["key"] == slice(None) @@ -148,7 +148,7 @@ def test_slice(test_graphic, slice_method: dict, test_axis: str): # check event if test_graphic: - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object if isinstance(s, slice): @@ -172,7 +172,7 @@ def test_slice(test_graphic, slice_method: dict, test_axis: str): # check event if test_graphic: - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object if isinstance(s, slice): @@ -191,7 +191,7 @@ def test_slice(test_graphic, slice_method: dict, test_axis: str): # check event if test_graphic: - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target is graphic.world_object if isinstance(s, slice): diff --git a/tests/test_positions_graphics.py b/tests/test_positions_graphics.py index b76ece2ca..ed791b6fa 100644 --- a/tests/test_positions_graphics.py +++ b/tests/test_positions_graphics.py @@ -5,7 +5,7 @@ import pygfx import fastplotlib as fpl -from fastplotlib.graphics._features import ( +from fastplotlib.graphics.features import ( VertexPositions, VertexColors, VertexCmap, @@ -13,7 +13,7 @@ UniformSize, PointsSizesFeature, Thickness, - FeatureEvent, + GraphicFeatureEvent, ) from .utils import ( @@ -58,7 +58,7 @@ } -EVENT_RETURN_VALUE: FeatureEvent = None +EVENT_RETURN_VALUE: GraphicFeatureEvent = None def event_handler(ev): diff --git a/tests/test_sizes_buffer_manager.py b/tests/test_sizes_buffer_manager.py index 1d0a17f3d..2f55eab27 100644 --- a/tests/test_sizes_buffer_manager.py +++ b/tests/test_sizes_buffer_manager.py @@ -2,7 +2,7 @@ from numpy import testing as npt import pytest -from fastplotlib.graphics._features import PointsSizesFeature +from fastplotlib.graphics.features import PointsSizesFeature from .utils import generate_slice_indices diff --git a/tests/test_text_graphic.py b/tests/test_text_graphic.py index deb25ca6b..ec3d0be54 100644 --- a/tests/test_text_graphic.py +++ b/tests/test_text_graphic.py @@ -1,8 +1,8 @@ from numpy import testing as npt import fastplotlib as fpl -from fastplotlib.graphics._features import ( - FeatureEvent, +from fastplotlib.graphics.features import ( + GraphicFeatureEvent, TextData, FontSize, TextFaceColor, @@ -40,7 +40,7 @@ def test_create_graphic(): assert text.world_object.material.outline_thickness == 0 -EVENT_RETURN_VALUE: FeatureEvent = None +EVENT_RETURN_VALUE: GraphicFeatureEvent = None def event_handler(ev): @@ -50,7 +50,7 @@ def event_handler(ev): def check_event(graphic, feature, value): global EVENT_RETURN_VALUE - assert isinstance(EVENT_RETURN_VALUE, FeatureEvent) + assert isinstance(EVENT_RETURN_VALUE, GraphicFeatureEvent) assert EVENT_RETURN_VALUE.type == feature assert EVENT_RETURN_VALUE.graphic == graphic assert EVENT_RETURN_VALUE.target == graphic.world_object diff --git a/tests/test_texture_array.py b/tests/test_texture_array.py index c85fc7652..6220f2fe5 100644 --- a/tests/test_texture_array.py +++ b/tests/test_texture_array.py @@ -5,7 +5,7 @@ import pygfx import fastplotlib as fpl -from fastplotlib.graphics._features import TextureArray +from fastplotlib.graphics.features import TextureArray from fastplotlib.graphics.image import _ImageTile