anyplotlib 0.6.0__tar.gz → 0.7.1__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.6.0 → anyplotlib-0.7.1}/CHANGELOG.rst +40 -0
  2. anyplotlib-0.7.1/Examples/PlotTypes/plot_key_overlays.py +169 -0
  3. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/PKG-INFO +1 -1
  4. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/FIGURE_ESM.md +28 -27
  5. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/__init__.py +2 -1
  6. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/_base_plot.py +166 -0
  7. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/figure/_figure.py +27 -3
  8. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/figure_esm.js +237 -1
  9. anyplotlib-0.7.1/anyplotlib/keys.py +249 -0
  10. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/plot2d/_plot2d.py +1 -1
  11. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/plot3d/_plot3d.py +1 -1
  12. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/plotxy/_plotxy.py +1 -1
  13. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/conftest.py +71 -0
  14. anyplotlib-0.7.1/anyplotlib/tests/test_embed/test_export_pixel_tokens.py +174 -0
  15. anyplotlib-0.7.1/anyplotlib/tests/test_keys/test_keys.py +413 -0
  16. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/_root/index.html +3 -3
  17. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/_root/switcher.json +10 -0
  18. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/api/index.rst +1 -0
  19. anyplotlib-0.7.1/docs/api/keys.rst +42 -0
  20. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/conf.py +1 -1
  21. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/pyproject.toml +1 -1
  22. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/.github/workflows/ci.yml +0 -0
  23. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/.github/workflows/docs.yml +0 -0
  24. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/.github/workflows/prepare_release.yml +0 -0
  25. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/.github/workflows/release.yml +0 -0
  26. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/.github/workflows/tests.yml +0 -0
  27. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/.gitignore +0 -0
  28. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/AGENTS.md +0 -0
  29. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Benchmarks/README.rst +0 -0
  30. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Benchmarks/plot_benchmark_comparison.py +0 -0
  31. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/README.rst +0 -0
  32. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_3d_spectral_viewer.py +0 -0
  33. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_eels_explorer.py +0 -0
  34. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_figure_annotations.py +0 -0
  35. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_interactive_fft.py +0 -0
  36. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_interactive_fitting.py +0 -0
  37. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_ipf_density_map.py +0 -0
  38. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_ipf_explorer.py +0 -0
  39. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_key_bindings.py +0 -0
  40. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_particle_picker.py +0 -0
  41. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_point_widget.py +0 -0
  42. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_segment_by_contrast.py +0 -0
  43. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_segment_by_contrast_advanced.py +0 -0
  44. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_spectra_roi_inspector.py +0 -0
  45. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_star_globe_explorer.py +0 -0
  46. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_threshold_explorer.py +0 -0
  47. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Interactive/plot_voxel_grain_explorer.py +0 -0
  48. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Markers/README.rst +0 -0
  49. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Markers/plot_arrows.py +0 -0
  50. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Markers/plot_circles.py +0 -0
  51. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Markers/plot_ellipses.py +0 -0
  52. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Markers/plot_horizontal_lines.py +0 -0
  53. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Markers/plot_line_segments.py +0 -0
  54. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Markers/plot_points.py +0 -0
  55. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Markers/plot_polygons.py +0 -0
  56. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Markers/plot_rectangles.py +0 -0
  57. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Markers/plot_squares.py +0 -0
  58. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Markers/plot_texts.py +0 -0
  59. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Markers/plot_transform_markers.py +0 -0
  60. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Markers/plot_vertical_lines.py +0 -0
  61. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/README.rst +0 -0
  62. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/plot_3d.py +0 -0
  63. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/plot_3d_texture.py +0 -0
  64. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/plot_bar.py +0 -0
  65. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/plot_gpu_voxels.py +0 -0
  66. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/plot_gridspec_custom.py +0 -0
  67. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/plot_image2d.py +0 -0
  68. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/plot_inset.py +0 -0
  69. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/plot_label_formatting.py +0 -0
  70. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/plot_line_styles.py +0 -0
  71. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/plot_pcolormesh.py +0 -0
  72. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/plot_spectra1d.py +0 -0
  73. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/plot_step_and_log.py +0 -0
  74. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/plot_tile_backend.py +0 -0
  75. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/PlotTypes/plot_twinx.py +0 -0
  76. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/README.rst +0 -0
  77. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Widgets/README.rst +0 -0
  78. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Widgets/plot_widget1d_hline.py +0 -0
  79. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Widgets/plot_widget1d_point.py +0 -0
  80. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Widgets/plot_widget1d_range.py +0 -0
  81. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Widgets/plot_widget1d_vline.py +0 -0
  82. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Widgets/plot_widget2d_annular.py +0 -0
  83. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Widgets/plot_widget2d_arrow.py +0 -0
  84. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Widgets/plot_widget2d_circle.py +0 -0
  85. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Widgets/plot_widget2d_crosshair.py +0 -0
  86. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Widgets/plot_widget2d_label.py +0 -0
  87. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Widgets/plot_widget2d_polygon.py +0 -0
  88. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Widgets/plot_widget2d_rectangle.py +0 -0
  89. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Examples/Widgets/plot_widget3d_plane.py +0 -0
  90. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/LICENSE +0 -0
  91. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/Makefile +0 -0
  92. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/README.md +0 -0
  93. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/_binary_frame.py +0 -0
  94. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/_electron.py +0 -0
  95. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/_repr_utils.py +0 -0
  96. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/_utils.py +0 -0
  97. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/axes/__init__.py +0 -0
  98. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/axes/_axes.py +0 -0
  99. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/axes/_inset_axes.py +0 -0
  100. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/callbacks.py +0 -0
  101. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/conftest.py +0 -0
  102. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/embed.py +0 -0
  103. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/figure/__init__.py +0 -0
  104. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/figure/_gridspec.py +0 -0
  105. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/figure/_subplots.py +0 -0
  106. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/markers.py +0 -0
  107. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/plot1d/__init__.py +0 -0
  108. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/plot1d/_plot1d.py +0 -0
  109. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/plot1d/_plotbar.py +0 -0
  110. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/plot2d/__init__.py +0 -0
  111. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/plot2d/_layer.py +0 -0
  112. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/plot2d/_plotmesh.py +0 -0
  113. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/plot2d/_tile_backend.py +0 -0
  114. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/plot3d/__init__.py +0 -0
  115. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/plotxy/__init__.py +0 -0
  116. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/sphinx_anywidget/__init__.py +0 -0
  117. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/sphinx_anywidget/_directive.py +0 -0
  118. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/sphinx_anywidget/_repr_utils.py +0 -0
  119. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/sphinx_anywidget/_scraper.py +0 -0
  120. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/sphinx_anywidget/_wheel_builder.py +0 -0
  121. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/sphinx_anywidget/static/anywidget_bridge.js +0 -0
  122. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/sphinx_anywidget/static/anywidget_overlay.css +0 -0
  123. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/sphinx_anywidget/tests/__init__.py +0 -0
  124. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/sphinx_anywidget/tests/conftest.py +0 -0
  125. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/sphinx_anywidget/tests/test_directive.py +0 -0
  126. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/sphinx_anywidget/tests/test_init.py +0 -0
  127. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/sphinx_anywidget/tests/test_repr_utils.py +0 -0
  128. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/sphinx_anywidget/tests/test_scraper.py +0 -0
  129. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/sphinx_anywidget/tests/test_wheel_builder.py +0 -0
  130. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/__init__.py +0 -0
  131. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/_gpu_clim_check.cjs +0 -0
  132. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/_png_utils.py +0 -0
  133. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/bar_basic.png +0 -0
  134. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/gridspec_3col_equal_spectra.png +0 -0
  135. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/gridspec_asymmetric_width_ratios.png +0 -0
  136. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/gridspec_height_ratio_image_histogram.png +0 -0
  137. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/gridspec_image_two_spectra.png +0 -0
  138. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/gridspec_side_by_side_1d.png +0 -0
  139. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/gridspec_spanning_top_two_bottom.png +0 -0
  140. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/imshow_axis_off.png +0 -0
  141. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/imshow_checkerboard.png +0 -0
  142. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/imshow_gradient.png +0 -0
  143. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/imshow_labels.png +0 -0
  144. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/imshow_viridis.png +0 -0
  145. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/inset_1d.png +0 -0
  146. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/inset_maximized.png +0 -0
  147. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/inset_minimized.png +0 -0
  148. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/inset_normal_2d.png +0 -0
  149. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/inset_stacked.png +0 -0
  150. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/inset_stacked_one_minimized.png +0 -0
  151. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/pcolormesh_uniform.png +0 -0
  152. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/plot1d_all_linestyles.png +0 -0
  153. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/plot1d_alpha.png +0 -0
  154. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/plot1d_axis_off.png +0 -0
  155. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/plot1d_dashed.png +0 -0
  156. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/plot1d_marker_symbols.png +0 -0
  157. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/plot1d_markers.png +0 -0
  158. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/plot1d_multi.png +0 -0
  159. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/plot1d_sine.png +0 -0
  160. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/plot1d_title.png +0 -0
  161. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/plot1d_twinx.png +0 -0
  162. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/plot3d_surface.png +0 -0
  163. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/baselines/subplots_2x1.png +0 -0
  164. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_benchmarks/__init__.py +0 -0
  165. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_benchmarks/baselines.json +0 -0
  166. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_benchmarks/benchmarks/baselines.json +0 -0
  167. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_benchmarks/test_benchmarks.py +0 -0
  168. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_benchmarks/test_benchmarks_py.py +0 -0
  169. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_documentation/__init__.py +0 -0
  170. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_documentation/test_bridge.py +0 -0
  171. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_documentation/test_push_hook.py +0 -0
  172. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_documentation/test_scraper.py +0 -0
  173. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_electron/__init__.py +0 -0
  174. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_electron/test_binary_frame.py +0 -0
  175. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_embed/__init__.py +0 -0
  176. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_embed/test_embed_api.py +0 -0
  177. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_embed/test_embed_mount.py +0 -0
  178. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_embed/test_export_png.py +0 -0
  179. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_embed/test_export_widget_sync.py +0 -0
  180. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_examples/__init__.py +0 -0
  181. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_examples/test_interactive_examples.py +0 -0
  182. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/__init__.py +0 -0
  183. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/_event_test_utils.py +0 -0
  184. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_blit_audit.py +0 -0
  185. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_callbacks_playwright.py +0 -0
  186. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_callbacks_unit.py +0 -0
  187. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_edit_chrome.py +0 -0
  188. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_edit_chrome_playwright.py +0 -0
  189. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_event_pause_hold.py +0 -0
  190. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_event_plots.py +0 -0
  191. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_event_settled.py +0 -0
  192. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_events_regression.py +0 -0
  193. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_orbit_direction.py +0 -0
  194. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_pointer_down_1d.py +0 -0
  195. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_range_orientation_snap.py +0 -0
  196. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_star_globe_linking.py +0 -0
  197. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_tile_parity_playwright.py +0 -0
  198. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_title.py +0 -0
  199. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_touch.py +0 -0
  200. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_widget_brush.py +0 -0
  201. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_widget_max_extent.py +0 -0
  202. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_widget_set_notify.py +0 -0
  203. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_widgets.py +0 -0
  204. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_interactive/test_widgets_line_2d.py +0 -0
  205. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_labels/__init__.py +0 -0
  206. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_labels/test_label_api.py +0 -0
  207. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_labels/test_label_rendering.py +0 -0
  208. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_labels/test_no_clipping.py +0 -0
  209. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_labels/test_ylabel_clearance.py +0 -0
  210. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_layouts/__init__.py +0 -0
  211. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_layouts/test_batch.py +0 -0
  212. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_layouts/test_coord_conversion.py +0 -0
  213. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_layouts/test_geom_channel.py +0 -0
  214. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_layouts/test_gridspec.py +0 -0
  215. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_layouts/test_inset.py +0 -0
  216. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_layouts/test_inset_callout.py +0 -0
  217. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_layouts/test_interaction.py +0 -0
  218. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_layouts/test_visual.py +0 -0
  219. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_markers/__init__.py +0 -0
  220. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_markers/test_marker_transforms.py +0 -0
  221. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_markers/test_markers.py +0 -0
  222. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_markers/test_per_marker_colors.py +0 -0
  223. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_markers/test_size_units.py +0 -0
  224. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot1d/__init__.py +0 -0
  225. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot1d/test_line_none_rendering.py +0 -0
  226. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot1d/test_plot1d.py +0 -0
  227. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot1d/test_plotbar.py +0 -0
  228. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/__init__.py +0 -0
  229. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_colorbar_gap_scalebar.py +0 -0
  230. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_detail_tile.py +0 -0
  231. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_first_paint_race.py +0 -0
  232. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_gpu_image.py +0 -0
  233. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_gpu_parity_playwright.py +0 -0
  234. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_imshow.py +0 -0
  235. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_imshow_rgb.py +0 -0
  236. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_layers.py +0 -0
  237. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_layers_playwright.py +0 -0
  238. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_pcolormesh.py +0 -0
  239. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_plot2d_api.py +0 -0
  240. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_rapid_frame_update.py +0 -0
  241. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_tile_backend.py +0 -0
  242. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_tile_diagnostics.py +0 -0
  243. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_tiled_imshow.py +0 -0
  244. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot2d/test_zoom_view_write.py +0 -0
  245. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot3d/__init__.py +0 -0
  246. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot3d/test_colors_highlight.py +0 -0
  247. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot3d/test_gpu_depth.py +0 -0
  248. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot3d/test_gpu_fallback.py +0 -0
  249. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot3d/test_gpu_hidden_reveal.py +0 -0
  250. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot3d/test_plot3d.py +0 -0
  251. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot3d/test_texture.py +0 -0
  252. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plot3d/test_voxels_planes.py +0 -0
  253. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/tests/test_plotxy/test_plotxy.py +0 -0
  254. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/widgets/__init__.py +0 -0
  255. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/widgets/_base.py +0 -0
  256. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/widgets/_widgets1d.py +0 -0
  257. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/widgets/_widgets2d.py +0 -0
  258. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/anyplotlib/widgets/_widgets3d.py +0 -0
  259. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/_sg_html_scraper.py +0 -0
  260. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/_static/anyplotlib.svg +0 -0
  261. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/_static/custom.css +0 -0
  262. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/_static/pyodide_bridge.js +0 -0
  263. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/_templates/autosummary/class.rst +0 -0
  264. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/api/callbacks.rst +0 -0
  265. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/api/figure.rst +0 -0
  266. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/api/figure_plots.rst +0 -0
  267. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/api/markers.rst +0 -0
  268. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/api/widgets.rst +0 -0
  269. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/benchmarking.rst +0 -0
  270. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/dev/index.rst +0 -0
  271. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/embedding.rst +0 -0
  272. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/events.rst +0 -0
  273. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/getting_started.rst +0 -0
  274. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/docs/index.rst +0 -0
  275. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/make.bat +0 -0
  276. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/upcoming_changes/.gitkeep +0 -0
  277. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/upcoming_changes/README.rst +0 -0
  278. {anyplotlib-0.6.0 → anyplotlib-0.7.1}/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.1 (2026-08-02)
14
+ ==================
15
+
16
+ Bug Fixes
17
+ ---------
18
+
19
+ - Fixed ``save_html`` / ``to_html`` / ``figure_state`` dropping image pixels
20
+ under the Electron binary transport: the snapshot kept the ``"\x00bin:"``
21
+ change-tokens whose bytes only ever ride the live PLOTBIN channel, so an
22
+ overlay added with :meth:`~anyplotlib.Plot2D.add_layer` did not render in the
23
+ exported document. (`#52 <https://github.com/CSSFrancis/anyplotlib/pull/52>`_)
24
+
25
+
26
+ Documentation
27
+ -------------
28
+
29
+ - Fixed the documentation build failing on
30
+ :class:`~anyplotlib.keys.KeyOverlay`, whose ``id`` and ``name`` were described
31
+ both in the class docstring and by the properties themselves — a duplicate
32
+ object description, which the build treats as an error. (`#53 <https://github.com/CSSFrancis/anyplotlib/pull/53>`_)
33
+
34
+
35
+ 0.7.0 (2026-07-31)
36
+ ==================
37
+
38
+ New Features
39
+ ------------
40
+
41
+ - Added :meth:`~anyplotlib.Plot2D.add_key` for pinning a floating image *key* over
42
+ a panel — an inverse pole figure triangle over an orientation map, a hue wheel
43
+ over a polarization field, a phase key over a segmentation. A key is the scale
44
+ bar's sibling: it floats in screen space and neither pans nor zooms with the
45
+ data, it takes an RGBA image so a triangle or a disc needs no rectangular card
46
+ around it, and ``labels=`` annotates the picture itself (an IPF triangle's
47
+ corner indices) in fractions of the key image. Optional ``bgcolor`` /
48
+ ``border`` / ``alpha`` give it a card when the data underneath is busy, and
49
+ ``hover_only=True`` reveals it only while the pointer is over the panel.
50
+ Available on every panel type, and included in PNG export.
51
+
52
+
13
53
  0.6.0 (2026-07-31)
14
54
  ==================
15
55
 
@@ -0,0 +1,169 @@
1
+ """
2
+ Floating keys — IPF triangles and colour wheels
3
+ ===============================================
4
+
5
+ Some plots are coloured by a *direction*, not by a magnitude, and a colorbar
6
+ cannot say what the colours mean. An orientation map is coloured by which
7
+ crystal axis points at you; a polarization map by which way the moment lies in
8
+ the plane. Both need a small picture as their legend: an inverse pole figure
9
+ triangle, or a hue wheel.
10
+
11
+ :meth:`~anyplotlib.Plot2D.add_key` pins that picture over the panel in *screen*
12
+ space — it does not pan or zoom with the data, exactly like the scale bar it is
13
+ modelled on.
14
+
15
+ This is deliberately not :meth:`~anyplotlib.Figure.add_inset`, which is a
16
+ draggable window with a title bar and its own canvas stack. That is the right
17
+ tool when the overlay is a live plot; a key is a static picture that should
18
+ read as part of the figure.
19
+ """
20
+ import numpy as np
21
+
22
+ import anyplotlib as apl
23
+
24
+ # %%
25
+ # The IPF colour key
26
+ # ------------------
27
+ # The standard cubic stereographic triangle. Each pixel's colour is its
28
+ # barycentric distance to the three corners, which is the classic IPF key: a
29
+ # grain pointing [1 0 0] at the detector reads red, [1 1 0] green, [1 1 1] blue.
30
+ #
31
+ # The triangle is built as an ``(H, W, 4)`` RGBA array, and **alpha 0 outside
32
+ # the triangle** is what lets it sit on the map without a rectangular card
33
+ # around it.
34
+
35
+ KEY_N = 220
36
+
37
+
38
+ def ipf_triangle(n=KEY_N):
39
+ """RGBA image of the 001–011–111 stereographic triangle."""
40
+ yy, xx = np.mgrid[0:n, 0:n]
41
+ u = xx / (n - 1)
42
+ v = 1.0 - yy / (n - 1)
43
+ inside = v <= u + 1e-9 # lower-right half
44
+
45
+ # Barycentric-ish weights: distance to each corner, normalised.
46
+ d100 = np.hypot(u, v) # corner (0, 0)
47
+ d110 = np.hypot(u - 1.0, v) # corner (1, 0)
48
+ d111 = np.hypot(u - 1.0, v - 1.0) # corner (1, 1)
49
+ far = np.maximum.reduce([d100, d110, d111])
50
+ rgb = np.stack([1 - d100 / far, 1 - d110 / far, 1 - d111 / far], -1)
51
+ rgb /= rgb.max(-1, keepdims=True) + 1e-9 # full saturation at the corners
52
+
53
+ img = np.zeros((n, n, 4), np.uint8)
54
+ img[..., :3] = np.clip(rgb, 0, 1) * 255
55
+ img[..., 3] = np.where(inside, 255, 0)
56
+ return img
57
+
58
+
59
+ # %%
60
+ # A synthetic orientation map
61
+ # ---------------------------
62
+ # Voronoi grains, each with a random orientation, coloured through the same key
63
+ # so the map and its legend agree by construction.
64
+
65
+ rng = np.random.default_rng(11)
66
+ H, W, NGRAIN = 210, 280, 40
67
+
68
+ cy, cx = rng.uniform(0, H, NGRAIN), rng.uniform(0, W, NGRAIN)
69
+ yy, xx = np.mgrid[0:H, 0:W]
70
+ grain = np.hypot(yy[..., None] - cy, xx[..., None] - cx).argmin(-1)
71
+
72
+ # Each grain gets a point in the triangle, then reads its colour off the key.
73
+ gu = rng.uniform(0, 1, NGRAIN)
74
+ gv = rng.uniform(0, 1, NGRAIN) * gu # keep it inside v <= u
75
+ key_img = ipf_triangle()
76
+ kx = np.clip((gu * (KEY_N - 1)).astype(int), 0, KEY_N - 1)
77
+ ky = np.clip(((1 - gv) * (KEY_N - 1)).astype(int), 0, KEY_N - 1)
78
+ grain_rgb = key_img[ky, kx, :3]
79
+ ipf_map = grain_rgb[grain] # (H, W, 3) true colour
80
+
81
+ # %%
82
+ # Pinning the key
83
+ # ---------------
84
+ # ``labels`` draws text *inside* the picture, positioned as fractions of the
85
+ # key image, so the corner indices stay on the corners at any ``size``.
86
+
87
+ fig, ax = apl.subplots(1, 1, figsize=(520, 420))
88
+ vmap = ax.imshow(ipf_map)
89
+ vmap.set_title("orientation map")
90
+
91
+ vmap.add_key(
92
+ key_img,
93
+ corner="bottom-right",
94
+ size=0.34,
95
+ # `align` keeps a label inside the key: centring text on a corner would
96
+ # hang half of it off the edge, where the panel clips it.
97
+ labels=[
98
+ {"x": 0.02, "y": 0.93, "text": "[1 0 0]", "align": "left"},
99
+ {"x": 0.98, "y": 0.93, "text": "[1 1 0]", "align": "right"},
100
+ {"x": 0.98, "y": 0.08, "text": "[1 1 1]", "align": "right"},
101
+ ],
102
+ name="ipf",
103
+ )
104
+
105
+ fig
106
+
107
+ # %%
108
+ # A colour wheel over a polarization map
109
+ # --------------------------------------
110
+ # Same mechanism, different legend. Here the key gets a translucent card
111
+ # (``bgcolor``) because the field underneath is saturated everywhere and a bare
112
+ # wheel would fight with it.
113
+
114
+ def hue_wheel(n=KEY_N):
115
+ """RGBA colour wheel: hue = in-plane angle, value = magnitude."""
116
+ yy, xx = np.mgrid[0:n, 0:n]
117
+ ang = (np.arctan2(-(yy - n / 2), xx - n / 2) + np.pi) / (2 * np.pi)
118
+ rad = np.hypot(yy - n / 2, xx - n / 2) / (n / 2)
119
+ h6 = ang * 6.0
120
+ chan = np.clip(
121
+ np.abs(((h6 + np.array([0, 4, 2])[:, None, None]) % 6) - 3) - 1, 0, 1)
122
+ img = np.zeros((n, n, 4), np.uint8)
123
+ img[..., :3] = chan.transpose(1, 2, 0) * 255 * np.clip(rad, 0, 1)[..., None]
124
+ img[..., 3] = np.where(rad <= 1.0, 255, 0)
125
+ return img
126
+
127
+
128
+ # A vortex: the moment angle winds once around the centre.
129
+ ang = np.arctan2(yy - H / 2, xx - W / 2)
130
+ mag = np.clip(np.hypot(yy - H / 2, xx - W / 2) / (0.5 * min(H, W)), 0, 1)
131
+ a6 = ((ang + np.pi) / (2 * np.pi)) * 6.0
132
+ chan = np.clip(np.abs(((a6 + np.array([0, 4, 2])[:, None, None]) % 6) - 3) - 1, 0, 1)
133
+ polar_map = (chan.transpose(1, 2, 0) * 255 * (0.3 + 0.7 * mag)[..., None]).astype(np.uint8)
134
+
135
+ fig2, ax2 = apl.subplots(1, 1, figsize=(520, 420))
136
+ vpol = ax2.imshow(polar_map)
137
+ vpol.set_title("in-plane magnetic polarization")
138
+
139
+ vpol.add_key(
140
+ hue_wheel(),
141
+ corner="top-right",
142
+ size=0.26,
143
+ bgcolor="rgba(0,0,0,0.45)", # legible over a busy field
144
+ border="#ffffff",
145
+ label="moment direction",
146
+ labels=[
147
+ (0.5, 0.06, "N"), (0.94, 0.5, "E"),
148
+ (0.5, 0.94, "S"), (0.06, 0.5, "W"),
149
+ ],
150
+ name="wheel",
151
+ )
152
+
153
+ fig2
154
+
155
+ # %%
156
+ # Keeping it out of the way
157
+ # -------------------------
158
+ # ``hover_only=True`` shows the key only while the pointer is over the panel —
159
+ # a reading aid that does not sit on the data while you study it. PNG export
160
+ # renders the panel as though the pointer were there, so an exported figure
161
+ # still carries the key.
162
+ #
163
+ # Everything is live: :meth:`~anyplotlib.KeyOverlay.set` restyles a key without
164
+ # re-sending the picture, and :meth:`~anyplotlib.KeyOverlay.set_image` swaps
165
+ # the picture without disturbing the placement::
166
+ #
167
+ # key = vmap.get_key("ipf")
168
+ # key.set(size=0.4, corner="top-left")
169
+ # key.visible = False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anyplotlib
3
- Version: 0.6.0
3
+ Version: 0.7.1
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
@@ -1,6 +1,6 @@
1
1
  # FIGURE_ESM.md — Navigator for `figure_esm.js`
2
2
 
3
- `figure_esm.js` is **~9,300 lines** and one big closure. Everything lives inside
3
+ `figure_esm.js` is **~9,470 lines** and one big closure. Everything lives inside
4
4
  `function render({ model, el })` so that all helpers share the same scope
5
5
  (`theme`, `PAD_*`, `panels` Map, etc.). This document is a section map so you
6
6
  can jump straight to the relevant code without reading the whole file.
@@ -55,23 +55,24 @@ Rule 5 – Text never clips. Optional gutters earn real layout space:
55
55
  | **2D gutter geometry**: `_cbWidth` / `_cbGap` / `_padT` / `_titlePx` | 301 / 313 / 323 / 333 |
56
56
  | **Layout engine** `applyLayout` | 774 |
57
57
  | `_buildCanvasStack` | 857 |
58
- | `_createPanelDOM` | 989 |
59
- | `_createInsetDOM` / `_applyAllInsetStates` | 1118 / 1500 |
60
- | `_resizePanelDOM` | 2213 |
61
- | **2D drawing**: `_imgFitRect` | 2372 |
62
- | `draw2d` | 2680 |
63
- | `drawScaleBar2d` / `drawColorbar2d` | 2875 / 2961 |
64
- | `_drawAxes2d` (ticks, labels, title) | 3016 |
65
- | `drawOverlay2d` / `drawMarkers2d` | 3169 / 3333 |
66
- | **Image layers**: `_layerBytes` / `_layerBitmap` / `_drawLayers2d` | 2500 / 2524 / 2585 |
58
+ | `_createPanelDOM` | 999 |
59
+ | `_createInsetDOM` / `_applyAllInsetStates` | 1129 / 1512 |
60
+ | `_resizePanelDOM` | 2225 |
61
+ | **2D drawing**: `_imgFitRect` | 2384 |
62
+ | `draw2d` | 2692 |
63
+ | `drawScaleBar2d` / `drawColorbar2d` | 2887 / 3125 |
64
+ | **Floating keys**: `_keyEnsure` / `_keyRect` / `drawKeys` | 2986 / 3009 / 3022 |
65
+ | `_drawAxes2d` (ticks, labels, title) | 3180 |
66
+ | `drawOverlay2d` / `drawMarkers2d` | 3333 / 3497 |
67
+ | **Image layers**: `_layerBytes` / `_layerBitmap` / `_drawLayers2d` | 2512 / 2536 / 2597 |
67
68
  | Binary-bytes splice: `_spliceBinaryBytes` / `_registerBinaryPixelListeners` | 730 / 761 |
68
- | **3D drawing**: `draw3d` | 5072 |
69
- | Event emission `_emitEvent` | 5909 |
70
- | 3D event handlers `_attachEvents3d` | 5961 |
71
- | **1D drawing**: `draw1d` | 6182 |
72
- | `_drawLine` (1D series + markers) | 6335 |
73
- | `drawOverlay1d` / `drawMarkers1d` | 6628 / 6712 |
74
- | Marker hit-test `_markerHitTest2d` | 6980 |
69
+ | **3D drawing**: `draw3d` | 5236 |
70
+ | Event emission `_emitEvent` | 6073 |
71
+ | 3D event handlers `_attachEvents3d` | 6125 |
72
+ | **1D drawing**: `draw1d` | 6346 |
73
+ | `_drawLine` (1D series + markers) | 6499 |
74
+ | `drawOverlay1d` / `drawMarkers1d` | 6792 / 6876 |
75
+ | Marker hit-test `_markerHitTest2d` | 7144 |
75
76
 
76
77
  > **`raster` marker (1D/PlotXY)** — `drawMarkers1d` has a `type==='raster'`
77
78
  > branch that blits a single RGBA image across data-coord `extent` (the fast
@@ -80,16 +81,16 @@ Rule 5 – Text never clips. Optional gutters earn real layout space:
80
81
  > redraws never re-transmit them; the decoded `OffscreenCanvas` is cached on
81
82
  > the marker set (`ms._rasterBmp`/`_rasterKey`). The shared `clip_path` block
82
83
  > clips it to a curved sector.
83
- | Panel event dispatch `_attachPanelEvents` | 7237 |
84
- | 2D events `_attachEvents2d` | 7261 |
85
- | 1D events `_attachEvents1d` | 7645 |
86
- | 2D widget drag `_ovHitTest2d` / `_doDrag2d` | 7917 / 8190 |
87
- | **Brush strokes**: `_brushLiveBegin` / `_brushCommit` / `_brushErase` / `_brushPaintAt` | 8103 / 8117 / 8146 / 8181 |
88
- | 1D widget drag `_canvasXToFrac1d` … / snapping `_snapVal` | 8313 / 8386 |
89
- | Shared-axis propagation `_getShareGroups` | 8457 |
90
- | Figure resize `_applyFigResizeDOM` | 8521 |
91
- | **Bar chart**: `_barGeom` / `drawBar` / `_attachEventsBar` | 8712 / 8775 / 9151 |
92
- | Generic redraw `_redrawPanel` | 9341 |
84
+ | Panel event dispatch `_attachPanelEvents` | 7401 |
85
+ | 2D events `_attachEvents2d` | 7443 |
86
+ | 1D events `_attachEvents1d` | 7827 |
87
+ | 2D widget drag `_ovHitTest2d` / `_doDrag2d` | 8099 / 8372 |
88
+ | **Brush strokes**: `_brushLiveBegin` / `_brushCommit` / `_brushErase` / `_brushPaintAt` | 8285 / 8299 / 8328 / 8363 |
89
+ | 1D widget drag `_canvasXToFrac1d` … / snapping `_snapVal` | 8495 / 8568 |
90
+ | Shared-axis propagation `_getShareGroups` | 8639 |
91
+ | Figure resize `_applyFigResizeDOM` | 8703 |
92
+ | **Bar chart**: `_barGeom` / `drawBar` / `_attachEventsBar` | 8894 / 8957 / 9333 |
93
+ | Generic redraw `_redrawPanel` | 9523 |
93
94
 
94
95
  > **`brush` widget (2-D)** — the one widget whose drag is *modal*, and the one
95
96
  > that must NOT write the model per tick. `_ovHitTest2d` takes an extra `mods`
@@ -15,6 +15,7 @@ from anyplotlib.plotxy import PlotXY
15
15
  from anyplotlib.callbacks import CallbackRegistry, Event
16
16
  from anyplotlib import embed
17
17
  from anyplotlib.markers import MarkerRegistry, MarkerGroup
18
+ from anyplotlib.keys import KeyOverlay
18
19
  from anyplotlib.widgets import (
19
20
  Widget, RectangleWidget, CircleWidget, AnnularWidget,
20
21
  CrosshairWidget, PolygonWidget, LabelWidget, ArrowWidget, BrushWidget,
@@ -43,7 +44,7 @@ __all__ = [
43
44
  "Axes", "InsetAxes", "Plot1D", "Plot2D", "PlotMesh", "Plot3D", "PlotBar",
44
45
  "Line1D",
45
46
  "CallbackRegistry", "Event",
46
- "MarkerRegistry", "MarkerGroup",
47
+ "MarkerRegistry", "MarkerGroup", "KeyOverlay",
47
48
  "Widget", "RectangleWidget", "CircleWidget", "AnnularWidget",
48
49
  "CrosshairWidget", "PolygonWidget", "LabelWidget", "ArrowWidget",
49
50
  "BrushWidget",
@@ -76,6 +76,172 @@ class _BasePlot(_EventMixin):
76
76
  self._state["axis_visible"] = True
77
77
  self._push()
78
78
 
79
+ # ------------------------------------------------------------------
80
+ # Floating image keys (see anyplotlib/keys.py)
81
+ # ------------------------------------------------------------------
82
+ def add_key(self, image, *, corner: str = "top-right", anchor=None,
83
+ size: float = 0.22, margin: float = 10.0,
84
+ bgcolor=None, border=None, border_width: float = 1.0,
85
+ radius: float = 4.0, alpha: float = 1.0,
86
+ hover_only: bool = False, visible: bool = True,
87
+ label=None, label_size: float = 10.0, label_color=None,
88
+ labels=None, name=None) -> "KeyOverlay":
89
+ """Pin a floating image key over this panel.
90
+
91
+ A key is a small picture that floats in screen space over the plot
92
+ area — it does not pan or zoom with the data. Use it for a colour
93
+ legend a colorbar cannot express: an inverse pole figure triangle over
94
+ an orientation map, a hue wheel over a polarization field, a phase key
95
+ over a segmentation::
96
+
97
+ key = plot.add_key(ipf_triangle, corner="bottom-right", size=0.28)
98
+ wheel = plot.add_key(hue_wheel, corner="top-left",
99
+ bgcolor="none", hover_only=True)
100
+
101
+ This is the lightweight sibling of :meth:`Figure.add_inset`. An inset
102
+ is a draggable window with a title bar and its own canvas stack — the
103
+ right tool when the overlay is a live plot. A key is a static picture
104
+ with no chrome unless you ask for it, so it reads as part of the
105
+ figure rather than as a floating panel.
106
+
107
+ Parameters
108
+ ----------
109
+ image : array-like, bytes, or path
110
+ An ``(H, W, 3|4)`` colour array (uint8, or float 0–1), the raw
111
+ bytes of a PNG/JPEG/GIF/WebP, or a path to such a file. An RGBA
112
+ array is the usual choice: alpha 0 outside the shape lets a
113
+ triangle or a disc sit on the image without a rectangular card
114
+ around it.
115
+ corner : str, optional
116
+ ``"top-right"`` (default), ``"top-left"``, ``"bottom-right"`` or
117
+ ``"bottom-left"``. Ignored when *anchor* is given.
118
+ anchor : (x_frac, y_frac), optional
119
+ Free placement: the key's centre as a fraction of the plot area,
120
+ from its top-left. Overrides *corner*.
121
+ size : float, optional
122
+ Width as a fraction of the plot area's **shorter** side, so a key
123
+ keeps its proportions when the panel is resized. Default 0.22.
124
+ Height follows the image's aspect ratio.
125
+ margin : float, optional
126
+ Gap in CSS px between the key and the plot-area edge, for corner
127
+ placement. Default 10.
128
+ bgcolor : str, optional
129
+ CSS colour painted behind the image — e.g. ``"rgba(0,0,0,0.45)"``
130
+ for a legible card over busy data. Default ``None`` (fully
131
+ transparent); ``"none"`` means the same thing.
132
+ border : str, optional
133
+ CSS colour for a hairline around the card. Default ``None``.
134
+ border_width : float, optional
135
+ Border stroke width in px. Default 1.
136
+ radius : float, optional
137
+ Corner radius of the card in px. Default 4.
138
+ alpha : float, optional
139
+ Opacity of the whole key, 0–1. Default 1.
140
+ hover_only : bool, optional
141
+ Show the key only while the pointer is over the panel. Default
142
+ ``False``. Useful for a reading aid you do not want sitting over
143
+ the data all the time. PNG export renders the panel as though the
144
+ pointer were over it, so a hover-only key **is** included in an
145
+ exported figure — what you save is what you see while reading.
146
+ visible : bool, optional
147
+ Draw the key at all. Default ``True``.
148
+ label : str, optional
149
+ Caption drawn under the image, mini-TeX enabled like axis labels.
150
+ label_size : float, optional
151
+ Caption size in px. Default 10.
152
+ label_color : str, optional
153
+ Caption colour. Default ``None`` (the theme's tick-label colour).
154
+ labels : list, optional
155
+ Text drawn *inside* the key, for annotating the picture itself —
156
+ an IPF triangle's corner indices, a wheel's compass points. Each
157
+ entry is ``(x, y, text)`` or a dict with those keys plus optional
158
+ ``size`` / ``color`` / ``align``. ``x`` and ``y`` are fractions of
159
+ the key image, so the text follows the picture when the key is
160
+ resized. Mini-TeX works here as in any label::
161
+
162
+ tri.add_key(ipf, labels=[
163
+ (0.02, 0.97, "[1 0 0]"),
164
+ (0.98, 0.97, "[1 1 0]"),
165
+ (0.98, 0.03, "[1 1 1]"),
166
+ ])
167
+ name : str, optional
168
+ Handle for :meth:`get_key`. Defaults to the generated id.
169
+
170
+ Returns
171
+ -------
172
+ KeyOverlay
173
+
174
+ See Also
175
+ --------
176
+ remove_key, list_keys, get_key
177
+ anyplotlib.Figure.add_inset : a full floating axes, for live plots.
178
+ """
179
+ from anyplotlib._utils import _image_to_data_url
180
+ from anyplotlib.keys import KeyOverlay, _validate
181
+
182
+ fields = _validate(dict(
183
+ corner=corner, anchor=anchor, size=size, margin=margin,
184
+ bgcolor=bgcolor, border=border, border_width=border_width,
185
+ radius=radius, alpha=alpha, hover_only=hover_only,
186
+ visible=visible, label=label, label_size=label_size,
187
+ label_color=label_color, labels=labels))
188
+ key = KeyOverlay(self, _image_to_data_url(image), name=name, **fields)
189
+ if any(k.name == key.name for k in self._key_map.values()):
190
+ raise ValueError(f"a key named {key.name!r} already exists on this "
191
+ "panel; pass a different name=")
192
+ self._key_map[key.id] = key
193
+ self._push_keys()
194
+ return key
195
+
196
+ @property
197
+ def _key_map(self) -> dict:
198
+ """Lazily-created ``{id: KeyOverlay}`` — panels predate this feature."""
199
+ if getattr(self, "_keys_dict", None) is None:
200
+ self._keys_dict = {}
201
+ return self._keys_dict
202
+
203
+ def _push_keys(self) -> None:
204
+ """Re-serialise the key list + image table, then push.
205
+
206
+ The pictures ride the geometry channel under ``key_images`` so that
207
+ restyling a key — or a hover toggle — never re-transmits them.
208
+ """
209
+ keys = list(self._key_map.values())
210
+ self._state["keys"] = [k.to_dict() for k in keys]
211
+ self._state["key_images"] = {k.id: k.image_url for k in keys}
212
+ self._push()
213
+
214
+ def get_key(self, name):
215
+ """Return the key with this *name* (or id).
216
+
217
+ Raises
218
+ ------
219
+ KeyError
220
+ If no key matches.
221
+ """
222
+ for k in self._key_map.values():
223
+ if k.name == name or k.id == name:
224
+ return k
225
+ raise KeyError(f"no key named {name!r}")
226
+
227
+ def remove_key(self, key) -> None:
228
+ """Remove a key, by object, name, or id."""
229
+ from anyplotlib.keys import KeyOverlay
230
+ kid = key.id if isinstance(key, KeyOverlay) else self.get_key(key).id
231
+ if kid not in self._key_map:
232
+ raise KeyError(kid)
233
+ del self._key_map[kid]
234
+ self._push_keys()
235
+
236
+ def list_keys(self) -> list:
237
+ """Every key on this panel, in creation order."""
238
+ return list(self._key_map.values())
239
+
240
+ def clear_keys(self) -> None:
241
+ """Remove every key from this panel."""
242
+ self._key_map.clear()
243
+ self._push_keys()
244
+
79
245
  @contextmanager
80
246
  def _python_view_push(self):
81
247
  """Context manager for view setters that must signal _view_from_python.
@@ -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.