anyplotlib 0.1.0b1__tar.gz → 0.3.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.1.0b1 → anyplotlib-0.3.0}/.github/workflows/prepare_release.yml +42 -11
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/CHANGELOG.rst +142 -0
- anyplotlib-0.3.0/Examples/Interactive/plot_figure_annotations.py +87 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_voxel_grain_explorer.py +5 -2
- anyplotlib-0.3.0/Examples/Markers/plot_transform_markers.py +49 -0
- anyplotlib-0.3.0/Examples/PlotTypes/plot_gpu_voxels.py +82 -0
- anyplotlib-0.3.0/Examples/PlotTypes/plot_step_and_log.py +43 -0
- anyplotlib-0.3.0/Examples/PlotTypes/plot_tile_backend.py +184 -0
- anyplotlib-0.3.0/Examples/PlotTypes/plot_twinx.py +45 -0
- anyplotlib-0.3.0/Examples/Widgets/plot_widget1d_point.py +40 -0
- anyplotlib-0.3.0/Examples/Widgets/plot_widget2d_arrow.py +41 -0
- anyplotlib-0.3.0/Examples/Widgets/plot_widget3d_plane.py +47 -0
- anyplotlib-0.3.0/LARGE_IMAGE_WEBGPU_PLAN.md +223 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/PKG-INFO +1 -1
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/FIGURE_ESM.md +225 -1
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/__init__.py +4 -4
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/_base_plot.py +69 -0
- anyplotlib-0.3.0/anyplotlib/_binary_frame.py +78 -0
- anyplotlib-0.3.0/anyplotlib/_electron.py +201 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/_repr_utils.py +119 -9
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/_utils.py +49 -13
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/axes/_axes.py +13 -5
- anyplotlib-0.3.0/anyplotlib/axes/_inset_axes.py +254 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/callbacks.py +13 -1
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/figure/_figure.py +222 -8
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/figure_esm.js +2402 -103
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/markers.py +10 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plot1d/_plot1d.py +218 -36
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plot2d/__init__.py +3 -2
- anyplotlib-0.3.0/anyplotlib/plot2d/_layer.py +121 -0
- anyplotlib-0.3.0/anyplotlib/plot2d/_plot2d.py +1911 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plot2d/_plotmesh.py +1 -1
- anyplotlib-0.3.0/anyplotlib/plot2d/_tile_backend.py +181 -0
- anyplotlib-0.3.0/anyplotlib/tests/_gpu_clim_check.cjs +51 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/_png_utils.py +32 -0
- anyplotlib-0.3.0/anyplotlib/tests/baselines/plot1d_twinx.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/conftest.py +149 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_electron/test_binary_frame.py +245 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_embed/test_export_png.py +777 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_interactive/test_edit_chrome.py +435 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_interactive/test_edit_chrome_playwright.py +842 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_interactive/test_tile_parity_playwright.py +315 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_title.py +3 -3
- anyplotlib-0.3.0/anyplotlib/tests/test_layouts/test_inset_callout.py +636 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_visual.py +80 -1
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_markers/test_marker_transforms.py +32 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_markers/test_markers.py +77 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot1d/test_plot1d.py +158 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_detail_tile.py +419 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_first_paint_race.py +259 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_gpu_image.py +98 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_gpu_parity_playwright.py +425 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_imshow.py +65 -3
- anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_layers.py +427 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_layers_playwright.py +313 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_plot2d_api.py +15 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_rapid_frame_update.py +160 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_tile_backend.py +99 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_tiled_imshow.py +386 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_zoom_view_write.py +76 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_plot3d/__init__.py +0 -0
- anyplotlib-0.3.0/anyplotlib/tests/test_plot3d/test_gpu_hidden_reveal.py +330 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/widgets/__init__.py +2 -2
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/widgets/_widgets2d.py +69 -12
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/_root/index.html +3 -3
- anyplotlib-0.3.0/docs/_root/switcher.json +27 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/api/figure_plots.rst +7 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/api/index.rst +3 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/conf.py +10 -1
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/pyproject.toml +1 -1
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/upcoming_changes/README.rst +7 -1
- anyplotlib-0.1.0b1/anyplotlib/_electron.py +0 -74
- anyplotlib-0.1.0b1/anyplotlib/axes/_inset_axes.py +0 -126
- anyplotlib-0.1.0b1/anyplotlib/plot2d/_plot2d.py +0 -726
- anyplotlib-0.1.0b1/docs/_root/switcher.json +0 -12
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/.github/workflows/ci.yml +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/.github/workflows/docs.yml +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/.github/workflows/release.yml +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/.github/workflows/tests.yml +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/.gitignore +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/AGENTS.md +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Benchmarks/README.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Benchmarks/plot_benchmark_comparison.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/README.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_3d_spectral_viewer.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_eels_explorer.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_interactive_fft.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_interactive_fitting.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_ipf_density_map.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_ipf_explorer.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_key_bindings.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_particle_picker.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_point_widget.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_segment_by_contrast.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_segment_by_contrast_advanced.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_spectra_roi_inspector.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_threshold_explorer.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/README.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_arrows.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_circles.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_ellipses.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_horizontal_lines.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_line_segments.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_points.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_polygons.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_rectangles.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_squares.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_texts.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_vertical_lines.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/README.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_3d.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_bar.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_gridspec_custom.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_image2d.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_inset.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_label_formatting.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_line_styles.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_pcolormesh.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_spectra1d.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/README.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/README.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget1d_hline.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget1d_range.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget1d_vline.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_annular.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_circle.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_crosshair.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_label.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_polygon.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_rectangle.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/LICENSE +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Makefile +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/ORIX_BACKEND_PLAN.md +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/README.md +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/RELEASE_NOTES.md +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/RELEASE_PLAN.md +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/WEBGPU_PLAN.md +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/axes/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/conftest.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/embed.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/figure/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/figure/_gridspec.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/figure/_subplots.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plot1d/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plot1d/_plotbar.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plot3d/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plot3d/_plot3d.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plotxy/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plotxy/_plotxy.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/_directive.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/_repr_utils.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/_scraper.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/_wheel_builder.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/static/anywidget_bridge.js +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/static/anywidget_overlay.css +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/conftest.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_directive.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_init.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_repr_utils.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_scraper.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_wheel_builder.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/bar_basic.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_3col_equal_spectra.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_asymmetric_width_ratios.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_height_ratio_image_histogram.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_image_two_spectra.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_side_by_side_1d.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_spanning_top_two_bottom.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_axis_off.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_checkerboard.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_gradient.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_labels.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_viridis.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_1d.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_maximized.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_minimized.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_normal_2d.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_stacked.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_stacked_one_minimized.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/pcolormesh_uniform.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_all_linestyles.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_alpha.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_axis_off.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_dashed.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_marker_symbols.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_markers.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_multi.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_sine.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_title.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot3d_surface.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/subplots_2x1.png +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/baselines.json +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/benchmarks/baselines.json +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/test_benchmarks.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/test_benchmarks_py.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_documentation/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_documentation/test_bridge.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_documentation/test_push_hook.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_documentation/test_scraper.py +0 -0
- {anyplotlib-0.1.0b1/anyplotlib/tests/test_embed → anyplotlib-0.3.0/anyplotlib/tests/test_electron}/__init__.py +0 -0
- {anyplotlib-0.1.0b1/anyplotlib/tests/test_examples → anyplotlib-0.3.0/anyplotlib/tests/test_embed}/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_embed/test_embed_api.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_embed/test_embed_mount.py +0 -0
- {anyplotlib-0.1.0b1/anyplotlib/tests/test_interactive → anyplotlib-0.3.0/anyplotlib/tests/test_examples}/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_examples/test_interactive_examples.py +0 -0
- {anyplotlib-0.1.0b1/anyplotlib/tests/test_labels → anyplotlib-0.3.0/anyplotlib/tests/test_interactive}/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/_event_test_utils.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_blit_audit.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_callbacks_playwright.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_callbacks_unit.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_event_pause_hold.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_event_plots.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_event_settled.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_events_regression.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_touch.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_widgets.py +0 -0
- {anyplotlib-0.1.0b1/anyplotlib/tests/test_layouts → anyplotlib-0.3.0/anyplotlib/tests/test_labels}/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_labels/test_label_api.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_labels/test_label_rendering.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_labels/test_no_clipping.py +0 -0
- {anyplotlib-0.1.0b1/anyplotlib/tests/test_markers → anyplotlib-0.3.0/anyplotlib/tests/test_layouts}/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_batch.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_geom_channel.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_gridspec.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_inset.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_interaction.py +0 -0
- {anyplotlib-0.1.0b1/anyplotlib/tests/test_plot1d → anyplotlib-0.3.0/anyplotlib/tests/test_markers}/__init__.py +0 -0
- {anyplotlib-0.1.0b1/anyplotlib/tests/test_plot2d → anyplotlib-0.3.0/anyplotlib/tests/test_plot1d}/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot1d/test_plotbar.py +0 -0
- {anyplotlib-0.1.0b1/anyplotlib/tests/test_plot3d → anyplotlib-0.3.0/anyplotlib/tests/test_plot2d}/__init__.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_imshow_rgb.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_pcolormesh.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot3d/test_colors_highlight.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot3d/test_gpu_fallback.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot3d/test_plot3d.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot3d/test_voxels_planes.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plotxy/test_plotxy.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/widgets/_base.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/widgets/_widgets1d.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/widgets/_widgets3d.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/_sg_html_scraper.py +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/_static/anyplotlib.svg +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/_static/custom.css +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/_static/pyodide_bridge.js +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/_templates/autosummary/class.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/api/callbacks.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/api/figure.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/api/markers.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/api/widgets.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/benchmarking.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/dev/index.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/embedding.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/events.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/getting_started.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/index.rst +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/make.bat +0 -0
- {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/uv.lock +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,148 @@ Fragment files in ``upcoming_changes/`` are assembled into this file by
|
|
|
10
10
|
|
|
11
11
|
.. towncrier release notes start
|
|
12
12
|
|
|
13
|
+
0.3.0b1 (2026-07-13)
|
|
14
|
+
====================
|
|
15
|
+
|
|
16
|
+
New Features
|
|
17
|
+
------------
|
|
18
|
+
|
|
19
|
+
- Added :class:`~anyplotlib.widgets.ArrowWidget` (draggable arrow overlay, tail
|
|
20
|
+
at ``(x, y)`` and head at ``(x + u, y + v)``) via ``Plot2D.add_arrow_widget`` /
|
|
21
|
+
``add_widget("arrow")``, and a ``show_handles`` option (default ``True``) on
|
|
22
|
+
every 2-D overlay widget to hide the grab-handle dots without affecting drag.
|
|
23
|
+
- Added figure-level edit-mode chrome to :class:`~anyplotlib.Figure`: the
|
|
24
|
+
``edit_chrome`` and ``selected_panel`` traits (per-panel hover / selection
|
|
25
|
+
outlines), figure-background click events, and a figure-level annotation layer
|
|
26
|
+
(``set_figure_markers`` / ``figure_markers``, positioned in figure fractions and
|
|
27
|
+
always included in ``exportPNG``) with figure-level callbacks via
|
|
28
|
+
``add_event_handler``.
|
|
29
|
+
- Extended :class:`~anyplotlib.Figure` edit-mode interaction:
|
|
30
|
+
|
|
31
|
+
* Circle and rectangle overlay widgets are now **resizable via visible nodes** —
|
|
32
|
+
a circle draws a centre (move) node and an east-point radius node; a rectangle
|
|
33
|
+
draws all four corner nodes (opposite corner anchored on drag). Drawn only
|
|
34
|
+
when ``show_handles`` is ``True``, with matching resize cursors.
|
|
35
|
+
* :class:`~anyplotlib.widgets.ArrowWidget` **tail is now a reshape node**:
|
|
36
|
+
dragging the tail moves it while the head stays anchored (dragging the shaft
|
|
37
|
+
still moves the whole arrow; the head node still re-aims it).
|
|
38
|
+
* The selected-panel and hover **outlines are fully inset** (``outline-offset:
|
|
39
|
+
-2px``) so an edge/corner panel's ring is no longer clipped at the figure's
|
|
40
|
+
right/bottom edge.
|
|
41
|
+
* **Panel drag-swap** under ``edit_chrome``: each grid panel shows a move grip
|
|
42
|
+
in its top-left corner; dragging it over a *different* panel emits a
|
|
43
|
+
figure-level ``pointer_up`` event with ``panel_swap: true`` and
|
|
44
|
+
``source_panel_id`` / ``target_panel_id`` (new :class:`~anyplotlib.Event`
|
|
45
|
+
fields). anyplotlib performs no layout change itself — the host swaps and
|
|
46
|
+
rebuilds. Releasing on the source panel or empty space cancels cleanly; the
|
|
47
|
+
grip is inert when ``edit_chrome`` is off.
|
|
48
|
+
* The JS ``mount()`` embedding entry point accepts an ``onResize({width,
|
|
49
|
+
height})`` callback, fired (debounced) when the **root container resizes**, so
|
|
50
|
+
an embedding host can relayout the figure to its new box.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
0.2.0 (2026-07-10)
|
|
54
|
+
==================
|
|
55
|
+
|
|
56
|
+
New Features
|
|
57
|
+
------------
|
|
58
|
+
|
|
59
|
+
- 2-D scalar images can now render on the **GPU via WebGPU** (``imshow(...,
|
|
60
|
+
gpu="auto"|True|False)``): the image uploads as an R8 texture and a WGSL fragment
|
|
61
|
+
shader applies the colormap LUT + contrast (clim) in one draw, replacing the
|
|
62
|
+
per-pixel JavaScript colormap loop. Large images (≳1 megapixel) take the GPU path
|
|
63
|
+
automatically; everything below the threshold, RGB images, ``gpu=False``, and any
|
|
64
|
+
device without WebGPU keep the identical Canvas2D path. ``plot.gpu_active`` reports
|
|
65
|
+
which path ran. Verified on an NVIDIA Pascal GPU.
|
|
66
|
+
- Arbitrarily large images can now display through **tile mode**
|
|
67
|
+
(``imshow(..., tile="auto")``): the figure shows a downsampled overview as its
|
|
68
|
+
base and, after each zoom/pan settles, samples a high-resolution detail tile of
|
|
69
|
+
just the visible region at panel resolution — deep zooms stay crisp without
|
|
70
|
+
ever shipping the full-resolution frame. ``Plot2D.enable_tile`` /
|
|
71
|
+
``update_tile_source`` swap the underlying frame while the zoom and
|
|
72
|
+
subselection persist (live-data contract), and a pluggable ``TileBackend``
|
|
73
|
+
(default: a fast vectorised numpy box-mean) lets out-of-core or GPU sources
|
|
74
|
+
own the sampling.
|
|
75
|
+
- Markers gained a ``clip_display`` option controlling whether they draw
|
|
76
|
+
outside the current axes view or are clipped to it.
|
|
77
|
+
- ``set_extent`` now updates the axes state (calibrated units / scale bar), so
|
|
78
|
+
applying a calibrated extent after figure creation renders labelled axes
|
|
79
|
+
instead of bare pixels.
|
|
80
|
+
- Regular ``pcolormesh`` meshes are detected and rasterized to an image for
|
|
81
|
+
display (fast IPF-style heatmaps) instead of drawing per-quad.
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
Bug Fixes
|
|
85
|
+
---------
|
|
86
|
+
|
|
87
|
+
- Fixed a display freeze under the Electron binary pixel transport: the routing
|
|
88
|
+
layer stripped the pixel key out of the slimmed geom JSON, so the renderer's
|
|
89
|
+
"unchanged → skip re-upload" caches (Canvas2D blit cache, WebGPU texture,
|
|
90
|
+
overlay-mask cache) fell back to a 4-sampled-byte fingerprint of the buffer —
|
|
91
|
+
two frames differing anywhere else collided and the display stayed frozen on
|
|
92
|
+
the old frame (seen as a stale overview after a movie scrub). The slimmed geom
|
|
93
|
+
now carries a small ``\x00bin:<checksum>`` content token under the pixel key,
|
|
94
|
+
binary buffers are additionally stamped with an arrival sequence as a fallback
|
|
95
|
+
key, and the overlay-mask draw path now reads the binary byte side-channel
|
|
96
|
+
(it previously only decoded base64, so masks never displayed over the binary
|
|
97
|
+
transport).
|
|
98
|
+
- Fixed the WebGPU 2-D image path sampling a vertically MIRRORED window when the
|
|
99
|
+
view was panned off-centre: the shader applied a global ``1 - v`` flip after
|
|
100
|
+
interpolating the ``[v0, v1]`` uv window, which sampled ``[1-v1, 1-v0]``
|
|
101
|
+
instead — correct only for a full or vertically-centred view. Symptoms: pan-y
|
|
102
|
+
moved the image the wrong way on GPU-rendered panels, and markers/widgets
|
|
103
|
+
(drawn by the shared Canvas2D overlay transform, which was always correct)
|
|
104
|
+
appeared detached from the image features they marked. The base and
|
|
105
|
+
detail-tile passes share the shader, so both are fixed. GPU-vs-CPU screenshot
|
|
106
|
+
parity tests (zoom, pan, markers, widgets, detail tile) now run on real WebGPU
|
|
107
|
+
in headless Chromium (``channel="chromium"`` + ``--enable-unsafe-webgpu``) and
|
|
108
|
+
skip on machines with no adapter.
|
|
109
|
+
- ``Plot2D.set_data`` no longer makes a float64 copy of every incoming frame.
|
|
110
|
+
The float64 cast now happens lazily in the ``.data`` property (the only reader),
|
|
111
|
+
so a frame stream — e.g. scrubbing an in-situ movie — keeps the source dtype and
|
|
112
|
+
skips a ~12 ms float64 copy of a 4k frame per tick. ``.data`` still returns a
|
|
113
|
+
read-only float64 copy, unchanged for callers.
|
|
114
|
+
- The Electron binary pixel transport now ships the RAW uint8 image bytes end to
|
|
115
|
+
end, instead of base64-encoding them in ``set_data`` only to base64-decode them
|
|
116
|
+
straight back in the routing layer. ``Plot2D.set_data`` stashes the raw bytes on
|
|
117
|
+
the Figure's ``_raw_pixels`` side-table and leaves a tiny content-checksum
|
|
118
|
+
change-token in ``image_b64``; ``_electron._route_change`` ships those bytes to a
|
|
119
|
+
PLOTBIN frame directly. This removes the ~20 ms base64 encode, the ~17 ms decode,
|
|
120
|
+
and the megabyte ``json.dumps`` of the pixel string from every scrub frame — a
|
|
121
|
+
2.2x faster ``set_data`` (≈98 ms → ≈44 ms on a 2048² frame) and ~25% less
|
|
122
|
+
bytes-on-wire. Non-Electron hosts (Jupyter / Pyodide / standalone / ``save_html``)
|
|
123
|
+
are unchanged: they have no PLOTBIN channel, so the token is resolved back to
|
|
124
|
+
inline base64 via ``Plot2D.resolve_pixel_tokens`` when the figure state is
|
|
125
|
+
serialised for them.
|
|
126
|
+
- Tile mode: a data update while zoomed in (``update_tile_source`` with a detail
|
|
127
|
+
tile shown) refreshes only the detail tile, leaving the overview base on the
|
|
128
|
+
old frame — zooming out then flashed the pre-update frame. The skipped
|
|
129
|
+
overview is now marked stale and re-sampled once on the next view settle
|
|
130
|
+
(riding the same push as the detail/clear), preserving the per-frame skip
|
|
131
|
+
optimisation while never exposing stale base pixels.
|
|
132
|
+
- Interactive zoom/pan on a 2-D image no longer re-serialises (and re-transmits)
|
|
133
|
+
the full image on every mouse tick. The wheel/pan/orbit handlers write only the
|
|
134
|
+
light *view* state back to the ``panel_<id>_json`` trait now, excluding the
|
|
135
|
+
cached geometry (pixels, colormap LUT) that ``_applyGeom`` splices into the panel
|
|
136
|
+
state for drawing. Previously the whole frame was ``JSON.stringify``-d per tick —
|
|
137
|
+
catastrophically so on the binary transport, where the pixel buffer is a
|
|
138
|
+
``Uint8Array`` that stringifies to a ``{"0":..,"1":..}`` object with one key per
|
|
139
|
+
byte — which stalled zoom on large images.
|
|
140
|
+
- Fixed a first-paint race under the Electron binary transport: binary
|
|
141
|
+
side-table bytes that arrived before ``render()`` attached its listeners were
|
|
142
|
+
stranded, leaving the first frame blank until the next update — they are now
|
|
143
|
+
spliced into the initial paint.
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
Maintenance
|
|
147
|
+
-----------
|
|
148
|
+
|
|
149
|
+
- ``anyplotlib.__version__`` is now exposed from the package metadata.
|
|
150
|
+
- Per-frame hot-path costs trimmed: the colormap LUT is cached instead of being
|
|
151
|
+
rebuilt every frame (~100 ms), and small-range data rescales in float32
|
|
152
|
+
(~60 ms → ~27 ms per 2048² frame).
|
|
153
|
+
|
|
154
|
+
|
|
13
155
|
0.1.0 (2026-06-24)
|
|
14
156
|
==================
|
|
15
157
|
|
|
@@ -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
|