diff --git a/examples/collection.ipynb b/examples/collection.ipynb
deleted file mode 100644
index 796471c5d..000000000
--- a/examples/collection.ipynb
+++ /dev/null
@@ -1,377 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": 1,
- "id": "fa7d8f38-12cd-4eab-9e02-a793cc663408",
- "metadata": {},
- "outputs": [],
- "source": [
- "from fastplotlib import Plot\n",
- "from ipywidgets import VBox, HBox, IntSlider\n",
- "import numpy as np"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "id": "b05bd9e7-6248-46da-bb68-07ae1582ab53",
- "metadata": {},
- "outputs": [],
- "source": [
- "# linspace, create 100 evenly spaced x values from -10 to 10\n",
- "xs = np.linspace(-10, 50, 100)\n",
- "# sine wave\n",
- "ys = np.sin(xs)\n",
- "sine = np.dstack([xs, ys])[0]\n",
- "\n",
- "# cosine wave\n",
- "ys = np.cos(xs)\n",
- "cosine = np.dstack([xs, ys])[0]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "id": "9c5a0750-f5c4-44c7-988b-64e9fab5f7c3",
- "metadata": {},
- "outputs": [],
- "source": [
- "coll = list()\n",
- "for i in range(0, 10, 1):\n",
- " s = np.copy(sine)\n",
- " s[:, 1] += i * 5\n",
- " coll.append(s)\n",
- " \n",
- " c = np.copy(cosine)\n",
- " c[:, 1] += (i * 5) + 3\n",
- " coll.append(c)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "id": "04e0146c-3864-4b36-9bf3-fb5b99b69250",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "df7c6fbec06545f099161547b726f93a",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "RFBOutputContext()"
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "plot = Plot()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "id": "9e7dc422-6596-4859-bf80-52b8685cb33a",
- "metadata": {},
- "outputs": [],
- "source": [
- "lines = plot.add_line_collection(coll, cmap=\"jet\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "id": "d4bdf613-5930-419a-8072-8feb1a37037a",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "

initial snapshot
"
- ],
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "6f2d142f99d343c7a7d5fe7316e8b980",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "JupyterWgpuCanvas()"
- ]
- },
- "execution_count": 6,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "plot.show()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "id": "5cd3062c-e633-4c33-ad2b-1d74818da2a8",
- "metadata": {},
- "outputs": [],
- "source": [
- "lines[:].present.add_event_handler(plot.auto_scale)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "id": "dcebf81e-8d34-4025-87ac-34d62d93b756",
- "metadata": {},
- "outputs": [],
- "source": [
- "lines[10:].present = False\n",
- "lines[:5].present = False"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "id": "62698b0a-4f9b-4989-8bed-452848aa46a2",
- "metadata": {},
- "outputs": [],
- "source": [
- "lines[:].present = True"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 10,
- "id": "39cb889a-6cb8-44b8-89c4-f1025f6be1a6",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "CollectionIndexer @ 0x7fe4a3749b40\n",
- "Collection of <5> LineGraphic"
- ]
- },
- "execution_count": 10,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "lines[10:15]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 11,
- "id": "ba189ba5-b0b1-4129-be9b-129578bc794d",
- "metadata": {},
- "outputs": [],
- "source": [
- "lines[10:15].colors = \"g\""
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 12,
- "id": "5f5a557d-4563-44bd-9343-4319f64e9bdb",
- "metadata": {},
- "outputs": [],
- "source": [
- "lines[10:15].colors[30:60] = \"cyan\""
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 13,
- "id": "9a0ead4d-b714-49f7-b8d2-733cd5a71eaa",
- "metadata": {},
- "outputs": [],
- "source": [
- "lines[[1, 2, 5]].colors = \"w\""
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 14,
- "id": "9f8fe42b-348c-4ca4-b12a-2f0192fb9a39",
- "metadata": {},
- "outputs": [],
- "source": [
- "lines[[16, 19]].colors[::2] = \"w\""
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 15,
- "id": "b8cee8c7-070b-40af-bf63-f2cf29e3db19",
- "metadata": {},
- "outputs": [],
- "source": [
- "lines[2:6].colors.add_event_handler(lambda x: print(x))"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 16,
- "id": "d0a4c6b3-5fa0-43ef-a7a8-baaada70bc19",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "FeatureEvent @ 0x7fe4cd37dc30\n",
- "type: color-changed\n",
- "pick_info: {'index': range(0, 100), 'collection-index': 3, 'world_object': , 'new_data': array([[1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.],\n",
- " [1., 0., 1., 1.]], dtype=float32)}\n",
- "\n"
- ]
- }
- ],
- "source": [
- "lines[3].colors = \"magenta\""
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "5ec14ed9-8151-4bfa-b21f-c3acc48bd968",
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3 (ipykernel)",
- "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.10.5"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/examples/line_collection_event.ipynb b/examples/line_collection_event.ipynb
new file mode 100644
index 000000000..c88971042
--- /dev/null
+++ b/examples/line_collection_event.ipynb
@@ -0,0 +1,212 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "f02f7349-ac1a-49b7-b304-d5b701723e0f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%load_ext autoreload\n",
+ "%autoreload 2"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "d9f54448-7718-4212-ac6d-163a2d3be146",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "from fastplotlib.graphics import LineGraphic, LineCollection, ImageGraphic\n",
+ "from fastplotlib.plot import Plot\n",
+ "import pickle"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "b683c6d1-d926-43e3-a221-4ec6450e3677",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "contours = pickle.load(open(\"/home/caitlin/Downloads/contours.pickle\", \"rb\"))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "26ced005-c52f-4696-903d-a6974ae6cefc",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "2bc75fa52d484cd1b03006a6530f10b3",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "RFBOutputContext()"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "MESA-INTEL: warning: Performance support disabled, consider sysctl dev.i915.perf_stream_paranoid=0\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "plot = Plot()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "1aea0a61-db63-41e1-b330-5a922da4bac5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "line_collection = LineCollection(contours, cmap=\"Oranges\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "310fd5b3-49f2-4dbb-831e-cb9f369d58c8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plot.add_graphic(line_collection)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "661a5991-0de3-44d7-a626-2ae72704dcec",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "image = ImageGraphic(data=np.ones((180, 180)))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "816f6382-f4ea-4cd4-b9f3-2dc5c232b0a5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plot.add_graphic(image)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8187fd25-18e1-451f-b2fe-8cd2e7785c8b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plot.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d5ddd3c4-84e2-44a3-a14f-74871aa0bb8f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "black = np.array([0.0, 0.0, 0.0, 1.0])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6ac64f1c-21e0-4c21-b968-3953e7858848",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from typing import *"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d0d0c971-aac9-4c77-b88c-de9d79c7d74e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def callback_function(source: Any, target: Any, event, new_data: Any):\n",
+ " # calculate coms of line collection\n",
+ " indices = np.array(event.pick_info[\"index\"])\n",
+ " \n",
+ " coms = list()\n",
+ "\n",
+ " for contour in target.items:\n",
+ " coors = contour.data.feature_data[~np.isnan(contour.data.feature_data).any(axis=1)]\n",
+ " com = coors.mean(axis=0)\n",
+ " coms.append(com)\n",
+ "\n",
+ " # euclidean distance to find closest index of com \n",
+ " indices = np.append(indices, [0])\n",
+ " \n",
+ " ix = np.linalg.norm((coms - indices), axis=1).argsort()[0]\n",
+ " \n",
+ " ix = int(ix)\n",
+ " \n",
+ " print(ix)\n",
+ " \n",
+ " target._set_feature(feature=\"colors\", new_data=new_data, indices=ix)\n",
+ " \n",
+ " return None"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1a46d148-3007-4c7a-bf2b-91057eba855d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "image.link(event_type=\"click\", target=line_collection, feature=\"colors\", new_data=black, callback_function=callback_function)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8040456e-24a5-423b-8822-99a20e7ea470",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "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.9.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/examples/single_contour_event.ipynb b/examples/single_contour_event.ipynb
new file mode 100644
index 000000000..a4b7f1f91
--- /dev/null
+++ b/examples/single_contour_event.ipynb
@@ -0,0 +1,225 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "3a992f41-b157-4b6f-9630-ef370389f318",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%load_ext autoreload\n",
+ "%autoreload 2"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "589eaea4-e749-46ff-ac3d-e22aa4f75641",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "from fastplotlib.graphics import LineGraphic\n",
+ "from fastplotlib.plot import Plot\n",
+ "import pickle"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "650279ac-e7df-4c6f-aac1-078ae4287028",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "contours = pickle.load(open(\"/home/caitlin/Downloads/contours.pickle\", \"rb\"))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "49dc6123-39d8-4f60-b14b-9cfd9a008940",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "single_contour = LineGraphic(data=contours[0], size=10.0, cmap=\"jet\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "776e916f-16c9-4114-b1ff-7ea209aa7b04",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "b9c85f639ca34c6c882396fc4753818b",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "RFBOutputContext()"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "MESA-INTEL: warning: Performance support disabled, consider sysctl dev.i915.perf_stream_paranoid=0\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "plot = Plot()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "203768f0-6bb4-4ba9-b099-395f2bdd2a8c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plot.add_graphic(single_contour)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "4e46d687-d81a-4b6f-bece-c9edf3606d4f",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
initial snapshot
"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "730d202ce0424559b30cd4d7d5f3b77b",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "JupyterWgpuCanvas()"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "plot.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "dbcda1d6-3f21-4a5e-b60f-75bf9103fbe6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "white = np.ones(shape=single_contour.colors.feature_data.shape)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "6db453cf-5856-4a7b-879f-83032cb9e9ac",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "single_contour.link(event_type=\"click\", target=single_contour, feature=\"colors\", new_data=white, indices_mapper=None)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "712c3f43-3339-4d1b-9d64-fe4f4d6bd672",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'click': [CallbackData(target=fastplotlib.LineGraphic @ 0x7f516cc02880, feature='colors', new_data=array([[1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.],\n",
+ " [1., 1., 1., 1.]]), indices_mapper=None)]}"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "single_contour.registered_callbacks"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9d353d7b-a0d0-4629-a8c0-87b767d99bd2",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "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.9.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py
index 3cf358451..e5606f781 100644
--- a/fastplotlib/graphics/_base.py
+++ b/fastplotlib/graphics/_base.py
@@ -1,9 +1,14 @@
from typing import *
-from pygfx import WorldObject
+from .features._base import cleanup_slice
+
+from pygfx import WorldObject, Group
from pygfx.linalg import Vector3
-from .features import GraphicFeature, PresentFeature
+from .features import GraphicFeature, PresentFeature, GraphicFeatureIndexable
+
+from abc import ABC, abstractmethod
+from dataclasses import dataclass
class BaseGraphic:
@@ -14,6 +19,10 @@ def __init_subclass__(cls, **kwargs):
class Graphic(BaseGraphic):
+ pygfx_events = [
+ "click"
+ ]
+
def __init__(
self,
name: str = None
@@ -28,16 +37,9 @@ def __init__(
"""
self.name = name
+ self.registered_callbacks = dict()
self.present = PresentFeature(parent=self)
- valid_features = ["visible"]
- for attr_name in self.__dict__.keys():
- attr = getattr(self, attr_name)
- if isinstance(attr, GraphicFeature):
- valid_features.append(attr_name)
-
- self._valid_features = tuple(valid_features)
-
@property
def world_object(self) -> WorldObject:
return self._world_object
@@ -79,3 +81,246 @@ def __repr__(self):
return f"'{self.name}' fastplotlib.{self.__class__.__name__} @ {hex(id(self))}"
else:
return f"fastplotlib.{self.__class__.__name__} @ {hex(id(self))}"
+
+
+class Interaction(ABC):
+ @abstractmethod
+ def _set_feature(self, feature: str, new_data: Any, indices: Any):
+ pass
+
+ @abstractmethod
+ def _reset_feature(self, feature: str):
+ pass
+
+ def link(self, event_type: str, target: Any, feature: str, new_data: Any, callback_function: callable = None):
+ if event_type in self.pygfx_events:
+ self.world_object.add_event_handler(self.event_handler, event_type)
+
+ elif event_type in self.feature_events:
+ if isinstance(self, GraphicCollection):
+ feature_instance = getattr(self[:], event_type)
+ else:
+ feature_instance = getattr(self, event_type)
+
+ feature_instance.add_event_handler(self.event_handler)
+
+ else:
+ raise ValueError("event not possible")
+
+ if event_type in self.registered_callbacks.keys():
+ self.registered_callbacks[event_type].append(
+ CallbackData(target=target, feature=feature, new_data=new_data, callback_function=callback_function))
+ else:
+ self.registered_callbacks[event_type] = list()
+ self.registered_callbacks[event_type].append(
+ CallbackData(target=target, feature=feature, new_data=new_data, callback_function=callback_function))
+
+ def event_handler(self, event):
+ if event.type in self.registered_callbacks.keys():
+ for target_info in self.registered_callbacks[event.type]:
+ if target_info.callback_function is not None:
+ # if callback_function is not None, then callback function should handle the entire event
+ target_info.callback_function(source=self, target=target_info.target, event=event, new_data=target_info.new_data)
+ elif isinstance(self, GraphicCollection):
+ # if target is a GraphicCollection, then indices will be stored in collection_index
+ if event.type in self.feature_events:
+ indices = event.pick_info["collection-index"]
+
+ # for now we only have line collections so this works
+ else:
+ for i, item in enumerate(self._items):
+ if item.world_object is event.pick_info["world_object"]:
+ indices = i
+ target_info.target._set_feature(feature=target_info.feature, new_data=target_info.new_data, indices=indices)
+ else:
+ # if target is a single graphic, then indices do not matter
+ target_info.target._set_feature(feature=target_info.feature, new_data=target_info.new_data,
+ indices=None)
+
+@dataclass
+class CallbackData:
+ """Class for keeping track of the info necessary for interactivity after event occurs."""
+ target: Any
+ feature: str
+ new_data: Any
+ callback_function: callable = None
+
+
+@dataclass
+class PreviouslyModifiedData:
+ """Class for keeping track of previously modified data at indices"""
+ data: Any
+ indices: Any
+
+
+class GraphicCollection(BaseGraphic):
+ """Graphic Collection base class"""
+
+ pygfx_events = [
+ "click"
+ ]
+
+ def __init__(self, name: str = None):
+ self.name = name
+ self._items: List[Graphic] = list()
+ self.registered_callbacks = dict()
+
+ @property
+ def world_object(self) -> Group:
+ return self._world_object
+
+ @property
+ def items(self) -> Tuple[Graphic]:
+ """Get the Graphic instances within this collection"""
+ return tuple(self._items)
+
+ def add_graphic(self, graphic: Graphic, reset_index: True):
+ """Add a graphic to the collection"""
+ if not isinstance(graphic, self.child_type):
+ raise TypeError(
+ f"Can only add graphics of the same type to a collection, "
+ f"You can only add {self.child_type} to a {self.__class__.__name__}, "
+ f"you are trying to add a {graphic.__class__.__name__}."
+ )
+ self._items.append(graphic)
+ if reset_index:
+ self._reset_index()
+ self.world_object.add(graphic.world_object)
+
+ def remove_graphic(self, graphic: Graphic, reset_index: True):
+ """Remove a graphic from the collection"""
+ self._items.remove(graphic)
+ if reset_index:
+ self._reset_index()
+ self.world_object.remove(graphic)
+
+ def _reset_index(self):
+ for new_index, graphic in enumerate(self._items):
+ graphic.collection_index = new_index
+
+ def __getitem__(self, key):
+ if isinstance(key, int):
+ key = [key]
+
+ if isinstance(key, slice):
+ key = cleanup_slice(key, upper_bound=len(self))
+ selection_indices = range(key.start, key.stop, key.step)
+ selection = self._items[key]
+
+ # fancy-ish indexing
+ elif isinstance(key, (tuple, list)):
+ selection = list()
+ for ix in key:
+ selection.append(self._items[ix])
+
+ selection_indices = key
+ else:
+ raise TypeError("Graphic Collection indexing supports int, slice, tuple or list of integers")
+ return CollectionIndexer(
+ parent=self,
+ selection=selection,
+ selection_indices=selection_indices
+ )
+
+ def __len__(self):
+ return len(self._items)
+
+
+class CollectionIndexer:
+ """Collection Indexer"""
+ def __init__(
+ self,
+ parent: GraphicCollection,
+ selection: List[Graphic],
+ selection_indices: Union[list, range],
+ ):
+ """
+
+ Parameters
+ ----------
+ parent
+ selection
+ selection_indices: Union[list, range]
+ """
+ self._selection = selection
+ self._selection_indices = selection_indices
+
+ for attr_name in self._selection[0].__dict__.keys():
+ attr = getattr(self._selection[0], attr_name)
+ if isinstance(attr, GraphicFeature):
+ collection_feature = CollectionFeature(
+ parent,
+ self._selection,
+ selection_indices=selection_indices,
+ feature=attr_name
+ )
+ collection_feature.__doc__ = f"indexable {attr_name} feature for collection"
+ setattr(self, attr_name, collection_feature)
+
+ def __setattr__(self, key, value):
+ if hasattr(self, key):
+ attr = getattr(self, key)
+ if isinstance(attr, CollectionFeature):
+ attr._set(value)
+ return
+
+ super().__setattr__(key, value)
+
+ def __repr__(self):
+ return f"{self.__class__.__name__} @ {hex(id(self))}\n" \
+ f"Collection of <{len(self._selection)}> {self._selection[0].__class__.__name__}"
+
+
+class CollectionFeature:
+ """Collection Feature"""
+ def __init__(
+ self,
+ parent: GraphicCollection,
+ selection: List[Graphic],
+ selection_indices, feature: str
+ ):
+ self._selection = selection
+ self._selection_indices = selection_indices
+ self._feature = feature
+
+ self._feature_instances: List[GraphicFeature] = list()
+
+ for graphic in self._selection:
+ fi = getattr(graphic, self._feature)
+ self._feature_instances.append(fi)
+
+ if isinstance(fi, GraphicFeatureIndexable):
+ self._indexable = True
+ else:
+ self._indexable = False
+
+ def _set(self, value):
+ self[:] = value
+
+ def __getitem__(self, item):
+ # only for indexable graphic features
+ return [fi[item] for fi in self._feature_instances]
+
+ def __setitem__(self, key, value):
+ if self._indexable:
+ for fi in self._feature_instances:
+ fi[key] = value
+
+ else:
+ for fi in self._feature_instances:
+ fi._set(value)
+
+ def add_event_handler(self, handler: callable):
+ for fi in self._feature_instances:
+ fi.add_event_handler(handler)
+
+ def remove_event_handler(self, handler: callable):
+ for fi in self._feature_instances:
+ fi.remove_event_handler(handler)
+
+ def block_events(self, b: bool):
+ for fi in self._feature_instances:
+ fi.block_events(b)
+
+ def __repr__(self):
+ return f"Collection feature for: <{self._feature}>"
diff --git a/fastplotlib/graphics/_collection.py b/fastplotlib/graphics/_collection.py
deleted file mode 100644
index 15cc26a60..000000000
--- a/fastplotlib/graphics/_collection.py
+++ /dev/null
@@ -1,172 +0,0 @@
-from typing import *
-
-import numpy as np
-
-from pygfx import Group
-
-from ._base import BaseGraphic, Graphic
-from .features._base import GraphicFeature, GraphicFeatureIndexable, cleanup_slice
-
-
-class GraphicCollection(BaseGraphic):
- """Graphic Collection base class"""
- def __init__(self, name: str = None):
- self.name = name
- self._items: List[Graphic] = list()
-
- @property
- def world_object(self) -> Group:
- return self._world_object
-
- @property
- def items(self) -> Tuple[Graphic]:
- """Get the Graphic instances within this collection"""
- return tuple(self._items)
-
- def add_graphic(self, graphic: Graphic, reset_index: True):
- """Add a graphic to the collection"""
- if not isinstance(graphic, self.child_type):
- raise TypeError(
- f"Can only add graphics of the same type to a collection, "
- f"You can only add {self.child_type} to a {self.__class__.__name__}, "
- f"you are trying to add a {graphic.__class__.__name__}."
- )
- self._items.append(graphic)
- if reset_index:
- self._reset_index()
- self.world_object.add(graphic.world_object)
-
- def remove_graphic(self, graphic: Graphic, reset_index: True):
- """Remove a graphic from the collection"""
- self._items.remove(graphic)
- if reset_index:
- self._reset_index()
- self.world_object.remove(graphic)
-
- def _reset_index(self):
- for new_index, graphic in enumerate(self._items):
- graphic.collection_index = new_index
-
- def __getitem__(self, key):
- if isinstance(key, int):
- key = [key]
-
- if isinstance(key, slice):
- key = cleanup_slice(key, upper_bound=len(self))
- selection_indices = range(key.start, key.stop, key.step)
- selection = self._items[key]
-
- # fancy-ish indexing
- elif isinstance(key, (tuple, list)):
- selection = list()
- for ix in key:
- selection.append(self._items[ix])
-
- selection_indices = key
- else:
- raise TypeError("Graphic Collection indexing supports int, slice, tuple or list of integers")
- return CollectionIndexer(
- parent=self,
- selection=selection,
- selection_indices=selection_indices
- )
-
- def __len__(self):
- return len(self._items)
-
-
-class CollectionIndexer:
- """Collection Indexer"""
- def __init__(
- self,
- parent: GraphicCollection,
- selection: List[Graphic],
- selection_indices: Union[list, range],
- ):
- """
-
- Parameters
- ----------
- parent
- selection
- selection_indices: Union[list, range]
- """
- self._selection = selection
- self._selection_indices = selection_indices
-
- for attr_name in self._selection[0].__dict__.keys():
- attr = getattr(self._selection[0], attr_name)
- if isinstance(attr, GraphicFeature):
- collection_feature = CollectionFeature(
- parent,
- self._selection,
- selection_indices=selection_indices,
- feature=attr_name
- )
- collection_feature.__doc__ = f"indexable {attr_name} feature for collection"
- setattr(self, attr_name, collection_feature)
-
- def __setattr__(self, key, value):
- if hasattr(self, key):
- attr = getattr(self, key)
- if isinstance(attr, CollectionFeature):
- attr._set(value)
- return
-
- super().__setattr__(key, value)
-
- def __repr__(self):
- return f"{self.__class__.__name__} @ {hex(id(self))}\n" \
- f"Collection of <{len(self._selection)}> {self._selection[0].__class__.__name__}"
-
-
-class CollectionFeature:
- """Collection Feature"""
- def __init__(
- self,
- parent: GraphicCollection,
- selection: List[Graphic],
- selection_indices, feature: str
- ):
- self._selection = selection
- self._selection_indices = selection_indices
- self._feature = feature
-
- self._feature_instances: List[GraphicFeature] = list()
-
- for graphic in self._selection:
- fi = getattr(graphic, self._feature)
- self._feature_instances.append(fi)
-
- if isinstance(fi, GraphicFeatureIndexable):
- self._indexable = True
- else:
- self._indexable = False
-
- def _set(self, value):
- self[:] = value
-
- def __getitem__(self, item):
- # only for indexable graphic features
- return [fi[item] for fi in self._feature_instances]
-
- def __setitem__(self, key, value):
- if self._indexable:
- for fi in self._feature_instances:
- fi[key] = value
-
- else:
- for fi in self._feature_instances:
- fi._set(value)
- key = None
-
- def add_event_handler(self, handler: callable):
- for fi in self._feature_instances:
- fi.add_event_handler(handler)
-
- def remove_event_handler(self, handler: callable):
- for fi in self._feature_instances:
- fi.remove_event_handler(handler)
-
- def __repr__(self):
- return f"Collection feature for: <{self._feature}>"
diff --git a/fastplotlib/graphics/features/__init__.py b/fastplotlib/graphics/features/__init__.py
index 1fcb71246..f1df7a042 100644
--- a/fastplotlib/graphics/features/__init__.py
+++ b/fastplotlib/graphics/features/__init__.py
@@ -1,4 +1,4 @@
from ._colors import ColorFeature, CmapFeature, ImageCmapFeature
from ._data import PointsDataFeature, ImageDataFeature
from ._present import PresentFeature
-from ._base import GraphicFeature
+from ._base import GraphicFeature, GraphicFeatureIndexable
diff --git a/fastplotlib/graphics/features/_base.py b/fastplotlib/graphics/features/_base.py
index 3aec126df..d392efa7e 100644
--- a/fastplotlib/graphics/features/_base.py
+++ b/fastplotlib/graphics/features/_base.py
@@ -9,7 +9,7 @@
class FeatureEvent:
"""
- type: -, example: "color-changed"
+ type: , example: "colors"
pick_info: dict in the form:
{
"index": indices where feature data was changed, ``range`` object or List[int],
@@ -50,6 +50,11 @@ def __init__(self, parent, data: Any, collection_index: int = None):
self._collection_index = collection_index
self._event_handlers = list()
+ self._block_events = False
+
+ def block_events(self, b: bool):
+ self._block_events = b
+
@property
def feature_data(self):
"""graphic feature data managed by fastplotlib, do not modify directly"""
@@ -98,6 +103,9 @@ def _feature_changed(self, key: Union[int, slice, Tuple[slice]], new_data: Any):
pass
def _call_event_handlers(self, event_data: FeatureEvent):
+ if self._block_events:
+ return
+
for func in self._event_handlers:
try:
if len(getfullargspec(func).args) > 0:
diff --git a/fastplotlib/graphics/features/_colors.py b/fastplotlib/graphics/features/_colors.py
index 7304c2b21..067687aca 100644
--- a/fastplotlib/graphics/features/_colors.py
+++ b/fastplotlib/graphics/features/_colors.py
@@ -189,7 +189,7 @@ def _feature_changed(self, key, new_data):
"new_data": new_data,
}
- event_data = FeatureEvent(type="color-changed", pick_info=pick_info)
+ event_data = FeatureEvent(type="colors", pick_info=pick_info)
self._call_event_handlers(event_data)
@@ -241,6 +241,6 @@ def _feature_changed(self, key, new_data):
"new_data": new_data
}
- event_data = FeatureEvent(type="cmap-changed", pick_info=pick_info)
+ event_data = FeatureEvent(type="cmap", pick_info=pick_info)
self._call_event_handlers(event_data)
diff --git a/fastplotlib/graphics/features/_data.py b/fastplotlib/graphics/features/_data.py
index 923524bb1..3039c7931 100644
--- a/fastplotlib/graphics/features/_data.py
+++ b/fastplotlib/graphics/features/_data.py
@@ -88,7 +88,7 @@ def _feature_changed(self, key, new_data):
"new_data": new_data
}
- event_data = FeatureEvent(type="data-changed", pick_info=pick_info)
+ event_data = FeatureEvent(type="data", pick_info=pick_info)
self._call_event_handlers(event_data)
@@ -145,6 +145,6 @@ def _feature_changed(self, key, new_data):
"new_data": new_data
}
- event_data = FeatureEvent(type="data-changed", pick_info=pick_info)
+ event_data = FeatureEvent(type="data", pick_info=pick_info)
self._call_event_handlers(event_data)
diff --git a/fastplotlib/graphics/features/_present.py b/fastplotlib/graphics/features/_present.py
index 269ef1a6b..d028bd102 100644
--- a/fastplotlib/graphics/features/_present.py
+++ b/fastplotlib/graphics/features/_present.py
@@ -50,6 +50,6 @@ def _feature_changed(self, key, new_data):
"new_data": new_data
}
- event_data = FeatureEvent(type="present-changed", pick_info=pick_info)
+ event_data = FeatureEvent(type="present", pick_info=pick_info)
self._call_event_handlers(event_data)
\ No newline at end of file
diff --git a/fastplotlib/graphics/heatmap.py b/fastplotlib/graphics/heatmap.py
index 2c33564db..103a0fc2e 100644
--- a/fastplotlib/graphics/heatmap.py
+++ b/fastplotlib/graphics/heatmap.py
@@ -53,24 +53,18 @@ def __init__(
):
"""
Create a Heatmap Graphic
-
Parameters
----------
data: array-like, must be 2-dimensional
| array-like, usually numpy.ndarray, must support ``memoryview()``
| Tensorflow Tensors also work _I think_, but not thoroughly tested
-
vmin: int, optional
minimum value for color scaling, calculated from data if not provided
-
vmax: int, optional
maximum value for color scaling, calculated from data if not provided
-
cmap: str, optional
colormap to use to display the image data, default is ``"plasma"``
-
selection_options
-
args:
additional arguments passed to Graphic
kwargs:
@@ -140,4 +134,4 @@ def add_highlight(self, event):
self.world_object.add(self.selection_graphic)
self._highlights.append(self.selection_graphic)
- return rval
+ return rval
\ No newline at end of file
diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py
index 48459c63e..d1842ff85 100644
--- a/fastplotlib/graphics/image.py
+++ b/fastplotlib/graphics/image.py
@@ -2,12 +2,16 @@
import pygfx
-from ._base import Graphic
+from ._base import Graphic, Interaction, PreviouslyModifiedData
from .features import ImageCmapFeature, ImageDataFeature
from ..utils import quick_min_max
-class ImageGraphic(Graphic):
+class ImageGraphic(Graphic, Interaction):
+ feature_events = [
+ "data",
+ "colors",
+ ]
def __init__(
self,
data: Any,
@@ -20,50 +24,35 @@ def __init__(
):
"""
Create an Image Graphic
-
Parameters
----------
data: array-like, must be 2-dimensional
| array-like, usually numpy.ndarray, must support ``memoryview()``
| Tensorflow Tensors also work _I think_, but not thoroughly tested
-
vmin: int, optional
minimum value for color scaling, calculated from data if not provided
-
vmax: int, optional
maximum value for color scaling, calculated from data if not provided
-
cmap: str, optional, default "nearest"
colormap to use to display the image data, default is ``"plasma"``
-
filter: str, optional, default "nearest"
interpolation filter, one of "nearest" or "linear"
-
args:
additional arguments passed to Graphic
-
kwargs:
additional keyword arguments passed to Graphic
-
Examples
--------
-
.. code-block:: python
-
from fastplotlib import Plot
-
# create a `Plot` instance
plot = Plot()
-
# make some random 2D image data
data = np.random.rand(512, 512)
-
# plot the image data
plot.add_image(data=data)
-
# show the plot
plot.show()
-
"""
super().__init__(*args, **kwargs)
@@ -103,3 +92,13 @@ def vmax(self, value: float):
self.world_object.material.clim[0],
value
)
+
+ def _set_feature(self, feature: str, new_data: Any, indices: Any):
+ pass
+
+ def _reset_feature(self, feature: str):
+ pass
+
+
+
+
diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py
index cb0224007..9df940b52 100644
--- a/fastplotlib/graphics/line.py
+++ b/fastplotlib/graphics/line.py
@@ -2,12 +2,17 @@
import numpy as np
import pygfx
-from ._base import Graphic
+from ._base import Graphic, Interaction, PreviouslyModifiedData
from .features import PointsDataFeature, ColorFeature, CmapFeature
from ..utils import get_colors
-class LineGraphic(Graphic):
+class LineGraphic(Graphic, Interaction):
+ feature_events = [
+ "data",
+ "colors",
+ ]
+
def __init__(
self,
data: Any,
@@ -83,3 +88,30 @@ def __init__(
if z_position is not None:
self.world_object.position.z = z_position
+
+ def _set_feature(self, feature: str, new_data: Any, indices: Any = None):
+ if not hasattr(self, "_previous_data"):
+ self._previous_data = dict()
+ elif hasattr(self, "_previous_data"):
+ self._reset_feature(feature)
+
+ feature_instance = getattr(self, feature)
+ if indices is not None:
+ previous = feature_instance[indices].copy()
+ feature_instance[indices] = new_data
+ else:
+ previous = feature_instance._data.copy()
+ feature_instance._set(new_data)
+ if feature in self._previous_data.keys():
+ self._previous_data[feature].data = previous
+ self._previous_data[feature].indices = indices
+ else:
+ self._previous_data[feature] = PreviouslyModifiedData(data=previous, indices=indices)
+
+ def _reset_feature(self, feature: str):
+ prev_ixs = self._previous_data[feature].indices
+ feature_instance = getattr(self, feature)
+ if prev_ixs is not None:
+ feature_instance[prev_ixs] = self._previous_data[feature].data
+ else:
+ feature_instance._set(self._previous_data[feature].data)
\ No newline at end of file
diff --git a/fastplotlib/graphics/linecollection.py b/fastplotlib/graphics/linecollection.py
index 49b123028..d17f9c241 100644
--- a/fastplotlib/graphics/linecollection.py
+++ b/fastplotlib/graphics/linecollection.py
@@ -2,15 +2,19 @@
import pygfx
from typing import *
-from ._collection import GraphicCollection
+from ._base import Interaction, PreviouslyModifiedData, GraphicCollection
from .line import LineGraphic
from ..utils import get_colors
-from typing import *
+from copy import deepcopy
-class LineCollection(GraphicCollection):
+class LineCollection(GraphicCollection, Interaction):
"""Line Collection graphic"""
child_type = LineGraphic
+ feature_events = [
+ "data",
+ "colors",
+ ]
def __init__(
self,
@@ -113,3 +117,34 @@ def __init__(
)
self.add_graphic(lg, reset_index=False)
+
+ def _set_feature(self, feature: str, new_data: Any, indices: Any):
+ if not hasattr(self, "_previous_data"):
+ self._previous_data = dict()
+ elif hasattr(self, "_previous_data"):
+ self._reset_feature(feature)
+
+ coll_feature = getattr(self[indices], feature)
+
+ data = list()
+ for fea in coll_feature._feature_instances:
+ data.append(fea._data)
+
+ # later we can think about multi-index events
+ previous = deepcopy(data[0])
+ coll_feature._set(new_data)
+
+ if feature in self._previous_data.keys():
+ self._previous_data[feature].data = previous
+ self._previous_data[feature].indices = indices
+ else:
+ self._previous_data[feature] = PreviouslyModifiedData(data=previous, indices=indices)
+
+ def _reset_feature(self, feature: str):
+ # implemented for a single index at moment
+ prev_ixs = self._previous_data[feature].indices
+ coll_feature = getattr(self[prev_ixs], feature)
+
+ coll_feature.block_events(True)
+ coll_feature._set(self._previous_data[feature].data)
+ coll_feature.block_events(False)
\ No newline at end of file