anyplotlib 0.3.0b1__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/.github/workflows/prepare_release.yml +42 -11
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/CHANGELOG.rst +60 -0
- anyplotlib-0.4.0/Examples/Interactive/plot_figure_annotations.py +87 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/plot_voxel_grain_explorer.py +5 -2
- anyplotlib-0.4.0/Examples/Markers/plot_transform_markers.py +49 -0
- anyplotlib-0.4.0/Examples/PlotTypes/plot_gpu_voxels.py +82 -0
- anyplotlib-0.4.0/Examples/PlotTypes/plot_step_and_log.py +43 -0
- anyplotlib-0.4.0/Examples/PlotTypes/plot_tile_backend.py +184 -0
- anyplotlib-0.4.0/Examples/PlotTypes/plot_twinx.py +45 -0
- anyplotlib-0.4.0/Examples/Widgets/plot_widget1d_point.py +40 -0
- anyplotlib-0.4.0/Examples/Widgets/plot_widget2d_arrow.py +41 -0
- anyplotlib-0.4.0/Examples/Widgets/plot_widget3d_plane.py +47 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/PKG-INFO +1 -1
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/FIGURE_ESM.md +27 -11
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/__init__.py +4 -4
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/_base_plot.py +69 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/_utils.py +38 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/axes/_axes.py +2 -2
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/axes/_inset_axes.py +163 -5
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/callbacks.py +26 -2
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/figure/_figure.py +29 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/figure_esm.js +596 -46
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/markers.py +3 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/plot1d/_plot1d.py +216 -18
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/plot2d/_layer.py +18 -5
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/plot2d/_plot2d.py +48 -14
- anyplotlib-0.4.0/anyplotlib/tests/baselines/plot1d_twinx.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_embed/test_export_png.py +131 -51
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_callbacks_playwright.py +100 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_callbacks_unit.py +72 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_widgets.py +196 -1
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_labels/test_label_api.py +46 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/test_inset.py +153 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/test_inset_callout.py +371 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/test_visual.py +20 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_markers/test_markers.py +77 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot1d/test_plot1d.py +158 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_layers.py +94 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_layers_playwright.py +107 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot3d/test_gpu_fallback.py +1 -2
- anyplotlib-0.4.0/anyplotlib/tests/test_plot3d/test_gpu_hidden_reveal.py +330 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/widgets/_widgets1d.py +21 -8
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/widgets/_widgets2d.py +23 -6
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/_root/index.html +3 -3
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/_root/switcher.json +10 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/api/figure_plots.rst +7 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/api/index.rst +3 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/conf.py +10 -1
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/pyproject.toml +1 -1
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/uv.lock +1 -1
- anyplotlib-0.3.0b1/LARGE_IMAGE_WEBGPU_PLAN.md +0 -223
- anyplotlib-0.3.0b1/ORIX_BACKEND_PLAN.md +0 -133
- anyplotlib-0.3.0b1/RELEASE_NOTES.md +0 -94
- anyplotlib-0.3.0b1/RELEASE_PLAN.md +0 -104
- anyplotlib-0.3.0b1/WEBGPU_PLAN.md +0 -234
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/.github/workflows/ci.yml +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/.github/workflows/docs.yml +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/.github/workflows/release.yml +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/.github/workflows/tests.yml +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/.gitignore +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/AGENTS.md +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Benchmarks/README.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Benchmarks/plot_benchmark_comparison.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/README.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/plot_3d_spectral_viewer.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/plot_eels_explorer.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/plot_interactive_fft.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/plot_interactive_fitting.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/plot_ipf_density_map.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/plot_ipf_explorer.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/plot_key_bindings.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/plot_particle_picker.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/plot_point_widget.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/plot_segment_by_contrast.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/plot_segment_by_contrast_advanced.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/plot_spectra_roi_inspector.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Interactive/plot_threshold_explorer.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Markers/README.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Markers/plot_arrows.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Markers/plot_circles.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Markers/plot_ellipses.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Markers/plot_horizontal_lines.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Markers/plot_line_segments.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Markers/plot_points.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Markers/plot_polygons.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Markers/plot_rectangles.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Markers/plot_squares.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Markers/plot_texts.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Markers/plot_vertical_lines.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/PlotTypes/README.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_3d.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_bar.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_gridspec_custom.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_image2d.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_inset.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_label_formatting.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_line_styles.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_pcolormesh.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_spectra1d.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/README.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Widgets/README.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget1d_hline.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget1d_range.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget1d_vline.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget2d_annular.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget2d_circle.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget2d_crosshair.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget2d_label.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget2d_polygon.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget2d_rectangle.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/LICENSE +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/Makefile +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/README.md +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/_binary_frame.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/_electron.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/_repr_utils.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/axes/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/conftest.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/embed.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/figure/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/figure/_gridspec.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/figure/_subplots.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/plot1d/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/plot1d/_plotbar.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/plot2d/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/plot2d/_plotmesh.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/plot2d/_tile_backend.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/plot3d/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/plot3d/_plot3d.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/plotxy/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/plotxy/_plotxy.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/_directive.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/_repr_utils.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/_scraper.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/_wheel_builder.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/static/anywidget_bridge.js +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/static/anywidget_overlay.css +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/tests/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/tests/conftest.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/tests/test_directive.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/tests/test_init.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/tests/test_repr_utils.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/tests/test_scraper.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/tests/test_wheel_builder.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/_gpu_clim_check.cjs +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/_png_utils.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/bar_basic.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/gridspec_3col_equal_spectra.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/gridspec_asymmetric_width_ratios.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/gridspec_height_ratio_image_histogram.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/gridspec_image_two_spectra.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/gridspec_side_by_side_1d.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/gridspec_spanning_top_two_bottom.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/imshow_axis_off.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/imshow_checkerboard.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/imshow_gradient.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/imshow_labels.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/imshow_viridis.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/inset_1d.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/inset_maximized.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/inset_minimized.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/inset_normal_2d.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/inset_stacked.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/inset_stacked_one_minimized.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/pcolormesh_uniform.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_all_linestyles.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_alpha.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_axis_off.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_dashed.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_marker_symbols.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_markers.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_multi.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_sine.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_title.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot3d_surface.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/subplots_2x1.png +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/conftest.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_benchmarks/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_benchmarks/baselines.json +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_benchmarks/benchmarks/baselines.json +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_benchmarks/test_benchmarks.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_benchmarks/test_benchmarks_py.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_documentation/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_documentation/test_bridge.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_documentation/test_push_hook.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_documentation/test_scraper.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_electron/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_electron/test_binary_frame.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_embed/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_embed/test_embed_api.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_embed/test_embed_mount.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_examples/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_examples/test_interactive_examples.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/_event_test_utils.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_blit_audit.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_edit_chrome.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_edit_chrome_playwright.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_event_pause_hold.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_event_plots.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_event_settled.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_events_regression.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_tile_parity_playwright.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_title.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_touch.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_labels/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_labels/test_label_rendering.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_labels/test_no_clipping.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/test_batch.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/test_geom_channel.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/test_gridspec.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/test_interaction.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_markers/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_markers/test_marker_transforms.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot1d/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot1d/test_plotbar.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_detail_tile.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_first_paint_race.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_gpu_image.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_gpu_parity_playwright.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_imshow.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_imshow_rgb.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_pcolormesh.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_plot2d_api.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_rapid_frame_update.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_tile_backend.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_tiled_imshow.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_zoom_view_write.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot3d/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot3d/test_colors_highlight.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot3d/test_plot3d.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot3d/test_voxels_planes.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/tests/test_plotxy/test_plotxy.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/widgets/__init__.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/widgets/_base.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/anyplotlib/widgets/_widgets3d.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/_sg_html_scraper.py +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/_static/anyplotlib.svg +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/_static/custom.css +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/_static/pyodide_bridge.js +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/_templates/autosummary/class.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/api/callbacks.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/api/figure.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/api/markers.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/api/widgets.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/benchmarking.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/dev/index.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/embedding.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/events.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/getting_started.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/docs/index.rst +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/make.bat +0 -0
- {anyplotlib-0.3.0b1 → anyplotlib-0.4.0}/upcoming_changes/README.rst +0 -0
|
@@ -11,12 +11,13 @@ on:
|
|
|
11
11
|
required: true
|
|
12
12
|
type: choice
|
|
13
13
|
options:
|
|
14
|
+
- finalize # drop the bN suffix: release the current beta's base as stable (0.3.0b2 -> 0.3.0)
|
|
14
15
|
- minor
|
|
15
16
|
- bugfix
|
|
16
17
|
- major
|
|
17
18
|
- pre-release # increments the bN counter on the current base version
|
|
18
19
|
beta:
|
|
19
|
-
description: "Mark as beta pre-release (adds bN suffix
|
|
20
|
+
description: "Mark as beta pre-release (adds bN suffix). Ignored for 'pre-release' (always beta) and 'finalize' (always stable)."
|
|
20
21
|
required: false
|
|
21
22
|
type: boolean
|
|
22
23
|
default: false
|
|
@@ -67,17 +68,42 @@ jobs:
|
|
|
67
68
|
minor = int(m.group(2))
|
|
68
69
|
patch = int(m.group(3))
|
|
69
70
|
beta_n = int(m.group(4)) if m.group(4) else None
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
on_beta = beta_n is not None
|
|
72
|
+
|
|
73
|
+
if bump == "finalize":
|
|
74
|
+
# Release the current beta's base as stable: just drop the bN
|
|
75
|
+
# suffix, keep major.minor.patch. e.g. 0.3.0b2 -> 0.3.0.
|
|
76
|
+
if not on_beta:
|
|
77
|
+
raise SystemExit(
|
|
78
|
+
f"'finalize' requires a beta base version, but current "
|
|
79
|
+
f"version {current!r} has no bN suffix. Use minor / bugfix "
|
|
80
|
+
f"/ major to start a new release instead."
|
|
81
|
+
)
|
|
82
|
+
is_beta = False
|
|
83
|
+
elif bump == "pre-release":
|
|
84
|
+
# Keep the same base; just walk the beta counter forward.
|
|
85
|
+
is_beta = True
|
|
86
|
+
beta_n = (beta_n or 0) + 1
|
|
87
|
+
elif on_beta:
|
|
88
|
+
# We are on a beta of the NEXT release (e.g. 0.3.0b2). The base
|
|
89
|
+
# major.minor.patch is that upcoming version, so minor/bugfix/major
|
|
90
|
+
# must bump relative to the LAST STABLE (base - the in-progress
|
|
91
|
+
# component), not skip a whole version. The common intent from a
|
|
92
|
+
# beta is 'finalize', so steer the user there rather than guess.
|
|
93
|
+
raise SystemExit(
|
|
94
|
+
f"Current version {current!r} is a beta of the upcoming "
|
|
95
|
+
f"{major}.{minor}.{patch} release. To ship it, use bump="
|
|
96
|
+
f"'finalize' (-> {major}.{minor}.{patch}). A '{bump}' bump from "
|
|
97
|
+
f"a beta would skip {major}.{minor}.{patch} entirely "
|
|
98
|
+
f"(e.g. -> {'%d.%d.0' % (major, minor + 1) if bump == 'minor' else '...'}); "
|
|
99
|
+
f"that is almost never intended."
|
|
100
|
+
)
|
|
101
|
+
elif bump == "major":
|
|
72
102
|
major, minor, patch = major + 1, 0, 0
|
|
73
103
|
elif bump == "minor":
|
|
74
104
|
minor, patch = minor + 1, 0
|
|
75
105
|
elif bump == "bugfix":
|
|
76
106
|
patch += 1
|
|
77
|
-
elif bump == "pre-release":
|
|
78
|
-
# Keep the same base; just walk the beta counter forward.
|
|
79
|
-
is_beta = True
|
|
80
|
-
beta_n = (beta_n or 0) + 1
|
|
81
107
|
|
|
82
108
|
if is_beta:
|
|
83
109
|
if bump != "pre-release":
|
|
@@ -88,10 +114,15 @@ jobs:
|
|
|
88
114
|
PYEOF
|
|
89
115
|
)
|
|
90
116
|
|
|
117
|
+
# Derive is_beta from the COMPUTED version (ends in bN?), not the raw
|
|
118
|
+
# input — so 'finalize' is always treated as stable and a mismatched
|
|
119
|
+
# beta checkbox can't mislabel the switcher / skip the root redirect.
|
|
120
|
+
if [[ "$NEW_VERSION" =~ b[0-9]+$ ]]; then IS_BETA_OUT=true; else IS_BETA_OUT=false; fi
|
|
121
|
+
|
|
91
122
|
echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
|
|
92
123
|
echo "tag=v$NEW_VERSION" >> "$GITHUB_OUTPUT"
|
|
93
124
|
echo "branch=release/v$NEW_VERSION" >> "$GITHUB_OUTPUT"
|
|
94
|
-
echo "is_beta=$
|
|
125
|
+
echo "is_beta=$IS_BETA_OUT" >> "$GITHUB_OUTPUT"
|
|
95
126
|
echo "Bumping (${{ inputs.bump }}): $CURRENT → $NEW_VERSION"
|
|
96
127
|
|
|
97
128
|
# ── Bump version strings ─────────────────────────────────────────────
|
|
@@ -118,7 +149,7 @@ jobs:
|
|
|
118
149
|
- name: Update docs/switcher.json
|
|
119
150
|
env:
|
|
120
151
|
VERSION_TAG: ${{ steps.version.outputs.tag }}
|
|
121
|
-
IS_BETA: ${{
|
|
152
|
+
IS_BETA: ${{ steps.version.outputs.is_beta }}
|
|
122
153
|
shell: python
|
|
123
154
|
run: |
|
|
124
155
|
import json, re, pathlib, os
|
|
@@ -144,7 +175,7 @@ jobs:
|
|
|
144
175
|
|
|
145
176
|
# ── Update root redirect for stable releases ─────────────────────────
|
|
146
177
|
- name: Update root redirect (stable releases only)
|
|
147
|
-
if: ${{
|
|
178
|
+
if: ${{ steps.version.outputs.is_beta == 'false' && inputs.bump != 'pre-release' }}
|
|
148
179
|
env:
|
|
149
180
|
VERSION_TAG: ${{ steps.version.outputs.tag }}
|
|
150
181
|
shell: python
|
|
@@ -200,7 +231,7 @@ jobs:
|
|
|
200
231
|
- Version bumped to \`${TAG}\` in \`pyproject.toml\` and \`docs/conf.py\`
|
|
201
232
|
- \`CHANGELOG.rst\` updated from towncrier fragments
|
|
202
233
|
- \`docs/_root/switcher.json\` updated with the new version entry
|
|
203
|
-
$([ '${{
|
|
234
|
+
$([ '${{ steps.version.outputs.is_beta }}' = 'false' ] && echo '- Root redirect updated to point to this release' || echo '')
|
|
204
235
|
|
|
205
236
|
### Review checklist
|
|
206
237
|
- [ ] \`CHANGELOG.rst\` reads well — edit the fragment text directly if needed
|
|
@@ -10,6 +10,66 @@ Fragment files in ``upcoming_changes/`` are assembled into this file by
|
|
|
10
10
|
|
|
11
11
|
.. towncrier release notes start
|
|
12
12
|
|
|
13
|
+
0.4.0 (2026-07-18)
|
|
14
|
+
==================
|
|
15
|
+
|
|
16
|
+
New Features
|
|
17
|
+
------------
|
|
18
|
+
|
|
19
|
+
- Added :meth:`~anyplotlib.axes.InsetAxes.indicate_point` — the point sibling of
|
|
20
|
+
:meth:`~anyplotlib.axes.InsetAxes.indicate_region`: a circle-and-cross marker
|
|
21
|
+
at a data point of the parent plot plus a single leader line to the inset's
|
|
22
|
+
nearest corner, tracking zoom/pan and hiding the leader while minimized.
|
|
23
|
+
- Added :meth:`~anyplotlib.plot1d.Plot1D.set_legend_fontsize` to control the
|
|
24
|
+
legend text size on 1-D line plots.
|
|
25
|
+
- Added ``linewidth=`` to every overlay widget constructor and ``add_*_widget``
|
|
26
|
+
factory on :class:`~anyplotlib.plot2d.Plot2D` and
|
|
27
|
+
:class:`~anyplotlib.plot1d.Plot1D` (rectangle, circle, annular, crosshair,
|
|
28
|
+
polygon, vline, hline, range, point) — stroke width in px, default 2, stored
|
|
29
|
+
and round-tripped like ``color``.
|
|
30
|
+
- Added ``tint=`` to :meth:`~anyplotlib.plot2d.Plot2D.add_layer` and
|
|
31
|
+
:meth:`~anyplotlib.plot2d.Layer.set` — a ``#rgb``/``#rrggbb`` hex colour that
|
|
32
|
+
renders the layer as a clear→colour intensity ramp (transparent at low
|
|
33
|
+
intensity, opaque tint at high, via a 256×4 RGBA LUT) instead of a named
|
|
34
|
+
colormap; passing ``cmap=`` reverts a tinted layer to colormap display.
|
|
35
|
+
- An :meth:`~anyplotlib.figure.Figure.add_inset` with no title (the default,
|
|
36
|
+
``title=""``) now renders with NO title-bar strip at all — a clean bordered
|
|
37
|
+
plot box, content filling the whole area, instead of a useless empty header.
|
|
38
|
+
A titled inset is unchanged: its bar renders as before, with click-to-toggle
|
|
39
|
+
minimize. A title-less inset has no minimize affordance (there is no bar to
|
|
40
|
+
click), but drag-to-move / drag-to-resize in edit mode still work exactly as
|
|
41
|
+
before, since those gestures are wired on the inset body, not the bar.
|
|
42
|
+
- Double-clicking a plot's text chrome now reports which element was hit. The
|
|
43
|
+
``double_click`` :class:`~anyplotlib.callbacks.Event` gains a ``target`` field
|
|
44
|
+
naming the hit element — one of ``'title'``, ``'x_label'``, ``'x_ticks'``,
|
|
45
|
+
``'y_label'``, ``'y_ticks'``, ``'colorbar_label'`` or ``'legend'`` — so a host
|
|
46
|
+
can open the right edit affordance for the axis label vs the ticks vs the title
|
|
47
|
+
vs the colorbar label vs the legend. The axis gutters, colorbar strip and title
|
|
48
|
+
band each get their own hit-test (2-D panels emit from the separate axis/title
|
|
49
|
+
canvases; 1-D panels zone-split the single canvas around the plot rect and
|
|
50
|
+
legend box). A plain plot-area double-click is unchanged and carries no
|
|
51
|
+
``target`` (``event.target is None``), so existing handlers keep working.
|
|
52
|
+
- Insets can now be dragged and resized directly in the renderer's edit mode
|
|
53
|
+
(``edit_chrome``): drag the body to move an inset (a corner-stacked inset
|
|
54
|
+
converts to a free anchor and its siblings re-stack), or drag the bottom-right
|
|
55
|
+
grip to resize it (min 64 px per dimension). On release the renderer emits a
|
|
56
|
+
new figure-level ``inset_geometry_change`` event carrying the final
|
|
57
|
+
``anchor``/``w_frac``/``h_frac`` (figure fractions), which
|
|
58
|
+
:meth:`~anyplotlib.figure.Figure.add_event_handler` handlers can observe to
|
|
59
|
+
persist the layout. The same geometry is applied programmatically via the new
|
|
60
|
+
:meth:`~anyplotlib.axes.InsetAxes.set_geometry` (``anchor``, ``w_frac``,
|
|
61
|
+
``h_frac``). Off edit mode the affordances are hidden and the inset is inert.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
Bug Fixes
|
|
65
|
+
---------
|
|
66
|
+
|
|
67
|
+
- Fixed ``exportPNG`` compositing WebGPU-rendered 3-D panels (``scatter3d`` /
|
|
68
|
+
``voxels``) as blank background rectangles — the 3-D render pass is now
|
|
69
|
+
re-rendered synchronously in-task before the canvas readback, exactly like
|
|
70
|
+
active-GPU 2-D image panels.
|
|
71
|
+
|
|
72
|
+
|
|
13
73
|
0.3.0b1 (2026-07-13)
|
|
14
74
|
====================
|
|
15
75
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interactive figure annotations — double-click to add, drag to place
|
|
3
|
+
===================================================================
|
|
4
|
+
|
|
5
|
+
anyplotlib has a *figure-level* annotation layer that floats above the panels,
|
|
6
|
+
positioned in **figure fractions** (0…1, origin top-left) rather than any
|
|
7
|
+
panel's data coordinates. Set it with
|
|
8
|
+
:meth:`~anyplotlib.Figure.set_figure_markers`; the supported kinds are
|
|
9
|
+
``"text"``, ``"circle"``, ``"rect"`` and ``"arrow"``.
|
|
10
|
+
|
|
11
|
+
Turn on ``fig.edit_chrome`` and every annotation becomes draggable in the
|
|
12
|
+
browser. Combine that with a ``double_click`` handler and you get a simple
|
|
13
|
+
annotate-by-clicking workflow: double-click a feature to drop a labelled
|
|
14
|
+
arrow, then drag it to line it up. Positions round-trip back to Python via
|
|
15
|
+
:attr:`~anyplotlib.Figure.figure_markers`.
|
|
16
|
+
"""
|
|
17
|
+
import numpy as np
|
|
18
|
+
import anyplotlib as apl
|
|
19
|
+
|
|
20
|
+
rng = np.random.default_rng(7)
|
|
21
|
+
data = rng.standard_normal((160, 160)).cumsum(0).cumsum(1)
|
|
22
|
+
data = (data - data.min()) / (data.max() - data.min())
|
|
23
|
+
|
|
24
|
+
fig, ax = apl.subplots(1, 1, figsize=(520, 520))
|
|
25
|
+
v = ax.imshow(data, cmap="magma", units="px")
|
|
26
|
+
|
|
27
|
+
# Enable the editable-annotation ("report builder") mode so figure markers are
|
|
28
|
+
# hit-testable and draggable, and the figure emits background/marker events.
|
|
29
|
+
fig.edit_chrome = True
|
|
30
|
+
|
|
31
|
+
# %%
|
|
32
|
+
# Seed a couple of annotations
|
|
33
|
+
# ----------------------------
|
|
34
|
+
# Each marker is a dict with a ``kind`` and fraction-space geometry. A text
|
|
35
|
+
# label and an arrow pointing into the image to start with.
|
|
36
|
+
|
|
37
|
+
fig.set_figure_markers([
|
|
38
|
+
{"kind": "text", "x": 0.5, "y": 0.06,
|
|
39
|
+
"text": "Double-click a feature to annotate it",
|
|
40
|
+
"color": "#ffffff", "fontsize": 14},
|
|
41
|
+
{"kind": "arrow", "x": 0.20, "y": 0.30, "u": 0.12, "v": 0.12,
|
|
42
|
+
"color": "#ffd54f", "linewidth": 2},
|
|
43
|
+
])
|
|
44
|
+
|
|
45
|
+
fig
|
|
46
|
+
|
|
47
|
+
# %%
|
|
48
|
+
# Double-click to drop a new annotation
|
|
49
|
+
# -------------------------------------
|
|
50
|
+
# On a single-panel figure the panel nearly fills the canvas, so we turn the
|
|
51
|
+
# click's device pixels into a figure fraction with the panel's
|
|
52
|
+
# ``display_width`` / ``display_height`` and append an arrow + label there.
|
|
53
|
+
# Because ``edit_chrome`` is on, the new marker is immediately draggable.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _on_double_click(event):
|
|
57
|
+
if event.x is None or event.display_width is None:
|
|
58
|
+
return
|
|
59
|
+
fx = float(np.clip(event.x / event.display_width, 0.02, 0.98))
|
|
60
|
+
fy = float(np.clip(event.y / event.display_height, 0.02, 0.98))
|
|
61
|
+
markers = fig.figure_markers # current list (a copy)
|
|
62
|
+
n = sum(1 for m in markers if m["kind"] == "text")
|
|
63
|
+
markers.append({"kind": "arrow", "x": fx, "y": fy,
|
|
64
|
+
"u": 0.08, "v": -0.08, "color": "#40c4ff", "linewidth": 2})
|
|
65
|
+
markers.append({"kind": "text", "x": fx + 0.08, "y": fy - 0.10,
|
|
66
|
+
"text": f"mark {n}", "color": "#40c4ff", "fontsize": 13})
|
|
67
|
+
fig.set_figure_markers(markers)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
v.add_event_handler(_on_double_click, "double_click")
|
|
71
|
+
|
|
72
|
+
fig.set_help(
|
|
73
|
+
"Double-click on the image to drop a labelled arrow.\n"
|
|
74
|
+
"Drag any annotation to reposition it (edit mode is on)."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
fig # Interactive
|
|
78
|
+
|
|
79
|
+
# %%
|
|
80
|
+
# Read the placements back
|
|
81
|
+
# ------------------------
|
|
82
|
+
# After the user drags things around, ``fig.figure_markers`` reflects the
|
|
83
|
+
# current fraction positions — persist them, export them, or feed them into a
|
|
84
|
+
# report.
|
|
85
|
+
|
|
86
|
+
for m in fig.figure_markers:
|
|
87
|
+
print(m["kind"], round(m["x"], 3), round(m["y"], 3))
|
|
@@ -29,7 +29,7 @@ import anyplotlib as apl
|
|
|
29
29
|
rng = np.random.default_rng(11)
|
|
30
30
|
|
|
31
31
|
# ── 1. Synthetic 3-D polycrystal: nearest-seed voxel grain map ──────────────
|
|
32
|
-
N =
|
|
32
|
+
N = 24 # volume is N³ voxels, indexed V[z, y, x]
|
|
33
33
|
N_GRAINS = 40
|
|
34
34
|
|
|
35
35
|
seeds = rng.uniform(0, N, size=(N_GRAINS, 3)) # (z, y, x)
|
|
@@ -76,7 +76,8 @@ grain_rgb_u8 = (rgb * 255).astype(np.uint8) # (N_GRAINS, 3)
|
|
|
76
76
|
# planes. This anchors the highlight exactly where the slices intersect,
|
|
77
77
|
# shows real slice contents in 3-D, and scales: the on-plane count is
|
|
78
78
|
# ~3·(N/step)² regardless of N, so it stays fast even for a 256³ volume.
|
|
79
|
-
VSTEP = max(1, N // 48) # in-plane downsample
|
|
79
|
+
VSTEP = max(1, N // 48) # in-plane downsample, capping at ~48² cubes/plane
|
|
80
|
+
# for large N (no downsampling at this N=24)
|
|
80
81
|
|
|
81
82
|
# Voxel cube size in data units. A touch larger than VSTEP so the three
|
|
82
83
|
# slabs read as solid sheets rather than a dotted grid.
|
|
@@ -140,6 +141,8 @@ v_vol = ax_vol.voxels(
|
|
|
140
141
|
size=VOXSIZE, alpha=0.55,
|
|
141
142
|
x_label="x", y_label="y", z_label="z",
|
|
142
143
|
bounds=((0, N - 1),) * 3, zoom=1.1,
|
|
144
|
+
# gpu="auto" (default): ~1.7k cubes is over the ~1k voxel threshold, so the
|
|
145
|
+
# WebGPU instanced path handles each drag re-slice when WebGPU is present.
|
|
143
146
|
)
|
|
144
147
|
v_vol.set_title("Grain volume — drag a plane to re-slice")
|
|
145
148
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Coordinate transforms — pin markers to the axes or the screen
|
|
3
|
+
=============================================================
|
|
4
|
+
|
|
5
|
+
Every ``add_*`` marker method takes a ``transform`` that decides which
|
|
6
|
+
coordinate system its positions live in:
|
|
7
|
+
|
|
8
|
+
- ``"data"`` (default) — data coordinates; the marker moves and scales with
|
|
9
|
+
zoom / pan, staying glued to the underlying data.
|
|
10
|
+
- ``"axes"`` — axes-normalised ``(0, 0)`` bottom-left … ``(1, 1)`` top-right;
|
|
11
|
+
the marker stays in the same corner of the panel no matter how you zoom.
|
|
12
|
+
- ``"display"`` — raw CSS pixels within the panel; a fixed-size decoration.
|
|
13
|
+
|
|
14
|
+
The ``"axes"`` and ``"display"`` transforms are how you build overlays that
|
|
15
|
+
should *not* track the data — a navigation index in the corner, a scale bar, a
|
|
16
|
+
persistent legend chip.
|
|
17
|
+
"""
|
|
18
|
+
import numpy as np
|
|
19
|
+
import anyplotlib as apl
|
|
20
|
+
|
|
21
|
+
rng = np.random.default_rng(1)
|
|
22
|
+
data = rng.standard_normal((128, 128)).cumsum(0).cumsum(1)
|
|
23
|
+
data = (data - data.min()) / (data.max() - data.min())
|
|
24
|
+
xy = np.linspace(0, 10, 128)
|
|
25
|
+
|
|
26
|
+
fig, ax = apl.subplots(1, 1, figsize=(480, 480))
|
|
27
|
+
v = ax.imshow(data, axes=[xy, xy], units="nm")
|
|
28
|
+
|
|
29
|
+
# Data-anchored label — sits at (5, 5) in nm and rides along on zoom/pan.
|
|
30
|
+
v.add_texts(offsets=[(5, 5)], texts=["feature @ (5, 5)"],
|
|
31
|
+
color="#ffffff", fontsize=12, name="data_label")
|
|
32
|
+
|
|
33
|
+
# Axes-anchored index — stays pinned to the top-left corner of the panel
|
|
34
|
+
# regardless of zoom (0, 1 = top-left in axes fractions).
|
|
35
|
+
v.add_texts(offsets=[(0.04, 0.96)], texts=["frame 3 / 20"],
|
|
36
|
+
transform="axes", color="#ffd54f", fontsize=13, name="nav_index")
|
|
37
|
+
|
|
38
|
+
fig
|
|
39
|
+
|
|
40
|
+
# %%
|
|
41
|
+
# Try it
|
|
42
|
+
# ------
|
|
43
|
+
# Zoom into the image: the white ``feature`` label moves with the data, while
|
|
44
|
+
# the yellow ``frame 3 / 20`` index stays locked to the corner — because it is
|
|
45
|
+
# positioned in ``"axes"`` coordinates.
|
|
46
|
+
|
|
47
|
+
fig.set_help("Zoom in: the corner index stays put; the data label moves.")
|
|
48
|
+
|
|
49
|
+
fig # Interactive
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GPU-accelerated voxels
|
|
3
|
+
======================
|
|
4
|
+
|
|
5
|
+
:meth:`~anyplotlib.Axes.voxels` renders shaded translucent cubes for a
|
|
6
|
+
volumetric field. With ``gpu="auto"`` (the default) anyplotlib uses WebGPU
|
|
7
|
+
instancing when a GPU is available and the cube count is large, so hundreds of
|
|
8
|
+
thousands of voxels stay interactive; otherwise it falls back to the Canvas2D
|
|
9
|
+
path. Read :attr:`~anyplotlib.Plot3D.gpu_active` after the first frame to see
|
|
10
|
+
which path was chosen.
|
|
11
|
+
|
|
12
|
+
Here we build a dense spherical shell — enough cubes that the WebGPU path
|
|
13
|
+
kicks in — and drop a draggable :class:`~anyplotlib.PlaneWidget` through it as
|
|
14
|
+
a slice selector.
|
|
15
|
+
"""
|
|
16
|
+
import numpy as np
|
|
17
|
+
import anyplotlib as apl
|
|
18
|
+
|
|
19
|
+
# %%
|
|
20
|
+
# Build a volumetric field
|
|
21
|
+
# ------------------------
|
|
22
|
+
# Voxel *centres* are passed as three flat coordinate arrays (not a dense 3-D
|
|
23
|
+
# grid), so you only send the cubes you actually want drawn. We keep the
|
|
24
|
+
# voxels inside a spherical shell and colour them by radius.
|
|
25
|
+
|
|
26
|
+
N = 64
|
|
27
|
+
g = np.arange(N)
|
|
28
|
+
Z, Y, X = np.meshgrid(g, g, g, indexing="ij")
|
|
29
|
+
r = np.sqrt((X - N / 2) ** 2 + (Y - N / 2) ** 2 + (Z - N / 2) ** 2)
|
|
30
|
+
shell = (r > N * 0.30) & (r < N * 0.42) # a hollow sphere
|
|
31
|
+
|
|
32
|
+
xs, ys, zs = X[shell], Y[shell], Z[shell]
|
|
33
|
+
print(f"{xs.size:,} voxels") # tens of thousands → GPU path under gpu='auto'
|
|
34
|
+
|
|
35
|
+
# Colour by radius with the viridis-ish default cycle mapped through intensity.
|
|
36
|
+
t = (r[shell] - r[shell].min()) / (np.ptp(r[shell]) + 1e-9)
|
|
37
|
+
colors = np.stack([0.2 + 0.8 * t, 0.4 * np.ones_like(t), 1.0 - 0.8 * t], axis=1)
|
|
38
|
+
|
|
39
|
+
fig, ax = apl.subplots(1, 1, figsize=(560, 520))
|
|
40
|
+
vol = ax.voxels(
|
|
41
|
+
xs, ys, zs, colors=colors,
|
|
42
|
+
size=1.0, alpha=0.35,
|
|
43
|
+
bounds=((0, N - 1),) * 3,
|
|
44
|
+
azimuth=-55, elevation=28, zoom=1.1,
|
|
45
|
+
gpu="auto", # WebGPU when available, Canvas2D otherwise
|
|
46
|
+
)
|
|
47
|
+
vol.set_title("Spherical shell — drag to rotate, scroll to zoom")
|
|
48
|
+
|
|
49
|
+
# %%
|
|
50
|
+
# Add a slice-selector plane
|
|
51
|
+
# --------------------------
|
|
52
|
+
# A :class:`~anyplotlib.PlaneWidget` is a draggable axis-aligned plane. Voxels
|
|
53
|
+
# lying on it render more opaque, so the current slice pops out of the
|
|
54
|
+
# translucent volume. Drag it along z in the browser, or move it from Python.
|
|
55
|
+
|
|
56
|
+
plane = vol.add_widget("plane", axis="z", position=N // 2,
|
|
57
|
+
color="#40c4ff", alpha=0.18)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@plane.add_event_handler("pointer_move")
|
|
61
|
+
def _on_slice(event):
|
|
62
|
+
# Fires while the plane is dragged; pw.position holds the live slice index.
|
|
63
|
+
print("slice at z =", round(plane.position, 1))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
fig.set_help(
|
|
67
|
+
"Drag: rotate · Scroll: zoom · R: reset view\n"
|
|
68
|
+
"Drag the blue plane to slide the z-slice through the shell."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
fig # Interactive
|
|
72
|
+
|
|
73
|
+
# %%
|
|
74
|
+
# Which render path ran?
|
|
75
|
+
# ----------------------
|
|
76
|
+
# ``gpu_active`` is populated once the browser reports back after the first
|
|
77
|
+
# frame. It is ``True`` when the WebGPU instanced path is live, ``False`` on
|
|
78
|
+
# the Canvas2D fallback, and ``None`` before the first frame (as when this
|
|
79
|
+
# gallery page is built headlessly). Force a path with ``gpu=True`` /
|
|
80
|
+
# ``gpu=False`` if you need determinism.
|
|
81
|
+
|
|
82
|
+
print("gpu_active:", vol.gpu_active)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Step lines and log-scale spectra
|
|
3
|
+
================================
|
|
4
|
+
|
|
5
|
+
Two 1-D options that suit spectral data: a **mid-riser step** line (constant
|
|
6
|
+
within each bin, jumping at bin midpoints) via ``linestyle="step-mid"``, and a
|
|
7
|
+
**logarithmic y-axis** via :meth:`~anyplotlib.Axes.semilogy` (or
|
|
8
|
+
``ax.plot(..., yscale="log")``).
|
|
9
|
+
"""
|
|
10
|
+
import numpy as np
|
|
11
|
+
import anyplotlib as apl
|
|
12
|
+
|
|
13
|
+
# A noisy binned spectrum.
|
|
14
|
+
rng = np.random.default_rng(0)
|
|
15
|
+
energy = np.linspace(0, 20, 60)
|
|
16
|
+
counts = (np.exp(-(energy - 6) ** 2 / 4) * 1000
|
|
17
|
+
+ np.exp(-(energy - 13) ** 2 / 8) * 400
|
|
18
|
+
+ rng.uniform(0, 20, energy.size))
|
|
19
|
+
|
|
20
|
+
# %%
|
|
21
|
+
# Step line
|
|
22
|
+
# ---------
|
|
23
|
+
# ``linestyle="step-mid"`` draws a horizontal segment centred on each x value
|
|
24
|
+
# with vertical risers between them — the standard way to show histogram-like
|
|
25
|
+
# spectra without implying interpolation between channels.
|
|
26
|
+
|
|
27
|
+
fig, ax = apl.subplots(1, 1, figsize=(560, 340))
|
|
28
|
+
ax.plot(counts, axes=[energy], color="#4fc3f7",
|
|
29
|
+
linestyle="step-mid", label="counts")
|
|
30
|
+
|
|
31
|
+
fig
|
|
32
|
+
|
|
33
|
+
# %%
|
|
34
|
+
# Log y-axis
|
|
35
|
+
# ----------
|
|
36
|
+
# ``semilogy`` is shorthand for a log y-scale, which brings out the small
|
|
37
|
+
# secondary peak that the linear plot flattens. Combine it with the step line
|
|
38
|
+
# for a classic spectroscopy view.
|
|
39
|
+
|
|
40
|
+
fig2, ax2 = apl.subplots(1, 1, figsize=(560, 340))
|
|
41
|
+
ax2.semilogy(counts, axes=[energy], color="#ff7043", linestyle="step-mid")
|
|
42
|
+
|
|
43
|
+
fig2
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom Tile Backend — pan & zoom a huge image you never hold in memory
|
|
3
|
+
======================================================================
|
|
4
|
+
|
|
5
|
+
For a very large image, anyplotlib does not need the whole array. It asks a
|
|
6
|
+
:class:`~anyplotlib.plot2d._tile_backend.TileBackend` for a downsampled
|
|
7
|
+
*overview* to use as the base texture, and then — on every zoom / pan — for a
|
|
8
|
+
crisp *detail tile* of just the visible region at the panel's resolution.
|
|
9
|
+
anyplotlib owns that zoom/pan → re-tile lifecycle; the backend owns the data.
|
|
10
|
+
|
|
11
|
+
A backend is any object implementing the ``TileBackend`` protocol:
|
|
12
|
+
|
|
13
|
+
``full_shape`` (H, W), ``dtype``, ``origin``, ``extent()``, and
|
|
14
|
+
``sample(x0, x1, y0, y1, out_w, out_h, method)`` — return the logical region
|
|
15
|
+
``[y0:y1, x0:x1]`` resampled to ``(out_h, out_w)``.
|
|
16
|
+
|
|
17
|
+
Because ``sample`` is called *on demand*, the source never has to exist in
|
|
18
|
+
full. Here we make a **procedural** backend that computes a Mandelbrot fractal
|
|
19
|
+
for whatever region is requested — a 65 536 × 65 536 "image" (4.3 gigapixels)
|
|
20
|
+
that is generated tile-by-tile as you explore it. The array is never
|
|
21
|
+
materialised; only the small tiles you look at are ever computed.
|
|
22
|
+
"""
|
|
23
|
+
import numpy as np
|
|
24
|
+
import anyplotlib as apl
|
|
25
|
+
|
|
26
|
+
# %%
|
|
27
|
+
# A procedural tile backend
|
|
28
|
+
# -------------------------
|
|
29
|
+
# The Mandelbrot escape count over a data-space window. The full logical
|
|
30
|
+
# image is ``SIZE x SIZE`` pixels mapping to the complex plane
|
|
31
|
+
# ``[-2.5, 1.0] x [-1.75, 1.75]``, but no pixel is computed until ``sample``
|
|
32
|
+
# is called for it.
|
|
33
|
+
|
|
34
|
+
SIZE = 65_536 # 65k x 65k logical pixels ≈ 4.3 gigapixels — never allocated
|
|
35
|
+
CX0, CX1 = -2.5, 1.0
|
|
36
|
+
CY0, CY1 = -1.75, 1.75
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class MandelbrotTileBackend:
|
|
40
|
+
"""A ``TileBackend`` that synthesises fractal tiles on demand."""
|
|
41
|
+
|
|
42
|
+
max_iter = 80
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def full_shape(self):
|
|
46
|
+
return (SIZE, SIZE)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def dtype(self):
|
|
50
|
+
return np.dtype("uint16") # escape counts
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def origin(self):
|
|
54
|
+
return "lower" # row 0 at the bottom (matches the y data-axis below)
|
|
55
|
+
|
|
56
|
+
def extent(self):
|
|
57
|
+
# Data-space (x0, x1, y0, y1) the image spans, or None for pixel
|
|
58
|
+
# coordinates. Return None here and set the coordinate axes explicitly
|
|
59
|
+
# on imshow (below) so the ticks read as real/imaginary values.
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
def sample(self, x0, x1, y0, y1, out_w, out_h, method="mean"):
|
|
63
|
+
# Only the requested window is ever evaluated, at exactly the output
|
|
64
|
+
# resolution the panel asked for — so a zoomed-in tile is as sharp as a
|
|
65
|
+
# zoomed-out overview is cheap. The escape loop is written for speed
|
|
66
|
+
# (it runs once per pan/zoom), using three tricks over the textbook
|
|
67
|
+
# version:
|
|
68
|
+
# * real / imag ``float32`` arithmetic instead of complex128 — half
|
|
69
|
+
# the memory traffic and no complex-multiply overhead;
|
|
70
|
+
# * the escape test is ``|z|² > 4`` (``zr² + zi²``), avoiding the
|
|
71
|
+
# ``sqrt`` in ``abs``;
|
|
72
|
+
# * no shrinking boolean mask. Instead we ``counts += inside`` every
|
|
73
|
+
# iteration (a cheap bool→uint add): each pixel's count is simply
|
|
74
|
+
# how many steps it stayed inside. Escaped pixels keep iterating to
|
|
75
|
+
# ``inf``, which is harmless — ``inf > 4`` just keeps them counted
|
|
76
|
+
# out. This is ~3× faster on the overview and ~8× faster on a
|
|
77
|
+
# deep-zoom tile (where nothing escapes early, so a mask never
|
|
78
|
+
# shrinks and the old code crawled).
|
|
79
|
+
cr = (CX0 + (np.linspace(x0, x1, out_w, dtype=np.float32) / SIZE)
|
|
80
|
+
* (CX1 - CX0))[np.newaxis, :]
|
|
81
|
+
ci = (CY0 + (np.linspace(y0, y1, out_h, dtype=np.float32) / SIZE)
|
|
82
|
+
* (CY1 - CY0))[:, np.newaxis]
|
|
83
|
+
cr = np.ascontiguousarray(np.broadcast_to(cr, (out_h, out_w)))
|
|
84
|
+
ci = np.ascontiguousarray(np.broadcast_to(ci, (out_h, out_w)))
|
|
85
|
+
|
|
86
|
+
zr = np.zeros((out_h, out_w), np.float32)
|
|
87
|
+
zi = np.zeros((out_h, out_w), np.float32)
|
|
88
|
+
counts = np.zeros((out_h, out_w), np.uint16)
|
|
89
|
+
zr2 = np.empty_like(zr)
|
|
90
|
+
zi2 = np.empty_like(zr)
|
|
91
|
+
tmp = np.empty_like(zr)
|
|
92
|
+
# Escaped pixels run off to ±inf; the inf−inf / inf·0 they produce is
|
|
93
|
+
# expected and never touches `counts` (an escaped pixel already fails
|
|
94
|
+
# the `<= 4` test), so silence the overflow/invalid warnings.
|
|
95
|
+
with np.errstate(over="ignore", invalid="ignore"):
|
|
96
|
+
for i in range(self.max_iter):
|
|
97
|
+
np.multiply(zr, zr, out=zr2)
|
|
98
|
+
np.multiply(zi, zi, out=zi2)
|
|
99
|
+
inside = (zr2 + zi2) <= 4.0
|
|
100
|
+
counts += inside # still-inside pixels tick up
|
|
101
|
+
# z ← z² + c (in real / imag form; the shared temporaries
|
|
102
|
+
# above keep this allocation-free inside the loop).
|
|
103
|
+
np.multiply(zr, zi, out=tmp)
|
|
104
|
+
tmp *= 2.0
|
|
105
|
+
tmp += ci # zi_next = 2·zr·zi + ci
|
|
106
|
+
np.subtract(zr2, zi2, out=zr)
|
|
107
|
+
zr += cr # zr_next = zr² − zi² + cr
|
|
108
|
+
zi, tmp = tmp, zi # swap in the new zi
|
|
109
|
+
if i % 16 == 15 and not inside.any():
|
|
110
|
+
break # whole tile escaped — done
|
|
111
|
+
return counts
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# %%
|
|
115
|
+
# Wire it into a plot
|
|
116
|
+
# -------------------
|
|
117
|
+
# Pass the backend as ``tile_backend=``. The ``data`` argument is just a
|
|
118
|
+
# placeholder — the backend's ``full_shape`` drives the axes, and ``tile=True``
|
|
119
|
+
# forces the tiled path. Zoom and pan to stream sharp detail tiles; anyplotlib
|
|
120
|
+
# computes only what is on screen.
|
|
121
|
+
#
|
|
122
|
+
# **Tiling and the GPU cooperate.** With ``gpu="auto"`` (the default) and a
|
|
123
|
+
# WebGPU-capable browser, the downsampled overview is uploaded once as a GPU
|
|
124
|
+
# texture and drawn by the shader-side colormap, and the crisp detail tile for
|
|
125
|
+
# the visible region is stitched on top of it each frame. The logical image
|
|
126
|
+
# here is 4.3 gigapixels, which is far above the ~1-megapixel threshold that
|
|
127
|
+
# switches the WebGPU image path on under ``"auto"`` — so the base raster is
|
|
128
|
+
# GPU-composited rather than blitted on the CPU. (Headless/off-screen renders,
|
|
129
|
+
# like this gallery's static thumbnail, have no WebGPU and fall back to
|
|
130
|
+
# Canvas2D; the live "Run in browser" version above uses the GPU when your
|
|
131
|
+
# browser supports it.)
|
|
132
|
+
|
|
133
|
+
backend = MandelbrotTileBackend()
|
|
134
|
+
|
|
135
|
+
# Coordinate axes spanning the logical image, mapped to the complex plane so
|
|
136
|
+
# ticks read as real / imaginary values (the tiling still works in pixels).
|
|
137
|
+
x_axis = np.linspace(CX0, CX1, SIZE)
|
|
138
|
+
y_axis = np.linspace(CY0, CY1, SIZE)
|
|
139
|
+
|
|
140
|
+
fig, ax = apl.subplots(1, 1, figsize=(560, 560))
|
|
141
|
+
v = ax.imshow(
|
|
142
|
+
np.zeros((2, 2)), # placeholder; overview comes from backend.sample
|
|
143
|
+
axes=[x_axis, y_axis],
|
|
144
|
+
tile_backend=backend,
|
|
145
|
+
tile=True,
|
|
146
|
+
gpu="auto", # WebGPU texture path when available (see above)
|
|
147
|
+
cmap="inferno",
|
|
148
|
+
units="Re / Im",
|
|
149
|
+
)
|
|
150
|
+
fig.set_help(
|
|
151
|
+
"Scroll to zoom, drag to pan.\n"
|
|
152
|
+
"Each view fetches a fresh detail tile — the 4.3 GP fractal is\n"
|
|
153
|
+
"generated on demand and never held in memory."
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
fig # Interactive
|
|
157
|
+
|
|
158
|
+
# %%
|
|
159
|
+
# Is the GPU actually painting?
|
|
160
|
+
# -----------------------------
|
|
161
|
+
# Read :attr:`~anyplotlib.Plot2D.gpu_active` to see which path ran. anyplotlib
|
|
162
|
+
# updates it automatically once the browser reports back after the first frame:
|
|
163
|
+
# ``True`` when the WebGPU image path is live, ``False`` on the Canvas2D
|
|
164
|
+
# fallback. It is ``False`` here because the gallery build is headless (no
|
|
165
|
+
# WebGPU); open the live "Run in browser" version above and read ``v.gpu_active``
|
|
166
|
+
# to confirm the accelerated path on your own machine.
|
|
167
|
+
|
|
168
|
+
print("gpu_active:", v.gpu_active)
|
|
169
|
+
|
|
170
|
+
# %%
|
|
171
|
+
# Swap in a real source
|
|
172
|
+
# ---------------------
|
|
173
|
+
# The same protocol wraps any large source without changing the plot: back
|
|
174
|
+
# ``sample`` with a memory-mapped file, a dask/zarr chunk store, or a
|
|
175
|
+
# GPU-resident tensor. For an in-memory ndarray you don't need a custom class
|
|
176
|
+
# at all — :func:`~anyplotlib.plot2d._tile_backend.as_tile_backend` (used
|
|
177
|
+
# internally when you pass a plain array with ``tile="auto"``) wraps it in the
|
|
178
|
+
# default :class:`~anyplotlib.plot2d._tile_backend.NumpyTileBackend`.
|
|
179
|
+
#
|
|
180
|
+
# .. note::
|
|
181
|
+
#
|
|
182
|
+
# ``sample`` should be reasonably fast — it runs once per view change. A
|
|
183
|
+
# slow source (network, disk) still works, but pan/zoom will feel as
|
|
184
|
+
# responsive as the slowest ``sample`` call.
|