anyplotlib 0.7.0__tar.gz → 0.7.2__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.
Files changed (278) hide show
  1. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/CHANGELOG.rst +40 -0
  2. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/PKG-INFO +1 -1
  3. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/figure/_figure.py +27 -3
  4. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/keys.py +5 -8
  5. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/plot2d/_plot2d.py +64 -5
  6. anyplotlib-0.7.2/anyplotlib/tests/test_embed/test_export_pixel_tokens.py +174 -0
  7. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_imshow.py +54 -0
  8. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/_root/index.html +3 -3
  9. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/_root/switcher.json +10 -0
  10. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/conf.py +1 -1
  11. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/pyproject.toml +1 -1
  12. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/.github/workflows/ci.yml +0 -0
  13. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/.github/workflows/docs.yml +0 -0
  14. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/.github/workflows/prepare_release.yml +0 -0
  15. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/.github/workflows/release.yml +0 -0
  16. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/.github/workflows/tests.yml +0 -0
  17. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/.gitignore +0 -0
  18. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/AGENTS.md +0 -0
  19. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Benchmarks/README.rst +0 -0
  20. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Benchmarks/plot_benchmark_comparison.py +0 -0
  21. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/README.rst +0 -0
  22. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_3d_spectral_viewer.py +0 -0
  23. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_eels_explorer.py +0 -0
  24. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_figure_annotations.py +0 -0
  25. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_interactive_fft.py +0 -0
  26. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_interactive_fitting.py +0 -0
  27. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_ipf_density_map.py +0 -0
  28. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_ipf_explorer.py +0 -0
  29. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_key_bindings.py +0 -0
  30. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_particle_picker.py +0 -0
  31. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_point_widget.py +0 -0
  32. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_segment_by_contrast.py +0 -0
  33. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_segment_by_contrast_advanced.py +0 -0
  34. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_spectra_roi_inspector.py +0 -0
  35. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_star_globe_explorer.py +0 -0
  36. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_threshold_explorer.py +0 -0
  37. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Interactive/plot_voxel_grain_explorer.py +0 -0
  38. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Markers/README.rst +0 -0
  39. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Markers/plot_arrows.py +0 -0
  40. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Markers/plot_circles.py +0 -0
  41. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Markers/plot_ellipses.py +0 -0
  42. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Markers/plot_horizontal_lines.py +0 -0
  43. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Markers/plot_line_segments.py +0 -0
  44. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Markers/plot_points.py +0 -0
  45. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Markers/plot_polygons.py +0 -0
  46. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Markers/plot_rectangles.py +0 -0
  47. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Markers/plot_squares.py +0 -0
  48. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Markers/plot_texts.py +0 -0
  49. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Markers/plot_transform_markers.py +0 -0
  50. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Markers/plot_vertical_lines.py +0 -0
  51. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/README.rst +0 -0
  52. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_3d.py +0 -0
  53. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_3d_texture.py +0 -0
  54. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_bar.py +0 -0
  55. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_gpu_voxels.py +0 -0
  56. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_gridspec_custom.py +0 -0
  57. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_image2d.py +0 -0
  58. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_inset.py +0 -0
  59. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_key_overlays.py +0 -0
  60. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_label_formatting.py +0 -0
  61. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_line_styles.py +0 -0
  62. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_pcolormesh.py +0 -0
  63. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_spectra1d.py +0 -0
  64. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_step_and_log.py +0 -0
  65. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_tile_backend.py +0 -0
  66. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/PlotTypes/plot_twinx.py +0 -0
  67. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/README.rst +0 -0
  68. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Widgets/README.rst +0 -0
  69. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Widgets/plot_widget1d_hline.py +0 -0
  70. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Widgets/plot_widget1d_point.py +0 -0
  71. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Widgets/plot_widget1d_range.py +0 -0
  72. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Widgets/plot_widget1d_vline.py +0 -0
  73. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Widgets/plot_widget2d_annular.py +0 -0
  74. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Widgets/plot_widget2d_arrow.py +0 -0
  75. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Widgets/plot_widget2d_circle.py +0 -0
  76. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Widgets/plot_widget2d_crosshair.py +0 -0
  77. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Widgets/plot_widget2d_label.py +0 -0
  78. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Widgets/plot_widget2d_polygon.py +0 -0
  79. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Widgets/plot_widget2d_rectangle.py +0 -0
  80. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Examples/Widgets/plot_widget3d_plane.py +0 -0
  81. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/LICENSE +0 -0
  82. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/Makefile +0 -0
  83. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/README.md +0 -0
  84. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/FIGURE_ESM.md +0 -0
  85. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/__init__.py +0 -0
  86. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/_base_plot.py +0 -0
  87. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/_binary_frame.py +0 -0
  88. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/_electron.py +0 -0
  89. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/_repr_utils.py +0 -0
  90. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/_utils.py +0 -0
  91. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/axes/__init__.py +0 -0
  92. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/axes/_axes.py +0 -0
  93. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/axes/_inset_axes.py +0 -0
  94. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/callbacks.py +0 -0
  95. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/conftest.py +0 -0
  96. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/embed.py +0 -0
  97. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/figure/__init__.py +0 -0
  98. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/figure/_gridspec.py +0 -0
  99. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/figure/_subplots.py +0 -0
  100. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/figure_esm.js +0 -0
  101. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/markers.py +0 -0
  102. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/plot1d/__init__.py +0 -0
  103. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/plot1d/_plot1d.py +0 -0
  104. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/plot1d/_plotbar.py +0 -0
  105. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/plot2d/__init__.py +0 -0
  106. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/plot2d/_layer.py +0 -0
  107. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/plot2d/_plotmesh.py +0 -0
  108. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/plot2d/_tile_backend.py +0 -0
  109. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/plot3d/__init__.py +0 -0
  110. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/plot3d/_plot3d.py +0 -0
  111. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/plotxy/__init__.py +0 -0
  112. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/plotxy/_plotxy.py +0 -0
  113. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/sphinx_anywidget/__init__.py +0 -0
  114. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/sphinx_anywidget/_directive.py +0 -0
  115. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/sphinx_anywidget/_repr_utils.py +0 -0
  116. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/sphinx_anywidget/_scraper.py +0 -0
  117. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/sphinx_anywidget/_wheel_builder.py +0 -0
  118. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/sphinx_anywidget/static/anywidget_bridge.js +0 -0
  119. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/sphinx_anywidget/static/anywidget_overlay.css +0 -0
  120. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/sphinx_anywidget/tests/__init__.py +0 -0
  121. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/sphinx_anywidget/tests/conftest.py +0 -0
  122. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/sphinx_anywidget/tests/test_directive.py +0 -0
  123. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/sphinx_anywidget/tests/test_init.py +0 -0
  124. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/sphinx_anywidget/tests/test_repr_utils.py +0 -0
  125. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/sphinx_anywidget/tests/test_scraper.py +0 -0
  126. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/sphinx_anywidget/tests/test_wheel_builder.py +0 -0
  127. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/__init__.py +0 -0
  128. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/_gpu_clim_check.cjs +0 -0
  129. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/_png_utils.py +0 -0
  130. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/bar_basic.png +0 -0
  131. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/gridspec_3col_equal_spectra.png +0 -0
  132. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/gridspec_asymmetric_width_ratios.png +0 -0
  133. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/gridspec_height_ratio_image_histogram.png +0 -0
  134. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/gridspec_image_two_spectra.png +0 -0
  135. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/gridspec_side_by_side_1d.png +0 -0
  136. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/gridspec_spanning_top_two_bottom.png +0 -0
  137. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/imshow_axis_off.png +0 -0
  138. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/imshow_checkerboard.png +0 -0
  139. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/imshow_gradient.png +0 -0
  140. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/imshow_labels.png +0 -0
  141. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/imshow_viridis.png +0 -0
  142. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/inset_1d.png +0 -0
  143. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/inset_maximized.png +0 -0
  144. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/inset_minimized.png +0 -0
  145. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/inset_normal_2d.png +0 -0
  146. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/inset_stacked.png +0 -0
  147. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/inset_stacked_one_minimized.png +0 -0
  148. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/pcolormesh_uniform.png +0 -0
  149. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/plot1d_all_linestyles.png +0 -0
  150. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/plot1d_alpha.png +0 -0
  151. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/plot1d_axis_off.png +0 -0
  152. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/plot1d_dashed.png +0 -0
  153. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/plot1d_marker_symbols.png +0 -0
  154. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/plot1d_markers.png +0 -0
  155. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/plot1d_multi.png +0 -0
  156. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/plot1d_sine.png +0 -0
  157. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/plot1d_title.png +0 -0
  158. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/plot1d_twinx.png +0 -0
  159. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/plot3d_surface.png +0 -0
  160. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/baselines/subplots_2x1.png +0 -0
  161. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/conftest.py +0 -0
  162. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_benchmarks/__init__.py +0 -0
  163. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_benchmarks/baselines.json +0 -0
  164. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_benchmarks/benchmarks/baselines.json +0 -0
  165. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_benchmarks/test_benchmarks.py +0 -0
  166. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_benchmarks/test_benchmarks_py.py +0 -0
  167. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_documentation/__init__.py +0 -0
  168. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_documentation/test_bridge.py +0 -0
  169. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_documentation/test_push_hook.py +0 -0
  170. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_documentation/test_scraper.py +0 -0
  171. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_electron/__init__.py +0 -0
  172. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_electron/test_binary_frame.py +0 -0
  173. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_embed/__init__.py +0 -0
  174. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_embed/test_embed_api.py +0 -0
  175. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_embed/test_embed_mount.py +0 -0
  176. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_embed/test_export_png.py +0 -0
  177. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_embed/test_export_widget_sync.py +0 -0
  178. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_examples/__init__.py +0 -0
  179. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_examples/test_interactive_examples.py +0 -0
  180. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/__init__.py +0 -0
  181. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/_event_test_utils.py +0 -0
  182. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_blit_audit.py +0 -0
  183. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_callbacks_playwright.py +0 -0
  184. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_callbacks_unit.py +0 -0
  185. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_edit_chrome.py +0 -0
  186. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_edit_chrome_playwright.py +0 -0
  187. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_event_pause_hold.py +0 -0
  188. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_event_plots.py +0 -0
  189. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_event_settled.py +0 -0
  190. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_events_regression.py +0 -0
  191. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_orbit_direction.py +0 -0
  192. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_pointer_down_1d.py +0 -0
  193. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_range_orientation_snap.py +0 -0
  194. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_star_globe_linking.py +0 -0
  195. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_tile_parity_playwright.py +0 -0
  196. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_title.py +0 -0
  197. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_touch.py +0 -0
  198. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_widget_brush.py +0 -0
  199. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_widget_max_extent.py +0 -0
  200. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_widget_set_notify.py +0 -0
  201. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_widgets.py +0 -0
  202. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_interactive/test_widgets_line_2d.py +0 -0
  203. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_keys/test_keys.py +0 -0
  204. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_labels/__init__.py +0 -0
  205. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_labels/test_label_api.py +0 -0
  206. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_labels/test_label_rendering.py +0 -0
  207. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_labels/test_no_clipping.py +0 -0
  208. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_labels/test_ylabel_clearance.py +0 -0
  209. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_layouts/__init__.py +0 -0
  210. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_layouts/test_batch.py +0 -0
  211. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_layouts/test_coord_conversion.py +0 -0
  212. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_layouts/test_geom_channel.py +0 -0
  213. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_layouts/test_gridspec.py +0 -0
  214. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_layouts/test_inset.py +0 -0
  215. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_layouts/test_inset_callout.py +0 -0
  216. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_layouts/test_interaction.py +0 -0
  217. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_layouts/test_visual.py +0 -0
  218. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_markers/__init__.py +0 -0
  219. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_markers/test_marker_transforms.py +0 -0
  220. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_markers/test_markers.py +0 -0
  221. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_markers/test_per_marker_colors.py +0 -0
  222. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_markers/test_size_units.py +0 -0
  223. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot1d/__init__.py +0 -0
  224. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot1d/test_line_none_rendering.py +0 -0
  225. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot1d/test_plot1d.py +0 -0
  226. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot1d/test_plotbar.py +0 -0
  227. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/__init__.py +0 -0
  228. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_colorbar_gap_scalebar.py +0 -0
  229. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_detail_tile.py +0 -0
  230. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_first_paint_race.py +0 -0
  231. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_gpu_image.py +0 -0
  232. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_gpu_parity_playwright.py +0 -0
  233. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_imshow_rgb.py +0 -0
  234. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_layers.py +0 -0
  235. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_layers_playwright.py +0 -0
  236. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_pcolormesh.py +0 -0
  237. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_plot2d_api.py +0 -0
  238. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_rapid_frame_update.py +0 -0
  239. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_tile_backend.py +0 -0
  240. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_tile_diagnostics.py +0 -0
  241. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_tiled_imshow.py +0 -0
  242. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot2d/test_zoom_view_write.py +0 -0
  243. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot3d/__init__.py +0 -0
  244. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot3d/test_colors_highlight.py +0 -0
  245. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot3d/test_gpu_depth.py +0 -0
  246. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot3d/test_gpu_fallback.py +0 -0
  247. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot3d/test_gpu_hidden_reveal.py +0 -0
  248. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot3d/test_plot3d.py +0 -0
  249. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot3d/test_texture.py +0 -0
  250. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plot3d/test_voxels_planes.py +0 -0
  251. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/tests/test_plotxy/test_plotxy.py +0 -0
  252. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/widgets/__init__.py +0 -0
  253. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/widgets/_base.py +0 -0
  254. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/widgets/_widgets1d.py +0 -0
  255. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/widgets/_widgets2d.py +0 -0
  256. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/anyplotlib/widgets/_widgets3d.py +0 -0
  257. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/_sg_html_scraper.py +0 -0
  258. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/_static/anyplotlib.svg +0 -0
  259. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/_static/custom.css +0 -0
  260. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/_static/pyodide_bridge.js +0 -0
  261. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/_templates/autosummary/class.rst +0 -0
  262. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/api/callbacks.rst +0 -0
  263. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/api/figure.rst +0 -0
  264. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/api/figure_plots.rst +0 -0
  265. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/api/index.rst +0 -0
  266. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/api/keys.rst +0 -0
  267. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/api/markers.rst +0 -0
  268. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/api/widgets.rst +0 -0
  269. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/benchmarking.rst +0 -0
  270. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/dev/index.rst +0 -0
  271. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/embedding.rst +0 -0
  272. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/events.rst +0 -0
  273. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/getting_started.rst +0 -0
  274. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/docs/index.rst +0 -0
  275. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/make.bat +0 -0
  276. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/upcoming_changes/.gitkeep +0 -0
  277. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/upcoming_changes/README.rst +0 -0
  278. {anyplotlib-0.7.0 → anyplotlib-0.7.2}/uv.lock +0 -0
@@ -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.7.2 (2026-08-03)
14
+ ==================
15
+
16
+ Bug Fixes
17
+ ---------
18
+
19
+ - ``Plot2D.set_overlay_mask`` now works in TILE mode. The renderer sizes the mask
20
+ against ``base_width || image_width`` -- the tile overview grid -- but tile mode
21
+ sets ``image_width`` to the full native frame, so the shape check accepted only
22
+ the one shape the renderer silently discards (``maskCache = null``, no error) and
23
+ rejected the one that actually renders. On a 4096x4096 tiled plot neither a
24
+ 1024x1024 nor a 4096x4096 mask could be drawn: the first raised ``ValueError``,
25
+ the second encoded 22.4 MB the renderer dropped. Both shapes are now accepted and
26
+ a full-resolution mask is reduced to the overview grid with a block ANY -- never
27
+ a subsample, so an object a few pixels across cannot vanish into a skipped
28
+ sample.
29
+
30
+
31
+ 0.7.1 (2026-08-02)
32
+ ==================
33
+
34
+ Bug Fixes
35
+ ---------
36
+
37
+ - Fixed ``save_html`` / ``to_html`` / ``figure_state`` dropping image pixels
38
+ under the Electron binary transport: the snapshot kept the ``"\x00bin:"``
39
+ change-tokens whose bytes only ever ride the live PLOTBIN channel, so an
40
+ overlay added with :meth:`~anyplotlib.Plot2D.add_layer` did not render in the
41
+ exported document. (`#52 <https://github.com/CSSFrancis/anyplotlib/pull/52>`_)
42
+
43
+
44
+ Documentation
45
+ -------------
46
+
47
+ - Fixed the documentation build failing on
48
+ :class:`~anyplotlib.keys.KeyOverlay`, whose ``id`` and ``name`` were described
49
+ both in the class docstring and by the properties themselves — a duplicate
50
+ object description, which the build treats as an error. (`#53 <https://github.com/CSSFrancis/anyplotlib/pull/53>`_)
51
+
52
+
13
53
  0.7.0 (2026-07-31)
14
54
  ==================
15
55
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anyplotlib
3
- Version: 0.7.0
3
+ Version: 0.7.2
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
@@ -300,7 +300,7 @@ class Figure(anywidget.AnyWidget, _EventMixin):
300
300
  self._push(pid)
301
301
  self._push_layout()
302
302
 
303
- def _push(self, panel_id: str) -> None:
303
+ def _push(self, panel_id: str, *, resolve_pixels: bool = False) -> None:
304
304
  """Serialise one panel and write to its trait.
305
305
 
306
306
  Inside a :meth:`batch` block, pushes are coalesced: each panel is
@@ -309,6 +309,14 @@ class Figure(anywidget.AnyWidget, _EventMixin):
309
309
  many per-frame pushes of a linked-view update (set_data + set_title +
310
310
  widget moves on the same panel) into one serialise/transfer per panel
311
311
  — the dominant cost over a Pyodide comm boundary.
312
+
313
+ Parameters
314
+ ----------
315
+ resolve_pixels : bool, optional
316
+ Force pixel change-tokens to be materialised to inline base64 even
317
+ when the binary transport is live. Set by :meth:`_sync_for_export`
318
+ because a snapshot has no PLOTBIN channel to carry the bytes; see
319
+ the token discussion below.
312
320
  """
313
321
  plot = self._plots_map.get(panel_id)
314
322
  if plot is None:
@@ -329,7 +337,15 @@ class Figure(anywidget.AnyWidget, _EventMixin):
329
337
  # save_html / Jupyter figure with no binary channel — materialise the
330
338
  # real base64 inline so the pixels actually travel. ``_binary_wire``
331
339
  # matches the producer's gate (``Plot2D._encode_pixels``).
332
- if geom_keys and not _binary_wire() and hasattr(plot, "resolve_pixel_tokens"):
340
+ #
341
+ # ``resolve_pixels`` overrides that gate for an EXPORT. A snapshot is by
342
+ # definition off-wire: whatever PLOTBIN would have delivered never
343
+ # arrives, so a token left in the state is a dangling reference and the
344
+ # pixels are simply lost. The gate reads a process-global env var, which
345
+ # is on in a host app even while THIS push is serialising a snapshot —
346
+ # so the export has to say so explicitly.
347
+ if (geom_keys and (resolve_pixels or not _binary_wire())
348
+ and hasattr(plot, "resolve_pixel_tokens")):
333
349
  plot.resolve_pixel_tokens(state)
334
350
  if geom_keys and self.has_trait(gname):
335
351
  # Split heavy geometry into its own channel. Detect change by
@@ -814,9 +830,17 @@ class Figure(anywidget.AnyWidget, _EventMixin):
814
830
  Called from ``_repr_utils._widget_state``, the one chokepoint every
815
831
  export path goes through. The live Jupyter path deliberately keeps
816
832
  using targeted pushes and does not pay this cost.
833
+
834
+ The re-push passes ``resolve_pixels=True``: this rewrites the panel
835
+ traits, and under a live binary transport an ordinary push leaves pixel
836
+ change-tokens in place for PLOTBIN to fill in. A snapshot has no
837
+ PLOTBIN, so those tokens would be dangling — and because the re-push
838
+ rewrites ``panel_<id>_json`` unconditionally it would also undo any
839
+ materialisation the caller did beforehand. An export always ships real
840
+ pixels.
817
841
  """
818
842
  for panel_id in list(self._plots_map):
819
- self._push(panel_id)
843
+ self._push(panel_id, resolve_pixels=True)
820
844
 
821
845
  def _push_panel_fields(self, panel_id: str, fields: dict) -> None:
822
846
  """Apply a small set of changed *fields* to a panel, then push once.
@@ -37,14 +37,6 @@ class KeyOverlay:
37
37
  key = plot.add_key(ipf_triangle, corner="bottom-right")
38
38
  key.set(size=0.3, bgcolor="none")
39
39
  key.visible = False # plain attribute assignment also pushes
40
-
41
- Attributes
42
- ----------
43
- id : str
44
- Short unique identifier.
45
- name : str
46
- Caller-supplied name, or the id when none was given. Use it with
47
- :meth:`~anyplotlib.Plot2D.get_key`.
48
40
  """
49
41
 
50
42
  #: Fields that live on the light view channel. The image itself does NOT
@@ -68,10 +60,15 @@ class KeyOverlay:
68
60
 
69
61
  @property
70
62
  def id(self) -> str:
63
+ """Short unique identifier."""
71
64
  return self._id
72
65
 
73
66
  @property
74
67
  def name(self) -> str:
68
+ """Caller-supplied name, or the id when none was given.
69
+
70
+ Use it with :meth:`~anyplotlib.Plot2D.get_key`.
71
+ """
75
72
  return self._name
76
73
 
77
74
  # ── attribute access ──────────────────────────────────────────────
@@ -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
- if arr.shape != (self._state["image_height"], self._state["image_width"]):
1141
- raise ValueError(
1142
- f"mask shape {arr.shape} does not match image "
1143
- f"({self._state['image_height']} x {self._state['image_width']})"
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")
@@ -0,0 +1,174 @@
1
+ """
2
+ Snapshots must carry real pixels, never a dangling binary change-token.
3
+
4
+ Under the Electron binary transport (``APL_BINARY_TRANSPORT=1``) a panel's
5
+ image bytes do not travel in the state at all: ``Plot2D._encode_pixels`` writes
6
+ a ``"\\x00bin:<adler>"`` change-token and the real bytes ride a PLOTBIN frame
7
+ emitted by ``_electron._route_change``. That split is right for a live wire and
8
+ wrong for a snapshot — ``save_html`` / ``to_html`` / ``figure_state`` produce a
9
+ document with no PLOTBIN behind it, so a token left in the state resolves to
10
+ nothing and the pixels are simply lost.
11
+
12
+ ``Figure._sync_for_export`` therefore re-pushes with ``resolve_pixels=True``.
13
+ It has to say so explicitly because the transport gate is a process-global env
14
+ var, which is on in a host app even while that push is serialising a snapshot.
15
+
16
+ Regression: the re-push (added for widget-position capture) rewrote
17
+ ``panel_<id>_json`` unconditionally with unresolved tokens, which both lost the
18
+ pixels and undid any materialisation a caller had done beforehand. An overlay
19
+ LAYER was the visible casualty — ``_layerBytes`` in figure_esm.js bails on a
20
+ token (``b64.charCodeAt(0) === 0``), so the layer silently did not draw while
21
+ the base image, which is plain base64, still did.
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import json
26
+
27
+ import numpy as np
28
+ import pytest
29
+
30
+ import anyplotlib as apl
31
+ from anyplotlib.embed import figure_state
32
+
33
+
34
+ TOKEN = "\x00bin:"
35
+
36
+
37
+ @pytest.fixture
38
+ def binary_wire(monkeypatch):
39
+ """Turn on the same env gate the Electron host sets."""
40
+ monkeypatch.setenv("APL_BINARY_TRANSPORT", "1")
41
+
42
+
43
+ def _fig_with_layer():
44
+ """A base image + one overlay layer, both on the token path.
45
+
46
+ ``imshow`` encodes the base BEFORE the plot is attached to its Figure, so
47
+ ``_encode_pixels`` finds no ``_raw_pixels`` side-table and falls back to
48
+ base64 for it; ``add_layer`` runs after the attach and does produce a token.
49
+ One ``set_data`` puts the base on the token path too — the state a live host
50
+ is actually in once anything has redrawn. (That asymmetry is why the
51
+ regression looked so odd in the wild: the base image still rendered and only
52
+ the overlay vanished.)
53
+ """
54
+ img = np.linspace(0, 1, 32 * 32, dtype=np.float32).reshape(32, 32)
55
+ fig, ax = apl.subplots(1, 1, figsize=(300, 300))
56
+ plot = ax.imshow(img, cmap="gray")
57
+ plot.add_layer(img, cmap="magma", alpha=0.5)
58
+ plot.set_data(img)
59
+ return fig, plot
60
+
61
+
62
+ def _panel(state, plot):
63
+ return json.loads(state[f"panel_{plot._id}_json"])
64
+
65
+
66
+ def _geom(state, plot):
67
+ return json.loads(state[f"panel_{plot._id}_geom"])
68
+
69
+
70
+ def _is_b64(value):
71
+ return isinstance(value, str) and value != "" and not value.startswith(TOKEN)
72
+
73
+
74
+ class TestSnapshotResolvesPixelTokens:
75
+ def test_producer_really_emits_tokens(self, binary_wire):
76
+ """Guard the premise: without this, the tests below prove nothing."""
77
+ fig, plot = _fig_with_layer()
78
+ state = plot.to_state_dict()
79
+ assert state["image_b64"].startswith(TOKEN)
80
+ assert state["layers"][0]["image_b64"].startswith(TOKEN)
81
+
82
+ def test_base_image_is_inline_base64(self, binary_wire):
83
+ fig, plot = _fig_with_layer()
84
+ assert _is_b64(_geom(figure_state(fig), plot)["image_b64"])
85
+
86
+ def test_layer_pixels_are_inline_base64(self, binary_wire):
87
+ """The nested copy is the one figure_esm.js `_layerBytes` reads."""
88
+ fig, plot = _fig_with_layer()
89
+ layer = _panel(figure_state(fig), plot)["layers"][0]
90
+ assert _is_b64(layer["image_b64"])
91
+
92
+ def test_layer_geom_key_is_inline_base64(self, binary_wire):
93
+ fig, plot = _fig_with_layer()
94
+ geom = _geom(figure_state(fig), plot)
95
+ assert _is_b64(geom[f"layer_{plot._state['layers'][0]['id']}_b64"])
96
+
97
+ def test_no_token_survives_anywhere_in_the_snapshot(self, binary_wire):
98
+ fig, plot = _fig_with_layer()
99
+ blob = json.dumps(figure_state(fig))
100
+ # json.dumps escapes NUL as \\u0000.
101
+ assert "\\u0000bin:" not in blob
102
+
103
+ def test_caller_materialisation_is_not_undone(self, binary_wire):
104
+ """A host that resolves the traits itself before exporting keeps them.
105
+
106
+ ``_sync_for_export`` rewrites ``panel_<id>_json`` unconditionally, so an
107
+ unresolved re-push would clobber the caller's work — the exact shape of
108
+ the reported regression.
109
+ """
110
+ fig, plot = _fig_with_layer()
111
+ state = plot.to_state_dict()
112
+ plot.resolve_pixel_tokens(state)
113
+ resolved = state["layers"][0]["image_b64"]
114
+ setattr(fig, f"panel_{plot._id}_json", json.dumps(state))
115
+
116
+ after = _panel(figure_state(fig), plot)["layers"][0]["image_b64"]
117
+ assert after == resolved
118
+
119
+ def test_multi_layer_all_resolved(self, binary_wire):
120
+ img = np.linspace(0, 1, 16 * 16, dtype=np.float32).reshape(16, 16)
121
+ fig, ax = apl.subplots(1, 1, figsize=(300, 300))
122
+ plot = ax.imshow(img, cmap="gray")
123
+ for cmap in ("magma", "cividis", "plasma"):
124
+ plot.add_layer(img, cmap=cmap, alpha=0.4)
125
+ layers = _panel(figure_state(fig), plot)["layers"]
126
+ assert len(layers) == 3
127
+ assert all(_is_b64(ly["image_b64"]) for ly in layers)
128
+
129
+ def test_widget_positions_still_reconciled(self, binary_wire):
130
+ """The pixel fix must not cost the reason _sync_for_export exists."""
131
+ fig, plot = _fig_with_layer()
132
+ widget = plot.add_widget("rectangle", x=2, y=2, w=4, h=4)
133
+ widget.set(x=21, y=23)
134
+ got = _panel(figure_state(fig), plot)["overlay_widgets"][0]
135
+ assert got["x"] == 21 and got["y"] == 23
136
+
137
+
138
+ class TestLiveWireStillUsesTokens:
139
+ """The live path must keep its token/PLOTBIN split — that is the whole
140
+ point of the binary transport, and resolving there would push megabytes of
141
+ base64 through the comm on every scrub frame."""
142
+
143
+ def test_ordinary_push_keeps_the_token(self, binary_wire):
144
+ fig, plot = _fig_with_layer()
145
+ fig._push(plot._id)
146
+ geom = json.loads(getattr(fig, f"panel_{plot._id}_geom"))
147
+ assert geom["image_b64"].startswith(TOKEN)
148
+ layer_key = f"layer_{plot._state['layers'][0]['id']}_b64"
149
+ assert geom[layer_key].startswith(TOKEN)
150
+
151
+ def test_set_data_keeps_the_token(self, binary_wire):
152
+ fig, plot = _fig_with_layer()
153
+ plot.set_data(np.zeros((32, 32), dtype=np.float32))
154
+ geom = json.loads(getattr(fig, f"panel_{plot._id}_geom"))
155
+ assert geom["image_b64"].startswith(TOKEN)
156
+
157
+ def test_push_after_export_returns_to_tokens(self, binary_wire):
158
+ """An export must not leave the live wire on the base64 path."""
159
+ fig, plot = _fig_with_layer()
160
+ figure_state(fig)
161
+ plot.set_data(np.ones((32, 32), dtype=np.float32))
162
+ geom = json.loads(getattr(fig, f"panel_{plot._id}_geom"))
163
+ assert geom["image_b64"].startswith(TOKEN)
164
+
165
+
166
+ class TestNoBinaryTransport:
167
+ """With no binary wire the state was always inline base64; keep it so."""
168
+
169
+ def test_snapshot_is_inline_base64(self, monkeypatch):
170
+ monkeypatch.delenv("APL_BINARY_TRANSPORT", raising=False)
171
+ fig, plot = _fig_with_layer()
172
+ layer = _panel(figure_state(fig), plot)["layers"][0]
173
+ assert _is_b64(layer["image_b64"])
174
+ assert _is_b64(_geom(figure_state(fig), plot)["image_b64"])
@@ -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)
@@ -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.0/" />
8
- <link rel="canonical" href="v0.7.0/" />
7
+ <meta http-equiv="refresh" content="0; url=v0.7.2/" />
8
+ <link rel="canonical" href="v0.7.2/" />
9
9
  </head>
10
10
  <body>
11
11
  <p>
12
- Redirecting to <a href="v0.7.0/">v0.7.0 documentation</a>…
12
+ Redirecting to <a href="v0.7.2/">v0.7.2 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.2 (stable)",
9
+ "version": "v0.7.2",
10
+ "url": "https://cssfrancis.github.io/anyplotlib/v0.7.2/"
11
+ },
12
+ {
13
+ "name": "v0.7.1 (stable)",
14
+ "version": "v0.7.1",
15
+ "url": "https://cssfrancis.github.io/anyplotlib/v0.7.1/"
16
+ },
7
17
  {
8
18
  "name": "v0.7.0 (stable)",
9
19
  "version": "v0.7.0",
@@ -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.0"
20
+ release = "0.7.2"
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.
@@ -13,7 +13,7 @@ exclude = [
13
13
 
14
14
  [project]
15
15
  name = "anyplotlib"
16
- version = "0.7.0"
16
+ version = "0.7.2"
17
17
  description = "A plotting library using python, javascript and anywidget for performant in browser plotting."
18
18
  readme = "README.md"
19
19
  license = { text = "MIT" }
File without changes
File without changes