anyplotlib 0.5.0__tar.gz → 0.7.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 (277) hide show
  1. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/.github/workflows/docs.yml +17 -3
  2. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/.gitignore +11 -0
  3. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/AGENTS.md +25 -2
  4. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/CHANGELOG.rst +76 -0
  5. anyplotlib-0.7.0/Examples/Interactive/plot_star_globe_explorer.py +341 -0
  6. anyplotlib-0.7.0/Examples/PlotTypes/plot_3d_texture.py +344 -0
  7. anyplotlib-0.7.0/Examples/PlotTypes/plot_key_overlays.py +169 -0
  8. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/PKG-INFO +3 -2
  9. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/README.md +2 -1
  10. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/FIGURE_ESM.md +117 -33
  11. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/__init__.py +4 -2
  12. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/_base_plot.py +166 -0
  13. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/_utils.py +86 -0
  14. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/axes/_axes.py +20 -2
  15. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/callbacks.py +6 -0
  16. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/figure/_figure.py +2 -0
  17. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/figure_esm.js +1171 -93
  18. anyplotlib-0.7.0/anyplotlib/keys.py +252 -0
  19. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/plot2d/_plot2d.py +75 -4
  20. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/plot3d/_plot3d.py +199 -4
  21. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/plotxy/_plotxy.py +1 -1
  22. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/conftest.py +135 -0
  23. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_examples/test_interactive_examples.py +1 -0
  24. anyplotlib-0.7.0/anyplotlib/tests/test_interactive/test_orbit_direction.py +219 -0
  25. anyplotlib-0.7.0/anyplotlib/tests/test_interactive/test_star_globe_linking.py +187 -0
  26. anyplotlib-0.7.0/anyplotlib/tests/test_interactive/test_widget_brush.py +777 -0
  27. anyplotlib-0.7.0/anyplotlib/tests/test_keys/test_keys.py +413 -0
  28. anyplotlib-0.7.0/anyplotlib/tests/test_plot3d/test_gpu_depth.py +139 -0
  29. anyplotlib-0.7.0/anyplotlib/tests/test_plot3d/test_texture.py +636 -0
  30. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/widgets/__init__.py +2 -1
  31. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/widgets/_widgets2d.py +240 -0
  32. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/_root/index.html +3 -3
  33. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/_root/switcher.json +10 -0
  34. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/api/index.rst +1 -0
  35. anyplotlib-0.7.0/docs/api/keys.rst +42 -0
  36. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/api/widgets.rst +7 -0
  37. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/conf.py +1 -1
  38. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/pyproject.toml +8 -1
  39. anyplotlib-0.7.0/upcoming_changes/.gitkeep +3 -0
  40. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/upcoming_changes/README.rst +5 -0
  41. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/uv.lock +1 -1
  42. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/.github/workflows/ci.yml +0 -0
  43. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/.github/workflows/prepare_release.yml +0 -0
  44. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/.github/workflows/release.yml +0 -0
  45. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/.github/workflows/tests.yml +0 -0
  46. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Benchmarks/README.rst +0 -0
  47. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Benchmarks/plot_benchmark_comparison.py +0 -0
  48. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/README.rst +0 -0
  49. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_3d_spectral_viewer.py +0 -0
  50. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_eels_explorer.py +0 -0
  51. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_figure_annotations.py +0 -0
  52. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_interactive_fft.py +0 -0
  53. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_interactive_fitting.py +0 -0
  54. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_ipf_density_map.py +0 -0
  55. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_ipf_explorer.py +0 -0
  56. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_key_bindings.py +0 -0
  57. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_particle_picker.py +0 -0
  58. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_point_widget.py +0 -0
  59. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_segment_by_contrast.py +0 -0
  60. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_segment_by_contrast_advanced.py +0 -0
  61. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_spectra_roi_inspector.py +0 -0
  62. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_threshold_explorer.py +0 -0
  63. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Interactive/plot_voxel_grain_explorer.py +0 -0
  64. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Markers/README.rst +0 -0
  65. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Markers/plot_arrows.py +0 -0
  66. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Markers/plot_circles.py +0 -0
  67. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Markers/plot_ellipses.py +0 -0
  68. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Markers/plot_horizontal_lines.py +0 -0
  69. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Markers/plot_line_segments.py +0 -0
  70. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Markers/plot_points.py +0 -0
  71. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Markers/plot_polygons.py +0 -0
  72. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Markers/plot_rectangles.py +0 -0
  73. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Markers/plot_squares.py +0 -0
  74. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Markers/plot_texts.py +0 -0
  75. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Markers/plot_transform_markers.py +0 -0
  76. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Markers/plot_vertical_lines.py +0 -0
  77. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/PlotTypes/README.rst +0 -0
  78. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/PlotTypes/plot_3d.py +0 -0
  79. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/PlotTypes/plot_bar.py +0 -0
  80. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/PlotTypes/plot_gpu_voxels.py +0 -0
  81. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/PlotTypes/plot_gridspec_custom.py +0 -0
  82. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/PlotTypes/plot_image2d.py +0 -0
  83. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/PlotTypes/plot_inset.py +0 -0
  84. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/PlotTypes/plot_label_formatting.py +0 -0
  85. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/PlotTypes/plot_line_styles.py +0 -0
  86. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/PlotTypes/plot_pcolormesh.py +0 -0
  87. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/PlotTypes/plot_spectra1d.py +0 -0
  88. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/PlotTypes/plot_step_and_log.py +0 -0
  89. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/PlotTypes/plot_tile_backend.py +0 -0
  90. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/PlotTypes/plot_twinx.py +0 -0
  91. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/README.rst +0 -0
  92. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Widgets/README.rst +0 -0
  93. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Widgets/plot_widget1d_hline.py +0 -0
  94. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Widgets/plot_widget1d_point.py +0 -0
  95. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Widgets/plot_widget1d_range.py +0 -0
  96. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Widgets/plot_widget1d_vline.py +0 -0
  97. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Widgets/plot_widget2d_annular.py +0 -0
  98. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Widgets/plot_widget2d_arrow.py +0 -0
  99. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Widgets/plot_widget2d_circle.py +0 -0
  100. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Widgets/plot_widget2d_crosshair.py +0 -0
  101. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Widgets/plot_widget2d_label.py +0 -0
  102. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Widgets/plot_widget2d_polygon.py +0 -0
  103. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Widgets/plot_widget2d_rectangle.py +0 -0
  104. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Examples/Widgets/plot_widget3d_plane.py +0 -0
  105. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/LICENSE +0 -0
  106. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/Makefile +0 -0
  107. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/_binary_frame.py +0 -0
  108. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/_electron.py +0 -0
  109. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/_repr_utils.py +0 -0
  110. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/axes/__init__.py +0 -0
  111. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/axes/_inset_axes.py +0 -0
  112. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/conftest.py +0 -0
  113. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/embed.py +0 -0
  114. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/figure/__init__.py +0 -0
  115. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/figure/_gridspec.py +0 -0
  116. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/figure/_subplots.py +0 -0
  117. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/markers.py +0 -0
  118. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/plot1d/__init__.py +0 -0
  119. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/plot1d/_plot1d.py +0 -0
  120. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/plot1d/_plotbar.py +0 -0
  121. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/plot2d/__init__.py +0 -0
  122. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/plot2d/_layer.py +0 -0
  123. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/plot2d/_plotmesh.py +0 -0
  124. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/plot2d/_tile_backend.py +0 -0
  125. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/plot3d/__init__.py +0 -0
  126. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/plotxy/__init__.py +0 -0
  127. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/sphinx_anywidget/__init__.py +0 -0
  128. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/sphinx_anywidget/_directive.py +0 -0
  129. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/sphinx_anywidget/_repr_utils.py +0 -0
  130. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/sphinx_anywidget/_scraper.py +0 -0
  131. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/sphinx_anywidget/_wheel_builder.py +0 -0
  132. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/sphinx_anywidget/static/anywidget_bridge.js +0 -0
  133. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/sphinx_anywidget/static/anywidget_overlay.css +0 -0
  134. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/sphinx_anywidget/tests/__init__.py +0 -0
  135. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/sphinx_anywidget/tests/conftest.py +0 -0
  136. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/sphinx_anywidget/tests/test_directive.py +0 -0
  137. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/sphinx_anywidget/tests/test_init.py +0 -0
  138. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/sphinx_anywidget/tests/test_repr_utils.py +0 -0
  139. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/sphinx_anywidget/tests/test_scraper.py +0 -0
  140. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/sphinx_anywidget/tests/test_wheel_builder.py +0 -0
  141. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/__init__.py +0 -0
  142. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/_gpu_clim_check.cjs +0 -0
  143. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/_png_utils.py +0 -0
  144. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/bar_basic.png +0 -0
  145. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/gridspec_3col_equal_spectra.png +0 -0
  146. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/gridspec_asymmetric_width_ratios.png +0 -0
  147. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/gridspec_height_ratio_image_histogram.png +0 -0
  148. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/gridspec_image_two_spectra.png +0 -0
  149. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/gridspec_side_by_side_1d.png +0 -0
  150. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/gridspec_spanning_top_two_bottom.png +0 -0
  151. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/imshow_axis_off.png +0 -0
  152. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/imshow_checkerboard.png +0 -0
  153. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/imshow_gradient.png +0 -0
  154. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/imshow_labels.png +0 -0
  155. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/imshow_viridis.png +0 -0
  156. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/inset_1d.png +0 -0
  157. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/inset_maximized.png +0 -0
  158. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/inset_minimized.png +0 -0
  159. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/inset_normal_2d.png +0 -0
  160. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/inset_stacked.png +0 -0
  161. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/inset_stacked_one_minimized.png +0 -0
  162. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/pcolormesh_uniform.png +0 -0
  163. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/plot1d_all_linestyles.png +0 -0
  164. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/plot1d_alpha.png +0 -0
  165. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/plot1d_axis_off.png +0 -0
  166. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/plot1d_dashed.png +0 -0
  167. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/plot1d_marker_symbols.png +0 -0
  168. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/plot1d_markers.png +0 -0
  169. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/plot1d_multi.png +0 -0
  170. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/plot1d_sine.png +0 -0
  171. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/plot1d_title.png +0 -0
  172. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/plot1d_twinx.png +0 -0
  173. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/plot3d_surface.png +0 -0
  174. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/baselines/subplots_2x1.png +0 -0
  175. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_benchmarks/__init__.py +0 -0
  176. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_benchmarks/baselines.json +0 -0
  177. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_benchmarks/benchmarks/baselines.json +0 -0
  178. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_benchmarks/test_benchmarks.py +0 -0
  179. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_benchmarks/test_benchmarks_py.py +0 -0
  180. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_documentation/__init__.py +0 -0
  181. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_documentation/test_bridge.py +0 -0
  182. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_documentation/test_push_hook.py +0 -0
  183. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_documentation/test_scraper.py +0 -0
  184. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_electron/__init__.py +0 -0
  185. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_electron/test_binary_frame.py +0 -0
  186. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_embed/__init__.py +0 -0
  187. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_embed/test_embed_api.py +0 -0
  188. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_embed/test_embed_mount.py +0 -0
  189. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_embed/test_export_png.py +0 -0
  190. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_embed/test_export_widget_sync.py +0 -0
  191. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_examples/__init__.py +0 -0
  192. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/__init__.py +0 -0
  193. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/_event_test_utils.py +0 -0
  194. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_blit_audit.py +0 -0
  195. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_callbacks_playwright.py +0 -0
  196. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_callbacks_unit.py +0 -0
  197. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_edit_chrome.py +0 -0
  198. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_edit_chrome_playwright.py +0 -0
  199. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_event_pause_hold.py +0 -0
  200. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_event_plots.py +0 -0
  201. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_event_settled.py +0 -0
  202. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_events_regression.py +0 -0
  203. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_pointer_down_1d.py +0 -0
  204. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_range_orientation_snap.py +0 -0
  205. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_tile_parity_playwright.py +0 -0
  206. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_title.py +0 -0
  207. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_touch.py +0 -0
  208. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_widget_max_extent.py +0 -0
  209. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_widget_set_notify.py +0 -0
  210. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_widgets.py +0 -0
  211. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_interactive/test_widgets_line_2d.py +0 -0
  212. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_labels/__init__.py +0 -0
  213. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_labels/test_label_api.py +0 -0
  214. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_labels/test_label_rendering.py +0 -0
  215. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_labels/test_no_clipping.py +0 -0
  216. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_labels/test_ylabel_clearance.py +0 -0
  217. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_layouts/__init__.py +0 -0
  218. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_layouts/test_batch.py +0 -0
  219. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_layouts/test_coord_conversion.py +0 -0
  220. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_layouts/test_geom_channel.py +0 -0
  221. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_layouts/test_gridspec.py +0 -0
  222. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_layouts/test_inset.py +0 -0
  223. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_layouts/test_inset_callout.py +0 -0
  224. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_layouts/test_interaction.py +0 -0
  225. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_layouts/test_visual.py +0 -0
  226. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_markers/__init__.py +0 -0
  227. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_markers/test_marker_transforms.py +0 -0
  228. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_markers/test_markers.py +0 -0
  229. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_markers/test_per_marker_colors.py +0 -0
  230. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_markers/test_size_units.py +0 -0
  231. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot1d/__init__.py +0 -0
  232. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot1d/test_line_none_rendering.py +0 -0
  233. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot1d/test_plot1d.py +0 -0
  234. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot1d/test_plotbar.py +0 -0
  235. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/__init__.py +0 -0
  236. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_colorbar_gap_scalebar.py +0 -0
  237. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_detail_tile.py +0 -0
  238. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_first_paint_race.py +0 -0
  239. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_gpu_image.py +0 -0
  240. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_gpu_parity_playwright.py +0 -0
  241. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_imshow.py +0 -0
  242. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_imshow_rgb.py +0 -0
  243. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_layers.py +0 -0
  244. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_layers_playwright.py +0 -0
  245. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_pcolormesh.py +0 -0
  246. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_plot2d_api.py +0 -0
  247. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_rapid_frame_update.py +0 -0
  248. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_tile_backend.py +0 -0
  249. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_tile_diagnostics.py +0 -0
  250. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_tiled_imshow.py +0 -0
  251. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot2d/test_zoom_view_write.py +0 -0
  252. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot3d/__init__.py +0 -0
  253. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot3d/test_colors_highlight.py +0 -0
  254. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot3d/test_gpu_fallback.py +0 -0
  255. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot3d/test_gpu_hidden_reveal.py +0 -0
  256. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot3d/test_plot3d.py +0 -0
  257. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plot3d/test_voxels_planes.py +0 -0
  258. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/tests/test_plotxy/test_plotxy.py +0 -0
  259. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/widgets/_base.py +0 -0
  260. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/widgets/_widgets1d.py +0 -0
  261. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/anyplotlib/widgets/_widgets3d.py +0 -0
  262. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/_sg_html_scraper.py +0 -0
  263. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/_static/anyplotlib.svg +0 -0
  264. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/_static/custom.css +0 -0
  265. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/_static/pyodide_bridge.js +0 -0
  266. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/_templates/autosummary/class.rst +0 -0
  267. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/api/callbacks.rst +0 -0
  268. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/api/figure.rst +0 -0
  269. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/api/figure_plots.rst +0 -0
  270. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/api/markers.rst +0 -0
  271. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/benchmarking.rst +0 -0
  272. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/dev/index.rst +0 -0
  273. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/embedding.rst +0 -0
  274. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/events.rst +0 -0
  275. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/getting_started.rst +0 -0
  276. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/docs/index.rst +0 -0
  277. {anyplotlib-0.5.0 → anyplotlib-0.7.0}/make.bat +0 -0
@@ -11,10 +11,13 @@ on:
11
11
  # Allow manual re-builds from the Actions tab.
12
12
  workflow_dispatch:
13
13
 
14
- # Only one docs deployment should run at a time to avoid race conditions on
15
- # the gh-pages branch.
14
+ # Per-ref grouping so a new push supersedes an in-flight BUILD of the same
15
+ # ref (PR iterations, repeated pushes to main). This deliberately does NOT
16
+ # serialise deployments: a push to main and its release tag are different
17
+ # refs, so they land in different groups. The deploy job carries its own
18
+ # ref-independent group for that — see below.
16
19
  concurrency:
17
- group: docs-${{ github.ref }}
20
+ group: docs-build-${{ github.ref }}
18
21
  cancel-in-progress: true
19
22
 
20
23
  permissions:
@@ -91,6 +94,17 @@ jobs:
91
94
  # Skip deployment for pull requests.
92
95
  if: github.event_name != 'pull_request'
93
96
 
97
+ # Every deploy pushes to the SAME branch (gh-pages), so they must be
98
+ # serialised across refs, not per-ref. Releasing tags main and v* almost
99
+ # simultaneously, which previously raced: whichever run pushed second was
100
+ # rejected with "cannot lock ref 'refs/heads/gh-pages'" and its version
101
+ # directory never appeared, while switcher.json still advertised it.
102
+ # cancel-in-progress is false because these runs must QUEUE — cancelling
103
+ # one would drop a version directory just as surely as the race did.
104
+ concurrency:
105
+ group: docs-deploy
106
+ cancel-in-progress: false
107
+
94
108
  steps:
95
109
  - name: Checkout repository
96
110
  uses: actions/checkout@v4
@@ -51,3 +51,14 @@ docs/_static/anywidget_config.js
51
51
 
52
52
  # Generated by Sphinx-Gallery (anywidget iframe HTML) — never commit
53
53
  docs/_static/viewer_widgets/
54
+
55
+ # Scratch doc builds (`make html O=...` into a throwaway output dir)
56
+ docs/_dummy/
57
+
58
+ # Scratch working files that land in the repo root / Examples during
59
+ # development: saved diffs and throwaway notebooks. Real examples are
60
+ # `Examples/<Section>/plot_*.py` and are picked up by Sphinx-Gallery.
61
+ *.diff
62
+ *.patch
63
+ Examples/testing.ipynb
64
+ Examples/*.ipynb
@@ -7,7 +7,7 @@
7
7
  - **`Figure`** (`anyplotlib/figure/_figure.py`) — the only `anywidget.AnyWidget` subclass. Owns all traitlets and is the Python↔JS bridge.
8
8
  - **Plot objects** (`plot1d/`, `plot2d/`, `plot3d/`) — `Plot1D`, `PlotBar`, `Plot2D`, `PlotMesh`, `Plot3D` are **plain Python classes**, not widgets. They hold state in `_state` dicts and push to the Figure. Shared behaviour lives in `_base_plot.py` (`_BasePlot`, `_PanelMixin`, `_MarkerMixin`).
9
9
  - **`Axes`** (`axes/_axes.py`) — grid-cell container; factory methods (`imshow`, `plot`, `bar`, `pcolormesh`, `plot_surface`, …) create plot objects and attach them.
10
- - **`figure_esm.js`** — pure-JS canvas renderer (~4,400 lines); all rendering logic lives here. **Read `anyplotlib/FIGURE_ESM.md` first** — it is the section map.
10
+ - **`figure_esm.js`** — pure-JS canvas renderer (~9,300 lines); all rendering logic lives here. **Read `anyplotlib/FIGURE_ESM.md` first** — it is the section map.
11
11
  - **`markers.py`** — static visual overlays (circles, arrows, lines, etc.) with a two-level dict registry: `plot.markers[type][name]`.
12
12
  - **`widgets/`** — interactive draggable overlays (`RectangleWidget`, `CrosshairWidget`, etc.) that receive JS position updates.
13
13
  - **`callbacks.py`** — event system: `Event` dataclass, `CallbackRegistry` (priority ordering, wildcard, pause/hold), `_EventMixin` (`add_event_handler`).
@@ -95,8 +95,31 @@ make html
95
95
  make clean # wipe build artefacts
96
96
  ```
97
97
 
98
+ **After editing `figure_esm.js`, docs need `make clean` first.** Sphinx Gallery
99
+ re-executes an example only when its *source* MD5 changes, and each example's
100
+ interactive widget page (`build/html/_static/viewer_widgets/*.html`, ~1 MB) is
101
+ generated during that execution with the renderer **inlined**. A plain
102
+ `make html` therefore rebuilds the pages and the Pyodide wheel but silently
103
+ serves widgets built from an older `figure_esm.js` — the figures still work,
104
+ so nothing looks broken; they just behave like the old renderer. Check with
105
+ `grep -c '<some new symbol>' build/html/_static/viewer_widgets/<page>_001.html`
106
+ when a JS change doesn't seem to reach the docs.
107
+
108
+ **Any insertion into `figure_esm.js` shifts every `FIGURE_ESM.md` line anchor
109
+ below it.** The anchors are maintained by hand, so re-check them in the *same*
110
+ commit that moves them — a map that is 500 lines out is worse than no map. Dump
111
+ the real numbers with:
112
+
113
+ ```bash
114
+ grep -nE '^\s*(function|const|let) [A-Za-z_]' anyplotlib/figure_esm.js
115
+ ```
116
+
117
+ and reconcile against the two numbered tables (the section map near the top and
118
+ the 2-D function table). Both were last verified at 9,285 lines.
119
+
98
120
  Changelog entries: add a fragment file to `upcoming_changes/` (e.g.
99
121
  `123.new_feature.rst`) — towncrier assembles `CHANGELOG.rst` at release time.
122
+ Use `api_change` when existing behaviour changes, even if the change is a fix.
100
123
 
101
124
  ## Key Files
102
125
 
@@ -106,7 +129,7 @@ Changelog entries: add a fragment file to `upcoming_changes/` (e.g.
106
129
  | `anyplotlib/figure/_gridspec.py` | `GridSpec`, `SubplotSpec` |
107
130
  | `anyplotlib/figure/_subplots.py` | `subplots()` factory |
108
131
  | `anyplotlib/axes/_axes.py` | `Axes` — plot factory methods |
109
- | `anyplotlib/figure_esm.js` | All JS canvas rendering (~4,400 lines) |
132
+ | `anyplotlib/figure_esm.js` | All JS canvas rendering (~9,300 lines) |
110
133
  | `anyplotlib/FIGURE_ESM.md` | Section map for `figure_esm.js` — read this before editing the JS |
111
134
  | `anyplotlib/markers.py` | Static marker collections; `to_wire()` translation |
112
135
  | `anyplotlib/widgets/` | Interactive overlay widgets |
@@ -10,6 +10,82 @@ Fragment files in ``upcoming_changes/`` are assembled into this file by
10
10
 
11
11
  .. towncrier release notes start
12
12
 
13
+ 0.7.0 (2026-07-31)
14
+ ==================
15
+
16
+ New Features
17
+ ------------
18
+
19
+ - Added :meth:`~anyplotlib.Plot2D.add_key` for pinning a floating image *key* over
20
+ a panel — an inverse pole figure triangle over an orientation map, a hue wheel
21
+ over a polarization field, a phase key over a segmentation. A key is the scale
22
+ bar's sibling: it floats in screen space and neither pans nor zooms with the
23
+ data, it takes an RGBA image so a triangle or a disc needs no rectangular card
24
+ around it, and ``labels=`` annotates the picture itself (an IPF triangle's
25
+ corner indices) in fractions of the key image. Optional ``bgcolor`` /
26
+ ``border`` / ``alpha`` give it a card when the data underneath is busy, and
27
+ ``hover_only=True`` reveals it only while the pointer is over the panel.
28
+ Available on every panel type, and included in PNG export.
29
+
30
+
31
+ 0.6.0 (2026-07-31)
32
+ ==================
33
+
34
+ API and Behaviour Changes
35
+ -------------------------
36
+
37
+ - **3-D orbit drags have reversed direction.** Both azimuth and elevation now
38
+ move the geometry *with* the cursor instead of away from it, matching
39
+ matplotlib's ``mplot3d`` and every other turntable control — dragging right
40
+ spins a globe right. Azimuth and elevation position the *camera*, so adding
41
+ the drag delta swept the surface the opposite way, as if you had grabbed its
42
+ far side. Any muscle memory (or scripted pointer drag) built against the old
43
+ direction is inverted; panels driven from Python with
44
+ :meth:`~anyplotlib.Plot3D.set_view` are unaffected.
45
+
46
+
47
+ New Features
48
+ ------------
49
+
50
+ - :class:`~anyplotlib.Event` now carries ``azimuth`` and ``elevation`` for 3-D
51
+ orbit events. The renderer had always emitted them alongside ``zoom``, but they
52
+ were dropped on the way to Python — and since a JS-side drag does not sync back
53
+ into ``Plot3D._state``, a handler had no way to react to an orbit at all. See
54
+ the new ``Star Globe Explorer`` gallery example, which links a celestial sphere
55
+ to a sky map through them.
56
+ - Added :meth:`~anyplotlib.Plot2D.add_brush_widget` for painting freehand
57
+ multi-class label strokes on a 2-D image with Shift-drag, leaving a bare drag to
58
+ pan as before.
59
+ - Added :meth:`~anyplotlib.Plot3D.set_texture` for wrapping an image around a 3-D
60
+ surface — a globe, a planet, or a star chart on the celestial sphere — with
61
+ optional diffuse shading and backface culling. ``Axes.plot_surface`` gained
62
+ ``texture=``, ``bounds=``, and ``gpu=`` to match. Textured surfaces render on
63
+ WebGPU when it is available (roughly 9k triangles at 54 ms/frame on Canvas2D
64
+ versus 0.4 ms on the GPU), falling back to Canvas2D silently otherwise.
65
+ ``set_axis_off()`` now also hides a 3-D panel's axis lines, labels, and ticks.
66
+
67
+
68
+ Bug Fixes
69
+ ---------
70
+
71
+ - Fixed WebGPU 3-D geometry being clipped at the corners of a cube-shaped
72
+ dataset. The clip-space depth scale let ``clip.z`` reach 1.09 for a point at
73
+ the far corner of the normalised bounds box, outside the ``[0, 1]`` range
74
+ WebGPU keeps, so the nearest corner of a dense :meth:`~anyplotlib.Axes.scatter3d`
75
+ cloud silently vanished at the default camera angles. Spherical geometry was
76
+ never affected.
77
+ - Fixed an inverted depth comparison in the WebGPU 3-D projection: a GPU-rendered
78
+ :meth:`~anyplotlib.Axes.scatter3d` cloud drew its far points on top of its near
79
+ ones wherever two points overlapped on screen. Voxel panels were unaffected
80
+ (they disable depth writes), and the Canvas2D path was always correct.
81
+ - Fixed the docs deployment racing itself on release. A push to ``main`` and its
82
+ release tag are different refs, so the ``docs-${{ github.ref }}`` concurrency
83
+ group put them in separate groups and both pushed to ``gh-pages`` at once; the
84
+ loser was rejected and its versioned directory never appeared, while
85
+ ``switcher.json`` still advertised the version. The deploy job now uses a
86
+ ref-independent group so deployments queue instead.
87
+
88
+
13
89
  0.5.0 (2026-07-26)
14
90
  ==================
15
91
 
@@ -0,0 +1,341 @@
1
+ """
2
+ Star Globe Explorer — a linked celestial sphere and sky map
3
+ ===========================================================
4
+
5
+ A celestial globe wired to a flat star map, with a sidereal clock.
6
+
7
+ * **Left** — the celestial sphere. Whatever you turn toward the camera is
8
+ your **zenith**: the point directly overhead.
9
+ * **Right** — the same sky as an equirectangular map, with the zenith marked
10
+ and the **horizon** drawn as the great circle 90° away from it. Everything
11
+ inside the horizon is above your head right now; everything outside is
12
+ below the ground.
13
+ * **Bottom** — a sidereal clock and a latitude control.
14
+
15
+ The coupling runs both ways. Orbit the globe and the clock and latitude
16
+ follow, because a zenith *is* a time and a place: the right ascension
17
+ overhead equals the local sidereal time, and its declination equals your
18
+ latitude. Drag either control and the globe turns to match.
19
+
20
+ Star positions are real (approximate J2000, rounded to about an arcminute),
21
+ carried over from :doc:`../PlotTypes/plot_3d_texture`; the Milky Way band is
22
+ placed by true galactic latitude.
23
+
24
+ .. note::
25
+ The globe is the sky seen from *outside*, the way a physical celestial
26
+ globe is built — so the constellations read mirrored compared with lying
27
+ on your back and looking up. The flat map is the conventional
28
+ inside-out view.
29
+ """
30
+ import numpy as np
31
+
32
+ import anyplotlib as apl
33
+
34
+ # ── Star catalogue ───────────────────────────────────────────────────────────
35
+ # name: (right ascension in hours, declination in degrees, visual magnitude).
36
+ # Swapping in a full catalogue (e.g. HYG, ~9k stars to naked-eye magnitude)
37
+ # is a change to this table alone — everything below is generic.
38
+ STARS = {
39
+ "Sirius": (6.752, -16.72, -1.46), "Canopus": (6.399, -52.70, -0.74),
40
+ "Rigil Kent": (14.660, -60.83, -0.27), "Arcturus": (14.261, 19.18, -0.05),
41
+ "Vega": (18.615, 38.78, 0.03), "Capella": (5.278, 46.00, 0.08),
42
+ "Rigel": (5.242, -8.20, 0.13), "Procyon": (7.655, 5.22, 0.34),
43
+ "Achernar": (1.629, -57.24, 0.46), "Betelgeuse": (5.919, 7.41, 0.50),
44
+ "Hadar": (14.064, -60.37, 0.61), "Altair": (19.846, 8.87, 0.77),
45
+ "Acrux": (12.443, -63.10, 0.77), "Aldebaran": (4.599, 16.51, 0.85),
46
+ "Spica": (13.420, -11.16, 1.04), "Antares": (16.490, -26.43, 1.09),
47
+ "Pollux": (7.755, 28.03, 1.14), "Fomalhaut": (22.961, -29.62, 1.16),
48
+ "Deneb": (20.690, 45.28, 1.25), "Mimosa": (12.795, -59.69, 1.25),
49
+ "Regulus": (10.140, 11.97, 1.35), "Adhara": (6.977, -28.97, 1.50),
50
+ "Castor": (7.577, 31.89, 1.58), "Shaula": (17.560, -37.10, 1.62),
51
+ "Gacrux": (12.520, -57.11, 1.63), "Bellatrix": (5.418, 6.35, 1.64),
52
+ "Elnath": (5.438, 28.61, 1.65), "Miaplacidus": (9.220, -69.72, 1.67),
53
+ "Alnilam": (5.604, -1.20, 1.69), "Alnair": (22.137, -46.96, 1.74),
54
+ "Alioth": (12.900, 55.96, 1.76), "Alnitak": (5.679, -1.94, 1.77),
55
+ "Dubhe": (11.062, 61.75, 1.79), "Mirfak": (3.405, 49.86, 1.79),
56
+ "Wezen": (7.140, -26.39, 1.83), "Kaus Aust.": (18.403, -34.38, 1.85),
57
+ "Alkaid": (13.792, 49.31, 1.85), "Avior": (8.375, -59.51, 1.86),
58
+ "Sargas": (17.622, -43.00, 1.86), "Menkalinan": (5.992, 44.95, 1.90),
59
+ "Atria": (16.811, -69.03, 1.91), "Alhena": (6.628, 16.40, 1.93),
60
+ "Peacock": (20.427, -56.74, 1.94), "Polaris": (2.530, 89.26, 1.98),
61
+ "Mirzam": (6.378, -17.96, 1.98), "Alphard": (9.460, -8.66, 2.00),
62
+ "Hamal": (2.120, 23.46, 2.00), "Diphda": (0.727, -17.99, 2.04),
63
+ "Nunki": (18.921, -26.30, 2.05), "Menkent": (14.112, -36.37, 2.06),
64
+ "Mirach": (1.162, 35.62, 2.06), "Alpheratz": (0.140, 29.09, 2.06),
65
+ "Rasalhague": (17.582, 12.56, 2.08), "Kochab": (14.845, 74.16, 2.08),
66
+ "Algieba": (10.333, 19.84, 2.08), "Saiph": (5.796, -9.67, 2.09),
67
+ "Tiaki": (22.711, -46.88, 2.11), "Algol": (3.136, 40.96, 2.12),
68
+ "Denebola": (11.818, 14.57, 2.14), "Muhlifain": (12.692, -48.96, 2.20),
69
+ "Aspidiske": (9.285, -59.28, 2.21), "Alphecca": (15.578, 26.71, 2.22),
70
+ "Sadr": (20.370, 40.26, 2.23), "Mizar": (13.399, 54.93, 2.23),
71
+ "Eltanin": (17.943, 51.49, 2.23), "Suhail": (9.133, -43.43, 2.23),
72
+ "Schedar": (0.675, 56.54, 2.24), "Mintaka": (5.533, -0.30, 2.25),
73
+ "Caph": (0.153, 59.15, 2.28), "Gamma Cas": (0.945, 60.72, 2.47),
74
+ "Merak": (11.030, 56.38, 2.37), "Enif": (21.737, 9.88, 2.38),
75
+ "Phecda": (11.897, 53.69, 2.44), "Ruchbah": (1.430, 60.24, 2.68),
76
+ "Delta Cru": (12.252, -58.75, 2.79), "Alcyone": (3.792, 24.11, 2.87),
77
+ "Megrez": (12.257, 57.03, 3.31), "Segin": (1.907, 63.67, 3.38),
78
+ "Meissa": (5.585, 9.93, 3.39),
79
+ }
80
+
81
+ FIGURES = [
82
+ ["Betelgeuse", "Bellatrix", "Mintaka", "Rigel"],
83
+ ["Mintaka", "Alnilam", "Alnitak", "Saiph"],
84
+ ["Betelgeuse", "Alnitak"], ["Bellatrix", "Meissa", "Betelgeuse"],
85
+ ["Dubhe", "Merak", "Phecda", "Megrez", "Dubhe"],
86
+ ["Megrez", "Alioth", "Mizar", "Alkaid"],
87
+ ["Segin", "Ruchbah", "Gamma Cas", "Schedar", "Caph"],
88
+ ["Acrux", "Gacrux"], ["Mimosa", "Delta Cru"],
89
+ ["Castor", "Pollux"], ["Vega", "Deneb", "Altair", "Vega"],
90
+ ]
91
+
92
+ TEX_W, TEX_H = 1440, 720
93
+ NGP_RA, NGP_DEC = np.radians(192.85948), np.radians(27.12825)
94
+ GC_RA, GC_DEC = np.radians(266.41684), np.radians(-29.00781)
95
+
96
+
97
+ def sky_pixel(ra_h, dec_deg):
98
+ """Catalogue coordinates → (column, row) in the equirectangular map."""
99
+ return (ra_h / 24.0 * (TEX_W - 1), (90.0 - dec_deg) / 180.0 * (TEX_H - 1))
100
+
101
+
102
+ def angsep(ra, dec, ra0, dec0):
103
+ return np.arccos(np.clip(
104
+ np.sin(dec) * np.sin(dec0) + np.cos(dec) * np.cos(dec0) * np.cos(ra - ra0),
105
+ -1.0, 1.0))
106
+
107
+
108
+ def _splat(img, x, y, radius, rgb, amp):
109
+ r = int(np.ceil(3 * radius))
110
+ xs = np.arange(int(round(x)) - r, int(round(x)) + r + 1)
111
+ ys = np.arange(int(round(y)) - r, int(round(y)) + r + 1)
112
+ ys = ys[(ys >= 0) & (ys < img.shape[0])]
113
+ if len(ys) == 0:
114
+ return
115
+ g = np.exp(-((ys[:, None] - y) ** 2 + (xs[None, :] - x) ** 2)
116
+ / (2 * radius ** 2)) * amp
117
+ img[np.ix_(ys, xs % img.shape[1])] += g[..., None] * np.asarray(rgb)
118
+
119
+
120
+ def _stroke(img, p0, p1, rgb, amp):
121
+ (x0, y0), (x1, y1) = p0, p1
122
+ if abs(x1 - x0) > img.shape[1] / 2: # take the short way in RA
123
+ x1 += img.shape[1] if x0 > x1 else -img.shape[1]
124
+ n = int(max(abs(x1 - x0), abs(y1 - y0))) * 2 + 2
125
+ xs = np.linspace(x0, x1, n).astype(int) % img.shape[1]
126
+ ys = np.clip(np.linspace(y0, y1, n), 0, img.shape[0] - 1).astype(int)
127
+ img[ys, xs] = np.maximum(img[ys, xs], np.asarray(rgb) * amp)
128
+
129
+
130
+ # ── Build the sky image ──────────────────────────────────────────────────────
131
+ rng = np.random.default_rng(20260730)
132
+ ra_grid = np.linspace(0, 2 * np.pi, TEX_W)[None, :]
133
+ dec_grid = np.radians(np.linspace(90, -90, TEX_H))[:, None]
134
+
135
+ sky = np.zeros((TEX_H, TEX_W, 3), np.float32)
136
+ sky[:] = np.array([0.020, 0.028, 0.062], np.float32)
137
+ sky += (np.abs(np.sin(dec_grid)) ** 3 * 0.02)[..., None]
138
+
139
+ gal_lat = np.arcsin(np.sin(dec_grid) * np.sin(NGP_DEC)
140
+ + np.cos(dec_grid) * np.cos(NGP_DEC)
141
+ * np.cos(ra_grid - NGP_RA))
142
+ band = np.exp(-(gal_lat / np.radians(11.0)) ** 2)
143
+ band = band * (0.45 + 0.85 * np.exp(
144
+ -(angsep(ra_grid, dec_grid, GC_RA, GC_DEC) / np.radians(55.0)) ** 2))
145
+ dust, amp = np.zeros((TEX_H, TEX_W), np.float32), 1.0
146
+ for k in range(1, 6):
147
+ dust += amp * (np.sin(3 * k * ra_grid + rng.uniform(0, 2 * np.pi))
148
+ * np.sin(2.5 * k * (dec_grid + np.pi / 2)
149
+ + rng.uniform(0, 2 * np.pi)))
150
+ amp *= 0.55
151
+ band = band * np.clip(0.72 + 0.28 * dust, 0.25, 1.0)
152
+ sky += band[..., None] * np.array([0.46, 0.44, 0.55], np.float32)
153
+
154
+ n_faint = 24_000
155
+ f_dec = np.degrees(np.arcsin(rng.uniform(-1, 1, n_faint)))
156
+ fx = rng.uniform(0, TEX_W, n_faint).astype(int) % TEX_W
157
+ fy = ((90.0 - f_dec) / 180.0 * (TEX_H - 1)).astype(int)
158
+ keep = rng.random(n_faint) < (0.22 + 0.78 * band[fy, fx])
159
+ fx, fy = fx[keep], fy[keep]
160
+ sky[fy, fx] += rng.uniform(0.25, 0.95, (len(fx), 1)).astype(np.float32)
161
+
162
+ for chain in FIGURES:
163
+ for a, b in zip(chain, chain[1:]):
164
+ _stroke(sky, sky_pixel(*STARS[a][:2]), sky_pixel(*STARS[b][:2]),
165
+ (0.30, 0.58, 0.85), 1.0)
166
+
167
+ for name, (ra_h, dec_deg, mag) in STARS.items():
168
+ x, y = sky_pixel(ra_h, dec_deg)
169
+ flux = 10 ** (-0.4 * mag)
170
+ _splat(sky, x, y, 1.3 + 2.0 * flux ** 0.35, (1.0, 0.97, 0.90),
171
+ 0.85 + 2.2 * flux ** 0.30)
172
+
173
+ sky_u8 = (np.clip(sky, 0, 1) * 255).astype(np.uint8)
174
+
175
+ # ── Spherical geometry ───────────────────────────────────────────────────────
176
+ NDEC, NRA = 49, 97
177
+ _ra = np.linspace(0, 2 * np.pi, NRA)
178
+ _dec = np.linspace(np.pi / 2, -np.pi / 2, NDEC)
179
+ RA, DEC = np.meshgrid(_ra, _dec)
180
+ X, Y, Z = np.cos(DEC) * np.cos(RA), np.cos(DEC) * np.sin(RA), np.sin(DEC)
181
+
182
+
183
+ def camera_to_zenith(azimuth, elevation):
184
+ """Globe camera angles → the sky point facing you, as (RA hours, Dec°).
185
+
186
+ The renderer's turntable faces the unit vector with ``el = asin(vz)`` and
187
+ ``az = atan2(vx, -vy)``. Feeding that through this example's
188
+ ``x = cos δ cos α`` parameterisation collapses to ``α = azimuth − 90°``.
189
+ """
190
+ return ((azimuth - 90.0) % 360.0) / 15.0, elevation
191
+
192
+
193
+ def zenith_to_camera(lst_hours, latitude):
194
+ """The inverse: put (LST, latitude) overhead."""
195
+ return (lst_hours * 15.0 + 90.0) % 360.0, latitude
196
+
197
+
198
+ def horizon_segments(lst_hours, latitude, n=240):
199
+ """Map-space polyline for the horizon: the circle 90° from the zenith.
200
+
201
+ Built as an orthonormal frame about the zenith rather than by solving the
202
+ altitude equation, so it stays well conditioned at the poles. Returned as
203
+ ``(N, 2, 2)`` segments, split wherever the curve wraps in right ascension
204
+ so nothing is drawn straight across the map.
205
+ """
206
+ a0, d0 = np.radians(lst_hours * 15.0), np.radians(latitude)
207
+ n_hat = np.array([np.cos(d0) * np.cos(a0), np.cos(d0) * np.sin(a0), np.sin(d0)])
208
+ # Any vector not parallel to the zenith gives a usable first basis vector.
209
+ tmp = np.array([0.0, 0.0, 1.0]) if abs(n_hat[2]) < 0.9 else np.array([1.0, 0.0, 0.0])
210
+ e1 = np.cross(n_hat, tmp)
211
+ e1 /= np.linalg.norm(e1)
212
+ e2 = np.cross(n_hat, e1)
213
+
214
+ t = np.linspace(0, 2 * np.pi, n)
215
+ pts = np.cos(t)[:, None] * e1 + np.sin(t)[:, None] * e2
216
+ ra_h = (np.degrees(np.arctan2(pts[:, 1], pts[:, 0])) % 360.0) / 15.0
217
+ dec_d = np.degrees(np.arcsin(np.clip(pts[:, 2], -1, 1)))
218
+ px = np.column_stack(sky_pixel(ra_h, dec_d))
219
+
220
+ segs = np.stack([px[:-1], px[1:]], axis=1) # (N-1, 2, 2)
221
+ keep = np.abs(segs[:, 0, 0] - segs[:, 1, 0]) < TEX_W / 2 # drop wrap jumps
222
+ return segs[keep]
223
+
224
+
225
+ def visible_stars(lst_hours, latitude):
226
+ """Map pixels of the catalogue stars currently above the horizon."""
227
+ a0, d0 = np.radians(lst_hours * 15.0), np.radians(latitude)
228
+ out = []
229
+ for ra_h, dec_deg, _ in STARS.values():
230
+ alt = np.pi / 2 - angsep(np.radians(ra_h * 15.0), np.radians(dec_deg), a0, d0)
231
+ if alt > 0:
232
+ out.append(sky_pixel(ra_h, dec_deg))
233
+ return np.asarray(out) if out else np.zeros((0, 2))
234
+
235
+
236
+ # ── Figure ───────────────────────────────────────────────────────────────────
237
+ LST0, LAT0 = 5.5, 35.0 # Orion overhead, mid-northern latitude
238
+
239
+ fig = apl.Figure(figsize=(1080, 620))
240
+ gs = apl.GridSpec(3, 2, width_ratios=[1, 1.35], height_ratios=[6, 1, 1])
241
+
242
+ az0, el0 = zenith_to_camera(LST0, LAT0)
243
+ globe = fig.add_subplot(gs[0, 0]).plot_surface(
244
+ X, Y, Z, bounds=((-1, 1),) * 3, azimuth=az0, elevation=el0)
245
+ globe.set_axis_off()
246
+ globe.set_texture(sky_u8, cull_backfaces=True)
247
+ globe.set_title("celestial sphere — drag to move your zenith")
248
+
249
+ sky_map = fig.add_subplot(gs[0, 1]).imshow(sky_u8)
250
+ sky_map.set_title("sky map — inside the curve is above the horizon")
251
+
252
+ clock = fig.add_subplot(gs[1, :]).plot(
253
+ np.zeros(2), axes=[np.array([0.0, 24.0])], color="#37474f", linewidth=1)
254
+ clock.set_xlabel("local sidereal time (h)")
255
+
256
+ lat_ax = fig.add_subplot(gs[2, :]).plot(
257
+ np.zeros(2), axes=[np.array([-90.0, 90.0])], color="#37474f", linewidth=1)
258
+ lat_ax.set_xlabel("observer latitude ($\\degree$)")
259
+
260
+ # ── Overlays, redrawn whenever the zenith moves ──────────────────────────────
261
+ horizon = sky_map.add_lines(horizon_segments(LST0, LAT0), name="horizon",
262
+ edgecolors="#4dd0e1", linewidths=2.0)
263
+ # size_units="px" keeps the markers a fixed screen size as the map is zoomed.
264
+ zenith = sky_map.add_points(np.array([sky_pixel(LST0, LAT0)]), name="zenith",
265
+ sizes=9, color="#ffd54f", facecolors="#ffd54f",
266
+ alpha=1.0, size_units="px")
267
+ up_now = sky_map.add_points(visible_stars(LST0, LAT0), name="up",
268
+ sizes=4, color="#4dd0e1", facecolors="#4dd0e1",
269
+ alpha=0.55, size_units="px")
270
+
271
+ lst_line = clock.add_vline_widget(LST0, color="#ffd54f")
272
+ lat_line = lat_ax.add_vline_widget(LAT0, color="#ffd54f")
273
+
274
+ _syncing = False # guards the two-way coupling against feedback
275
+
276
+
277
+ def refresh(lst_hours, latitude, *, move_globe, move_sliders):
278
+ """Push a new zenith everywhere. One writer, three followers."""
279
+ global _syncing
280
+ if _syncing:
281
+ return
282
+ _syncing = True
283
+ try:
284
+ with fig.batch():
285
+ horizon.set(segments=horizon_segments(lst_hours, latitude))
286
+ zenith.set(offsets=np.array([sky_pixel(lst_hours, latitude)]))
287
+ up_now.set(offsets=visible_stars(lst_hours, latitude))
288
+ sky_map.set_title(
289
+ f"sky map — LST {lst_hours:04.1f} h, latitude {latitude:+.0f}°")
290
+ if move_globe:
291
+ az, el = zenith_to_camera(lst_hours, latitude)
292
+ globe.set_view(azimuth=az, elevation=el)
293
+ if move_sliders:
294
+ lst_line.set(x=lst_hours)
295
+ lat_line.set(x=latitude)
296
+ finally:
297
+ _syncing = False
298
+
299
+
300
+ @globe.add_event_handler("pointer_move")
301
+ def _on_orbit(event):
302
+ """Orbiting the globe IS choosing a time and a place."""
303
+ if event.azimuth is None:
304
+ return
305
+ lst_hours, latitude = camera_to_zenith(event.azimuth, event.elevation)
306
+ refresh(lst_hours, latitude, move_globe=False, move_sliders=True)
307
+
308
+
309
+ @lst_line.add_event_handler("pointer_move")
310
+ def _on_clock(event):
311
+ refresh(lst_line.x % 24.0, lat_line.x, move_globe=True, move_sliders=False)
312
+
313
+
314
+ @lat_line.add_event_handler("pointer_move")
315
+ def _on_latitude(event):
316
+ refresh(lst_line.x % 24.0, np.clip(lat_line.x, -89, 89),
317
+ move_globe=True, move_sliders=False)
318
+
319
+
320
+ refresh(LST0, LAT0, move_globe=False, move_sliders=False)
321
+
322
+ fig
323
+
324
+ # %%
325
+ # Reading it
326
+ # ----------
327
+ # The cyan circle is the horizon and the gold dot is the zenith. Stars inside
328
+ # the circle are above the ground and are marked in cyan; the rest have set.
329
+ #
330
+ # Two things fall out of the geometry that are worth watching for:
331
+ #
332
+ # * Push the latitude to :math:`+90°` and the horizon collapses onto the
333
+ # celestial equator — at the north pole the sky never rises or sets, it
334
+ # just wheels around Polaris.
335
+ # * Run the clock through 24 h at a mid latitude and the horizon sweeps the
336
+ # whole sky except two caps around the poles. Those caps are the
337
+ # circumpolar stars, the ones that never set from where you are standing.
338
+
339
+ refresh(2.0, 89.0, move_globe=True, move_sliders=True)
340
+
341
+ fig