anyplotlib 0.7.1__tar.gz → 0.7.3__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.7.1 → anyplotlib-0.7.3}/CHANGELOG.rst +32 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/PKG-INFO +2 -2
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/figure_esm.js +10 -2
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/plot2d/_plot2d.py +76 -8
- anyplotlib-0.7.3/anyplotlib/tests/test_interactive/test_circle_lock_center.py +160 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_imshow.py +54 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/widgets/_widgets2d.py +18 -2
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/_root/index.html +3 -3
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/_root/switcher.json +10 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/conf.py +1 -1
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/pyproject.toml +1 -1
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/.github/workflows/ci.yml +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/.github/workflows/docs.yml +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/.github/workflows/prepare_release.yml +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/.github/workflows/release.yml +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/.github/workflows/tests.yml +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/.gitignore +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/AGENTS.md +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Benchmarks/README.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Benchmarks/plot_benchmark_comparison.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/README.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_3d_spectral_viewer.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_eels_explorer.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_figure_annotations.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_interactive_fft.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_interactive_fitting.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_ipf_density_map.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_ipf_explorer.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_key_bindings.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_particle_picker.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_point_widget.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_segment_by_contrast.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_segment_by_contrast_advanced.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_spectra_roi_inspector.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_star_globe_explorer.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_threshold_explorer.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_voxel_grain_explorer.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Markers/README.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Markers/plot_arrows.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Markers/plot_circles.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Markers/plot_ellipses.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Markers/plot_horizontal_lines.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Markers/plot_line_segments.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Markers/plot_points.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Markers/plot_polygons.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Markers/plot_rectangles.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Markers/plot_squares.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Markers/plot_texts.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Markers/plot_transform_markers.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Markers/plot_vertical_lines.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/README.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_3d.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_3d_texture.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_bar.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_gpu_voxels.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_gridspec_custom.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_image2d.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_inset.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_key_overlays.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_label_formatting.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_line_styles.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_pcolormesh.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_spectra1d.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_step_and_log.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_tile_backend.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/PlotTypes/plot_twinx.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/README.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Widgets/README.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Widgets/plot_widget1d_hline.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Widgets/plot_widget1d_point.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Widgets/plot_widget1d_range.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Widgets/plot_widget1d_vline.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Widgets/plot_widget2d_annular.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Widgets/plot_widget2d_arrow.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Widgets/plot_widget2d_circle.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Widgets/plot_widget2d_crosshair.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Widgets/plot_widget2d_label.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Widgets/plot_widget2d_polygon.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Widgets/plot_widget2d_rectangle.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Widgets/plot_widget3d_plane.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/LICENSE +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/Makefile +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/README.md +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/FIGURE_ESM.md +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/_base_plot.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/_binary_frame.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/_electron.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/_repr_utils.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/_utils.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/axes/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/axes/_axes.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/axes/_inset_axes.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/callbacks.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/conftest.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/embed.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/figure/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/figure/_figure.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/figure/_gridspec.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/figure/_subplots.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/keys.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/markers.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/plot1d/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/plot1d/_plot1d.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/plot1d/_plotbar.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/plot2d/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/plot2d/_layer.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/plot2d/_plotmesh.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/plot2d/_tile_backend.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/plot3d/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/plot3d/_plot3d.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/plotxy/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/plotxy/_plotxy.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/sphinx_anywidget/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/sphinx_anywidget/_directive.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/sphinx_anywidget/_repr_utils.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/sphinx_anywidget/_scraper.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/sphinx_anywidget/_wheel_builder.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/sphinx_anywidget/static/anywidget_bridge.js +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/sphinx_anywidget/static/anywidget_overlay.css +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/sphinx_anywidget/tests/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/sphinx_anywidget/tests/conftest.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/sphinx_anywidget/tests/test_directive.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/sphinx_anywidget/tests/test_init.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/sphinx_anywidget/tests/test_repr_utils.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/sphinx_anywidget/tests/test_scraper.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/sphinx_anywidget/tests/test_wheel_builder.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/_gpu_clim_check.cjs +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/_png_utils.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/bar_basic.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/gridspec_3col_equal_spectra.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/gridspec_asymmetric_width_ratios.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/gridspec_height_ratio_image_histogram.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/gridspec_image_two_spectra.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/gridspec_side_by_side_1d.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/gridspec_spanning_top_two_bottom.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/imshow_axis_off.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/imshow_checkerboard.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/imshow_gradient.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/imshow_labels.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/imshow_viridis.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/inset_1d.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/inset_maximized.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/inset_minimized.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/inset_normal_2d.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/inset_stacked.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/inset_stacked_one_minimized.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/pcolormesh_uniform.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/plot1d_all_linestyles.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/plot1d_alpha.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/plot1d_axis_off.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/plot1d_dashed.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/plot1d_marker_symbols.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/plot1d_markers.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/plot1d_multi.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/plot1d_sine.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/plot1d_title.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/plot1d_twinx.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/plot3d_surface.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/baselines/subplots_2x1.png +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/conftest.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_benchmarks/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_benchmarks/baselines.json +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_benchmarks/benchmarks/baselines.json +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_benchmarks/test_benchmarks.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_benchmarks/test_benchmarks_py.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_documentation/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_documentation/test_bridge.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_documentation/test_push_hook.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_documentation/test_scraper.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_electron/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_electron/test_binary_frame.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_embed/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_embed/test_embed_api.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_embed/test_embed_mount.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_embed/test_export_pixel_tokens.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_embed/test_export_png.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_embed/test_export_widget_sync.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_examples/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_examples/test_interactive_examples.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/_event_test_utils.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_blit_audit.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_callbacks_playwright.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_callbacks_unit.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_edit_chrome.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_edit_chrome_playwright.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_event_pause_hold.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_event_plots.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_event_settled.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_events_regression.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_orbit_direction.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_pointer_down_1d.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_range_orientation_snap.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_star_globe_linking.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_tile_parity_playwright.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_title.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_touch.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_widget_brush.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_widget_max_extent.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_widget_set_notify.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_widgets.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_interactive/test_widgets_line_2d.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_keys/test_keys.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_labels/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_labels/test_label_api.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_labels/test_label_rendering.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_labels/test_no_clipping.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_labels/test_ylabel_clearance.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_layouts/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_layouts/test_batch.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_layouts/test_coord_conversion.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_layouts/test_geom_channel.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_layouts/test_gridspec.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_layouts/test_inset.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_layouts/test_inset_callout.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_layouts/test_interaction.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_layouts/test_visual.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_markers/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_markers/test_marker_transforms.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_markers/test_markers.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_markers/test_per_marker_colors.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_markers/test_size_units.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot1d/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot1d/test_line_none_rendering.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot1d/test_plot1d.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot1d/test_plotbar.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_colorbar_gap_scalebar.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_detail_tile.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_first_paint_race.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_gpu_image.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_gpu_parity_playwright.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_imshow_rgb.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_layers.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_layers_playwright.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_pcolormesh.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_plot2d_api.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_rapid_frame_update.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_tile_backend.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_tile_diagnostics.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_tiled_imshow.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot2d/test_zoom_view_write.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot3d/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot3d/test_colors_highlight.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot3d/test_gpu_depth.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot3d/test_gpu_fallback.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot3d/test_gpu_hidden_reveal.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot3d/test_plot3d.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot3d/test_texture.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plot3d/test_voxels_planes.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/tests/test_plotxy/test_plotxy.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/widgets/__init__.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/widgets/_base.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/widgets/_widgets1d.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/anyplotlib/widgets/_widgets3d.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/_sg_html_scraper.py +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/_static/anyplotlib.svg +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/_static/custom.css +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/_static/pyodide_bridge.js +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/_templates/autosummary/class.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/api/callbacks.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/api/figure.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/api/figure_plots.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/api/index.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/api/keys.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/api/markers.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/api/widgets.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/benchmarking.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/dev/index.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/embedding.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/events.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/getting_started.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/docs/index.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/make.bat +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/upcoming_changes/.gitkeep +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/upcoming_changes/README.rst +0 -0
- {anyplotlib-0.7.1 → anyplotlib-0.7.3}/uv.lock +0 -0
|
@@ -10,6 +10,38 @@ Fragment files in ``upcoming_changes/`` are assembled into this file by
|
|
|
10
10
|
|
|
11
11
|
.. towncrier release notes start
|
|
12
12
|
|
|
13
|
+
0.7.3 (2026-08-13)
|
|
14
|
+
==================
|
|
15
|
+
|
|
16
|
+
New Features
|
|
17
|
+
------------
|
|
18
|
+
|
|
19
|
+
- ``Plot2D.add_circle_widget`` gains ``lock_center``: the centre is pinned and
|
|
20
|
+
only the radius is draggable. A grab on the ring body is refused at hit-test
|
|
21
|
+
time and falls through to the plot's own pan, so the hover cursor never
|
|
22
|
+
promises a move and the centre cannot drift. Use it when the centre is fixed by
|
|
23
|
+
the data — a ring on a power spectrum is centred on the DC term, and one nudged
|
|
24
|
+
off-centre silently corrupts every radius measured from it.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
0.7.2 (2026-08-03)
|
|
28
|
+
==================
|
|
29
|
+
|
|
30
|
+
Bug Fixes
|
|
31
|
+
---------
|
|
32
|
+
|
|
33
|
+
- ``Plot2D.set_overlay_mask`` now works in TILE mode. The renderer sizes the mask
|
|
34
|
+
against ``base_width || image_width`` -- the tile overview grid -- but tile mode
|
|
35
|
+
sets ``image_width`` to the full native frame, so the shape check accepted only
|
|
36
|
+
the one shape the renderer silently discards (``maskCache = null``, no error) and
|
|
37
|
+
rejected the one that actually renders. On a 4096x4096 tiled plot neither a
|
|
38
|
+
1024x1024 nor a 4096x4096 mask could be drawn: the first raised ``ValueError``,
|
|
39
|
+
the second encoded 22.4 MB the renderer dropped. Both shapes are now accepted and
|
|
40
|
+
a full-resolution mask is reduced to the overview grid with a block ANY -- never
|
|
41
|
+
a subsample, so an object a few pixels across cannot vanish into a skipped
|
|
42
|
+
sample.
|
|
43
|
+
|
|
44
|
+
|
|
13
45
|
0.7.1 (2026-08-02)
|
|
14
46
|
==================
|
|
15
47
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: anyplotlib
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.3
|
|
4
4
|
Summary: A plotting library using python, javascript and anywidget for performant in browser plotting.
|
|
5
5
|
Project-URL: Homepage, https://cssfrancis.github.io/anyplotlib/
|
|
6
6
|
Project-URL: Repository, https://github.com/CSSFrancis/anyplotlib
|
|
@@ -8182,7 +8182,13 @@ fn fs(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
8182
8182
|
// outer radius handle
|
|
8183
8183
|
if (Math.hypot(mx - (ccx + cr), my - ccy) <= HR)
|
|
8184
8184
|
return { idx:i, mode:'resize_r', snapW:{...w}, startMX:mx, startMY:my };
|
|
8185
|
-
// body (inside ring ± tolerance)
|
|
8185
|
+
// body (inside ring ± tolerance). `lock_center` refuses the grab
|
|
8186
|
+
// outright rather than letting the drag run and correcting afterwards:
|
|
8187
|
+
// a centre snapped back on release still tracks the cursor for the
|
|
8188
|
+
// whole drag, which reads as a broken lock. Refusing here also leaves
|
|
8189
|
+
// the hover cursor at 'default' and lets the drag fall through to the
|
|
8190
|
+
// plot's own pan, so the ring is simply not a handle any more.
|
|
8191
|
+
if (w.lock_center) continue;
|
|
8186
8192
|
if (Math.abs(Math.hypot(mx-ccx, my-ccy) - cr) <= Math.max(HR, cr*0.18) ||
|
|
8187
8193
|
Math.hypot(mx-ccx, my-ccy) <= HR)
|
|
8188
8194
|
return { idx:i, mode:'move', snapW:{...w}, startMX:mx, startMY:my };
|
|
@@ -8441,7 +8447,9 @@ fn fs(in : VsOut) -> @location(0) vec4<f32> {
|
|
|
8441
8447
|
}
|
|
8442
8448
|
|
|
8443
8449
|
if (w.type === 'circle') {
|
|
8444
|
-
|
|
8450
|
+
// A drag already in flight when `lock_center` is turned on must not keep
|
|
8451
|
+
// translating the widget: the hit-test gate only runs at grab time.
|
|
8452
|
+
if (d.mode === 'move' && !w.lock_center) {
|
|
8445
8453
|
w.cx = s.cx + dix; w.cy = s.cy + diy;
|
|
8446
8454
|
} else if (d.mode === 'resize_r') {
|
|
8447
8455
|
// distance from centre in image-px
|
|
@@ -45,6 +45,40 @@ def _binary_transport_active() -> bool:
|
|
|
45
45
|
return os.environ.get("APL_BINARY_TRANSPORT") == "1"
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
def _reduce_mask_any(mask, out_h: int, out_w: int):
|
|
49
|
+
"""Reduce a boolean *mask* to ``(out_h, out_w)`` with a block ANY.
|
|
50
|
+
|
|
51
|
+
A block of the source is True in the output if ANY of its pixels is. For a
|
|
52
|
+
mask that is the only defensible reduction: it marks *where objects are*,
|
|
53
|
+
and an object smaller than one output block must not vanish because the
|
|
54
|
+
sampled pixel happened to miss it.
|
|
55
|
+
|
|
56
|
+
The tail rows/columns that do not divide evenly are folded into the last
|
|
57
|
+
block rather than cropped, so an object at the right or bottom edge is kept.
|
|
58
|
+
"""
|
|
59
|
+
import numpy as _np
|
|
60
|
+
|
|
61
|
+
src_h, src_w = mask.shape
|
|
62
|
+
if (src_h, src_w) == (out_h, out_w):
|
|
63
|
+
return mask
|
|
64
|
+
ys = max(1, src_h // out_h)
|
|
65
|
+
xs = max(1, src_w // out_w)
|
|
66
|
+
keep_h, keep_w = (src_h // ys) * ys, (src_w // xs) * xs
|
|
67
|
+
small = mask[:keep_h, :keep_w].reshape(
|
|
68
|
+
keep_h // ys, ys, keep_w // xs, xs).any(axis=(1, 3))
|
|
69
|
+
out = _np.zeros((out_h, out_w), bool)
|
|
70
|
+
sh, sw = min(out_h, small.shape[0]), min(out_w, small.shape[1])
|
|
71
|
+
out[:sh, :sw] = small[:sh, :sw]
|
|
72
|
+
# Fold any cropped tail into the final block so edge objects survive.
|
|
73
|
+
if keep_h < src_h and sh > 0:
|
|
74
|
+
out[sh - 1, :sw] |= mask[keep_h:, :keep_w].reshape(
|
|
75
|
+
1, src_h - keep_h, keep_w // xs, xs).any(axis=(1, 3))[0, :sw]
|
|
76
|
+
if keep_w < src_w and sw > 0:
|
|
77
|
+
out[:sh, sw - 1] |= mask[:keep_h, keep_w:].reshape(
|
|
78
|
+
keep_h // ys, ys, 1, src_w - keep_w).any(axis=(1, 3))[:sh, 0]
|
|
79
|
+
return out
|
|
80
|
+
|
|
81
|
+
|
|
48
82
|
class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
|
|
49
83
|
"""2-D image plot panel.
|
|
50
84
|
|
|
@@ -1115,6 +1149,13 @@ class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
|
|
|
1115
1149
|
Boolean array aligned to the image data. ``True`` / non-zero
|
|
1116
1150
|
pixels are filled with *color* at transparency *alpha*.
|
|
1117
1151
|
Pass ``None`` to clear the overlay.
|
|
1152
|
+
|
|
1153
|
+
In TILE mode pass the mask at the FULL image resolution: the
|
|
1154
|
+
renderer composites the mask against the OVERVIEW texture, so it
|
|
1155
|
+
is reduced to the base grid here. A mask already at the base grid
|
|
1156
|
+
is accepted unchanged. (Both are accepted because neither is
|
|
1157
|
+
wrong; what was wrong was accepting only the full-resolution one
|
|
1158
|
+
and then shipping bytes the renderer silently discards.)
|
|
1118
1159
|
color : str, optional
|
|
1119
1160
|
CSS hex colour for the overlay, e.g. ``"#ff4444"``. Default red.
|
|
1120
1161
|
Must be in ``#RRGGBB`` format.
|
|
@@ -1137,14 +1178,32 @@ class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
|
|
|
1137
1178
|
self._state["overlay_mask_alpha"] = alpha
|
|
1138
1179
|
else:
|
|
1139
1180
|
arr = np.asarray(mask)
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1181
|
+
ih, iw = self._state["image_height"], self._state["image_width"]
|
|
1182
|
+
bh = int(self._state.get("base_height") or 0)
|
|
1183
|
+
bw = int(self._state.get("base_width") or 0)
|
|
1184
|
+
# THE TILE-MODE SHAPE TRAP. The renderer sizes the mask against
|
|
1185
|
+
# `base_width || image_width` -- the OVERVIEW grid -- but tile mode
|
|
1186
|
+
# sets image_width/height to the FULL native frame. So validating
|
|
1187
|
+
# only against the image shape rejected the one shape that renders
|
|
1188
|
+
# and accepted the one the renderer drops on the floor
|
|
1189
|
+
# (`mBytes.length===iw*ih` else `maskCache=null`, silently). Both
|
|
1190
|
+
# shapes are legal here; the overview is what actually ships.
|
|
1191
|
+
tiled = bw > 0 and bh > 0 and (bh, bw) != (ih, iw)
|
|
1192
|
+
if arr.shape != (ih, iw) and not (tiled and arr.shape == (bh, bw)):
|
|
1193
|
+
want = f"({ih} x {iw})"
|
|
1194
|
+
if tiled:
|
|
1195
|
+
want += f" or the tile overview ({bh} x {bw})"
|
|
1196
|
+
raise ValueError(f"mask shape {arr.shape} does not match image {want}")
|
|
1145
1197
|
# For origin='lower' the image data was flipped; flip mask to match.
|
|
1146
1198
|
if self._origin == "lower":
|
|
1147
1199
|
arr = np.flipud(arr)
|
|
1200
|
+
if tiled and arr.shape == (ih, iw):
|
|
1201
|
+
# Block ANY, never a subsample: a mask marks objects that are
|
|
1202
|
+
# often only a few pixels across, and striding a 4096² mask
|
|
1203
|
+
# down to 1024² drops three quarters of them at random. ANY
|
|
1204
|
+
# keeps every object visible at the cost of fattening it, which
|
|
1205
|
+
# is the right trade for an overlay you are looking at.
|
|
1206
|
+
arr = _reduce_mask_any(np.asarray(arr, dtype=bool), bh, bw)
|
|
1148
1207
|
# Convert to uint8: True/non-zero → 255, False/zero → 0
|
|
1149
1208
|
u8 = (np.asarray(arr, dtype=bool).view(np.uint8) * 255).astype(np.uint8)
|
|
1150
1209
|
self._state["overlay_mask_b64"] = base64.b64encode(u8.tobytes()).decode("ascii")
|
|
@@ -1672,15 +1731,24 @@ class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
|
|
|
1672
1731
|
def add_circle_widget(self, cx: float | None = None, cy: float | None = None,
|
|
1673
1732
|
r: float | None = None, color: str = "#00e5ff",
|
|
1674
1733
|
linewidth: float = 2,
|
|
1675
|
-
show_handles: bool = True
|
|
1676
|
-
|
|
1734
|
+
show_handles: bool = True,
|
|
1735
|
+
lock_center: bool = False) -> CircleWidget:
|
|
1736
|
+
"""Add a draggable circle overlay.
|
|
1737
|
+
|
|
1738
|
+
``lock_center`` pins the centre and leaves only the radius draggable —
|
|
1739
|
+
a grab on the ring body is refused and pans the plot instead. Use it
|
|
1740
|
+
when the centre is fixed by the data (a ring on a power spectrum is
|
|
1741
|
+
centred on the DC term), so it cannot be nudged off and silently
|
|
1742
|
+
corrupt every radius measured from it.
|
|
1743
|
+
"""
|
|
1677
1744
|
iw, ih = self._state["image_width"], self._state["image_height"]
|
|
1678
1745
|
widget = CircleWidget(lambda: None,
|
|
1679
1746
|
cx=float(cx) if cx is not None else iw / 2,
|
|
1680
1747
|
cy=float(cy) if cy is not None else ih / 2,
|
|
1681
1748
|
r=float(r) if r is not None else iw * 0.1,
|
|
1682
1749
|
color=color, linewidth=linewidth,
|
|
1683
|
-
show_handles=show_handles
|
|
1750
|
+
show_handles=show_handles,
|
|
1751
|
+
lock_center=lock_center)
|
|
1684
1752
|
widget._push_fn = self._make_widget_push_fn(widget)
|
|
1685
1753
|
self._widgets[widget.id] = widget
|
|
1686
1754
|
self._push()
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tests/test_interactive/test_circle_lock_center.py
|
|
3
|
+
=================================================
|
|
4
|
+
|
|
5
|
+
``lock_center`` — a circle whose centre is fixed by the data.
|
|
6
|
+
|
|
7
|
+
Why it exists: a ring measured on a power spectrum is centred on the DC term.
|
|
8
|
+
Its centre is not a free parameter, so a draggable one is a control that can
|
|
9
|
+
only ever be wrong — and a ring nudged off-centre silently corrupts every
|
|
10
|
+
radius measured from it.
|
|
11
|
+
|
|
12
|
+
Why it is enforced in the HIT-TEST and not afterwards. The obvious
|
|
13
|
+
implementation is a Python handler that watches the widget and snaps the centre
|
|
14
|
+
back when the drag settles. It cannot work: ``_doDrag2d`` recomputes the
|
|
15
|
+
position every frame from its own grab-time snapshot, so a pushed-back centre
|
|
16
|
+
is overwritten on the next mousemove. The ring tracks the cursor for the whole
|
|
17
|
+
drag and jumps back on release, which reads as a broken lock rather than a
|
|
18
|
+
locked ring. Refusing the grab is the only place the constraint holds.
|
|
19
|
+
|
|
20
|
+
Coordinate system mirrors figure_esm.js — see ``_img_to_page``.
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
import pytest
|
|
26
|
+
|
|
27
|
+
import anyplotlib as apl
|
|
28
|
+
from anyplotlib.widgets import CircleWidget
|
|
29
|
+
from anyplotlib.tests.test_interactive._event_test_utils import (
|
|
30
|
+
_collect_events, _get_events,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
FIG_W, FIG_H = 400, 300
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
37
|
+
# 1. Python API — the flag has to reach the JS renderer to do anything
|
|
38
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
39
|
+
|
|
40
|
+
class TestLockCenterAttribute:
|
|
41
|
+
def test_defaults_to_unlocked(self):
|
|
42
|
+
w = CircleWidget(lambda: None, cx=16, cy=16, r=6)
|
|
43
|
+
assert w.lock_center is False
|
|
44
|
+
|
|
45
|
+
def test_stores_the_flag(self):
|
|
46
|
+
w = CircleWidget(lambda: None, cx=16, cy=16, r=6, lock_center=True)
|
|
47
|
+
assert w.lock_center is True
|
|
48
|
+
|
|
49
|
+
def test_reaches_the_state_dict(self):
|
|
50
|
+
"""JS reads the widget dict, so the flag has to survive to_dict()."""
|
|
51
|
+
w = CircleWidget(lambda: None, cx=16, cy=16, r=6, lock_center=True)
|
|
52
|
+
assert w.to_dict()["lock_center"] is True
|
|
53
|
+
|
|
54
|
+
def test_add_circle_widget_passes_it_through(self):
|
|
55
|
+
fig, ax = apl.subplots(1, 1, figsize=(FIG_W, FIG_H))
|
|
56
|
+
v = ax.imshow(np.zeros((32, 32), dtype=np.float32))
|
|
57
|
+
w = v.add_circle_widget(cx=16, cy=16, r=6, lock_center=True)
|
|
58
|
+
assert w.lock_center is True
|
|
59
|
+
|
|
60
|
+
def test_add_circle_widget_defaults_to_unlocked(self):
|
|
61
|
+
fig, ax = apl.subplots(1, 1, figsize=(FIG_W, FIG_H))
|
|
62
|
+
v = ax.imshow(np.zeros((32, 32), dtype=np.float32))
|
|
63
|
+
assert v.add_circle_widget(cx=16, cy=16, r=6).lock_center is False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
67
|
+
# 2. Real drags in a real browser — the only place the constraint is enforced
|
|
68
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
69
|
+
|
|
70
|
+
_OVERLAY_RECT_JS = """() => {
|
|
71
|
+
for (const cv of document.querySelectorAll('canvas')) {
|
|
72
|
+
if (getComputedStyle(cv).pointerEvents === 'all') {
|
|
73
|
+
const r = cv.getBoundingClientRect();
|
|
74
|
+
return {left:r.left, top:r.top, w:r.width, h:r.height};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}"""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _img_to_page(page, ix, iy, iw=32, ih=32):
|
|
82
|
+
"""Image px → page coords, for an overlay laid out 'contain' with no zoom."""
|
|
83
|
+
r = page.evaluate(_OVERLAY_RECT_JS)
|
|
84
|
+
assert r is not None, "overlay canvas not found"
|
|
85
|
+
s = min(r["w"] / iw, r["h"] / ih)
|
|
86
|
+
ox = (r["w"] - iw * s) / 2.0
|
|
87
|
+
oy = (r["h"] - ih * s) / 2.0
|
|
88
|
+
return r["left"] + ox + ix * s, r["top"] + oy + iy * s
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _setup(interact_page, lock_center):
|
|
92
|
+
fig, ax = apl.subplots(1, 1, figsize=(FIG_W, FIG_H))
|
|
93
|
+
v = ax.imshow(np.zeros((32, 32), dtype=np.float32))
|
|
94
|
+
v.add_circle_widget(cx=16, cy=16, r=6, color="#ff0000",
|
|
95
|
+
lock_center=lock_center)
|
|
96
|
+
page = interact_page(fig)
|
|
97
|
+
_collect_events(page)
|
|
98
|
+
return page
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _drag(page, frm, to, steps=8):
|
|
102
|
+
page.mouse.move(*_img_to_page(page, *frm))
|
|
103
|
+
page.mouse.down()
|
|
104
|
+
page.mouse.move(*_img_to_page(page, *to), steps=steps)
|
|
105
|
+
page.mouse.up()
|
|
106
|
+
page.wait_for_timeout(80)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.mark.usefixtures("interact_page")
|
|
110
|
+
class TestLockedCentreDrag:
|
|
111
|
+
def test_body_drag_does_not_move_a_locked_circle(self, interact_page):
|
|
112
|
+
"""THE regression. Grab the centre and pull: nothing about the widget
|
|
113
|
+
may change — not even transiently, which is why the assertion is on
|
|
114
|
+
every event and not only the last one."""
|
|
115
|
+
page = _setup(interact_page, lock_center=True)
|
|
116
|
+
|
|
117
|
+
_drag(page, (16, 16), (24, 24))
|
|
118
|
+
|
|
119
|
+
moved = [e for e in _get_events(page)
|
|
120
|
+
if e.get("cx") is not None
|
|
121
|
+
and (abs(e["cx"] - 16.0) > 0.5 or abs(e["cy"] - 16.0) > 0.5)]
|
|
122
|
+
assert not moved, (
|
|
123
|
+
f"a locked circle's centre moved during the drag: {moved[:3]}")
|
|
124
|
+
|
|
125
|
+
def test_an_unlocked_circle_still_moves(self, interact_page):
|
|
126
|
+
"""The counterpart, so a lock that accidentally applied to every circle
|
|
127
|
+
would fail here rather than passing the whole file."""
|
|
128
|
+
page = _setup(interact_page, lock_center=False)
|
|
129
|
+
|
|
130
|
+
_drag(page, (16, 16), (20, 22))
|
|
131
|
+
|
|
132
|
+
last = _get_events(page, "pointer_up")[-1]
|
|
133
|
+
assert last["cx"] > 16.0 and last["cy"] > 16.0
|
|
134
|
+
assert last["r"] == pytest.approx(6.0, abs=0.5)
|
|
135
|
+
|
|
136
|
+
def test_the_radius_handle_still_drags_when_locked(self, interact_page):
|
|
137
|
+
"""A locked centre must not cost the measurement. The handle sits at
|
|
138
|
+
the east point (cx+r, cy) = (22, 16); drag it out to (26, 16)."""
|
|
139
|
+
page = _setup(interact_page, lock_center=True)
|
|
140
|
+
|
|
141
|
+
_drag(page, (22, 16), (26, 16))
|
|
142
|
+
|
|
143
|
+
ups = _get_events(page, "pointer_up")
|
|
144
|
+
assert ups, "the radius drag should still emit a pointer_up"
|
|
145
|
+
last = ups[-1]
|
|
146
|
+
assert last["r"] > 6.0, "the radius handle stopped working under the lock"
|
|
147
|
+
assert last["cx"] == pytest.approx(16.0, abs=0.5)
|
|
148
|
+
assert last["cy"] == pytest.approx(16.0, abs=0.5)
|
|
149
|
+
|
|
150
|
+
def test_a_locked_ring_band_is_not_grabbable_either(self, interact_page):
|
|
151
|
+
"""Not just the centre hotspot: the ring BAND is the circle's move
|
|
152
|
+
target, and it is the part a user actually grabs. (22, 16) is the
|
|
153
|
+
handle, so aim at the west point (10, 16) — same band, no handle."""
|
|
154
|
+
page = _setup(interact_page, lock_center=True)
|
|
155
|
+
|
|
156
|
+
_drag(page, (10, 16), (4, 16))
|
|
157
|
+
|
|
158
|
+
moved = [e for e in _get_events(page)
|
|
159
|
+
if e.get("cx") is not None and abs(e["cx"] - 16.0) > 0.5]
|
|
160
|
+
assert not moved, f"the ring band dragged the locked circle: {moved[:3]}"
|
|
@@ -575,6 +575,60 @@ class TestImshowOverlayMask:
|
|
|
575
575
|
with pytest.raises(ValueError, match="mask shape"):
|
|
576
576
|
plot.set_overlay_mask(bad_mask)
|
|
577
577
|
|
|
578
|
+
def test_overlay_mask_in_TILE_mode_ships_the_overview_size(self):
|
|
579
|
+
"""The mask must be sized for what the RENDERER checks.
|
|
580
|
+
|
|
581
|
+
The renderer sizes the mask against ``base_width || image_width`` — the
|
|
582
|
+
OVERVIEW grid — and on a mismatch sets ``maskCache=null``: no error, no
|
|
583
|
+
overlay, nothing in the log. Tile mode sets ``image_width`` to the FULL
|
|
584
|
+
native frame, so validating a mask only against the image shape both
|
|
585
|
+
rejected the one shape that renders and accepted the one the renderer
|
|
586
|
+
drops. A caller that did the reduction itself got a ValueError; one that
|
|
587
|
+
did not got 22 MB of bytes silently discarded.
|
|
588
|
+
|
|
589
|
+
Either shape in, overview-sized bytes out.
|
|
590
|
+
"""
|
|
591
|
+
import base64
|
|
592
|
+
|
|
593
|
+
plot = apl.subplots(1, 1)[1].imshow(
|
|
594
|
+
np.zeros((2048, 2048), np.uint8), tile="auto")
|
|
595
|
+
st = plot._state
|
|
596
|
+
bw, bh = st.get("base_width"), st.get("base_height")
|
|
597
|
+
assert bw and bh and (bw, bh) != (st["image_width"], st["image_height"]), \
|
|
598
|
+
"this test needs a genuinely tiled plot with a smaller base grid"
|
|
599
|
+
|
|
600
|
+
for shape in [(st["image_height"], st["image_width"]), (bh, bw)]:
|
|
601
|
+
mask = np.zeros(shape, bool)
|
|
602
|
+
mask[::5, ::5] = True
|
|
603
|
+
plot.set_overlay_mask(mask)
|
|
604
|
+
sent = base64.b64decode(st["overlay_mask_b64"])
|
|
605
|
+
assert len(sent) == bw * bh, (
|
|
606
|
+
f"a {shape} mask shipped {len(sent)} bytes; the renderer "
|
|
607
|
+
f"expects {bw * bh} and drops anything else silently")
|
|
608
|
+
assert any(sent), "the reduction emptied the mask"
|
|
609
|
+
|
|
610
|
+
def test_overlay_mask_reduction_is_block_ANY_not_a_subsample(self):
|
|
611
|
+
"""A single-pixel object 4 blocks in must survive the reduction.
|
|
612
|
+
|
|
613
|
+
Objects here are often a few pixels across; a strided sample of a 2048²
|
|
614
|
+
mask at 512² keeps one pixel in sixteen and drops them at random.
|
|
615
|
+
"""
|
|
616
|
+
import base64
|
|
617
|
+
|
|
618
|
+
plot = apl.subplots(1, 1)[1].imshow(
|
|
619
|
+
np.zeros((2048, 2048), np.uint8), tile="auto")
|
|
620
|
+
st = plot._state
|
|
621
|
+
bw, bh = st["base_width"], st["base_height"]
|
|
622
|
+
mask = np.zeros((st["image_height"], st["image_width"]), bool)
|
|
623
|
+
# Deliberately NOT on a block boundary — a subsample would miss it.
|
|
624
|
+
mask[537, 921] = True
|
|
625
|
+
plot.set_overlay_mask(mask)
|
|
626
|
+
sent = np.frombuffer(base64.b64decode(st["overlay_mask_b64"]), np.uint8)
|
|
627
|
+
# Bytes are 0/255, so COUNT the lit ones rather than summing them.
|
|
628
|
+
assert np.count_nonzero(sent.reshape(bh, bw)) == 1, (
|
|
629
|
+
"the single lit pixel did not survive the reduction — this is a "
|
|
630
|
+
"subsample, not a block ANY")
|
|
631
|
+
|
|
578
632
|
def test_set_overlay_mask_alpha_boundary(self):
|
|
579
633
|
plot = _img(n=16)
|
|
580
634
|
mask = np.zeros((16, 16), dtype=bool)
|
|
@@ -74,13 +74,29 @@ class CircleWidget(Widget):
|
|
|
74
74
|
Outline stroke width in px. Default 2.
|
|
75
75
|
show_handles : bool, optional
|
|
76
76
|
Draw the radius grab handle. Default ``True``.
|
|
77
|
+
lock_center : bool, optional
|
|
78
|
+
Pin ``cx, cy`` and let only the radius change. Default ``False``.
|
|
79
|
+
|
|
80
|
+
The centre is refused at HIT-TEST time, not corrected afterwards: a
|
|
81
|
+
grab on the ring body is simply not a widget grab, so it falls through
|
|
82
|
+
to the plot's own pan and the hover cursor never promises a move.
|
|
83
|
+
Enforcing it from Python instead — snapping the centre back when the
|
|
84
|
+
drag settles — cannot work, because the JS drag recomputes the position
|
|
85
|
+
from its own grab-time snapshot on every frame; the ring tracks the
|
|
86
|
+
cursor for the whole drag and only jumps back on release.
|
|
87
|
+
|
|
88
|
+
Use it when the centre is not a free parameter — a ring on a power
|
|
89
|
+
spectrum is centred on the DC term, so a draggable centre is a control
|
|
90
|
+
that can only ever be wrong, and one nudged off-centre silently
|
|
91
|
+
corrupts every radius measured from it.
|
|
77
92
|
"""
|
|
78
93
|
def __init__(self, push_fn, *, cx, cy, r, color="#00e5ff",
|
|
79
|
-
linewidth=2, show_handles=True):
|
|
94
|
+
linewidth=2, show_handles=True, lock_center=False):
|
|
80
95
|
super().__init__("circle", push_fn,
|
|
81
96
|
cx=float(cx), cy=float(cy), r=float(r), color=color,
|
|
82
97
|
linewidth=float(linewidth),
|
|
83
|
-
show_handles=bool(show_handles)
|
|
98
|
+
show_handles=bool(show_handles),
|
|
99
|
+
lock_center=bool(lock_center))
|
|
84
100
|
|
|
85
101
|
|
|
86
102
|
class AnnularWidget(Widget):
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<title>anyplotlib – redirecting…</title>
|
|
6
6
|
<!-- Redirect to the latest dev docs. -->
|
|
7
|
-
<meta http-equiv="refresh" content="0; url=v0.7.
|
|
8
|
-
<link rel="canonical" href="v0.7.
|
|
7
|
+
<meta http-equiv="refresh" content="0; url=v0.7.3/" />
|
|
8
|
+
<link rel="canonical" href="v0.7.3/" />
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<p>
|
|
12
|
-
Redirecting to <a href="v0.7.
|
|
12
|
+
Redirecting to <a href="v0.7.3/">v0.7.3 documentation</a>…
|
|
13
13
|
</p>
|
|
14
14
|
</body>
|
|
15
15
|
</html>
|
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
"version": "dev",
|
|
5
5
|
"url": "https://cssfrancis.github.io/anyplotlib/dev/"
|
|
6
6
|
},
|
|
7
|
+
{
|
|
8
|
+
"name": "v0.7.3 (stable)",
|
|
9
|
+
"version": "v0.7.3",
|
|
10
|
+
"url": "https://cssfrancis.github.io/anyplotlib/v0.7.3/"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "v0.7.2 (stable)",
|
|
14
|
+
"version": "v0.7.2",
|
|
15
|
+
"url": "https://cssfrancis.github.io/anyplotlib/v0.7.2/"
|
|
16
|
+
},
|
|
7
17
|
{
|
|
8
18
|
"name": "v0.7.1 (stable)",
|
|
9
19
|
"version": "v0.7.1",
|
|
@@ -17,7 +17,7 @@ from _sg_html_scraper import ViewerScraper # noqa: E402
|
|
|
17
17
|
project = "anyplotlib"
|
|
18
18
|
copyright = "2026, anyplotlib contributors"
|
|
19
19
|
author = "anyplotlib contributors"
|
|
20
|
-
release = "0.7.
|
|
20
|
+
release = "0.7.3"
|
|
21
21
|
|
|
22
22
|
# When built in CI the workflow sets DOCS_VERSION to the tag name (e.g.
|
|
23
23
|
# "v0.1.0") or "dev". Fall back to "dev" for local builds.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anyplotlib-0.7.1 → anyplotlib-0.7.3}/Examples/Interactive/plot_segment_by_contrast_advanced.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|