anyplotlib 0.1.0b1__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/.github/workflows/prepare_release.yml +42 -11
  2. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/CHANGELOG.rst +142 -0
  3. anyplotlib-0.3.0/Examples/Interactive/plot_figure_annotations.py +87 -0
  4. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_voxel_grain_explorer.py +5 -2
  5. anyplotlib-0.3.0/Examples/Markers/plot_transform_markers.py +49 -0
  6. anyplotlib-0.3.0/Examples/PlotTypes/plot_gpu_voxels.py +82 -0
  7. anyplotlib-0.3.0/Examples/PlotTypes/plot_step_and_log.py +43 -0
  8. anyplotlib-0.3.0/Examples/PlotTypes/plot_tile_backend.py +184 -0
  9. anyplotlib-0.3.0/Examples/PlotTypes/plot_twinx.py +45 -0
  10. anyplotlib-0.3.0/Examples/Widgets/plot_widget1d_point.py +40 -0
  11. anyplotlib-0.3.0/Examples/Widgets/plot_widget2d_arrow.py +41 -0
  12. anyplotlib-0.3.0/Examples/Widgets/plot_widget3d_plane.py +47 -0
  13. anyplotlib-0.3.0/LARGE_IMAGE_WEBGPU_PLAN.md +223 -0
  14. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/PKG-INFO +1 -1
  15. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/FIGURE_ESM.md +225 -1
  16. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/__init__.py +4 -4
  17. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/_base_plot.py +69 -0
  18. anyplotlib-0.3.0/anyplotlib/_binary_frame.py +78 -0
  19. anyplotlib-0.3.0/anyplotlib/_electron.py +201 -0
  20. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/_repr_utils.py +119 -9
  21. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/_utils.py +49 -13
  22. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/axes/_axes.py +13 -5
  23. anyplotlib-0.3.0/anyplotlib/axes/_inset_axes.py +254 -0
  24. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/callbacks.py +13 -1
  25. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/figure/_figure.py +222 -8
  26. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/figure_esm.js +2402 -103
  27. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/markers.py +10 -0
  28. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plot1d/_plot1d.py +218 -36
  29. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plot2d/__init__.py +3 -2
  30. anyplotlib-0.3.0/anyplotlib/plot2d/_layer.py +121 -0
  31. anyplotlib-0.3.0/anyplotlib/plot2d/_plot2d.py +1911 -0
  32. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plot2d/_plotmesh.py +1 -1
  33. anyplotlib-0.3.0/anyplotlib/plot2d/_tile_backend.py +181 -0
  34. anyplotlib-0.3.0/anyplotlib/tests/_gpu_clim_check.cjs +51 -0
  35. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/_png_utils.py +32 -0
  36. anyplotlib-0.3.0/anyplotlib/tests/baselines/plot1d_twinx.png +0 -0
  37. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/conftest.py +149 -0
  38. anyplotlib-0.3.0/anyplotlib/tests/test_electron/test_binary_frame.py +245 -0
  39. anyplotlib-0.3.0/anyplotlib/tests/test_embed/test_export_png.py +777 -0
  40. anyplotlib-0.3.0/anyplotlib/tests/test_interactive/test_edit_chrome.py +435 -0
  41. anyplotlib-0.3.0/anyplotlib/tests/test_interactive/test_edit_chrome_playwright.py +842 -0
  42. anyplotlib-0.3.0/anyplotlib/tests/test_interactive/test_tile_parity_playwright.py +315 -0
  43. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_title.py +3 -3
  44. anyplotlib-0.3.0/anyplotlib/tests/test_layouts/test_inset_callout.py +636 -0
  45. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_visual.py +80 -1
  46. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_markers/test_marker_transforms.py +32 -0
  47. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_markers/test_markers.py +77 -0
  48. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot1d/test_plot1d.py +158 -0
  49. anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_detail_tile.py +419 -0
  50. anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_first_paint_race.py +259 -0
  51. anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_gpu_image.py +98 -0
  52. anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_gpu_parity_playwright.py +425 -0
  53. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_imshow.py +65 -3
  54. anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_layers.py +427 -0
  55. anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_layers_playwright.py +313 -0
  56. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_plot2d_api.py +15 -0
  57. anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_rapid_frame_update.py +160 -0
  58. anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_tile_backend.py +99 -0
  59. anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_tiled_imshow.py +386 -0
  60. anyplotlib-0.3.0/anyplotlib/tests/test_plot2d/test_zoom_view_write.py +76 -0
  61. anyplotlib-0.3.0/anyplotlib/tests/test_plot3d/__init__.py +0 -0
  62. anyplotlib-0.3.0/anyplotlib/tests/test_plot3d/test_gpu_hidden_reveal.py +330 -0
  63. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/widgets/__init__.py +2 -2
  64. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/widgets/_widgets2d.py +69 -12
  65. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/_root/index.html +3 -3
  66. anyplotlib-0.3.0/docs/_root/switcher.json +27 -0
  67. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/api/figure_plots.rst +7 -0
  68. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/api/index.rst +3 -0
  69. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/conf.py +10 -1
  70. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/pyproject.toml +1 -1
  71. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/upcoming_changes/README.rst +7 -1
  72. anyplotlib-0.1.0b1/anyplotlib/_electron.py +0 -74
  73. anyplotlib-0.1.0b1/anyplotlib/axes/_inset_axes.py +0 -126
  74. anyplotlib-0.1.0b1/anyplotlib/plot2d/_plot2d.py +0 -726
  75. anyplotlib-0.1.0b1/docs/_root/switcher.json +0 -12
  76. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/.github/workflows/ci.yml +0 -0
  77. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/.github/workflows/docs.yml +0 -0
  78. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/.github/workflows/release.yml +0 -0
  79. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/.github/workflows/tests.yml +0 -0
  80. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/.gitignore +0 -0
  81. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/AGENTS.md +0 -0
  82. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Benchmarks/README.rst +0 -0
  83. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Benchmarks/plot_benchmark_comparison.py +0 -0
  84. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/README.rst +0 -0
  85. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_3d_spectral_viewer.py +0 -0
  86. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_eels_explorer.py +0 -0
  87. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_interactive_fft.py +0 -0
  88. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_interactive_fitting.py +0 -0
  89. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_ipf_density_map.py +0 -0
  90. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_ipf_explorer.py +0 -0
  91. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_key_bindings.py +0 -0
  92. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_particle_picker.py +0 -0
  93. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_point_widget.py +0 -0
  94. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_segment_by_contrast.py +0 -0
  95. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_segment_by_contrast_advanced.py +0 -0
  96. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_spectra_roi_inspector.py +0 -0
  97. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Interactive/plot_threshold_explorer.py +0 -0
  98. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/README.rst +0 -0
  99. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_arrows.py +0 -0
  100. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_circles.py +0 -0
  101. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_ellipses.py +0 -0
  102. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_horizontal_lines.py +0 -0
  103. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_line_segments.py +0 -0
  104. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_points.py +0 -0
  105. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_polygons.py +0 -0
  106. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_rectangles.py +0 -0
  107. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_squares.py +0 -0
  108. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_texts.py +0 -0
  109. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Markers/plot_vertical_lines.py +0 -0
  110. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/README.rst +0 -0
  111. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_3d.py +0 -0
  112. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_bar.py +0 -0
  113. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_gridspec_custom.py +0 -0
  114. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_image2d.py +0 -0
  115. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_inset.py +0 -0
  116. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_label_formatting.py +0 -0
  117. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_line_styles.py +0 -0
  118. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_pcolormesh.py +0 -0
  119. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/PlotTypes/plot_spectra1d.py +0 -0
  120. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/README.rst +0 -0
  121. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/README.rst +0 -0
  122. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget1d_hline.py +0 -0
  123. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget1d_range.py +0 -0
  124. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget1d_vline.py +0 -0
  125. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_annular.py +0 -0
  126. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_circle.py +0 -0
  127. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_crosshair.py +0 -0
  128. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_label.py +0 -0
  129. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_polygon.py +0 -0
  130. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Examples/Widgets/plot_widget2d_rectangle.py +0 -0
  131. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/LICENSE +0 -0
  132. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/Makefile +0 -0
  133. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/ORIX_BACKEND_PLAN.md +0 -0
  134. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/README.md +0 -0
  135. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/RELEASE_NOTES.md +0 -0
  136. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/RELEASE_PLAN.md +0 -0
  137. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/WEBGPU_PLAN.md +0 -0
  138. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/axes/__init__.py +0 -0
  139. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/conftest.py +0 -0
  140. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/embed.py +0 -0
  141. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/figure/__init__.py +0 -0
  142. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/figure/_gridspec.py +0 -0
  143. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/figure/_subplots.py +0 -0
  144. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plot1d/__init__.py +0 -0
  145. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plot1d/_plotbar.py +0 -0
  146. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plot3d/__init__.py +0 -0
  147. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plot3d/_plot3d.py +0 -0
  148. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plotxy/__init__.py +0 -0
  149. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/plotxy/_plotxy.py +0 -0
  150. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/__init__.py +0 -0
  151. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/_directive.py +0 -0
  152. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/_repr_utils.py +0 -0
  153. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/_scraper.py +0 -0
  154. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/_wheel_builder.py +0 -0
  155. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/static/anywidget_bridge.js +0 -0
  156. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/static/anywidget_overlay.css +0 -0
  157. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/__init__.py +0 -0
  158. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/conftest.py +0 -0
  159. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_directive.py +0 -0
  160. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_init.py +0 -0
  161. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_repr_utils.py +0 -0
  162. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_scraper.py +0 -0
  163. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/sphinx_anywidget/tests/test_wheel_builder.py +0 -0
  164. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/__init__.py +0 -0
  165. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/bar_basic.png +0 -0
  166. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_3col_equal_spectra.png +0 -0
  167. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_asymmetric_width_ratios.png +0 -0
  168. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_height_ratio_image_histogram.png +0 -0
  169. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_image_two_spectra.png +0 -0
  170. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_side_by_side_1d.png +0 -0
  171. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/gridspec_spanning_top_two_bottom.png +0 -0
  172. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_axis_off.png +0 -0
  173. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_checkerboard.png +0 -0
  174. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_gradient.png +0 -0
  175. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_labels.png +0 -0
  176. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/imshow_viridis.png +0 -0
  177. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_1d.png +0 -0
  178. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_maximized.png +0 -0
  179. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_minimized.png +0 -0
  180. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_normal_2d.png +0 -0
  181. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_stacked.png +0 -0
  182. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/inset_stacked_one_minimized.png +0 -0
  183. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/pcolormesh_uniform.png +0 -0
  184. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_all_linestyles.png +0 -0
  185. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_alpha.png +0 -0
  186. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_axis_off.png +0 -0
  187. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_dashed.png +0 -0
  188. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_marker_symbols.png +0 -0
  189. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_markers.png +0 -0
  190. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_multi.png +0 -0
  191. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_sine.png +0 -0
  192. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot1d_title.png +0 -0
  193. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/plot3d_surface.png +0 -0
  194. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/baselines/subplots_2x1.png +0 -0
  195. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/__init__.py +0 -0
  196. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/baselines.json +0 -0
  197. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/benchmarks/baselines.json +0 -0
  198. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/test_benchmarks.py +0 -0
  199. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_benchmarks/test_benchmarks_py.py +0 -0
  200. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_documentation/__init__.py +0 -0
  201. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_documentation/test_bridge.py +0 -0
  202. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_documentation/test_push_hook.py +0 -0
  203. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_documentation/test_scraper.py +0 -0
  204. {anyplotlib-0.1.0b1/anyplotlib/tests/test_embed → anyplotlib-0.3.0/anyplotlib/tests/test_electron}/__init__.py +0 -0
  205. {anyplotlib-0.1.0b1/anyplotlib/tests/test_examples → anyplotlib-0.3.0/anyplotlib/tests/test_embed}/__init__.py +0 -0
  206. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_embed/test_embed_api.py +0 -0
  207. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_embed/test_embed_mount.py +0 -0
  208. {anyplotlib-0.1.0b1/anyplotlib/tests/test_interactive → anyplotlib-0.3.0/anyplotlib/tests/test_examples}/__init__.py +0 -0
  209. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_examples/test_interactive_examples.py +0 -0
  210. {anyplotlib-0.1.0b1/anyplotlib/tests/test_labels → anyplotlib-0.3.0/anyplotlib/tests/test_interactive}/__init__.py +0 -0
  211. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/_event_test_utils.py +0 -0
  212. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_blit_audit.py +0 -0
  213. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_callbacks_playwright.py +0 -0
  214. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_callbacks_unit.py +0 -0
  215. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_event_pause_hold.py +0 -0
  216. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_event_plots.py +0 -0
  217. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_event_settled.py +0 -0
  218. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_events_regression.py +0 -0
  219. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_touch.py +0 -0
  220. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_interactive/test_widgets.py +0 -0
  221. {anyplotlib-0.1.0b1/anyplotlib/tests/test_layouts → anyplotlib-0.3.0/anyplotlib/tests/test_labels}/__init__.py +0 -0
  222. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_labels/test_label_api.py +0 -0
  223. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_labels/test_label_rendering.py +0 -0
  224. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_labels/test_no_clipping.py +0 -0
  225. {anyplotlib-0.1.0b1/anyplotlib/tests/test_markers → anyplotlib-0.3.0/anyplotlib/tests/test_layouts}/__init__.py +0 -0
  226. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_batch.py +0 -0
  227. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_geom_channel.py +0 -0
  228. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_gridspec.py +0 -0
  229. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_inset.py +0 -0
  230. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_layouts/test_interaction.py +0 -0
  231. {anyplotlib-0.1.0b1/anyplotlib/tests/test_plot1d → anyplotlib-0.3.0/anyplotlib/tests/test_markers}/__init__.py +0 -0
  232. {anyplotlib-0.1.0b1/anyplotlib/tests/test_plot2d → anyplotlib-0.3.0/anyplotlib/tests/test_plot1d}/__init__.py +0 -0
  233. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot1d/test_plotbar.py +0 -0
  234. {anyplotlib-0.1.0b1/anyplotlib/tests/test_plot3d → anyplotlib-0.3.0/anyplotlib/tests/test_plot2d}/__init__.py +0 -0
  235. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_imshow_rgb.py +0 -0
  236. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot2d/test_pcolormesh.py +0 -0
  237. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot3d/test_colors_highlight.py +0 -0
  238. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot3d/test_gpu_fallback.py +0 -0
  239. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot3d/test_plot3d.py +0 -0
  240. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plot3d/test_voxels_planes.py +0 -0
  241. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/tests/test_plotxy/test_plotxy.py +0 -0
  242. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/widgets/_base.py +0 -0
  243. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/widgets/_widgets1d.py +0 -0
  244. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/anyplotlib/widgets/_widgets3d.py +0 -0
  245. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/_sg_html_scraper.py +0 -0
  246. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/_static/anyplotlib.svg +0 -0
  247. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/_static/custom.css +0 -0
  248. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/_static/pyodide_bridge.js +0 -0
  249. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/_templates/autosummary/class.rst +0 -0
  250. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/api/callbacks.rst +0 -0
  251. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/api/figure.rst +0 -0
  252. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/api/markers.rst +0 -0
  253. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/api/widgets.rst +0 -0
  254. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/benchmarking.rst +0 -0
  255. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/dev/index.rst +0 -0
  256. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/embedding.rst +0 -0
  257. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/events.rst +0 -0
  258. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/getting_started.rst +0 -0
  259. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/docs/index.rst +0 -0
  260. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/make.bat +0 -0
  261. {anyplotlib-0.1.0b1 → anyplotlib-0.3.0}/uv.lock +0 -0
@@ -11,12 +11,13 @@ on:
11
11
  required: true
12
12
  type: choice
13
13
  options:
14
+ - finalize # drop the bN suffix: release the current beta's base as stable (0.3.0b2 -> 0.3.0)
14
15
  - minor
15
16
  - bugfix
16
17
  - major
17
18
  - pre-release # increments the bN counter on the current base version
18
19
  beta:
19
- description: "Mark as beta pre-release (adds bN suffix; always true for pre-release)"
20
+ description: "Mark as beta pre-release (adds bN suffix). Ignored for 'pre-release' (always beta) and 'finalize' (always stable)."
20
21
  required: false
21
22
  type: boolean
22
23
  default: false
@@ -67,17 +68,42 @@ jobs:
67
68
  minor = int(m.group(2))
68
69
  patch = int(m.group(3))
69
70
  beta_n = int(m.group(4)) if m.group(4) else None
70
-
71
- if bump == "major":
71
+ on_beta = beta_n is not None
72
+
73
+ if bump == "finalize":
74
+ # Release the current beta's base as stable: just drop the bN
75
+ # suffix, keep major.minor.patch. e.g. 0.3.0b2 -> 0.3.0.
76
+ if not on_beta:
77
+ raise SystemExit(
78
+ f"'finalize' requires a beta base version, but current "
79
+ f"version {current!r} has no bN suffix. Use minor / bugfix "
80
+ f"/ major to start a new release instead."
81
+ )
82
+ is_beta = False
83
+ elif bump == "pre-release":
84
+ # Keep the same base; just walk the beta counter forward.
85
+ is_beta = True
86
+ beta_n = (beta_n or 0) + 1
87
+ elif on_beta:
88
+ # We are on a beta of the NEXT release (e.g. 0.3.0b2). The base
89
+ # major.minor.patch is that upcoming version, so minor/bugfix/major
90
+ # must bump relative to the LAST STABLE (base - the in-progress
91
+ # component), not skip a whole version. The common intent from a
92
+ # beta is 'finalize', so steer the user there rather than guess.
93
+ raise SystemExit(
94
+ f"Current version {current!r} is a beta of the upcoming "
95
+ f"{major}.{minor}.{patch} release. To ship it, use bump="
96
+ f"'finalize' (-> {major}.{minor}.{patch}). A '{bump}' bump from "
97
+ f"a beta would skip {major}.{minor}.{patch} entirely "
98
+ f"(e.g. -> {'%d.%d.0' % (major, minor + 1) if bump == 'minor' else '...'}); "
99
+ f"that is almost never intended."
100
+ )
101
+ elif bump == "major":
72
102
  major, minor, patch = major + 1, 0, 0
73
103
  elif bump == "minor":
74
104
  minor, patch = minor + 1, 0
75
105
  elif bump == "bugfix":
76
106
  patch += 1
77
- elif bump == "pre-release":
78
- # Keep the same base; just walk the beta counter forward.
79
- is_beta = True
80
- beta_n = (beta_n or 0) + 1
81
107
 
82
108
  if is_beta:
83
109
  if bump != "pre-release":
@@ -88,10 +114,15 @@ jobs:
88
114
  PYEOF
89
115
  )
90
116
 
117
+ # Derive is_beta from the COMPUTED version (ends in bN?), not the raw
118
+ # input — so 'finalize' is always treated as stable and a mismatched
119
+ # beta checkbox can't mislabel the switcher / skip the root redirect.
120
+ if [[ "$NEW_VERSION" =~ b[0-9]+$ ]]; then IS_BETA_OUT=true; else IS_BETA_OUT=false; fi
121
+
91
122
  echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
92
123
  echo "tag=v$NEW_VERSION" >> "$GITHUB_OUTPUT"
93
124
  echo "branch=release/v$NEW_VERSION" >> "$GITHUB_OUTPUT"
94
- echo "is_beta=${{ inputs.beta }}" >> "$GITHUB_OUTPUT"
125
+ echo "is_beta=$IS_BETA_OUT" >> "$GITHUB_OUTPUT"
95
126
  echo "Bumping (${{ inputs.bump }}): $CURRENT → $NEW_VERSION"
96
127
 
97
128
  # ── Bump version strings ─────────────────────────────────────────────
@@ -118,7 +149,7 @@ jobs:
118
149
  - name: Update docs/switcher.json
119
150
  env:
120
151
  VERSION_TAG: ${{ steps.version.outputs.tag }}
121
- IS_BETA: ${{ inputs.beta }}
152
+ IS_BETA: ${{ steps.version.outputs.is_beta }}
122
153
  shell: python
123
154
  run: |
124
155
  import json, re, pathlib, os
@@ -144,7 +175,7 @@ jobs:
144
175
 
145
176
  # ── Update root redirect for stable releases ─────────────────────────
146
177
  - name: Update root redirect (stable releases only)
147
- if: ${{ inputs.beta == false && inputs.bump != 'pre-release' }}
178
+ if: ${{ steps.version.outputs.is_beta == 'false' && inputs.bump != 'pre-release' }}
148
179
  env:
149
180
  VERSION_TAG: ${{ steps.version.outputs.tag }}
150
181
  shell: python
@@ -200,7 +231,7 @@ jobs:
200
231
  - Version bumped to \`${TAG}\` in \`pyproject.toml\` and \`docs/conf.py\`
201
232
  - \`CHANGELOG.rst\` updated from towncrier fragments
202
233
  - \`docs/_root/switcher.json\` updated with the new version entry
203
- $([ '${{ inputs.beta }}' = 'false' ] && echo '- Root redirect updated to point to this release' || echo '')
234
+ $([ '${{ steps.version.outputs.is_beta }}' = 'false' ] && echo '- Root redirect updated to point to this release' || echo '')
204
235
 
205
236
  ### Review checklist
206
237
  - [ ] \`CHANGELOG.rst\` reads well — edit the fragment text directly if needed
@@ -10,6 +10,148 @@ Fragment files in ``upcoming_changes/`` are assembled into this file by
10
10
 
11
11
  .. towncrier release notes start
12
12
 
13
+ 0.3.0b1 (2026-07-13)
14
+ ====================
15
+
16
+ New Features
17
+ ------------
18
+
19
+ - Added :class:`~anyplotlib.widgets.ArrowWidget` (draggable arrow overlay, tail
20
+ at ``(x, y)`` and head at ``(x + u, y + v)``) via ``Plot2D.add_arrow_widget`` /
21
+ ``add_widget("arrow")``, and a ``show_handles`` option (default ``True``) on
22
+ every 2-D overlay widget to hide the grab-handle dots without affecting drag.
23
+ - Added figure-level edit-mode chrome to :class:`~anyplotlib.Figure`: the
24
+ ``edit_chrome`` and ``selected_panel`` traits (per-panel hover / selection
25
+ outlines), figure-background click events, and a figure-level annotation layer
26
+ (``set_figure_markers`` / ``figure_markers``, positioned in figure fractions and
27
+ always included in ``exportPNG``) with figure-level callbacks via
28
+ ``add_event_handler``.
29
+ - Extended :class:`~anyplotlib.Figure` edit-mode interaction:
30
+
31
+ * Circle and rectangle overlay widgets are now **resizable via visible nodes** —
32
+ a circle draws a centre (move) node and an east-point radius node; a rectangle
33
+ draws all four corner nodes (opposite corner anchored on drag). Drawn only
34
+ when ``show_handles`` is ``True``, with matching resize cursors.
35
+ * :class:`~anyplotlib.widgets.ArrowWidget` **tail is now a reshape node**:
36
+ dragging the tail moves it while the head stays anchored (dragging the shaft
37
+ still moves the whole arrow; the head node still re-aims it).
38
+ * The selected-panel and hover **outlines are fully inset** (``outline-offset:
39
+ -2px``) so an edge/corner panel's ring is no longer clipped at the figure's
40
+ right/bottom edge.
41
+ * **Panel drag-swap** under ``edit_chrome``: each grid panel shows a move grip
42
+ in its top-left corner; dragging it over a *different* panel emits a
43
+ figure-level ``pointer_up`` event with ``panel_swap: true`` and
44
+ ``source_panel_id`` / ``target_panel_id`` (new :class:`~anyplotlib.Event`
45
+ fields). anyplotlib performs no layout change itself — the host swaps and
46
+ rebuilds. Releasing on the source panel or empty space cancels cleanly; the
47
+ grip is inert when ``edit_chrome`` is off.
48
+ * The JS ``mount()`` embedding entry point accepts an ``onResize({width,
49
+ height})`` callback, fired (debounced) when the **root container resizes**, so
50
+ an embedding host can relayout the figure to its new box.
51
+
52
+
53
+ 0.2.0 (2026-07-10)
54
+ ==================
55
+
56
+ New Features
57
+ ------------
58
+
59
+ - 2-D scalar images can now render on the **GPU via WebGPU** (``imshow(...,
60
+ gpu="auto"|True|False)``): the image uploads as an R8 texture and a WGSL fragment
61
+ shader applies the colormap LUT + contrast (clim) in one draw, replacing the
62
+ per-pixel JavaScript colormap loop. Large images (≳1 megapixel) take the GPU path
63
+ automatically; everything below the threshold, RGB images, ``gpu=False``, and any
64
+ device without WebGPU keep the identical Canvas2D path. ``plot.gpu_active`` reports
65
+ which path ran. Verified on an NVIDIA Pascal GPU.
66
+ - Arbitrarily large images can now display through **tile mode**
67
+ (``imshow(..., tile="auto")``): the figure shows a downsampled overview as its
68
+ base and, after each zoom/pan settles, samples a high-resolution detail tile of
69
+ just the visible region at panel resolution — deep zooms stay crisp without
70
+ ever shipping the full-resolution frame. ``Plot2D.enable_tile`` /
71
+ ``update_tile_source`` swap the underlying frame while the zoom and
72
+ subselection persist (live-data contract), and a pluggable ``TileBackend``
73
+ (default: a fast vectorised numpy box-mean) lets out-of-core or GPU sources
74
+ own the sampling.
75
+ - Markers gained a ``clip_display`` option controlling whether they draw
76
+ outside the current axes view or are clipped to it.
77
+ - ``set_extent`` now updates the axes state (calibrated units / scale bar), so
78
+ applying a calibrated extent after figure creation renders labelled axes
79
+ instead of bare pixels.
80
+ - Regular ``pcolormesh`` meshes are detected and rasterized to an image for
81
+ display (fast IPF-style heatmaps) instead of drawing per-quad.
82
+
83
+
84
+ Bug Fixes
85
+ ---------
86
+
87
+ - Fixed a display freeze under the Electron binary pixel transport: the routing
88
+ layer stripped the pixel key out of the slimmed geom JSON, so the renderer's
89
+ "unchanged → skip re-upload" caches (Canvas2D blit cache, WebGPU texture,
90
+ overlay-mask cache) fell back to a 4-sampled-byte fingerprint of the buffer —
91
+ two frames differing anywhere else collided and the display stayed frozen on
92
+ the old frame (seen as a stale overview after a movie scrub). The slimmed geom
93
+ now carries a small ``\x00bin:<checksum>`` content token under the pixel key,
94
+ binary buffers are additionally stamped with an arrival sequence as a fallback
95
+ key, and the overlay-mask draw path now reads the binary byte side-channel
96
+ (it previously only decoded base64, so masks never displayed over the binary
97
+ transport).
98
+ - Fixed the WebGPU 2-D image path sampling a vertically MIRRORED window when the
99
+ view was panned off-centre: the shader applied a global ``1 - v`` flip after
100
+ interpolating the ``[v0, v1]`` uv window, which sampled ``[1-v1, 1-v0]``
101
+ instead — correct only for a full or vertically-centred view. Symptoms: pan-y
102
+ moved the image the wrong way on GPU-rendered panels, and markers/widgets
103
+ (drawn by the shared Canvas2D overlay transform, which was always correct)
104
+ appeared detached from the image features they marked. The base and
105
+ detail-tile passes share the shader, so both are fixed. GPU-vs-CPU screenshot
106
+ parity tests (zoom, pan, markers, widgets, detail tile) now run on real WebGPU
107
+ in headless Chromium (``channel="chromium"`` + ``--enable-unsafe-webgpu``) and
108
+ skip on machines with no adapter.
109
+ - ``Plot2D.set_data`` no longer makes a float64 copy of every incoming frame.
110
+ The float64 cast now happens lazily in the ``.data`` property (the only reader),
111
+ so a frame stream — e.g. scrubbing an in-situ movie — keeps the source dtype and
112
+ skips a ~12 ms float64 copy of a 4k frame per tick. ``.data`` still returns a
113
+ read-only float64 copy, unchanged for callers.
114
+ - The Electron binary pixel transport now ships the RAW uint8 image bytes end to
115
+ end, instead of base64-encoding them in ``set_data`` only to base64-decode them
116
+ straight back in the routing layer. ``Plot2D.set_data`` stashes the raw bytes on
117
+ the Figure's ``_raw_pixels`` side-table and leaves a tiny content-checksum
118
+ change-token in ``image_b64``; ``_electron._route_change`` ships those bytes to a
119
+ PLOTBIN frame directly. This removes the ~20 ms base64 encode, the ~17 ms decode,
120
+ and the megabyte ``json.dumps`` of the pixel string from every scrub frame — a
121
+ 2.2x faster ``set_data`` (≈98 ms → ≈44 ms on a 2048² frame) and ~25% less
122
+ bytes-on-wire. Non-Electron hosts (Jupyter / Pyodide / standalone / ``save_html``)
123
+ are unchanged: they have no PLOTBIN channel, so the token is resolved back to
124
+ inline base64 via ``Plot2D.resolve_pixel_tokens`` when the figure state is
125
+ serialised for them.
126
+ - Tile mode: a data update while zoomed in (``update_tile_source`` with a detail
127
+ tile shown) refreshes only the detail tile, leaving the overview base on the
128
+ old frame — zooming out then flashed the pre-update frame. The skipped
129
+ overview is now marked stale and re-sampled once on the next view settle
130
+ (riding the same push as the detail/clear), preserving the per-frame skip
131
+ optimisation while never exposing stale base pixels.
132
+ - Interactive zoom/pan on a 2-D image no longer re-serialises (and re-transmits)
133
+ the full image on every mouse tick. The wheel/pan/orbit handlers write only the
134
+ light *view* state back to the ``panel_<id>_json`` trait now, excluding the
135
+ cached geometry (pixels, colormap LUT) that ``_applyGeom`` splices into the panel
136
+ state for drawing. Previously the whole frame was ``JSON.stringify``-d per tick —
137
+ catastrophically so on the binary transport, where the pixel buffer is a
138
+ ``Uint8Array`` that stringifies to a ``{"0":..,"1":..}`` object with one key per
139
+ byte — which stalled zoom on large images.
140
+ - Fixed a first-paint race under the Electron binary transport: binary
141
+ side-table bytes that arrived before ``render()`` attached its listeners were
142
+ stranded, leaving the first frame blank until the next update — they are now
143
+ spliced into the initial paint.
144
+
145
+
146
+ Maintenance
147
+ -----------
148
+
149
+ - ``anyplotlib.__version__`` is now exposed from the package metadata.
150
+ - Per-frame hot-path costs trimmed: the colormap LUT is cached instead of being
151
+ rebuilt every frame (~100 ms), and small-range data rescales in float32
152
+ (~60 ms → ~27 ms per 2048² frame).
153
+
154
+
13
155
  0.1.0 (2026-06-24)
14
156
  ==================
15
157
 
@@ -0,0 +1,87 @@
1
+ """
2
+ Interactive figure annotations — double-click to add, drag to place
3
+ ===================================================================
4
+
5
+ anyplotlib has a *figure-level* annotation layer that floats above the panels,
6
+ positioned in **figure fractions** (0…1, origin top-left) rather than any
7
+ panel's data coordinates. Set it with
8
+ :meth:`~anyplotlib.Figure.set_figure_markers`; the supported kinds are
9
+ ``"text"``, ``"circle"``, ``"rect"`` and ``"arrow"``.
10
+
11
+ Turn on ``fig.edit_chrome`` and every annotation becomes draggable in the
12
+ browser. Combine that with a ``double_click`` handler and you get a simple
13
+ annotate-by-clicking workflow: double-click a feature to drop a labelled
14
+ arrow, then drag it to line it up. Positions round-trip back to Python via
15
+ :attr:`~anyplotlib.Figure.figure_markers`.
16
+ """
17
+ import numpy as np
18
+ import anyplotlib as apl
19
+
20
+ rng = np.random.default_rng(7)
21
+ data = rng.standard_normal((160, 160)).cumsum(0).cumsum(1)
22
+ data = (data - data.min()) / (data.max() - data.min())
23
+
24
+ fig, ax = apl.subplots(1, 1, figsize=(520, 520))
25
+ v = ax.imshow(data, cmap="magma", units="px")
26
+
27
+ # Enable the editable-annotation ("report builder") mode so figure markers are
28
+ # hit-testable and draggable, and the figure emits background/marker events.
29
+ fig.edit_chrome = True
30
+
31
+ # %%
32
+ # Seed a couple of annotations
33
+ # ----------------------------
34
+ # Each marker is a dict with a ``kind`` and fraction-space geometry. A text
35
+ # label and an arrow pointing into the image to start with.
36
+
37
+ fig.set_figure_markers([
38
+ {"kind": "text", "x": 0.5, "y": 0.06,
39
+ "text": "Double-click a feature to annotate it",
40
+ "color": "#ffffff", "fontsize": 14},
41
+ {"kind": "arrow", "x": 0.20, "y": 0.30, "u": 0.12, "v": 0.12,
42
+ "color": "#ffd54f", "linewidth": 2},
43
+ ])
44
+
45
+ fig
46
+
47
+ # %%
48
+ # Double-click to drop a new annotation
49
+ # -------------------------------------
50
+ # On a single-panel figure the panel nearly fills the canvas, so we turn the
51
+ # click's device pixels into a figure fraction with the panel's
52
+ # ``display_width`` / ``display_height`` and append an arrow + label there.
53
+ # Because ``edit_chrome`` is on, the new marker is immediately draggable.
54
+
55
+
56
+ def _on_double_click(event):
57
+ if event.x is None or event.display_width is None:
58
+ return
59
+ fx = float(np.clip(event.x / event.display_width, 0.02, 0.98))
60
+ fy = float(np.clip(event.y / event.display_height, 0.02, 0.98))
61
+ markers = fig.figure_markers # current list (a copy)
62
+ n = sum(1 for m in markers if m["kind"] == "text")
63
+ markers.append({"kind": "arrow", "x": fx, "y": fy,
64
+ "u": 0.08, "v": -0.08, "color": "#40c4ff", "linewidth": 2})
65
+ markers.append({"kind": "text", "x": fx + 0.08, "y": fy - 0.10,
66
+ "text": f"mark {n}", "color": "#40c4ff", "fontsize": 13})
67
+ fig.set_figure_markers(markers)
68
+
69
+
70
+ v.add_event_handler(_on_double_click, "double_click")
71
+
72
+ fig.set_help(
73
+ "Double-click on the image to drop a labelled arrow.\n"
74
+ "Drag any annotation to reposition it (edit mode is on)."
75
+ )
76
+
77
+ fig # Interactive
78
+
79
+ # %%
80
+ # Read the placements back
81
+ # ------------------------
82
+ # After the user drags things around, ``fig.figure_markers`` reflects the
83
+ # current fraction positions — persist them, export them, or feed them into a
84
+ # report.
85
+
86
+ for m in fig.figure_markers:
87
+ print(m["kind"], round(m["x"], 3), round(m["y"], 3))
@@ -29,7 +29,7 @@ import anyplotlib as apl
29
29
  rng = np.random.default_rng(11)
30
30
 
31
31
  # ── 1. Synthetic 3-D polycrystal: nearest-seed voxel grain map ──────────────
32
- N = 48 # volume is N³ voxels, indexed V[z, y, x]
32
+ N = 24 # volume is N³ voxels, indexed V[z, y, x]
33
33
  N_GRAINS = 40
34
34
 
35
35
  seeds = rng.uniform(0, N, size=(N_GRAINS, 3)) # (z, y, x)
@@ -76,7 +76,8 @@ grain_rgb_u8 = (rgb * 255).astype(np.uint8) # (N_GRAINS, 3)
76
76
  # planes. This anchors the highlight exactly where the slices intersect,
77
77
  # shows real slice contents in 3-D, and scales: the on-plane count is
78
78
  # ~3·(N/step)² regardless of N, so it stays fast even for a 256³ volume.
79
- VSTEP = max(1, N // 48) # in-plane downsample ~48² cubes per plane
79
+ VSTEP = max(1, N // 48) # in-plane downsample, capping at ~48² cubes/plane
80
+ # for large N (no downsampling at this N=24)
80
81
 
81
82
  # Voxel cube size in data units. A touch larger than VSTEP so the three
82
83
  # slabs read as solid sheets rather than a dotted grid.
@@ -140,6 +141,8 @@ v_vol = ax_vol.voxels(
140
141
  size=VOXSIZE, alpha=0.55,
141
142
  x_label="x", y_label="y", z_label="z",
142
143
  bounds=((0, N - 1),) * 3, zoom=1.1,
144
+ # gpu="auto" (default): ~1.7k cubes is over the ~1k voxel threshold, so the
145
+ # WebGPU instanced path handles each drag re-slice when WebGPU is present.
143
146
  )
144
147
  v_vol.set_title("Grain volume — drag a plane to re-slice")
145
148
 
@@ -0,0 +1,49 @@
1
+ """
2
+ Coordinate transforms — pin markers to the axes or the screen
3
+ =============================================================
4
+
5
+ Every ``add_*`` marker method takes a ``transform`` that decides which
6
+ coordinate system its positions live in:
7
+
8
+ - ``"data"`` (default) — data coordinates; the marker moves and scales with
9
+ zoom / pan, staying glued to the underlying data.
10
+ - ``"axes"`` — axes-normalised ``(0, 0)`` bottom-left … ``(1, 1)`` top-right;
11
+ the marker stays in the same corner of the panel no matter how you zoom.
12
+ - ``"display"`` — raw CSS pixels within the panel; a fixed-size decoration.
13
+
14
+ The ``"axes"`` and ``"display"`` transforms are how you build overlays that
15
+ should *not* track the data — a navigation index in the corner, a scale bar, a
16
+ persistent legend chip.
17
+ """
18
+ import numpy as np
19
+ import anyplotlib as apl
20
+
21
+ rng = np.random.default_rng(1)
22
+ data = rng.standard_normal((128, 128)).cumsum(0).cumsum(1)
23
+ data = (data - data.min()) / (data.max() - data.min())
24
+ xy = np.linspace(0, 10, 128)
25
+
26
+ fig, ax = apl.subplots(1, 1, figsize=(480, 480))
27
+ v = ax.imshow(data, axes=[xy, xy], units="nm")
28
+
29
+ # Data-anchored label — sits at (5, 5) in nm and rides along on zoom/pan.
30
+ v.add_texts(offsets=[(5, 5)], texts=["feature @ (5, 5)"],
31
+ color="#ffffff", fontsize=12, name="data_label")
32
+
33
+ # Axes-anchored index — stays pinned to the top-left corner of the panel
34
+ # regardless of zoom (0, 1 = top-left in axes fractions).
35
+ v.add_texts(offsets=[(0.04, 0.96)], texts=["frame 3 / 20"],
36
+ transform="axes", color="#ffd54f", fontsize=13, name="nav_index")
37
+
38
+ fig
39
+
40
+ # %%
41
+ # Try it
42
+ # ------
43
+ # Zoom into the image: the white ``feature`` label moves with the data, while
44
+ # the yellow ``frame 3 / 20`` index stays locked to the corner — because it is
45
+ # positioned in ``"axes"`` coordinates.
46
+
47
+ fig.set_help("Zoom in: the corner index stays put; the data label moves.")
48
+
49
+ fig # Interactive
@@ -0,0 +1,82 @@
1
+ """
2
+ GPU-accelerated voxels
3
+ ======================
4
+
5
+ :meth:`~anyplotlib.Axes.voxels` renders shaded translucent cubes for a
6
+ volumetric field. With ``gpu="auto"`` (the default) anyplotlib uses WebGPU
7
+ instancing when a GPU is available and the cube count is large, so hundreds of
8
+ thousands of voxels stay interactive; otherwise it falls back to the Canvas2D
9
+ path. Read :attr:`~anyplotlib.Plot3D.gpu_active` after the first frame to see
10
+ which path was chosen.
11
+
12
+ Here we build a dense spherical shell — enough cubes that the WebGPU path
13
+ kicks in — and drop a draggable :class:`~anyplotlib.PlaneWidget` through it as
14
+ a slice selector.
15
+ """
16
+ import numpy as np
17
+ import anyplotlib as apl
18
+
19
+ # %%
20
+ # Build a volumetric field
21
+ # ------------------------
22
+ # Voxel *centres* are passed as three flat coordinate arrays (not a dense 3-D
23
+ # grid), so you only send the cubes you actually want drawn. We keep the
24
+ # voxels inside a spherical shell and colour them by radius.
25
+
26
+ N = 64
27
+ g = np.arange(N)
28
+ Z, Y, X = np.meshgrid(g, g, g, indexing="ij")
29
+ r = np.sqrt((X - N / 2) ** 2 + (Y - N / 2) ** 2 + (Z - N / 2) ** 2)
30
+ shell = (r > N * 0.30) & (r < N * 0.42) # a hollow sphere
31
+
32
+ xs, ys, zs = X[shell], Y[shell], Z[shell]
33
+ print(f"{xs.size:,} voxels") # tens of thousands → GPU path under gpu='auto'
34
+
35
+ # Colour by radius with the viridis-ish default cycle mapped through intensity.
36
+ t = (r[shell] - r[shell].min()) / (np.ptp(r[shell]) + 1e-9)
37
+ colors = np.stack([0.2 + 0.8 * t, 0.4 * np.ones_like(t), 1.0 - 0.8 * t], axis=1)
38
+
39
+ fig, ax = apl.subplots(1, 1, figsize=(560, 520))
40
+ vol = ax.voxels(
41
+ xs, ys, zs, colors=colors,
42
+ size=1.0, alpha=0.35,
43
+ bounds=((0, N - 1),) * 3,
44
+ azimuth=-55, elevation=28, zoom=1.1,
45
+ gpu="auto", # WebGPU when available, Canvas2D otherwise
46
+ )
47
+ vol.set_title("Spherical shell — drag to rotate, scroll to zoom")
48
+
49
+ # %%
50
+ # Add a slice-selector plane
51
+ # --------------------------
52
+ # A :class:`~anyplotlib.PlaneWidget` is a draggable axis-aligned plane. Voxels
53
+ # lying on it render more opaque, so the current slice pops out of the
54
+ # translucent volume. Drag it along z in the browser, or move it from Python.
55
+
56
+ plane = vol.add_widget("plane", axis="z", position=N // 2,
57
+ color="#40c4ff", alpha=0.18)
58
+
59
+
60
+ @plane.add_event_handler("pointer_move")
61
+ def _on_slice(event):
62
+ # Fires while the plane is dragged; pw.position holds the live slice index.
63
+ print("slice at z =", round(plane.position, 1))
64
+
65
+
66
+ fig.set_help(
67
+ "Drag: rotate · Scroll: zoom · R: reset view\n"
68
+ "Drag the blue plane to slide the z-slice through the shell."
69
+ )
70
+
71
+ fig # Interactive
72
+
73
+ # %%
74
+ # Which render path ran?
75
+ # ----------------------
76
+ # ``gpu_active`` is populated once the browser reports back after the first
77
+ # frame. It is ``True`` when the WebGPU instanced path is live, ``False`` on
78
+ # the Canvas2D fallback, and ``None`` before the first frame (as when this
79
+ # gallery page is built headlessly). Force a path with ``gpu=True`` /
80
+ # ``gpu=False`` if you need determinism.
81
+
82
+ print("gpu_active:", vol.gpu_active)
@@ -0,0 +1,43 @@
1
+ """
2
+ Step lines and log-scale spectra
3
+ ================================
4
+
5
+ Two 1-D options that suit spectral data: a **mid-riser step** line (constant
6
+ within each bin, jumping at bin midpoints) via ``linestyle="step-mid"``, and a
7
+ **logarithmic y-axis** via :meth:`~anyplotlib.Axes.semilogy` (or
8
+ ``ax.plot(..., yscale="log")``).
9
+ """
10
+ import numpy as np
11
+ import anyplotlib as apl
12
+
13
+ # A noisy binned spectrum.
14
+ rng = np.random.default_rng(0)
15
+ energy = np.linspace(0, 20, 60)
16
+ counts = (np.exp(-(energy - 6) ** 2 / 4) * 1000
17
+ + np.exp(-(energy - 13) ** 2 / 8) * 400
18
+ + rng.uniform(0, 20, energy.size))
19
+
20
+ # %%
21
+ # Step line
22
+ # ---------
23
+ # ``linestyle="step-mid"`` draws a horizontal segment centred on each x value
24
+ # with vertical risers between them — the standard way to show histogram-like
25
+ # spectra without implying interpolation between channels.
26
+
27
+ fig, ax = apl.subplots(1, 1, figsize=(560, 340))
28
+ ax.plot(counts, axes=[energy], color="#4fc3f7",
29
+ linestyle="step-mid", label="counts")
30
+
31
+ fig
32
+
33
+ # %%
34
+ # Log y-axis
35
+ # ----------
36
+ # ``semilogy`` is shorthand for a log y-scale, which brings out the small
37
+ # secondary peak that the linear plot flattens. Combine it with the step line
38
+ # for a classic spectroscopy view.
39
+
40
+ fig2, ax2 = apl.subplots(1, 1, figsize=(560, 340))
41
+ ax2.semilogy(counts, axes=[energy], color="#ff7043", linestyle="step-mid")
42
+
43
+ fig2