anyplotlib 0.4.2__tar.gz → 0.5.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 (266) hide show
  1. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/CHANGELOG.rst +65 -0
  2. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/PKG-INFO +1 -1
  3. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/FIGURE_ESM.md +34 -33
  4. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/_base_plot.py +206 -0
  5. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/_repr_utils.py +11 -1
  6. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/_utils.py +11 -3
  7. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/axes/_axes.py +3 -1
  8. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/figure/_figure.py +25 -1
  9. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/figure_esm.js +314 -84
  10. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/markers.py +24 -0
  11. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/plot1d/_plot1d.py +30 -9
  12. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/plot2d/_plot2d.py +191 -14
  13. anyplotlib-0.5.0/anyplotlib/tests/baselines/imshow_labels.png +0 -0
  14. anyplotlib-0.5.0/anyplotlib/tests/test_embed/test_export_widget_sync.py +92 -0
  15. anyplotlib-0.5.0/anyplotlib/tests/test_interactive/test_pointer_down_1d.py +144 -0
  16. anyplotlib-0.5.0/anyplotlib/tests/test_interactive/test_range_orientation_snap.py +286 -0
  17. anyplotlib-0.5.0/anyplotlib/tests/test_interactive/test_widget_set_notify.py +110 -0
  18. anyplotlib-0.5.0/anyplotlib/tests/test_interactive/test_widgets_line_2d.py +330 -0
  19. anyplotlib-0.5.0/anyplotlib/tests/test_labels/test_ylabel_clearance.py +129 -0
  20. anyplotlib-0.5.0/anyplotlib/tests/test_layouts/test_coord_conversion.py +181 -0
  21. anyplotlib-0.5.0/anyplotlib/tests/test_markers/test_per_marker_colors.py +188 -0
  22. anyplotlib-0.5.0/anyplotlib/tests/test_markers/test_size_units.py +110 -0
  23. anyplotlib-0.5.0/anyplotlib/tests/test_plot1d/test_line_none_rendering.py +118 -0
  24. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot1d/test_plot1d.py +21 -0
  25. anyplotlib-0.5.0/anyplotlib/tests/test_plot2d/test_colorbar_gap_scalebar.py +200 -0
  26. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/widgets/__init__.py +2 -1
  27. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/widgets/_base.py +44 -2
  28. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/widgets/_widgets1d.py +64 -7
  29. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/widgets/_widgets2d.py +40 -0
  30. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/_root/index.html +3 -3
  31. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/_root/switcher.json +5 -0
  32. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/conf.py +1 -1
  33. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/pyproject.toml +1 -1
  34. anyplotlib-0.4.2/anyplotlib/tests/baselines/imshow_labels.png +0 -0
  35. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/.github/workflows/ci.yml +0 -0
  36. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/.github/workflows/docs.yml +0 -0
  37. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/.github/workflows/prepare_release.yml +0 -0
  38. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/.github/workflows/release.yml +0 -0
  39. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/.github/workflows/tests.yml +0 -0
  40. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/.gitignore +0 -0
  41. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/AGENTS.md +0 -0
  42. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Benchmarks/README.rst +0 -0
  43. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Benchmarks/plot_benchmark_comparison.py +0 -0
  44. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/README.rst +0 -0
  45. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_3d_spectral_viewer.py +0 -0
  46. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_eels_explorer.py +0 -0
  47. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_figure_annotations.py +0 -0
  48. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_interactive_fft.py +0 -0
  49. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_interactive_fitting.py +0 -0
  50. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_ipf_density_map.py +0 -0
  51. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_ipf_explorer.py +0 -0
  52. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_key_bindings.py +0 -0
  53. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_particle_picker.py +0 -0
  54. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_point_widget.py +0 -0
  55. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_segment_by_contrast.py +0 -0
  56. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_segment_by_contrast_advanced.py +0 -0
  57. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_spectra_roi_inspector.py +0 -0
  58. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_threshold_explorer.py +0 -0
  59. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Interactive/plot_voxel_grain_explorer.py +0 -0
  60. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Markers/README.rst +0 -0
  61. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Markers/plot_arrows.py +0 -0
  62. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Markers/plot_circles.py +0 -0
  63. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Markers/plot_ellipses.py +0 -0
  64. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Markers/plot_horizontal_lines.py +0 -0
  65. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Markers/plot_line_segments.py +0 -0
  66. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Markers/plot_points.py +0 -0
  67. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Markers/plot_polygons.py +0 -0
  68. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Markers/plot_rectangles.py +0 -0
  69. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Markers/plot_squares.py +0 -0
  70. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Markers/plot_texts.py +0 -0
  71. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Markers/plot_transform_markers.py +0 -0
  72. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Markers/plot_vertical_lines.py +0 -0
  73. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/PlotTypes/README.rst +0 -0
  74. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/PlotTypes/plot_3d.py +0 -0
  75. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/PlotTypes/plot_bar.py +0 -0
  76. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/PlotTypes/plot_gpu_voxels.py +0 -0
  77. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/PlotTypes/plot_gridspec_custom.py +0 -0
  78. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/PlotTypes/plot_image2d.py +0 -0
  79. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/PlotTypes/plot_inset.py +0 -0
  80. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/PlotTypes/plot_label_formatting.py +0 -0
  81. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/PlotTypes/plot_line_styles.py +0 -0
  82. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/PlotTypes/plot_pcolormesh.py +0 -0
  83. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/PlotTypes/plot_spectra1d.py +0 -0
  84. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/PlotTypes/plot_step_and_log.py +0 -0
  85. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/PlotTypes/plot_tile_backend.py +0 -0
  86. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/PlotTypes/plot_twinx.py +0 -0
  87. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/README.rst +0 -0
  88. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Widgets/README.rst +0 -0
  89. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Widgets/plot_widget1d_hline.py +0 -0
  90. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Widgets/plot_widget1d_point.py +0 -0
  91. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Widgets/plot_widget1d_range.py +0 -0
  92. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Widgets/plot_widget1d_vline.py +0 -0
  93. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Widgets/plot_widget2d_annular.py +0 -0
  94. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Widgets/plot_widget2d_arrow.py +0 -0
  95. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Widgets/plot_widget2d_circle.py +0 -0
  96. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Widgets/plot_widget2d_crosshair.py +0 -0
  97. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Widgets/plot_widget2d_label.py +0 -0
  98. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Widgets/plot_widget2d_polygon.py +0 -0
  99. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Widgets/plot_widget2d_rectangle.py +0 -0
  100. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Examples/Widgets/plot_widget3d_plane.py +0 -0
  101. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/LICENSE +0 -0
  102. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/Makefile +0 -0
  103. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/README.md +0 -0
  104. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/__init__.py +0 -0
  105. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/_binary_frame.py +0 -0
  106. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/_electron.py +0 -0
  107. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/axes/__init__.py +0 -0
  108. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/axes/_inset_axes.py +0 -0
  109. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/callbacks.py +0 -0
  110. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/conftest.py +0 -0
  111. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/embed.py +0 -0
  112. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/figure/__init__.py +0 -0
  113. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/figure/_gridspec.py +0 -0
  114. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/figure/_subplots.py +0 -0
  115. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/plot1d/__init__.py +0 -0
  116. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/plot1d/_plotbar.py +0 -0
  117. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/plot2d/__init__.py +0 -0
  118. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/plot2d/_layer.py +0 -0
  119. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/plot2d/_plotmesh.py +0 -0
  120. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/plot2d/_tile_backend.py +0 -0
  121. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/plot3d/__init__.py +0 -0
  122. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/plot3d/_plot3d.py +0 -0
  123. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/plotxy/__init__.py +0 -0
  124. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/plotxy/_plotxy.py +0 -0
  125. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/sphinx_anywidget/__init__.py +0 -0
  126. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/sphinx_anywidget/_directive.py +0 -0
  127. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/sphinx_anywidget/_repr_utils.py +0 -0
  128. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/sphinx_anywidget/_scraper.py +0 -0
  129. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/sphinx_anywidget/_wheel_builder.py +0 -0
  130. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/sphinx_anywidget/static/anywidget_bridge.js +0 -0
  131. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/sphinx_anywidget/static/anywidget_overlay.css +0 -0
  132. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/sphinx_anywidget/tests/__init__.py +0 -0
  133. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/sphinx_anywidget/tests/conftest.py +0 -0
  134. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/sphinx_anywidget/tests/test_directive.py +0 -0
  135. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/sphinx_anywidget/tests/test_init.py +0 -0
  136. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/sphinx_anywidget/tests/test_repr_utils.py +0 -0
  137. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/sphinx_anywidget/tests/test_scraper.py +0 -0
  138. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/sphinx_anywidget/tests/test_wheel_builder.py +0 -0
  139. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/__init__.py +0 -0
  140. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/_gpu_clim_check.cjs +0 -0
  141. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/_png_utils.py +0 -0
  142. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/bar_basic.png +0 -0
  143. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/gridspec_3col_equal_spectra.png +0 -0
  144. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/gridspec_asymmetric_width_ratios.png +0 -0
  145. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/gridspec_height_ratio_image_histogram.png +0 -0
  146. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/gridspec_image_two_spectra.png +0 -0
  147. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/gridspec_side_by_side_1d.png +0 -0
  148. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/gridspec_spanning_top_two_bottom.png +0 -0
  149. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/imshow_axis_off.png +0 -0
  150. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/imshow_checkerboard.png +0 -0
  151. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/imshow_gradient.png +0 -0
  152. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/imshow_viridis.png +0 -0
  153. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/inset_1d.png +0 -0
  154. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/inset_maximized.png +0 -0
  155. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/inset_minimized.png +0 -0
  156. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/inset_normal_2d.png +0 -0
  157. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/inset_stacked.png +0 -0
  158. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/inset_stacked_one_minimized.png +0 -0
  159. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/pcolormesh_uniform.png +0 -0
  160. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/plot1d_all_linestyles.png +0 -0
  161. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/plot1d_alpha.png +0 -0
  162. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/plot1d_axis_off.png +0 -0
  163. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/plot1d_dashed.png +0 -0
  164. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/plot1d_marker_symbols.png +0 -0
  165. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/plot1d_markers.png +0 -0
  166. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/plot1d_multi.png +0 -0
  167. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/plot1d_sine.png +0 -0
  168. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/plot1d_title.png +0 -0
  169. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/plot1d_twinx.png +0 -0
  170. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/plot3d_surface.png +0 -0
  171. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/baselines/subplots_2x1.png +0 -0
  172. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/conftest.py +0 -0
  173. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_benchmarks/__init__.py +0 -0
  174. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_benchmarks/baselines.json +0 -0
  175. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_benchmarks/benchmarks/baselines.json +0 -0
  176. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_benchmarks/test_benchmarks.py +0 -0
  177. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_benchmarks/test_benchmarks_py.py +0 -0
  178. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_documentation/__init__.py +0 -0
  179. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_documentation/test_bridge.py +0 -0
  180. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_documentation/test_push_hook.py +0 -0
  181. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_documentation/test_scraper.py +0 -0
  182. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_electron/__init__.py +0 -0
  183. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_electron/test_binary_frame.py +0 -0
  184. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_embed/__init__.py +0 -0
  185. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_embed/test_embed_api.py +0 -0
  186. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_embed/test_embed_mount.py +0 -0
  187. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_embed/test_export_png.py +0 -0
  188. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_examples/__init__.py +0 -0
  189. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_examples/test_interactive_examples.py +0 -0
  190. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/__init__.py +0 -0
  191. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/_event_test_utils.py +0 -0
  192. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/test_blit_audit.py +0 -0
  193. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/test_callbacks_playwright.py +0 -0
  194. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/test_callbacks_unit.py +0 -0
  195. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/test_edit_chrome.py +0 -0
  196. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/test_edit_chrome_playwright.py +0 -0
  197. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/test_event_pause_hold.py +0 -0
  198. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/test_event_plots.py +0 -0
  199. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/test_event_settled.py +0 -0
  200. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/test_events_regression.py +0 -0
  201. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/test_tile_parity_playwright.py +0 -0
  202. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/test_title.py +0 -0
  203. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/test_touch.py +0 -0
  204. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/test_widget_max_extent.py +0 -0
  205. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_interactive/test_widgets.py +0 -0
  206. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_labels/__init__.py +0 -0
  207. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_labels/test_label_api.py +0 -0
  208. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_labels/test_label_rendering.py +0 -0
  209. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_labels/test_no_clipping.py +0 -0
  210. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_layouts/__init__.py +0 -0
  211. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_layouts/test_batch.py +0 -0
  212. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_layouts/test_geom_channel.py +0 -0
  213. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_layouts/test_gridspec.py +0 -0
  214. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_layouts/test_inset.py +0 -0
  215. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_layouts/test_inset_callout.py +0 -0
  216. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_layouts/test_interaction.py +0 -0
  217. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_layouts/test_visual.py +0 -0
  218. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_markers/__init__.py +0 -0
  219. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_markers/test_marker_transforms.py +0 -0
  220. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_markers/test_markers.py +0 -0
  221. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot1d/__init__.py +0 -0
  222. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot1d/test_plotbar.py +0 -0
  223. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/__init__.py +0 -0
  224. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_detail_tile.py +0 -0
  225. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_first_paint_race.py +0 -0
  226. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_gpu_image.py +0 -0
  227. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_gpu_parity_playwright.py +0 -0
  228. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_imshow.py +0 -0
  229. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_imshow_rgb.py +0 -0
  230. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_layers.py +0 -0
  231. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_layers_playwright.py +0 -0
  232. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_pcolormesh.py +0 -0
  233. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_plot2d_api.py +0 -0
  234. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_rapid_frame_update.py +0 -0
  235. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_tile_backend.py +0 -0
  236. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_tile_diagnostics.py +0 -0
  237. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_tiled_imshow.py +0 -0
  238. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot2d/test_zoom_view_write.py +0 -0
  239. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot3d/__init__.py +0 -0
  240. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot3d/test_colors_highlight.py +0 -0
  241. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot3d/test_gpu_fallback.py +0 -0
  242. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot3d/test_gpu_hidden_reveal.py +0 -0
  243. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot3d/test_plot3d.py +0 -0
  244. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plot3d/test_voxels_planes.py +0 -0
  245. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/tests/test_plotxy/test_plotxy.py +0 -0
  246. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/anyplotlib/widgets/_widgets3d.py +0 -0
  247. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/_sg_html_scraper.py +0 -0
  248. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/_static/anyplotlib.svg +0 -0
  249. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/_static/custom.css +0 -0
  250. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/_static/pyodide_bridge.js +0 -0
  251. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/_templates/autosummary/class.rst +0 -0
  252. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/api/callbacks.rst +0 -0
  253. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/api/figure.rst +0 -0
  254. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/api/figure_plots.rst +0 -0
  255. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/api/index.rst +0 -0
  256. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/api/markers.rst +0 -0
  257. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/api/widgets.rst +0 -0
  258. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/benchmarking.rst +0 -0
  259. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/dev/index.rst +0 -0
  260. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/embedding.rst +0 -0
  261. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/events.rst +0 -0
  262. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/getting_started.rst +0 -0
  263. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/docs/index.rst +0 -0
  264. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/make.bat +0 -0
  265. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/upcoming_changes/README.rst +0 -0
  266. {anyplotlib-0.4.2 → anyplotlib-0.5.0}/uv.lock +0 -0
@@ -10,6 +10,71 @@ Fragment files in ``upcoming_changes/`` are assembled into this file by
10
10
 
11
11
  .. towncrier release notes start
12
12
 
13
+ 0.5.0 (2026-07-26)
14
+ ==================
15
+
16
+ New Features
17
+ ------------
18
+
19
+ - :meth:`Widget.set` takes ``_notify=False`` to move a widget without firing
20
+ ``pointer_move`` callbacks, so a handler that writes back to its own widget no
21
+ longer feeds into itself. Widgets also gained a
22
+ :meth:`~anyplotlib.widgets.Widget.remove` method.
23
+ - :meth:`~anyplotlib.Plot1D.add_range_widget` takes ``orientation="vertical"``
24
+ for a band that selects a range of values, and ``snap_values`` to restrict a
25
+ drag to a set of allowed positions (matplotlib's
26
+ ``SpanSelector.snap_values``). ``snap_values`` is also available on the
27
+ vline, hline and point widgets.
28
+ - Added :meth:`~anyplotlib.Plot2D.set_scalebar_style` to recolour the automatic
29
+ scale bar, which was hardcoded white on a translucent dark pill and unreadable
30
+ over a light image. ``bgcolor="none"`` drops the pill entirely.
31
+ - Added ``linestyle="none"`` (also spelled ``"None"``) for a series drawn as
32
+ markers with no connecting line — matplotlib's scatter idiom,
33
+ ``ax.plot(y, linestyle="none", marker="o")``. An explicit ``linewidth=0``
34
+ now means the same thing; it previously fell back to the 1.5 default in the
35
+ renderer.
36
+ - Added three 2-D overlay widget kinds: ``line``
37
+ (:meth:`~anyplotlib.Plot2D.add_line_widget`), a bare two-endpoint segment for
38
+ line profiles and two-point measurements, and ``vline`` / ``hline``, full-height
39
+ and full-width rules grabbable anywhere along their length.
40
+ - Clicking a 1-D panel now emits a ``pointer_down`` event carrying the clicked
41
+ position as ``xdata``/``ydata``, matching 2-D panels; it previously fired only
42
+ when the click landed on a line. Clicks on a line still report ``line_id``,
43
+ so existing line-click handlers are unaffected.
44
+ - Panels expose their geometry through :meth:`~anyplotlib.Plot1D.plot_box`,
45
+ :meth:`~anyplotlib.Plot1D.data_to_display` and
46
+ :meth:`~anyplotlib.Plot1D.display_to_data`, so callers working in display space
47
+ no longer have to re-derive the renderer's layout constants and letterbox maths
48
+ themselves.
49
+ - Sized marker types take ``size_units="px"`` so their radii and widths stay
50
+ fixed in screen pixels through a zoom instead of scaling with the data — what
51
+ a marker standing in for a *point* wants, and what matplotlib does by sizing
52
+ scatter markers in display points.
53
+ - ``edgecolors`` and ``facecolors`` accept a sequence of colours parallel to the
54
+ markers — matplotlib's ``edgecolors=[...]`` / scatter ``c=[...]`` — for every
55
+ marker type on both 1-D and 2-D panels, where previously only ``points`` and
56
+ ``polygons`` on 1-D panels honoured it. A short sequence cycles.
57
+
58
+
59
+ Bug Fixes
60
+ ---------
61
+
62
+ - A ``crosshair`` widget can now be grabbed anywhere along either of its rules
63
+ rather than only at the one-pixel centre hotspot; grabbing a rule constrains
64
+ the drag to that rule's own axis.
65
+ - The 1-D y-axis label is no longer drawn through the tick numbers; its position
66
+ was a fixed fraction of the left gutter and is now measured against the widest
67
+ tick string.
68
+ - The colorbar strip is no longer drawn flush against the image: there is now a
69
+ 6 px gap, taken out of the image width so the strip cannot be pushed off the
70
+ panel, and settable with :meth:`~anyplotlib.Plot2D.set_colorbar_pad`. This
71
+ shifts every colorbar plot by 4 px.
72
+ - ``save_html`` / ``to_html`` / ``figure_state`` now capture overlay widgets at
73
+ their current positions; widget moves reach JS as targeted events that never
74
+ rewrite the panel traits, so a snapshot used to show every widget where it was
75
+ created.
76
+
77
+
13
78
  0.4.1 (2026-07-26)
14
79
  ==================
15
80
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anyplotlib
3
- Version: 0.4.2
3
+ Version: 0.5.0
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 **~6,000 lines** and one big closure. Everything lives inside
3
+ `figure_esm.js` is **~9,000 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.
@@ -48,29 +48,30 @@ Rule 5 – Text never clips. Optional gutters earn real layout space:
48
48
  | Section / function | Line |
49
49
  |--------------------|------|
50
50
  | Shared plot-area padding (`PAD_*`) | 9 |
51
- | Theme (dark/light detection) | 15 |
52
- | Shared math helpers | 53 |
53
- | b64 array decode helpers | 95 |
54
- | **Rich-text (mini-TeX) engine**: `_texRuns` / `_texLayout` / `_drawTex` | 147 / 214 / 236 |
55
- | **2D gutter geometry**: `_cbWidth` / `_padT` / `_titlePx` | 287 / 299 / 309 |
56
- | **Layout engine** `applyLayout` | 590 |
57
- | `_buildCanvasStack` | 656 |
58
- | `_createPanelDOM` | 763 |
59
- | `_createInsetDOM` / `_applyAllInsetStates` | 846 / 968 |
60
- | `_resizePanelDOM` | 1027 |
61
- | **2D drawing**: `_imgFitRect` | 1176 |
62
- | `draw2d` | 1258 |
63
- | `drawScaleBar2d` / `drawColorbar2d` | 1360 / 1436 |
64
- | `_drawAxes2d` (ticks, labels, title) | 1491 |
65
- | `drawOverlay2d` / `drawMarkers2d` | 1629 / 1685 |
66
- | **Image layers**: `_layerBytes` / `_layerBitmap` / `_drawLayers2d` | 1553 / 1577 / 1629 |
67
- | Binary-bytes splice: `_spliceBinaryBytes` / `_registerBinaryPixelListeners` | 675 / 706 |
68
- | **3D drawing**: `draw3d` | 1833 |
69
- | Event emission `_emitEvent` | 2031 |
70
- | 3D event handlers `_attachEvents3d` | 2059 |
71
- | **1D drawing**: `draw1d` | 2177 |
72
- | `drawOverlay1d` / `drawMarkers1d` | 2516 / 2586 |
73
- | Marker hit-test `_markerHitTest2d` | 2787 |
51
+ | Theme (dark/light detection) | 26 |
52
+ | Shared math helpers | 64 |
53
+ | b64 array decode helpers | 109 |
54
+ | **Rich-text (mini-TeX) engine**: `_texRuns` / `_texLayout` / `_drawTex` | 161 / 228 / 250 |
55
+ | **2D gutter geometry**: `_cbWidth` / `_cbGap` / `_padT` / `_titlePx` | 301 / 310 / 323 / 333 |
56
+ | **Layout engine** `applyLayout` | 774 |
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 / 3287 |
66
+ | **Image layers**: `_layerBytes` / `_layerBitmap` / `_drawLayers2d` | 2500 / 2524 / 2585 |
67
+ | Binary-bytes splice: `_spliceBinaryBytes` / `_registerBinaryPixelListeners` | 730 / 761 |
68
+ | **3D drawing**: `draw3d` | 4623 |
69
+ | Event emission `_emitEvent` | 5270 |
70
+ | 3D event handlers `_attachEvents3d` | 5322 |
71
+ | **1D drawing**: `draw1d` | 5536 |
72
+ | `_drawLine` (1D series + markers) | 5689 |
73
+ | `drawOverlay1d` / `drawMarkers1d` | 5982 / 6066 |
74
+ | Marker hit-test `_markerHitTest2d` | 6334 |
74
75
 
75
76
  > **`raster` marker (1D/PlotXY)** — `drawMarkers1d` has a `type==='raster'`
76
77
  > branch that blits a single RGBA image across data-coord `extent` (the fast
@@ -79,15 +80,15 @@ Rule 5 – Text never clips. Optional gutters earn real layout space:
79
80
  > redraws never re-transmit them; the decoded `OffscreenCanvas` is cached on
80
81
  > the marker set (`ms._rasterBmp`/`_rasterKey`). The shared `clip_path` block
81
82
  > clips it to a curved sector.
82
- | Panel event dispatch `_attachPanelEvents` | 2905 |
83
- | 2D events `_attachEvents2d` | 2928 |
84
- | 1D events `_attachEvents1d` | 3201 |
85
- | 2D widget drag `_ovHitTest2d` / `_doDrag2d` | 3409 / 3491 |
86
- | 1D widget drag `_canvasXToFrac1d` … | 3565 |
87
- | Shared-axis propagation `_getShareGroups` | 3650 |
88
- | Figure resize `_applyFigResizeDOM` | 3714 |
89
- | **Bar chart**: `_barGeom` / `drawBar` / `_attachEventsBar` | 3902 / 3965 / 4341 |
90
- | Generic redraw `_redrawPanel` | 4531 |
83
+ | Panel event dispatch `_attachPanelEvents` | 6591 |
84
+ | 2D events `_attachEvents2d` | 6615 |
85
+ | 1D events `_attachEvents1d` | 6962 |
86
+ | 2D widget drag `_ovHitTest2d` / `_doDrag2d` | 7229 / 7363 |
87
+ | 1D widget drag `_canvasXToFrac1d` … / snapping `_snapVal` | 7473 / 7546 |
88
+ | Shared-axis propagation `_getShareGroups` | 7617 |
89
+ | Figure resize `_applyFigResizeDOM` | 7681 |
90
+ | **Bar chart**: `_barGeom` / `drawBar` / `_attachEventsBar` | 7872 / 7935 / 8311 |
91
+ | Generic redraw `_redrawPanel` | 8501 |
91
92
 
92
93
  ---
93
94
 
@@ -95,8 +95,13 @@ class _BasePlot(_EventMixin):
95
95
 
96
96
  Replaces the repeated _tp / _targeted_push closures in every
97
97
  add_*_widget method.
98
+
99
+ Also back-links the widget to this plot so :meth:`Widget.remove` can
100
+ find its owner. Every ``add_*_widget`` routes through here, so this is
101
+ the one place that has to know.
98
102
  """
99
103
  plot_ref, wid_id = self, widget._id
104
+ widget._plot = self
100
105
  def _push():
101
106
  if plot_ref._fig is not None:
102
107
  fields = {k: v for k, v in widget._data.items()
@@ -124,6 +129,207 @@ class _PanelMixin:
124
129
  self._state["overlay_widgets"] = [w.to_dict() for w in self._widgets.values()]
125
130
  self._fig._push(self._id)
126
131
 
132
+ # ── geometry / coordinate conversion ──────────────────────────────────
133
+ #
134
+ # These mirror the layout constants and letterbox math in figure_esm.js
135
+ # (the PAD_* block near the top, and _imgFitRect). They are here so that
136
+ # callers doing display-space work — pixel-sized handles, hit-test
137
+ # tolerances, screenshot-driven tests — do not each have to re-derive the
138
+ # renderer's layout in their own code and then drift from it.
139
+
140
+ #: Plot-area padding in CSS px: (left, right, top, bottom).
141
+ #: Matches ``PAD_L``/``PAD_R``/``PAD_T``/``PAD_B`` in ``figure_esm.js``.
142
+ PLOT_PADDING = (58, 12, 12, 42)
143
+
144
+ def _panel_size(self) -> tuple[float, float]:
145
+ """(panel_width, panel_height) in CSS px, from the figure's layout."""
146
+ import json
147
+
148
+ if self._fig is None:
149
+ raise RuntimeError("panel is not attached to a figure")
150
+ try:
151
+ layout = json.loads(self._fig.layout_json)
152
+ spec = next(s for s in layout["panel_specs"] if s["id"] == self._id)
153
+ except (AttributeError, KeyError, StopIteration, ValueError) as exc:
154
+ raise RuntimeError(f"no layout entry for panel {self._id!r}") from exc
155
+ return float(spec["panel_width"]), float(spec["panel_height"])
156
+
157
+ def _pad_top(self) -> float:
158
+ """Title-strip height. Mirrors ``_padT`` in figure_esm.js."""
159
+ pad_t = float(self.PLOT_PADDING[2])
160
+ title = self._state.get("title")
161
+ if not title:
162
+ return pad_t
163
+ size = float(self._state.get("title_size") or 11)
164
+ has_tex = "$" in str(title)
165
+ if size <= 11 and not has_tex:
166
+ return pad_t
167
+ import math
168
+
169
+ return max(pad_t, math.ceil(size * 1.3) + (4 if has_tex else 2))
170
+
171
+ def plot_box(self) -> dict:
172
+ """Return the panel's plot area in CSS pixels.
173
+
174
+ Returns
175
+ -------
176
+ dict
177
+ ``{"x", "y", "width", "height"}`` — the drawable rectangle
178
+ relative to the panel's top-left corner. On a 2-D image panel it
179
+ is the *letterboxed image* rather than the whole padded area,
180
+ because that is what image coordinates actually map onto.
181
+
182
+ Raises
183
+ ------
184
+ RuntimeError
185
+ If the panel is not attached to a figure, so it has no layout.
186
+
187
+ Notes
188
+ -----
189
+ Computed from the figure's own layout, so it is exact for the sizes
190
+ the renderer was given. A browser resized without a re-layout will
191
+ disagree. Zoom and pan are *not* folded in — this is the box, not
192
+ the visible data window; :meth:`data_to_display` accounts for them.
193
+ """
194
+ left, right, top, bottom = self.PLOT_PADDING
195
+ pw, ph = self._panel_size()
196
+ pad_t = self._pad_top()
197
+
198
+ # A 2-D panel drops the left/right/bottom gutters when it has no
199
+ # physical axes to label — the image then fills the panel width.
200
+ # 1-D panels always draw axes. Mirrors `hasPhysAxis` in the JS.
201
+ iw = self._state.get("image_width")
202
+ ih = self._state.get("image_height")
203
+ is_image = bool(iw and ih)
204
+ has_axes = (not is_image) or bool(
205
+ self._state.get("has_axes") or self._state.get("is_mesh")
206
+ )
207
+
208
+ x = float(left) if has_axes else 0.0
209
+ y = float(pad_t)
210
+ width = max((pw - left - right) if has_axes else pw, 1.0)
211
+ height = max(ph - pad_t - (bottom if has_axes else 0.0), 1.0)
212
+
213
+ # The colorbar strip and its gap come out of the image width.
214
+ if self._state.get("show_colorbar") and not self._state.get("is_rgb"):
215
+ label = self._state.get("colorbar_label")
216
+ label_w = round((self._state.get("colorbar_label_size") or 10) + 8) \
217
+ if label else 0
218
+ pad = self._state.get("colorbar_pad")
219
+ gap = 6.0 if pad is None else max(0.0, float(pad))
220
+ width = max(width - (16 + label_w) - gap, 1.0)
221
+
222
+ if is_image:
223
+ # Images are drawn "contain" — see _imgFitRect.
224
+ scale = min(width / float(iw), height / float(ih))
225
+ fit_w, fit_h = float(iw) * scale, float(ih) * scale
226
+ x += (width - fit_w) / 2.0
227
+ y += (height - fit_h) / 2.0
228
+ width, height = fit_w, fit_h
229
+
230
+ return {"x": x, "y": y, "width": width, "height": height}
231
+
232
+ def _visible_image_window(self) -> tuple[float, float, float, float]:
233
+ """(src_x, src_y, vis_w, vis_h) of the visible image region, in image px.
234
+
235
+ Mirrors the zoom/pan window in ``_imgToCanvas2d``.
236
+ """
237
+ iw = float(self._state.get("image_width") or 1)
238
+ ih = float(self._state.get("image_height") or 1)
239
+ zoom = float(self._state.get("zoom") or 1.0)
240
+ cx = float(self._state.get("center_x", 0.5))
241
+ cy = float(self._state.get("center_y", 0.5))
242
+ if zoom < 1.0:
243
+ # Zoomed out: the whole image, drawn smaller and centred. The box
244
+ # shrinks rather than the source window growing.
245
+ return 0.0, 0.0, iw, ih
246
+ vis_w, vis_h = iw / zoom, ih / zoom
247
+ src_x = max(0.0, min(iw - vis_w, cx * iw - vis_w / 2.0))
248
+ src_y = max(0.0, min(ih - vis_h, cy * ih - vis_h / 2.0))
249
+ return src_x, src_y, vis_w, vis_h
250
+
251
+ def data_to_display(self, points):
252
+ """Convert data coordinates to CSS pixels within the panel.
253
+
254
+ Parameters
255
+ ----------
256
+ points : array-like
257
+ A single ``(x, y)`` pair or an ``(N, 2)`` sequence of them.
258
+
259
+ On a 1-D panel these are axis values. On a 2-D panel they are
260
+ **image-pixel coordinates** — the same space marker ``offsets``
261
+ and widget positions use — where integer *i* means the *centre*
262
+ of pixel *i*, not its leading edge.
263
+
264
+ Returns
265
+ -------
266
+ numpy.ndarray
267
+ Same shape as *points*, in panel pixels with the origin at the
268
+ panel's top-left corner and y increasing downwards. Zoom and pan
269
+ are accounted for.
270
+ """
271
+ import numpy as np
272
+
273
+ pts = np.atleast_2d(np.asarray(points, dtype=float))
274
+ if pts.shape[-1] != 2:
275
+ raise ValueError(f"points must have shape (N, 2); got {pts.shape}")
276
+ box = self.plot_box()
277
+
278
+ if self._state.get("image_width") and self._state.get("image_height"):
279
+ zoom = float(self._state.get("zoom") or 1.0)
280
+ src_x, src_y, vis_w, vis_h = self._visible_image_window()
281
+ w = box["width"] * (zoom if zoom < 1.0 else 1.0)
282
+ h = box["height"] * (zoom if zoom < 1.0 else 1.0)
283
+ x0 = box["x"] + (box["width"] - w) / 2.0
284
+ y0 = box["y"] + (box["height"] - h) / 2.0
285
+ px = x0 + (pts[:, 0] + 0.5 - src_x) / vis_w * w
286
+ py = y0 + (pts[:, 1] + 0.5 - src_y) / vis_h * h
287
+ else:
288
+ xlo, xhi = self.get_xlim()
289
+ ylo, yhi = self.get_ylim()
290
+ px = box["x"] + (pts[:, 0] - xlo) / ((xhi - xlo) or 1.0) * box["width"]
291
+ # Screen y grows downwards, data y upwards.
292
+ py = box["y"] + (
293
+ 1.0 - (pts[:, 1] - ylo) / ((yhi - ylo) or 1.0)
294
+ ) * box["height"]
295
+
296
+ out = np.column_stack([px, py])
297
+ return out if np.ndim(points) == 2 else out[0]
298
+
299
+ def display_to_data(self, points):
300
+ """Convert CSS pixels within the panel to data coordinates.
301
+
302
+ The inverse of :meth:`data_to_display`; same argument shapes and the
303
+ same per-panel-kind coordinate meaning.
304
+ """
305
+ import numpy as np
306
+
307
+ pts = np.atleast_2d(np.asarray(points, dtype=float))
308
+ if pts.shape[-1] != 2:
309
+ raise ValueError(f"points must have shape (N, 2); got {pts.shape}")
310
+ box = self.plot_box()
311
+
312
+ if self._state.get("image_width") and self._state.get("image_height"):
313
+ zoom = float(self._state.get("zoom") or 1.0)
314
+ src_x, src_y, vis_w, vis_h = self._visible_image_window()
315
+ w = box["width"] * (zoom if zoom < 1.0 else 1.0)
316
+ h = box["height"] * (zoom if zoom < 1.0 else 1.0)
317
+ x0 = box["x"] + (box["width"] - w) / 2.0
318
+ y0 = box["y"] + (box["height"] - h) / 2.0
319
+ dx = (pts[:, 0] - x0) / (w or 1.0) * vis_w + src_x - 0.5
320
+ dy = (pts[:, 1] - y0) / (h or 1.0) * vis_h + src_y - 0.5
321
+ else:
322
+ xlo, xhi = self.get_xlim()
323
+ ylo, yhi = self.get_ylim()
324
+ dx = xlo + (pts[:, 0] - box["x"]) / (box["width"] or 1.0) \
325
+ * ((xhi - xlo) or 1.0)
326
+ dy = ylo + (
327
+ 1.0 - (pts[:, 1] - box["y"]) / (box["height"] or 1.0)
328
+ ) * ((yhi - ylo) or 1.0)
329
+
330
+ out = np.column_stack([dx, dy])
331
+ return out if np.ndim(points) == 2 else out[0]
332
+
127
333
  def set_tick_label_size(self, size: float) -> None:
128
334
  """Set the font size of the tick (axis number) labels in CSS pixels.
129
335
 
@@ -35,7 +35,17 @@ MAX_NOTEBOOK_WIDTH = 860
35
35
  # ---------------------------------------------------------------------------
36
36
 
37
37
  def _widget_state(widget) -> dict:
38
- """Return a {name: value} dict of every synced traitlet."""
38
+ """Return a {name: value} dict of every synced traitlet.
39
+
40
+ A widget that keeps state outside its traits between renders may define
41
+ ``_sync_for_export()`` to reconcile it first. ``Figure`` uses this to fold
42
+ widget geometry — pushed to JS as targeted events that never touch the
43
+ panel traits — back in, so a snapshot captures widgets where they are
44
+ rather than where they were created.
45
+ """
46
+ sync = getattr(widget, "_sync_for_export", None)
47
+ if callable(sync):
48
+ sync()
39
49
  state: dict = {}
40
50
  for name, trait in widget.traits(sync=True).items():
41
51
  if name.startswith("_"):
@@ -21,6 +21,12 @@ _LINESTYLE_ALIASES: dict[str, str] = {
21
21
  "dashdot": "dashdot",
22
22
  "step-mid": "step-mid",
23
23
  "steps-mid": "step-mid",
24
+ # "no connecting line" — draw the per-point markers only, so a matplotlib
25
+ # ``linestyle="None", marker="o"`` scatter ports over unchanged.
26
+ # matplotlib also spells this ``""`` and ``" "``; those stay errors here
27
+ # because an empty style string is far more often a typo than an intent.
28
+ "none": "none",
29
+ "None": "none",
24
30
  }
25
31
 
26
32
 
@@ -42,7 +48,9 @@ def _norm_linestyle(ls: str) -> str:
42
48
  Accepted values
43
49
  ---------------
44
50
  ``"solid"`` / ``"-"``, ``"dashed"`` / ``"--"``,
45
- ``"dotted"`` / ``":"``, ``"dashdot"`` / ``"-."``.
51
+ ``"dotted"`` / ``":"``, ``"dashdot"`` / ``"-."``,
52
+ ``"step-mid"`` / ``"steps-mid"``, and ``"none"`` / ``"None"`` / ``""`` /
53
+ ``" "`` for no connecting line (markers only).
46
54
 
47
55
  Raises
48
56
  ------
@@ -53,8 +61,8 @@ def _norm_linestyle(ls: str) -> str:
53
61
  if canonical is None:
54
62
  raise ValueError(
55
63
  f"Unknown linestyle {ls!r}. Expected one of: "
56
- "'solid', 'dashed', 'dotted', 'dashdot', 'step-mid' (alias: 'steps-mid') "
57
- "or shorthands '-', '--', ':', '-.'."
64
+ "'solid', 'dashed', 'dotted', 'dashdot', 'step-mid' (alias: 'steps-mid'), "
65
+ "'none' (markers only) or shorthands '-', '--', ':', '-.'."
58
66
  )
59
67
  return canonical
60
68
 
@@ -309,7 +309,9 @@ class Axes:
309
309
  linestyle : str, optional
310
310
  Dash pattern. Accepted values: ``"solid"`` (``"-"``),
311
311
  ``"dashed"`` (``"--"``), ``"dotted"`` (``":"``),
312
- ``"dashdot"`` (``"-."``) . Default ``"solid"``.
312
+ ``"dashdot"`` (``"-."``), or ``"none"`` to draw no connecting
313
+ line at all — pair it with *marker* for a scatter.
314
+ Default ``"solid"``.
313
315
  ls : str, optional
314
316
  Short alias for *linestyle*. Takes precedence if both are given.
315
317
  alpha : float, optional
@@ -786,12 +786,36 @@ class Figure(anywidget.AnyWidget, _EventMixin):
786
786
  return [dict(m) for m in self._figure_markers]
787
787
 
788
788
  def _push_widget(self, panel_id: str, widget_id: str, fields: dict) -> None:
789
- """Send a targeted widget-position update to JS (no image data)."""
789
+ """Send a targeted widget-position update to JS (no image data).
790
+
791
+ This writes ``event_json`` only — deliberately, because re-serialising
792
+ a whole panel (image bytes included) on every drag frame is exactly
793
+ what this path exists to avoid. The consequence is that
794
+ ``panel_<id>_json`` carries stale widget geometry between plot-level
795
+ pushes; :meth:`_sync_for_export` reconciles it before any snapshot.
796
+ """
790
797
  payload = {"source": "python", "panel_id": panel_id,
791
798
  "widget_id": widget_id}
792
799
  payload.update(fields)
793
800
  self.event_json = json.dumps(payload)
794
801
 
802
+ def _sync_for_export(self) -> None:
803
+ """Refresh every panel trait so a snapshot sees current widget state.
804
+
805
+ Widget moves reach JS as targeted ``event_json`` updates that never
806
+ touch the panel traits (see :meth:`_push_widget`), so ``save_html`` /
807
+ ``to_html`` / ``figure_state`` would otherwise capture every widget at
808
+ the position it was *created* at rather than where it now is. Python
809
+ holds the authoritative geometry — a JS-side drag writes back through
810
+ ``_dispatch_event`` — so re-pushing each panel is enough to reconcile.
811
+
812
+ Called from ``_repr_utils._widget_state``, the one chokepoint every
813
+ export path goes through. The live Jupyter path deliberately keeps
814
+ using targeted pushes and does not pay this cost.
815
+ """
816
+ for panel_id in list(self._plots_map):
817
+ self._push(panel_id)
818
+
795
819
  def _push_panel_fields(self, panel_id: str, fields: dict) -> None:
796
820
  """Apply a small set of changed *fields* to a panel, then push once.
797
821