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 pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy