From 720aeb25dcd302d32d6ebe940f12cf97cca70679 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Thu, 7 Sep 2023 16:40:33 -0400 Subject: [PATCH 01/10] add jupyter-sidecar as a dependency, update simple notebook --- examples/notebooks/simple.ipynb | 92 +++++++++++++++++++++++++++++---- setup.py | 3 +- 2 files changed, 85 insertions(+), 10 deletions(-) diff --git a/examples/notebooks/simple.ipynb b/examples/notebooks/simple.ipynb index e994bfba8..601d21aaf 100644 --- a/examples/notebooks/simple.ipynb +++ b/examples/notebooks/simple.ipynb @@ -55,7 +55,8 @@ "source": [ "from fastplotlib import Plot\n", "from ipywidgets import VBox, HBox, IntSlider\n", - "import numpy as np" + "import numpy as np\n", + "from sidecar import Sidecar" ] }, { @@ -76,7 +77,9 @@ "id": "a9b386ac-9218-4f8f-97b3-f29b4201ef55", "metadata": {}, "source": [ - "## Simple image" + "## Simple image\n", + "\n", + "We are going to be using `jupyterlab-sidecar` to render some of the plots on the side. This makes it very easy to interact with your plots without having to constantly scroll up and down :D" ] }, { @@ -97,8 +100,12 @@ "# plot the image data\n", "image_graphic = plot.add_image(data=data, name=\"sample-image\")\n", "\n", - "# show the plot\n", - "plot.show()" + "# display plot in sidecar\n", + "sc = Sidecar(title=\"sample image\", layout={'width': '800px'})\n", + "\n", + "with sc:\n", + " # show the plot\n", + " display(plot.show())" ] }, { @@ -429,6 +436,17 @@ "image_graphic == plot[\"sample-image\"]" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "058d9785-a692-46f6-a062-cdec9c040afe", + "metadata": {}, + "outputs": [], + "source": [ + "# close sidecar\n", + "sc.close()" + ] + }, { "cell_type": "markdown", "id": "5694dca1-1041-4e09-a1da-85b293c5af47", @@ -452,7 +470,10 @@ "\n", "plot_rgb.add_image(new_data, name=\"rgb-image\")\n", "\n", - "plot_rgb.show()" + "sc = Sidecar(title=\"RGB image\", layout={'width': '800px'})\n", + "\n", + "with sc:\n", + " display(plot_rgb.show())" ] }, { @@ -500,6 +521,17 @@ "plot_test(\"astronaut_RGB\", plot_rgb)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "8316b4f2-3d6e-46b5-8776-c7c963a7aa99", + "metadata": {}, + "outputs": [], + "source": [ + "# close sidecar\n", + "sc.close()" + ] + }, { "cell_type": "markdown", "id": "1cb03f42-1029-4b16-a16b-35447d9e2955", @@ -541,8 +573,11 @@ "#add this as an animation function\n", "plot_v.add_animations(update_data)\n", "\n", - "# show the plot\n", - "plot_v.show()" + "sc = Sidecar(title=\"random image\", layout={'width': '800px'})\n", + "\n", + "with sc:\n", + " # show the plot\n", + " display(plot_v.show())" ] }, { @@ -615,6 +650,17 @@ "HBox([plot_v.show(), plot_sync.show()])" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "f33f4cd9-02fc-41b7-961b-9dfeb455b63a", + "metadata": {}, + "outputs": [], + "source": [ + "# close sidecar\n", + "sc.close()" + ] + }, { "cell_type": "markdown", "id": "e7859338-8162-408b-ac72-37e606057045", @@ -688,7 +734,10 @@ "colors = [\"r\"] * 25 + [\"purple\"] * 25 + [\"y\"] * 25 + [\"b\"] * 25\n", "sinc_graphic = plot_l.add_line(data=sinc, thickness=5, colors = colors)\n", "\n", - "plot_l.show()" + "sc = Sidecar(title=\"lines\", layout={'width': '800px'})\n", + "\n", + "with sc: \n", + " display(plot_l.show())" ] }, { @@ -971,6 +1020,17 @@ "plot_test(\"lines-underlay\", plot_l)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "bef729ea-f524-4efd-a189-bfca23b39af5", + "metadata": {}, + "outputs": [], + "source": [ + "# close sidecar\n", + "sc.close()" + ] + }, { "cell_type": "markdown", "id": "2c90862e-2f2a-451f-a468-0cf6b857e87a", @@ -1093,7 +1153,10 @@ "# use an alpha value since this will be a lot of points\n", "scatter_graphic = plot_s.add_scatter(data=cloud, sizes=3, colors=colors, alpha=0.7)\n", "\n", - "plot_s.show()" + "sc = Sidecar()\n", + "\n", + "with sc:\n", + " display(plot_s.show())" ] }, { @@ -1159,6 +1222,17 @@ "scatter_graphic.data[n_points:n_points * 2, 0] = np.linspace(-40, 0, n_points)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "a9ffdde4-4b8e-4ff7-98b3-464cf5462d20", + "metadata": {}, + "outputs": [], + "source": [ + "# close sidecar\n", + "sc.close()" + ] + }, { "cell_type": "markdown", "id": "d9e554de-c436-4684-a46a-ce8a33d409ac", diff --git a/setup.py b/setup.py index 2616093fc..9a89d013b 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,8 @@ [ "jupyterlab", "jupyter-rfb>=0.4.1", - "ipywidgets>=8.0.0,<9" + "ipywidgets>=8.0.0,<9", + "sidecar" ], "tests": From eb71003a6ab1b80a1cdeca5b0f80306996686140 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis <69729525+clewis7@users.noreply.github.com> Date: Thu, 7 Sep 2023 18:37:02 -0400 Subject: [PATCH 02/10] Update setup.py --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9a89d013b..1d1204a69 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,8 @@ "jupyter-rfb>=0.4.1", "ipywidgets>=8.0.0,<9", "scikit-learn", - "tqdm" + "tqdm", + "sidecar" ] } From 7ba8dc776be05ae5ea8d0ae5cb3043d722bb63a8 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Mon, 11 Sep 2023 09:23:01 -0400 Subject: [PATCH 03/10] add sidecar for plots --- examples/notebooks/simple.ipynb | 70 ++++++++++++++++----------------- fastplotlib/layouts/_plot.py | 33 +++++++++++++++- setup.py | 3 +- 3 files changed, 67 insertions(+), 39 deletions(-) diff --git a/examples/notebooks/simple.ipynb b/examples/notebooks/simple.ipynb index 601d21aaf..482e87117 100644 --- a/examples/notebooks/simple.ipynb +++ b/examples/notebooks/simple.ipynb @@ -55,8 +55,7 @@ "source": [ "from fastplotlib import Plot\n", "from ipywidgets import VBox, HBox, IntSlider\n", - "import numpy as np\n", - "from sidecar import Sidecar" + "import numpy as np" ] }, { @@ -100,12 +99,8 @@ "# plot the image data\n", "image_graphic = plot.add_image(data=data, name=\"sample-image\")\n", "\n", - "# display plot in sidecar\n", - "sc = Sidecar(title=\"sample image\", layout={'width': '800px'})\n", - "\n", - "with sc:\n", - " # show the plot\n", - " display(plot.show())" + "# show the plot\n", + "plot.show(sidecar_kwargs={\"title\": \"sample image\", \"layout\": {'width': '800px'}})" ] }, { @@ -443,8 +438,8 @@ "metadata": {}, "outputs": [], "source": [ - "# close sidecar\n", - "sc.close()" + "# close the sidecar\n", + "plot.sidecar.close()" ] }, { @@ -470,10 +465,8 @@ "\n", "plot_rgb.add_image(new_data, name=\"rgb-image\")\n", "\n", - "sc = Sidecar(title=\"RGB image\", layout={'width': '800px'})\n", - "\n", - "with sc:\n", - " display(plot_rgb.show())" + "# show the plot\n", + "plot_rgb.show(sidecar_kwargs={\"title\": \"RGB image\", \"layout\": {'width': '800px'}})" ] }, { @@ -529,7 +522,7 @@ "outputs": [], "source": [ "# close sidecar\n", - "sc.close()" + "plot_rgb.sidecar.close()" ] }, { @@ -573,11 +566,8 @@ "#add this as an animation function\n", "plot_v.add_animations(update_data)\n", "\n", - "sc = Sidecar(title=\"random image\", layout={'width': '800px'})\n", - "\n", - "with sc:\n", - " # show the plot\n", - " display(plot_v.show())" + "# show the plot\n", + "plot_v.show(sidecar_kwargs={\"title\": \"random image\", \"layout\": {'width': '800px'}})" ] }, { @@ -611,7 +601,7 @@ "\n", "plot_sync.add_animations(update_data_2)\n", "\n", - "plot_sync.show()" + "plot_sync.show(sidecar=False)" ] }, { @@ -637,7 +627,7 @@ "metadata": {}, "outputs": [], "source": [ - "VBox([plot_v.show(), plot_sync.show()])" + "VBox([plot_v.show(sidecar=False), plot_sync.show(sidecar=False)])" ] }, { @@ -647,7 +637,7 @@ "metadata": {}, "outputs": [], "source": [ - "HBox([plot_v.show(), plot_sync.show()])" + "HBox([plot_v.show(sidecar=False), plot_sync.show(sidecar=False)])" ] }, { @@ -658,7 +648,7 @@ "outputs": [], "source": [ "# close sidecar\n", - "sc.close()" + "plot_v.sidecar.close()" ] }, { @@ -734,10 +724,8 @@ "colors = [\"r\"] * 25 + [\"purple\"] * 25 + [\"y\"] * 25 + [\"b\"] * 25\n", "sinc_graphic = plot_l.add_line(data=sinc, thickness=5, colors = colors)\n", "\n", - "sc = Sidecar(title=\"lines\", layout={'width': '800px'})\n", - "\n", - "with sc: \n", - " display(plot_l.show())" + "# show the plot\n", + "plot_l.show(sidecar_kwargs={\"title\": \"lines\", \"layout\": {'width': '800px'}})" ] }, { @@ -1028,7 +1016,7 @@ "outputs": [], "source": [ "# close sidecar\n", - "sc.close()" + "plot_l.sidecar.close()" ] }, { @@ -1090,6 +1078,19 @@ "plot_test(\"lines-3d\", plot_l3d)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "c2c70541-98fe-4e02-a718-ac2857cc25be", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# close sidecar\n", + "plot_l3d.sidecar.close()" + ] + }, { "cell_type": "markdown", "id": "a202b3d0-2a0b-450a-93d4-76d0a1129d1d", @@ -1153,10 +1154,7 @@ "# use an alpha value since this will be a lot of points\n", "scatter_graphic = plot_s.add_scatter(data=cloud, sizes=3, colors=colors, alpha=0.7)\n", "\n", - "sc = Sidecar()\n", - "\n", - "with sc:\n", - " display(plot_s.show())" + "plot_s.show()" ] }, { @@ -1230,7 +1228,7 @@ "outputs": [], "source": [ "# close sidecar\n", - "sc.close()" + "plot_s.sidecar.close()" ] }, { @@ -1250,8 +1248,8 @@ "metadata": {}, "outputs": [], "source": [ - "row1 = HBox([plot.show(), plot_v.show(), plot_sync.show()])\n", - "row2 = HBox([plot_l.show(), plot_l3d.show(), plot_s.show()])\n", + "row1 = HBox([plot.show(sidecar=False), plot_v.show(sidecar=False), plot_sync.show(sidecar=False)])\n", + "row2 = HBox([plot_l.show(sidecar=False), plot_l3d.show(sidecar=False), plot_s.show(sidecar=False)])\n", "\n", "VBox([row1, row2])" ] diff --git a/fastplotlib/layouts/_plot.py b/fastplotlib/layouts/_plot.py index 1f91bb303..4ff8dbd4d 100644 --- a/fastplotlib/layouts/_plot.py +++ b/fastplotlib/layouts/_plot.py @@ -4,10 +4,12 @@ import os import pygfx +from IPython.display import display from wgpu.gui.auto import WgpuCanvas, is_jupyter if is_jupyter(): from ipywidgets import HBox, Layout, Button, ToggleButton, VBox + from sidecar import Sidecar from ._subplot import Subplot from ._record_mixin import RecordMixin @@ -64,6 +66,7 @@ def __init__( self._starting_size = size self.toolbar = None + self.sidecar = None def render(self): super(Plot, self).render() @@ -72,7 +75,12 @@ def render(self): self.canvas.request_draw() def show( - self, autoscale: bool = True, maintain_aspect: bool = None, toolbar: bool = True + self, + autoscale: bool = True, + maintain_aspect: bool = None, + toolbar: bool = True, + sidecar: bool = True, + sidecar_kwargs: dict = None ): """ Begins the rendering event loop and returns the canvas @@ -88,6 +96,13 @@ def show( toolbar: bool, default True show toolbar + sidecar: bool, default True + display the plot in a ``jupyterlab-sidecar`` + + sidecar: dict, default None + kwargs for sidecar instance to display plot + i.e. title, layout + Returns ------- WgpuCanvas @@ -117,7 +132,18 @@ def show( self.toolbar = ToolBar(self) self.toolbar.maintain_aspect_button.value = maintain_aspect - return VBox([self.canvas, self.toolbar.widget]) + if not sidecar: + return VBox([self.canvas, self.toolbar.widget]) + + if self.sidecar is None: + # sidecar doesn't like unpacking when kwargs are `None` + if sidecar_kwargs is not None: + self.sidecar = Sidecar(**sidecar_kwargs) + else: + self.sidecar = Sidecar() + + with self.sidecar: + return display(VBox([self.canvas, self.toolbar.widget])) def close(self): """Close Plot""" @@ -126,6 +152,9 @@ def close(self): if self.toolbar is not None: self.toolbar.widget.close() + if self.sidecar is not None: + self.sidecar.close() + class ToolBar: def __init__(self, plot: Plot): diff --git a/setup.py b/setup.py index 9a89d013b..1d1204a69 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,8 @@ "jupyter-rfb>=0.4.1", "ipywidgets>=8.0.0,<9", "scikit-learn", - "tqdm" + "tqdm", + "sidecar" ] } From 1478da8ef7534af8a0f779176daaf5d82bd22dc1 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Tue, 12 Sep 2023 07:40:42 -0400 Subject: [PATCH 04/10] sidecar updates --- examples/notebooks/simple.ipynb | 18 +++++++++++++++--- fastplotlib/layouts/_plot.py | 18 +++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/examples/notebooks/simple.ipynb b/examples/notebooks/simple.ipynb index 482e87117..a79f7a954 100644 --- a/examples/notebooks/simple.ipynb +++ b/examples/notebooks/simple.ipynb @@ -100,7 +100,7 @@ "image_graphic = plot.add_image(data=data, name=\"sample-image\")\n", "\n", "# show the plot\n", - "plot.show(sidecar_kwargs={\"title\": \"sample image\", \"layout\": {'width': '800px'}})" + "plot.show()" ] }, { @@ -327,6 +327,18 @@ "plot_test(\"astronaut\", plot)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "0bb1cfc7-1a06-4abb-a10a-a877a0d51c6b", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "plot.canvas.get_logical_size()" + ] + }, { "cell_type": "markdown", "id": "b53bc11a-ddf1-4786-8dca-8f3d2eaf993d", @@ -466,7 +478,7 @@ "plot_rgb.add_image(new_data, name=\"rgb-image\")\n", "\n", "# show the plot\n", - "plot_rgb.show(sidecar_kwargs={\"title\": \"RGB image\", \"layout\": {'width': '800px'}})" + "plot_rgb.show()" ] }, { @@ -567,7 +579,7 @@ "plot_v.add_animations(update_data)\n", "\n", "# show the plot\n", - "plot_v.show(sidecar_kwargs={\"title\": \"random image\", \"layout\": {'width': '800px'}})" + "plot_v.show()" ] }, { diff --git a/fastplotlib/layouts/_plot.py b/fastplotlib/layouts/_plot.py index 4ff8dbd4d..32707a960 100644 --- a/fastplotlib/layouts/_plot.py +++ b/fastplotlib/layouts/_plot.py @@ -4,17 +4,19 @@ import os import pygfx -from IPython.display import display from wgpu.gui.auto import WgpuCanvas, is_jupyter if is_jupyter(): from ipywidgets import HBox, Layout, Button, ToggleButton, VBox from sidecar import Sidecar + from IPython.display import display from ._subplot import Subplot from ._record_mixin import RecordMixin from ..graphics.selectors import PolygonSelector +PLOT_OPEN = False + class Plot(Subplot, RecordMixin): def __init__( @@ -99,7 +101,7 @@ def show( sidecar: bool, default True display the plot in a ``jupyterlab-sidecar`` - sidecar: dict, default None + sidecar_kwargs: dict, default None kwargs for sidecar instance to display plot i.e. title, layout @@ -109,6 +111,9 @@ def show( the canvas """ + # define global PLOT_OPEN to use from outer scope + global PLOT_OPEN + self.canvas.request_draw(self.render) self.canvas.set_logical_size(*self._starting_size) @@ -135,12 +140,19 @@ def show( if not sidecar: return VBox([self.canvas, self.toolbar.widget]) + # used when plot.show() is being called again but sidecar has been closed via "x" button + # need to force new sidecar instance + # couldn't figure out how to get access to "close" button in order to add observe method on click + if PLOT_OPEN: + self.sidecar = None + if self.sidecar is None: - # sidecar doesn't like unpacking when kwargs are `None` if sidecar_kwargs is not None: self.sidecar = Sidecar(**sidecar_kwargs) + PLOT_OPEN = True else: self.sidecar = Sidecar() + PLOT_OPEN = True with self.sidecar: return display(VBox([self.canvas, self.toolbar.widget])) From 283211ae390070b44933dc548d6bbe6df24f1d84 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Wed, 13 Sep 2023 09:00:22 -0400 Subject: [PATCH 05/10] add sidecar to gridplot --- examples/notebooks/gridplot_simple.ipynb | 119 +++++++++++++---------- fastplotlib/layouts/_gridplot.py | 46 ++++++++- fastplotlib/layouts/_plot.py | 6 +- 3 files changed, 113 insertions(+), 58 deletions(-) diff --git a/examples/notebooks/gridplot_simple.ipynb b/examples/notebooks/gridplot_simple.ipynb index f90c0b157..8b50b2701 100644 --- a/examples/notebooks/gridplot_simple.ipynb +++ b/examples/notebooks/gridplot_simple.ipynb @@ -12,7 +12,9 @@ "cell_type": "code", "execution_count": 1, "id": "5171a06e-1bdc-4908-9726-3c1fd45dbb9d", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "import numpy as np\n", @@ -23,12 +25,14 @@ "cell_type": "code", "execution_count": 2, "id": "86a2488f-ae1c-4b98-a7c0-18eae8013af1", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "5e4e0c5ca610425b8216db8e30cae997", + "model_id": "f9067cd724094b8c8dfecf60208acbfa", "version_major": 2, "version_minor": 0 }, @@ -40,31 +44,12 @@ "output_type": "display_data" }, { - "data": { - "text/html": [ - "
initial snapshot
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "1eeb8c42e1b24c4fb40e3b5daa63909a", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "JupyterWgpuCanvas()" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/clewis7/repos/fastplotlib/fastplotlib/graphics/_features/_base.py:34: UserWarning: converting float64 array to float32\n", + " warn(f\"converting {array.dtype} array to float32\")\n" + ] } ], "source": [ @@ -105,15 +90,18 @@ "cell_type": "code", "execution_count": 3, "id": "17c6bc4a-5340-49f1-8597-f54528cfe915", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { "text/plain": [ - "unnamed: Subplot @ 0x7fd4cc9bf820\n", - " parent: None\n", + "unnamed: Subplot @ 0x7f15df4f5c50\n", + " parent: fastplotlib.GridPlot @ 0x7f15d3f27890\n", + "\n", " Graphics:\n", - "\t'rand-img': ImageGraphic @ 0x7fd4f675a350" + "\t'rand-img': ImageGraphic @ 0x7f15d3fb5390" ] }, "execution_count": 3, @@ -139,12 +127,14 @@ "cell_type": "code", "execution_count": 4, "id": "34130f12-9ef6-43b0-b929-931de8b7da25", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { "text/plain": [ - "('rand-img': ImageGraphic @ 0x7fd4a03295a0,)" + "(,)" ] }, "execution_count": 4, @@ -166,12 +156,14 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 11, "id": "ef8a29a6-b19c-4e6b-a2ba-fb4823c01451", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "grid_plot[0, 1].graphics[0].vmax = 0.5" + "grid_plot[0, 1].graphics[0].cmap.vmax = 0.5" ] }, { @@ -186,7 +178,9 @@ "cell_type": "code", "execution_count": 6, "id": "d6c2fa4b-c634-4dcf-8b61-f1986f7c4918", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "# you can give subplots human-readable string names\n", @@ -197,15 +191,18 @@ "cell_type": "code", "execution_count": 7, "id": "2f6b549c-3165-496d-98aa-45b96c3de674", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { "text/plain": [ - "top-right-plot: Subplot @ 0x7fd4cca0ffd0\n", - " parent: None\n", + "top-right-plot: Subplot @ 0x7f15d3f769d0\n", + " parent: fastplotlib.GridPlot @ 0x7f15d3f27890\n", + "\n", " Graphics:\n", - "\t'rand-img': ImageGraphic @ 0x7fd4a03716c0" + "\t'rand-img': ImageGraphic @ 0x7f15b83f7250" ] }, "execution_count": 7, @@ -219,9 +216,11 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 12, "id": "be436e04-33a6-4597-8e6a-17e1e5225419", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { @@ -229,7 +228,7 @@ "(0, 2)" ] }, - "execution_count": 8, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -241,9 +240,11 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 13, "id": "6699cda6-af86-4258-87f5-1832f989a564", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { @@ -251,7 +252,7 @@ "True" ] }, - "execution_count": 9, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -271,9 +272,11 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 14, "id": "545b627b-d794-459a-a75a-3fde44f0ea95", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "grid_plot[\"top-right-plot\"][\"rand-img\"].vmin = 0.5" @@ -281,8 +284,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "36432d5b-b76c-4a2a-a32c-097faf5ab269", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "grid_plot.close()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b507b723-1371-44e7-aa6d-6aeb3196b27d", "metadata": {}, "outputs": [], "source": [] @@ -304,7 +319,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.5" + "version": "3.11.3" } }, "nbformat": 4, diff --git a/fastplotlib/layouts/_gridplot.py b/fastplotlib/layouts/_gridplot.py index b339e8659..c14769a54 100644 --- a/fastplotlib/layouts/_gridplot.py +++ b/fastplotlib/layouts/_gridplot.py @@ -13,12 +13,16 @@ if is_jupyter(): from ipywidgets import HBox, Layout, Button, ToggleButton, VBox, Dropdown + from sidecar import Sidecar + from IPython.display import display from ._utils import make_canvas_and_renderer from ._defaults import create_controller from ._subplot import Subplot from ._record_mixin import RecordMixin +PLOT_OPEN = False + def to_array(a) -> np.ndarray: if isinstance(a, np.ndarray): @@ -81,6 +85,7 @@ def __init__( self.shape = shape self.toolbar = None + self.sidecar = None canvas, renderer = make_canvas_and_renderer(canvas, renderer) @@ -294,7 +299,12 @@ def remove_animation(self, func): self._animate_funcs_post.remove(func) def show( - self, autoscale: bool = True, maintain_aspect: bool = None, toolbar: bool = True + self, + autoscale: bool = True, + maintain_aspect: bool = None, + toolbar: bool = True, + sidecar: bool = True, + sidecar_kwargs: dict = None ): """ Begins the rendering event loop and returns the canvas @@ -307,15 +317,24 @@ def show( maintain_aspect: bool, default ``True`` maintain aspect ratio - toolbar: bool, default True + toolbar: bool, default ``True`` show toolbar + sidecar: bool, default ``True`` + display plot in a ``jupyterlab-sidecar`` + + sidecar_kwargs: dict, default ``None`` + kwargs for sidecar instance to display plot + i.e. title, layout + Returns ------- WgpuCanvas the canvas """ + global PLOT_OPEN + self.canvas.request_draw(self.render) self.canvas.set_logical_size(*self._starting_size) @@ -343,7 +362,25 @@ def show( 0, 0 ].camera.maintain_aspect - return VBox([self.canvas, self.toolbar.widget]) + if not sidecar: + return VBox([self.canvas, self.toolbar.widget]) + + # used when plot.show() is being called again but sidecar has been closed via "x" button + # need to force new sidecar instance + # couldn't figure out how to get access to "close" button in order to add observe method on click + if PLOT_OPEN: + self.sidecar = None + + if self.sidecar is None: + if sidecar_kwargs is not None: + self.sidecar = Sidecar(**sidecar_kwargs) + PLOT_OPEN = True + else: + self.sidecar = Sidecar() + PLOT_OPEN = True + + with self.sidecar: + return display(VBox([self.canvas, self.toolbar.widget])) def close(self): """Close the GridPlot""" @@ -352,6 +389,9 @@ def close(self): if self.toolbar is not None: self.toolbar.widget.close() + if self.sidecar is not None: + self.sidecar.close() + def clear(self): """Clear all Subplots""" for subplot in self: diff --git a/fastplotlib/layouts/_plot.py b/fastplotlib/layouts/_plot.py index 32707a960..4869ace61 100644 --- a/fastplotlib/layouts/_plot.py +++ b/fastplotlib/layouts/_plot.py @@ -95,13 +95,13 @@ def show( maintain_aspect: bool, default ``None`` maintain aspect ratio, uses ``camera.maintain_aspect`` if ``None`` - toolbar: bool, default True + toolbar: bool, default ``True`` show toolbar - sidecar: bool, default True + sidecar: bool, default ``True`` display the plot in a ``jupyterlab-sidecar`` - sidecar_kwargs: dict, default None + sidecar_kwargs: dict, default ``None`` kwargs for sidecar instance to display plot i.e. title, layout From 3c188c033aa9110d1fa5402fab7b2d22681361c8 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Fri, 15 Sep 2023 03:56:11 -0400 Subject: [PATCH 06/10] add close method to image widget, add sidecar to image widget --- fastplotlib/layouts/_gridplot.py | 12 +++---- fastplotlib/layouts/_plot.py | 13 ++++---- fastplotlib/widgets/image.py | 57 +++++++++++++++++++++++++++----- 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/fastplotlib/layouts/_gridplot.py b/fastplotlib/layouts/_gridplot.py index c14769a54..b8d5db062 100644 --- a/fastplotlib/layouts/_gridplot.py +++ b/fastplotlib/layouts/_gridplot.py @@ -21,8 +21,6 @@ from ._subplot import Subplot from ._record_mixin import RecordMixin -PLOT_OPEN = False - def to_array(a) -> np.ndarray: if isinstance(a, np.ndarray): @@ -86,6 +84,7 @@ def __init__( self.shape = shape self.toolbar = None self.sidecar = None + self.plot_open = False canvas, renderer = make_canvas_and_renderer(canvas, renderer) @@ -333,7 +332,6 @@ def show( the canvas """ - global PLOT_OPEN self.canvas.request_draw(self.render) @@ -368,16 +366,16 @@ def show( # used when plot.show() is being called again but sidecar has been closed via "x" button # need to force new sidecar instance # couldn't figure out how to get access to "close" button in order to add observe method on click - if PLOT_OPEN: + if self.plot_open: self.sidecar = None if self.sidecar is None: if sidecar_kwargs is not None: self.sidecar = Sidecar(**sidecar_kwargs) - PLOT_OPEN = True + self.plot_open = True else: self.sidecar = Sidecar() - PLOT_OPEN = True + self.plot_open = True with self.sidecar: return display(VBox([self.canvas, self.toolbar.widget])) @@ -392,6 +390,8 @@ def close(self): if self.sidecar is not None: self.sidecar.close() + self.plot_open = False + def clear(self): """Clear all Subplots""" for subplot in self: diff --git a/fastplotlib/layouts/_plot.py b/fastplotlib/layouts/_plot.py index 4869ace61..9e2f9441f 100644 --- a/fastplotlib/layouts/_plot.py +++ b/fastplotlib/layouts/_plot.py @@ -15,8 +15,6 @@ from ._record_mixin import RecordMixin from ..graphics.selectors import PolygonSelector -PLOT_OPEN = False - class Plot(Subplot, RecordMixin): def __init__( @@ -69,6 +67,7 @@ def __init__( self.toolbar = None self.sidecar = None + self.plot_open = False def render(self): super(Plot, self).render() @@ -111,8 +110,6 @@ def show( the canvas """ - # define global PLOT_OPEN to use from outer scope - global PLOT_OPEN self.canvas.request_draw(self.render) @@ -143,16 +140,16 @@ def show( # used when plot.show() is being called again but sidecar has been closed via "x" button # need to force new sidecar instance # couldn't figure out how to get access to "close" button in order to add observe method on click - if PLOT_OPEN: + if self.plot_open: self.sidecar = None if self.sidecar is None: if sidecar_kwargs is not None: self.sidecar = Sidecar(**sidecar_kwargs) - PLOT_OPEN = True + self.plot_open = True else: self.sidecar = Sidecar() - PLOT_OPEN = True + self.plot_open = True with self.sidecar: return display(VBox([self.canvas, self.toolbar.widget])) @@ -167,6 +164,8 @@ def close(self): if self.sidecar is not None: self.sidecar.close() + self.plot_open = False + class ToolBar: def __init__(self, plot: Plot): diff --git a/fastplotlib/widgets/image.py b/fastplotlib/widgets/image.py index 962a94151..db718b638 100644 --- a/fastplotlib/widgets/image.py +++ b/fastplotlib/widgets/image.py @@ -17,6 +17,8 @@ Play, jslink, ) +from sidecar import Sidecar +from IPython.display import display from ..layouts import GridPlot from ..graphics import ImageGraphic @@ -271,6 +273,8 @@ def __init__( self._names = None self.toolbar = None + self.sidecar = None + self.plot_open = False if isinstance(data, list): # verify that it's a list of np.ndarray @@ -913,7 +917,7 @@ def set_data( if reset_vmin_vmax: self.reset_vmin_vmax() - def show(self, toolbar: bool = True): + def show(self, toolbar: bool = True, sidecar: bool = True, sidecar_kwargs: dict = None): """ Show the widget @@ -930,13 +934,50 @@ def show(self, toolbar: bool = True): if self.toolbar is None: self.toolbar = ImageWidgetToolbar(self) - return VBox( - [ - self.gridplot.show(toolbar=True), - self.toolbar.widget, - self._vbox_sliders, - ] - ) + if not sidecar: + return VBox( + [ + self.gridplot.show(toolbar=True, sidecar=False, sidecar_kwargs=None), + self.toolbar.widget, + self._vbox_sliders, + ] + ) + + if self.plot_open: + self.sidecar = None + + if self.sidecar is None: + if sidecar_kwargs is not None: + self.sidecar = Sidecar(**sidecar_kwargs) + self.plot_open = True + else: + self.sidecar = Sidecar() + self.plot_open = True + + with self.sidecar: + return display(VBox( + [ + self.gridplot.show(toolbar=True, sidecar=False, sidecar_kwargs=None), + self.toolbar.widget, + self._vbox_sliders + ] + ) + ) + + def close(self): + """Close Widget""" + self.gridplot.canvas.close() + + self._vbox_sliders.close() + + if self.toolbar is not None: + self.toolbar.widget.close() + self.gridplot.toolbar.widget.close() + + if self.sidecar is not None: + self.sidecar.close() + + self.plot_open = False class ImageWidgetToolbar: From 4ed458d94e10a28b56118d4527a879e3af6d9765 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Fri, 15 Sep 2023 04:22:57 -0400 Subject: [PATCH 07/10] fix notebook errors --- examples/notebooks/linear_region_selector.ipynb | 6 +++--- examples/notebooks/linear_selector.ipynb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/notebooks/linear_region_selector.ipynb b/examples/notebooks/linear_region_selector.ipynb index 11cd3a490..859893297 100644 --- a/examples/notebooks/linear_region_selector.ipynb +++ b/examples/notebooks/linear_region_selector.ipynb @@ -83,7 +83,7 @@ "ls_x.selection.add_event_handler(set_zoom_x)\n", "ls_y.selection.add_event_handler(set_zoom_y)\n", "\n", - "gp.show()" + "gp.show(sidecar=False)" ] }, { @@ -199,7 +199,7 @@ "\n", "\n", "selector.selection.add_event_handler(update_zoomed_subplots)\n", - "plot.show()" + "plot.show(sidecar=False)" ] }, { @@ -253,7 +253,7 @@ "\n", "\n", "stack_selector.selection.add_event_handler(update_zoomed_stack)\n", - "plot.show()" + "plot.show(sidecar=False)" ] }, { diff --git a/examples/notebooks/linear_selector.ipynb b/examples/notebooks/linear_selector.ipynb index a4d6b97ea..463d7fbfa 100644 --- a/examples/notebooks/linear_selector.ipynb +++ b/examples/notebooks/linear_selector.ipynb @@ -52,7 +52,7 @@ "\n", "plot.auto_scale()\n", "plot.show()\n", - "VBox([plot.show(), ipywidget_slider])" + "VBox([plot.show(sidecar=False), ipywidget_slider])" ] }, { From 1f0fa3782a9257082a7a4ed21dfbe292ed2dbd88 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Sat, 23 Sep 2023 17:55:04 -0400 Subject: [PATCH 08/10] fix linear region selector --- examples/notebooks/linear_region_selector.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/notebooks/linear_region_selector.ipynb b/examples/notebooks/linear_region_selector.ipynb index 859893297..11cd3a490 100644 --- a/examples/notebooks/linear_region_selector.ipynb +++ b/examples/notebooks/linear_region_selector.ipynb @@ -83,7 +83,7 @@ "ls_x.selection.add_event_handler(set_zoom_x)\n", "ls_y.selection.add_event_handler(set_zoom_y)\n", "\n", - "gp.show(sidecar=False)" + "gp.show()" ] }, { @@ -199,7 +199,7 @@ "\n", "\n", "selector.selection.add_event_handler(update_zoomed_subplots)\n", - "plot.show(sidecar=False)" + "plot.show()" ] }, { @@ -253,7 +253,7 @@ "\n", "\n", "stack_selector.selection.add_event_handler(update_zoomed_stack)\n", - "plot.show(sidecar=False)" + "plot.show()" ] }, { From faa6154abf6b4e8afcfa556dcb5998ab56277aab Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Sat, 23 Sep 2023 18:21:07 -0400 Subject: [PATCH 09/10] add vbox as kwarg for additional ipywidgets when showing plot and gridplot --- fastplotlib/layouts/_gridplot.py | 29 +++++++++++++++++++++++++---- fastplotlib/layouts/_plot.py | 30 ++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/fastplotlib/layouts/_gridplot.py b/fastplotlib/layouts/_gridplot.py index b8d5db062..e619722d3 100644 --- a/fastplotlib/layouts/_gridplot.py +++ b/fastplotlib/layouts/_gridplot.py @@ -12,7 +12,7 @@ from wgpu.gui.auto import WgpuCanvas, is_jupyter if is_jupyter(): - from ipywidgets import HBox, Layout, Button, ToggleButton, VBox, Dropdown + from ipywidgets import HBox, Layout, Button, ToggleButton, VBox, Dropdown, Widget from sidecar import Sidecar from IPython.display import display @@ -84,6 +84,7 @@ def __init__( self.shape = shape self.toolbar = None self.sidecar = None + self.vbox = None self.plot_open = False canvas, renderer = make_canvas_and_renderer(canvas, renderer) @@ -303,7 +304,8 @@ def show( maintain_aspect: bool = None, toolbar: bool = True, sidecar: bool = True, - sidecar_kwargs: dict = None + sidecar_kwargs: dict = None, + vbox: list = None ): """ Begins the rendering event loop and returns the canvas @@ -326,6 +328,9 @@ def show( kwargs for sidecar instance to display plot i.e. title, layout + vbox: list, default ``None`` + list of ipywidgets to be displayed with plot + Returns ------- WgpuCanvas @@ -360,8 +365,18 @@ def show( 0, 0 ].camera.maintain_aspect + # validate vbox if not None + if vbox is not None: + for widget in vbox: + if not isinstance(widget, Widget): + raise ValueError(f"Items in vbox must be ipywidgets. Item: {widget} is of type: {type(widget)}") + self.vbox = VBox(vbox) + if not sidecar: - return VBox([self.canvas, self.toolbar.widget]) + if self.vbox is not None: + return VBox([self.canvas, self.toolbar.widget, self.vbox]) + else: + return VBox([self.canvas, self.toolbar.widget]) # used when plot.show() is being called again but sidecar has been closed via "x" button # need to force new sidecar instance @@ -378,7 +393,10 @@ def show( self.plot_open = True with self.sidecar: - return display(VBox([self.canvas, self.toolbar.widget])) + if self.vbox is not None: + return display(VBox([self.canvas, self.toolbar.widget, self.vbox])) + else: + return display(VBox([self.canvas, self.toolbar.widget])) def close(self): """Close the GridPlot""" @@ -390,6 +408,9 @@ def close(self): if self.sidecar is not None: self.sidecar.close() + if self.vbox is not None: + self.vbox.close() + self.plot_open = False def clear(self): diff --git a/fastplotlib/layouts/_plot.py b/fastplotlib/layouts/_plot.py index 9e2f9441f..9f6df89e0 100644 --- a/fastplotlib/layouts/_plot.py +++ b/fastplotlib/layouts/_plot.py @@ -3,11 +3,12 @@ import traceback import os +import ipywidgets import pygfx from wgpu.gui.auto import WgpuCanvas, is_jupyter if is_jupyter(): - from ipywidgets import HBox, Layout, Button, ToggleButton, VBox + from ipywidgets import HBox, Layout, Button, ToggleButton, VBox, Widget from sidecar import Sidecar from IPython.display import display @@ -67,6 +68,7 @@ def __init__( self.toolbar = None self.sidecar = None + self.vbox = None self.plot_open = False def render(self): @@ -81,7 +83,8 @@ def show( maintain_aspect: bool = None, toolbar: bool = True, sidecar: bool = True, - sidecar_kwargs: dict = None + sidecar_kwargs: dict = None, + vbox: list = None ): """ Begins the rendering event loop and returns the canvas @@ -104,6 +107,9 @@ def show( kwargs for sidecar instance to display plot i.e. title, layout + vbox: list, default ``None`` + list of ipywidgets to be displayed with plot + Returns ------- WgpuCanvas @@ -134,8 +140,18 @@ def show( self.toolbar = ToolBar(self) self.toolbar.maintain_aspect_button.value = maintain_aspect + # validate vbox if not None + if vbox is not None: + for widget in vbox: + if not isinstance(widget, Widget): + raise ValueError(f"Items in vbox must be ipywidgets. Item: {widget} is of type: {type(widget)}") + self.vbox = VBox(vbox) + if not sidecar: - return VBox([self.canvas, self.toolbar.widget]) + if self.vbox is not None: + return VBox([self.canvas, self.toolbar.widget, self.vbox]) + else: + return VBox([self.canvas, self.toolbar.widget]) # used when plot.show() is being called again but sidecar has been closed via "x" button # need to force new sidecar instance @@ -152,7 +168,10 @@ def show( self.plot_open = True with self.sidecar: - return display(VBox([self.canvas, self.toolbar.widget])) + if self.vbox is not None: + return display(VBox([self.canvas, self.toolbar.widget, self.vbox])) + else: + return display(VBox([self.canvas, self.toolbar.widget])) def close(self): """Close Plot""" @@ -164,6 +183,9 @@ def close(self): if self.sidecar is not None: self.sidecar.close() + if self.vbox is not None: + self.vbox.close() + self.plot_open = False From ea546cc2d56583ddbba6e4a37f680ef65c97ae60 Mon Sep 17 00:00:00 2001 From: Caitlin Lewis Date: Sat, 23 Sep 2023 18:33:51 -0400 Subject: [PATCH 10/10] fix notebooks --- .../notebooks/linear_region_selector.ipynb | 275 ++++++++++++++++++ examples/notebooks/linear_selector.ipynb | 144 +++++++++ 2 files changed, 419 insertions(+) create mode 100644 examples/notebooks/linear_region_selector.ipynb create mode 100644 examples/notebooks/linear_selector.ipynb diff --git a/examples/notebooks/linear_region_selector.ipynb b/examples/notebooks/linear_region_selector.ipynb new file mode 100644 index 000000000..43cea4f81 --- /dev/null +++ b/examples/notebooks/linear_region_selector.ipynb @@ -0,0 +1,275 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1db50ec4-8754-4421-9f5e-6ba8ca6b81e3", + "metadata": {}, + "source": [ + "# `LinearRegionSelector` with single lines" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7bbfeb4-1ad0-47db-9a82-3d3f642a1f63", + "metadata": {}, + "outputs": [], + "source": [ + "import fastplotlib as fpl\n", + "import numpy as np\n", + "from ipywidgets import IntRangeSlider, FloatRangeSlider, VBox\n", + "\n", + "gp = fpl.GridPlot((2, 2))\n", + "\n", + "# preallocated size for zoomed data\n", + "zoomed_prealloc = 1_000\n", + "\n", + "# data to plot\n", + "xs = np.linspace(0, 100, 1_000)\n", + "sine = np.sin(xs) * 20\n", + "\n", + "# make sine along x axis\n", + "sine_graphic_x = gp[0, 0].add_line(sine)\n", + "\n", + "# just something that looks different for line along y-axis\n", + "sine_y = sine\n", + "sine_y[sine_y > 0] = 0\n", + "\n", + "# sine along y axis\n", + "sine_graphic_y = gp[0, 1].add_line(np.column_stack([sine_y, xs]))\n", + "\n", + "# offset the position of the graphic to demonstrate `get_selected_data()` later\n", + "sine_graphic_y.position_x = 50\n", + "sine_graphic_y.position_y = 50\n", + "\n", + "# add linear selectors\n", + "ls_x = sine_graphic_x.add_linear_region_selector() # default axis is \"x\"\n", + "ls_y = sine_graphic_y.add_linear_region_selector(axis=\"y\")\n", + "\n", + "# preallocate array for storing zoomed in data\n", + "zoomed_init = np.column_stack([np.arange(zoomed_prealloc), np.random.rand(zoomed_prealloc)])\n", + "\n", + "# make line graphics for displaying zoomed data\n", + "zoomed_x = gp[1, 0].add_line(zoomed_init)\n", + "zoomed_y = gp[1, 1].add_line(zoomed_init)\n", + "\n", + "\n", + "def interpolate(subdata: np.ndarray, axis: int):\n", + " \"\"\"1D interpolation to display within the preallocated data array\"\"\"\n", + " x = np.arange(0, zoomed_prealloc)\n", + " xp = np.linspace(0, zoomed_prealloc, subdata.shape[0])\n", + " \n", + " # interpolate to preallocated size\n", + " return np.interp(x, xp, fp=subdata[:, axis]) # use the y-values\n", + "\n", + "\n", + "def set_zoom_x(ev):\n", + " \"\"\"sets zoomed x selector data\"\"\"\n", + " selected_data = ev.pick_info[\"selected_data\"]\n", + " zoomed_x.data = interpolate(selected_data, axis=1) # use the y-values\n", + " gp[1, 0].auto_scale()\n", + "\n", + "\n", + "def set_zoom_y(ev):\n", + " \"\"\"sets zoomed y selector data\"\"\"\n", + " selected_data = ev.pick_info[\"selected_data\"]\n", + " zoomed_y.data = -interpolate(selected_data, axis=0) # use the x-values\n", + " gp[1, 1].auto_scale()\n", + "\n", + "\n", + "# update zoomed plots when bounds change\n", + "ls_x.selection.add_event_handler(set_zoom_x)\n", + "ls_y.selection.add_event_handler(set_zoom_y)\n", + "\n", + "gp.show()" + ] + }, + { + "cell_type": "markdown", + "id": "0bad4a35-f860-4f85-9061-920154ab682b", + "metadata": {}, + "source": [ + "### On the x-axis we have a 1-1 mapping from the data that we have passed and the line geometry positions. So the `bounds` min max corresponds directly to the data indices." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c96a3ff-c2e7-4683-8097-8491e97dd6d3", + "metadata": {}, + "outputs": [], + "source": [ + "ls_x.selection()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ec71e3f-291c-43c6-a954-0a082ba5981c", + "metadata": {}, + "outputs": [], + "source": [ + "ls_x.get_selected_indices()" + ] + }, + { + "cell_type": "markdown", + "id": "1588a89e-1da4-4ada-92e2-7437ba942065", + "metadata": {}, + "source": [ + "### However, for the y-axis line we have passed a 2D array where we've used a linspace, so there is not a 1-1 mapping from the data to the line geometry positions. Use `get_selected_indices()` to get the indices of the data bounded by the current selection. In addition the position of the Graphic is not `(0, 0)`. You must use `get_selected_indices()` whenever you want the indices of the selected data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18e10277-6d5d-42fe-8715-1733efabefa0", + "metadata": {}, + "outputs": [], + "source": [ + "ls_y.selection()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8e9c42b9-60d2-4544-96c5-c8c6832b79e3", + "metadata": {}, + "outputs": [], + "source": [ + "ls_y.get_selected_indices()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a9583d2e-ec52-405c-a875-f3fec5e3aa16", + "metadata": {}, + "outputs": [], + "source": [ + "import fastplotlib as fpl\n", + "import numpy as np\n", + "\n", + "# data to plot\n", + "xs = np.linspace(0, 100, 1_000)\n", + "sine = np.sin(xs) * 20\n", + "cosine = np.cos(xs) * 20\n", + "\n", + "plot = fpl.GridPlot((5, 1))\n", + "\n", + "# sines and cosines\n", + "sines = [sine] * 2\n", + "cosines = [cosine] * 2\n", + "\n", + "# make line stack\n", + "line_stack = plot[0, 0].add_line_stack(sines + cosines, separation=50)\n", + "\n", + "# make selector\n", + "selector = line_stack.add_linear_region_selector()\n", + "\n", + "# populate subplots with preallocated graphics\n", + "for i, subplot in enumerate(plot):\n", + " if i == 0:\n", + " # skip the first one\n", + " continue\n", + " # make line graphics for displaying zoomed data\n", + " subplot.add_line(zoomed_init, name=\"zoomed\")\n", + "\n", + "\n", + "def update_zoomed_subplots(ev):\n", + " \"\"\"update the zoomed subplots\"\"\"\n", + " zoomed_data = selector.get_selected_data()\n", + " \n", + " for i in range(len(zoomed_data)):\n", + " data = interpolate(zoomed_data[i], axis=1)\n", + " plot[i + 1, 0][\"zoomed\"].data = data\n", + " plot[i + 1, 0].auto_scale()\n", + "\n", + "\n", + "selector.selection.add_event_handler(update_zoomed_subplots)\n", + "plot.show()" + ] + }, + { + "cell_type": "markdown", + "id": "0fa051b5-d6bc-4e4e-8f12-44f638a00c88", + "metadata": {}, + "source": [ + "# Large line stack with selector" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d5ffb678-c989-49ee-85a9-4fd7822f033c", + "metadata": {}, + "outputs": [], + "source": [ + "import fastplotlib as fpl\n", + "import numpy as np\n", + "\n", + "# data to plot\n", + "xs = np.linspace(0, 250, 10_000)\n", + "sine = np.sin(xs) * 20\n", + "cosine = np.cos(xs) * 20\n", + "\n", + "plot = fpl.GridPlot((1, 2))\n", + "\n", + "# sines and cosines\n", + "sines = [sine] * 1_00\n", + "cosines = [cosine] * 1_00\n", + "\n", + "# make line stack\n", + "line_stack = plot[0, 0].add_line_stack(sines + cosines, separation=50)\n", + "\n", + "# make selector\n", + "stack_selector = line_stack.add_linear_region_selector(padding=200)\n", + "\n", + "zoomed_line_stack = plot[0, 1].add_line_stack([zoomed_init] * 2_000, separation=50, name=\"zoomed\")\n", + " \n", + "def update_zoomed_stack(ev):\n", + " \"\"\"update the zoomed subplots\"\"\"\n", + " zoomed_data = stack_selector.get_selected_data()\n", + " \n", + " for i in range(len(zoomed_data)):\n", + " data = interpolate(zoomed_data[i], axis=1)\n", + " zoomed_line_stack.graphics[i].data = data\n", + " \n", + " plot[0, 1].auto_scale()\n", + "\n", + "\n", + "stack_selector.selection.add_event_handler(update_zoomed_stack)\n", + "plot.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cbcd6309-fb47-4941-9fd1-2b091feb3ae7", + "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.11.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/notebooks/linear_selector.ipynb b/examples/notebooks/linear_selector.ipynb new file mode 100644 index 000000000..9382ffa63 --- /dev/null +++ b/examples/notebooks/linear_selector.ipynb @@ -0,0 +1,144 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a06e1fd9-47df-42a3-a76c-19e23d7b89fd", + "metadata": {}, + "source": [ + "## `LinearSelector`, draggable selector that can optionally associated with an ipywidget." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb95ba19-14b5-4bf4-93d9-05182fa500cb", + "metadata": {}, + "outputs": [], + "source": [ + "import fastplotlib as fpl\n", + "from fastplotlib.graphics.selectors import Synchronizer\n", + "\n", + "import numpy as np\n", + "from ipywidgets import VBox, IntSlider, FloatSlider\n", + "\n", + "plot = fpl.Plot()\n", + "\n", + "# data to plot\n", + "xs = np.linspace(0, 100, 1000)\n", + "sine = np.sin(xs) * 20\n", + "\n", + "# make sine along x axis\n", + "sine_graphic = plot.add_line(np.column_stack([xs, sine]).astype(np.float32))\n", + "\n", + "# make some selectors\n", + "selector = sine_graphic.add_linear_selector()\n", + "selector2 = sine_graphic.add_linear_selector(20)\n", + "selector3 = sine_graphic.add_linear_selector(40)\n", + "\n", + "ss = Synchronizer(selector, selector2, selector3)\n", + "\n", + "def set_color_at_index(ev):\n", + " # changes the color at the index where the slider is\n", + " ix = ev.pick_info[\"selected_index\"]\n", + " g = ev.pick_info[\"graphic\"].parent\n", + " g.colors[ix] = \"green\"\n", + "\n", + "selector.selection.add_event_handler(set_color_at_index)\n", + "\n", + "# fastplotlib LineSelector can make an ipywidget slider and return it :D \n", + "ipywidget_slider = selector.make_ipywidget_slider()\n", + "ipywidget_slider.description = \"slider1\"\n", + "\n", + "# or you can make your own ipywidget sliders and connect them to the linear selector\n", + "ipywidget_slider2 = IntSlider(min=0, max=100, description=\"slider2\")\n", + "ipywidget_slider3 = FloatSlider(min=0, max=100, description=\"slider3\")\n", + "\n", + "selector2.add_ipywidget_handler(ipywidget_slider2, step=5)\n", + "selector3.add_ipywidget_handler(ipywidget_slider3, step=0.1)\n", + "\n", + "plot.auto_scale()\n", + "plot.show(vbox=[ipywidget_slider])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7ab9f141-f92f-4c4c-808b-97dafd64ca25", + "metadata": {}, + "outputs": [], + "source": [ + "selector.step = 0.1" + ] + }, + { + "cell_type": "markdown", + "id": "3b0f448f-bbe4-4b87-98e3-093f561c216c", + "metadata": {}, + "source": [ + "### Drag linear selectors with the mouse, hold \"Shift\" to synchronize movement of all the selectors" + ] + }, + { + "cell_type": "markdown", + "id": "c6f041b7-8779-46f1-8454-13cec66f53fd", + "metadata": {}, + "source": [ + "## Also works for line collections" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e36da217-f82a-4dfa-9556-1f4a2c7c4f1c", + "metadata": {}, + "outputs": [], + "source": [ + "sines = [sine] * 10\n", + "\n", + "plot = fpl.Plot()\n", + "\n", + "sine_stack = plot.add_line_stack(sines)\n", + "\n", + "colors = \"y\", \"blue\", \"red\", \"green\"\n", + "\n", + "selectors = list()\n", + "for i, c in enumerate(colors):\n", + " sel = sine_stack.add_linear_selector(i * 100, color=c, name=str(i))\n", + " selectors.append(sel)\n", + " \n", + "ss = Synchronizer(*selectors)\n", + "\n", + "plot.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71ae4fca-f644-4d4f-8f32-f9d069bbc2f1", + "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.11.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} 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