anyplotlib 0.2.0__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.2.0 → anyplotlib-0.3.0}/.github/workflows/prepare_release.yml +42 -11
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/CHANGELOG.rst +40 -0
- anyplotlib-0.3.0/Examples/Interactive/plot_figure_annotations.py +87 -0
- {anyplotlib-0.2.0 → 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.2.0 → anyplotlib-0.3.0}/PKG-INFO +1 -1
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/FIGURE_ESM.md +225 -1
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/__init__.py +4 -4
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/_base_plot.py +69 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/_electron.py +15 -2
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/_repr_utils.py +76 -4
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/axes/_axes.py +2 -2
- anyplotlib-0.3.0/anyplotlib/axes/_inset_axes.py +254 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/callbacks.py +4 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/figure/_figure.py +176 -8
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/figure_esm.js +1251 -52
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/markers.py +3 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/plot1d/_plot1d.py +170 -12
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/plot2d/__init__.py +3 -2
- anyplotlib-0.3.0/anyplotlib/plot2d/_layer.py +121 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/plot2d/_plot2d.py +385 -23
- anyplotlib-0.3.0/anyplotlib/tests/baselines/plot1d_twinx.png +0 -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_layouts/test_inset_callout.py +636 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_visual.py +20 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_markers/test_markers.py +77 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot1d/test_plot1d.py +158 -0
- 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.3.0/anyplotlib/tests/test_plot3d/test_gpu_hidden_reveal.py +330 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/widgets/__init__.py +2 -2
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/widgets/_widgets2d.py +69 -12
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/_root/index.html +3 -3
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/_root/switcher.json +10 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/api/figure_plots.rst +7 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/api/index.rst +3 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/conf.py +10 -1
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/pyproject.toml +1 -1
- anyplotlib-0.2.0/anyplotlib/axes/_inset_axes.py +0 -126
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/.github/workflows/ci.yml +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/.github/workflows/docs.yml +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/.github/workflows/release.yml +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/.github/workflows/tests.yml +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/.gitignore +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/AGENTS.md +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Benchmarks/README.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Benchmarks/plot_benchmark_comparison.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Interactive/README.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Interactive/plot_3d_spectral_viewer.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Interactive/plot_eels_explorer.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Interactive/plot_interactive_fft.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Interactive/plot_interactive_fitting.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Interactive/plot_ipf_density_map.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Interactive/plot_ipf_explorer.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Interactive/plot_key_bindings.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Interactive/plot_particle_picker.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Interactive/plot_point_widget.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Interactive/plot_segment_by_contrast.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Interactive/plot_segment_by_contrast_advanced.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Interactive/plot_spectra_roi_inspector.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Interactive/plot_threshold_explorer.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Markers/README.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Markers/plot_arrows.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Markers/plot_circles.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Markers/plot_ellipses.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Markers/plot_horizontal_lines.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Markers/plot_line_segments.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Markers/plot_points.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Markers/plot_polygons.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Markers/plot_rectangles.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Markers/plot_squares.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Markers/plot_texts.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Markers/plot_vertical_lines.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/PlotTypes/README.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_3d.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_bar.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_gridspec_custom.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_image2d.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_inset.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_label_formatting.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_line_styles.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_pcolormesh.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_spectra1d.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/README.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Widgets/README.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget1d_hline.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget1d_range.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget1d_vline.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_annular.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_circle.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_crosshair.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_label.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_polygon.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_rectangle.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/LARGE_IMAGE_WEBGPU_PLAN.md +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/LICENSE +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/Makefile +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/ORIX_BACKEND_PLAN.md +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/README.md +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/RELEASE_NOTES.md +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/RELEASE_PLAN.md +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/WEBGPU_PLAN.md +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/_binary_frame.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/_utils.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/axes/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/conftest.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/embed.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/figure/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/figure/_gridspec.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/figure/_subplots.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/plot1d/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/plot1d/_plotbar.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/plot2d/_plotmesh.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/plot2d/_tile_backend.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/plot3d/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/plot3d/_plot3d.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/plotxy/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/plotxy/_plotxy.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/_directive.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/_repr_utils.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/_scraper.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/_wheel_builder.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/static/anywidget_bridge.js +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/static/anywidget_overlay.css +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/conftest.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_directive.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_init.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_repr_utils.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_scraper.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_wheel_builder.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/_gpu_clim_check.cjs +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/_png_utils.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/bar_basic.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_3col_equal_spectra.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_asymmetric_width_ratios.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_height_ratio_image_histogram.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_image_two_spectra.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_side_by_side_1d.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_spanning_top_two_bottom.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_axis_off.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_checkerboard.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_gradient.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_labels.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_viridis.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_1d.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_maximized.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_minimized.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_normal_2d.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_stacked.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_stacked_one_minimized.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/pcolormesh_uniform.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_all_linestyles.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_alpha.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_axis_off.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_dashed.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_marker_symbols.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_markers.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_multi.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_sine.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_title.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot3d_surface.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/subplots_2x1.png +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/conftest.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/baselines.json +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/benchmarks/baselines.json +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/test_benchmarks.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/test_benchmarks_py.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_documentation/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_documentation/test_bridge.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_documentation/test_push_hook.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_documentation/test_scraper.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_electron/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_electron/test_binary_frame.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_embed/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_embed/test_embed_api.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_embed/test_embed_mount.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_examples/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_examples/test_interactive_examples.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/_event_test_utils.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_blit_audit.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_callbacks_playwright.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_callbacks_unit.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_event_pause_hold.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_event_plots.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_event_settled.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_events_regression.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_tile_parity_playwright.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_title.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_touch.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_widgets.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_labels/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_labels/test_label_api.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_labels/test_label_rendering.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_labels/test_no_clipping.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_batch.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_geom_channel.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_gridspec.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_inset.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_interaction.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_markers/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_markers/test_marker_transforms.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot1d/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot1d/test_plotbar.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_detail_tile.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_first_paint_race.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_gpu_image.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_gpu_parity_playwright.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_imshow.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_imshow_rgb.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_pcolormesh.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_plot2d_api.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_rapid_frame_update.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_tile_backend.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_tiled_imshow.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_zoom_view_write.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot3d/__init__.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot3d/test_colors_highlight.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot3d/test_gpu_fallback.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot3d/test_plot3d.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot3d/test_voxels_planes.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/tests/test_plotxy/test_plotxy.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/widgets/_base.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/widgets/_widgets1d.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/anyplotlib/widgets/_widgets3d.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/_sg_html_scraper.py +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/_static/anyplotlib.svg +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/_static/custom.css +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/_static/pyodide_bridge.js +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/_templates/autosummary/class.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/api/callbacks.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/api/figure.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/api/markers.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/api/widgets.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/benchmarking.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/dev/index.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/embedding.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/events.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/getting_started.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/docs/index.rst +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/make.bat +0 -0
- {anyplotlib-0.2.0 → anyplotlib-0.3.0}/upcoming_changes/README.rst +0 -0
- {anyplotlib-0.2.0 → 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,46 @@ 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
|
+
|
|
13
53
|
0.2.0 (2026-07-10)
|
|
14
54
|
==================
|
|
15
55
|
|
|
@@ -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.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Twinned (secondary) y-axis
|
|
3
|
+
==========================
|
|
4
|
+
|
|
5
|
+
Overlay two series that live on very different scales on a single
|
|
6
|
+
:class:`~anyplotlib.Plot1D` panel, each with its own y-axis. Enable the
|
|
7
|
+
right-hand axis with :meth:`~anyplotlib.Plot1D.add_right_axis`, then add curves
|
|
8
|
+
to it with ``add_line(..., axis="right")`` — they are scaled and labelled
|
|
9
|
+
independently of the left axis.
|
|
10
|
+
"""
|
|
11
|
+
import numpy as np
|
|
12
|
+
import anyplotlib as apl
|
|
13
|
+
|
|
14
|
+
x = np.linspace(0, 10, 400)
|
|
15
|
+
signal = np.sin(x) # left axis: −1 … 1
|
|
16
|
+
temperature = 300 + 350 * np.cos(x / 2) # right axis: ~ −50 … 650
|
|
17
|
+
|
|
18
|
+
fig, ax = apl.subplots(1, 1, figsize=(560, 360))
|
|
19
|
+
plot = ax.plot(signal, axes=[x], color="#4fc3f7", label="signal")
|
|
20
|
+
plot.set_ylabel("Amplitude")
|
|
21
|
+
|
|
22
|
+
# %%
|
|
23
|
+
# Add the secondary axis
|
|
24
|
+
# ----------------------
|
|
25
|
+
# ``add_right_axis`` turns on the right-hand y-axis; ``axis="right"`` anchors
|
|
26
|
+
# the new curve to it. The left axis stays fixed at −1 … 1 while the right
|
|
27
|
+
# axis auto-scales to the temperature range.
|
|
28
|
+
|
|
29
|
+
plot.add_right_axis(color="#e05a2b")
|
|
30
|
+
plot.add_line(temperature, x_axis=x, color="#e05a2b",
|
|
31
|
+
axis="right", label="temperature")
|
|
32
|
+
plot.set_right_ylabel("Temperature (K)")
|
|
33
|
+
|
|
34
|
+
fig
|
|
35
|
+
|
|
36
|
+
# %%
|
|
37
|
+
# Pin the secondary range
|
|
38
|
+
# -----------------------
|
|
39
|
+
# By default the right axis auto-scales to its lines. Call
|
|
40
|
+
# :meth:`~anyplotlib.Plot1D.set_right_ylim` to fix it explicitly (for example
|
|
41
|
+
# to line two datasets up at a shared reference level).
|
|
42
|
+
|
|
43
|
+
plot.set_right_ylim(0, 700)
|
|
44
|
+
|
|
45
|
+
fig
|