diff --git a/docs/source/api/layouts/figure.rst b/docs/source/api/layouts/figure.rst index 3d6c745e9..b5cbbd2bb 100644 --- a/docs/source/api/layouts/figure.rst +++ b/docs/source/api/layouts/figure.rst @@ -23,11 +23,10 @@ Properties Figure.cameras Figure.canvas Figure.controllers - Figure.mode + Figure.layout Figure.names Figure.renderer Figure.shape - Figure.spacing Methods ~~~~~~~ @@ -35,6 +34,7 @@ Methods :toctree: Figure_api Figure.add_animations + Figure.add_subplot Figure.clear Figure.close Figure.export @@ -42,5 +42,6 @@ Methods Figure.get_pygfx_render_area Figure.open_popup Figure.remove_animation + Figure.remove_subplot Figure.show diff --git a/docs/source/api/layouts/imgui_figure.rst b/docs/source/api/layouts/imgui_figure.rst index 6d6bb2dd4..a338afe96 100644 --- a/docs/source/api/layouts/imgui_figure.rst +++ b/docs/source/api/layouts/imgui_figure.rst @@ -25,11 +25,10 @@ Properties ImguiFigure.controllers ImguiFigure.guis ImguiFigure.imgui_renderer - ImguiFigure.mode + ImguiFigure.layout ImguiFigure.names ImguiFigure.renderer ImguiFigure.shape - ImguiFigure.spacing Methods ~~~~~~~ @@ -38,6 +37,7 @@ Methods ImguiFigure.add_animations ImguiFigure.add_gui + ImguiFigure.add_subplot ImguiFigure.clear ImguiFigure.close ImguiFigure.export @@ -46,5 +46,6 @@ Methods ImguiFigure.open_popup ImguiFigure.register_popup ImguiFigure.remove_animation + ImguiFigure.remove_subplot ImguiFigure.show diff --git a/docs/source/api/layouts/subplot.rst b/docs/source/api/layouts/subplot.rst index 1cf9be31c..e1c55514d 100644 --- a/docs/source/api/layouts/subplot.rst +++ b/docs/source/api/layouts/subplot.rst @@ -26,6 +26,7 @@ Properties Subplot.canvas Subplot.controller Subplot.docks + Subplot.frame Subplot.graphics Subplot.legends Subplot.name @@ -34,6 +35,7 @@ Properties Subplot.renderer Subplot.scene Subplot.selectors + Subplot.title Subplot.toolbar Subplot.viewport @@ -53,7 +55,6 @@ Methods Subplot.auto_scale Subplot.center_graphic Subplot.center_scene - Subplot.center_title Subplot.clear Subplot.delete_graphic Subplot.get_figure @@ -61,5 +62,4 @@ Methods Subplot.map_screen_to_world Subplot.remove_animation Subplot.remove_graphic - Subplot.set_title diff --git a/docs/source/conf.py b/docs/source/conf.py index 76298d4ff..865c462a6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -59,6 +59,7 @@ "../../examples/heatmap", "../../examples/image_widget", "../../examples/gridplot", + "../../examples/window_layouts", "../../examples/line", "../../examples/line_collection", "../../examples/scatter", diff --git a/examples/image_widget/image_widget_videos.py b/examples/image_widget/image_widget_videos.py index 1e367f0ad..7de4a9c04 100644 --- a/examples/image_widget/image_widget_videos.py +++ b/examples/image_widget/image_widget_videos.py @@ -29,7 +29,7 @@ [random_data, cockatoo_sub], rgb=[False, True], figure_shape=(2, 1), # 2 rows, 1 column - figure_kwargs={"size": (700, 560)} + figure_kwargs={"size": (700, 940)} ) iw.show() diff --git a/examples/machine_learning/kmeans.py b/examples/machine_learning/kmeans.py index 620fa15fb..0aae8fdae 100644 --- a/examples/machine_learning/kmeans.py +++ b/examples/machine_learning/kmeans.py @@ -3,6 +3,9 @@ =================================== Example showing how you can perform K-Means clustering on the MNIST dataset. + +Use WASD keys on your keyboard to fly through the data in PCA space. +Use the mouse pointer to select points. """ # test_example = false @@ -29,17 +32,17 @@ # iterate through each subplot for i, subplot in enumerate(fig_data): # reshape each image to (8, 8) - subplot.add_image(data[i].reshape(8,8), cmap="gray", interpolation="linear") + subplot.add_image(data[i].reshape(8, 8), cmap="gray", interpolation="linear") # add the label as a title - subplot.set_title(f"Label: {labels[i]}") + subplot.title = f"Label: {labels[i]}" # turn off the axes and toolbar subplot.axes.visible = False - subplot.toolbar = False + subplot.toolbar = False fig_data.show() # project the data from 64 dimensions down to the number of unique digits -n_digits = len(np.unique(labels)) # 10 +n_digits = len(np.unique(labels)) # 10 reduced_data = PCA(n_components=n_digits).fit_transform(data) # (1797, 10) @@ -53,17 +56,17 @@ # plot the kmeans result and corresponding original image figure = fpl.Figure( - shape=(1,2), - size=(700, 400), + shape=(1, 2), + size=(700, 560), cameras=["3d", "2d"], - controller_types=[["fly", "panzoom"]] + controller_types=["fly", "panzoom"] ) -# set the axes to False -figure[0, 0].axes.visible = False +# set the axes to False in the image subplot figure[0, 1].axes.visible = False -figure[0, 0].set_title(f"K-means clustering of PCA-reduced data") +figure[0, 0].title = "k-means clustering of PCA-reduced data" +figure[0, 1].title = "handwritten digit" # plot the centroids figure[0, 0].add_scatter( @@ -94,6 +97,7 @@ digit_scatter.colors[ix] = "magenta" digit_scatter.sizes[ix] = 10 + # define event handler to update the selected data point @digit_scatter.add_event_handler("pointer_enter") def update(ev): @@ -110,8 +114,10 @@ def update(ev): # update digit fig figure[0, 1]["digit"].data = data[ix].reshape(8, 8) + figure.show() + # NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively # please see our docs for using fastplotlib interactively in ipython and jupyter if __name__ == "__main__": diff --git a/examples/notebooks/nb_test_utils.py b/examples/notebooks/nb_test_utils.py index f1505f98a..9d99e3be3 100644 --- a/examples/notebooks/nb_test_utils.py +++ b/examples/notebooks/nb_test_utils.py @@ -102,7 +102,7 @@ def plot_test(name, fig: fpl.Figure): # hacky but it works for now fig.imgui_renderer.render() - fig._set_viewport_rects() + fig._fpl_reset_layout() # render each subplot for subplot in fig: subplot.viewport.render(subplot.scene, subplot.camera) diff --git a/examples/notebooks/screenshots/nb-astronaut.png b/examples/notebooks/screenshots/nb-astronaut.png index 32b09caf9..2370c5988 100644 --- a/examples/notebooks/screenshots/nb-astronaut.png +++ b/examples/notebooks/screenshots/nb-astronaut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d9e2b0479d3de1c12764b984679dba83a1876ea6a88c072789a0e06f957ca2a -size 70655 +oid sha256:0a6e8bb3c72f1be6915e8e78c9a4f269419cfb4faded16e39b5cb11d70bec247 +size 64185 diff --git a/examples/notebooks/screenshots/nb-astronaut_RGB.png b/examples/notebooks/screenshots/nb-astronaut_RGB.png index be498bb6d..2a7eac585 100644 --- a/examples/notebooks/screenshots/nb-astronaut_RGB.png +++ b/examples/notebooks/screenshots/nb-astronaut_RGB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2d02877510191e951d38d03a6fe9d31f5c0c335913876c65b175c4bb1a9c0e1 -size 69942 +oid sha256:9f9f32e86018f87057435f7121b02bbe98823444babb330645bab618e1d586b7 +size 63838 diff --git a/examples/notebooks/screenshots/nb-camera.png b/examples/notebooks/screenshots/nb-camera.png index 3e9a518f9..bfe226ca4 100644 --- a/examples/notebooks/screenshots/nb-camera.png +++ b/examples/notebooks/screenshots/nb-camera.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5271c2204a928185b287c73c852ffa06b708d8d6a33de09acda8d2ea734e78c5 -size 51445 +oid sha256:2964d0150b38f990a7b804e9057f99505e8c99bb04538a13137989d540704593 +size 47456 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 8c353442a..2578ad028 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:4d8563587c4f642d5e4edb34f41b569673d7ea71bcbafdb734369272776baeef -size 62316 +oid sha256:78e7e99fafc15cc6edf53cfb2e5b679623ad14e0d594e0ad615088e623be22e1 +size 60988 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png index 22c7ad73a..bb2e1ee37 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-0-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b122f0ba9bfff0b0868778f09744870238bf7b4945e57410b6aa36341eaaf4a -size 116781 +oid sha256:6c9b898259fc965452ef0b6ff53ac7fa41196826c6e27b6b5d417d33fb352051 +size 112399 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png index 22c7ad73a..bb2e1ee37 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b122f0ba9bfff0b0868778f09744870238bf7b4945e57410b6aa36341eaaf4a -size 116781 +oid sha256:6c9b898259fc965452ef0b6ff53ac7fa41196826c6e27b6b5d417d33fb352051 +size 112399 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png index 84e2514d0..1841cd237 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-279.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fcc5092f35c881da4a9b9f3c216fb608b8dfc27a791b83e0d5184ef3973746cf -size 139375 +oid sha256:b9cbc2a6916c7518d40812a13276270eb1acfc596f3e6e02e98a6a5185da03a4 +size 132971 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png index 075116ff4..6cc1821fa 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-max-33.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3fabd9d52ae2815ae883a4c8c8a8b1385c0824e0212347896a09eb3600c29430 -size 124238 +oid sha256:070748e90bd230a01d3ae7c6d6487815926b0158888a52db272356dc8b0a89d7 +size 119453 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png index 216ae2b9e..3865aef93 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86ad31cab3aefa24a1c4c0adc2033cbc9fa594e9cf8ab8e4a6ff0a3630bb7896 -size 109041 +oid sha256:b24450ccf1f8cf902b8e37e73907186f37a6495f227dcbd5ec53f75c52125f56 +size 105213 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png index 99302d4e6..025086930 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-mean-33.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ebf4e875199c7e682dc15aa03a36ea9f111618853a94076064b055bf6ce788e -size 101209 +oid sha256:3dfc8e978eddf08d1ed32e16fbf93c037ccdf5f7349180dcda54578a8c9e1a18 +size 97359 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png index 3bb5081f0..5ff5052b0 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50-window-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8dbf6b76818315e40d9d4cc97807c4276c27e7a9a09d2643f74adf701ef1cdc -size 123136 +oid sha256:00130242d3f199926604df16dda70a062071f002566a8056e4794805f29adfde +size 118044 diff --git a/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png b/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png index 3bb5081f0..5ff5052b0 100644 --- a/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-movie-single-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8dbf6b76818315e40d9d4cc97807c4276c27e7a9a09d2643f74adf701ef1cdc -size 123136 +oid sha256:00130242d3f199926604df16dda70a062071f002566a8056e4794805f29adfde +size 118044 diff --git a/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png b/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png index 48ab5d6fe..e8c02adfe 100644 --- a/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png +++ b/examples/notebooks/screenshots/nb-image-widget-single-gnuplot2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c65e2dc4276393278ab769706f22172fd71e38eeb3c9f4d70fa51de31820f1d1 -size 234012 +oid sha256:8c8562f8e1178cf21af98af635006c64010f3c5fc615533d1df8c49479232843 +size 217693 diff --git a/examples/notebooks/screenshots/nb-image-widget-single.png b/examples/notebooks/screenshots/nb-image-widget-single.png index 5e1cb8cc1..8de4099fb 100644 --- a/examples/notebooks/screenshots/nb-image-widget-single.png +++ b/examples/notebooks/screenshots/nb-image-widget-single.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d4e4edf1429a135bafb7c1c927ea87f78a93fb5f3e0cbee2fb5c156af88d5a0 -size 220490 +oid sha256:5c9bae3c9c5521a4054288be7ae548204fc7b0eafbc3e99cb6b649e0be797169 +size 207176 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png index ec2911374..13297e09f 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-gaussian.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39adce1898e5b00ccf9d8792bd4e76f2da2591a8c3f6e201a5c2af1f814d37cb -size 58692 +oid sha256:70c7738ed303f5a3e19271e8dfc12ab857a6f3aff767bdbecb485b763a09913e +size 55584 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png index ae72c8175..b8307bc44 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-frame-apply-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d50b960c66acc6672aaeb6a97f6a69aad14f9e54060c3702679d6a5bf2b70e78 -size 70582 +oid sha256:66a435e45dc4643135633115af2eeaf70761e408a94d70d94d80c14141574528 +size 69343 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png index 66f9136dc..d6237dc9f 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-max-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d244a8a91d04380f2ebe255b2b56b3be5249c0a382821877710cae6bdaa2d414 -size 128643 +oid sha256:731f225fa2de3457956b2095d1cc539734983d041b13d6ad1a1f9d8e7ebfa4bc +size 115239 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png index 230e71c0f..ecf63a369 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24c991457b8b081ee271cbdb03821ea1024c8340db92340e0e445bf3c70aba40 -size 97903 +oid sha256:7e2d70159ac47c004acb022b3a669e7bd307299ddd590b83c08854b0dba27b70 +size 93885 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png index a355670a0..e7106fae9 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bdd62a9bd1ca4f1ff110a30fb4064d778f02120295a3e3d30552e06892146e40 -size 93658 +oid sha256:1756783ab90435b46ded650033cf29ac36d2b4380744bf312caa2813267f7f38 +size 89813 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png index c47545ccb..ddd4f85ca 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db7e2cf15ff3ce61b169b114c630e2339c1c6b5c687c580e1ee6964785df6790 -size 74844 +oid sha256:a35e2e4b892b55f5d2500f895951f6a0289a2df3b69cf12f59409bbc091d1caf +size 72810 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png index 69ef49149..d9971c3fd 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-frame-99.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64d2d3fd91ac8e10736add5a82a312927ae6f976119cfa2aaa1fc1e008bcf6f1 -size 66038 +oid sha256:3bdb0ed864c8a6f2118cfe0d29476f61c54576f7b8e041f3c3a895ba0a440c05 +size 65039 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png index bb04d1800..6736e108c 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-gaussian.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d2a805c85e1cdf5bd2d995600b033019680ac645d7634efeaf1db7d0d00d4aa -size 79403 +oid sha256:7ae7c86bee3a30bde6cfa44e1e583e6dfd8de6bb29e7c86cea9141ae30637b4a +size 80627 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png index 5b1a4a8da..dce99223b 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-frame-apply-reset.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:440623bb4588994c4f52f17e027af29d1f2d5d330c5691630fd2acb9e38f8a25 -size 99033 +oid sha256:b51a5d26f2408748e59e3ee481735694f8f376539b50deb2b5c5a864b7de1079 +size 105581 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png index bd72160dd..cdea3673d 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-max-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ee56adf8f2a516ef74a799e9e503f980c36c4dfb41f9ff6d8168cfcf65ad092 -size 132745 +oid sha256:e854f7f2fdaeeac6c8358f94a33698b5794c0f6c55b240d384e8c6d51fbfb0ff +size 143301 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png index 438d1e2d4..25a2fa53e 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de4733b82c2e77baa659582401eff0c70ec583c50462b33bcbfd2bb00ceaa517 -size 102959 +oid sha256:c8c8d3c59c145a4096deceabc71775a03e5e121e82509590787c768944d155bd +size 110744 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png index ee081c6df..00a4a1fd2 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6107f108b5a86ba376c53f5e207841c01a85b686100efb46e5df3565127201d2 -size 106765 +oid sha256:c4b4af7b99cad95ea3f688af8633de24b6602bd700cb244f93c28718af2e1e85 +size 114982 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png index c2071c850..3b5594c64 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:caa15f6bc21a3be0f480b442414ec4b96b21cc1de2cdcb891b366692962d4ef8 -size 100753 +oid sha256:6d28a4be4c76d5c0da5f5767b169acf7048a268b010f33f96829a5de7f06fd7d +size 107477 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png index 3d90fd77a..239237b45 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-frame-99.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e23288d695a5a91188b285f6a0a2c9f0643dd19f3d6dedb56f4389f44ed1f44 -size 98621 +oid sha256:30dba982c9a605a7a3c0f2fa6d8cdf0df4160b2913a95b26ffdb6b04ead12add +size 104603 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png index 3fd5688d9..0745a4d4a 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-init-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b4e1bb60466d7553b4d1afc14015b7c4edc6e79c724c0afb5acd123a10093d0 -size 105541 +oid sha256:e431229806ee32a78fb9313a09af20829c27799798232193feab1723b66b1bca +size 112646 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png index 048078520..498b19cb7 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-false.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33ce1260b4715b3d28ba28d0ad4c5eb94c9997bdc1676ff6208121e789e168a5 -size 99287 +oid sha256:a8e899b48881e3eb9200cc4e776db1f865b0911c340c06d4009b3ae12aa1fc85 +size 105421 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png index ade8fb483..369168141 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-grid-set_data-reset-indices-true.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e08f4e4cb3330fbbbf827af56c02039af3b293036c7676f2a87c309ad07f2f6 -size 99759 +oid sha256:93933e7ba5f791072df2934c94a782e39ed97f7db5b55c5d71c8c5bbfc69d800 +size 106360 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png b/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png index 14d9e8448..b62721be2 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-init-mean-window-5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3aad82db14f8100f669d2ad36b5bc3973b7c12457adfdd73adbc81c759338f7b -size 80964 +oid sha256:bf38b2af1ceb372cd0949d42c027acb5fcc4c6b9a8f38c5aacdce1cd14e290fe +size 78533 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png index af04a6f73..76ed01a7c 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-frame-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e40559eea03790315718c55b4ec4976aacb97a2f07bcdc49d917c044745687c2 -size 117144 +oid sha256:ff462d24820f0bdd509e58267071fa956b5c863b8b8d66fea061c5253b7557cf +size 113926 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png index 7f530e554..d9a593ee7 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-set-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:414ebe9a0b2bc4eb1caa4b4aeef070955c662bb691899c4b2046be3e2ca821e3 -size 113649 +oid sha256:2b8fd14f8e8a90c3cd3fbb84a00d50b1b826b596d64dfae4a5ea1bab0687d906 +size 110829 diff --git a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png index e2f6b8318..cf10c6d42 100644 --- a/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png +++ b/examples/notebooks/screenshots/nb-image-widget-zfish-mixed-rgb-cockatoo-windowrgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea6d0c4756db434af6e257b7cd809f1d49089eca6b9eae9e347801e20b175686 -size 113631 +oid sha256:d88c64b716d19a3978bd60f8d75ffe09e022183381898fa1c48b77598be8fb7c +size 111193 diff --git a/examples/notebooks/screenshots/nb-lines-3d.png b/examples/notebooks/screenshots/nb-lines-3d.png index 2e26a8cd7..fb84ef21a 100644 --- a/examples/notebooks/screenshots/nb-lines-3d.png +++ b/examples/notebooks/screenshots/nb-lines-3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:857eb528b02fd7dd4b9f46ce1e65942066736f1bdf5271db141d73a0abab82b0 -size 19457 +oid sha256:c70c01b3ade199864df227a44fb28a53626df3beecee722a7b782c9a9f4658d8 +size 19907 diff --git a/examples/notebooks/screenshots/nb-lines-colors.png b/examples/notebooks/screenshots/nb-lines-colors.png index 1e13983f3..ab221d83f 100644 --- a/examples/notebooks/screenshots/nb-lines-colors.png +++ b/examples/notebooks/screenshots/nb-lines-colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6681a1e5658c1f2214217dcb7321cad89c7a0a3fd7919296a1069f27f1a7ee92 -size 35381 +oid sha256:3b238b085eddb664ff56bd265423d85b35fc70769ebec050b27fefa8fe6380de +size 35055 diff --git a/examples/notebooks/screenshots/nb-lines-data.png b/examples/notebooks/screenshots/nb-lines-data.png index a7e8287ef..44b142f55 100644 --- a/examples/notebooks/screenshots/nb-lines-data.png +++ b/examples/notebooks/screenshots/nb-lines-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:043d8d9cd6dfc7627a6ccdb5810efd4b1a15e8880a4e30c0f558ae4d67c2f470 -size 42410 +oid sha256:4df736ec3ea90478930a77437949977f8e30f7d9272f65ef9f4908f2103dd11e +size 40679 diff --git a/examples/notebooks/screenshots/nb-lines-underlay.png b/examples/notebooks/screenshots/nb-lines-underlay.png index c2908d479..f4a5b4e76 100644 --- a/examples/notebooks/screenshots/nb-lines-underlay.png +++ b/examples/notebooks/screenshots/nb-lines-underlay.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c52ac60ffc08005d1f1fcad1b29339a89a0f31b58c9ca692f9d93400e7c8ac9e -size 48540 +oid sha256:3a8b59386015b4c1eaa85c33c7b041d566ac1ac76fbba829075e9a3af021bedf +size 46228 diff --git a/examples/notebooks/screenshots/nb-lines.png b/examples/notebooks/screenshots/nb-lines.png index f4a4d58b1..8c86b48d0 100644 --- a/examples/notebooks/screenshots/nb-lines.png +++ b/examples/notebooks/screenshots/nb-lines.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2cef0e2fb84e985f8d9c18f77817fb3eba31bd30b8fa4c54bb71432587909458 -size 30075 +oid sha256:823558e877830b816cc87df0776a92d5316d98a4f40e475cbf997b597c5eb8de +size 30338 diff --git a/examples/notebooks/screenshots/no-imgui-nb-astronaut.png b/examples/notebooks/screenshots/no-imgui-nb-astronaut.png index a1e524e2a..9f9e2013a 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-astronaut.png +++ b/examples/notebooks/screenshots/no-imgui-nb-astronaut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:915f6c4695c932dc2aa467be750e58a0435fe86fe0e0fa5a52c6065e05ec3193 -size 85456 +oid sha256:4758a94e6c066d95569515c0bff8e4c9ec383c65c5928a827550c142214df085 +size 72372 diff --git a/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png b/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png index ec3208e01..23d1bd906 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png +++ b/examples/notebooks/screenshots/no-imgui-nb-astronaut_RGB.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:31cfa60229a4e297be507a8888e08d6950c2a7d4b323d34774c9462419272ada -size 84284 +oid sha256:fb3c72edc6f41d6f77e44bc68e7f5277525d2548d369925827c14d855dc33bbd +size 71588 diff --git a/examples/notebooks/screenshots/no-imgui-nb-camera.png b/examples/notebooks/screenshots/no-imgui-nb-camera.png index 31b60d9c0..22c70a760 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-camera.png +++ b/examples/notebooks/screenshots/no-imgui-nb-camera.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:800845fae18093945ed921237c8756b1afa31ee391fe679b03c57a67929e4ba9 -size 60087 +oid sha256:6de3880cc22a8f6cdb77305e4d5be520fe92fd54a9a107bdbddf1e6f72c19262 +size 52157 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png b/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png index 35c777e6a..1a5a7b548 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4253362c0908e0d983542be3691a3d94f27a0319fb9e7183315c77891dac140 -size 23232 +oid sha256:f0e63c918aac713af2015cb85289c9451be181400834b0f60bcbb50564551f08 +size 20546 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png b/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png index b8e34aab3..cdce4bf46 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc95d6291d06ab64d142ba0048318caefa28b404bb4b31635df075dc651eaa08 -size 37276 +oid sha256:2bd481f558907ac1af97bd7ee08d58951bada758cc32467c73483fa66e4602f8 +size 36206 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-data.png b/examples/notebooks/screenshots/no-imgui-nb-lines-data.png index 8f58dbc6d..8923be766 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-data.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-data.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8aa0b8303f0a69609198ea312800fc0eb98007c18d0ebc37672a9cf4f1cbaff -size 46780 +oid sha256:ea39e2651408431ad5e49af378828a41b7b377f7f0098adc8ce2c7b5e10d0234 +size 43681 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png b/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png index b33cde5a6..b6b4cf340 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines-underlay.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:822410f43d48d12e70930b5b581bafe624ea72475d53ca0d98cdaa5649338c63 -size 51849 +oid sha256:6a8d4aba2411598ecae1b7f202fbb1a1fa7416a814b7b4c5fdd1e0e584cdb06a +size 49343 diff --git a/examples/notebooks/screenshots/no-imgui-nb-lines.png b/examples/notebooks/screenshots/no-imgui-nb-lines.png index 5d7e704ca..5d03421a4 100644 --- a/examples/notebooks/screenshots/no-imgui-nb-lines.png +++ b/examples/notebooks/screenshots/no-imgui-nb-lines.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e3ba744fcfa43df839fddce88f79fb8d7c5eafdd22f271e6b885e09b8891072 -size 31222 +oid sha256:b2fdaf79703c475521184ab9dc948d3e817160b0162e9d88fcb20207225d0233 +size 31153 diff --git a/examples/scatter/spinning_spiral.py b/examples/scatter/spinning_spiral.py new file mode 100644 index 000000000..c032fc1c8 --- /dev/null +++ b/examples/scatter/spinning_spiral.py @@ -0,0 +1,62 @@ +""" +Spinning spiral scatter +======================= + +Example of a spinning spiral scatter +""" + +# test_example = false +# sphinx_gallery_pygfx_docs = 'animate 10s' + +import numpy as np +import fastplotlib as fpl + +# number of points +n = 100_000 + +# create data in the shape of a spiral +phi = np.linspace(0, 30, n) + +xs = phi * np.cos(phi) + np.random.normal(scale=1.5, size=n) +ys = np.random.normal(scale=1, size=n) +zs = phi * np.sin(phi) + np.random.normal(scale=1.5, size=n) + +data = np.column_stack([xs, ys, zs]) + +figure = fpl.Figure(cameras="3d", size=(700, 560)) + +spiral = figure[0, 0].add_scatter(data, cmap="viridis_r", alpha=0.8) + + +def update(): + # rotate around y axis + spiral.rotate(0.005, axis="y") + # add small jitter + spiral.data[:] += np.random.normal(scale=0.01, size=n * 3).reshape((n, 3)) + + +figure.add_animations(update) +figure.show() + +# pre-saved camera state +camera_state = { + 'position': np.array([-0.13046005, 20.09142224, 29.03347696]), + 'rotation': np.array([-0.44485092, 0.05335406, 0.11586037, 0.88647469]), + 'scale': np.array([1., 1., 1.]), + 'reference_up': np.array([0., 1., 0.]), + 'fov': 50.0, + 'width': 62.725074768066406, + 'height': 8.856056690216064, + 'zoom': 0.75, + 'maintain_aspect': True, + 'depth_range': None +} +figure[0, 0].camera.set_state(camera_state) +figure[0, 0].axes.visible = False + + +# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively +# please see our docs for using fastplotlib interactively in ipython and jupyter +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/screenshots/extent_frac_layout.png b/examples/screenshots/extent_frac_layout.png new file mode 100644 index 000000000..7fe6d3d37 --- /dev/null +++ b/examples/screenshots/extent_frac_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5991b755432318310cfc2b4826bd9639cc234883aa06f1895817f710714cb58f +size 156297 diff --git a/examples/screenshots/extent_layout.png b/examples/screenshots/extent_layout.png new file mode 100644 index 000000000..dec391ac2 --- /dev/null +++ b/examples/screenshots/extent_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0cf23f845932023789e0823a105910e9f701d0f03c04e3c18488f0da62420921 +size 123409 diff --git a/examples/screenshots/gridplot.png b/examples/screenshots/gridplot.png index 1a222affd..08e6d6b78 100644 --- a/examples/screenshots/gridplot.png +++ b/examples/screenshots/gridplot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8de769538bb435b71b33e038998b2bafa340c635211c0dfc388c7a5bf55fd36d -size 286794 +oid sha256:6f424ec68dbc0761566cd147f3bf5b8f15e4126c3b30b2ff47b6fb48f04d512a +size 252269 diff --git a/examples/screenshots/gridplot_non_square.png b/examples/screenshots/gridplot_non_square.png index 45d71abb2..781de8749 100644 --- a/examples/screenshots/gridplot_non_square.png +++ b/examples/screenshots/gridplot_non_square.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92f55da7e2912a68e69e212b31df760f27e72253ec234fe1dd5b5463b60061b3 -size 212647 +oid sha256:9ac9ee6fd1118a06a1f0de4eee73e7b6bee188c533da872c5cbaf7119114414f +size 194385 diff --git a/examples/screenshots/gridplot_viewports_check.png b/examples/screenshots/gridplot_viewports_check.png index 050067e22..b1faf9b69 100644 --- a/examples/screenshots/gridplot_viewports_check.png +++ b/examples/screenshots/gridplot_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:250959179b0f998e1c586951864e9cbce3ac63bf6d2e12a680a47b9b1be061a1 -size 46456 +oid sha256:67dd50d61a0caaf563d95110f99fa24c567ddd778a697715247d697a1b5bb1ac +size 46667 diff --git a/examples/screenshots/heatmap.png b/examples/screenshots/heatmap.png index a63eb5ec8..defcca301 100644 --- a/examples/screenshots/heatmap.png +++ b/examples/screenshots/heatmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f2f0699e01eb12c44a2dbefd1d8371b86b3b3456b28cb5f1850aed44c13f412 -size 94505 +oid sha256:0789d249cb4cfad21c9f1629721ade26ed734e05b1b13c3a5871793f6271362b +size 91831 diff --git a/examples/screenshots/image_cmap.png b/examples/screenshots/image_cmap.png index 6f7081b03..0301d2ed4 100644 --- a/examples/screenshots/image_cmap.png +++ b/examples/screenshots/image_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1482ce72511bc4f815825c29fabac5dd0f2586ac4c827a220a5cecb1162be4d -size 210019 +oid sha256:d2bbb79716fecce08479fbe7977565daccadf4688c8a99e155db297ecce4c484 +size 199979 diff --git a/examples/screenshots/image_rgb.png b/examples/screenshots/image_rgb.png index 88beb7df3..11129ceaa 100644 --- a/examples/screenshots/image_rgb.png +++ b/examples/screenshots/image_rgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8210ad8d1755f7819814bdaaf236738cdf1e9a0c4f77120aca4968fcd8aa8a7a -size 239431 +oid sha256:23024936931651cdf4761f2cafcd8002bb12ab86e9efb13ddc99a9bf659c3935 +size 226879 diff --git a/examples/screenshots/image_rgbvminvmax.png b/examples/screenshots/image_rgbvminvmax.png index f3ef59d84..afe4de6f7 100644 --- a/examples/screenshots/image_rgbvminvmax.png +++ b/examples/screenshots/image_rgbvminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ebbcc4a2e83e9733eb438fe2341f77c86579421f3fa96b6a49e94073c0ffd32 -size 48270 +oid sha256:2fb9cd6d32813df6a9e3bf183f73cb69fdb61d290d7f2a4cc223ab34301351a1 +size 50231 diff --git a/examples/screenshots/image_simple.png b/examples/screenshots/image_simple.png index 0c7e011f4..702a1ac5c 100644 --- a/examples/screenshots/image_simple.png +++ b/examples/screenshots/image_simple.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44bc2d1fd97921fef0be45424f21513d5d978b807db8cf148dfc59c07f6e292f -size 211333 +oid sha256:b3eb6f03364226e9f1aae72f6414ad05b0239a15c2a0fbcd71d3718fee477e2c +size 199468 diff --git a/examples/screenshots/image_small.png b/examples/screenshots/image_small.png index 41a4a240e..d17cb7ab2 100644 --- a/examples/screenshots/image_small.png +++ b/examples/screenshots/image_small.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:079ee6254dc995cc5fc8c20ff1c00cb0899f21ba2d5d1a4dc0d020c3a71902c4 -size 13022 +oid sha256:2dcfc7b8a964db9a950bf4d3217fb171d081251b107977f9acd612fcd5fb0be1 +size 14453 diff --git a/examples/screenshots/image_vminvmax.png b/examples/screenshots/image_vminvmax.png index f3ef59d84..afe4de6f7 100644 --- a/examples/screenshots/image_vminvmax.png +++ b/examples/screenshots/image_vminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ebbcc4a2e83e9733eb438fe2341f77c86579421f3fa96b6a49e94073c0ffd32 -size 48270 +oid sha256:2fb9cd6d32813df6a9e3bf183f73cb69fdb61d290d7f2a4cc223ab34301351a1 +size 50231 diff --git a/examples/screenshots/image_widget.png b/examples/screenshots/image_widget.png index af248dd3e..23d34ae50 100644 --- a/examples/screenshots/image_widget.png +++ b/examples/screenshots/image_widget.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2ae1938c5e7b742fb2dac0336877028f6ece26cd80e84f309195a55601025cb -size 197495 +oid sha256:220ebb5286b48426f9457b62d6e7f9fe61b5a62b8874c7e010e07e146ae205a5 +size 184633 diff --git a/examples/screenshots/image_widget_grid.png b/examples/screenshots/image_widget_grid.png index e0f0ff5c8..45bc70726 100644 --- a/examples/screenshots/image_widget_grid.png +++ b/examples/screenshots/image_widget_grid.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eeb5b86e7c15dfe2e71267453426930200223026f72156f34ff1ccc2f9389b6e -size 253769 +oid sha256:306977f7eebdb652828ba425d73b6018e97c100f3cf8f3cbaa0244ffb6c040a3 +size 249103 diff --git a/examples/screenshots/image_widget_imgui.png b/examples/screenshots/image_widget_imgui.png index 135a0d4c4..cb165cc86 100644 --- a/examples/screenshots/image_widget_imgui.png +++ b/examples/screenshots/image_widget_imgui.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e2cd0e3892377e6e2d552199391fc64aac6a02413168a5b4c5c4848f3390dec -size 166265 +oid sha256:7522a35768d013a257e3cf3b00cce626b023b169484e035f46c635efc553b0bf +size 165747 diff --git a/examples/screenshots/image_widget_single_video.png b/examples/screenshots/image_widget_single_video.png index 5d10d91a6..aa757a950 100644 --- a/examples/screenshots/image_widget_single_video.png +++ b/examples/screenshots/image_widget_single_video.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de1750c9c1c3cd28c356fb51687f4a8f00afb3cc7e365502342168fce8459d3a -size 90307 +oid sha256:5f0843f4693460ae985c1f33d84936fbcc943d0405e0893186cbee7a5765dbc0 +size 90283 diff --git a/examples/screenshots/image_widget_videos.png b/examples/screenshots/image_widget_videos.png index f0e262e24..2e289ae3c 100644 --- a/examples/screenshots/image_widget_videos.png +++ b/examples/screenshots/image_widget_videos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23d993e0b5b6bcfe67da7aa4ceab3f06e99358b00f287b9703c4c3bff19648ba -size 169541 +oid sha256:eec22392f85db1fd375d7ffa995a2719cf86821fe3fe85913f4ab66084eccbf9 +size 290587 diff --git a/examples/screenshots/image_widget_viewports_check.png b/examples/screenshots/image_widget_viewports_check.png index 6bfbc0153..662432e59 100644 --- a/examples/screenshots/image_widget_viewports_check.png +++ b/examples/screenshots/image_widget_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27e8aaab0085d15965649f0a4b367e313bab382c13b39de0354d321398565a46 -size 99567 +oid sha256:1c4449f7e97375aa9d7fe1d00364945fc86b568303022157621de21a20d1d13e +size 93914 diff --git a/examples/screenshots/imgui_basic.png b/examples/screenshots/imgui_basic.png index 27288e38f..1ff9952a9 100644 --- a/examples/screenshots/imgui_basic.png +++ b/examples/screenshots/imgui_basic.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3391b7cf02fc7bd2c73dc57214b21ceaca9a1513556b3a4725639f21588824e4 -size 36261 +oid sha256:09cc7b0680e53ae1a2689b63f9b0ed641535fcffc99443cd455cc8d9b6923229 +size 36218 diff --git a/examples/screenshots/line.png b/examples/screenshots/line.png index 492ea2ada..02603b692 100644 --- a/examples/screenshots/line.png +++ b/examples/screenshots/line.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1458d472362f8d5bcef599fd64f931997a246f9e7649c80cc95f465cbd858850 -size 170243 +oid sha256:9bfaa54bde0967463413ecd2defa8ca18169d534163cc8b297879900e812fee8 +size 167012 diff --git a/examples/screenshots/line_cmap.png b/examples/screenshots/line_cmap.png index 10779fcd5..1ecc930e4 100644 --- a/examples/screenshots/line_cmap.png +++ b/examples/screenshots/line_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66e64835f824d80dd7606d90530517dbc320bcc11a68393ab92c08fef3d23f5a -size 48828 +oid sha256:d0503c008f8869dcf83793c21b15169a93558988c1a5c4edfd2aa93c549d25e1 +size 49343 diff --git a/examples/screenshots/line_cmap_more.png b/examples/screenshots/line_cmap_more.png index 56e3fe8cc..4bf597e8b 100644 --- a/examples/screenshots/line_cmap_more.png +++ b/examples/screenshots/line_cmap_more.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de08452e47799d9afcadfc583e63da1c02513cf73000bd5c2649236e61ed6b34 -size 126725 +oid sha256:ab4d759dd679a2959c0fda724e7b7a1b7593d6f67ce797f08a5292dd0eb74fb1 +size 125023 diff --git a/examples/screenshots/line_collection.png b/examples/screenshots/line_collection.png index d9124daf1..382132770 100644 --- a/examples/screenshots/line_collection.png +++ b/examples/screenshots/line_collection.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50920f4bc21bb5beffe317777a20d8d09f90f3631a14df51c219814d3507c602 -size 100758 +oid sha256:b3b6b973a52f7088536a4f437be2a7f6ebb2787756f9170145a945c53e90093c +size 98950 diff --git a/examples/screenshots/line_collection_cmap_values.png b/examples/screenshots/line_collection_cmap_values.png index e04289699..c00bffdb6 100644 --- a/examples/screenshots/line_collection_cmap_values.png +++ b/examples/screenshots/line_collection_cmap_values.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:850e3deb2220d44f01e6366ee7cffb83085cad933a137b9838ce8c2231e7786a -size 64152 +oid sha256:45bb6652f477ab0165bf59e504c1935e5781bceea9a891fcfa9975dec92eef4b +size 64720 diff --git a/examples/screenshots/line_collection_cmap_values_qualitative.png b/examples/screenshots/line_collection_cmap_values_qualitative.png index 710cee119..662d3254d 100644 --- a/examples/screenshots/line_collection_cmap_values_qualitative.png +++ b/examples/screenshots/line_collection_cmap_values_qualitative.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba5fefc8e1043fe0ebd926a6b8e6ab19e724205a4c13e4d7740122cfe464e38b -size 67017 +oid sha256:4e5b5cb45e78ae24d72f3cb84e482fac7bf0a98cd9b9b934444d2e67c9910d57 +size 66565 diff --git a/examples/screenshots/line_collection_colors.png b/examples/screenshots/line_collection_colors.png index 6c1d05f04..3b90e5b4c 100644 --- a/examples/screenshots/line_collection_colors.png +++ b/examples/screenshots/line_collection_colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17d48f07310090b835e5cd2e6fa9c178db9af8954f4b0a9d52d21997ec229abd -size 57778 +oid sha256:4edf84af27535e4a30b48906ab3cacaeb38d073290828df3c5707620e222b4d3 +size 58635 diff --git a/examples/screenshots/line_collection_slicing.png b/examples/screenshots/line_collection_slicing.png index abb63760f..e0537a261 100644 --- a/examples/screenshots/line_collection_slicing.png +++ b/examples/screenshots/line_collection_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed0d4fdb729409d07ec9ec9e05d915a04ebb237087d266591e7f46b0838e05b3 -size 130192 +oid sha256:66933c1fa349ebb4dd69b9bf396acb8f0aeeabbf17a3b7054d1f1e038a6e04be +size 129484 diff --git a/examples/screenshots/line_colorslice.png b/examples/screenshots/line_colorslice.png index 1f100d89e..f3374e221 100644 --- a/examples/screenshots/line_colorslice.png +++ b/examples/screenshots/line_colorslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b2c5562f4150ec69029a4a139469b0a2524a14078b78055df40d9b487946ce5 -size 57037 +oid sha256:d654aa666ac1f4cfbf228fc4c5fbd2f68eed841c7cc6265637d5b836b918314c +size 57989 diff --git a/examples/screenshots/line_dataslice.png b/examples/screenshots/line_dataslice.png index b2f963195..6ecf63b26 100644 --- a/examples/screenshots/line_dataslice.png +++ b/examples/screenshots/line_dataslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c31a12afa3e66c442e370e6157ad9a5aad225b21f0f95fb6a115066b1b4f2e73 -size 68811 +oid sha256:a9b93af2028eb0186dd75d74c079d5effdb284a8677e6eec1a7fd2c8de4c8498 +size 70489 diff --git a/examples/screenshots/line_stack.png b/examples/screenshots/line_stack.png index 786f434be..9a9ad4fd6 100644 --- a/examples/screenshots/line_stack.png +++ b/examples/screenshots/line_stack.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fcfa7c49d465ff9cfe472ee885bcc9d9a44106b82adfc151544847b95035d760 -size 121640 +oid sha256:4b6c2d1ee4c49ff5b193b5105b2794c6b5bd7a089a8a2c6fa03e09e02352aa65 +size 121462 diff --git a/examples/screenshots/linear_region_selectors_match_offsets.png b/examples/screenshots/linear_region_selectors_match_offsets.png index 9d2371403..327f14e72 100644 --- a/examples/screenshots/linear_region_selectors_match_offsets.png +++ b/examples/screenshots/linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f12310c09c4e84ea2c6f8245d1aa0ce9389a3d9637d7d4f9dc233bea173a0e3 -size 95366 +oid sha256:8fac4f439b34a5464792588b77856f08c127c0ee06fa77722818f8d6b48dd64c +size 95433 diff --git a/examples/screenshots/no-imgui-extent_frac_layout.png b/examples/screenshots/no-imgui-extent_frac_layout.png new file mode 100644 index 000000000..4dc3b2aa6 --- /dev/null +++ b/examples/screenshots/no-imgui-extent_frac_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5923e8b9f687f97d488b282b35f16234898ed1038b0737b7b57fb9cbd72ebf34 +size 157321 diff --git a/examples/screenshots/no-imgui-extent_layout.png b/examples/screenshots/no-imgui-extent_layout.png new file mode 100644 index 000000000..16d1ff446 --- /dev/null +++ b/examples/screenshots/no-imgui-extent_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2ffe0a8d625322cc22d2abdde80a3f179f01552dde974bbbd49f9e371ab39aa +size 138936 diff --git a/examples/screenshots/no-imgui-gridplot.png b/examples/screenshots/no-imgui-gridplot.png index 45571161d..7f870cf76 100644 --- a/examples/screenshots/no-imgui-gridplot.png +++ b/examples/screenshots/no-imgui-gridplot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a27ccf2230628980d16ab22a17df64504268da35a27cd1adb44102e64df033af -size 329247 +oid sha256:b31f2002053b5934ae78393214e67717d10bd567e590212eaff4062440657acd +size 292558 diff --git a/examples/screenshots/no-imgui-gridplot_non_square.png b/examples/screenshots/no-imgui-gridplot_non_square.png index f8c307c22..e08d64805 100644 --- a/examples/screenshots/no-imgui-gridplot_non_square.png +++ b/examples/screenshots/no-imgui-gridplot_non_square.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58f50c4fc1b00c9e78c840193d1e15d008b9fe1e7f2a3d8b90065be91e2178f5 -size 236474 +oid sha256:c9ef00db82a3559b4d7c77b68838f5876f98a2b9e80ef9ecb257f32c62161b5e +size 216512 diff --git a/examples/screenshots/no-imgui-gridplot_viewports_check.png b/examples/screenshots/no-imgui-gridplot_viewports_check.png index 8dea071d0..2a8c0dc6f 100644 --- a/examples/screenshots/no-imgui-gridplot_viewports_check.png +++ b/examples/screenshots/no-imgui-gridplot_viewports_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cda256658e84b14b48bf5151990c828092ff461f394fb9e54341ab601918aa1 -size 45113 +oid sha256:6818a7c8bdb29567bb09cfe00acaa6872a046d4d35a87ef2be7afa06c2a8a089 +size 44869 diff --git a/examples/screenshots/no-imgui-heatmap.png b/examples/screenshots/no-imgui-heatmap.png index 3d1cf5ef2..e91d06c4f 100644 --- a/examples/screenshots/no-imgui-heatmap.png +++ b/examples/screenshots/no-imgui-heatmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4fac55efd9339b180b9e34d5cf244c473d6439e57e34f272c1a7e59183f1afa2 -size 98573 +oid sha256:875c15e74e7ea2eaa6b00ddbdd80b4775ecb1fe0002a5122371d49f975369cce +size 95553 diff --git a/examples/screenshots/no-imgui-image_cmap.png b/examples/screenshots/no-imgui-image_cmap.png index 6c565ca2b..2d42899fc 100644 --- a/examples/screenshots/no-imgui-image_cmap.png +++ b/examples/screenshots/no-imgui-image_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82f7176a61e2c6953c22171bea561845bb79cb8179d76b20eef2b2cc475bbb23 -size 237327 +oid sha256:2b43bd64ceec8c5c1287a2df57abf7bd148955d6ba97a425b32ae53bad03a051 +size 216050 diff --git a/examples/screenshots/no-imgui-image_rgb.png b/examples/screenshots/no-imgui-image_rgb.png index 355238724..6be5205ac 100644 --- a/examples/screenshots/no-imgui-image_rgb.png +++ b/examples/screenshots/no-imgui-image_rgb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fce532d713d2c664eb3b676e0128060ebf17241387134812b490d3ad398d42c2 -size 269508 +oid sha256:42516cd0719d5b33ec32523dd2efe7874398bac6d0aecb5163ff1cb5c105135f +size 244717 diff --git a/examples/screenshots/no-imgui-image_rgbvminvmax.png b/examples/screenshots/no-imgui-image_rgbvminvmax.png index 6282f2438..48d8fff95 100644 --- a/examples/screenshots/no-imgui-image_rgbvminvmax.png +++ b/examples/screenshots/no-imgui-image_rgbvminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42e01469f0f7da37d3c1c90225bf7c03c44badd1f3612ac9bf88eaed5eeb6850 -size 50145 +oid sha256:7f8a99a9172ae5edf98f0d189455fad2074a99f2280c9352675bab8d4c0e3491 +size 50751 diff --git a/examples/screenshots/no-imgui-image_simple.png b/examples/screenshots/no-imgui-image_simple.png index d00a166ce..1e4487757 100644 --- a/examples/screenshots/no-imgui-image_simple.png +++ b/examples/screenshots/no-imgui-image_simple.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8bb29f192617b9dde2490ce36c69bd8352b6ba5d068434bc53edaad91871356 -size 237960 +oid sha256:3cfa6469803f44a682c9ce7337ae265a8d60749070991e6f3a723eb37c5a9a23 +size 215410 diff --git a/examples/screenshots/no-imgui-image_small.png b/examples/screenshots/no-imgui-image_small.png index aca14cd69..3613a8139 100644 --- a/examples/screenshots/no-imgui-image_small.png +++ b/examples/screenshots/no-imgui-image_small.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1ea4bcf76158169bc06973457ea09997c13ecd4a91e6e634566beb31348ef68 -size 13194 +oid sha256:17ccf0014c7ba7054440e3daf8d4e2a397e9013d1aea804c40dc7302dad4171e +size 13327 diff --git a/examples/screenshots/no-imgui-image_vminvmax.png b/examples/screenshots/no-imgui-image_vminvmax.png index 6282f2438..48d8fff95 100644 --- a/examples/screenshots/no-imgui-image_vminvmax.png +++ b/examples/screenshots/no-imgui-image_vminvmax.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42e01469f0f7da37d3c1c90225bf7c03c44badd1f3612ac9bf88eaed5eeb6850 -size 50145 +oid sha256:7f8a99a9172ae5edf98f0d189455fad2074a99f2280c9352675bab8d4c0e3491 +size 50751 diff --git a/examples/screenshots/no-imgui-line.png b/examples/screenshots/no-imgui-line.png index 29610c612..cdc24e382 100644 --- a/examples/screenshots/no-imgui-line.png +++ b/examples/screenshots/no-imgui-line.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:709458b03d535bcf407fdae1720ccdcd11a5f79ccf673e85c7e64c5748f6d25e -size 173422 +oid sha256:d3952cf9b0c9d008a885dc4abb3aeaaed6fd94a5db05ba83c6f4c4c76fe6e925 +size 171519 diff --git a/examples/screenshots/no-imgui-line_cmap.png b/examples/screenshots/no-imgui-line_cmap.png index 9340e191e..4f2bbba43 100644 --- a/examples/screenshots/no-imgui-line_cmap.png +++ b/examples/screenshots/no-imgui-line_cmap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69426f5aac61e59a08764626b2aded602e576479e652d76b6b3bf646e3218cc1 -size 48028 +oid sha256:d3c9ac8d2b8157ffd575e5ad2b2bb23b684b52403c2f4f021c52d100cfb28a83 +size 49048 diff --git a/examples/screenshots/no-imgui-line_cmap_more.png b/examples/screenshots/no-imgui-line_cmap_more.png index f0cea4ec1..8125be49f 100644 --- a/examples/screenshots/no-imgui-line_cmap_more.png +++ b/examples/screenshots/no-imgui-line_cmap_more.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df9a2ef9d54b417e0387116eb6e6215c54b7c939867d0d62c768768baae27e5f -size 129510 +oid sha256:5ddd88200aa824d4e05ba3f94fdb4216a1e7c7137b202cd8fb47997453dfd5a6 +size 126830 diff --git a/examples/screenshots/no-imgui-line_collection.png b/examples/screenshots/no-imgui-line_collection.png index ca74d3362..a31cf55fe 100644 --- a/examples/screenshots/no-imgui-line_collection.png +++ b/examples/screenshots/no-imgui-line_collection.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90f281301e8b23a22a5333e7b34316475907ac25ffc9a23b7395b7431c965343 -size 106518 +oid sha256:7d807f770c118e668c6bda1919856d7804f716a2bf95a5ae060345df1cd2b3c7 +size 102703 diff --git a/examples/screenshots/no-imgui-line_collection_cmap_values.png b/examples/screenshots/no-imgui-line_collection_cmap_values.png index df237aa1b..c909c766f 100644 --- a/examples/screenshots/no-imgui-line_collection_cmap_values.png +++ b/examples/screenshots/no-imgui-line_collection_cmap_values.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f5a7257d121a15a8a35ca6e9c70de9d6fbb4977221c840dd34e25e67136f4ea -size 67209 +oid sha256:2e8612de5c3ee252ce9c8cc8afd5bd6075d5e242e8a93cd025e28ec82526120f +size 64698 diff --git a/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png b/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png index 0347f7361..61d5a21d0 100644 --- a/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png +++ b/examples/screenshots/no-imgui-line_collection_cmap_values_qualitative.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:89a7bc62495e6454ee008e15f1504211777cc01e52f303c18f6068fd38ab3c12 -size 70090 +oid sha256:7847cd4399ce5b43bda9985eb72467ad292744aaeb9e8d210dd6c86c4eb1a090 +size 67959 diff --git a/examples/screenshots/no-imgui-line_collection_colors.png b/examples/screenshots/no-imgui-line_collection_colors.png index dff4f83db..567bb4d06 100644 --- a/examples/screenshots/no-imgui-line_collection_colors.png +++ b/examples/screenshots/no-imgui-line_collection_colors.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78b14e90e5ae1e185abb51d94ac9d99c1a4318b0ddf79c26a55e6061f22c0ed9 -size 60447 +oid sha256:15216a0900bcaef492e5d9e3380db9f28d7b7e4bd11b26eb87ce956666dcd2b1 +size 58414 diff --git a/examples/screenshots/no-imgui-line_collection_slicing.png b/examples/screenshots/no-imgui-line_collection_slicing.png index 70c343361..c9bc6d931 100644 --- a/examples/screenshots/no-imgui-line_collection_slicing.png +++ b/examples/screenshots/no-imgui-line_collection_slicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d6b4090d3ae9e38256c9f04e17bf2499f0a35348552f62e9c8d8dc97c9e760a7 -size 132125 +oid sha256:e8d3d7813580be188766c2d0200bcbff28122758d36d0faa846b0bb4dceac654 +size 130453 diff --git a/examples/screenshots/no-imgui-line_colorslice.png b/examples/screenshots/no-imgui-line_colorslice.png index 3befac6da..fe54de5d6 100644 --- a/examples/screenshots/no-imgui-line_colorslice.png +++ b/examples/screenshots/no-imgui-line_colorslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f161ad7f351b56c988e1b27155e3963be5191dc09cbaa55615026d07df07334 -size 56338 +oid sha256:be429bf910979cf4c9483b8ae1f7aa877fde64fb6ec8a4cf32be143f282c9103 +size 57353 diff --git a/examples/screenshots/no-imgui-line_dataslice.png b/examples/screenshots/no-imgui-line_dataslice.png index 957462d09..649a9df59 100644 --- a/examples/screenshots/no-imgui-line_dataslice.png +++ b/examples/screenshots/no-imgui-line_dataslice.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2f737e0afd8f57c7d621197d37fcf30199086f6c083ec0d3d8e5497965e6d12 -size 67938 +oid sha256:cf873f1479cec065f0062ce58ce78ddfbd5673654aacf0ecdbd559747ae741cb +size 69381 diff --git a/examples/screenshots/no-imgui-line_stack.png b/examples/screenshots/no-imgui-line_stack.png index 26f4a3af8..3ef24e73a 100644 --- a/examples/screenshots/no-imgui-line_stack.png +++ b/examples/screenshots/no-imgui-line_stack.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4dd69dc4be7a2283ec11a8427a75a2ddfe4be0cdbbdaedef3dcbf5f567c11ea7 -size 130519 +oid sha256:4b9d02719e7051c2a0e848cc828f21be52ac108c6f9be16795d1150a1e215371 +size 123674 diff --git a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png index 9871d65c1..809908432 100644 --- a/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png +++ b/examples/screenshots/no-imgui-linear_region_selectors_match_offsets.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:747b0915eeaf5985346e3b6807a550da53b516769d2517d7c2e0f189baefef91 -size 100604 +oid sha256:303d562f1a16f6a704415072d43ca08a51e12a702292b522e0f17f397b1aee60 +size 96668 diff --git a/examples/screenshots/no-imgui-rect_frac_layout.png b/examples/screenshots/no-imgui-rect_frac_layout.png new file mode 100644 index 000000000..4dc3b2aa6 --- /dev/null +++ b/examples/screenshots/no-imgui-rect_frac_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5923e8b9f687f97d488b282b35f16234898ed1038b0737b7b57fb9cbd72ebf34 +size 157321 diff --git a/examples/screenshots/no-imgui-rect_layout.png b/examples/screenshots/no-imgui-rect_layout.png new file mode 100644 index 000000000..16d1ff446 --- /dev/null +++ b/examples/screenshots/no-imgui-rect_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2ffe0a8d625322cc22d2abdde80a3f179f01552dde974bbbd49f9e371ab39aa +size 138936 diff --git a/examples/screenshots/no-imgui-scatter_cmap_iris.png b/examples/screenshots/no-imgui-scatter_cmap_iris.png index 35812357a..0d1f8dbb0 100644 --- a/examples/screenshots/no-imgui-scatter_cmap_iris.png +++ b/examples/screenshots/no-imgui-scatter_cmap_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:74438dc47ff3fc1391b6952a52c66160fece0545de4ad40c13d3d56b2e093257 -size 59951 +oid sha256:7e197c84911cf7711d09653d6c54d7a756fbe4fe80daa84f0cf1a1d516217423 +size 60341 diff --git a/examples/screenshots/no-imgui-scatter_colorslice_iris.png b/examples/screenshots/no-imgui-scatter_colorslice_iris.png index 61812c8d7..84447c70f 100644 --- a/examples/screenshots/no-imgui-scatter_colorslice_iris.png +++ b/examples/screenshots/no-imgui-scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a02a21459deeca379a69b30054bebcc3739553b9d377d25b953315094e714d1a -size 35763 +oid sha256:780b680de7d3a22d2cb73a6829cad1e1066163e084b8daa9e8362f2543ba62eb +size 36881 diff --git a/examples/screenshots/no-imgui-scatter_dataslice_iris.png b/examples/screenshots/no-imgui-scatter_dataslice_iris.png index 9ef39785c..a19d66270 100644 --- a/examples/screenshots/no-imgui-scatter_dataslice_iris.png +++ b/examples/screenshots/no-imgui-scatter_dataslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21ccf85a9242f6d7a724c38797688abd804d9a565e818b81ea0c8931aa05ca4e -size 38337 +oid sha256:6b4f6635f48e047944c923ac46a9bd5b77e736f26421978ff74cd37a9677c622 +size 39457 diff --git a/examples/screenshots/no-imgui-scatter_iris.png b/examples/screenshots/no-imgui-scatter_iris.png index 91dc29397..631672504 100644 --- a/examples/screenshots/no-imgui-scatter_iris.png +++ b/examples/screenshots/no-imgui-scatter_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ec960574580af159f3502da09f1f34e841267985edb52b89baf034c1d49125e -size 37410 +oid sha256:80cc8c1ed5276b0b8cbd5aeb3151182a73984829f889195b57442a58c3124a43 +size 38488 diff --git a/examples/screenshots/no-imgui-scatter_size.png b/examples/screenshots/no-imgui-scatter_size.png index 6fadfec4d..241e38ad5 100644 --- a/examples/screenshots/no-imgui-scatter_size.png +++ b/examples/screenshots/no-imgui-scatter_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94b4b9d39f3d4ef2c46b6b4dd7f712ca612f31a7fc94ab5fad8015e48c637e91 -size 70290 +oid sha256:71f3db93ea28e773c708093319985fb0fe04fae9a8a78d4f4f764f0417979b72 +size 68596 diff --git a/examples/screenshots/rect_frac_layout.png b/examples/screenshots/rect_frac_layout.png new file mode 100644 index 000000000..7fe6d3d37 --- /dev/null +++ b/examples/screenshots/rect_frac_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5991b755432318310cfc2b4826bd9639cc234883aa06f1895817f710714cb58f +size 156297 diff --git a/examples/screenshots/rect_layout.png b/examples/screenshots/rect_layout.png new file mode 100644 index 000000000..dec391ac2 --- /dev/null +++ b/examples/screenshots/rect_layout.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0cf23f845932023789e0823a105910e9f701d0f03c04e3c18488f0da62420921 +size 123409 diff --git a/examples/screenshots/scatter_cmap_iris.png b/examples/screenshots/scatter_cmap_iris.png index a887d1f99..c069d6b11 100644 --- a/examples/screenshots/scatter_cmap_iris.png +++ b/examples/screenshots/scatter_cmap_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d6bfba80eb737099040eebce9b70e1b261720f26cc895ec4b81ca21af60471c -size 60550 +oid sha256:fad40cf8004e31f7d30f4bb552ee1c7f79a499d3bad310c0eac83396f0aabd62 +size 61193 diff --git a/examples/screenshots/scatter_colorslice_iris.png b/examples/screenshots/scatter_colorslice_iris.png index e260df642..58c2b61fe 100644 --- a/examples/screenshots/scatter_colorslice_iris.png +++ b/examples/screenshots/scatter_colorslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:febd4aa7240eea70b2759337cf98be31cacc1b147859bf628e929ead0153ef9c -size 36791 +oid sha256:427587ef9a73bf9c3ea6e739b61d5af7380a5488c454a9d3653019b40d569292 +size 37589 diff --git a/examples/screenshots/scatter_dataslice_iris.png b/examples/screenshots/scatter_dataslice_iris.png index e5f05bb74..ab61f0405 100644 --- a/examples/screenshots/scatter_dataslice_iris.png +++ b/examples/screenshots/scatter_dataslice_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6cfbc717281c15c6d1d8fe2989770bc9c46f42052c897c2270294ad1b4b40d66 -size 39296 +oid sha256:e3dd9ad854f41386d353ca0dae689a263eff942817727e328690427e2e62e2f3 +size 40112 diff --git a/examples/screenshots/scatter_iris.png b/examples/screenshots/scatter_iris.png index 9c452d448..01bd5cacd 100644 --- a/examples/screenshots/scatter_iris.png +++ b/examples/screenshots/scatter_iris.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:98eab41312eb42cbffdf8add0651b55e63b5c2fb5f4523e32dc51ed28a1be369 -size 38452 +oid sha256:c7978b93f7eac8176c54ed0e39178424d9cb6474c73e9013d5164d3e88d54c95 +size 39147 diff --git a/examples/screenshots/scatter_size.png b/examples/screenshots/scatter_size.png index f2f036ea4..2f6c045f3 100644 --- a/examples/screenshots/scatter_size.png +++ b/examples/screenshots/scatter_size.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3522468f99c030cb27c225f009ecb4c7aafbd97cfc743cf1d07fb8d7ff8e0d4 -size 71336 +oid sha256:eb05b8378d94e16094738850dca6328caf7477c641bf474b9deae426344bc7a4 +size 70898 diff --git a/examples/selection_tools/unit_circle.py b/examples/selection_tools/unit_circle.py index 76f6a207c..2850b1bc1 100644 --- a/examples/selection_tools/unit_circle.py +++ b/examples/selection_tools/unit_circle.py @@ -28,12 +28,39 @@ def make_circle(center, radius: float, n_points: int) -> np.ndarray: return np.column_stack([xs, ys]) + center +# We will have 3 subplots in a layout like this: +""" +|========|========| +| | | +| | sine | +| | | +| circle |========| +| | | +| | cosine | +| | | +|========|========| +""" + +# we can define this layout using "extents", i.e. min and max ranges on the canvas +# (x_min, x_max, y_min, y_max) +# extents can be defined as fractions as shown here +extents = [ + (0, 0.5, 0, 1), # circle subplot + (0.5, 1, 0, 0.5), # sine subplot + (0.5, 1, 0.5, 1), # cosine subplot +] + # create a figure with 3 subplots -figure = fpl.Figure((3, 1), names=["unit circle", "sin(x)", "cos(x)"], size=(700, 1024)) +figure = fpl.Figure( + extents=extents, + names=["unit circle", "sin(x)", "cos(x)"], + size=(700, 560) +) # set the axes to intersect at (0, 0, 0) to better illustrate the unit circle for subplot in figure: subplot.axes.intersection = (0, 0, 0) + subplot.toolbar = False # reduce clutter figure["sin(x)"].camera.maintain_aspect = False figure["cos(x)"].camera.maintain_aspect = False @@ -73,6 +100,7 @@ def make_circle(center, radius: float, n_points: int) -> np.ndarray: sine_selector = sine_graphic.add_linear_selector() cosine_selector = cosine_graphic.add_linear_selector() + def set_circle_cmap(ev): # sets the cmap transforms diff --git a/examples/tests/test_examples.py b/examples/tests/test_examples.py index d5f3e8ab9..7fbd32e2f 100644 --- a/examples/tests/test_examples.py +++ b/examples/tests/test_examples.py @@ -103,7 +103,7 @@ def test_example_screenshots(module, force_offscreen): # hacky but it works for now example.figure.imgui_renderer.render() - example.figure._set_viewport_rects() + example.figure._fpl_reset_layout() # render each subplot for subplot in example.figure: subplot.viewport.render(subplot.scene, subplot.camera) diff --git a/examples/tests/testutils.py b/examples/tests/testutils.py index f72a87123..d6fce52fe 100644 --- a/examples/tests/testutils.py +++ b/examples/tests/testutils.py @@ -24,6 +24,7 @@ "line/*.py", "line_collection/*.py", "gridplot/*.py", + "window_layouts/*.py", "misc/*.py", "selection_tools/*.py", "guis/*.py", diff --git a/examples/window_layouts/README.rst b/examples/window_layouts/README.rst new file mode 100644 index 000000000..3c7df2366 --- /dev/null +++ b/examples/window_layouts/README.rst @@ -0,0 +1,2 @@ +WindowLayout Examples +===================== diff --git a/examples/window_layouts/extent_frac_layout.py b/examples/window_layouts/extent_frac_layout.py new file mode 100644 index 000000000..0c5293e09 --- /dev/null +++ b/examples/window_layouts/extent_frac_layout.py @@ -0,0 +1,74 @@ +""" +Fractional Extent Layout +======================== + +Create subplots using extents given as fractions of the canvas. +This example plots two images and their histograms in separate subplots + +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import imageio.v3 as iio +import fastplotlib as fpl + +# load images +img1 = iio.imread("imageio:astronaut.png") +img2 = iio.imread("imageio:wikkie.png") + +# calculate histograms +hist_1, edges_1 = np.histogram(img1) +centers_1 = edges_1[:-1] + np.diff(edges_1) / 2 + +hist_2, edges_2 = np.histogram(img2) +centers_2 = edges_2[:-1] + np.diff(edges_2) / 2 + +# figure size in pixels +size = (700, 560) + +# extent is (xmin, xmax, ymin, ymax) +# here it is defined as fractions of the canvas +extents = [ + (0, 0.3, 0, 0.5), # for image1 + (0, 0.3, 0.5, 1), # for image2 + (0.3, 1, 0, 0.5), # for image1 histogram + (0.3, 1, 0.5, 1), # for image2 histogram +] + +# create a figure using the rects and size +# also give each subplot a name +figure = fpl.Figure( + extents=extents, + names=["astronaut image", "wikkie image", "astronaut histogram", "wikkie histogram"], + size=size +) + +# add image to the corresponding subplots +figure["astronaut image"].add_image(img1) +figure["wikkie image"].add_image(img2) + +# add histogram to the corresponding subplots +figure["astronaut histogram"].add_line(np.column_stack([centers_1, hist_1])) +figure["wikkie histogram"].add_line(np.column_stack([centers_2, hist_2])) + + +for subplot in figure: + if "image" in subplot.name: + # remove axes from image subplots to reduce clutter + subplot.axes.visible = False + continue + + # don't maintain aspect ratio for the histogram subplots + subplot.camera.maintain_aspect = False + + +figure.show() + + +# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively +# please see our docs for using fastplotlib interactively in ipython and jupyter +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/window_layouts/extent_layout.py b/examples/window_layouts/extent_layout.py new file mode 100644 index 000000000..e6facaaa2 --- /dev/null +++ b/examples/window_layouts/extent_layout.py @@ -0,0 +1,74 @@ +""" +Extent Layout +============= + +Create subplots using given extents in absolute pixels. +This example plots two images and their histograms in separate subplots + +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import imageio.v3 as iio +import fastplotlib as fpl + +# load images +img1 = iio.imread("imageio:astronaut.png") +img2 = iio.imread("imageio:wikkie.png") + +# calculate histograms +hist_1, edges_1 = np.histogram(img1) +centers_1 = edges_1[:-1] + np.diff(edges_1) / 2 + +hist_2, edges_2 = np.histogram(img2) +centers_2 = edges_2[:-1] + np.diff(edges_2) / 2 + +# figure size in pixels +size = (640, 480) + +# extent is (xmin, xmax, ymin, ymax) +# here it is defined in absolute pixels +extents = [ + (0, 200, 0, 240), # for image1 + (0, 200, 240, 480), # for image2 + (200, 640, 0, 240), # for image1 histogram + (200, 640, 240, 480), # for image2 histogram +] + +# create a figure using the rects and size +# also give each subplot a name +figure = fpl.Figure( + extents=extents, + names=["astronaut image", "wikkie image", "astronaut histogram", "wikkie histogram"], + size=size +) + +# add image to the corresponding subplots +figure["astronaut image"].add_image(img1) +figure["wikkie image"].add_image(img2) + +# add histogram to the corresponding subplots +figure["astronaut histogram"].add_line(np.column_stack([centers_1, hist_1])) +figure["wikkie histogram"].add_line(np.column_stack([centers_2, hist_2])) + + +for subplot in figure: + if "image" in subplot.name: + # remove axes from image subplots to reduce clutter + subplot.axes.visible = False + continue + + # don't maintain aspect ratio for the histogram subplots + subplot.camera.maintain_aspect = False + + +figure.show() + + +# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively +# please see our docs for using fastplotlib interactively in ipython and jupyter +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/window_layouts/rect_frac_layout.py b/examples/window_layouts/rect_frac_layout.py new file mode 100644 index 000000000..072fa1107 --- /dev/null +++ b/examples/window_layouts/rect_frac_layout.py @@ -0,0 +1,74 @@ +""" +Rect Fractional Layout +====================== + +Create subplots using rects given as fractions of the canvas. +This example plots two images and their histograms in separate subplots + +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import imageio.v3 as iio +import fastplotlib as fpl + +# load images +img1 = iio.imread("imageio:astronaut.png") +img2 = iio.imread("imageio:wikkie.png") + +# calculate histograms +hist_1, edges_1 = np.histogram(img1) +centers_1 = edges_1[:-1] + np.diff(edges_1) / 2 + +hist_2, edges_2 = np.histogram(img2) +centers_2 = edges_2[:-1] + np.diff(edges_2) / 2 + +# figure size in pixels +size = (700, 560) + +# rect is (x, y, width, height) +# here it is defined as fractions of the canvas +rects = [ + (0, 0, 0.3, 0.5), # for image1 + (0, 0.5, 0.3, 0.5), # for image2 + (0.3, 0, 0.7, 0.5), # for image1 histogram + (0.3, 0.5, 0.7, 0.5), # for image2 histogram +] + +# create a figure using the rects and size +# also give each subplot a name +figure = fpl.Figure( + rects=rects, + names=["astronaut image", "wikkie image", "astronaut histogram", "wikkie histogram"], + size=size +) + +# add image to the corresponding subplots +figure["astronaut image"].add_image(img1) +figure["wikkie image"].add_image(img2) + +# add histogram to the corresponding subplots +figure["astronaut histogram"].add_line(np.column_stack([centers_1, hist_1])) +figure["wikkie histogram"].add_line(np.column_stack([centers_2, hist_2])) + + +for subplot in figure: + if "image" in subplot.name: + # remove axes from image subplots to reduce clutter + subplot.axes.visible = False + continue + + # don't maintain aspect ratio for the histogram subplots + subplot.camera.maintain_aspect = False + + +figure.show() + + +# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively +# please see our docs for using fastplotlib interactively in ipython and jupyter +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/examples/window_layouts/rect_layout.py b/examples/window_layouts/rect_layout.py new file mode 100644 index 000000000..962b8a4f1 --- /dev/null +++ b/examples/window_layouts/rect_layout.py @@ -0,0 +1,74 @@ +""" +Rect Layout +=========== + +Create subplots using given rects in absolute pixels. +This example plots two images and their histograms in separate subplots + +""" + +# test_example = true +# sphinx_gallery_pygfx_docs = 'screenshot' + +import numpy as np +import imageio.v3 as iio +import fastplotlib as fpl + +# load images +img1 = iio.imread("imageio:astronaut.png") +img2 = iio.imread("imageio:wikkie.png") + +# calculate histograms +hist_1, edges_1 = np.histogram(img1) +centers_1 = edges_1[:-1] + np.diff(edges_1) / 2 + +hist_2, edges_2 = np.histogram(img2) +centers_2 = edges_2[:-1] + np.diff(edges_2) / 2 + +# figure size in pixels +size = (640, 480) + +# a rect is (x, y, width, height) +# here it is defined in absolute pixels +rects = [ + (0, 0, 200, 240), # for image1 + (0, 240, 200, 240), # for image2 + (200, 0, 440, 240), # for image1 histogram + (200, 240, 440, 240), # for image2 histogram +] + +# create a figure using the rects and size +# also give each subplot a name +figure = fpl.Figure( + rects=rects, + names=["astronaut image", "wikkie image", "astronaut histogram", "wikkie histogram"], + size=size +) + +# add image to the corresponding subplots +figure["astronaut image"].add_image(img1) +figure["wikkie image"].add_image(img2) + +# add histogram to the corresponding subplots +figure["astronaut histogram"].add_line(np.column_stack([centers_1, hist_1])) +figure["wikkie histogram"].add_line(np.column_stack([centers_2, hist_2])) + + +for subplot in figure: + if "image" in subplot.name: + # remove axes from image subplots to reduce clutter + subplot.axes.visible = False + continue + + # don't maintain aspect ratio for the histogram subplots + subplot.camera.maintain_aspect = False + + +figure.show() + + +# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively +# please see our docs for using fastplotlib interactively in ipython and jupyter +if __name__ == "__main__": + print(__doc__) + fpl.loop.run() diff --git a/fastplotlib/graphics/_base.py b/fastplotlib/graphics/_base.py index a25bc7176..61ad291ee 100644 --- a/fastplotlib/graphics/_base.py +++ b/fastplotlib/graphics/_base.py @@ -365,7 +365,7 @@ def _fpl_add_plot_area_hook(self, plot_area): self._plot_area = plot_area def __repr__(self): - rval = f"{self.__class__.__name__} @ {hex(id(self))}" + rval = f"{self.__class__.__name__}" if self.name is not None: return f"'{self.name}': {rval}" else: diff --git a/fastplotlib/graphics/_features/_text.py b/fastplotlib/graphics/_features/_text.py index 90af7c719..a95fe256c 100644 --- a/fastplotlib/graphics/_features/_text.py +++ b/fastplotlib/graphics/_features/_text.py @@ -16,7 +16,7 @@ def value(self) -> str: @block_reentrance def set_value(self, graphic, value: str): - graphic.world_object.geometry.set_text(value) + graphic.world_object.set_text(value) self._value = value event = FeatureEvent(type="text", info={"value": value}) @@ -34,8 +34,8 @@ def value(self) -> float | int: @block_reentrance def set_value(self, graphic, value: float | int): - graphic.world_object.geometry.font_size = value - self._value = graphic.world_object.geometry.font_size + graphic.world_object.font_size = value + self._value = graphic.world_object.font_size event = FeatureEvent(type="font_size", info={"value": value}) self._call_event_handlers(event) diff --git a/fastplotlib/graphics/text.py b/fastplotlib/graphics/text.py index fcee6129b..e3794743a 100644 --- a/fastplotlib/graphics/text.py +++ b/fastplotlib/graphics/text.py @@ -79,13 +79,11 @@ def __init__( self._outline_thickness = TextOutlineThickness(outline_thickness) world_object = pygfx.Text( - pygfx.TextGeometry( - text=self.text, - font_size=self.font_size, - screen_space=screen_space, - anchor=anchor, - ), - pygfx.TextMaterial( + text=self.text, + font_size=self.font_size, + screen_space=screen_space, + anchor=anchor, + material=pygfx.TextMaterial( color=self.face_color, outline_color=self.outline_color, outline_thickness=self.outline_thickness, @@ -97,6 +95,11 @@ def __init__( self.offset = offset + @property + def world_object(self) -> pygfx.Text: + """Text world object""" + return super(TextGraphic, self).world_object + @property def text(self) -> str: """the text displayed""" diff --git a/fastplotlib/layouts/__init__.py b/fastplotlib/layouts/__init__.py index 4a4f45174..8fb1d54d8 100644 --- a/fastplotlib/layouts/__init__.py +++ b/fastplotlib/layouts/__init__.py @@ -1,11 +1,5 @@ from ._figure import Figure - -try: - import imgui_bundle -except ImportError: - IMGUI = False -else: - IMGUI = True +from ._utils import IMGUI if IMGUI: from ._imgui_figure import ImguiFigure diff --git a/fastplotlib/layouts/_engine.py b/fastplotlib/layouts/_engine.py new file mode 100644 index 000000000..877a7fbab --- /dev/null +++ b/fastplotlib/layouts/_engine.py @@ -0,0 +1,390 @@ +from functools import partial + +import numpy as np +import pygfx + +from ._subplot import Subplot +from ._rect import RectManager + + +class UnderlayCamera(pygfx.Camera): + """ + Same as pygfx.ScreenCoordsCamera but y-axis is inverted. + + So top left corner is (0, 0). This is easier to manage because we + often resize using the bottom right corner. + + """ + + def _update_projection_matrix(self): + width, height = self._view_size + sx, sy, sz = 2 / width, 2 / height, 1 + dx, dy, dz = -1, 1, 0 # pygfx is -1, -1, 0 + m = sx, 0, 0, dx, 0, sy, 0, dy, 0, 0, sz, dz, 0, 0, 0, 1 + proj_matrix = np.array(m, dtype=float).reshape(4, 4) + proj_matrix.flags.writeable = False + return proj_matrix + + +class BaseLayout: + def __init__( + self, + renderer: pygfx.WgpuRenderer, + subplots: np.ndarray[Subplot], + canvas_rect: tuple[float, float], + moveable: bool, + resizeable: bool, + ): + """ + Base layout engine, subclass to create a usable layout engine. + """ + self._renderer = renderer + self._subplots: np.ndarray[Subplot] = subplots.ravel() + self._canvas_rect = canvas_rect + + self._last_pointer_pos: np.ndarray[np.float64, np.float64] = np.array( + [np.nan, np.nan] + ) + + # the current user action, move or resize + self._active_action: str | None = None + # subplot that is currently in action, i.e. currently being moved or resized + self._active_subplot: Subplot | None = None + # subplot that is in focus, i.e. being hovered by the pointer + self._subplot_focus: Subplot | None = None + + for subplot in self._subplots: + # highlight plane when pointer enters it + subplot.frame.plane.add_event_handler( + partial(self._highlight_plane, subplot), "pointer_enter" + ) + + if resizeable: + # highlight/unhighlight resize handler when pointer enters/leaves + subplot.frame.resize_handle.add_event_handler( + partial(self._highlight_resize_handler, subplot), "pointer_enter" + ) + subplot.frame.resize_handle.add_event_handler( + partial(self._unhighlight_resize_handler, subplot), "pointer_leave" + ) + + def _inside_render_rect(self, subplot: Subplot, pos: tuple[int, int]) -> bool: + """whether the pos is within the render area, used for filtering out pointer events""" + rect = subplot.frame.get_render_rect() + + x0, y0 = rect[:2] + + x1 = x0 + rect[2] + y1 = y0 + rect[3] + + if (x0 < pos[0] < x1) and (y0 < pos[1] < y1): + return True + + return False + + def canvas_resized(self, canvas_rect: tuple): + """ + called by figure when canvas is resized + + Parameters + ---------- + canvas_rect: (x, y, w, h) + the rect that pygfx can render to, excludes any areas used by imgui. + + """ + + self._canvas_rect = canvas_rect + for subplot in self._subplots: + subplot.frame.canvas_resized(canvas_rect) + + def _highlight_resize_handler(self, subplot: Subplot, ev): + if self._active_action == "resize": + return + + ev.target.material.color = subplot.frame.resize_handle_color.highlight + + def _unhighlight_resize_handler(self, subplot: Subplot, ev): + if self._active_action == "resize": + return + + ev.target.material.color = subplot.frame.resize_handle_color.idle + + def _highlight_plane(self, subplot: Subplot, ev): + if self._active_action is not None: + return + + # reset color of previous focus + if self._subplot_focus is not None: + self._subplot_focus.frame.plane.material.color = ( + subplot.frame.plane_color.idle + ) + + self._subplot_focus = subplot + ev.target.material.color = subplot.frame.plane_color.highlight + + def __len__(self): + return len(self._subplots) + + +class WindowLayout(BaseLayout): + def __init__( + self, + renderer, + subplots: np.ndarray[Subplot], + canvas_rect: tuple, + moveable=True, + resizeable=True, + ): + """ + Flexible layout engine that allows freely moving and resizing subplots. + Subplots are not allowed to overlap. + + We use a screenspace camera to perform an underlay render pass to draw the + subplot frames, there is no depth rendering so we do not allow overlaps. + + """ + + super().__init__(renderer, subplots, canvas_rect, moveable, resizeable) + + self._last_pointer_pos: np.ndarray[np.float64, np.float64] = np.array( + [np.nan, np.nan] + ) + + for subplot in self._subplots: + if moveable: + # start a move action + subplot.frame.plane.add_event_handler( + partial(self._action_start, subplot, "move"), "pointer_down" + ) + # start a resize action + subplot.frame.resize_handle.add_event_handler( + partial(self._action_start, subplot, "resize"), "pointer_down" + ) + + if moveable or resizeable: + # when pointer moves, do an iteration of move or resize action + self._renderer.add_event_handler(self._action_iter, "pointer_move") + + # end the action when pointer button goes up + self._renderer.add_event_handler(self._action_end, "pointer_up") + + def _new_extent_from_delta(self, delta: tuple[int, int]) -> np.ndarray: + delta_x, delta_y = delta + if self._active_action == "resize": + # subtract only from x1, y1 + new_extent = self._active_subplot.frame.extent - np.asarray( + [0, delta_x, 0, delta_y] + ) + else: + # moving + new_extent = self._active_subplot.frame.extent - np.asarray( + [delta_x, delta_x, delta_y, delta_y] + ) + + x0, x1, y0, y1 = new_extent + w = x1 - x0 + h = y1 - y0 + + # make sure width and height are valid + # min width, height is 50px + if w <= 50: # width > 0 + new_extent[:2] = self._active_subplot.frame.extent[:2] + + if h <= 50: # height > 0 + new_extent[2:] = self._active_subplot.frame.extent[2:] + + # ignore movement if this would cause an overlap + for subplot in self._subplots: + if subplot is self._active_subplot: + continue + + if subplot.frame.rect_manager.overlaps(new_extent): + # we have an overlap, need to ignore one or more deltas + # ignore x + if not subplot.frame.rect_manager.is_left_of( + x0 + ) or not subplot.frame.rect_manager.is_right_of(x1): + new_extent[:2] = self._active_subplot.frame.extent[:2] + + # ignore y + if not subplot.frame.rect_manager.is_above( + y0 + ) or not subplot.frame.rect_manager.is_below(y1): + new_extent[2:] = self._active_subplot.frame.extent[2:] + + # make sure all vals are non-negative + if (new_extent[:2] < 0).any(): + # ignore delta_x + new_extent[:2] = self._active_subplot.frame.extent[:2] + + if (new_extent[2:] < 0).any(): + # ignore delta_y + new_extent[2:] = self._active_subplot.frame.extent[2:] + + # canvas extent + cx0, cy0, cw, ch = self._canvas_rect + + # check if new x-range is beyond canvas x-max + if (new_extent[:2] > cx0 + cw).any(): + new_extent[:2] = self._active_subplot.frame.extent[:2] + + # check if new y-range is beyond canvas y-max + if (new_extent[2:] > cy0 + ch).any(): + new_extent[2:] = self._active_subplot.frame.extent[2:] + + return new_extent + + def _action_start(self, subplot: Subplot, action: str, ev): + if self._inside_render_rect(subplot, pos=(ev.x, ev.y)): + return + + if ev.button == 1: # left mouse button + self._active_action = action + if action == "resize": + subplot.frame.resize_handle.material.color = ( + subplot.frame.resize_handle_color.action + ) + elif action == "move": + subplot.frame.plane.material.color = subplot.frame.plane_color.action + else: + raise ValueError + + self._active_subplot = subplot + self._last_pointer_pos[:] = ev.x, ev.y + + def _action_iter(self, ev): + if self._active_action is None: + return + + delta_x, delta_y = self._last_pointer_pos - (ev.x, ev.y) + new_extent = self._new_extent_from_delta((delta_x, delta_y)) + self._active_subplot.frame.extent = new_extent + self._last_pointer_pos[:] = ev.x, ev.y + + def _action_end(self, ev): + self._active_action = None + if self._active_subplot is not None: + self._active_subplot.frame.resize_handle.material.color = ( + self._active_subplot.frame.resize_handle_color.idle + ) + self._active_subplot.frame.plane.material.color = ( + self._active_subplot.frame.plane_color.idle + ) + self._active_subplot = None + + self._last_pointer_pos[:] = np.nan + + def set_rect(self, subplot: Subplot, rect: tuple | list | np.ndarray): + """ + Set the rect of a Subplot + + Parameters + ---------- + subplot: Subplot + the subplot to set the rect of + + rect: (x, y, w, h) + as absolute pixels or fractional. + If width & height <= 1 the rect is assumed to be fractional. + Conversely, if width & height > 1 the rect is assumed to be in absolute pixels. + width & height must be > 0. Negative values are not allowed. + + """ + + new_rect = RectManager(*rect, self._canvas_rect) + extent = new_rect.extent + # check for overlaps + for s in self._subplots: + if s is subplot: + continue + + if s.frame.rect_manager.overlaps(extent): + raise ValueError(f"Given rect: {rect} overlaps with another subplot.") + + def set_extent(self, subplot: Subplot, extent: tuple | list | np.ndarray): + """ + Set the extent of a Subplot + + Parameters + ---------- + subplot: Subplot + the subplot to set the extent of + + extent: (xmin, xmax, ymin, ymax) + as absolute pixels or fractional. + If xmax & ymax <= 1 the extent is assumed to be fractional. + Conversely, if xmax & ymax > 1 the extent is assumed to be in absolute pixels. + Negative values are not allowed. xmax - xmin & ymax - ymin must be > 0. + + """ + + new_rect = RectManager.from_extent(extent, self._canvas_rect) + extent = new_rect.extent + # check for overlaps + for s in self._subplots: + if s is subplot: + continue + + if s.frame.rect_manager.overlaps(extent): + raise ValueError( + f"Given extent: {extent} overlaps with another subplot." + ) + + +class GridLayout(WindowLayout): + def __init__( + self, + renderer, + subplots: np.ndarray[Subplot], + canvas_rect: tuple[float, float, float, float], + shape: tuple[int, int], + ): + """ + Grid layout engine that auto-sets Frame and Subplot rects such that they maintain + a fixed grid layout. Does not allow freely moving or resizing subplots. + + """ + + super().__init__( + renderer, subplots, canvas_rect, moveable=False, resizeable=False + ) + + # {Subplot: (row_ix, col_ix)}, dict mapping subplots to their row and col index in the grid layout + self._subplot_grid_position: dict[Subplot, tuple[int, int]] + self._shape = shape + + @property + def shape(self) -> tuple[int, int]: + return self._shape + + def set_rect(self, subplot, rect: np.ndarray | list | tuple): + raise NotImplementedError( + "set_rect() not implemented for GridLayout which is an auto layout manager" + ) + + def set_extent(self, subplot, extent: np.ndarray | list | tuple): + raise NotImplementedError( + "set_extent() not implemented for GridLayout which is an auto layout manager" + ) + + def add_row(self): + raise NotImplementedError("Not yet implemented") + + def add_column(self): + raise NotImplementedError("Not yet implemented") + + def remove_row(self): + raise NotImplementedError("Not yet implemented") + + def remove_column(self): + raise NotImplementedError("Not yet implemented") + + def add_subplot(self): + raise NotImplementedError( + "Not implemented for GridLayout which is an auto layout manager" + ) + + def remove_subplot(self, subplot): + raise NotImplementedError( + "Not implemented for GridLayout which is an auto layout manager" + ) diff --git a/fastplotlib/layouts/_figure.py b/fastplotlib/layouts/_figure.py index e09005a4c..e1822eb64 100644 --- a/fastplotlib/layouts/_figure.py +++ b/fastplotlib/layouts/_figure.py @@ -11,20 +11,24 @@ from rendercanvas import BaseRenderCanvas -from ._utils import make_canvas_and_renderer, create_controller, create_camera +from ._utils import ( + make_canvas_and_renderer, + create_controller, + create_camera, + get_extents_from_grid, +) from ._utils import controller_types as valid_controller_types from ._subplot import Subplot +from ._engine import GridLayout, WindowLayout, UnderlayCamera from .. import ImageGraphic -# number of pixels taken by the imgui toolbar when present -IMGUI_TOOLBAR_HEIGHT = 39 - - class Figure: def __init__( self, - shape: list[tuple[int, int, int, int]] | tuple[int, int] = (1, 1), + shape: tuple[int, int] = (1, 1), + rects: list[tuple | np.ndarray] = None, + extents: list[tuple | np.ndarray] = None, cameras: ( Literal["2d", "3d"] | Iterable[Iterable[Literal["2d", "3d"]]] @@ -48,14 +52,31 @@ def __init__( names: list | np.ndarray = None, ): """ - A grid of subplots. + Create a Figure containing Subplots. Parameters ---------- - shape: list[tuple[int, int, int, int]] | tuple[int, int], default (1, 1) - grid of shape [n_rows, n_cols] or list of bounding boxes: [x, y, width, height] (NOT YET IMPLEMENTED) - - cameras: "2d", "3", list of "2d" | "3d", Iterable of camera instances, or Iterable of "2d" | "3d", optional + shape: tuple[int, int], default (1, 1) + shape [n_rows, n_cols] that defines a grid of subplots + + rects: list of tuples or arrays + list of rects (x, y, width, height) that define the subplots. + rects can be defined in absolute pixels or as a fraction of the canvas. + If width & height <= 1 the rect is assumed to be fractional. + Conversely, if width & height > 1 the rect is assumed to be in absolute pixels. + width & height must be > 0. Negative values are not allowed. + + extents: list of tuples or arrays + list of extents (xmin, xmax, ymin, ymax) that define the subplots. + extents can be defined in absolute pixels or as a fraction of the canvas. + If xmax & ymax <= 1 the extent is assumed to be fractional. + Conversely, if xmax & ymax > 1 the extent is assumed to be in absolute pixels. + Negative values are not allowed. xmax - xmin & ymax - ymin must be > 0. + + If both ``rects`` and ``extents`` are provided, then ``rects`` takes precedence over ``extents``, i.e. + ``extents`` is ignored when ``rects`` are also provided. + + cameras: "2d", "3d", list of "2d" | "3d", Iterable of camera instances, or Iterable of "2d" | "3d", optional | if str, one of ``"2d"`` or ``"3d"`` indicating 2D or 3D cameras for all subplots | Iterable/list/array of ``2d`` and/or ``3d`` that specifies the camera type for each subplot | Iterable/list/array of pygfx.PerspectiveCamera instances @@ -91,84 +112,85 @@ def __init__( pygfx renderer instance size: (int, int), optional - starting size of canvas, default (500, 300) + starting size of canvas in absolute pixels, default (500, 300) names: list or array of str, optional subplot names + """ - if isinstance(shape, list): - raise NotImplementedError("bounding boxes for shape not yet implemented") - if not all(isinstance(v, (tuple, list)) for v in shape): + if rects is not None: + if not all(isinstance(v, (np.ndarray, tuple, list)) for v in rects): raise TypeError( - "shape argument must be a list of bounding boxes or a tuple[n_rows, n_cols]" + f"rects must a list of arrays, tuples, or lists of rects (x, y, w, h), you have passed: {rects}" ) - for item in shape: - if not all(isinstance(v, (int, np.integer)) for v in item): - raise TypeError( - "shape argument must be a list of bounding boxes or a tuple[n_rows, n_cols]" - ) - # constant that sets the Figure to be in "rect" mode - self._mode: str = "rect" + n_subplots = len(rects) + layout_mode = "rect" + extents = [None] * n_subplots - elif isinstance(shape, tuple): - if not all(isinstance(v, (int, np.integer)) for v in shape): + elif extents is not None: + if not all(isinstance(v, (np.ndarray, tuple, list)) for v in extents): raise TypeError( - "shape argument must be a list of bounding boxes or a tuple[n_rows, n_cols]" + f"extents must a list of arrays, tuples, or lists of extents (xmin, xmax, ymin, ymax), " + f"you have passed: {extents}" ) - # constant that sets the Figure to be in "grid" mode - self._mode: str = "grid" - - # shape is [n_subplots, row_col_index] - self._subplot_grid_positions: dict[Subplot, tuple[int, int]] = dict() + n_subplots = len(extents) + layout_mode = "extent" + rects = [None] * n_subplots else: - raise TypeError( - "shape argument must be a list of bounding boxes or a tuple[n_rows, n_cols]" - ) - - self._shape = shape + if not all(isinstance(v, (int, np.integer)) for v in shape): + raise TypeError("shape argument must be a tuple[n_rows, n_cols]") + n_subplots = shape[0] * shape[1] + layout_mode = "grid" - # default spacing of 2 pixels between subplots - self._spacing = 2 + # create fractional extents from the grid + extents = get_extents_from_grid(shape) + # empty rects + rects = [None] * n_subplots if names is not None: subplot_names = np.asarray(names).flatten() - if subplot_names.size != len(self): + if subplot_names.size != n_subplots: raise ValueError( - "must provide same number of subplot `names` as specified by Figure `shape`" + f"must provide same number of subplot `names` as specified by shape, extents, or rects: {n_subplots}" ) else: - subplot_names = None + if layout_mode == "grid": + subplot_names = np.asarray( + list(map(str, product(range(shape[0]), range(shape[1])))) + ) + else: + subplot_names = None canvas, renderer = make_canvas_and_renderer( canvas, renderer, canvas_kwargs={"size": size} ) - canvas.add_event_handler(self._set_viewport_rects, "resize") + canvas.add_event_handler(self._fpl_reset_layout, "resize") if isinstance(cameras, str): # create the array representing the views for each subplot in the grid - cameras = np.array([cameras] * len(self)) + cameras = np.array([cameras] * n_subplots) # list/tuple -> array if necessary cameras = np.asarray(cameras).flatten() - if cameras.size != len(self): + if cameras.size != n_subplots: raise ValueError( - f"Number of cameras: {cameras.size} does not match the number of subplots: {len(self)}" + f"Number of cameras: {cameras.size} does not match the number of subplots: {n_subplots}" ) # create the cameras - subplot_cameras = np.empty(len(self), dtype=object) - for index in range(len(self)): + subplot_cameras = np.empty(n_subplots, dtype=object) + for index in range(n_subplots): subplot_cameras[index] = create_camera(camera_type=cameras[index]) # if controller instances have been specified for each subplot if controllers is not None: # one controller for all subplots if isinstance(controllers, pygfx.Controller): - controllers = [controllers] * len(self) + controllers = [controllers] * n_subplots # individual controller instance specified for each subplot else: @@ -188,25 +210,25 @@ def __init__( subplot_controllers: np.ndarray[pygfx.Controller] = np.asarray( controllers ).flatten() - if not subplot_controllers.size == len(self): + if not subplot_controllers.size == n_subplots: raise ValueError( f"number of controllers passed must be the same as the number of subplots specified " - f"by shape: {len(self)}. You have passed: {subplot_controllers.size} controllers" + f"by shape, extents, or rects: {n_subplots}. You have passed: {subplot_controllers.size} controllers" ) from None - for index in range(len(self)): + for index in range(n_subplots): subplot_controllers[index].add_camera(subplot_cameras[index]) # parse controller_ids and controller_types to make desired controller for each subplot else: if controller_ids is None: # individual controller for each subplot - controller_ids = np.arange(len(self)) + controller_ids = np.arange(n_subplots) elif isinstance(controller_ids, str): if controller_ids == "sync": # this will end up creating one controller to control the camera of every subplot - controller_ids = np.zeros(len(self), dtype=int) + controller_ids = np.zeros(n_subplots, dtype=int) else: raise ValueError( f"`controller_ids` must be one of 'sync', an array/list of subplot names, or an array/list of " @@ -236,7 +258,7 @@ def __init__( ) # initialize controller_ids array - ids_init = np.arange(len(self)) + ids_init = np.arange(n_subplots) # set id based on subplot position for each synced sublist for row_ix, sublist in enumerate(controller_ids): @@ -261,18 +283,18 @@ def __init__( f"you have passed: {controller_ids}" ) - if controller_ids.size != len(self): + if controller_ids.size != n_subplots: raise ValueError( - "Number of controller_ids does not match the number of subplots" + f"Number of controller_ids does not match the number of subplots: {n_subplots}" ) if controller_types is None: # `create_controller()` will auto-determine controller for each subplot based on defaults - controller_types = np.array(["default"] * len(self)) + controller_types = np.array(["default"] * n_subplots) # valid controller types if isinstance(controller_types, str): - controller_types = np.array([controller_types] * len(self)) + controller_types = np.array([controller_types] * n_subplots) controller_types: np.ndarray[pygfx.Controller] = np.asarray( controller_types @@ -292,7 +314,7 @@ def __init__( ) # make the real controllers for each subplot - subplot_controllers = np.empty(shape=len(self), dtype=object) + subplot_controllers = np.empty(shape=n_subplots, dtype=object) for cid in np.unique(controller_ids): cont_type = controller_types[controller_ids == cid] if np.unique(cont_type).size > 1: @@ -323,34 +345,66 @@ def __init__( self._canvas = canvas self._renderer = renderer - if self.mode == "grid": - nrows, ncols = self.shape + if layout_mode == "grid": + n_rows, n_cols = shape + grid_index_iterator = list(product(range(n_rows), range(n_cols))) + self._subplots: np.ndarray[Subplot] = np.empty(shape=shape, dtype=object) + resizeable = False - self._subplots: np.ndarray[Subplot] = np.ndarray( - shape=(nrows, ncols), dtype=object + else: + self._subplots: np.ndarray[Subplot] = np.empty( + shape=n_subplots, dtype=object ) + resizeable = True - for i, (row_ix, col_ix) in enumerate(product(range(nrows), range(ncols))): - camera = subplot_cameras[i] - controller = subplot_controllers[i] + for i in range(n_subplots): + camera = subplot_cameras[i] + controller = subplot_controllers[i] - if subplot_names is not None: - name = subplot_names[i] - else: - name = None - - subplot = Subplot( - parent=self, - camera=camera, - controller=controller, - canvas=canvas, - renderer=renderer, - name=name, - ) + if subplot_names is not None: + name = subplot_names[i] + else: + name = None + + subplot = Subplot( + parent=self, + camera=camera, + controller=controller, + canvas=canvas, + renderer=renderer, + name=name, + rect=rects[i], + extent=extents[i], # figure created extents for grid layout + resizeable=resizeable, + ) + if layout_mode == "grid": + row_ix, col_ix = grid_index_iterator[i] self._subplots[row_ix, col_ix] = subplot + else: + self._subplots[i] = subplot + + if layout_mode == "grid": + self._layout = GridLayout( + self.renderer, + subplots=self._subplots, + canvas_rect=self.get_pygfx_render_area(), + shape=shape, + ) + + elif layout_mode == "rect" or layout_mode == "extent": + self._layout = WindowLayout( + self.renderer, + subplots=self._subplots, + canvas_rect=self.get_pygfx_render_area(), + ) + + self._underlay_camera = UnderlayCamera() + + self._underlay_scene = pygfx.Scene() - self._subplot_grid_positions[subplot] = (row_ix, col_ix) + for subplot in self._subplots.ravel(): + self._underlay_scene.add(subplot.frame._world_object) self._animate_funcs_pre: list[callable] = list() self._animate_funcs_post: list[callable] = list() @@ -366,31 +420,15 @@ def __init__( @property def shape(self) -> list[tuple[int, int, int, int]] | tuple[int, int]: """[n_rows, n_cols]""" - return self._shape + if isinstance(self.layout, GridLayout): + return self.layout.shape @property - def mode(self) -> str: + def layout(self) -> WindowLayout | GridLayout: """ - one of 'grid' or 'rect' - - Used by Figure to determine certain aspects, such as how to calculate - rects and shapes of properties for cameras, controllers, and subplots arrays + Layout engine """ - return self._mode - - @property - def spacing(self) -> int: - """spacing between subplots, in pixels""" - return self._spacing - - @spacing.setter - def spacing(self, value: int): - """set the spacing between subplots, in pixels""" - if not isinstance(value, (int, np.integer)): - raise TypeError("spacing must be of type ") - - self._spacing = value - self._set_viewport_rects() + return self._layout @property def canvas(self) -> BaseRenderCanvas: @@ -407,7 +445,7 @@ def controllers(self) -> np.ndarray[pygfx.Controller]: """controllers, read-only array, access individual subplots to change a controller""" controllers = np.asarray([subplot.controller for subplot in self], dtype=object) - if self.mode == "grid": + if isinstance(self.layout, GridLayout): controllers = controllers.reshape(self.shape) controllers.flags.writeable = False @@ -418,7 +456,7 @@ def cameras(self) -> np.ndarray[pygfx.Camera]: """cameras, read-only array, access individual subplots to change a camera""" cameras = np.asarray([subplot.camera for subplot in self], dtype=object) - if self.mode == "grid": + if isinstance(self.layout, GridLayout): cameras = cameras.reshape(self.shape) cameras.flags.writeable = False @@ -429,25 +467,16 @@ def names(self) -> np.ndarray[str]: """subplot names, read-only array, access individual subplots to change a name""" names = np.asarray([subplot.name for subplot in self]) - if self.mode == "grid": + if isinstance(self.layout, GridLayout): names = names.reshape(self.shape) names.flags.writeable = False return names - def __getitem__(self, index: str | int | tuple[int, int]) -> Subplot: - if isinstance(index, str): - for subplot in self._subplots.ravel(): - if subplot.name == index: - return subplot - raise IndexError(f"no subplot with given name: {index}") - - if self.mode == "grid": - return self._subplots[index[0], index[1]] - - return self._subplots[index] - def _render(self, draw=True): + # draw the underlay planes + self.renderer.render(self._underlay_scene, self._underlay_camera, flush=False) + # call the animation functions before render self._call_animate_functions(self._animate_funcs_pre) for subplot in self: @@ -458,6 +487,10 @@ def _render(self, draw=True): # call post-render animate functions self._call_animate_functions(self._animate_funcs_post) + if draw: + # needs to be here else events don't get processed + self.canvas.request_draw() + def _start_render(self): """start render cycle""" self.canvas.request_draw(self._render) @@ -538,7 +571,7 @@ def show( elif self.canvas.__class__.__name__ == "OffscreenRenderCanvas": # for test and docs gallery screenshots - self._set_viewport_rects() + self._fpl_reset_layout() for subplot in self: subplot.axes.update_using_camera() @@ -709,176 +742,92 @@ def export(self, uri: str | Path | bytes, **kwargs): def open_popup(self, *args, **kwargs): warn("popups only supported by ImguiFigure") - def _fpl_set_subplot_viewport_rect(self, subplot: Subplot): - """ - Sets the viewport rect for the given subplot - """ - - if self.mode == "grid": - # row, col position of this subplot within the grid - row_ix, col_ix = self._subplot_grid_positions[subplot] - - # number of rows, cols in the grid - nrows, ncols = self.shape - - # get starting positions and dimensions for the pygfx portion of the canvas - # anything outside the pygfx portion of the canvas is for imgui - x0_canvas, y0_canvas, width_canvas, height_canvas = ( - self.get_pygfx_render_area() - ) - - # width of an individual subplot - width_subplot = width_canvas / ncols - # height of an individual subplot - height_subplot = height_canvas / nrows - - # x position of this subplot - x_pos = ( - ((col_ix - 1) * width_subplot) - + width_subplot - + x0_canvas - + self.spacing - ) - # y position of this subplot - y_pos = ( - ((row_ix - 1) * height_subplot) - + height_subplot - + y0_canvas - + self.spacing - ) - - if self.__class__.__name__ == "ImguiFigure" and subplot.toolbar: - # leave space for imgui toolbar - height_subplot -= IMGUI_TOOLBAR_HEIGHT - - # clip so that min (w, h) is always 1, otherwise JupyterRenderCanvas causes issues because it - # initializes with a width, height of (0, 0) - rect = np.array( - [ - x_pos, - y_pos, - width_subplot - self.spacing, - height_subplot - self.spacing, - ] - ).clip(min=[0, 0, 1, 1]) - - # adjust if a subplot dock is present - adjust = np.array( - [ - # add left dock size to x_pos - subplot.docks["left"].size, - # add top dock size to y_pos - subplot.docks["top"].size, - # remove left and right dock sizes from width - -subplot.docks["right"].size - subplot.docks["left"].size, - # remove top and bottom dock sizes from height - -subplot.docks["top"].size - subplot.docks["bottom"].size, - ] - ) - - subplot.viewport.rect = rect + adjust + def _fpl_reset_layout(self, *ev): + """set the viewport rects for all subplots, *ev argument is not used, exists because of renderer resize event""" + self.layout.canvas_resized(self.get_pygfx_render_area()) - def _fpl_set_subplot_dock_viewport_rect(self, subplot, position): - """ - Sets the viewport rect for the given subplot dock + def get_pygfx_render_area(self, *args) -> tuple[float, float, float, float]: """ + Get rect for the portion of the canvas that the pygfx renderer draws to, + i.e. non-imgui, part of canvas - dock = subplot.docks[position] + Returns + ------- + tuple[float, float, float, float] + x_pos, y_pos, width, height - if dock.size == 0: - dock.viewport.rect = None - return + """ - if self.mode == "grid": - # row, col position of this subplot within the grid - row_ix, col_ix = self._subplot_grid_positions[subplot] + width, height = self.canvas.get_logical_size() - # number of rows, cols in the grid - nrows, ncols = self.shape + return 0, 0, width, height - x0_canvas, y0_canvas, width_canvas, height_canvas = ( - self.get_pygfx_render_area() + def add_subplot( + self, + rect=None, + extent=None, + camera: str | pygfx.PerspectiveCamera = "2d", + controller: str | pygfx.Controller = None, + name: str = None, + ) -> Subplot: + if isinstance(self.layout, GridLayout): + raise NotImplementedError( + "`add_subplot()` is not implemented for Figures using a GridLayout" ) - # width of an individual subplot - width_subplot = width_canvas / ncols - # height of an individual subplot - height_subplot = height_canvas / nrows - - # calculate the rect based on the dock position - match position: - case "right": - x_pos = ( - ((col_ix - 1) * width_subplot) + (width_subplot * 2) - dock.size - ) - y_pos = ( - ((row_ix - 1) * height_subplot) + height_subplot + self.spacing - ) - width_viewport = dock.size - height_viewport = height_subplot - self.spacing - - case "left": - x_pos = ((col_ix - 1) * width_subplot) + width_subplot - y_pos = ( - ((row_ix - 1) * height_subplot) + height_subplot + self.spacing - ) - width_viewport = dock.size - height_viewport = height_subplot - self.spacing - - case "top": - x_pos = ( - ((col_ix - 1) * width_subplot) + width_subplot + self.spacing - ) - y_pos = ( - ((row_ix - 1) * height_subplot) + height_subplot + self.spacing - ) - width_viewport = width_subplot - self.spacing - height_viewport = dock.size + raise NotImplementedError("Not yet implemented") + + camera = create_camera(camera) + controller = create_controller(controller, camera) + + subplot = Subplot( + parent=self, + camera=camera, + controller=controller, + canvas=self.canvas, + renderer=self.renderer, + name=name, + rect=rect, + extent=extent, # figure created extents for grid layout + resizeable=True, + ) - case "bottom": - x_pos = ( - ((col_ix - 1) * width_subplot) + width_subplot + self.spacing - ) - y_pos = ( - ((row_ix - 1) * height_subplot) - + (height_subplot * 2) - - dock.size - ) - width_viewport = width_subplot - self.spacing - height_viewport = dock.size + return subplot - case _: - raise ValueError("invalid position") + def remove_subplot(self, subplot: Subplot): + raise NotImplementedError("Not yet implemented") - dock.viewport.rect = [ - x_pos + x0_canvas, - y_pos + y0_canvas, - width_viewport, - height_viewport, - ] + if isinstance(self.layout, GridLayout): + raise NotImplementedError( + "`remove_subplot()` is not implemented for Figures using a GridLayout" + ) - def _set_viewport_rects(self, *ev): - """set the viewport rects for all subplots, *ev argument is not used, exists because of renderer resize event""" - for subplot in self: - self._fpl_set_subplot_viewport_rect(subplot) - for dock_pos in subplot.docks.keys(): - self._fpl_set_subplot_dock_viewport_rect(subplot, dock_pos) + if subplot not in self._subplots.tolist(): + raise KeyError(f"given subplot: {subplot} not found in the layout.") - def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: - """ - Fet rect for the portion of the canvas that the pygfx renderer draws to, - i.e. non-imgui, part of canvas + subplot.clear() + self._underlay_scene.remove(subplot.frame._world_object) + subplot.frame._world_object.clear() + self.layout._subplots = None + subplots = self._subplots.tolist() + subplots.remove(subplot) + self.layout.remove_subplot(subplot) + del subplot - Returns - ------- - tuple[int, int, int, int] - x_pos, y_pos, width, height + self._subplots = np.asarray(subplots) + self.layout._subplots = self._subplots.ravel() - """ + def __getitem__(self, index: str | int | tuple[int, int]) -> Subplot: + if isinstance(index, str): + for subplot in self._subplots.ravel(): + if subplot.name == index: + return subplot + raise IndexError(f"no subplot with given name: {index}") - width, height = self.canvas.get_logical_size() + if isinstance(self.layout, GridLayout): + return self._subplots[index[0], index[1]] - return 0, 0, width, height + return self._subplots[index] def __iter__(self): self._current_iter = iter(range(len(self))) @@ -890,19 +839,16 @@ def __next__(self) -> Subplot: def __len__(self): """number of subplots""" - if isinstance(self._shape, tuple): - return self.shape[0] * self.shape[1] - if isinstance(self._shape, list): - return len(self._shape) + return len(self._layout) def __str__(self): - return f"{self.__class__.__name__} @ {hex(id(self))}" + return f"{self.__class__.__name__}" def __repr__(self): newline = "\n\t" return ( - f"fastplotlib.{self.__class__.__name__} @ {hex(id(self))}\n" + f"fastplotlib.{self.__class__.__name__}" f" Subplots:\n" f"\t{newline.join(subplot.__str__() for subplot in self)}" f"\n" diff --git a/fastplotlib/layouts/_frame.py b/fastplotlib/layouts/_frame.py new file mode 100644 index 000000000..cd2a1cbc2 --- /dev/null +++ b/fastplotlib/layouts/_frame.py @@ -0,0 +1,371 @@ +import numpy as np +import pygfx + +from ._rect import RectManager +from ._utils import IMGUI_TOOLBAR_HEIGHT +from ..utils.types import SelectorColorStates +from ..graphics import TextGraphic + + +""" +Each Subplot is framed by a 2D plane mesh, a rectangle. +The rectangles are viewed using the UnderlayCamera where (0, 0) is the top left corner. +We can control the bbox of this rectangle by changing the x and y boundaries of the rectangle. + +Note how the y values of the plane mesh are negative, this is because of the UnderlayCamera. +We always just keep the positive y value, and make it negative only when setting the plane mesh. + +Illustration: + +(0, 0) --------------------------------------------------- +---------------------------------------------------------- +---------------------------------------------------------- +--------------(x0, -y0) --------------- (x1, -y0) -------- +------------------------|||||||||||||||------------------- +------------------------|||||||||||||||------------------- +------------------------|||||||||||||||------------------- +------------------------|||rectangle|||------------------- +------------------------|||||||||||||||------------------- +------------------------|||||||||||||||------------------- +------------------------|||||||||||||||------------------- +--------------(x0, -y1) --------------- (x1, -y1)--------- +---------------------------------------------------------- +------------------------------------------- (canvas_width, canvas_height) + +""" + + +# wgsl shader snippet for SDF function that defines the resize handler, a lower right triangle. +sdf_wgsl_resize_handle = """ +// hardcode square root of 2 +let m_sqrt_2 = 1.4142135; + +// given a distance from an origin point, this defines the hypotenuse of a lower right triangle +let distance = (-coord.x + coord.y) / m_sqrt_2; + +// return distance for this position +return distance * size; +""" + + +class MeshMasks: + """Used set the x0, x1, y0, y1 positions of the plane mesh""" + + x0 = np.array( + [ + [False, False, False], + [True, False, False], + [False, False, False], + [True, False, False], + ] + ) + + x1 = np.array( + [ + [True, False, False], + [False, False, False], + [True, False, False], + [False, False, False], + ] + ) + + y0 = np.array( + [ + [False, True, False], + [False, True, False], + [False, False, False], + [False, False, False], + ] + ) + + y1 = np.array( + [ + [False, False, False], + [False, False, False], + [False, True, False], + [False, True, False], + ] + ) + + +masks = MeshMasks + + +class Frame: + # resize handle color states + resize_handle_color = SelectorColorStates( + idle=(0.6, 0.6, 0.6, 1), # gray + highlight=(1, 1, 1, 1), # white + action=(1, 0, 1, 1), # magenta + ) + + # plane color states + plane_color = SelectorColorStates( + idle=(0.1, 0.1, 0.1), # dark grey + highlight=(0.2, 0.2, 0.2), # less dark grey + action=(0.1, 0.1, 0.2), # dark gray-blue + ) + + def __init__( + self, + viewport, + rect, + extent, + resizeable, + title, + docks, + toolbar_visible, + canvas_rect, + ): + """ + Manages the plane mesh, resize handle point, and subplot title. + It also sets the viewport rects for the subplot rect and the rects of the docks. + + Note: This is a backend class not meant to be user-facing. + + Parameters + ---------- + viewport: pygfx.Viewport + Subplot viewport + + rect: tuple | np.ndarray + rect of this subplot + + extent: tuple | np.ndarray + extent of this subplot + + resizeable: bool + if the Frame is resizeable or not + + title: str + subplot title + + docks: dict[str, PlotArea] + subplot dock + + toolbar_visible: bool + toolbar visibility + + canvas_rect: tuple + figure canvas rect, the render area excluding any areas taken by imgui edge windows + + """ + + self.viewport = viewport + self.docks = docks + self._toolbar_visible = toolbar_visible + + # create rect manager to handle all the backend rect calculations + if rect is not None: + self._rect_manager = RectManager(*rect, canvas_rect) + elif extent is not None: + self._rect_manager = RectManager.from_extent(extent, canvas_rect) + else: + raise ValueError("Must provide `rect` or `extent`") + + wobjects = list() + + # make title graphic + if title is None: + title_text = "" + else: + title_text = title + self._title_graphic = TextGraphic(title_text, font_size=16, face_color="white") + wobjects.append(self._title_graphic.world_object) + + # init mesh of size 1 to graphically represent rect + geometry = pygfx.plane_geometry(1, 1) + material = pygfx.MeshBasicMaterial(color=self.plane_color.idle, pick_write=True) + self._plane = pygfx.Mesh(geometry, material) + wobjects.append(self._plane) + + # otherwise text isn't visible + self._plane.world.z = 0.5 + + # create resize handler at point (x1, y1) + x1, y1 = self.extent[[1, 3]] + self._resize_handle = pygfx.Points( + # note negative y since y is inverted in UnderlayCamera + # subtract 7 so that the bottom right corner of the triangle is at the center + pygfx.Geometry(positions=[[x1 - 7, -y1 + 7, 0]]), + pygfx.PointsMarkerMaterial( + color=self.resize_handle_color.idle, + marker="custom", + custom_sdf=sdf_wgsl_resize_handle, + size=12, + size_space="screen", + pick_write=True, + ), + ) + + if not resizeable: + # set all color states to transparent if Frame isn't resizeable + c = (0, 0, 0, 0) + self._resize_handle.material.color = c + self._resize_handle.material.edge_width = 0 + self.resize_handle_color = SelectorColorStates(c, c, c) + + wobjects.append(self._resize_handle) + + self._world_object = pygfx.Group() + self._world_object.add(*wobjects) + + self._reset() + self.reset_viewport() + + @property + def rect_manager(self) -> RectManager: + return self._rect_manager + + @property + def extent(self) -> np.ndarray: + """extent, (xmin, xmax, ymin, ymax)""" + # not actually stored, computed when needed + return self._rect_manager.extent + + @extent.setter + def extent(self, extent): + self._rect_manager.extent = extent + self._reset() + self.reset_viewport() + + @property + def rect(self) -> np.ndarray[int]: + """rect in absolute screen space, (x, y, w, h)""" + return self._rect_manager.rect + + @rect.setter + def rect(self, rect: np.ndarray): + self._rect_manager.rect = rect + self._reset() + self.reset_viewport() + + def reset_viewport(self): + """reset the viewport rect for the subplot and docks""" + + # get rect of the render area + x, y, w, h = self.get_render_rect() + + # dock sizes + s_left = self.docks["left"].size + s_top = self.docks["top"].size + s_right = self.docks["right"].size + s_bottom = self.docks["bottom"].size + + # top and bottom have same width + # subtract left and right dock sizes + w_top_bottom = w - s_left - s_right + # top and bottom have same x pos + x_top_bottom = x + s_left + + # set dock rects + self.docks["left"].viewport.rect = x, y, s_left, h + self.docks["top"].viewport.rect = x_top_bottom, y, w_top_bottom, s_top + self.docks["bottom"].viewport.rect = ( + x_top_bottom, + y + h - s_bottom, + w_top_bottom, + s_bottom, + ) + self.docks["right"].viewport.rect = x + w - s_right, y, s_right, h + + # calc subplot rect by adjusting for dock sizes + x += s_left + y += s_top + w -= s_left + s_right + h -= s_top + s_bottom + + # set subplot rect + self.viewport.rect = x, y, w, h + + def get_render_rect(self) -> tuple[float, float, float, float]: + """ + Get the actual render area of the subplot, including the docks. + + Excludes area taken by the subplot title and toolbar. Also adds a small amount of spacing around the subplot. + """ + # the rect of the entire Frame + x, y, w, h = self.rect + + x += 1 # add 1 so a 1 pixel edge is visible + w -= 2 # subtract 2, so we get a 1 pixel edge on both sides + + # add 4 pixels above and below title for better spacing + y = y + 4 + self._title_graphic.font_size + 4 + + # spacing on the bottom if imgui toolbar is visible + if self.toolbar_visible: + toolbar_space = IMGUI_TOOLBAR_HEIGHT + resize_handle_space = 0 + else: + toolbar_space = 0 + # need some space for resize handler if imgui toolbar isn't present + resize_handle_space = 13 + + # adjust for the 4 pixels from the line above + # also give space for resize handler if imgui toolbar is not present + h = ( + h + - 4 + - self._title_graphic.font_size + - toolbar_space + - 4 + - resize_handle_space + ) + + return x, y, w, h + + def _reset(self): + """reset the plane mesh using the current rect state""" + + x0, x1, y0, y1 = self._rect_manager.extent + w = self._rect_manager.w + + self._plane.geometry.positions.data[masks.x0] = x0 + self._plane.geometry.positions.data[masks.x1] = x1 + + # negative y because UnderlayCamera y is inverted + self._plane.geometry.positions.data[masks.y0] = -y0 + self._plane.geometry.positions.data[masks.y1] = -y1 + + self._plane.geometry.positions.update_full() + + # note negative y since y is inverted in UnderlayCamera + # subtract 7 so that the bottom right corner of the triangle is at the center + self._resize_handle.geometry.positions.data[0] = [x1 - 7, -y1 + 7, 0] + self._resize_handle.geometry.positions.update_full() + + # set subplot title position + x = x0 + (w / 2) + y = y0 + (self._title_graphic.font_size / 2) + self._title_graphic.world_object.world.x = x + self._title_graphic.world_object.world.y = -y - 4 # add 4 pixels for spacing + + @property + def toolbar_visible(self) -> bool: + return self._toolbar_visible + + @toolbar_visible.setter + def toolbar_visible(self, visible: bool): + self._toolbar_visible = visible + self.reset_viewport() + + @property + def title_graphic(self) -> TextGraphic: + return self._title_graphic + + @property + def plane(self) -> pygfx.Mesh: + """the plane mesh""" + return self._plane + + @property + def resize_handle(self) -> pygfx.Points: + """resize handler point""" + return self._resize_handle + + def canvas_resized(self, canvas_rect): + """called by layout is resized""" + self._rect_manager.canvas_resized(canvas_rect) + self._reset() + self.reset_viewport() diff --git a/fastplotlib/layouts/_graphic_methods_mixin.py b/fastplotlib/layouts/_graphic_methods_mixin.py index a08e9b110..a04b681f5 100644 --- a/fastplotlib/layouts/_graphic_methods_mixin.py +++ b/fastplotlib/layouts/_graphic_methods_mixin.py @@ -9,9 +9,6 @@ class GraphicMethodsMixin: - def __init__(self): - pass - def _create_graphic(self, graphic_class, *args, **kwargs) -> Graphic: if "center" in kwargs.keys(): center = kwargs.pop("center") diff --git a/fastplotlib/layouts/_imgui_figure.py b/fastplotlib/layouts/_imgui_figure.py index 2e77f350d..f6d3da20f 100644 --- a/fastplotlib/layouts/_imgui_figure.py +++ b/fastplotlib/layouts/_imgui_figure.py @@ -20,7 +20,9 @@ class ImguiFigure(Figure): def __init__( self, - shape: list[tuple[int, int, int, int]] | tuple[int, int] = (1, 1), + shape: tuple[int, int] = (1, 1), + rects=None, + extents=None, cameras: ( Literal["2d", "3d"] | Iterable[Iterable[Literal["2d", "3d"]]] @@ -52,6 +54,8 @@ def __init__( super().__init__( shape=shape, + rects=rects, + extents=extents, cameras=cameras, controller_types=controller_types, controller_ids=controller_ids, @@ -109,6 +113,8 @@ def _render(self, draw=False): super()._render(draw) self.imgui_renderer.render() + + # needs to be here else events don't get processed self.canvas.request_draw() def _draw_imgui(self) -> imgui.ImDrawData: @@ -164,11 +170,11 @@ def add_gui(self, gui: EdgeWindow): self.guis[location] = gui - self._set_viewport_rects() + self._fpl_reset_layout() def get_pygfx_render_area(self, *args) -> tuple[int, int, int, int]: """ - Fet rect for the portion of the canvas that the pygfx renderer draws to, + Get rect for the portion of the canvas that the pygfx renderer draws to, i.e. non-imgui, part of canvas Returns diff --git a/fastplotlib/layouts/_plot_area.py b/fastplotlib/layouts/_plot_area.py index c4e6a9d70..2e69af100 100644 --- a/fastplotlib/layouts/_plot_area.py +++ b/fastplotlib/layouts/_plot_area.py @@ -11,6 +11,7 @@ from ._utils import create_controller from ..graphics._base import Graphic from ..graphics.selectors._base_selector import BaseSelector +from ._graphic_methods_mixin import GraphicMethodsMixin from ..legends import Legend @@ -24,7 +25,7 @@ IPYTHON = get_ipython() -class PlotArea: +class PlotArea(GraphicMethodsMixin): def __init__( self, parent: Union["PlotArea", "Figure"], @@ -712,7 +713,7 @@ def __str__(self): else: name = self.name - return f"{name}: {self.__class__.__name__} @ {hex(id(self))}" + return f"{name}: {self.__class__.__name__}" def __repr__(self): newline = "\n\t" diff --git a/fastplotlib/layouts/_rect.py b/fastplotlib/layouts/_rect.py new file mode 100644 index 000000000..aa84ee8a2 --- /dev/null +++ b/fastplotlib/layouts/_rect.py @@ -0,0 +1,239 @@ +import numpy as np + + +class RectManager: + """ + Backend management of a rect. Allows converting between rects and extents, also works with fractional inputs. + """ + + def __init__(self, x: float, y: float, w: float, h: float, canvas_rect: tuple): + # initialize rect state arrays + # used to store internal state of the rect in both fractional screen space and absolute screen space + # the purpose of storing the fractional rect is that it remains constant when the canvas resizes + self._rect_frac = np.zeros(4, dtype=np.float64) + self._rect_screen_space = np.zeros(4, dtype=np.float64) + self._canvas_rect = np.asarray(canvas_rect) + + self._set((x, y, w, h)) + + def _set(self, rect): + """ + Using the passed rect which is either absolute screen space or fractional, + set the internal fractional and absolute screen space rects + """ + rect = np.asarray(rect) + for val, name in zip(rect, ["x-position", "y-position", "width", "height"]): + if val < 0: + raise ValueError( + f"Invalid rect value < 0: {rect}\n All values must be non-negative." + ) + + if (rect[2:] <= 1).all(): # fractional bbox + self._set_from_fract(rect) + + elif (rect[2:] > 1).all(): # bbox in already in screen coords coordinates + self._set_from_screen_space(rect) + + else: + raise ValueError(f"Invalid rect: {rect}") + + def _set_from_fract(self, rect): + """set rect from fractional representation""" + _, _, cw, ch = self._canvas_rect + mult = np.array([cw, ch, cw, ch]) + + # check that widths, heights are valid: + if rect[0] + rect[2] > 1: + raise ValueError( + f"invalid fractional rect: {rect}\n x + width > 1: {rect[0]} + {rect[2]} > 1" + ) + if rect[1] + rect[3] > 1: + raise ValueError( + f"invalid fractional rect: {rect}\n y + height > 1: {rect[1]} + {rect[3]} > 1" + ) + + # assign values to the arrays, don't just change the reference + self._rect_frac[:] = rect + self._rect_screen_space[:] = self._rect_frac * mult + + def _set_from_screen_space(self, rect): + """set rect from screen space representation""" + _, _, cw, ch = self._canvas_rect + mult = np.array([cw, ch, cw, ch]) + # for screen coords allow (x, y) = 1 or 0, but w, h must be > 1 + # check that widths, heights are valid + if rect[0] + rect[2] > cw: + raise ValueError( + f"invalid rect: {rect}\n x + width > canvas width: {rect[0]} + {rect[2]} > {cw}" + ) + if rect[1] + rect[3] > ch: + raise ValueError( + f"invalid rect: {rect}\n y + height > canvas height: {rect[1]} + {rect[3]} >{ch}" + ) + + self._rect_frac[:] = rect / mult + self._rect_screen_space[:] = rect + + @property + def x(self) -> np.float64: + """x position""" + return self._rect_screen_space[0] + + @property + def y(self) -> np.float64: + """y position""" + return self._rect_screen_space[1] + + @property + def w(self) -> np.float64: + """width""" + return self._rect_screen_space[2] + + @property + def h(self) -> np.float64: + """height""" + return self._rect_screen_space[3] + + @property + def rect(self) -> np.ndarray: + """rect, (x, y, w, h)""" + return self._rect_screen_space + + @rect.setter + def rect(self, rect: np.ndarray | tuple): + self._set(rect) + + def canvas_resized(self, canvas_rect: tuple): + # called by Frame when canvas is resized + self._canvas_rect[:] = canvas_rect + # set new rect using existing rect_frac since this remains constant regardless of resize + self._set(self._rect_frac) + + @property + def x0(self) -> np.float64: + """x0 position""" + return self.x + + @property + def x1(self) -> np.float64: + """x1 position""" + return self.x + self.w + + @property + def y0(self) -> np.float64: + """y0 position""" + return self.y + + @property + def y1(self) -> np.float64: + """y1 position""" + return self.y + self.h + + @classmethod + def from_extent(cls, extent, canvas_rect): + """create a RectManager from an extent""" + rect = cls.extent_to_rect(extent, canvas_rect) + return cls(*rect, canvas_rect) + + @property + def extent(self) -> np.ndarray: + """extent, (xmin, xmax, ymin, ymax)""" + # not actually stored, computed when needed + return np.asarray([self.x0, self.x1, self.y0, self.y1]) + + @extent.setter + def extent(self, extent): + rect = RectManager.extent_to_rect(extent, canvas_rect=self._canvas_rect) + + self._set(rect) + + @staticmethod + def extent_to_rect(extent, canvas_rect): + """convert an extent to a rect""" + RectManager.validate_extent(extent, canvas_rect) + x0, x1, y0, y1 = extent + + # width and height + w = x1 - x0 + h = y1 - y0 + + return x0, y0, w, h + + @staticmethod + def validate_extent(extent: np.ndarray | tuple, canvas_rect: tuple): + extent = np.asarray(extent) + cx0, cy0, cw, ch = canvas_rect + + # make sure extent is valid + if (extent < 0).any(): + raise ValueError(f"extent must be non-negative, you have passed: {extent}") + + if extent[1] <= 1 or extent[3] <= 1: # if x1 <= 1, or y1 <= 1 + # if fractional rect, convert to full + if not (extent <= 1).all(): # if x1 and y1 <= 1, then all vals must be <= 1 + raise ValueError( + f"if passing a fractional extent, all values must be fractional, you have passed: {extent}" + ) + extent *= np.asarray([cw, cw, ch, ch]) + + x0, x1, y0, y1 = extent + + # width and height + w = x1 - x0 + h = y1 - y0 + + # check if x1 - x0 <= 0 + if w <= 0: + raise ValueError(f"extent x-range must be non-negative: {extent}") + + # check if y1 - y0 <= 0 + if h <= 0: + raise ValueError(f"extent y-range must be non-negative: {extent}") + + # calc canvas extent + cx1 = cx0 + cw + cy1 = cy0 + ch + canvas_extent = np.asarray([cx0, cx1, cy0, cy1]) + + if x0 < cx0 or x1 < cx0 or x0 > cx1 or x1 > cx1: + raise ValueError( + f"extent: {extent} x-range is beyond the bounds of the canvas: {canvas_extent}" + ) + if y0 < cy0 or y1 < cy0 or y0 > cy1 or y1 > cy1: + raise ValueError( + f"extent: {extent} y-range is beyond the bounds of the canvas: {canvas_extent}" + ) + + def is_above(self, y0, dist: int = 1) -> bool: + # our bottom < other top within given distance + return self.y1 < y0 + dist + + def is_below(self, y1, dist: int = 1) -> bool: + # our top > other bottom + return self.y0 > y1 - dist + + def is_left_of(self, x0, dist: int = 1) -> bool: + # our right_edge < other left_edge + # self.x1 < other.x0 + return self.x1 < x0 + dist + + def is_right_of(self, x1, dist: int = 1) -> bool: + # self.x0 > other.x1 + return self.x0 > x1 - dist + + def overlaps(self, extent: np.ndarray) -> bool: + """returns whether this rect overlaps with the given extent""" + x0, x1, y0, y1 = extent + return not any( + [ + self.is_above(y0), + self.is_below(y1), + self.is_left_of(x0), + self.is_right_of(x1), + ] + ) + + def __repr__(self): + s = f"{self._rect_frac}\n{self.rect}" + + return s diff --git a/fastplotlib/layouts/_subplot.py b/fastplotlib/layouts/_subplot.py index a97e89b0d..73f669fe5 100644 --- a/fastplotlib/layouts/_subplot.py +++ b/fastplotlib/layouts/_subplot.py @@ -1,29 +1,32 @@ from typing import Literal, Union -import pygfx +import numpy as np +import pygfx from rendercanvas import BaseRenderCanvas from ..graphics import TextGraphic from ._utils import create_camera, create_controller from ._plot_area import PlotArea -from ._graphic_methods_mixin import GraphicMethodsMixin +from ._frame import Frame from ..graphics._axes import Axes -class Subplot(PlotArea, GraphicMethodsMixin): +class Subplot(PlotArea): def __init__( self, parent: Union["Figure"], camera: Literal["2d", "3d"] | pygfx.PerspectiveCamera, - controller: pygfx.Controller, + controller: pygfx.Controller | str, canvas: BaseRenderCanvas | pygfx.Texture, + rect: np.ndarray = None, + extent: np.ndarray = None, + resizeable: bool = True, renderer: pygfx.WgpuRenderer = None, name: str = None, ): """ - General plot object is found within a ``Figure``. Each ``Figure`` instance will have [n rows, n columns] - of subplots. + Subplot class. .. important:: ``Subplot`` is not meant to be constructed directly, it only exists as part of a ``Figure`` @@ -33,9 +36,6 @@ def __init__( parent: 'Figure' | None parent Figure instance - position: (int, int), optional - corresponds to the [row, column] position of the subplot within a ``Figure`` - 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. @@ -56,19 +56,18 @@ def __init__( """ - super(GraphicMethodsMixin, self).__init__() - camera = create_camera(camera) controller = create_controller(controller_type=controller, camera=camera) self._docks = dict() - self._title_graphic: TextGraphic = None - - self._toolbar = True + if "Imgui" in parent.__class__.__name__: + toolbar_visible = True + else: + toolbar_visible = False - super(Subplot, self).__init__( + super().__init__( parent=parent, camera=camera, controller=controller, @@ -79,23 +78,33 @@ def __init__( ) for pos in ["left", "top", "right", "bottom"]: - dv = Dock(self, pos, size=0) + dv = Dock(self, size=0) dv.name = pos self.docks[pos] = dv self.children.append(dv) - if self.name is not None: - self.set_title(self.name) - self._axes = Axes(self) self.scene.add(self.axes.world_object) + self._frame = Frame( + viewport=self.viewport, + rect=rect, + extent=extent, + resizeable=resizeable, + title=name, + docks=self.docks, + toolbar_visible=toolbar_visible, + canvas_rect=parent.get_pygfx_render_area(), + ) + @property def axes(self) -> Axes: + """Axes object""" return self._axes @property def name(self) -> str: + """Subplot name""" return self._name @name.setter @@ -130,60 +139,40 @@ def docks(self) -> dict: @property def toolbar(self) -> bool: """show/hide toolbar""" - return self._toolbar + return self.frame.toolbar_visible @toolbar.setter def toolbar(self, visible: bool): - self._toolbar = bool(visible) - self.get_figure()._fpl_set_subplot_viewport_rect(self) + self.frame.toolbar_visible = visible + self.frame.reset_viewport() def _render(self): self.axes.update_using_camera() super()._render() - def set_title(self, text: str): - """Sets the plot title, stored as a ``TextGraphic`` in the "top" dock area""" - if text is None: - return + @property + def title(self) -> TextGraphic: + """subplot title""" + return self._frame.title_graphic + @title.setter + def title(self, text: str): text = str(text) - if self._title_graphic is not None: - self._title_graphic.text = text - else: - tg = TextGraphic(text=text, font_size=18) - self._title_graphic = tg - - self.docks["top"].size = 35 - self.docks["top"].add_graphic(tg) - - self.center_title() + self.title.text = text - def center_title(self): - """Centers name of subplot.""" - if self._title_graphic is None: - raise AttributeError("No title graphic is set") - - self._title_graphic.world_object.position = (0, 0, 0) - self.docks["top"].center_graphic(self._title_graphic, zoom=1.5) - self._title_graphic.world_object.position_y = -3.5 + @property + def frame(self) -> Frame: + """Frame that the subplot lives in""" + return self._frame class Dock(PlotArea): - _valid_positions = ["right", "left", "top", "bottom"] - def __init__( self, parent: Subplot, - position: str, size: int, ): - if position not in self._valid_positions: - raise ValueError( - f"the `position` of an AnchoredViewport must be one of: {self._valid_positions}" - ) - self._size = size - self._position = position super().__init__( parent=parent, @@ -194,10 +183,6 @@ def __init__( renderer=parent.renderer, ) - @property - def position(self) -> str: - return self._position - @property def size(self) -> int: """Get or set the size of this dock""" @@ -206,14 +191,7 @@ def size(self) -> int: @size.setter def size(self, s: int): self._size = s - if self.position == "top": - # TODO: treat title dock separately, do not allow user to change viewport stuff - return - - self.get_figure(self.parent)._fpl_set_subplot_viewport_rect(self.parent) - self.get_figure(self.parent)._fpl_set_subplot_dock_viewport_rect( - self.parent, self._position - ) + self.get_figure()._fpl_reset_layout() def _render(self): if self.size == 0: diff --git a/fastplotlib/layouts/_utils.py b/fastplotlib/layouts/_utils.py index b42971570..866c26aa3 100644 --- a/fastplotlib/layouts/_utils.py +++ b/fastplotlib/layouts/_utils.py @@ -1,10 +1,24 @@ import importlib +from itertools import product + +import numpy as np import pygfx from pygfx import WgpuRenderer, Texture, Renderer from ..utils.gui import BaseRenderCanvas, RenderCanvas +try: + import imgui_bundle +except ImportError: + IMGUI = False +else: + IMGUI = True + + +# number of pixels taken by the imgui toolbar when present +IMGUI_TOOLBAR_HEIGHT = 39 + def make_canvas_and_renderer( canvas: str | BaseRenderCanvas | Texture | None, @@ -92,3 +106,20 @@ def create_controller( ) return controller_types[controller_type](camera) + + +def get_extents_from_grid( + shape: tuple[int, int], +) -> list[tuple[float, float, float, float]]: + """create fractional extents from a given grid shape""" + x_min = np.arange(0, 1, (1 / shape[1])) + x_max = x_min + 1 / shape[1] + y_min = np.arange(0, 1, (1 / shape[0])) + y_max = y_min + 1 / shape[0] + + extents = list() + for row_ix, col_ix in product(range(shape[0]), range(shape[1])): + extent = x_min[col_ix], x_max[col_ix], y_min[row_ix], y_max[row_ix] + extents.append(extent) + + return extents diff --git a/fastplotlib/ui/_subplot_toolbar.py b/fastplotlib/ui/_subplot_toolbar.py index 7d183bf6d..a06e81b90 100644 --- a/fastplotlib/ui/_subplot_toolbar.py +++ b/fastplotlib/ui/_subplot_toolbar.py @@ -2,6 +2,7 @@ from ..layouts._subplot import Subplot from ._base import Window +from ..layouts._utils import IMGUI_TOOLBAR_HEIGHT class SubplotToolbar(Window): @@ -16,15 +17,18 @@ def __init__(self, subplot: Subplot, fa_icons: imgui.ImFont): def update(self): # get subplot rect - x, y, width, height = self._subplot.viewport.rect - y += self._subplot.docks["bottom"].size + x, y, width, height = self._subplot.frame.rect # place the toolbar window below the subplot - pos = (x, y + height) + pos = (x + 1, y + height - IMGUI_TOOLBAR_HEIGHT) - imgui.set_next_window_size((width, 0)) + imgui.set_next_window_size((width - 18, 0)) imgui.set_next_window_pos(pos) - flags = imgui.WindowFlags_.no_collapse | imgui.WindowFlags_.no_title_bar + flags = ( + imgui.WindowFlags_.no_collapse + | imgui.WindowFlags_.no_title_bar + | imgui.WindowFlags_.no_background + ) imgui.begin(f"Toolbar-{hex(id(self._subplot))}", p_open=None, flags=flags) diff --git a/fastplotlib/ui/right_click_menus/_standard_menu.py b/fastplotlib/ui/right_click_menus/_standard_menu.py index 772baa170..1937df858 100644 --- a/fastplotlib/ui/right_click_menus/_standard_menu.py +++ b/fastplotlib/ui/right_click_menus/_standard_menu.py @@ -55,7 +55,7 @@ def update(self): # open popup only if mouse was not moved between mouse_down and mouse_up events if self._last_right_click_pos == imgui.get_mouse_pos(): - if self.get_subplot(): + if self.get_subplot() is not False: # must explicitly check for False # open only if right click was inside a subplot imgui.open_popup(f"right-click-menu") @@ -64,7 +64,7 @@ def update(self): self.cleanup() if imgui.begin_popup(f"right-click-menu"): - if not self.get_subplot(): + if self.get_subplot() is False: # must explicitly check for False # for some reason it will still trigger at certain locations # despite open_popup() only being called when an actual # subplot is returned diff --git a/fastplotlib/utils/types.py b/fastplotlib/utils/types.py new file mode 100644 index 000000000..e99fce2fc --- /dev/null +++ b/fastplotlib/utils/types.py @@ -0,0 +1,4 @@ +from collections import namedtuple + + +SelectorColorStates = namedtuple("state", ["idle", "highlight", "action"]) diff --git a/scripts/generate_add_graphic_methods.py b/scripts/generate_add_graphic_methods.py index d69185521..533ae77c6 100644 --- a/scripts/generate_add_graphic_methods.py +++ b/scripts/generate_add_graphic_methods.py @@ -34,8 +34,6 @@ def generate_add_graphics_methods(): f.write("from ..graphics._base import Graphic\n\n") f.write("\nclass GraphicMethodsMixin:\n") - f.write(" def __init__(self):\n") - f.write(" pass\n\n") f.write( " def _create_graphic(self, graphic_class, *args, **kwargs) -> Graphic:\n" diff --git a/tests/test_text_graphic.py b/tests/test_text_graphic.py index a13dfe690..deb25ca6b 100644 --- a/tests/test_text_graphic.py +++ b/tests/test_text_graphic.py @@ -25,7 +25,7 @@ def test_create_graphic(): assert text.font_size == 14 assert isinstance(text._font_size, FontSize) - assert text.world_object.geometry.font_size == 14 + assert text.world_object.font_size == 14 assert text.face_color == pygfx.Color("w") assert isinstance(text._face_color, TextFaceColor) @@ -82,7 +82,7 @@ def test_text_changes_events(): text.font_size = 10.0 assert text.font_size == 10.0 - assert text.world_object.geometry.font_size == 10 + assert text.world_object.font_size == 10 check_event(text, "font_size", 10) text.face_color = "r" 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