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
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