From 1fdd891b4325663d87a248454bf35c98832eced8 Mon Sep 17 00:00:00 2001 From: DavidVFiumano Date: Sat, 15 Jul 2023 16:25:27 -0400 Subject: [PATCH 1/9] Added a size feature. Not sure if it works yet, still need to add tests. --- fastplotlib/graphics/_features/__init__.py | 2 + fastplotlib/graphics/_features/_base.py | 14 +++ fastplotlib/graphics/_features/_sizes.py | 101 +++++++++++++++++++++ fastplotlib/graphics/scatter.py | 23 +---- 4 files changed, 122 insertions(+), 18 deletions(-) diff --git a/fastplotlib/graphics/_features/__init__.py b/fastplotlib/graphics/_features/__init__.py index 8e78a6260..a6ce9c3a3 100644 --- a/fastplotlib/graphics/_features/__init__.py +++ b/fastplotlib/graphics/_features/__init__.py @@ -1,5 +1,6 @@ from ._colors import ColorFeature, CmapFeature, ImageCmapFeature, HeatmapCmapFeature from ._data import PointsDataFeature, ImageDataFeature, HeatmapDataFeature +from ._sizes import PointsSizesFeature from ._present import PresentFeature from ._thickness import ThicknessFeature from ._base import GraphicFeature, GraphicFeatureIndexable, FeatureEvent, to_gpu_supported_dtype @@ -11,6 +12,7 @@ "ImageCmapFeature", "HeatmapCmapFeature", "PointsDataFeature", + "PointsSizesFeature", "ImageDataFeature", "HeatmapDataFeature", "PresentFeature", diff --git a/fastplotlib/graphics/_features/_base.py b/fastplotlib/graphics/_features/_base.py index 5616eec19..e166318db 100644 --- a/fastplotlib/graphics/_features/_base.py +++ b/fastplotlib/graphics/_features/_base.py @@ -299,6 +299,20 @@ def cleanup_array_slice(key: np.ndarray, upper_bound) -> Union[np.ndarray, None] class GraphicFeatureIndexable(GraphicFeature): """An indexable Graphic Feature, colors, data, sizes etc.""" + @staticmethod + def is_sequence(seq): + """Check if a given value is a sequence. + + Credit to numpy, since this just reimplements np.distutils.misc_utils.is_sequence(). + Reimplemented here due to that function's pending deprecation.""" + if isinstance(seq, str): + return False + try: + len(seq) + except Exception: + return False + return True + def _set(self, value): value = self._parse_set_value(value) self[:] = value diff --git a/fastplotlib/graphics/_features/_sizes.py b/fastplotlib/graphics/_features/_sizes.py index e69de29bb..b709c13bd 100644 --- a/fastplotlib/graphics/_features/_sizes.py +++ b/fastplotlib/graphics/_features/_sizes.py @@ -0,0 +1,101 @@ +from typing import Any + +import numpy as np + +import pygfx + +from ._base import ( + GraphicFeatureIndexable, + cleanup_slice, + FeatureEvent, + to_gpu_supported_dtype, + cleanup_array_slice, +) + + +class PointsSizesFeature(GraphicFeatureIndexable): + """ + Access to the vertex buffer data shown in the graphic. + Supports fancy indexing if the data array also supports it. + """ + + def __init__(self, parent, sizes: Any, collection_index: int = None): + sizes = self._fix_sizes(sizes, parent) + super(PointsSizesFeature, self).__init__( + parent, sizes, collection_index=collection_index + ) + + @property + def buffer(self) -> pygfx.Buffer: + return self._parent.world_object.geometry.sizes + + def __getitem__(self, item): + return self.buffer.data[item] + + def _fix_sizes(self, sizes, parent): + graphic_type = parent.__class__.__name__ + + n_datapoints = parent.data().shape[0] + if not GraphicFeatureIndexable.is_sequence(sizes): + sizes = np.full(n_datapoints, sizes, dtype=np.float32) # force it into a float to avoid weird gpu errors + elif not isinstance(sizes, np.ndarray): # if it's not a ndarray already, make it one + sizes = np.array(sizes, dtype=np.float32) # read it in as a numpy.float32 + if (sizes.ndim != 1) or (sizes.size != parent.data().shape[0]): + raise ValueError( + f"sequence of `sizes` must be 1 dimensional with " + f"the same length as the number of datapoints" + ) + + sizes = to_gpu_supported_dtype(sizes) + + if sizes.ndim == 1: + if graphic_type == "ScatterGraphic": + sizes = np.array(sizes) + else: + raise ValueError(f"Sizes must be an array of shape (n,) where n == the number of data points provided.\ + Received shape={sizes.shape}.") + + return np.array(sizes) + + def __setitem__(self, key, value): + if isinstance(key, np.ndarray): + # make sure 1D array of int or boolean + key = cleanup_array_slice(key, self._upper_bound) + + # put sizes into right shape if they're only indexing datapoints + if isinstance(key, (slice, int, np.ndarray, np.integer)): + value = self._fix_sizes(value, self._parent) + # otherwise assume that they have the right shape + # numpy will throw errors if it can't broadcast + + self.buffer.data[key] = value + self._update_range(key) + # avoid creating dicts constantly if there are no events to handle + if len(self._event_handlers) > 0: + self._feature_changed(key, value) + + def _update_range(self, key): + self._update_range_indices(key) + + def _feature_changed(self, key, new_data): + if key is not None: + key = cleanup_slice(key, self._upper_bound) + if isinstance(key, (int, np.integer)): + indices = [key] + elif isinstance(key, slice): + indices = range(key.start, key.stop, key.step) + elif isinstance(key, np.ndarray): + indices = key + elif key is None: + indices = None + + pick_info = { + "index": indices, + "collection-index": self._collection_index, + "world_object": self._parent.world_object, + "new_data": new_data, + } + + event_data = FeatureEvent(type="sizes", pick_info=pick_info) + + self._call_event_handlers(event_data) \ No newline at end of file diff --git a/fastplotlib/graphics/scatter.py b/fastplotlib/graphics/scatter.py index 9e162c57a..141db2af3 100644 --- a/fastplotlib/graphics/scatter.py +++ b/fastplotlib/graphics/scatter.py @@ -5,16 +5,16 @@ from ..utils import parse_cmap_values from ._base import Graphic -from ._features import PointsDataFeature, ColorFeature, CmapFeature +from ._features import PointsDataFeature, ColorFeature, CmapFeature, PointsSizesFeature class ScatterGraphic(Graphic): - feature_events = ("data", "colors", "cmap", "present") + feature_events = ("data", "sizes", "colors", "cmap", "present") def __init__( self, data: np.ndarray, - sizes: Union[int, np.ndarray, list] = 1, + sizes: Union[int, float, np.ndarray, list] = 1, colors: np.ndarray = "w", alpha: float = 1.0, cmap: str = None, @@ -86,24 +86,11 @@ def __init__( self, self.colors(), cmap_name=cmap, cmap_values=cmap_values ) - if isinstance(sizes, int): - sizes = np.full(self.data().shape[0], sizes, dtype=np.float32) - elif isinstance(sizes, np.ndarray): - if (sizes.ndim != 1) or (sizes.size != self.data().shape[0]): - raise ValueError( - f"numpy array of `sizes` must be 1 dimensional with " - f"the same length as the number of datapoints" - ) - elif isinstance(sizes, list): - if len(sizes) != self.data().shape[0]: - raise ValueError( - "list of `sizes` must have the same length as the number of datapoints" - ) - + self.sizes = PointsSizesFeature(self, sizes) super(ScatterGraphic, self).__init__(*args, **kwargs) world_object = pygfx.Points( - pygfx.Geometry(positions=self.data(), sizes=sizes, colors=self.colors()), + pygfx.Geometry(positions=self.data(), sizes=self.sizes(), colors=self.colors()), material=pygfx.PointsMaterial(vertex_colors=True, vertex_sizes=True), ) From d34333a71210075a200623d3f3d9cfe58e50a85e Mon Sep 17 00:00:00 2001 From: DavidVFiumano Date: Sat, 15 Jul 2023 17:25:43 -0400 Subject: [PATCH 2/9] Added a few tests for the new sizes feature. --- examples/desktop/scatter/scatter_size.py | 52 +++++++ .../notebooks/scatter_sizes_animation.ipynb | 63 ++++++++ examples/notebooks/scatter_sizes_grid.ipynb | 138 ++++++++++++++++++ 3 files changed, 253 insertions(+) create mode 100644 examples/desktop/scatter/scatter_size.py create mode 100644 examples/notebooks/scatter_sizes_animation.ipynb create mode 100644 examples/notebooks/scatter_sizes_grid.ipynb diff --git a/examples/desktop/scatter/scatter_size.py b/examples/desktop/scatter/scatter_size.py new file mode 100644 index 000000000..2e809cb7b --- /dev/null +++ b/examples/desktop/scatter/scatter_size.py @@ -0,0 +1,52 @@ +""" +Scatter Plot +============ +Example showing point size change for scatter plot. +""" + +# test_example = true +import numpy as np +import fastplotlib as fpl + +# grid with 2 rows and 3 columns +grid_shape = (2,1) + +# pan-zoom controllers for each view +# views are synced if they have the +# same controller ID +controllers = [ + [0], + [1] +] + + +# you can give string names for each subplot within the gridplot +names = [ + ["scalar_size"], + ["array_size"] +] + +# Create the grid plot +plot = fpl.GridPlot( + shape=grid_shape, + controllers=controllers, + names=names +) + +# get y_values using sin function +y_values = 30*np.sin(np.arange(0, 20*np.pi+0.001, np.pi / 20)) # 1 thousand points +data = np.array([[x, y] for x, y in enumerate(y_values)], dtype=np.float32) + +plot["scalar_size"].add_scatter(data=data, sizes=5, colors="blue") # add a set of scalar sizes + +non_scalar_sizes = np.abs((y_values / np.pi)) # ensure minimum size of 5 +plot["array_size"].add_scatter(data=data, sizes=non_scalar_sizes, colors="red") + +for graph in plot: + graph.auto_scale(maintain_aspect=True) + +plot.show() + +if __name__ == "__main__": + print(__doc__) + fpl.run() \ No newline at end of file diff --git a/examples/notebooks/scatter_sizes_animation.ipynb b/examples/notebooks/scatter_sizes_animation.ipynb new file mode 100644 index 000000000..4f6c913c2 --- /dev/null +++ b/examples/notebooks/scatter_sizes_animation.ipynb @@ -0,0 +1,63 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from time import time\n", + "\n", + "import numpy as np\n", + "import fastplotlib as fpl\n", + "\n", + "plot = fpl.Plot()\n", + "\n", + "points = np.array([[-1,0,1],[-1,0,1]], dtype=np.float32).swapaxes(0,1)\n", + "size_delta_scales = np.array([10, 40, 100], dtype=np.float32)\n", + "min_sizes = 6\n", + "\n", + "def updatePositions():\n", + " current_time = time()\n", + " newPositions = points + np.sin(((current_time / 4) % 1)*np.pi)\n", + " plot.graphics[0].data = newPositions\n", + " plot.camera.width = 4*np.max(newPositions[0,:])\n", + " plot.camera.height = 4*np.max(newPositions[1,:])\n", + "\n", + "def updateSizes():\n", + " current_time = time()\n", + " sin_sample = np.sin(((current_time / 4) % 1)*np.pi)\n", + " size_delta = sin_sample*size_delta_scales\n", + " plot.graphics[0].sizes = min_sizes + size_delta\n", + "\n", + "points = np.array([[0,1,2],[0,1,2]], dtype=np.float32).swapaxes(0,1)\n", + "scatter = plot.add_scatter(points, colors=[\"red\", \"green\", \"blue\"], sizes=12)\n", + "plot.add_animations(updatePositions, updateSizes)\n", + "plot.show(autoscale=True)\n", + "fpl.run()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "fastplotlib-dev", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/notebooks/scatter_sizes_grid.ipynb b/examples/notebooks/scatter_sizes_grid.ipynb new file mode 100644 index 000000000..849163fb4 --- /dev/null +++ b/examples/notebooks/scatter_sizes_grid.ipynb @@ -0,0 +1,138 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0.00000000e+00 4.69303395e+00 9.27050983e+00 1.36197150e+01\n", + " 1.76335576e+01 2.12132034e+01 2.42705098e+01 2.67301957e+01\n", + " 2.85316955e+01 2.96306502e+01 3.00000000e+01 2.96306502e+01\n", + " 2.85316955e+01 2.67301957e+01 2.42705098e+01 2.12132034e+01\n", + " 1.76335576e+01 1.36197150e+01 9.27050983e+00 4.69303395e+00\n", + " 3.67394040e-15 -4.69303395e+00 -9.27050983e+00 -1.36197150e+01\n", + " -1.76335576e+01 -2.12132034e+01 -2.42705098e+01 -2.67301957e+01\n", + " -2.85316955e+01 -2.96306502e+01 -3.00000000e+01 -2.96306502e+01\n", + " -2.85316955e+01 -2.67301957e+01 -2.42705098e+01 -2.12132034e+01\n", + " -1.76335576e+01 -1.36197150e+01 -9.27050983e+00 -4.69303395e+00\n", + " -7.34788079e-15]\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\Mynam\\Desktop\\fastplotlib\\fastplotlib\\graphics\\_features\\_base.py:34: UserWarning: converting float64 array to float32\n", + " warn(f\"converting {array.dtype} array to float32\")\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "import fastplotlib as fpl\n", + "\n", + "# grid with 2 rows and 3 columns\n", + "grid_shape = (2,1)\n", + "\n", + "# pan-zoom controllers for each view\n", + "# views are synced if they have the \n", + "# same controller ID\n", + "controllers = [\n", + " [0],\n", + " [1]\n", + "]\n", + "\n", + "\n", + "# you can give string names for each subplot within the gridplot\n", + "names = [\n", + " [\"scalar_size\"],\n", + " [\"array_size\"]\n", + "]\n", + "\n", + "# Create the grid plot\n", + "grid_plot = fpl.GridPlot(\n", + " shape=grid_shape,\n", + " controllers=controllers,\n", + " names=names\n", + ")\n", + "\n", + "# get y_values using sin function\n", + "y_values = 30*np.sin(np.arange(0, 20*np.pi+0.001, np.pi / 20)) # 1 thousand points\n", + "print(y_values)\n", + "data = np.array([[x, y] for x, y in enumerate(y_values)], dtype=np.float32)\n", + "\n", + "grid_plot[\"scalar_size\"].add_scatter(data=data, sizes=5, colors=\"blue\") # add a set of scalar sizes\n", + "\n", + "non_scalar_sizes = np.abs((y_values / np.pi)) # ensure minimum size of 5\n", + "grid_plot[\"array_size\"].add_scatter(data=data, sizes=non_scalar_sizes, colors=\"red\")\n", + "\n", + "for graph in grid_plot:\n", + " graph.auto_scale(maintain_aspect=True)\n", + "\n", + "grid_plot.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(,)\n", + "(,)\n" + ] + } + ], + "source": [ + "for sub in grid_plot:\n", + " print(sub.graphics)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "fastplotlib-dev", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 54ab1333e425286c4aff9f4743bb3479053b312a Mon Sep 17 00:00:00 2001 From: DavidVFiumano Date: Sat, 15 Jul 2023 17:26:43 -0400 Subject: [PATCH 3/9] Forgot to add this file last commit. --- fastplotlib/graphics/_features/_sizes.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fastplotlib/graphics/_features/_sizes.py b/fastplotlib/graphics/_features/_sizes.py index b709c13bd..f65187c4c 100644 --- a/fastplotlib/graphics/_features/_sizes.py +++ b/fastplotlib/graphics/_features/_sizes.py @@ -48,6 +48,9 @@ def _fix_sizes(self, sizes, parent): sizes = to_gpu_supported_dtype(sizes) + if any(s < 0 for s in sizes): + raise ValueError("All sizes must be positive numbers greater than or equal to 0.0.") + if sizes.ndim == 1: if graphic_type == "ScatterGraphic": sizes = np.array(sizes) From 1362e0ae88ee6db587e709c143555efca8e6c3bd Mon Sep 17 00:00:00 2001 From: DavidVFiumano Date: Sat, 15 Jul 2023 17:31:46 -0400 Subject: [PATCH 4/9] Improved scatter_size.py example --- examples/desktop/scatter/scatter_size.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/desktop/scatter/scatter_size.py b/examples/desktop/scatter/scatter_size.py index 2e809cb7b..a4b6255c6 100644 --- a/examples/desktop/scatter/scatter_size.py +++ b/examples/desktop/scatter/scatter_size.py @@ -16,7 +16,7 @@ # same controller ID controllers = [ [0], - [1] + [0] ] @@ -30,7 +30,8 @@ plot = fpl.GridPlot( shape=grid_shape, controllers=controllers, - names=names + names=names, + size=(1000, 1000) ) # get y_values using sin function From 89175aa82ec766b13fbf90dcaf07917102b0ec50 Mon Sep 17 00:00:00 2001 From: DavidVFiumano Date: Sat, 15 Jul 2023 18:13:22 -0400 Subject: [PATCH 5/9] Made some changes addressing Kushal's comments. --- examples/desktop/scatter/scatter_size.py | 7 +- .../notebooks/scatter_sizes_animation.ipynb | 4 +- examples/notebooks/scatter_sizes_grid.ipynb | 87 ++++++------------- fastplotlib/graphics/_features/_base.py | 14 --- fastplotlib/graphics/_features/_sizes.py | 6 +- 5 files changed, 41 insertions(+), 77 deletions(-) diff --git a/examples/desktop/scatter/scatter_size.py b/examples/desktop/scatter/scatter_size.py index a4b6255c6..5b6987b7c 100644 --- a/examples/desktop/scatter/scatter_size.py +++ b/examples/desktop/scatter/scatter_size.py @@ -35,8 +35,11 @@ ) # get y_values using sin function -y_values = 30*np.sin(np.arange(0, 20*np.pi+0.001, np.pi / 20)) # 1 thousand points -data = np.array([[x, y] for x, y in enumerate(y_values)], dtype=np.float32) +angles = np.arange(0, 20*np.pi+0.001, np.pi / 20) +y_values = 30*np.sin(angles) # 1 thousand points +x_values = np.array([x for x in range(len(y_values))], dtype=np.float32) + +data = np.column_stack([x_values, y_values]) plot["scalar_size"].add_scatter(data=data, sizes=5, colors="blue") # add a set of scalar sizes diff --git a/examples/notebooks/scatter_sizes_animation.ipynb b/examples/notebooks/scatter_sizes_animation.ipynb index 4f6c913c2..e11468449 100644 --- a/examples/notebooks/scatter_sizes_animation.ipynb +++ b/examples/notebooks/scatter_sizes_animation.ipynb @@ -30,7 +30,9 @@ " size_delta = sin_sample*size_delta_scales\n", " plot.graphics[0].sizes = min_sizes + size_delta\n", "\n", - "points = np.array([[0,1,2],[0,1,2]], dtype=np.float32).swapaxes(0,1)\n", + "points = np.array([[0,0], \n", + " [1,1], \n", + " [2,2]])\n", "scatter = plot.add_scatter(points, colors=[\"red\", \"green\", \"blue\"], sizes=12)\n", "plot.add_animations(updatePositions, updateSizes)\n", "plot.show(autoscale=True)\n", diff --git a/examples/notebooks/scatter_sizes_grid.ipynb b/examples/notebooks/scatter_sizes_grid.ipynb index 849163fb4..508db3354 100644 --- a/examples/notebooks/scatter_sizes_grid.ipynb +++ b/examples/notebooks/scatter_sizes_grid.ipynb @@ -2,46 +2,17 @@ "cells": [ { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[ 0.00000000e+00 4.69303395e+00 9.27050983e+00 1.36197150e+01\n", - " 1.76335576e+01 2.12132034e+01 2.42705098e+01 2.67301957e+01\n", - " 2.85316955e+01 2.96306502e+01 3.00000000e+01 2.96306502e+01\n", - " 2.85316955e+01 2.67301957e+01 2.42705098e+01 2.12132034e+01\n", - " 1.76335576e+01 1.36197150e+01 9.27050983e+00 4.69303395e+00\n", - " 3.67394040e-15 -4.69303395e+00 -9.27050983e+00 -1.36197150e+01\n", - " -1.76335576e+01 -2.12132034e+01 -2.42705098e+01 -2.67301957e+01\n", - " -2.85316955e+01 -2.96306502e+01 -3.00000000e+01 -2.96306502e+01\n", - " -2.85316955e+01 -2.67301957e+01 -2.42705098e+01 -2.12132034e+01\n", - " -1.76335576e+01 -1.36197150e+01 -9.27050983e+00 -4.69303395e+00\n", - " -7.34788079e-15]\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\Mynam\\Desktop\\fastplotlib\\fastplotlib\\graphics\\_features\\_base.py:34: UserWarning: converting float64 array to float32\n", - " warn(f\"converting {array.dtype} array to float32\")\n" - ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ + "\"\"\"\n", + "Scatter Plot\n", + "============\n", + "Example showing point size change for scatter plot.\n", + "\"\"\"\n", + "\n", + "# test_example = true\n", "import numpy as np\n", "import fastplotlib as fpl\n", "\n", @@ -53,7 +24,7 @@ "# same controller ID\n", "controllers = [\n", " [0],\n", - " [1]\n", + " [0]\n", "]\n", "\n", "\n", @@ -64,42 +35,40 @@ "]\n", "\n", "# Create the grid plot\n", - "grid_plot = fpl.GridPlot(\n", + "plot = fpl.GridPlot(\n", " shape=grid_shape,\n", " controllers=controllers,\n", - " names=names\n", + " names=names,\n", + " size=(1000, 1000)\n", ")\n", "\n", "# get y_values using sin function\n", - "y_values = 30*np.sin(np.arange(0, 20*np.pi+0.001, np.pi / 20)) # 1 thousand points\n", - "print(y_values)\n", - "data = np.array([[x, y] for x, y in enumerate(y_values)], dtype=np.float32)\n", + "angles = np.arange(0, 20*np.pi+0.001, np.pi / 20)\n", + "y_values = 30*np.sin(angles) # 1 thousand points\n", + "x_values = np.array([x for x in range(len(y_values))], dtype=np.float32)\n", + "\n", + "data = np.column_stack([x_values, y_values])\n", "\n", - "grid_plot[\"scalar_size\"].add_scatter(data=data, sizes=5, colors=\"blue\") # add a set of scalar sizes\n", + "plot[\"scalar_size\"].add_scatter(data=data, sizes=5, colors=\"blue\") # add a set of scalar sizes\n", "\n", "non_scalar_sizes = np.abs((y_values / np.pi)) # ensure minimum size of 5\n", - "grid_plot[\"array_size\"].add_scatter(data=data, sizes=non_scalar_sizes, colors=\"red\")\n", + "plot[\"array_size\"].add_scatter(data=data, sizes=non_scalar_sizes, colors=\"red\")\n", "\n", - "for graph in grid_plot:\n", + "for graph in plot:\n", " graph.auto_scale(maintain_aspect=True)\n", "\n", - "grid_plot.show()" + "plot.show()\n", + "\n", + "if __name__ == \"__main__\":\n", + " print(__doc__)\n", + " fpl.run()" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(,)\n", - "(,)\n" - ] - } - ], + "outputs": [], "source": [ "for sub in grid_plot:\n", " print(sub.graphics)" diff --git a/fastplotlib/graphics/_features/_base.py b/fastplotlib/graphics/_features/_base.py index e166318db..5616eec19 100644 --- a/fastplotlib/graphics/_features/_base.py +++ b/fastplotlib/graphics/_features/_base.py @@ -299,20 +299,6 @@ def cleanup_array_slice(key: np.ndarray, upper_bound) -> Union[np.ndarray, None] class GraphicFeatureIndexable(GraphicFeature): """An indexable Graphic Feature, colors, data, sizes etc.""" - @staticmethod - def is_sequence(seq): - """Check if a given value is a sequence. - - Credit to numpy, since this just reimplements np.distutils.misc_utils.is_sequence(). - Reimplemented here due to that function's pending deprecation.""" - if isinstance(seq, str): - return False - try: - len(seq) - except Exception: - return False - return True - def _set(self, value): value = self._parse_set_value(value) self[:] = value diff --git a/fastplotlib/graphics/_features/_sizes.py b/fastplotlib/graphics/_features/_sizes.py index f65187c4c..377052918 100644 --- a/fastplotlib/graphics/_features/_sizes.py +++ b/fastplotlib/graphics/_features/_sizes.py @@ -36,7 +36,7 @@ def _fix_sizes(self, sizes, parent): graphic_type = parent.__class__.__name__ n_datapoints = parent.data().shape[0] - if not GraphicFeatureIndexable.is_sequence(sizes): + if not isinstance(sizes, (list, tuple, np.ndarray)): sizes = np.full(n_datapoints, sizes, dtype=np.float32) # force it into a float to avoid weird gpu errors elif not isinstance(sizes, np.ndarray): # if it's not a ndarray already, make it one sizes = np.array(sizes, dtype=np.float32) # read it in as a numpy.float32 @@ -71,6 +71,10 @@ def __setitem__(self, key, value): # otherwise assume that they have the right shape # numpy will throw errors if it can't broadcast + if value.size != self.buffer.data[key].size: + raise ValueError(f"{value.size} is not equal to buffer size {self.buffer.data[key].size}.\ + If you want to set size to a non-scalar value, make sure it's the right length!") + self.buffer.data[key] = value self._update_range(key) # avoid creating dicts constantly if there are no events to handle From 77ae43415e1ae355f1f5103855c9e5112105b952 Mon Sep 17 00:00:00 2001 From: DavidVFiumano Date: Sat, 15 Jul 2023 18:19:58 -0400 Subject: [PATCH 6/9] fixed an error caused by me forgetting to remove a cell in one of the notebooks --- examples/notebooks/scatter_sizes_grid.ipynb | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/examples/notebooks/scatter_sizes_grid.ipynb b/examples/notebooks/scatter_sizes_grid.ipynb index 508db3354..8d1087533 100644 --- a/examples/notebooks/scatter_sizes_grid.ipynb +++ b/examples/notebooks/scatter_sizes_grid.ipynb @@ -63,23 +63,6 @@ " print(__doc__)\n", " fpl.run()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for sub in grid_plot:\n", - " print(sub.graphics)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From 3bdcd05d02fb906e5037138d0cdcc179dfd51824 Mon Sep 17 00:00:00 2001 From: DavidVFiumano Date: Sat, 15 Jul 2023 18:29:53 -0400 Subject: [PATCH 7/9] scattter plot added --- examples/desktop/screenshots/scatter_size.png | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 examples/desktop/screenshots/scatter_size.png diff --git a/examples/desktop/screenshots/scatter_size.png b/examples/desktop/screenshots/scatter_size.png new file mode 100644 index 000000000..db637d270 --- /dev/null +++ b/examples/desktop/screenshots/scatter_size.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4cefd4cf57e54e1ef7883edea54806dfde57939d0a395c5a7758124e41b8beb +size 63485 From 139f0c5570a83d7659c79d179f4c84b053592bbb Mon Sep 17 00:00:00 2001 From: DavidVFiumano Date: Sat, 15 Jul 2023 18:32:16 -0400 Subject: [PATCH 8/9] updated to snake_case --- examples/notebooks/scatter_sizes_animation.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/notebooks/scatter_sizes_animation.ipynb b/examples/notebooks/scatter_sizes_animation.ipynb index e11468449..74d445d61 100644 --- a/examples/notebooks/scatter_sizes_animation.ipynb +++ b/examples/notebooks/scatter_sizes_animation.ipynb @@ -17,14 +17,14 @@ "size_delta_scales = np.array([10, 40, 100], dtype=np.float32)\n", "min_sizes = 6\n", "\n", - "def updatePositions():\n", + "def update_positions():\n", " current_time = time()\n", " newPositions = points + np.sin(((current_time / 4) % 1)*np.pi)\n", " plot.graphics[0].data = newPositions\n", " plot.camera.width = 4*np.max(newPositions[0,:])\n", " plot.camera.height = 4*np.max(newPositions[1,:])\n", "\n", - "def updateSizes():\n", + "def update_sizes():\n", " current_time = time()\n", " sin_sample = np.sin(((current_time / 4) % 1)*np.pi)\n", " size_delta = sin_sample*size_delta_scales\n", From 71980320927486ced55533de9710b47af92e6c65 Mon Sep 17 00:00:00 2001 From: DavidVFiumano Date: Sat, 15 Jul 2023 18:42:09 -0400 Subject: [PATCH 9/9] updated to fixed some missing dependencies and remove unecessary code in the notebooks --- examples/notebooks/scatter_sizes_animation.ipynb | 12 +++++++++--- examples/notebooks/scatter_sizes_grid.ipynb | 6 +----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/notebooks/scatter_sizes_animation.ipynb b/examples/notebooks/scatter_sizes_animation.ipynb index 74d445d61..061f444d6 100644 --- a/examples/notebooks/scatter_sizes_animation.ipynb +++ b/examples/notebooks/scatter_sizes_animation.ipynb @@ -34,10 +34,16 @@ " [1,1], \n", " [2,2]])\n", "scatter = plot.add_scatter(points, colors=[\"red\", \"green\", \"blue\"], sizes=12)\n", - "plot.add_animations(updatePositions, updateSizes)\n", - "plot.show(autoscale=True)\n", - "fpl.run()" + "plot.add_animations(update_positions, update_sizes)\n", + "plot.show(autoscale=True)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/examples/notebooks/scatter_sizes_grid.ipynb b/examples/notebooks/scatter_sizes_grid.ipynb index 8d1087533..ff64184f7 100644 --- a/examples/notebooks/scatter_sizes_grid.ipynb +++ b/examples/notebooks/scatter_sizes_grid.ipynb @@ -57,11 +57,7 @@ "for graph in plot:\n", " graph.auto_scale(maintain_aspect=True)\n", "\n", - "plot.show()\n", - "\n", - "if __name__ == \"__main__\":\n", - " print(__doc__)\n", - " fpl.run()" + "plot.show()" ] } ],