anyplotlib 0.3.0__tar.gz → 0.4.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 (257) hide show
  1. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/CHANGELOG.rst +60 -0
  2. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/PKG-INFO +1 -1
  3. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/FIGURE_ESM.md +27 -11
  4. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/_utils.py +38 -0
  5. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/axes/_inset_axes.py +163 -5
  6. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/callbacks.py +26 -2
  7. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/figure/_figure.py +29 -0
  8. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/figure_esm.js +477 -39
  9. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/plot1d/_plot1d.py +46 -6
  10. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/plot2d/_layer.py +18 -5
  11. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/plot2d/_plot2d.py +48 -14
  12. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_embed/test_export_png.py +131 -51
  13. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_callbacks_playwright.py +100 -0
  14. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_callbacks_unit.py +72 -0
  15. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_widgets.py +196 -1
  16. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_labels/test_label_api.py +46 -0
  17. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/test_inset.py +153 -0
  18. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/test_inset_callout.py +371 -0
  19. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_layers.py +94 -0
  20. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_layers_playwright.py +107 -0
  21. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot3d/test_gpu_fallback.py +1 -2
  22. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/widgets/_widgets1d.py +21 -8
  23. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/widgets/_widgets2d.py +23 -6
  24. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/_root/index.html +3 -3
  25. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/_root/switcher.json +5 -0
  26. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/conf.py +1 -1
  27. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/pyproject.toml +1 -1
  28. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/uv.lock +1 -1
  29. anyplotlib-0.3.0/LARGE_IMAGE_WEBGPU_PLAN.md +0 -223
  30. anyplotlib-0.3.0/ORIX_BACKEND_PLAN.md +0 -133
  31. anyplotlib-0.3.0/RELEASE_NOTES.md +0 -94
  32. anyplotlib-0.3.0/RELEASE_PLAN.md +0 -104
  33. anyplotlib-0.3.0/WEBGPU_PLAN.md +0 -234
  34. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/.github/workflows/ci.yml +0 -0
  35. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/.github/workflows/docs.yml +0 -0
  36. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/.github/workflows/prepare_release.yml +0 -0
  37. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/.github/workflows/release.yml +0 -0
  38. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/.github/workflows/tests.yml +0 -0
  39. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/.gitignore +0 -0
  40. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/AGENTS.md +0 -0
  41. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Benchmarks/README.rst +0 -0
  42. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Benchmarks/plot_benchmark_comparison.py +0 -0
  43. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/README.rst +0 -0
  44. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_3d_spectral_viewer.py +0 -0
  45. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_eels_explorer.py +0 -0
  46. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_figure_annotations.py +0 -0
  47. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_interactive_fft.py +0 -0
  48. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_interactive_fitting.py +0 -0
  49. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_ipf_density_map.py +0 -0
  50. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_ipf_explorer.py +0 -0
  51. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_key_bindings.py +0 -0
  52. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_particle_picker.py +0 -0
  53. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_point_widget.py +0 -0
  54. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_segment_by_contrast.py +0 -0
  55. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_segment_by_contrast_advanced.py +0 -0
  56. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_spectra_roi_inspector.py +0 -0
  57. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_threshold_explorer.py +0 -0
  58. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Interactive/plot_voxel_grain_explorer.py +0 -0
  59. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Markers/README.rst +0 -0
  60. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Markers/plot_arrows.py +0 -0
  61. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Markers/plot_circles.py +0 -0
  62. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Markers/plot_ellipses.py +0 -0
  63. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Markers/plot_horizontal_lines.py +0 -0
  64. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Markers/plot_line_segments.py +0 -0
  65. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Markers/plot_points.py +0 -0
  66. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Markers/plot_polygons.py +0 -0
  67. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Markers/plot_rectangles.py +0 -0
  68. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Markers/plot_squares.py +0 -0
  69. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Markers/plot_texts.py +0 -0
  70. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Markers/plot_transform_markers.py +0 -0
  71. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Markers/plot_vertical_lines.py +0 -0
  72. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/PlotTypes/README.rst +0 -0
  73. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_3d.py +0 -0
  74. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_bar.py +0 -0
  75. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_gpu_voxels.py +0 -0
  76. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_gridspec_custom.py +0 -0
  77. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_image2d.py +0 -0
  78. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_inset.py +0 -0
  79. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_label_formatting.py +0 -0
  80. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_line_styles.py +0 -0
  81. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_pcolormesh.py +0 -0
  82. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_spectra1d.py +0 -0
  83. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_step_and_log.py +0 -0
  84. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_tile_backend.py +0 -0
  85. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/PlotTypes/plot_twinx.py +0 -0
  86. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/README.rst +0 -0
  87. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Widgets/README.rst +0 -0
  88. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget1d_hline.py +0 -0
  89. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget1d_point.py +0 -0
  90. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget1d_range.py +0 -0
  91. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget1d_vline.py +0 -0
  92. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget2d_annular.py +0 -0
  93. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget2d_arrow.py +0 -0
  94. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget2d_circle.py +0 -0
  95. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget2d_crosshair.py +0 -0
  96. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget2d_label.py +0 -0
  97. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget2d_polygon.py +0 -0
  98. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget2d_rectangle.py +0 -0
  99. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Examples/Widgets/plot_widget3d_plane.py +0 -0
  100. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/LICENSE +0 -0
  101. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/Makefile +0 -0
  102. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/README.md +0 -0
  103. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/__init__.py +0 -0
  104. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/_base_plot.py +0 -0
  105. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/_binary_frame.py +0 -0
  106. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/_electron.py +0 -0
  107. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/_repr_utils.py +0 -0
  108. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/axes/__init__.py +0 -0
  109. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/axes/_axes.py +0 -0
  110. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/conftest.py +0 -0
  111. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/embed.py +0 -0
  112. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/figure/__init__.py +0 -0
  113. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/figure/_gridspec.py +0 -0
  114. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/figure/_subplots.py +0 -0
  115. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/markers.py +0 -0
  116. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/plot1d/__init__.py +0 -0
  117. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/plot1d/_plotbar.py +0 -0
  118. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/plot2d/__init__.py +0 -0
  119. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/plot2d/_plotmesh.py +0 -0
  120. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/plot2d/_tile_backend.py +0 -0
  121. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/plot3d/__init__.py +0 -0
  122. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/plot3d/_plot3d.py +0 -0
  123. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/plotxy/__init__.py +0 -0
  124. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/plotxy/_plotxy.py +0 -0
  125. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/__init__.py +0 -0
  126. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/_directive.py +0 -0
  127. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/_repr_utils.py +0 -0
  128. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/_scraper.py +0 -0
  129. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/_wheel_builder.py +0 -0
  130. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/static/anywidget_bridge.js +0 -0
  131. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/static/anywidget_overlay.css +0 -0
  132. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/tests/__init__.py +0 -0
  133. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/tests/conftest.py +0 -0
  134. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/tests/test_directive.py +0 -0
  135. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/tests/test_init.py +0 -0
  136. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/tests/test_repr_utils.py +0 -0
  137. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/tests/test_scraper.py +0 -0
  138. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/sphinx_anywidget/tests/test_wheel_builder.py +0 -0
  139. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/__init__.py +0 -0
  140. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/_gpu_clim_check.cjs +0 -0
  141. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/_png_utils.py +0 -0
  142. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/bar_basic.png +0 -0
  143. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/gridspec_3col_equal_spectra.png +0 -0
  144. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/gridspec_asymmetric_width_ratios.png +0 -0
  145. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/gridspec_height_ratio_image_histogram.png +0 -0
  146. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/gridspec_image_two_spectra.png +0 -0
  147. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/gridspec_side_by_side_1d.png +0 -0
  148. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/gridspec_spanning_top_two_bottom.png +0 -0
  149. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/imshow_axis_off.png +0 -0
  150. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/imshow_checkerboard.png +0 -0
  151. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/imshow_gradient.png +0 -0
  152. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/imshow_labels.png +0 -0
  153. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/imshow_viridis.png +0 -0
  154. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/inset_1d.png +0 -0
  155. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/inset_maximized.png +0 -0
  156. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/inset_minimized.png +0 -0
  157. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/inset_normal_2d.png +0 -0
  158. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/inset_stacked.png +0 -0
  159. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/inset_stacked_one_minimized.png +0 -0
  160. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/pcolormesh_uniform.png +0 -0
  161. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_all_linestyles.png +0 -0
  162. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_alpha.png +0 -0
  163. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_axis_off.png +0 -0
  164. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_dashed.png +0 -0
  165. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_marker_symbols.png +0 -0
  166. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_markers.png +0 -0
  167. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_multi.png +0 -0
  168. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_sine.png +0 -0
  169. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_title.png +0 -0
  170. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot1d_twinx.png +0 -0
  171. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/plot3d_surface.png +0 -0
  172. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/baselines/subplots_2x1.png +0 -0
  173. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/conftest.py +0 -0
  174. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_benchmarks/__init__.py +0 -0
  175. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_benchmarks/baselines.json +0 -0
  176. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_benchmarks/benchmarks/baselines.json +0 -0
  177. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_benchmarks/test_benchmarks.py +0 -0
  178. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_benchmarks/test_benchmarks_py.py +0 -0
  179. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_documentation/__init__.py +0 -0
  180. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_documentation/test_bridge.py +0 -0
  181. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_documentation/test_push_hook.py +0 -0
  182. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_documentation/test_scraper.py +0 -0
  183. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_electron/__init__.py +0 -0
  184. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_electron/test_binary_frame.py +0 -0
  185. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_embed/__init__.py +0 -0
  186. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_embed/test_embed_api.py +0 -0
  187. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_embed/test_embed_mount.py +0 -0
  188. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_examples/__init__.py +0 -0
  189. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_examples/test_interactive_examples.py +0 -0
  190. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/__init__.py +0 -0
  191. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/_event_test_utils.py +0 -0
  192. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_blit_audit.py +0 -0
  193. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_edit_chrome.py +0 -0
  194. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_edit_chrome_playwright.py +0 -0
  195. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_event_pause_hold.py +0 -0
  196. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_event_plots.py +0 -0
  197. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_event_settled.py +0 -0
  198. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_events_regression.py +0 -0
  199. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_tile_parity_playwright.py +0 -0
  200. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_title.py +0 -0
  201. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_interactive/test_touch.py +0 -0
  202. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_labels/__init__.py +0 -0
  203. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_labels/test_label_rendering.py +0 -0
  204. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_labels/test_no_clipping.py +0 -0
  205. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/__init__.py +0 -0
  206. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/test_batch.py +0 -0
  207. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/test_geom_channel.py +0 -0
  208. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/test_gridspec.py +0 -0
  209. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/test_interaction.py +0 -0
  210. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_layouts/test_visual.py +0 -0
  211. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_markers/__init__.py +0 -0
  212. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_markers/test_marker_transforms.py +0 -0
  213. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_markers/test_markers.py +0 -0
  214. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot1d/__init__.py +0 -0
  215. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot1d/test_plot1d.py +0 -0
  216. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot1d/test_plotbar.py +0 -0
  217. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/__init__.py +0 -0
  218. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_detail_tile.py +0 -0
  219. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_first_paint_race.py +0 -0
  220. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_gpu_image.py +0 -0
  221. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_gpu_parity_playwright.py +0 -0
  222. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_imshow.py +0 -0
  223. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_imshow_rgb.py +0 -0
  224. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_pcolormesh.py +0 -0
  225. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_plot2d_api.py +0 -0
  226. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_rapid_frame_update.py +0 -0
  227. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_tile_backend.py +0 -0
  228. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_tiled_imshow.py +0 -0
  229. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot2d/test_zoom_view_write.py +0 -0
  230. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot3d/__init__.py +0 -0
  231. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot3d/test_colors_highlight.py +0 -0
  232. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot3d/test_gpu_hidden_reveal.py +0 -0
  233. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot3d/test_plot3d.py +0 -0
  234. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plot3d/test_voxels_planes.py +0 -0
  235. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/tests/test_plotxy/test_plotxy.py +0 -0
  236. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/widgets/__init__.py +0 -0
  237. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/widgets/_base.py +0 -0
  238. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/anyplotlib/widgets/_widgets3d.py +0 -0
  239. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/_sg_html_scraper.py +0 -0
  240. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/_static/anyplotlib.svg +0 -0
  241. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/_static/custom.css +0 -0
  242. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/_static/pyodide_bridge.js +0 -0
  243. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/_templates/autosummary/class.rst +0 -0
  244. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/api/callbacks.rst +0 -0
  245. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/api/figure.rst +0 -0
  246. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/api/figure_plots.rst +0 -0
  247. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/api/index.rst +0 -0
  248. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/api/markers.rst +0 -0
  249. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/api/widgets.rst +0 -0
  250. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/benchmarking.rst +0 -0
  251. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/dev/index.rst +0 -0
  252. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/embedding.rst +0 -0
  253. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/events.rst +0 -0
  254. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/getting_started.rst +0 -0
  255. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/docs/index.rst +0 -0
  256. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/make.bat +0 -0
  257. {anyplotlib-0.3.0 → anyplotlib-0.4.0}/upcoming_changes/README.rst +0 -0
@@ -10,6 +10,66 @@ Fragment files in ``upcoming_changes/`` are assembled into this file by
10
10
 
11
11
  .. towncrier release notes start
12
12
 
13
+ 0.4.0 (2026-07-18)
14
+ ==================
15
+
16
+ New Features
17
+ ------------
18
+
19
+ - Added :meth:`~anyplotlib.axes.InsetAxes.indicate_point` — the point sibling of
20
+ :meth:`~anyplotlib.axes.InsetAxes.indicate_region`: a circle-and-cross marker
21
+ at a data point of the parent plot plus a single leader line to the inset's
22
+ nearest corner, tracking zoom/pan and hiding the leader while minimized.
23
+ - Added :meth:`~anyplotlib.plot1d.Plot1D.set_legend_fontsize` to control the
24
+ legend text size on 1-D line plots.
25
+ - Added ``linewidth=`` to every overlay widget constructor and ``add_*_widget``
26
+ factory on :class:`~anyplotlib.plot2d.Plot2D` and
27
+ :class:`~anyplotlib.plot1d.Plot1D` (rectangle, circle, annular, crosshair,
28
+ polygon, vline, hline, range, point) — stroke width in px, default 2, stored
29
+ and round-tripped like ``color``.
30
+ - Added ``tint=`` to :meth:`~anyplotlib.plot2d.Plot2D.add_layer` and
31
+ :meth:`~anyplotlib.plot2d.Layer.set` — a ``#rgb``/``#rrggbb`` hex colour that
32
+ renders the layer as a clear→colour intensity ramp (transparent at low
33
+ intensity, opaque tint at high, via a 256×4 RGBA LUT) instead of a named
34
+ colormap; passing ``cmap=`` reverts a tinted layer to colormap display.
35
+ - An :meth:`~anyplotlib.figure.Figure.add_inset` with no title (the default,
36
+ ``title=""``) now renders with NO title-bar strip at all — a clean bordered
37
+ plot box, content filling the whole area, instead of a useless empty header.
38
+ A titled inset is unchanged: its bar renders as before, with click-to-toggle
39
+ minimize. A title-less inset has no minimize affordance (there is no bar to
40
+ click), but drag-to-move / drag-to-resize in edit mode still work exactly as
41
+ before, since those gestures are wired on the inset body, not the bar.
42
+ - Double-clicking a plot's text chrome now reports which element was hit. The
43
+ ``double_click`` :class:`~anyplotlib.callbacks.Event` gains a ``target`` field
44
+ naming the hit element — one of ``'title'``, ``'x_label'``, ``'x_ticks'``,
45
+ ``'y_label'``, ``'y_ticks'``, ``'colorbar_label'`` or ``'legend'`` — so a host
46
+ can open the right edit affordance for the axis label vs the ticks vs the title
47
+ vs the colorbar label vs the legend. The axis gutters, colorbar strip and title
48
+ band each get their own hit-test (2-D panels emit from the separate axis/title
49
+ canvases; 1-D panels zone-split the single canvas around the plot rect and
50
+ legend box). A plain plot-area double-click is unchanged and carries no
51
+ ``target`` (``event.target is None``), so existing handlers keep working.
52
+ - Insets can now be dragged and resized directly in the renderer's edit mode
53
+ (``edit_chrome``): drag the body to move an inset (a corner-stacked inset
54
+ converts to a free anchor and its siblings re-stack), or drag the bottom-right
55
+ grip to resize it (min 64 px per dimension). On release the renderer emits a
56
+ new figure-level ``inset_geometry_change`` event carrying the final
57
+ ``anchor``/``w_frac``/``h_frac`` (figure fractions), which
58
+ :meth:`~anyplotlib.figure.Figure.add_event_handler` handlers can observe to
59
+ persist the layout. The same geometry is applied programmatically via the new
60
+ :meth:`~anyplotlib.axes.InsetAxes.set_geometry` (``anchor``, ``w_frac``,
61
+ ``h_frac``). Off edit mode the affordances are hidden and the inset is inert.
62
+
63
+
64
+ Bug Fixes
65
+ ---------
66
+
67
+ - Fixed ``exportPNG`` compositing WebGPU-rendered 3-D panels (``scatter3d`` /
68
+ ``voxels``) as blank background rectangles — the 3-D render pass is now
69
+ re-rendered synchronously in-task before the canvas readback, exactly like
70
+ active-GPU 2-D image panels.
71
+
72
+
13
73
  0.3.0b1 (2026-07-13)
14
74
  ====================
15
75
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anyplotlib
3
- Version: 0.3.0
3
+ Version: 0.4.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
@@ -148,18 +148,25 @@ inside the figure). Minimize / maximize / restore work for both — a maximized
148
148
  inset floats centred at ~72 % (z 45); a minimized one collapses to its title
149
149
  bar in place.
150
150
 
151
- #### Region indications (callouts — `_drawCallouts`)
151
+ #### Region/point indications (callouts — `_drawCallouts`)
152
152
  `layout.indications` is an array of mark_inset-style callouts, each
153
- `{inset_id, parent_id, region:[x,y,w,h], color, linestyle, linewidth}`.
154
- `_drawCallouts()` renders them onto a figure-level `calloutCanvas` (z 30, above
155
- panels + insets, below maximized-inset float and the resize handle,
156
- `pointer-events:none`):
153
+ `{inset_id, parent_id, region:[x,y,w,h], color, linestyle, linewidth}` (from
154
+ `indicate_region`) or `{inset_id, parent_id, point:[x,y], color, linestyle,
155
+ linewidth, marker_size}` (from `indicate_point` the `point` key selects the
156
+ branch). `_drawCallouts()` renders them onto a figure-level `calloutCanvas`
157
+ (z 30, above panels + insets, below maximized-inset float and the resize
158
+ handle, `pointer-events:none`):
157
159
  - The **dashed source rect** maps `region` (parent DATA coords) through the
158
160
  parent's `_imgToCanvas2d` every draw, so it tracks the parent's zoom/pan; it
159
161
  is clipped to the parent's image area.
160
162
  - Two **leader lines** connect the rect's corners facing the inset to the
161
163
  inset's nearest corners (loc1/loc2-auto by comparing centres); they follow
162
164
  the inset's live DOM rect and are **hidden while the inset is minimized**.
165
+ - A **point indication** draws a solid circle-and-cross marker (radius
166
+ `marker_size`, clipped to the parent image area like the rect) at the mapped
167
+ data point, plus ONE leader from the marker's rim to the inset's nearest
168
+ corner (same minimized-hide rule; the leader uses the indication's
169
+ linestyle, the marker itself is always solid).
163
170
 
164
171
  `_drawCallouts()` is called at the end of `_redrawPanel` / `redrawAll` (tracks
165
172
  zoom/pan), at the end of `_applyAllInsetStates` (inset moved), on `applyLayout`
@@ -345,9 +352,13 @@ draws each **visible** layer bottom-up on `plotCanvas`:
345
352
  - `_layerBytes(st, layer)` prefers `layer_<id>_b64_bytes` (binary) over the entry
346
353
  `image_b64` base64;
347
354
  - `_layerBitmap(p, st, layer)` builds a LUT-colormapped RGBA `OffscreenCanvas`,
348
- **cached per layer id** by `(pixel key, cmap, clim)` — rebuilt only when the
349
- layer's data or appearance changes (a live scrub that only swaps one layer's
350
- data rebuilds just that layer);
355
+ **cached per layer id** by `(pixel key, cmap, tint, has-alpha, clim)` —
356
+ rebuilt only when the layer's data or appearance changes (a live scrub that
357
+ only swaps one layer's data rebuilds just that layer). The LUT honours a 4th
358
+ (alpha) channel when present (`cmapData[i][3] ?? 255`) — a `tint=` layer
359
+ ships a 256×4 clear→colour ramp (`_build_tint_lut`), so per-texel alpha
360
+ composites through the unpremultiplied `ImageData` and multiplies naturally
361
+ with the per-layer `ctx.globalAlpha`;
351
362
  - it blits with the SAME fit-rect + zoom/pan transform as the base blit
352
363
  (`_imgFitRect` + the `zoom>=1` window math) at `ctx.globalAlpha = layer.alpha`,
353
364
  so zoom/pan track the base exactly.
@@ -493,9 +504,14 @@ figure onto one offscreen canvas at `devicePixelRatio × scale`:
493
504
 
494
505
  - **WebGPU hazard first**: a WebGPU canvas's drawing buffer is only valid right
495
506
  after its render pass, so exportPNG force-calls `draw2d(p)` on every
496
- active-GPU 2-D panel (`p._gpu==='active' && p.gpuCanvas` visible) to re-submit
497
- its pass, THEN composites in the SAME synchronous task — so
498
- `drawImage(gpuCanvas,…)` reads live pixels, not a blank buffer.
507
+ active-GPU 2-D panel and `draw3d(p)` on every active-GPU 3-D panel
508
+ (`p._gpu==='active' && p.gpuCanvas` visible, `_gpuImg`/`_gpuObj` present) to
509
+ re-submit its pass, THEN composites in the SAME synchronous task — so
510
+ `drawImage(gpuCanvas,…)` reads live pixels, not a blank buffer. (draw3d's
511
+ active-GPU path uploads + submits in-task, no rAF, so the same-task re-render
512
+ suffices for 3-D too — without it a scatter3d/voxels panel exported as an
513
+ empty background rectangle; see `TestExportGpu3d` in
514
+ `tests/test_embed/test_export_png.py`.)
499
515
  - **Extent**: `fig_width/height + 2×8 px` gridDiv padding (NOT the measured
500
516
  `gridDiv` width — a bare `mount()` page has no `.apl-outer` inline-block CSS,
501
517
  so the grid container can stretch to the viewport). **Origin**: `gridDiv`'s
@@ -192,6 +192,44 @@ def _build_colormap_lut(name: str) -> list:
192
192
  return [[v, v, v] for v in range(256)]
193
193
 
194
194
 
195
+ def _parse_hex_color(color: str) -> tuple:
196
+ """Parse a ``#rgb`` / ``#rrggbb`` hex string → ``(r, g, b)`` ints (0–255).
197
+
198
+ Raises ``ValueError`` for anything else — tint colours are user API input
199
+ and a silent fallback would just render the wrong colour.
200
+ """
201
+ if not isinstance(color, str):
202
+ raise ValueError(f"expected a hex colour string, got {color!r}")
203
+ h = color.strip().lstrip("#")
204
+ try:
205
+ if len(h) == 3:
206
+ return tuple(int(c * 2, 16) for c in h)
207
+ if len(h) == 6:
208
+ return (int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16))
209
+ except ValueError:
210
+ pass
211
+ raise ValueError(
212
+ f"invalid hex colour {color!r} — expected '#rgb' or '#rrggbb'")
213
+
214
+
215
+ @functools.lru_cache(maxsize=64)
216
+ def _build_tint_lut(color: str) -> list:
217
+ """Return a 256-entry ``[[r, g, b, a], ...]`` clear→colour TINT LUT.
218
+
219
+ RGB is the parsed *color* at every entry; alpha ramps linearly 0 → 255, so
220
+ a layer using this LUT is fully transparent at low intensity and the
221
+ opaque tint colour at high intensity (a solid-colour intensity ramp, the
222
+ overlay look, vs a named colormap's opaque colour gradient).
223
+
224
+ CACHED like :func:`_build_colormap_lut` and treated as read-only by
225
+ callers (they JSON-serialise it); do NOT mutate the returned list. The
226
+ 4th (alpha) channel is honoured by the JS layer compositor
227
+ (``_layerBitmap`` reads ``cmapData[i][3] ?? 255``).
228
+ """
229
+ r, g, b = _parse_hex_color(color)
230
+ return [[r, g, b, a] for a in range(256)]
231
+
232
+
195
233
  def _resample_mesh(data: np.ndarray, x_edges, y_edges) -> np.ndarray:
196
234
  """Resample a mesh to a regular pixel grid via nearest-neighbour lookup.
197
235
 
@@ -96,9 +96,10 @@ class InsetAxes(Axes):
96
96
  self.h_frac = h_frac
97
97
  self.title = title
98
98
  self._inset_state: str = "normal"
99
- # Region indication (mark_inset-style callout) tied to this inset, or
100
- # None. Set via :meth:`indicate_region`, cleared via
101
- # :meth:`clear_indication`. Persisted in Figure.layout_json.
99
+ # Region/point indication (mark_inset-style callout) tied to this
100
+ # inset, or None. Set via :meth:`indicate_region` /
101
+ # :meth:`indicate_point`, cleared via :meth:`clear_indication`.
102
+ # Persisted in Figure.layout_json.
102
103
  self._indication: dict | None = None
103
104
 
104
105
  # ── state API ─────────────────────────────────────────────────────────
@@ -129,6 +130,76 @@ class InsetAxes(Axes):
129
130
  self._inset_state = "normal"
130
131
  self._fig._push_layout()
131
132
 
133
+ def set_geometry(self, *, anchor=None, w_frac: float | None = None,
134
+ h_frac: float | None = None) -> "InsetAxes":
135
+ """Move and/or resize the inset in figure-fraction coordinates.
136
+
137
+ This is the Python-side counterpart to the interactive drag / resize
138
+ of an inset in the renderer's edit mode: the ``inset_geometry_change``
139
+ event dispatches here so the authoritative Python state converges with
140
+ what the user did on screen. It is also callable directly to place or
141
+ size an inset programmatically.
142
+
143
+ Passing *anchor* switches the inset to free (anchor) placement and
144
+ drops any corner snapping (``corner`` becomes ``None``), mirroring the
145
+ renderer's corner-to-anchor conversion on drag start.
146
+
147
+ Parameters
148
+ ----------
149
+ anchor : (x_frac, y_frac), optional
150
+ New position of the inset's TOP-LEFT corner as fractions of the
151
+ figure size (0–1), measured from the figure's top-left. Each
152
+ component is clamped into ``[0, 1]``. When given, ``corner`` is
153
+ set to ``None`` (free placement). When omitted, the current
154
+ placement (corner or existing anchor) is left unchanged.
155
+ w_frac, h_frac : float, optional
156
+ New width / height as fractions of the figure size. Each is
157
+ clamped into ``(0, 1]`` (must be positive, at most the full
158
+ figure). When omitted, the corresponding dimension is unchanged.
159
+
160
+ Returns
161
+ -------
162
+ InsetAxes
163
+ ``self``, for chaining.
164
+
165
+ Raises
166
+ ------
167
+ ValueError
168
+ If *anchor* is not a pair of finite numbers, or ``w_frac`` /
169
+ ``h_frac`` is not a finite number.
170
+ """
171
+ if anchor is not None:
172
+ try:
173
+ ax_, ay_ = (float(v) for v in anchor)
174
+ except (TypeError, ValueError):
175
+ raise ValueError(
176
+ f"set_geometry: anchor must be 2 numbers (x_frac, y_frac), "
177
+ f"got {anchor!r}") from None
178
+ if not all(math.isfinite(v) for v in (ax_, ay_)):
179
+ raise ValueError(
180
+ f"set_geometry: anchor values must be finite, got "
181
+ f"({ax_}, {ay_})")
182
+ ax_ = min(1.0, max(0.0, ax_))
183
+ ay_ = min(1.0, max(0.0, ay_))
184
+ self.anchor = (ax_, ay_)
185
+ # Anchor placement supersedes corner (matches __init__ / the JS
186
+ # corner→anchor conversion on drag start).
187
+ self.corner = None
188
+ if w_frac is not None:
189
+ w = float(w_frac)
190
+ if not math.isfinite(w):
191
+ raise ValueError(
192
+ f"set_geometry: w_frac must be finite, got {w_frac!r}")
193
+ self.w_frac = min(1.0, max(1e-6, w))
194
+ if h_frac is not None:
195
+ h = float(h_frac)
196
+ if not math.isfinite(h):
197
+ raise ValueError(
198
+ f"set_geometry: h_frac must be finite, got {h_frac!r}")
199
+ self.h_frac = min(1.0, max(1e-6, h))
200
+ self._fig._push_layout()
201
+ return self
202
+
132
203
  # ── region indication (mark_inset-style callout) ──────────────────────
133
204
 
134
205
  def indicate_region(self, parent_plot, region, *,
@@ -218,8 +289,94 @@ class InsetAxes(Axes):
218
289
  self._fig._push_layout()
219
290
  return self
220
291
 
292
+ def indicate_point(self, parent_plot, point, *,
293
+ color: str = "#ff9800",
294
+ linestyle: str = "dashed",
295
+ linewidth: float = 1.5,
296
+ marker_size: float = 5.0) -> "InsetAxes":
297
+ """Draw a callout tying this inset to a single POINT of *parent_plot*.
298
+
299
+ The point sibling of :meth:`indicate_region`: renders a small circular
300
+ marker (with a centre cross) at *point* — in the parent image's DATA
301
+ coordinates — plus ONE leader line joining the marker to the inset's
302
+ nearest corner. The marker tracks the parent's zoom / pan and the
303
+ leader follows the inset as it moves (the leader hides while the inset
304
+ is minimized), exactly like the region callout.
305
+
306
+ Calling ``indicate_point`` (or ``indicate_region``) again REPLACES any
307
+ previous indication for this inset — an inset carries at most one.
308
+ Remove it with :meth:`clear_indication`.
309
+
310
+ Parameters
311
+ ----------
312
+ parent_plot : Plot2D
313
+ The parent image plot the point lives on. Must be a 2-D image
314
+ panel registered on the SAME figure as this inset.
315
+ point : tuple of float
316
+ The source point in the parent image's data coordinates, as
317
+ ``(x, y)`` — same convention as :meth:`indicate_region`'s region
318
+ origin. Both values must be finite; a point outside the parent's
319
+ data bounds is allowed (it simply clips visually).
320
+ color : str, optional
321
+ Stroke colour of the marker and the leader line. Default warm
322
+ orange ``"#ff9800"``.
323
+ linestyle : str, optional
324
+ Leader style — ``"dashed"`` (default), ``"solid"``, or
325
+ ``"dotted"``. The marker itself is always drawn solid.
326
+ linewidth : float, optional
327
+ Stroke width in CSS px. Default ``1.5``.
328
+ marker_size : float, optional
329
+ Marker circle radius in CSS px. Default ``5.0``. Must be > 0.
330
+
331
+ Returns
332
+ -------
333
+ InsetAxes
334
+ ``self``, for chaining.
335
+
336
+ Raises
337
+ ------
338
+ ValueError
339
+ If ``parent_plot`` has no panel id, is not registered on this
340
+ inset's Figure, ``point`` is not 2 finite numbers, or
341
+ ``marker_size`` is not > 0.
342
+ """
343
+ pid = getattr(parent_plot, "_id", None)
344
+ if pid is None:
345
+ raise ValueError("indicate_point: parent_plot has no panel id "
346
+ "(attach it to the figure first)")
347
+ if self._fig._plots_map.get(pid) is not parent_plot:
348
+ raise ValueError(
349
+ "indicate_point: parent_plot is not registered on this "
350
+ "inset's Figure — pass a plot created on the same figure "
351
+ "as this inset (fig.add_inset / fig.subplots)")
352
+ try:
353
+ x, y = (float(v) for v in point)
354
+ except (TypeError, ValueError):
355
+ raise ValueError(
356
+ f"indicate_point: point must be 2 numbers (x, y), "
357
+ f"got {point!r}") from None
358
+ if not all(math.isfinite(v) for v in (x, y)):
359
+ raise ValueError(
360
+ f"indicate_point: point values must be finite, got "
361
+ f"(x={x}, y={y})")
362
+ ms = float(marker_size)
363
+ if not (math.isfinite(ms) and ms > 0):
364
+ raise ValueError(
365
+ f"indicate_point: marker_size must be > 0, got {marker_size!r}")
366
+ self._indication = {
367
+ "parent_id": pid,
368
+ "point": [x, y],
369
+ "color": color,
370
+ "linestyle": linestyle,
371
+ "linewidth": float(linewidth),
372
+ "marker_size": ms,
373
+ }
374
+ self._fig._push_layout()
375
+ return self
376
+
221
377
  def clear_indication(self) -> None:
222
- """Remove any region indication attached to this inset (idempotent)."""
378
+ """Remove any region/point indication attached to this inset
379
+ (idempotent)."""
223
380
  if self._indication is None:
224
381
  return
225
382
  self._indication = None
@@ -227,7 +384,8 @@ class InsetAxes(Axes):
227
384
 
228
385
  @property
229
386
  def indication(self) -> "dict | None":
230
- """The current region-indication spec (``dict``) or ``None``."""
387
+ """The current indication spec (``dict`` with either a ``region`` or a
388
+ ``point`` key) or ``None``."""
231
389
  return self._indication
232
390
 
233
391
  # ── internal ──────────────────────────────────────────────────────────
@@ -24,7 +24,10 @@ from typing import Any, Callable
24
24
  VALID_EVENT_TYPES = frozenset({
25
25
  "pointer_down", "pointer_up", "pointer_move", "pointer_settled",
26
26
  "pointer_enter", "pointer_leave", "double_click", "wheel",
27
- "key_down", "key_up", "close", "view_changed", "*",
27
+ "key_down", "key_up", "close", "view_changed",
28
+ # Figure-level inset drag / resize (JS → Python), so a host can persist the
29
+ # inset's new geometry. Fired through the figure's CallbackRegistry.
30
+ "inset_geometry_change", "*",
28
31
  })
29
32
 
30
33
 
@@ -43,6 +46,7 @@ class Event:
43
46
  ray — Plot3D only: {"origin": [...], "direction": [...]}
44
47
  line_id — Plot1D only: set when pointer is over a line
45
48
  dwell_ms — pointer_settled only: actual dwell time
49
+ target — double_click only: hit chrome element, or None for plot-area (title/x_label/x_ticks/y_label/y_ticks/colorbar_label/legend)
46
50
 
47
51
  PlotBar extra fields (pointer_down only):
48
52
  bar_index, value, x_label, group_index
@@ -54,6 +58,14 @@ class Event:
54
58
  key — key name e.g. "q", "Enter", "ArrowLeft"
55
59
  last_widget_id — id of the last widget the user clicked, or None
56
60
 
61
+ Panel-swap fields (figure-level panel_swap events):
62
+ source_panel_id, target_panel_id — the two panel ids dragged between
63
+
64
+ Inset drag/resize fields (figure-level inset_geometry_change events):
65
+ inset_id — the inset panel id
66
+ anchor — new top-left position [fx, fy] in figure fractions
67
+ w_frac, h_frac — new size in figure fractions
68
+
57
69
  Propagation:
58
70
  stop_propagation — set True inside a handler to halt remaining handlers
59
71
  """
@@ -94,6 +106,18 @@ class Event:
94
106
  # ids the user dragged between. Set only on panel_swap; None otherwise.
95
107
  source_panel_id: str | None = None
96
108
  target_panel_id: str | None = None
109
+ # Inset drag / resize (figure-level inset_geometry_change events): the
110
+ # inset panel id plus its new geometry in figure fractions. Set only on
111
+ # inset_geometry_change; None otherwise.
112
+ inset_id: str | None = None
113
+ anchor: list | None = None
114
+ w_frac: float | None = None
115
+ h_frac: float | None = None
116
+ # Tagged double-click hit-target (double_click events): identifies WHICH
117
+ # text/chrome element was double-clicked — one of 'title', 'x_label',
118
+ # 'x_ticks', 'y_label', 'y_ticks', 'colorbar_label', 'legend'. None for a
119
+ # plain plot-area double_click (back-compatible) and every other event.
120
+ target: str | None = None
97
121
  # Propagation (not repr'd)
98
122
  stop_propagation: bool = field(default=False, repr=False)
99
123
 
@@ -101,7 +125,7 @@ class Event:
101
125
  src = type(self.source).__name__ if self.source is not None else "None"
102
126
  parts = [f"event_type={self.event_type!r}", f"source={src}"]
103
127
  for fname in ("x", "y", "xdata", "ydata", "button", "key",
104
- "line_id", "bar_index", "dwell_ms"):
128
+ "line_id", "bar_index", "dwell_ms", "target"):
105
129
  v = getattr(self, fname)
106
130
  if v is not None:
107
131
  parts.append(f"{fname}={v!r}")
@@ -596,6 +596,23 @@ class Figure(anywidget.AnyWidget, _EventMixin):
596
596
  self._push_layout()
597
597
  return
598
598
 
599
+ # Inset drag / resize: the JS ships the inset's final geometry on
600
+ # pointer_up (anchor top-left fraction + size fractions). Apply it to
601
+ # the InsetAxes via set_geometry so the authoritative Python state
602
+ # converges (the event fires once, on release, so re-pushing layout is
603
+ # an idempotent echo), then fire the figure-level callbacks with the
604
+ # geometry so a host can persist it. Mirrors inset_state_change above.
605
+ if event_type == "inset_geometry_change":
606
+ inset_ax = self._insets_map.get(panel_id)
607
+ if inset_ax is not None:
608
+ anchor = msg.get("anchor")
609
+ w_frac = msg.get("w_frac")
610
+ h_frac = msg.get("h_frac")
611
+ inset_ax.set_geometry(anchor=anchor, w_frac=w_frac,
612
+ h_frac=h_frac)
613
+ self._fire_figure_event(event_type, msg)
614
+ return
615
+
599
616
  plot = self._plots_map.get(panel_id)
600
617
  if plot is None:
601
618
  if event_type == "view_changed":
@@ -656,6 +673,7 @@ class Figure(anywidget.AnyWidget, _EventMixin):
656
673
  display_height=msg.get("display_height"),
657
674
  key=msg.get("key"),
658
675
  last_widget_id=msg.get("last_widget_id"),
676
+ target=msg.get("target"),
659
677
  )
660
678
  plot.callbacks.fire(event)
661
679
 
@@ -683,6 +701,17 @@ class Figure(anywidget.AnyWidget, _EventMixin):
683
701
  last_widget_id=msg.get("marker_id"),
684
702
  source_panel_id=msg.get("source_panel_id"),
685
703
  target_panel_id=msg.get("target_panel_id"),
704
+ # Inset drag / resize geometry — populated only for the
705
+ # inset_geometry_change event; None for every other figure-level
706
+ # event (figure_background click, figure-marker drag, panel_swap).
707
+ inset_id=(msg.get("panel_id")
708
+ if event_type == "inset_geometry_change" else None),
709
+ anchor=(msg.get("anchor")
710
+ if event_type == "inset_geometry_change" else None),
711
+ w_frac=(msg.get("w_frac")
712
+ if event_type == "inset_geometry_change" else None),
713
+ h_frac=(msg.get("h_frac")
714
+ if event_type == "inset_geometry_change" else None),
686
715
  )
687
716
  self.callbacks.fire(event)
688
717