diff --git a/examples/notebooks/gridplot_simple.ipynb b/examples/notebooks/gridplot_simple.ipynb
index f90c0b157..788689807 100644
--- a/examples/notebooks/gridplot_simple.ipynb
+++ b/examples/notebooks/gridplot_simple.ipynb
@@ -10,7 +10,7 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "5171a06e-1bdc-4908-9726-3c1fd45dbb9d",
"metadata": {},
"outputs": [],
@@ -21,52 +21,10 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "86a2488f-ae1c-4b98-a7c0-18eae8013af1",
"metadata": {},
- "outputs": [
- {
- "data": {
- "application/vnd.jupyter.widget-view+json": {
- "model_id": "5e4e0c5ca610425b8216db8e30cae997",
- "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": "1eeb8c42e1b24c4fb40e3b5daa63909a",
- "version_major": 2,
- "version_minor": 0
- },
- "text/plain": [
- "JupyterWgpuCanvas()"
- ]
- },
- "execution_count": 2,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# GridPlot of shape 2 x 3 with all controllers synced\n",
"grid_plot = GridPlot(shape=(2, 3), controllers=\"sync\")\n",
@@ -103,24 +61,10 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"id": "17c6bc4a-5340-49f1-8597-f54528cfe915",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "unnamed: Subplot @ 0x7fd4cc9bf820\n",
- " parent: None\n",
- " Graphics:\n",
- "\t'rand-img': ImageGraphic @ 0x7fd4f675a350"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# positional indexing\n",
"# row 0 and col 0\n",
@@ -137,21 +81,10 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"id": "34130f12-9ef6-43b0-b929-931de8b7da25",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "('rand-img': ImageGraphic @ 0x7fd4a03295a0,)"
- ]
- },
- "execution_count": 4,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"grid_plot[0, 1].graphics"
]
@@ -166,7 +99,7 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": null,
"id": "ef8a29a6-b19c-4e6b-a2ba-fb4823c01451",
"metadata": {},
"outputs": [],
@@ -184,7 +117,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": null,
"id": "d6c2fa4b-c634-4dcf-8b61-f1986f7c4918",
"metadata": {},
"outputs": [],
@@ -195,45 +128,20 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": null,
"id": "2f6b549c-3165-496d-98aa-45b96c3de674",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "top-right-plot: Subplot @ 0x7fd4cca0ffd0\n",
- " parent: None\n",
- " Graphics:\n",
- "\t'rand-img': ImageGraphic @ 0x7fd4a03716c0"
- ]
- },
- "execution_count": 7,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"grid_plot[\"top-right-plot\"]"
]
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": null,
"id": "be436e04-33a6-4597-8e6a-17e1e5225419",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(0, 2)"
- ]
- },
- "execution_count": 8,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# view its position\n",
"grid_plot[\"top-right-plot\"].position"
@@ -241,21 +149,10 @@
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": null,
"id": "6699cda6-af86-4258-87f5-1832f989a564",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "True"
- ]
- },
- "execution_count": 9,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# these are really the same\n",
"grid_plot[\"top-right-plot\"] is grid_plot[0, 2]"
@@ -271,7 +168,7 @@
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": null,
"id": "545b627b-d794-459a-a75a-3fde44f0ea95",
"metadata": {},
"outputs": [],
@@ -279,10 +176,61 @@
"grid_plot[\"top-right-plot\"][\"rand-img\"].vmin = 0.5"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "8cb1db84-c2bb-4eaf-be55-1f45e27b2f93",
+ "metadata": {},
+ "source": [
+ "## Syncing subplots by name"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4ba1fab3-27ca-4b2f-8b41-4dd306fe7bd9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# GridPlot of shape 2 x 3 with \n",
+ "names = [\n",
+ " [\"red\", \"ir\", \"violet\"],\n",
+ " [\"magenta\", \"cyan\", \"maroon\"]\n",
+ "]\n",
+ "\n",
+ "controllers = [\n",
+ " [\"red\", \"ir\"],\n",
+ " [\"violet\", \"magenta\"],\n",
+ " [\"cyan\", \"maroon\"]\n",
+ "]\n",
+ "\n",
+ "grid_plot = GridPlot(shape=(2, 3), controllers=controllers, names=names)\n",
+ "\n",
+ "# Make a random image graphic for each subplot\n",
+ "for subplot in grid_plot:\n",
+ " # create image data\n",
+ " data = np.random.rand(512, 512)\n",
+ " # add an image to the subplot\n",
+ " subplot.add_image(data, name=\"rand-img\")\n",
+ "\n",
+ "# Define a function to update the image graphics with new data\n",
+ "# add_animations will pass the gridplot to the animation function\n",
+ "def update_data(gp):\n",
+ " for sp in gp:\n",
+ " new_data = np.random.rand(512, 512)\n",
+ " # index the image graphic by name and set the data\n",
+ " sp[\"rand-img\"].data = new_data\n",
+ " \n",
+ "# add the animation function\n",
+ "grid_plot.add_animations(update_data)\n",
+ "\n",
+ "# show the gridplot \n",
+ "grid_plot.show()"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
- "id": "36432d5b-b76c-4a2a-a32c-097faf5ab269",
+ "id": "ee7c802e-ebaf-4fd2-97df-cd79ba048580",
"metadata": {},
"outputs": [],
"source": []
@@ -304,7 +252,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.5"
+ "version": "3.11.3"
}
},
"nbformat": 4,
diff --git a/fastplotlib/layouts/_gridplot.py b/fastplotlib/layouts/_gridplot.py
index 3e9a16aac..66e2b72e3 100644
--- a/fastplotlib/layouts/_gridplot.py
+++ b/fastplotlib/layouts/_gridplot.py
@@ -26,7 +26,7 @@ def to_array(a) -> np.ndarray:
if not isinstance(a, list):
raise TypeError("must pass list or numpy array")
- return np.array(a)
+ return np.array(a, dtype=object)
valid_cameras = ["2d", "2d-big", "3d", "3d-big"]
@@ -38,6 +38,7 @@ def __init__(
shape: Tuple[int, int],
cameras: Union[np.ndarray, str] = "2d",
controllers: Union[np.ndarray, str] = None,
+ names: Union[np.ndarray, str] = None,
canvas: Union[str, WgpuCanvas, pygfx.Texture] = None,
renderer: pygfx.WgpuRenderer = None,
size: Tuple[int, int] = (500, 300),
@@ -92,11 +93,49 @@ def __init__(
self.shape
)
- if isinstance(controllers, str):
- if controllers == "sync":
- controllers = np.zeros(
- self.shape[0] * self.shape[1], dtype=int
- ).reshape(self.shape)
+ if names is not None:
+ self.names = to_array(names)
+ # Check if shape of names kwarg is same as gridplot shape
+ if self.names.shape != self.shape:
+ raise ValueError(f"subplot names: {self.names} must be in gridplot shape: {self.shape}")
+ # Check if all elements in names array are strings
+ if not all(isinstance(self.names[i], str) for i in product(range(self.shape[0]), range(self.shape[1]))):
+ raise ValueError(f"subplot names: {self.names} must all be strings")
+ else:
+ self.names = None
+
+ if controllers is not None:
+ # If value in controllers is 'sync', set all subplots to same controller
+ if isinstance(controllers, str):
+ if controllers == "sync":
+ controllers = np.zeros(
+ self.shape[0] * self.shape[1], dtype=int
+ ).reshape(self.shape)
+ else:
+ c = to_array(controllers)
+ # Confirm number of elements in controller array is same as number in gridplot shape
+ if (c.shape[0]*c.shape[1]) != (self.shape[0]*self.shape[1]):
+ raise ValueError(f"number of controllers: {len(controllers)} must be the same as number of elements"
+ f" in gridplot shape {self.shape}")
+ # Confirm controllers array doesn't have multiple dtypes
+ if any(isinstance(c[i], str) for i in product(range(c.shape[0]), range(c.shape[1]))) and \
+ any(not isinstance(c[i], str) for i in product(range(c.shape[0]), range(c.shape[1]))):
+ raise ValueError(f"controllers: {controllers} must all be the same type")
+ # Check if names kwarg is given, and if all elements in controller array are strings
+ if self.names is not None:
+ if all(isinstance(c[i], str) for i in product(range(c.shape[0]), range(c.shape[1]))):
+ controllers = np.zeros(
+ self.shape[0] * self.shape[1], dtype=int
+ ).reshape(self.shape)
+ # For each value in names array, find corresponding index in controller array
+ for positions in product(range(self.shape[0]), range(self.shape[1])):
+ controller_idx = np.argwhere(c == self.names[positions])
+ # If name exists in controller array, set controller for item to the row number
+ if len(controller_idx) != 0:
+ controllers[positions] = controller_idx[0][0]
+ else:
+ raise ValueError(f"string names in controllers: {c} must be the same as "
+ f"string names in names: {self.names}")
if controllers is None:
controllers = np.arange(self.shape[0] * self.shape[1]).reshape(self.shape)
@@ -144,13 +183,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