diff --git a/docs/source/quickstart.ipynb b/docs/source/quickstart.ipynb
index 6a3afec33..0de4667bf 100644
--- a/docs/source/quickstart.ipynb
+++ b/docs/source/quickstart.ipynb
@@ -97,21 +97,7 @@
"id": "be5b408f-dd91-4e36-807a-8c22c8d7d216",
"metadata": {},
"source": [
- "**In live notebooks or desktop applications, you can use the handle on the bottom right corner of the _canvas_ to resize it. You can also pan and zoom using your mouse!**\n",
- "\n",
- "By default the origin is on the bottom left, you can click the flip button to flip the y-axis, or use `plot.camera.world.scale_y *= -1`"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "9b4e977e-2a7d-4e2b-aee4-cfc36767b3c6",
- "metadata": {
- "tags": []
- },
- "outputs": [],
- "source": [
- "plot.camera.world.scale_y *= -1"
+ "**In live notebooks or desktop applications, you can use the handle on the bottom right corner of the _canvas_ to resize it. You can also pan and zoom using your mouse!**"
]
},
{
@@ -510,18 +496,6 @@
"plot_rgb.show()"
]
},
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "7e4b5a30-4293-4ae3-87dc-06a1355bb2c7",
- "metadata": {
- "tags": []
- },
- "outputs": [],
- "source": [
- "plot_rgb.camera.world.scale_y *= -1"
- ]
- },
{
"cell_type": "code",
"execution_count": null,
@@ -1361,7 +1335,7 @@
"outputs": [],
"source": [
"# GridPlot of shape 2 x 3 with all controllers synced\n",
- "grid_plot = fpl.GridPlot(shape=(2, 3), controllers=\"sync\")\n",
+ "grid_plot = fpl.GridPlot(shape=(2, 3), controller_ids=\"sync\")\n",
"\n",
"# Make a random image graphic for each subplot\n",
"for subplot in grid_plot:\n",
@@ -1549,7 +1523,7 @@
"# pan-zoom controllers for each view\n",
"# views are synced if they have the \n",
"# same controller ID\n",
- "controllers = [\n",
+ "controller_ids = [\n",
" [0, 3, 1], # id each controller with an integer\n",
" [2, 2, 3]\n",
"]\n",
@@ -1564,7 +1538,7 @@
"# Create the grid plot\n",
"grid_plot = fpl.GridPlot(\n",
" shape=grid_shape,\n",
- " controllers=controllers,\n",
+ " controller_ids=controller_ids,\n",
" names=names,\n",
")\n",
"\n",
@@ -1678,18 +1652,6 @@
"grid_plot[1, 0][\"rand-image\"].vim = 0.1\n",
"grid_plot[1, 0][\"rand-image\"].vmax = 0.3"
]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "8a5753b9-ee71-4ed1-bb0d-52bdb4ea365f",
- "metadata": {
- "tags": []
- },
- "outputs": [],
- "source": [
- "grid_plot[1, 0][\"rand-image\"].type"
- ]
}
],
"metadata": {
diff --git a/examples/desktop/gridplot/gridplot_non_square.py b/examples/desktop/gridplot/gridplot_non_square.py
index a41bcd9f4..fe43a3c04 100644
--- a/examples/desktop/gridplot/gridplot_non_square.py
+++ b/examples/desktop/gridplot/gridplot_non_square.py
@@ -10,7 +10,7 @@
import imageio.v3 as iio
-plot = fpl.GridPlot(shape=(2, 2), controllers="sync")
+plot = fpl.GridPlot(shape=(2, 2), controller_ids="sync")
# to force a specific framework such as glfw:
# plot = fpl.GridPlot(canvas="glfw")
diff --git a/examples/desktop/scatter/scatter_size.py b/examples/desktop/scatter/scatter_size.py
index 5b6987b7c..2ad995584 100644
--- a/examples/desktop/scatter/scatter_size.py
+++ b/examples/desktop/scatter/scatter_size.py
@@ -9,16 +9,7 @@
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],
- [0]
-]
-
+grid_shape = (2, 1)
# you can give string names for each subplot within the gridplot
names = [
@@ -29,7 +20,6 @@
# Create the grid plot
plot = fpl.GridPlot(
shape=grid_shape,
- controllers=controllers,
names=names,
size=(1000, 1000)
)
@@ -53,4 +43,4 @@
if __name__ == "__main__":
print(__doc__)
- fpl.run()
\ No newline at end of file
+ fpl.run()
diff --git a/examples/notebooks/gridplot.ipynb b/examples/notebooks/gridplot.ipynb
index cfcae3705..f1ceb2180 100644
--- a/examples/notebooks/gridplot.ipynb
+++ b/examples/notebooks/gridplot.ipynb
@@ -10,7 +10,7 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "a635b3b3-33fa-48f0-b1cc-bf83b1e883ab",
"metadata": {},
"outputs": [],
@@ -21,52 +21,10 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "8de931e2-bdb3-44a3-9538-e0b3965779af",
"metadata": {},
- "outputs": [
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "62f67ebf3b00494c826d92fc5b8bbf76",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "RFBOutputContext()"
- ]
- },
- "metadata": {},
- "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": "b6262056cff84619aea08fc48d00eb79",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "JupyterWgpuCanvas()"
- ]
- },
- "execution_count": 2,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# grid with 2 rows and 3 columns\n",
"grid_shape = (2, 3)\n",
@@ -74,9 +32,15 @@
"# pan-zoom controllers for each view\n",
"# views are synced if they have the \n",
"# same controller ID\n",
- "controllers = [\n",
- " [0, 3, 1], # id each controller with an integer\n",
- " [2, 2, 3]\n",
+ "controller_ids = [\n",
+ " [0, -3, 1], # id each controller with an integer\n",
+ " [2, 2, -3]\n",
+ "]\n",
+ "\n",
+ "# another way to set controller_ids\n",
+ "controller_ids = [\n",
+ " [\"subplot0\", \"subplot4\"],\n",
+ " [\"subplot1\", \"subplot2\", \"subplot5\"],\n",
"]\n",
"\n",
"\n",
@@ -89,7 +53,7 @@
"# Create the grid plot\n",
"grid_plot = GridPlot(\n",
" shape=grid_shape,\n",
- " controllers=controllers,\n",
+ " controller_ids=controller_ids,\n",
" names=names,\n",
")\n",
"\n",
@@ -123,24 +87,10 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"id": "2a6f7eb5-776e-42a6-b6c2-c26009a26795",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "subplot0: Subplot @ 0x7fd496b32830\n",
- " parent: None\n",
- " Graphics:\n",
- "\t'rand-image': ImageGraphic @ 0x7fd496b327d0"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# can access subplot by name\n",
"grid_plot[\"subplot0\"]"
@@ -148,24 +98,10 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"id": "45e83bca-5a44-48ce-874f-9ae9ca444233",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "subplot0: Subplot @ 0x7fd496b32830\n",
- " parent: None\n",
- " Graphics:\n",
- "\t'rand-image': ImageGraphic @ 0x7fd496b327d0"
- ]
- },
- "execution_count": 4,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# can access subplot by index\n",
"grid_plot[0, 0]"
@@ -182,21 +118,10 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": null,
"id": "c8cf9bfd-e0cc-4173-b64e-a9f2c87bb2c6",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "'rand-image': ImageGraphic @ 0x7fd496b327d0"
- ]
- },
- "execution_count": 5,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# can access graphic directly via name\n",
"grid_plot[\"subplot0\"][\"rand-image\"]"
@@ -204,13 +129,13 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": null,
"id": "1cfd1d45-8a60-4fc1-b873-46caa966fe6f",
"metadata": {},
"outputs": [],
"source": [
- "grid_plot[\"subplot0\"][\"rand-image\"].vmin = 0.6\n",
- "grid_plot[\"subplot0\"][\"rand-image\"].vmax = 0.8"
+ "grid_plot[\"subplot0\"][\"rand-image\"].cmap.vmin = 0.6\n",
+ "grid_plot[\"subplot0\"][\"rand-image\"].cmap.vmax = 0.8"
]
},
{
@@ -223,40 +148,19 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": null,
"id": "2fafe992-4783-40f2-b044-26a2835dd50a",
"metadata": {},
"outputs": [],
"source": [
- "grid_plot[1, 0][\"rand-image\"].vim = 0.1\n",
- "grid_plot[1, 0][\"rand-image\"].vmax = 0.3"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "id": "a025b76c-77f8-4aeb-ac33-5bb6d0bb5a9a",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "'image'"
- ]
- },
- "execution_count": 8,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "grid_plot[1, 0][\"rand-image\"].type"
+ "grid_plot[1, 0][\"rand-image\"].cmap.vim = 0.1\n",
+ "grid_plot[1, 0][\"rand-image\"].cmap.vmax = 0.3"
]
},
{
"cell_type": "code",
"execution_count": null,
- "id": "62584f91-a8ed-4317-98ee-28ad6d1800d6",
+ "id": "a61e34a5-ee1b-4abb-8718-ec4715ffaa52",
"metadata": {},
"outputs": [],
"source": []
@@ -278,7 +182,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.5"
+ "version": "3.11.3"
}
},
"nbformat": 4,
diff --git a/examples/notebooks/gridplot_simple.ipynb b/examples/notebooks/gridplot_simple.ipynb
index 8b50b2701..74807f55a 100644
--- a/examples/notebooks/gridplot_simple.ipynb
+++ b/examples/notebooks/gridplot_simple.ipynb
@@ -10,9 +10,13 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "5171a06e-1bdc-4908-9726-3c1fd45dbb9d",
"metadata": {
+ "ExecuteTime": {
+ "end_time": "2023-11-26T04:01:19.120171Z",
+ "start_time": "2023-11-26T04:01:18.618087Z"
+ },
"tags": []
},
"outputs": [],
@@ -23,38 +27,19 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "86a2488f-ae1c-4b98-a7c0-18eae8013af1",
"metadata": {
+ "ExecuteTime": {
+ "end_time": "2023-11-26T04:01:19.467264Z",
+ "start_time": "2023-11-26T04:01:19.121813Z"
+ },
"tags": []
},
- "outputs": [
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "f9067cd724094b8c8dfecf60208acbfa",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "RFBOutputContext()"
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "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"
- ]
- }
- ],
+ "outputs": [],
"source": [
"# GridPlot of shape 2 x 3 with all controllers synced\n",
- "grid_plot = GridPlot(shape=(2, 3), controllers=\"sync\")\n",
+ "grid_plot = GridPlot(shape=(2, 3), controller_ids=\"sync\")\n",
"\n",
"# Make a random image graphic for each subplot\n",
"for subplot in grid_plot:\n",
@@ -88,27 +73,22 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
+ "id": "52163bc7-2c77-4699-b7b0-e455a0ed7584",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "grid_plot"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
"id": "17c6bc4a-5340-49f1-8597-f54528cfe915",
"metadata": {
"tags": []
},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "unnamed: Subplot @ 0x7f15df4f5c50\n",
- " parent: fastplotlib.GridPlot @ 0x7f15d3f27890\n",
- "\n",
- " Graphics:\n",
- "\t'rand-img': ImageGraphic @ 0x7f15d3fb5390"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# positional indexing\n",
"# row 0 and col 0\n",
@@ -125,23 +105,12 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"id": "34130f12-9ef6-43b0-b929-931de8b7da25",
"metadata": {
"tags": []
},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(,)"
- ]
- },
- "execution_count": 4,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"grid_plot[0, 1].graphics"
]
@@ -156,7 +125,7 @@
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": null,
"id": "ef8a29a6-b19c-4e6b-a2ba-fb4823c01451",
"metadata": {
"tags": []
@@ -176,7 +145,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": null,
"id": "d6c2fa4b-c634-4dcf-8b61-f1986f7c4918",
"metadata": {
"tags": []
@@ -189,50 +158,24 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": null,
"id": "2f6b549c-3165-496d-98aa-45b96c3de674",
"metadata": {
"tags": []
},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "top-right-plot: Subplot @ 0x7f15d3f769d0\n",
- " parent: fastplotlib.GridPlot @ 0x7f15d3f27890\n",
- "\n",
- " Graphics:\n",
- "\t'rand-img': ImageGraphic @ 0x7f15b83f7250"
- ]
- },
- "execution_count": 7,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"grid_plot[\"top-right-plot\"]"
]
},
{
"cell_type": "code",
- "execution_count": 12,
+ "execution_count": null,
"id": "be436e04-33a6-4597-8e6a-17e1e5225419",
"metadata": {
"tags": []
},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(0, 2)"
- ]
- },
- "execution_count": 12,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# view its position\n",
"grid_plot[\"top-right-plot\"].position"
@@ -240,23 +183,12 @@
},
{
"cell_type": "code",
- "execution_count": 13,
+ "execution_count": null,
"id": "6699cda6-af86-4258-87f5-1832f989a564",
"metadata": {
"tags": []
},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "True"
- ]
- },
- "execution_count": 13,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# these are really the same\n",
"grid_plot[\"top-right-plot\"] is grid_plot[0, 2]"
@@ -272,19 +204,19 @@
},
{
"cell_type": "code",
- "execution_count": 14,
+ "execution_count": null,
"id": "545b627b-d794-459a-a75a-3fde44f0ea95",
"metadata": {
"tags": []
},
"outputs": [],
"source": [
- "grid_plot[\"top-right-plot\"][\"rand-img\"].vmin = 0.5"
+ "grid_plot[\"top-right-plot\"][\"rand-img\"].cmap.vmin = 0.5"
]
},
{
"cell_type": "code",
- "execution_count": 15,
+ "execution_count": null,
"id": "36432d5b-b76c-4a2a-a32c-097faf5ab269",
"metadata": {
"tags": []
diff --git a/examples/notebooks/image_widget.ipynb b/examples/notebooks/image_widget.ipynb
index 296126f5e..56d5c8a81 100644
--- a/examples/notebooks/image_widget.ipynb
+++ b/examples/notebooks/image_widget.ipynb
@@ -235,6 +235,16 @@
"iw_movie.gridplot[0, 0].auto_scale()# sidecar is optional"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3d323f67-4717-4241-ad84-5091e6caf2cd",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "iw_movie.close()"
+ ]
+ },
{
"cell_type": "markdown",
"id": "aca22179-1b1f-4c51-97bf-ce2d7044e451",
@@ -296,7 +306,7 @@
},
{
"cell_type": "markdown",
- "id": "0721dc40-677e-431d-94c6-da59606199cb",
+ "id": "037d899e-9e7c-4d58-a021-5f5b71e1db90",
"metadata": {},
"source": [
"pan-zoom controllers are all synced across subplots in a `ImageWidget`"
@@ -473,7 +483,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.11.2"
+ "version": "3.11.3"
}
},
"nbformat": 4,
diff --git a/examples/notebooks/lineplot.ipynb b/examples/notebooks/lineplot.ipynb
index e156a7150..667cae178 100644
--- a/examples/notebooks/lineplot.ipynb
+++ b/examples/notebooks/lineplot.ipynb
@@ -12,7 +12,7 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "9c974494-712e-4981-bae2-a3ee176a6b20",
"metadata": {},
"outputs": [],
@@ -23,7 +23,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "c3d8f967-f60f-4f0b-b6ba-21b1251b4856",
"metadata": {},
"outputs": [],
@@ -41,60 +41,10 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"id": "78cffe56-1147-4255-82c1-53cec6bc986a",
"metadata": {},
- "outputs": [
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "7993e0a4358f4678a7343b78b3b0b24c",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "RFBOutputContext()"
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "/home/kushalk/repos/fastplotlib/fastplotlib/layouts/_base.py:214: UserWarning: `center_scene()` not yet implemented for `PerspectiveCamera`\n",
- " warn(\"`center_scene()` not yet implemented for `PerspectiveCamera`\")\n"
- ]
- },
- {
- "data": {
- "text/html": [
- "
initial snapshot
"
- ],
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- },
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "898a109f489741a5b4624be77bd27db0",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "JupyterWgpuCanvas()"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# grid with 2 rows and 2 columns\n",
"shape = (2, 2)\n",
@@ -104,7 +54,7 @@
"# same controller ID\n",
"# in this example the first view has its own controller\n",
"# and the last 3 views are synced\n",
- "controllers = [\n",
+ "controller_ids = [\n",
" [0, 1], # id each controller with an integer\n",
" [1, 1]\n",
"]\n",
@@ -113,7 +63,7 @@
"grid_plot = GridPlot(\n",
" shape=shape,\n",
" cameras='3d', # 3D view for all subplots within the grid\n",
- " controllers=controllers\n",
+ " controller_ids=controller_ids\n",
")\n",
"\n",
"for i, subplot in enumerate(grid_plot):\n",
diff --git a/examples/notebooks/scatter.ipynb b/examples/notebooks/scatter.ipynb
index 948403f11..9d7ff099f 100644
--- a/examples/notebooks/scatter.ipynb
+++ b/examples/notebooks/scatter.ipynb
@@ -78,7 +78,7 @@
"# same controller ID\n",
"# you can only sync controllers that use the same camera type\n",
"# i.e. you cannot sync between 2d and 3d subplots\n",
- "controllers = [\n",
+ "controller_ids = [\n",
" [0, 1],\n",
" [1, 0]\n",
"]\n",
@@ -87,7 +87,7 @@
"grid_plot = GridPlot(\n",
" shape=shape,\n",
" cameras=cameras,\n",
- " controllers=controllers\n",
+ " controller_ids=controller_ids\n",
")\n",
"\n",
"for subplot in grid_plot:\n",
@@ -163,15 +163,7 @@
{
"cell_type": "code",
"execution_count": null,
- "id": "fb49930f-b795-4b41-bbc6-014a27c2f463",
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "7d8aac54-4f36-41d4-8e5b-8d8da2f0d17d",
+ "id": "a18e7a17-c2af-4674-a499-bf5f3b27c8ca",
"metadata": {},
"outputs": [],
"source": []
diff --git a/examples/notebooks/scatter_sizes_animation.ipynb b/examples/notebooks/scatter_sizes_animation.ipynb
index 061f444d6..06a6b11a2 100644
--- a/examples/notebooks/scatter_sizes_animation.ipynb
+++ b/examples/notebooks/scatter_sizes_animation.ipynb
@@ -2,9 +2,39 @@
"cells": [
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 1,
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "5d9f9913391a42af95d4d43d07c17b19",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "RFBOutputContext()"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "9cd08c319b814934a09fd266a1b6322b",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "JupyterOutputContext(children=(JupyterWgpuCanvas(), IpywidgetToolBar(children=(Button(icon='expand-arrows-alt'…"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"from time import time\n",
"\n",
@@ -21,8 +51,6 @@
" 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 update_sizes():\n",
" current_time = time()\n",
@@ -30,12 +58,11 @@
" size_delta = sin_sample*size_delta_scales\n",
" plot.graphics[0].sizes = min_sizes + size_delta\n",
"\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(update_positions, update_sizes)\n",
- "plot.show(autoscale=True)"
+ "\n",
+ "plot.camera.width = 12\n",
+ "plot.show(autoscale=False)"
]
},
{
@@ -48,7 +75,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": "fastplotlib-dev",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -62,10 +89,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.11.4"
- },
- "orig_nbformat": 4
+ "version": "3.11.3"
+ }
},
"nbformat": 4,
- "nbformat_minor": 2
+ "nbformat_minor": 4
}
diff --git a/examples/notebooks/scatter_sizes_grid.ipynb b/examples/notebooks/scatter_sizes_grid.ipynb
index ff64184f7..e152056c9 100644
--- a/examples/notebooks/scatter_sizes_grid.ipynb
+++ b/examples/notebooks/scatter_sizes_grid.ipynb
@@ -19,15 +19,6 @@
"# 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",
- " [0]\n",
- "]\n",
- "\n",
- "\n",
"# you can give string names for each subplot within the gridplot\n",
"names = [\n",
" [\"scalar_size\"],\n",
@@ -37,7 +28,6 @@
"# Create the grid plot\n",
"plot = fpl.GridPlot(\n",
" shape=grid_shape,\n",
- " controllers=controllers,\n",
" names=names,\n",
" size=(1000, 1000)\n",
")\n",
@@ -59,11 +49,18 @@
"\n",
"plot.show()"
]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
}
],
"metadata": {
"kernelspec": {
- "display_name": "fastplotlib-dev",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -77,10 +74,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.11.4"
- },
- "orig_nbformat": 4
+ "version": "3.11.3"
+ }
},
"nbformat": 4,
- "nbformat_minor": 2
+ "nbformat_minor": 4
}
diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png b/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png
index 33798861e..61c3aeb8c 100644
--- a/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png
+++ b/examples/notebooks/screenshots/nb-image-widget-movie-set_data.png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:df2f6e73fdb0946d915f40df1e27f2ae89ba6c6e5d0dadb84406fe8b753735ab
-size 34974
+oid sha256:60318615e4850d37a4ffae16ca1e3bbf2985ddafd0dd65ba6fae997e1d123d67
+size 31251
diff --git a/examples/notebooks/simple.ipynb b/examples/notebooks/simple.ipynb
index 681980d39..f5901080b 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()"
+ "plot.show(sidecar=True)"
]
},
{
@@ -589,7 +589,7 @@
"\n",
"plot_sync.add_animations(update_data_2)\n",
"\n",
- "plot_sync.show(sidecar=False)"
+ "plot_sync.show()"
]
},
{
@@ -704,7 +704,7 @@
"sinc_graphic = plot_l.add_line(data=sinc, thickness=5, colors = colors)\n",
"\n",
"# show the plot\n",
- "plot_l.show(sidecar_kwargs={\"title\": \"lines\", \"layout\": {'width': '800px'}})"
+ "plot_l.show(sidecar=True, sidecar_kwargs={\"title\": \"lines\"})"
]
},
{
@@ -1020,6 +1020,14 @@
"plot_l3d.show()"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "29f07af0-cdcb-47cc-bbb3-2fa4449fa084",
+ "metadata": {},
+ "source": [
+ "**Use WASD keys and the mouse to move around, just like in a game :D. Use the mouse weel to control the speed of movement.**"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -1045,6 +1053,17 @@
"plot_test(\"lines-3d\", plot_l3d)"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7a6da884-8b40-4ebf-837f-929b3e9cf4c4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# change the FOV of the persepctive camera\n",
+ "plot_l3d.camera.fov = 70"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -1187,6 +1206,25 @@
"scatter_graphic.data[n_points:n_points * 2, 0] = np.linspace(-40, 0, n_points)"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "5f3e206d-97af-4e07-9969-94f2fdb41004",
+ "metadata": {},
+ "source": [
+ "**Switch to a fly controller to move around the plot in 3D!**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c67944ca-52e7-4213-b820-6572cc3f76f0",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plot_s.camera = \"3d\"\n",
+ "plot_s.controller = \"fly\""
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
diff --git a/fastplotlib/layouts/_defaults.py b/fastplotlib/layouts/_defaults.py
index 9a223855f..b28b04f64 100644
--- a/fastplotlib/layouts/_defaults.py
+++ b/fastplotlib/layouts/_defaults.py
@@ -1,46 +1,3 @@
-import pygfx
-from typing import *
-camera_types = {
- "2d": pygfx.OrthographicCamera,
- "3d": pygfx.PerspectiveCamera,
-}
-controller_types = {
- "2d": pygfx.PanZoomController,
- "3d": pygfx.OrbitController,
- pygfx.OrthographicCamera: pygfx.PanZoomController,
- pygfx.PerspectiveCamera: pygfx.OrbitController,
-}
-
-def create_camera(
- camera_type: str, big_camera: bool = False
-) -> Union[pygfx.OrthographicCamera, pygfx.PerspectiveCamera]:
- camera_type = camera_type.split("-")
-
- # kinda messy but works for now
- if len(camera_type) > 1:
- if camera_type[1] == "big":
- big_camera = True
-
- camera_type = camera_type[0]
- else:
- camera_type = camera_type[0]
-
- cls = camera_types[camera_type]
-
- if cls is pygfx.OrthographicCamera:
- if big_camera:
- return cls(1024, 1024, -8192, 8192)
- else:
- return cls(1024, 1024)
-
- else:
- return cls()
-
-
-def create_controller(controller_type: str):
- controller_type = controller_type.split("-")[0]
-
- return controller_types[controller_type]()
diff --git a/fastplotlib/layouts/_gridplot.py b/fastplotlib/layouts/_gridplot.py
index 473196f78..459aca5fd 100644
--- a/fastplotlib/layouts/_gridplot.py
+++ b/fastplotlib/layouts/_gridplot.py
@@ -1,4 +1,4 @@
-from itertools import product
+from itertools import product, chain
import numpy as np
from typing import *
from inspect import getfullargspec
@@ -9,8 +9,8 @@
from wgpu.gui.auto import WgpuCanvas
from ._frame import Frame
-from ._utils import make_canvas_and_renderer
-from ._defaults import create_controller
+from ._utils import make_canvas_and_renderer, create_controller, create_camera
+from ._utils import controller_types as valid_controller_types
from ._subplot import Subplot
from ._record_mixin import RecordMixin
@@ -25,43 +25,47 @@ def to_array(a) -> np.ndarray:
return np.array(a)
-valid_cameras = ["2d", "2d-big", "3d", "3d-big"]
-
-
class GridPlot(Frame, RecordMixin):
def __init__(
self,
shape: Tuple[int, int],
- cameras: Union[np.ndarray, str] = "2d",
- controllers: Union[np.ndarray, str] = None,
+ cameras: Union[str, list, np.ndarray] = "2d",
+ controller_types: Union[str, list, np.ndarray] = None,
+ controller_ids: Union[str, list, np.ndarray] = None,
canvas: Union[str, WgpuCanvas, pygfx.Texture] = None,
renderer: pygfx.WgpuRenderer = None,
size: Tuple[int, int] = (500, 300),
- **kwargs,
+ names: Union[list, np.ndarray] = None,
):
"""
A grid of subplots.
Parameters
----------
- shape: tuple of int
+ shape: (int, int)
(n_rows, n_cols)
- cameras: np.ndarray or str, optional
+ cameras: str, list, or np.ndarray, optional
| One of ``"2d"`` or ``"3d"`` indicating 2D or 3D cameras for all subplots
- | OR
- | Array of ``2d`` and/or ``3d`` that specifies the camera type for each subplot
+ | list/array of ``2d`` and/or ``3d`` that specifies the camera type for each subplot
+ | list/array of pygfx.PerspectiveCamera instances
+
+ controller_types: str, list or np.ndarray, optional
+ list or array that specifies the controller type for each subplot, or list/array of
+ pygfx.Controller instances
- controllers: np.ndarray or str, optional
+ controller_ids: str, list or np.ndarray of int or str ids, optional
| If `None` a unique controller is created for each subplot
| If "sync" all the subplots use the same controller
| If ``numpy.array``, its shape must be the same as ``grid_shape``.
This allows custom assignment of controllers
- | Example:
- | unique controllers for a 2x2 gridplot: np.array([[0, 1], [2, 3]])
- | same controllers for first 2 plots and last 2 plots: np.array([[0, 0, 1], [2, 3, 3]])
+ | Example with integers:
+ | sync first 2 plots, and sync last 2 plots: [[0, 0, 1], [2, 3, 3]]
+ | Example with str subplot names:
+ | list of lists of subplot names, each sublist is synced: [[subplot_a, subplot_b], [subplot_f, subplot_c]]
+ | this syncs subplot_a and subplot_b together; syncs subplot_f and subplot_c together
canvas: WgpuCanvas, optional
Canvas for drawing
@@ -69,70 +73,147 @@ def __init__(
renderer: pygfx.Renderer, optional
pygfx renderer instance
- size: (int, int)
+ size: (int, int), optional
starting size of canvas, default (500, 300)
+ names: list or array of str, optional
+ subplot names
"""
self.shape = shape
+ if names is not None:
+ if len(list(chain(*names))) != self.shape[0] * self.shape[1]:
+ raise ValueError("must provide same number of subplot `names` as specified by gridplot shape")
+
+ self.names = to_array(names).reshape(self.shape)
+ else:
+ self.names = None
+
canvas, renderer = make_canvas_and_renderer(canvas, renderer)
if isinstance(cameras, str):
- if cameras not in valid_cameras:
- raise ValueError(
- f"If passing a str, `cameras` must be one of: {valid_cameras}"
- )
# create the array representing the views for each subplot in the grid
cameras = np.array([cameras] * self.shape[0] * self.shape[1]).reshape(
self.shape
)
- if isinstance(controllers, str):
- if controllers == "sync":
- controllers = np.zeros(
- self.shape[0] * self.shape[1], dtype=int
- ).reshape(self.shape)
+ # list -> array if necessary
+ cameras = to_array(cameras).reshape(self.shape)
- if controllers is None:
- controllers = np.arange(self.shape[0] * self.shape[1]).reshape(self.shape)
+ if cameras.shape != self.shape:
+ raise ValueError("Number of cameras does not match the number of subplots")
- controllers = to_array(controllers)
+ # create the cameras
+ self._cameras = np.empty(self.shape, dtype=object)
+ for i, j in product(range(self.shape[0]), range(self.shape[1])):
+ self._cameras[i, j] = create_camera(camera_type=cameras[i, j])
- if controllers.shape != self.shape:
- raise ValueError
+ if controller_ids is None:
+ # individual controller for each subplot
+ controller_ids = np.arange(self.shape[0] * self.shape[1]).reshape(self.shape)
- cameras = to_array(cameras)
+ elif isinstance(controller_ids, str):
+ if controller_ids == "sync":
+ controller_ids = np.zeros(self.shape, dtype=int)
+ else:
+ raise ValueError(
+ f"`controller_ids` must be one of 'sync', an array/list of subplot names, or an array/list of "
+ f"integer ids. See the docstring for more details."
+ )
- self._controllers = np.empty(shape=cameras.shape, dtype=object)
+ # list controller_ids
+ elif isinstance(controller_ids, (list, np.ndarray)):
+ ids_flat = list(chain(*controller_ids))
- if cameras.shape != self.shape:
- raise ValueError
-
- # create controllers if the arguments were integers
- if np.issubdtype(controllers.dtype, np.integer):
- if not np.all(
- np.sort(np.unique(controllers))
- == np.arange(np.unique(controllers).size)
- ):
- raise ValueError("controllers must be consecutive integers")
-
- for controller in np.unique(controllers):
- cam = np.unique(cameras[controllers == controller])
- if cam.size > 1:
+ # list of str of subplot names, convert this to integer ids
+ if all([isinstance(item, str) for item in ids_flat]):
+ if self.names is None:
+ raise ValueError("must specify subplot `names` to use list of str for `controller_ids`")
+
+ # make sure each controller_id str is a subplot name
+ if not all([n in self.names for n in ids_flat]):
+ raise KeyError(
+ f"all `controller_ids` strings must be one of the subplot names"
+ )
+
+ if len(ids_flat) > len(set(ids_flat)):
raise ValueError(
- f"Controller id: {controller} has been assigned to multiple different camera types"
+ "id strings must not appear twice in `controller_ids`"
)
- self._controllers[controllers == controller] = create_controller(cam[0])
- # else assume it's a single pygfx.Controller instance or a list of controllers
- else:
- if isinstance(controllers, pygfx.Controller):
- self._controllers = np.array(
- [controllers] * shape[0] * shape[1]
- ).reshape(shape)
+ # initialize controller_ids array
+ ids_init = np.arange(self.shape[0] * self.shape[1]).reshape(self.shape)
+
+ # set id based on subplot position for each synced sublist
+ for i, sublist in enumerate(controller_ids):
+ for name in sublist:
+ ids_init[self.names == name] = -(i + 1) # use negative numbers because why not
+
+ controller_ids = ids_init
+
+ # integer ids
+ elif all([isinstance(item, (int, np.integer)) for item in ids_flat]):
+ controller_ids = to_array(controller_ids).reshape(self.shape)
+
else:
- self._controllers = np.array(controllers).reshape(shape)
+ raise TypeError(
+ f"list argument to `controller_ids` must be a list of `str` or `int`, "
+ f"you have passed: {controller_ids}"
+ )
+
+ if controller_ids.shape != self.shape:
+ raise ValueError("Number of controller_ids does not match the number of subplots")
+
+ if controller_types is None:
+ # `create_controller()` will auto-determine controller for each subplot based on defaults
+ controller_types = np.array(["default"] * self.shape[0] * self.shape[1]).reshape(self.shape)
+
+ # validate controller types
+ types_flat = list(chain(*controller_types))
+ # str controller_type or pygfx instances
+ valid_str = list(valid_controller_types.keys()) + ["default"]
+ valid_instances = tuple(valid_controller_types.values())
+
+ # make sure each controller type is valid
+ for controller_type in types_flat:
+ if controller_type is None:
+ continue
+
+ if (controller_type not in valid_str) and (not isinstance(controller_type, valid_instances)):
+ raise ValueError(
+ f"You have passed an invalid controller type, valid controller_types arguments are:\n"
+ f"{valid_str} or instances of {[c.__name__ for c in valid_instances]}"
+ )
+
+ controller_types = to_array(controller_types).reshape(self.shape)
+
+ # make the real controllers for each subplot
+ self._controllers = np.empty(shape=self.shape, dtype=object)
+ for cid in np.unique(controller_ids):
+ cont_type = controller_types[controller_ids == cid]
+ if np.unique(cont_type).size > 1:
+ raise ValueError(
+ "Multiple controller types have been assigned to the same controller id. "
+ "All controllers with the same id must use the same type of controller."
+ )
+
+ cont_type = cont_type[0]
+
+ # get all the cameras that use this controller
+ cams = self._cameras[controller_ids == cid].ravel()
+
+ if cont_type == "default":
+ # hacky fix for now because of how `create_controller()` works
+ cont_type = None
+ _controller = create_controller(controller_type=cont_type, camera=cams[0])
+
+ self._controllers[controller_ids == cid] = _controller
+
+ # add the other cameras that go with this controller
+ if cams.size > 1:
+ for cam in cams[1:]:
+ _controller.add_camera(cam)
if canvas is None:
canvas = WgpuCanvas()
@@ -140,13 +221,6 @@ def __init__(
if renderer is None:
renderer = pygfx.renderers.WgpuRenderer(canvas)
- if "names" in kwargs.keys():
- self.names = to_array(kwargs["names"])
- if self.names.shape != self.shape:
- raise ValueError
- else:
- self.names = None
-
self._canvas = canvas
self._renderer = renderer
@@ -158,7 +232,7 @@ def __init__(
for i, j in self._get_iterator():
position = (i, j)
- camera = cameras[i, j]
+ camera = self._cameras[i, j]
controller = self._controllers[i, j]
if self.names is not None:
@@ -304,5 +378,15 @@ def __next__(self) -> Subplot:
pos = self._current_iter.__next__()
return self._subplots[pos]
+ def __str__(self):
+ return f"{self.__class__.__name__} @ {hex(id(self))}"
+
def __repr__(self):
- return f"fastplotlib.{self.__class__.__name__} @ {hex(id(self))}\n"
+ newline = "\n\t"
+
+ return (
+ f"fastplotlib.{self.__class__.__name__} @ {hex(id(self))}\n"
+ f" Subplots:\n"
+ f"\t{newline.join(subplot.__str__() for subplot in self)}"
+ f"\n"
+ )
diff --git a/fastplotlib/layouts/_plot.py b/fastplotlib/layouts/_plot.py
index 5aa04bb76..34027a276 100644
--- a/fastplotlib/layouts/_plot.py
+++ b/fastplotlib/layouts/_plot.py
@@ -11,10 +11,10 @@
class Plot(Subplot, Frame, RecordMixin):
def __init__(
self,
- canvas: WgpuCanvas = None,
+ canvas: Union[str, WgpuCanvas] = None,
renderer: pygfx.WgpuRenderer = None,
- camera: str = "2d",
- controller: Union[pygfx.PanZoomController, pygfx.OrbitController] = None,
+ camera: Union[str, pygfx.PerspectiveCamera] = "2d",
+ controller: Union[str, pygfx.Controller] = None,
size: Tuple[int, int] = (500, 300),
**kwargs,
):
@@ -29,12 +29,14 @@ def __init__(
renderer: pygfx.Renderer, optional
pygfx renderer instance
- camera:str, optional
+ camera: str or pygfx.PerspectiveCamera, optional
| One of ``"2d"`` or ``"3d"`` indicating 2D or 3D camera
- controller: None, PanZoomController or OrbitOrthoController, optional
+ controller: str or pygfx.Controller, optional
Usually ``None``, you can pass an existing controller from another
- ``Plot`` or ``Subplot`` within a ``GridPlot`` to synchronize them.
+ ``Plot`` or ``Subplot`` to synchronize them.
+
+ You can also pass str arguments of valid controller names, see Subplot docstring for valid names
size: (int, int)
starting size of canvas, default (500, 300)
diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py
index dec5d891e..9522832d3 100644
--- a/fastplotlib/layouts/_plot_area.py
+++ b/fastplotlib/layouts/_plot_area.py
@@ -6,18 +6,10 @@
import numpy as np
import pygfx
-from pygfx import (
- Scene,
- OrthographicCamera,
- PerspectiveCamera,
- PanZoomController,
- OrbitController,
- Viewport,
- WgpuRenderer,
-)
from pylinalg import vec_transform, vec_unproject
from wgpu.gui.auto import WgpuCanvas
+from ._utils import create_camera, create_controller
from ..graphics._base import Graphic
from ..graphics.selectors._base_selector import BaseSelector
@@ -33,11 +25,11 @@ def __init__(
self,
parent,
position: Any,
- camera: Union[OrthographicCamera, PerspectiveCamera],
- controller: Union[PanZoomController, OrbitController],
- scene: Scene,
+ camera: Union[pygfx.PerspectiveCamera],
+ controller: Union[pygfx.Controller],
+ scene: pygfx.Scene,
canvas: WgpuCanvas,
- renderer: WgpuRenderer,
+ renderer: pygfx.WgpuRenderer,
name: str = None,
):
"""
@@ -53,25 +45,23 @@ def __init__(
typical use will be for ``subplots`` in a ``gridplot``, position would correspond to the ``[row, column]``
location of the ``subplot`` in its ``gridplot``
- camera: pygfx OrthographicCamera or pygfx PerspectiveCamera
- ``OrthographicCamera`` type is used to visualize 2D content and ``PerspectiveCamera`` type is used to view
- 3D content, used to view the scene
+ camera: pygfx.PerspectiveCamera
+ Use perspective camera for both perspective and orthographic views. Set fov = 0 for orthographic mode.
- controller: pygfx PanZoomController or pygfx OrbitController
- ``PanZoomController`` type is used for 2D pan-zoom camera control and ``OrbitController`` type is used for
- rotating the camera around a center position, used to control the camera
+ controller: pygfx.Controller
+ One of the pygfx controllers, panzoom, fly, orbit, or trackball
- scene: pygfx Scene
+ scene: pygfx.Scene
represents the root of a scene graph, will be viewed by the given ``camera``
canvas: WgpuCanvas
provides surface on which a scene will be rendered
- renderer: WgpuRenderer
- object used to render scenes using wgpu
+ renderer: pygfx.WgpuRenderer
+ renders the scene onto the canvas
name: str, optional
- name of ``subplot`` or ``plot`` subclass being instantiated
+ name this ``subplot`` or ``plot``
"""
@@ -82,14 +72,14 @@ def __init__(
self._canvas = canvas
self._renderer = renderer
if parent is None:
- self._viewport: Viewport = Viewport(renderer)
+ self._viewport: pygfx.Viewport = pygfx.Viewport(renderer)
else:
- self._viewport = Viewport(parent.renderer)
+ self._viewport = pygfx.Viewport(parent.renderer)
self._camera = camera
self._controller = controller
- self.controller.add_camera(self.camera)
+ self.controller.add_camera(self._camera)
self.controller.register_events(
self.viewport,
)
@@ -126,7 +116,7 @@ def position(self) -> Union[Tuple[int, int], Any]:
return self._position
@property
- def scene(self) -> Scene:
+ def scene(self) -> pygfx.Scene:
"""The Scene where Graphics lie in this plot area"""
return self._scene
@@ -136,26 +126,82 @@ def canvas(self) -> WgpuCanvas:
return self._canvas
@property
- def renderer(self) -> WgpuRenderer:
+ def renderer(self) -> pygfx.WgpuRenderer:
"""Renderer associated to the plot area"""
return self._renderer
@property
- def viewport(self) -> Viewport:
+ def viewport(self) -> pygfx.Viewport:
"""The rectangular area of the renderer associated to this plot area"""
return self._viewport
@property
- def camera(self) -> Union[OrthographicCamera, PerspectiveCamera]:
+ def camera(self) -> pygfx.PerspectiveCamera:
"""camera used to view the scene"""
return self._camera
+ @camera.setter
+ def camera(self, new_camera: Union[str, pygfx.PerspectiveCamera]):
+ # user wants to set completely new camera, remove current camera from controller
+ if isinstance(new_camera, pygfx.PerspectiveCamera):
+ self.controller.remove_camera(self._camera)
+ # add new camera to controller
+ self.controller.add_camera(new_camera)
+
+ self._camera = new_camera
+
+ # modify FOV if necessary
+ elif isinstance(new_camera, str):
+ if new_camera == "2d":
+ self._camera.fov = 0
+
+ elif new_camera == "3d":
+ # orthographic -> perspective only if fov = 0, i.e. if camera is in ortho mode
+ # otherwise keep same FOV
+ if self._camera.fov == 0:
+ self._camera.fov = 50
+
+ else:
+ raise ValueError("camera must be one of '2d', '3d' or a pygfx.PerspectiveCamera instance")
+ else:
+ raise ValueError("camera must be one of '2d', '3d' or a pygfx.PerspectiveCamera instance")
+
# in the future we can think about how to allow changing the controller
@property
- def controller(self) -> Union[PanZoomController, OrbitController]:
- """controller used to control camera"""
+ def controller(self) -> pygfx.Controller:
+ """controller used to control the camera"""
return self._controller
+ @controller.setter
+ def controller(self, new_controller: Union[str, pygfx.Controller]):
+ new_controller = create_controller(new_controller, self._camera)
+
+ cameras_list = list()
+
+ # remove all the cameras associated to this controller
+ for camera in self._controller.cameras:
+ self._controller.remove_camera(camera)
+ cameras_list.append(camera)
+
+ # add the associated cameras to the new controller
+ for camera in cameras_list:
+ new_controller.add_camera(camera)
+
+ new_controller.register_events(
+ self.viewport
+ )
+
+ # TODO: monkeypatch until we figure out a better
+ # pygfx plans on refactoring viewports anyways
+ if self.parent is not None:
+ if self.parent.__class__.__name__ == "GridPlot":
+ for subplot in self.parent:
+ if subplot.camera in cameras_list:
+ new_controller.register_events(subplot.viewport)
+ subplot._controller = new_controller
+
+ self._controller = new_controller
+
@property
def graphics(self) -> Tuple[Graphic, ...]:
"""Graphics in the plot area. Always returns a proxy to the Graphic instances."""
@@ -663,7 +709,7 @@ def __repr__(self):
return (
f"{self}\n"
- f" parent: {self.parent}\n"
+ f" parent: {self.parent.__str__()}\n"
f" Graphics:\n"
f"\t{newline.join(graphic.__repr__() for graphic in self.graphics)}"
f"\n"
diff --git a/fastplotlib/layouts/_subplot.py b/fastplotlib/layouts/_subplot.py
index c178c0fca..e9eae7603 100644
--- a/fastplotlib/layouts/_subplot.py
+++ b/fastplotlib/layouts/_subplot.py
@@ -2,22 +2,13 @@
import numpy as np
-from pygfx import (
- Scene,
- OrthographicCamera,
- PanZoomController,
- OrbitController,
- AxesHelper,
- GridHelper,
- WgpuRenderer,
- Texture,
-)
+import pygfx
+
from wgpu.gui.auto import WgpuCanvas
from ..graphics import TextGraphic
-from ._utils import make_canvas_and_renderer
+from ._utils import make_canvas_and_renderer, create_camera, create_controller
from ._plot_area import PlotArea
-from ._defaults import create_camera, create_controller
from .graphic_methods_mixin import GraphicMethodsMixin
@@ -27,12 +18,11 @@ def __init__(
parent: Any = None,
position: Tuple[int, int] = None,
parent_dims: Tuple[int, int] = None,
- camera: str = "2d",
- controller: Union[PanZoomController, OrbitController] = None,
- canvas: Union[str, WgpuCanvas, Texture] = None,
- renderer: WgpuRenderer = None,
+ camera: Union[str, pygfx.PerspectiveCamera] = "2d",
+ controller: Union[str, pygfx.Controller] = None,
+ canvas: Union[str, WgpuCanvas, pygfx.Texture] = None,
+ renderer: pygfx.WgpuRenderer = None,
name: str = None,
- **kwargs,
):
"""
General plot object that composes a ``Gridplot``. Each ``Gridplot`` instance will have [n rows, n columns]
@@ -43,21 +33,25 @@ def __init__(
Parameters
----------
- position: int tuple, optional
+ parent: Any
+ parent GridPlot instance
+
+ position: (int, int), optional
corresponds to the [row, column] position of the subplot within a ``Gridplot``
- parent_dims: int tuple, optional
+ parent_dims: (int, int), optional
dimensions of the parent ``GridPlot``
- camera: str, default '2d'
- indicates the kind of pygfx camera that will be instantiated, '2d' uses pygfx ``OrthographicCamera`` and
- '3d' uses pygfx ``PerspectiveCamera``
+ camera: str or pygfx.PerspectiveCamera, default '2d'
+ indicates the FOV for the camera, '2d' sets ``fov = 0``, '3d' sets ``fov = 50``.
+ ``fov`` can be changed at any time.
- controller: PanZoomController or OrbitOrthoController, optional
- ``PanZoomController`` type is used for 2D pan-zoom camera control and ``OrbitController`` type is used for
- rotating the camera around a center position, used to control the camera
+ controller: str or pygfx.Controller, optional
+ | if ``None``, uses a PanZoomController for "2d" camera or FlyController for "3d" camera.
+ | if ``str``, must be one of: `"panzoom", "fly", "trackball", or "orbit"`.
+ | also accepts a pygfx.Controller instance
- canvas: WgpuCanvas, Texture, or one of "jupyter", "glfw", "qt", optional
+ canvas: one of "jupyter", "glfw", "qt", WgpuCanvas, or pygfx.Texture, optional
Provides surface on which a scene will be rendered. Can optionally provide a WgpuCanvas instance or a str
to force the PlotArea to use a specific canvas from one of the following options: "jupyter", "glfw", "qt".
Can also provide a pygfx Texture to render to.
@@ -82,25 +76,26 @@ def __init__(
self.nrows, self.ncols = parent_dims
- if controller is None:
- controller = create_controller(camera)
+ camera = create_camera(camera)
+
+ controller = create_controller(controller_type=controller, camera=camera)
self._docks = dict()
self.spacing = 2
- self._axes: AxesHelper = AxesHelper(size=100)
+ self._axes: pygfx.AxesHelper = pygfx.AxesHelper(size=100)
for arrow in self._axes.children:
self._axes.remove(arrow)
- self._grid: GridHelper = GridHelper(size=100, thickness=1)
+ self._grid: pygfx.GridHelper = pygfx.GridHelper(size=100, thickness=1)
super(Subplot, self).__init__(
parent=parent,
position=position,
- camera=create_camera(camera),
+ camera=camera,
controller=controller,
- scene=Scene(),
+ scene=pygfx.Scene(),
canvas=canvas,
renderer=renderer,
name=name,
@@ -221,9 +216,9 @@ def __init__(
super(Dock, self).__init__(
parent=parent,
position=position,
- camera=OrthographicCamera(),
- controller=PanZoomController(),
- scene=Scene(),
+ camera=pygfx.OrthographicCamera(),
+ controller=pygfx.PanZoomController(),
+ scene=pygfx.Scene(),
canvas=parent.canvas,
renderer=parent.renderer,
)
diff --git a/fastplotlib/layouts/_utils.py b/fastplotlib/layouts/_utils.py
index e8201d073..7db1d84c4 100644
--- a/fastplotlib/layouts/_utils.py
+++ b/fastplotlib/layouts/_utils.py
@@ -1,5 +1,6 @@
from typing import *
+import pygfx
from pygfx import WgpuRenderer, Texture
# default auto-determined canvas
@@ -88,3 +89,58 @@ def make_canvas_and_renderer(
renderer = WgpuRenderer(canvas)
return canvas, renderer
+
+
+def create_camera(
+ camera_type: Union[pygfx.PerspectiveCamera, str],
+) -> pygfx.PerspectiveCamera:
+ if isinstance(camera_type, pygfx.PerspectiveCamera):
+ return camera_type
+
+ elif camera_type == "2d":
+ # use perspective for orthographic, makes it easier to then switch to controllers that make sense with fov > 0
+ return pygfx.PerspectiveCamera(fov=0)
+
+ elif camera_type == "3d":
+ return pygfx.PerspectiveCamera()
+
+ else:
+ raise ValueError(
+ "camera must be one of: '2d', '3d' or an instance of pygfx.PerspectiveCamera"
+ )
+
+
+controller_types = {
+ "fly": pygfx.FlyController,
+ "panzoom": pygfx.PanZoomController,
+ "trackball": pygfx.TrackballController,
+ "orbit": pygfx.OrbitController,
+}
+
+
+def create_controller(
+ controller_type: Union[pygfx.Controller, None, str],
+ camera: pygfx.PerspectiveCamera,
+) -> pygfx.Controller:
+ """
+ Creates the controllers and adds the camera to it.
+ """
+ if isinstance(controller_type, pygfx.Controller):
+ controller_type.add_camera(camera)
+ return controller_type
+
+ if controller_type is None:
+ # default controllers
+ if camera.fov == 0:
+ # default for orthographic
+ return pygfx.PanZoomController(camera)
+ else:
+ return pygfx.FlyController(camera)
+
+ if controller_type not in controller_types.keys():
+ raise KeyError(
+ f"controller must be a valid pygfx.Controller or one of: "
+ f"{list(controller_types.keys())}, you have passed: {controller_type}"
+ )
+
+ return controller_types[controller_type](camera)
diff --git a/fastplotlib/widgets/image.py b/fastplotlib/widgets/image.py
index 522b0a962..56aa4aebc 100644
--- a/fastplotlib/widgets/image.py
+++ b/fastplotlib/widgets/image.py
@@ -550,7 +550,7 @@ def __init__(
self._dims_max_bounds[_dim], array.shape[order.index(_dim)]
)
- grid_plot_kwargs_default = {"controllers": "sync"}
+ grid_plot_kwargs_default = {"controller_ids": "sync"}
if grid_plot_kwargs is None:
grid_plot_kwargs = dict()
@@ -885,6 +885,7 @@ def set_data(
frame = self._process_frame_apply(frame, i)
new_graphic = ImageGraphic(data=frame, name="image_widget_managed")
subplot.insert_graphic(graphic=new_graphic)
+ subplot.docks["right"]["histogram_lut"].image_graphic = new_graphic
if new_array.ndim > 2:
# to set max of time slider, txy or tzxy