diff --git a/examples/gridplot.ipynb b/examples/gridplot.ipynb
index 2e558fa16..a5b7f4209 100644
--- a/examples/gridplot.ipynb
+++ b/examples/gridplot.ipynb
@@ -28,7 +28,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "8c47742795824d9e95c8d3b46df08a51",
+ "model_id": "cae156b0748142ff9335f4612d39e9ed",
"version_major": 2,
"version_minor": 0
},
@@ -42,7 +42,7 @@
{
"data": {
"text/html": [
- "

initial snapshot
"
+ "
initial snapshot
"
],
"text/plain": [
""
@@ -54,7 +54,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "3d95102a394d4fbcb4d4bba9fa527089",
+ "model_id": "03295486e8ba449d80429f53832698e4",
"version_major": 2,
"version_minor": 0
},
@@ -90,7 +90,7 @@
"grid_plot = GridPlot(\n",
" shape=grid_shape,\n",
" controllers=controllers,\n",
- " names=names\n",
+ " names=names,\n",
")\n",
"\n",
"\n",
@@ -130,10 +130,10 @@
{
"data": {
"text/plain": [
- "subplot0: Subplot @ 0x7f418bbcaef0\n",
+ "subplot0: Subplot @ 0x7fbddc5bf9d0\n",
" parent: None\n",
" Graphics:\n",
- "\t'rand-image' fastplotlib.ImageGraphic @ 0x7f418bbcae90"
+ "\t'rand-image' fastplotlib.ImageGraphic @ 0x7fbddc5bf970"
]
},
"execution_count": 3,
@@ -155,10 +155,10 @@
{
"data": {
"text/plain": [
- "subplot0: Subplot @ 0x7f418bbcaef0\n",
+ "subplot0: Subplot @ 0x7fbddc5bf9d0\n",
" parent: None\n",
" Graphics:\n",
- "\t'rand-image' fastplotlib.ImageGraphic @ 0x7f418bbcae90"
+ "\t'rand-image' fastplotlib.ImageGraphic @ 0x7fbddc5bf970"
]
},
"execution_count": 4,
@@ -189,7 +189,7 @@
{
"data": {
"text/plain": [
- "'rand-image' fastplotlib.ImageGraphic @ 0x7f418bbcae90"
+ "'rand-image' fastplotlib.ImageGraphic @ 0x7fbddc5bf970"
]
},
"execution_count": 5,
@@ -209,7 +209,8 @@
"metadata": {},
"outputs": [],
"source": [
- "grid_plot[\"subplot0\"][\"rand-image\"].clim = (0.6, 0.8)"
+ "grid_plot[\"subplot0\"][\"rand-image\"].vmin = 0.6\n",
+ "grid_plot[\"subplot0\"][\"rand-image\"].vmax = 0.8"
]
},
{
@@ -222,17 +223,18 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": 9,
"id": "2fafe992-4783-40f2-b044-26a2835dd50a",
"metadata": {},
"outputs": [],
"source": [
- "grid_plot[1, 0][\"rand-image\"].clim = (0.1, 0.3)"
+ "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,
+ "execution_count": 10,
"id": "a025b76c-77f8-4aeb-ac33-5bb6d0bb5a9a",
"metadata": {},
"outputs": [
@@ -242,7 +244,7 @@
"'image'"
]
},
- "execution_count": 8,
+ "execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
diff --git a/examples/simple.ipynb b/examples/simple.ipynb
index b1d3b88b5..b64aad56c 100644
--- a/examples/simple.ipynb
+++ b/examples/simple.ipynb
@@ -41,7 +41,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "d8de4d1574414bfcb186eaa62872c781",
+ "model_id": "a4fb8c6563f14824971deecd96965972",
"version_major": 2,
"version_minor": 0
},
@@ -55,7 +55,7 @@
{
"data": {
"text/html": [
- "
initial snapshot
"
+ "
initial snapshot
"
],
"text/plain": [
""
@@ -67,7 +67,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "5c46808362e840ad9cc50bfa2e5f7346",
+ "model_id": "95958dc9ae4e4bf18aa1d1f68ac667fb",
"version_major": 2,
"version_minor": 0
},
@@ -94,6 +94,16 @@
"plot.show()"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "de816c88-1c4a-4071-8a5e-c46c93671ef5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "image_graphic.cmap = \"viridis\""
+ ]
+ },
{
"cell_type": "markdown",
"id": "be5b408f-dd91-4e36-807a-8c22c8d7d216",
@@ -112,17 +122,17 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": 4,
"id": "e6ba689c-ff4a-44ef-9663-f2c8755072c4",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
- "['random-image' fastplotlib.ImageGraphic @ 0x7fa21d5dd1b0]"
+ "['random-image' fastplotlib.ImageGraphic @ 0x7f748162fd90]"
]
},
- "execution_count": 3,
+ "execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
@@ -133,17 +143,17 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": 5,
"id": "5b18f4e3-e13b-46d5-af1f-285c5a7fdc12",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
- "'random-image' fastplotlib.ImageGraphic @ 0x7faf6bd71600"
+ "'random-image' fastplotlib.ImageGraphic @ 0x7f748162fd90"
]
},
- "execution_count": 7,
+ "execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
@@ -162,17 +172,17 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": 6,
"id": "2b5c1321-1fd4-44bc-9433-7439ad3e22cf",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
- "'random-image' fastplotlib.ImageGraphic @ 0x7fa21d5dd1b0"
+ "'random-image' fastplotlib.ImageGraphic @ 0x7f748162fd90"
]
},
- "execution_count": 4,
+ "execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
@@ -183,7 +193,7 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": 7,
"id": "b12bf75e-4e93-4930-9146-e96324fdf3f6",
"metadata": {},
"outputs": [
@@ -193,7 +203,7 @@
"True"
]
},
- "execution_count": 5,
+ "execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
@@ -205,7 +215,9 @@
{
"cell_type": "markdown",
"id": "1cb03f42-1029-4b16-a16b-35447d9e2955",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"source": [
"### Image updates\n",
"\n",
@@ -214,14 +226,14 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": 8,
"id": "aadd757f-6379-4f52-a709-46aa57c56216",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "f2ccae25614d47cda980e011f17771cb",
+ "model_id": "ab597f9780064497b7ab0fc8d52dd538",
"version_major": 2,
"version_minor": 0
},
@@ -235,7 +247,7 @@
{
"data": {
"text/html": [
- "
initial snapshot
"
+ "
initial snapshot
"
],
"text/plain": [
""
@@ -247,7 +259,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "33ca1052a871445188bbe7d3c8725cf1",
+ "model_id": "c75d9b5ae24b4c98b865ee7a869d665f",
"version_major": 2,
"version_minor": 0
},
@@ -255,7 +267,7 @@
"JupyterWgpuCanvas()"
]
},
- "execution_count": 6,
+ "execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
@@ -264,6 +276,8 @@
"# create another `Plot` instance\n",
"plot_v = Plot()\n",
"\n",
+ "plot.canvas.max_buffered_frames = 1\n",
+ "\n",
"# make some random data again\n",
"data = np.random.rand(512, 512)\n",
"\n",
@@ -295,14 +309,14 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": 9,
"id": "86e70b1e-4328-4035-b992-70dff16d2a69",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "f486d69ebedb454ebc8d0fd164b3c04c",
+ "model_id": "36a85c99623947c9a3ed729b09f6b212",
"version_major": 2,
"version_minor": 0
},
@@ -316,7 +330,7 @@
{
"data": {
"text/html": [
- "
initial snapshot
"
+ "
initial snapshot
"
],
"text/plain": [
""
@@ -328,7 +342,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "c275d36864264618af7c9adde7ee5173",
+ "model_id": "692cfda570284fd0ae43f45530f87885",
"version_major": 2,
"version_minor": 0
},
@@ -336,7 +350,7 @@
"JupyterWgpuCanvas()"
]
},
- "execution_count": 7,
+ "execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
@@ -377,19 +391,19 @@
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": 10,
"id": "ef9743b3-5f81-4b79-9502-fa5fca08e56d",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "4dd8df34e05948638f5191c81735ddc8",
+ "model_id": "b9561a7e5aec4ad2b42b263c2fbdb87d",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
- "VBox(children=(JupyterWgpuCanvas(frame_feedback={'index': 819, 'timestamp': 1671971212.7522302, 'localtime': 1…"
+ "VBox(children=(JupyterWgpuCanvas(frame_feedback={'index': 137, 'timestamp': 1671994231.3569584, 'localtime': 1…"
]
},
"metadata": {},
@@ -411,19 +425,19 @@
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": 11,
"id": "11839d95-8ff7-444c-ae13-6b072c3112c5",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "a7b4e59995ce420e8b983ae6f9b3d4d8",
+ "model_id": "70b73156a4dd4710858258eee985ecae",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
- "HBox(children=(JupyterWgpuCanvas(frame_feedback={'index': 1004, 'timestamp': 1671971220.7120316, 'localtime': …"
+ "HBox(children=(JupyterWgpuCanvas(frame_feedback={'index': 244, 'timestamp': 1671994235.1249204, 'localtime': 1…"
]
},
"metadata": {},
@@ -454,7 +468,7 @@
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": 12,
"id": "0bcedf83-cbdd-4ec2-b8d5-172aa72a3e04",
"metadata": {},
"outputs": [],
@@ -529,7 +543,7 @@
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": 13,
"id": "8b560151-c258-415c-a20d-3cccd421f44a",
"metadata": {},
"outputs": [
@@ -539,7 +553,7 @@
"(1000, 512, 512)"
]
},
- "execution_count": 11,
+ "execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
@@ -566,14 +580,14 @@
},
{
"cell_type": "code",
- "execution_count": 12,
+ "execution_count": 14,
"id": "62166a9f-ab43-45cc-a6db-6d441387e9a5",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "f4b20b310229436889a80e9676e36865",
+ "model_id": "bbc9fc354724480898744eefc88ab995",
"version_major": 2,
"version_minor": 0
},
@@ -587,7 +601,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "5b1232536af6482e926793142c22aa3b",
+ "model_id": "0462fb2ba19a42c587111e7652d4343c",
"version_major": 2,
"version_minor": 0
},
@@ -633,7 +647,9 @@
{
"cell_type": "markdown",
"id": "e7859338-8162-408b-ac72-37e606057045",
- "metadata": {},
+ "metadata": {
+ "tags": []
+ },
"source": [
"# Line plots\n",
"\n",
@@ -642,6 +658,18 @@
"This example plots a sine wave, cosine wave, and ricker wavelet and demonstrates how **Graphic Features** can be modified by slicing!"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "b69b8edd-f87f-406d-af56-e851d4fc6e77",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from fastplotlib import Plot\n",
+ "from ipywidgets import VBox, HBox, IntSlider\n",
+ "import numpy as np"
+ ]
+ },
{
"cell_type": "markdown",
"id": "a6fee1c2-4a24-4325-bca2-26e5a4bf6338",
@@ -652,7 +680,7 @@
},
{
"cell_type": "code",
- "execution_count": 13,
+ "execution_count": 16,
"id": "8e8280da-b421-43a5-a1a6-2a196a408e9a",
"metadata": {},
"outputs": [],
@@ -670,7 +698,7 @@
"# sinc function\n",
"a = 0.5\n",
"ys = np.sinc(xs) * 3 + 8\n",
- "sinc = np.dstack([xs, ys])[0]\n"
+ "sinc = np.dstack([xs, ys])[0]"
]
},
{
@@ -683,14 +711,14 @@
},
{
"cell_type": "code",
- "execution_count": 14,
+ "execution_count": 17,
"id": "93a5d1e6-d019-4dd0-a0d1-25d1704ab7a7",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "d81c444c49124d7d9bbb88a9c64690fa",
+ "model_id": "380434748ed44486979846c314606408",
"version_major": 2,
"version_minor": 0
},
@@ -704,7 +732,7 @@
{
"data": {
"text/html": [
- "
initial snapshot
"
+ "
initial snapshot
"
],
"text/plain": [
""
@@ -716,7 +744,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "85e6e88bd68b4255b190bf7563ad77e4",
+ "model_id": "f1cab77b2f60497ab52fbf19764146ad",
"version_major": 2,
"version_minor": 0
},
@@ -724,7 +752,7 @@
"JupyterWgpuCanvas()"
]
},
- "execution_count": 14,
+ "execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
@@ -734,7 +762,7 @@
"plot_l = Plot()\n",
"\n",
"# plot sine wave, use a single color\n",
- "sine_graphic = plot_l.add_line(data=sine, size=1.5, colors=\"magenta\")\n",
+ "sine_graphic = plot_l.add_line(data=sine, size=5, colors=\"magenta\")\n",
"\n",
"# you can also use colormaps for lines!\n",
"cosine_graphic = plot_l.add_line(data=cosine, size=12, cmap=\"autumn\")\n",
@@ -756,17 +784,21 @@
},
{
"cell_type": "code",
- "execution_count": 15,
+ "execution_count": 18,
"id": "cb0d13ed-ef07-46ff-b19e-eeca4c831037",
"metadata": {},
"outputs": [],
"source": [
- "# fancy indexing of colors\n",
+ "# indexing of colors\n",
"cosine_graphic.colors[:15] = \"magenta\"\n",
"cosine_graphic.colors[90:] = \"red\"\n",
"cosine_graphic.colors[60] = \"w\"\n",
"\n",
- "# more complex indexing, set the blue value from an array\n",
+ "# indexing to assign colormaps to entire lines or segments\n",
+ "sinc_graphic.cmap[10:50] = \"gray\"\n",
+ "sine_graphic.cmap = \"seismic\"\n",
+ "\n",
+ "# more complex indexing, set the blue value directly from an array\n",
"cosine_graphic.colors[65:90, 0] = np.linspace(0, 1, 90-65)"
]
},
@@ -775,12 +807,12 @@
"id": "c9689887-cdf3-4a4d-948f-7efdb09bde4e",
"metadata": {},
"source": [
- "## You can capture changes to a graphic features as events"
+ "## You can capture changes to a graphic feature as events"
]
},
{
"cell_type": "code",
- "execution_count": 16,
+ "execution_count": 19,
"id": "cfa001f6-c640-4f91-beb0-c19b030e503f",
"metadata": {},
"outputs": [],
@@ -794,7 +826,7 @@
},
{
"cell_type": "code",
- "execution_count": 17,
+ "execution_count": 20,
"id": "bb8a0f95-0063-4cd4-a117-e3d62c6e120d",
"metadata": {},
"outputs": [
@@ -802,9 +834,9 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "FeatureEvent @ 0x7fa1f3a9ac50\n",
+ "FeatureEvent @ 0x7f7429bca830\n",
"type: color-changed\n",
- "pick_info: {'index': range(15, 50, 3), 'world_object': , 'new_data': array([[0., 1., 1., 1.],\n",
+ "pick_info: {'index': range(15, 50, 3), 'world_object': , 'new_data': array([[0., 1., 1., 1.],\n",
" [0., 1., 1., 1.],\n",
" [0., 1., 1., 1.],\n",
" [0., 1., 1., 1.],\n",
@@ -836,7 +868,7 @@
},
{
"cell_type": "code",
- "execution_count": 18,
+ "execution_count": 21,
"id": "d1a4314b-5723-43c7-94a0-b4cbb0e44d60",
"metadata": {},
"outputs": [],
@@ -847,7 +879,7 @@
},
{
"cell_type": "code",
- "execution_count": 19,
+ "execution_count": 22,
"id": "682db47b-8c7a-4934-9be4-2067e9fb12d5",
"metadata": {},
"outputs": [],
@@ -865,7 +897,7 @@
},
{
"cell_type": "code",
- "execution_count": 20,
+ "execution_count": 23,
"id": "fcba75b7-9a1e-4aae-9dec-715f7f7456c3",
"metadata": {},
"outputs": [],
@@ -875,7 +907,7 @@
},
{
"cell_type": "code",
- "execution_count": 21,
+ "execution_count": 24,
"id": "763b9943-53a4-4e2a-b47a-4e9e5c9d7be3",
"metadata": {},
"outputs": [],
@@ -893,7 +925,7 @@
},
{
"cell_type": "code",
- "execution_count": 22,
+ "execution_count": 25,
"id": "64a20a16-75a5-4772-a849-630ade9be4ff",
"metadata": {},
"outputs": [],
@@ -903,7 +935,7 @@
},
{
"cell_type": "code",
- "execution_count": 23,
+ "execution_count": 26,
"id": "fb093046-c94c-4085-86b4-8cd85cb638ff",
"metadata": {},
"outputs": [],
@@ -913,7 +945,7 @@
},
{
"cell_type": "code",
- "execution_count": 24,
+ "execution_count": 27,
"id": "f05981c3-c768-4631-ae62-6a8407b20c4c",
"metadata": {},
"outputs": [],
@@ -931,14 +963,14 @@
},
{
"cell_type": "code",
- "execution_count": 25,
+ "execution_count": 28,
"id": "9c51229f-13a2-4653-bff3-15d43ddbca7b",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "ee8725a8c6944a799eac247a98737688",
+ "model_id": "8462601c909049dda4e8fdcbb526c6f6",
"version_major": 2,
"version_minor": 0
},
@@ -960,7 +992,7 @@
{
"data": {
"text/html": [
- "
initial snapshot
"
+ "
initial snapshot
"
],
"text/plain": [
""
@@ -972,7 +1004,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "d1595cc3616e4018902c09e65cd99f40",
+ "model_id": "8ae62d829add48c1b5f26f9633b5b0ed",
"version_major": 2,
"version_minor": 0
},
@@ -980,7 +1012,7 @@
"JupyterWgpuCanvas()"
]
},
- "execution_count": 25,
+ "execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
@@ -1019,14 +1051,26 @@
},
{
"cell_type": "code",
- "execution_count": 26,
+ "execution_count": 29,
+ "id": "2ecb2385-8fa4-4239-881c-b754c24aed9f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from fastplotlib import Plot\n",
+ "from ipywidgets import VBox, HBox, IntSlider\n",
+ "import numpy as np"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
"id": "39252df5-9ae5-4132-b97b-2785c5fa92ea",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "9a091850b30e49b7b04153d6604176aa",
+ "model_id": "50c0b259d1f04e239dfbf733463fac3e",
"version_major": 2,
"version_minor": 0
},
@@ -1040,7 +1084,7 @@
{
"data": {
"text/html": [
- "
initial snapshot
"
+ "
initial snapshot
"
],
"text/plain": [
""
@@ -1052,7 +1096,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "cc57fd6c0807411bbde07b968fe7deef",
+ "model_id": "c138433d6890443faeab644cc5521b0d",
"version_major": 2,
"version_minor": 0
},
@@ -1060,7 +1104,7 @@
"JupyterWgpuCanvas()"
]
},
- "execution_count": 26,
+ "execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
@@ -1111,7 +1155,7 @@
},
{
"cell_type": "code",
- "execution_count": 27,
+ "execution_count": 31,
"id": "8fa46ec0-8680-44f5-894c-559de3145932",
"metadata": {},
"outputs": [],
@@ -1122,7 +1166,7 @@
},
{
"cell_type": "code",
- "execution_count": 28,
+ "execution_count": 32,
"id": "e4dc71e4-5144-436f-a464-f2a29eee8f0b",
"metadata": {},
"outputs": [],
@@ -1133,7 +1177,7 @@
},
{
"cell_type": "code",
- "execution_count": 29,
+ "execution_count": 33,
"id": "5b637a29-cd5e-4011-ab81-3f91490d9ecd",
"metadata": {},
"outputs": [],
@@ -1144,7 +1188,7 @@
},
{
"cell_type": "code",
- "execution_count": 30,
+ "execution_count": 34,
"id": "a4084fce-78a2-48b3-9a0d-7b57c165c3c1",
"metadata": {},
"outputs": [],
@@ -1155,7 +1199,7 @@
},
{
"cell_type": "code",
- "execution_count": 31,
+ "execution_count": 35,
"id": "f486083e-7c58-4255-ae1a-3fe5d9bfaeed",
"metadata": {},
"outputs": [],
@@ -1176,19 +1220,19 @@
},
{
"cell_type": "code",
- "execution_count": 32,
+ "execution_count": 36,
"id": "f404a5ea-633b-43f5-87d1-237017bbca2a",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
- "model_id": "62aa7a1eb8794301912c4f2da74abb25",
+ "model_id": "38cd7d8eacf3493c9bddd01ee3ec40f4",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
- "VBox(children=(HBox(children=(JupyterWgpuCanvas(frame_feedback={'index': 2402, 'timestamp': 1671971185.3935852…"
+ "VBox(children=(HBox(children=(JupyterWgpuCanvas(frame_feedback={'index': 123, 'timestamp': 1671994226.4736164,…"
]
},
"metadata": {},
diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py
index a1a2633b9..3cf358451 100644
--- a/fastplotlib/graphics/_base.py
+++ b/fastplotlib/graphics/_base.py
@@ -1,63 +1,33 @@
from typing import *
-import pygfx
+from pygfx import WorldObject
+from pygfx.linalg import Vector3
-from ..utils import get_colors
-from .features import GraphicFeature, DataFeature, ColorFeature, PresentFeature
+from .features import GraphicFeature, PresentFeature
-class Graphic:
+class BaseGraphic:
+ def __init_subclass__(cls, **kwargs):
+ """set the type of the graphic in lower case like "image", "line_collection", etc."""
+ cls.type = cls.__name__.lower().replace("graphic", "").replace("collection", "_collection")
+ super().__init_subclass__(**kwargs)
+
+
+class Graphic(BaseGraphic):
def __init__(
self,
- data,
- colors: Any = False,
- n_colors: int = None,
- cmap: str = None,
- alpha: float = 1.0,
name: str = None
):
"""
Parameters
----------
- data: array-like
- data to show in the graphic, must be float32.
- Automatically converted to float32 for numpy arrays.
- Tensorflow Tensors also work but this is not fully
- tested and might not be supported in the future.
-
- colors: Any
- if ``False``, no color generation is performed, cmap is also ignored.
-
- n_colors
-
- cmap: str
- name of colormap to use
-
- alpha: float, optional
- alpha value for the colors
-
name: str, optional
name this graphic, makes it indexable within plots
"""
- # self.data = data.astype(np.float32)
- self.data = DataFeature(parent=self, data=data, graphic_name=self.__class__.__name__)
- self.colors = None
self.name = name
-
- if n_colors is None:
- n_colors = self.data.feature_data.shape[0]
-
- if cmap is not None and colors is not False:
- colors = get_colors(n_colors=n_colors, cmap=cmap, alpha=alpha)
-
- if colors is not False:
- self.colors = ColorFeature(parent=self, colors=colors, n_colors=n_colors, alpha=alpha)
-
- # different from visible, toggles the Graphic presence in the Scene
- # useful for bbox calculations to ignore these Graphics
self.present = PresentFeature(parent=self)
valid_features = ["visible"]
@@ -69,9 +39,14 @@ def __init__(
self._valid_features = tuple(valid_features)
@property
- def world_object(self) -> pygfx.WorldObject:
+ def world_object(self) -> WorldObject:
return self._world_object
+ @property
+ def position(self) -> Vector3:
+ """The position of the graphic"""
+ return self.world_object.position
+
@property
def interact_features(self) -> Tuple[str]:
"""The features for this ``Graphic`` that support interaction."""
@@ -87,7 +62,7 @@ def visible(self, v):
self.world_object.visible = v
@property
- def children(self) -> pygfx.WorldObject:
+ def children(self) -> WorldObject:
return self.world_object.children
def __setattr__(self, key, value):
diff --git a/fastplotlib/graphics/features/__init__.py b/fastplotlib/graphics/features/__init__.py
index 2c489c94f..1fcb71246 100644
--- a/fastplotlib/graphics/features/__init__.py
+++ b/fastplotlib/graphics/features/__init__.py
@@ -1,4 +1,4 @@
-from ._colors import ColorFeature
-from ._data import DataFeature
+from ._colors import ColorFeature, CmapFeature, ImageCmapFeature
+from ._data import PointsDataFeature, ImageDataFeature
from ._present import PresentFeature
from ._base import GraphicFeature
diff --git a/fastplotlib/graphics/features/_base.py b/fastplotlib/graphics/features/_base.py
index 9292f4944..519bf40d0 100644
--- a/fastplotlib/graphics/features/_base.py
+++ b/fastplotlib/graphics/features/_base.py
@@ -90,6 +90,22 @@ def _call_event_handlers(self, event_data: FeatureEvent):
def cleanup_slice(key: Union[int, slice], upper_bound) -> Union[slice, int]:
+ """
+
+ If the key in an `int`, it just returns it. Otherwise,
+ it parses it and removes the `None` vals and replaces
+ them with corresponding values that can be used to
+ create a `range`, get `len` etc.
+
+ Parameters
+ ----------
+ key
+ upper_bound
+
+ Returns
+ -------
+
+ """
if isinstance(key, int):
return key
@@ -157,7 +173,7 @@ def _upper_bound(self) -> int:
return self.feature_data.shape[0]
def _update_range_indices(self, key):
- """Currently used by colors and data"""
+ """Currently used by colors and positions data"""
key = cleanup_slice(key, self._upper_bound)
if isinstance(key, int):
@@ -178,5 +194,3 @@ def _update_range_indices(self, key):
self._buffer.update_range(ix, size=1)
else:
raise TypeError("must pass int or slice to update range")
-
-
diff --git a/fastplotlib/graphics/features/_colors.py b/fastplotlib/graphics/features/_colors.py
index f45f99040..afb0d85a8 100644
--- a/fastplotlib/graphics/features/_colors.py
+++ b/fastplotlib/graphics/features/_colors.py
@@ -1,6 +1,7 @@
import numpy as np
-from ._base import GraphicFeatureIndexable, cleanup_slice, FeatureEvent
+from ._base import GraphicFeature, GraphicFeatureIndexable, cleanup_slice, FeatureEvent
+from ...utils import get_colors, get_cmap_texture
from pygfx import Color
@@ -15,7 +16,7 @@ def __getitem__(self, item):
def __repr__(self):
return repr(self._buffer.data)
- def __init__(self, parent, colors, n_colors, alpha: float = 1.0):
+ def __init__(self, parent, colors, n_colors: int, alpha: float = 1.0):
"""
ColorFeature
@@ -27,7 +28,12 @@ def __init__(self, parent, colors, n_colors, alpha: float = 1.0):
specify colors as a single human readable string, RGBA array,
or an iterable of strings or RGBA arrays
- n_colors: number of colors to hold, if passing in a single str or single RGBA array
+ n_colors: int
+ number of colors to hold, if passing in a single str or single RGBA array
+
+ alpha: float
+ alpha value for the colors
+
"""
# if provided as a numpy array of str
if isinstance(colors, np.ndarray):
@@ -185,3 +191,55 @@ def _feature_changed(self, key, new_data):
event_data = FeatureEvent(type="color-changed", pick_info=pick_info)
self._call_event_handlers(event_data)
+
+
+class CmapFeature(ColorFeature):
+ """
+ Indexable colormap feature, mostly wraps colors and just provides a way to set colormaps.
+ """
+ def __init__(self, parent, colors):
+ super(ColorFeature, self).__init__(parent, colors)
+
+ def __setitem__(self, key, value):
+ key = cleanup_slice(key, self._upper_bound)
+ if not isinstance(key, slice):
+ raise TypeError("Cannot set cmap on single indices, must pass a slice object or "
+ "set it on the entire data.")
+
+ n_colors = len(range(key.start, key.stop, key.step))
+
+ colors = get_colors(n_colors, cmap=value)
+ super(CmapFeature, self).__setitem__(key, colors)
+
+
+class ImageCmapFeature(GraphicFeature):
+ """
+ Colormap for ImageGraphic
+ """
+ def __init__(self, parent, cmap: str):
+ cmap_texture_view = get_cmap_texture(cmap)
+ super(ImageCmapFeature, self).__init__(parent, cmap_texture_view)
+ self.name = cmap
+
+ def _set(self, cmap_name: str):
+ self._parent.world_object.material.map.texture.data[:] = get_colors(256, cmap_name)
+ self._parent.world_object.material.map.texture.update_range((0, 0, 0), size=(256, 1, 1))
+ self.name = cmap_name
+
+ self._feature_changed(key=None, new_data=self.name)
+
+ def __repr__(self):
+ return repr(self.name)
+
+ def _feature_changed(self, key, new_data):
+ # this is a non-indexable feature so key=None
+
+ pick_info = {
+ "index": None,
+ "world_object": self._parent.world_object,
+ "new_data": new_data
+ }
+
+ event_data = FeatureEvent(type="cmap-changed", pick_info=pick_info)
+
+ self._call_event_handlers(event_data)
diff --git a/fastplotlib/graphics/features/_data.py b/fastplotlib/graphics/features/_data.py
index 6e1feac2a..1839bd9a1 100644
--- a/fastplotlib/graphics/features/_data.py
+++ b/fastplotlib/graphics/features/_data.py
@@ -1,32 +1,30 @@
-from ._base import GraphicFeatureIndexable, cleanup_slice, FeatureEvent
-from pygfx import Buffer
from typing import *
-from ...utils import fix_data, to_float32
+
+import numpy as np
+from pygfx import Buffer, Texture
+
+from ._base import GraphicFeatureIndexable, cleanup_slice, FeatureEvent
-class DataFeature(GraphicFeatureIndexable):
+def to_float32(array):
+ if isinstance(array, np.ndarray):
+ return array.astype(np.float32, copy=False)
+
+ return array
+
+
+class PointsDataFeature(GraphicFeatureIndexable):
"""
- Access to the buffer data being shown in the graphic.
- Supports fancy indexing if the data array also does.
+ Access to the vertex buffer data shown in the graphic.
+ Supports fancy indexing if the data array also supports it.
"""
- # the correct data buffer is search for in this order
- data_buffer_names = ["grid", "positions"]
-
- def __init__(self, parent, data: Any, graphic_name):
- data = fix_data(data, graphic_name=graphic_name)
- self.graphic_name = graphic_name
- super(DataFeature, self).__init__(parent, data)
+ def __init__(self, parent, data: Any):
+ data = self._fix_data(data, parent)
+ super(PointsDataFeature, self).__init__(parent, data)
@property
def _buffer(self) -> Buffer:
- buffer = getattr(self._parent.world_object.geometry, self._buffer_name)
- return buffer
-
- @property
- def _buffer_name(self) -> str:
- for buffer_name in self.data_buffer_names:
- if hasattr(self._parent.world_object.geometry, buffer_name):
- return buffer_name
+ return self._parent.world_object.geometry.positions
def __repr__(self):
return repr(self._buffer.data)
@@ -34,31 +32,103 @@ def __repr__(self):
def __getitem__(self, item):
return self._buffer.data[item]
+ def _fix_data(self, data, parent):
+ graphic_type = parent.__class__.__name__
+
+ if data.ndim == 1:
+ # for scatter if we receive just 3 points in a 1d array, treat it as just a single datapoint
+ # this is different from fix_data for LineGraphic since there we assume that a 1d array
+ # is just y-values
+ if graphic_type == "ScatterGraphic":
+ data = np.array([data])
+ elif graphic_type == "LineGraphic":
+ data = np.dstack([np.arange(data.size), data])[0].astype(np.float32)
+
+ if data.shape[1] != 3:
+ if data.shape[1] != 2:
+ raise ValueError(f"Must pass 1D, 2D or 3D data to {graphic_type}")
+
+ # zeros for z
+ zs = np.zeros(data.shape[0], dtype=np.float32)
+
+ data = np.dstack([data[:, 0], data[:, 1], zs])[0]
+
+ return data
+
def __setitem__(self, key, value):
+ # put data into right shape if they're only indexing datapoints
if isinstance(key, (slice, int)):
- # data must be provided in the right shape
- value = fix_data(value, graphic_name=self.graphic_name)
- else:
- # otherwise just make sure float32
- value = to_float32(value)
+ value = self._fix_data(value, self._parent)
+ # otherwise assume that they have the right shape
+ # numpy will throw errors if it can't broadcast
+
self._buffer.data[key] = value
self._update_range(key)
+ # avoid creating dicts constantly if there are no events to handle
+ if len(self._event_handlers) > 0:
+ self._feature_changed(key, value)
def _update_range(self, key):
- if self._buffer_name == "grid":
- self._update_range_grid(key)
- self._feature_changed(key=None, new_data=None)
- elif self._buffer_name == "positions":
- self._update_range_indices(key)
- self._feature_changed(key=key, new_data=None)
+ self._update_range_indices(key)
+
+ def _feature_changed(self, key, new_data):
+ if key is not None:
+ key = cleanup_slice(key, self._upper_bound)
+ if isinstance(key, int):
+ indices = [key]
+ elif isinstance(key, slice):
+ indices = range(key.start, key.stop, key.step)
+ elif key is None:
+ indices = None
+
+ pick_info = {
+ "index": indices,
+ "world_object": self._parent.world_object,
+ "new_data": new_data
+ }
+
+ event_data = FeatureEvent(type="data-changed", pick_info=pick_info)
+
+ self._call_event_handlers(event_data)
- def _update_range_grid(self, key):
- # image data
- self._buffer.update_range((0, 0, 0), self._buffer.size)
+
+class ImageDataFeature(GraphicFeatureIndexable):
+ """
+ Access to the TextureView buffer shown in an ImageGraphic.
+ """
+
+ def __init__(self, parent, data: Any):
+ if data.ndim != 2:
+ raise ValueError("`data.ndim !=2`, you must pass only a 2D array to an Image graphic")
+
+ data = to_float32(data)
+ super(ImageDataFeature, self).__init__(parent, data)
+
+ @property
+ def _buffer(self) -> Texture:
+ return self._parent.world_object.geometry.grid.texture
+
+ def __repr__(self):
+ return repr(self._buffer.data)
+
+ def __getitem__(self, item):
+ return self._buffer.data[item]
+
+ def __setitem__(self, key, value):
+ # make sure float32
+ value = to_float32(value)
+
+ self._buffer.data[key] = value
+ self._update_range(key)
+
+ # avoid creating dicts constantly if there are no events to handle
+ if len(self._event_handlers) > 0:
+ self._feature_changed(key, value)
+
+ def _update_range(self, key):
+ self._buffer.update_range((0, 0, 0), size=self._buffer.size)
def _feature_changed(self, key, new_data):
- # for now if key=None that means all data changed, i.e. ImageGraphic
- # also for now new data isn't stored for DataFeature
if key is not None:
key = cleanup_slice(key, self._upper_bound)
if isinstance(key, int):
diff --git a/fastplotlib/graphics/image.py b/fastplotlib/graphics/image.py
index 77c531c8a..48459c63e 100644
--- a/fastplotlib/graphics/image.py
+++ b/fastplotlib/graphics/image.py
@@ -1,10 +1,10 @@
from typing import *
-import numpy as np
import pygfx
from ._base import Graphic
-from ..utils import quick_min_max, get_cmap_texture
+from .features import ImageCmapFeature, ImageDataFeature
+from ..utils import quick_min_max
class ImageGraphic(Graphic):
@@ -14,6 +14,7 @@ def __init__(
vmin: int = None,
vmax: int = None,
cmap: str = 'plasma',
+ filter: str = "nearest",
*args,
**kwargs
):
@@ -32,10 +33,15 @@ def __init__(
vmax: int, optional
maximum value for color scaling, calculated from data if not provided
- cmap: str, optional
+ cmap: str, optional, default "nearest"
colormap to use to display the image data, default is ``"plasma"``
+
+ filter: str, optional, default "nearest"
+ interpolation filter, one of "nearest" or "linear"
+
args:
additional arguments passed to Graphic
+
kwargs:
additional keyword arguments passed to Graphic
@@ -59,23 +65,41 @@ def __init__(
plot.show()
"""
- if data.ndim != 2:
- raise ValueError("`data.ndim !=2`, you must pass only a 2D array to `data`")
- super().__init__(data, cmap=cmap, *args, **kwargs)
+ super().__init__(*args, **kwargs)
+
+ self.data = ImageDataFeature(self, data)
if (vmin is None) or (vmax is None):
vmin, vmax = quick_min_max(data)
+ self.cmap = ImageCmapFeature(self, cmap)
+
+ texture_view = pygfx.Texture(self.data.feature_data, dim=2).get_view(filter=filter)
+
self._world_object: pygfx.Image = pygfx.Image(
- pygfx.Geometry(grid=pygfx.Texture(self.data.feature_data, dim=2)),
- pygfx.ImageBasicMaterial(clim=(vmin, vmax), map=get_cmap_texture(cmap))
+ pygfx.Geometry(grid=texture_view),
+ pygfx.ImageBasicMaterial(clim=(vmin, vmax), map=self.cmap.feature_data)
)
@property
- def clim(self) -> Tuple[float, float]:
- return self.world_object.material.clim
+ def vmin(self) -> float:
+ return self.world_object.material.clim[0]
+
+ @vmin.setter
+ def vmin(self, value: float):
+ self.world_object.material.clim = (
+ value,
+ self.world_object.material.clim[1]
+ )
- @clim.setter
- def clim(self, levels: Tuple[float, float]):
- self.world_object.material.clim = levels
+ @property
+ def vmax(self) -> float:
+ return self.world_object.material.clim[1]
+
+ @vmax.setter
+ def vmax(self, value: float):
+ self.world_object.material.clim = (
+ self.world_object.material.clim[0],
+ value
+ )
diff --git a/fastplotlib/graphics/line.py b/fastplotlib/graphics/line.py
index edf99e43c..e53eb9203 100644
--- a/fastplotlib/graphics/line.py
+++ b/fastplotlib/graphics/line.py
@@ -3,16 +3,19 @@
import pygfx
from ._base import Graphic
+from .features import PointsDataFeature, ColorFeature, CmapFeature
+from ..utils import get_colors
class LineGraphic(Graphic):
def __init__(
self,
data: Any,
- z_position: float = 0.0,
size: float = 2.0,
colors: Union[str, np.ndarray, Iterable] = "w",
+ alpha: float = 1.0,
cmap: str = None,
+ z_position: float = 0.0,
*args,
**kwargs
):
@@ -24,34 +27,46 @@ def __init__(
data: array-like
Line data to plot, 2D must be of shape [n_points, 2], 3D must be of shape [n_points, 3]
- z_position: float, optional
- z-axis position for placing the graphic
-
- size: float, optional
+ size: float, optional, default 2.0
thickness of the line
- colors: str, array, or iterable
+ colors: str, array, or iterable, default "w"
specify colors as a single human readable string, a single RGBA array,
or an iterable of strings or RGBA arrays
cmap: str, optional
- apply a colormap to the line instead of assigning colors manually
+ apply a colormap to the line instead of assigning colors manually, this
+ overrides any argument passed to "colors"
+
+ alpha: float, optional, default 1.0
+ alpha value for the colors
+
+ z_position: float, optional
+ z-axis position for placing the graphic
args
passed to Graphic
+
kwargs
passed to Graphic
+
"""
- super(LineGraphic, self).__init__(data, colors=colors, cmap=cmap, *args, **kwargs)
+ self.data = PointsDataFeature(self, data)
+
+ if cmap is not None:
+ colors = get_colors(n_colors=self.data.feature_data.shape[0], cmap=cmap, alpha=alpha)
+
+ self.colors = ColorFeature(self, colors, n_colors=self.data.feature_data.shape[0], alpha=alpha)
+ self.cmap = CmapFeature(self, self.colors.feature_data)
+
+ super(LineGraphic, self).__init__(*args, **kwargs)
if size < 1.1:
material = pygfx.LineThinMaterial
else:
material = pygfx.LineMaterial
- # self.data = np.ascontiguousarray(self.data)
-
self._world_object: pygfx.Line = pygfx.Line(
# self.data.feature_data because data is a Buffer
geometry=pygfx.Geometry(positions=self.data.feature_data, colors=self.colors.feature_data),
diff --git a/fastplotlib/graphics/linecollection.py b/fastplotlib/graphics/linecollection.py
index ec4b1e4dd..cfdf5389f 100644
--- a/fastplotlib/graphics/linecollection.py
+++ b/fastplotlib/graphics/linecollection.py
@@ -1,11 +1,12 @@
import numpy as np
import pygfx
from typing import Union
+from ._base import BaseGraphic
from .line import LineGraphic
from typing import *
-class LineCollection():
+class LineCollection(BaseGraphic):
def __init__(self, data: List[np.ndarray], z_position: Union[List[float], float] = None, size: Union[float, List[float]] = 2.0, colors: Union[List[np.ndarray], np.ndarray] = None,
cmap: Union[List[str], str] = None, *args, **kwargs):
diff --git a/fastplotlib/graphics/scatter.py b/fastplotlib/graphics/scatter.py
index 0ea5a8831..a1083d132 100644
--- a/fastplotlib/graphics/scatter.py
+++ b/fastplotlib/graphics/scatter.py
@@ -4,11 +4,61 @@
import pygfx
from ._base import Graphic
+from .features import PointsDataFeature, ColorFeature, CmapFeature
+from ..utils import get_colors
class ScatterGraphic(Graphic):
- def __init__(self, data: np.ndarray, z_position: float = 0.0, sizes: Union[int, np.ndarray, list] = 1, colors: np.ndarray = "w", cmap: str = None, *args, **kwargs):
- super(ScatterGraphic, self).__init__(data, colors=colors, cmap=cmap, *args, **kwargs)
+ def __init__(
+ self,
+ data: np.ndarray,
+ sizes: Union[int, np.ndarray, list] = 1,
+ colors: np.ndarray = "w",
+ alpha: float = 1.0,
+ cmap: str = None,
+ z_position: float = 0.0,
+ *args,
+ **kwargs
+ ):
+ """
+ Create a Scatter Graphic, 2d or 3d
+
+ Parameters
+ ----------
+ data: array-like
+ Scatter data to plot, 2D must be of shape [n_points, 2], 3D must be of shape [n_points, 3]
+
+ sizes: float or iterable of float, optional, default 1.0
+ size of the scatter points
+
+ colors: str, array, or iterable, default "w"
+ specify colors as a single human readable string, a single RGBA array,
+ or an iterable of strings or RGBA arrays
+
+ cmap: str, optional
+ apply a colormap to the scatter instead of assigning colors manually, this
+ overrides any argument passed to "colors"
+
+ alpha: float, optional, default 1.0
+ alpha value for the colors
+
+ z_position: float, optional
+ z-axis position for placing the graphic
+
+ args
+ passed to Graphic
+
+ kwargs
+ passed to Graphic
+
+ """
+ self.data = PointsDataFeature(self, data)
+
+ if cmap is not None:
+ colors = get_colors(n_colors=self.data.feature_data.shape[0], cmap=cmap, alpha=alpha)
+
+ self.colors = ColorFeature(self, colors, n_colors=self.data.feature_data.shape[0], alpha=alpha)
+ self.cmap = CmapFeature(self, self.colors.feature_data)
if isinstance(sizes, int):
sizes = np.full(self.data.feature_data.shape[0], sizes, dtype=np.float32)
@@ -20,6 +70,8 @@ def __init__(self, data: np.ndarray, z_position: float = 0.0, sizes: Union[int,
if len(sizes) != self.data.feature_data.shape[0]:
raise ValueError("list of `sizes` must have the same length as the number of datapoints")
+ super(ScatterGraphic, self).__init__(*args, **kwargs)
+
self._world_object: pygfx.Points = pygfx.Points(
pygfx.Geometry(positions=self.data.feature_data, sizes=sizes, colors=self.colors.feature_data),
material=pygfx.PointsMaterial(vertex_colors=True, vertex_sizes=True)
diff --git a/fastplotlib/graphics/text.py b/fastplotlib/graphics/text.py
index ed47bd270..0096e102c 100644
--- a/fastplotlib/graphics/text.py
+++ b/fastplotlib/graphics/text.py
@@ -2,8 +2,10 @@
import pygfx
import numpy as np
+from ._base import BaseGraphic
-class TextGraphic:
+
+class TextGraphic(BaseGraphic):
def __init__(
self,
text: str,
diff --git a/fastplotlib/layouts/_subplot.py b/fastplotlib/layouts/_subplot.py
index c8e8d2b23..7a0e923de 100644
--- a/fastplotlib/layouts/_subplot.py
+++ b/fastplotlib/layouts/_subplot.py
@@ -6,7 +6,7 @@
from warnings import warn
from pygfx import Scene, OrthographicCamera, PanZoomController, OrbitOrthoController, \
- AxesHelper, GridHelper, WgpuRenderer, Background, BackgroundMaterial
+ AxesHelper, GridHelper, WgpuRenderer
from wgpu.gui.auto import WgpuCanvas
from ._base import PlotArea
@@ -82,8 +82,8 @@ def __init__(
pfunc.__signature__ = signature(cls)
pfunc.__doc__ = cls.__init__.__doc__
- graphic_cls_name = graphic_cls_name.lower().replace("graphic", "").replace("collection", "_collection")
- setattr(self, f"add_{graphic_cls_name}", pfunc)
+ # cls.type is defined in Graphic.__init_subclass__
+ setattr(self, f"add_{cls.type}", pfunc)
self._title_graphic: TextGraphic = None
if self.name is not None:
@@ -130,13 +130,13 @@ def get_rect(self):
x_pos = ((width_canvas / self.ncols) + ((col_ix - 1) * (width_canvas / self.ncols))) + self.spacing
y_pos = ((height_canvas / self.nrows) + ((row_ix - 1) * (height_canvas / self.nrows))) + self.spacing
width_subplot = (width_canvas / self.ncols) - self.spacing
- height_suplot = (height_canvas / self.nrows) - self.spacing
+ height_subplot = (height_canvas / self.nrows) - self.spacing
rect = np.array([
x_pos,
y_pos,
width_subplot,
- height_suplot
+ height_subplot
])
for dv in self.docked_viewports.values():
@@ -221,6 +221,7 @@ def remove_animation(self, func):
self._animate_funcs_post.remove(func)
def add_graphic(self, graphic, center: bool = True):
+ graphic.world_object.position.z = len(self._graphics)
super(Subplot, self).add_graphic(graphic, center)
if isinstance(graphic, graphics.HeatmapGraphic):
diff --git a/fastplotlib/utils/functions.py b/fastplotlib/utils/functions.py
index 4df784b6e..698d20113 100644
--- a/fastplotlib/utils/functions.py
+++ b/fastplotlib/utils/functions.py
@@ -88,36 +88,3 @@ def quick_min_max(data: np.ndarray) -> Tuple[float, float]:
data = data[tuple(sl)]
return float(np.nanmin(data)), float(np.nanmax(data))
-
-
-def to_float32(array):
- if isinstance(array, np.ndarray):
- return array.astype(np.float32, copy=False)
-
- return array
-
-
-def fix_data(array, graphic_name: str) -> np.ndarray:
- """1d or 2d to 3d, cleanup data passed from user before instantiating any Graphic class"""
- if graphic_name == "ImageGraphic":
- return to_float32(array)
-
- if array.ndim == 1:
- # for scatter if we receive just 3 points in a 1d array, treat it as just a single datapoint
- # this is different from fix_data for LineGraphic since there we assume that a 1d array
- # is just y-values
- if graphic_name == "ScatterGraphic":
- array = np.array([array])
- elif graphic_name == "LineGraphic":
- array = np.dstack([np.arange(array.size), array])[0].astype(np.float32)
-
- if array.shape[1] != 3:
- if array.shape[1] != 2:
- raise ValueError(f"Must pass 1D, 2D or 3D data to {graphic_name}")
-
- # zeros for z
- zs = np.zeros(array.shape[0], dtype=np.float32)
-
- array = np.dstack([array[:, 0], array[:, 1], zs])[0]
-
- return array
diff --git a/fastplotlib/widgets/image.py b/fastplotlib/widgets/image.py
index 5fba44d56..f8dc6c73f 100644
--- a/fastplotlib/widgets/image.py
+++ b/fastplotlib/widgets/image.py
@@ -472,8 +472,8 @@ def __init__(
max=minmax[1] + data_range_30p,
step=data_range / 150,
description=f"min-max",
- readout = True,
- readout_format = '.3f',
+ readout=True,
+ readout_format='.3f',
)
minmax_slider.observe(
@@ -779,7 +779,9 @@ def _vmin_vmax_slider_changed(
data_ix: int,
change: dict
):
- self.image_graphics[data_ix].clim = change["new"]
+ vmin, vmax = change["new"]
+ self.image_graphics[data_ix].vmin = vmin
+ self.image_graphics[data_ix].vmax = vmax
def _set_slider_layout(self, *args):
w, h = self.plot.renderer.logical_size