anyplotlib 0.7.3__tar.gz → 0.8.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 (280) hide show
  1. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/AGENTS.md +1 -1
  2. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/CHANGELOG.rst +49 -0
  3. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/PKG-INFO +1 -1
  4. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/FIGURE_ESM.md +30 -29
  5. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/figure_esm.js +24 -4
  6. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/plot2d/_plot2d.py +111 -6
  7. anyplotlib-0.8.0/anyplotlib/tests/test_plot2d/test_display_window.py +135 -0
  8. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_tiled_imshow.py +205 -7
  9. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/_root/index.html +3 -3
  10. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/_root/switcher.json +5 -0
  11. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/conf.py +1 -1
  12. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/pyproject.toml +1 -1
  13. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/.github/workflows/ci.yml +0 -0
  14. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/.github/workflows/docs.yml +0 -0
  15. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/.github/workflows/prepare_release.yml +0 -0
  16. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/.github/workflows/release.yml +0 -0
  17. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/.github/workflows/tests.yml +0 -0
  18. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/.gitignore +0 -0
  19. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Benchmarks/README.rst +0 -0
  20. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Benchmarks/plot_benchmark_comparison.py +0 -0
  21. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/README.rst +0 -0
  22. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_3d_spectral_viewer.py +0 -0
  23. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_eels_explorer.py +0 -0
  24. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_figure_annotations.py +0 -0
  25. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_interactive_fft.py +0 -0
  26. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_interactive_fitting.py +0 -0
  27. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_ipf_density_map.py +0 -0
  28. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_ipf_explorer.py +0 -0
  29. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_key_bindings.py +0 -0
  30. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_particle_picker.py +0 -0
  31. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_point_widget.py +0 -0
  32. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_segment_by_contrast.py +0 -0
  33. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_segment_by_contrast_advanced.py +0 -0
  34. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_spectra_roi_inspector.py +0 -0
  35. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_star_globe_explorer.py +0 -0
  36. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_threshold_explorer.py +0 -0
  37. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Interactive/plot_voxel_grain_explorer.py +0 -0
  38. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Markers/README.rst +0 -0
  39. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Markers/plot_arrows.py +0 -0
  40. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Markers/plot_circles.py +0 -0
  41. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Markers/plot_ellipses.py +0 -0
  42. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Markers/plot_horizontal_lines.py +0 -0
  43. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Markers/plot_line_segments.py +0 -0
  44. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Markers/plot_points.py +0 -0
  45. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Markers/plot_polygons.py +0 -0
  46. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Markers/plot_rectangles.py +0 -0
  47. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Markers/plot_squares.py +0 -0
  48. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Markers/plot_texts.py +0 -0
  49. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Markers/plot_transform_markers.py +0 -0
  50. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Markers/plot_vertical_lines.py +0 -0
  51. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/README.rst +0 -0
  52. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_3d.py +0 -0
  53. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_3d_texture.py +0 -0
  54. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_bar.py +0 -0
  55. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_gpu_voxels.py +0 -0
  56. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_gridspec_custom.py +0 -0
  57. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_image2d.py +0 -0
  58. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_inset.py +0 -0
  59. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_key_overlays.py +0 -0
  60. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_label_formatting.py +0 -0
  61. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_line_styles.py +0 -0
  62. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_pcolormesh.py +0 -0
  63. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_spectra1d.py +0 -0
  64. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_step_and_log.py +0 -0
  65. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_tile_backend.py +0 -0
  66. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/PlotTypes/plot_twinx.py +0 -0
  67. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/README.rst +0 -0
  68. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Widgets/README.rst +0 -0
  69. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Widgets/plot_widget1d_hline.py +0 -0
  70. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Widgets/plot_widget1d_point.py +0 -0
  71. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Widgets/plot_widget1d_range.py +0 -0
  72. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Widgets/plot_widget1d_vline.py +0 -0
  73. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Widgets/plot_widget2d_annular.py +0 -0
  74. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Widgets/plot_widget2d_arrow.py +0 -0
  75. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Widgets/plot_widget2d_circle.py +0 -0
  76. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Widgets/plot_widget2d_crosshair.py +0 -0
  77. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Widgets/plot_widget2d_label.py +0 -0
  78. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Widgets/plot_widget2d_polygon.py +0 -0
  79. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Widgets/plot_widget2d_rectangle.py +0 -0
  80. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Examples/Widgets/plot_widget3d_plane.py +0 -0
  81. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/LICENSE +0 -0
  82. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/Makefile +0 -0
  83. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/README.md +0 -0
  84. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/__init__.py +0 -0
  85. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/_base_plot.py +0 -0
  86. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/_binary_frame.py +0 -0
  87. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/_electron.py +0 -0
  88. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/_repr_utils.py +0 -0
  89. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/_utils.py +0 -0
  90. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/axes/__init__.py +0 -0
  91. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/axes/_axes.py +0 -0
  92. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/axes/_inset_axes.py +0 -0
  93. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/callbacks.py +0 -0
  94. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/conftest.py +0 -0
  95. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/embed.py +0 -0
  96. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/figure/__init__.py +0 -0
  97. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/figure/_figure.py +0 -0
  98. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/figure/_gridspec.py +0 -0
  99. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/figure/_subplots.py +0 -0
  100. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/keys.py +0 -0
  101. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/markers.py +0 -0
  102. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/plot1d/__init__.py +0 -0
  103. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/plot1d/_plot1d.py +0 -0
  104. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/plot1d/_plotbar.py +0 -0
  105. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/plot2d/__init__.py +0 -0
  106. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/plot2d/_layer.py +0 -0
  107. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/plot2d/_plotmesh.py +0 -0
  108. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/plot2d/_tile_backend.py +0 -0
  109. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/plot3d/__init__.py +0 -0
  110. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/plot3d/_plot3d.py +0 -0
  111. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/plotxy/__init__.py +0 -0
  112. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/plotxy/_plotxy.py +0 -0
  113. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/sphinx_anywidget/__init__.py +0 -0
  114. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/sphinx_anywidget/_directive.py +0 -0
  115. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/sphinx_anywidget/_repr_utils.py +0 -0
  116. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/sphinx_anywidget/_scraper.py +0 -0
  117. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/sphinx_anywidget/_wheel_builder.py +0 -0
  118. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/sphinx_anywidget/static/anywidget_bridge.js +0 -0
  119. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/sphinx_anywidget/static/anywidget_overlay.css +0 -0
  120. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/sphinx_anywidget/tests/__init__.py +0 -0
  121. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/sphinx_anywidget/tests/conftest.py +0 -0
  122. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/sphinx_anywidget/tests/test_directive.py +0 -0
  123. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/sphinx_anywidget/tests/test_init.py +0 -0
  124. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/sphinx_anywidget/tests/test_repr_utils.py +0 -0
  125. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/sphinx_anywidget/tests/test_scraper.py +0 -0
  126. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/sphinx_anywidget/tests/test_wheel_builder.py +0 -0
  127. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/__init__.py +0 -0
  128. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/_gpu_clim_check.cjs +0 -0
  129. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/_png_utils.py +0 -0
  130. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/bar_basic.png +0 -0
  131. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/gridspec_3col_equal_spectra.png +0 -0
  132. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/gridspec_asymmetric_width_ratios.png +0 -0
  133. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/gridspec_height_ratio_image_histogram.png +0 -0
  134. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/gridspec_image_two_spectra.png +0 -0
  135. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/gridspec_side_by_side_1d.png +0 -0
  136. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/gridspec_spanning_top_two_bottom.png +0 -0
  137. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/imshow_axis_off.png +0 -0
  138. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/imshow_checkerboard.png +0 -0
  139. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/imshow_gradient.png +0 -0
  140. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/imshow_labels.png +0 -0
  141. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/imshow_viridis.png +0 -0
  142. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/inset_1d.png +0 -0
  143. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/inset_maximized.png +0 -0
  144. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/inset_minimized.png +0 -0
  145. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/inset_normal_2d.png +0 -0
  146. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/inset_stacked.png +0 -0
  147. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/inset_stacked_one_minimized.png +0 -0
  148. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/pcolormesh_uniform.png +0 -0
  149. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/plot1d_all_linestyles.png +0 -0
  150. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/plot1d_alpha.png +0 -0
  151. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/plot1d_axis_off.png +0 -0
  152. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/plot1d_dashed.png +0 -0
  153. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/plot1d_marker_symbols.png +0 -0
  154. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/plot1d_markers.png +0 -0
  155. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/plot1d_multi.png +0 -0
  156. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/plot1d_sine.png +0 -0
  157. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/plot1d_title.png +0 -0
  158. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/plot1d_twinx.png +0 -0
  159. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/plot3d_surface.png +0 -0
  160. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/baselines/subplots_2x1.png +0 -0
  161. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/conftest.py +0 -0
  162. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_benchmarks/__init__.py +0 -0
  163. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_benchmarks/baselines.json +0 -0
  164. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_benchmarks/benchmarks/baselines.json +0 -0
  165. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_benchmarks/test_benchmarks.py +0 -0
  166. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_benchmarks/test_benchmarks_py.py +0 -0
  167. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_documentation/__init__.py +0 -0
  168. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_documentation/test_bridge.py +0 -0
  169. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_documentation/test_push_hook.py +0 -0
  170. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_documentation/test_scraper.py +0 -0
  171. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_electron/__init__.py +0 -0
  172. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_electron/test_binary_frame.py +0 -0
  173. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_embed/__init__.py +0 -0
  174. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_embed/test_embed_api.py +0 -0
  175. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_embed/test_embed_mount.py +0 -0
  176. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_embed/test_export_pixel_tokens.py +0 -0
  177. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_embed/test_export_png.py +0 -0
  178. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_embed/test_export_widget_sync.py +0 -0
  179. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_examples/__init__.py +0 -0
  180. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_examples/test_interactive_examples.py +0 -0
  181. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/__init__.py +0 -0
  182. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/_event_test_utils.py +0 -0
  183. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_blit_audit.py +0 -0
  184. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_callbacks_playwright.py +0 -0
  185. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_callbacks_unit.py +0 -0
  186. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_circle_lock_center.py +0 -0
  187. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_edit_chrome.py +0 -0
  188. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_edit_chrome_playwright.py +0 -0
  189. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_event_pause_hold.py +0 -0
  190. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_event_plots.py +0 -0
  191. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_event_settled.py +0 -0
  192. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_events_regression.py +0 -0
  193. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_orbit_direction.py +0 -0
  194. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_pointer_down_1d.py +0 -0
  195. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_range_orientation_snap.py +0 -0
  196. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_star_globe_linking.py +0 -0
  197. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_tile_parity_playwright.py +0 -0
  198. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_title.py +0 -0
  199. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_touch.py +0 -0
  200. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_widget_brush.py +0 -0
  201. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_widget_max_extent.py +0 -0
  202. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_widget_set_notify.py +0 -0
  203. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_widgets.py +0 -0
  204. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_interactive/test_widgets_line_2d.py +0 -0
  205. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_keys/test_keys.py +0 -0
  206. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_labels/__init__.py +0 -0
  207. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_labels/test_label_api.py +0 -0
  208. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_labels/test_label_rendering.py +0 -0
  209. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_labels/test_no_clipping.py +0 -0
  210. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_labels/test_ylabel_clearance.py +0 -0
  211. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_layouts/__init__.py +0 -0
  212. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_layouts/test_batch.py +0 -0
  213. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_layouts/test_coord_conversion.py +0 -0
  214. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_layouts/test_geom_channel.py +0 -0
  215. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_layouts/test_gridspec.py +0 -0
  216. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_layouts/test_inset.py +0 -0
  217. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_layouts/test_inset_callout.py +0 -0
  218. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_layouts/test_interaction.py +0 -0
  219. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_layouts/test_visual.py +0 -0
  220. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_markers/__init__.py +0 -0
  221. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_markers/test_marker_transforms.py +0 -0
  222. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_markers/test_markers.py +0 -0
  223. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_markers/test_per_marker_colors.py +0 -0
  224. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_markers/test_size_units.py +0 -0
  225. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot1d/__init__.py +0 -0
  226. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot1d/test_line_none_rendering.py +0 -0
  227. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot1d/test_plot1d.py +0 -0
  228. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot1d/test_plotbar.py +0 -0
  229. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/__init__.py +0 -0
  230. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_colorbar_gap_scalebar.py +0 -0
  231. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_detail_tile.py +0 -0
  232. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_first_paint_race.py +0 -0
  233. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_gpu_image.py +0 -0
  234. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_gpu_parity_playwright.py +0 -0
  235. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_imshow.py +0 -0
  236. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_imshow_rgb.py +0 -0
  237. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_layers.py +0 -0
  238. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_layers_playwright.py +0 -0
  239. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_pcolormesh.py +0 -0
  240. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_plot2d_api.py +0 -0
  241. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_rapid_frame_update.py +0 -0
  242. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_tile_backend.py +0 -0
  243. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_tile_diagnostics.py +0 -0
  244. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot2d/test_zoom_view_write.py +0 -0
  245. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot3d/__init__.py +0 -0
  246. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot3d/test_colors_highlight.py +0 -0
  247. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot3d/test_gpu_depth.py +0 -0
  248. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot3d/test_gpu_fallback.py +0 -0
  249. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot3d/test_gpu_hidden_reveal.py +0 -0
  250. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot3d/test_plot3d.py +0 -0
  251. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot3d/test_texture.py +0 -0
  252. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plot3d/test_voxels_planes.py +0 -0
  253. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/tests/test_plotxy/test_plotxy.py +0 -0
  254. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/widgets/__init__.py +0 -0
  255. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/widgets/_base.py +0 -0
  256. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/widgets/_widgets1d.py +0 -0
  257. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/widgets/_widgets2d.py +0 -0
  258. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/anyplotlib/widgets/_widgets3d.py +0 -0
  259. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/_sg_html_scraper.py +0 -0
  260. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/_static/anyplotlib.svg +0 -0
  261. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/_static/custom.css +0 -0
  262. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/_static/pyodide_bridge.js +0 -0
  263. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/_templates/autosummary/class.rst +0 -0
  264. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/api/callbacks.rst +0 -0
  265. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/api/figure.rst +0 -0
  266. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/api/figure_plots.rst +0 -0
  267. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/api/index.rst +0 -0
  268. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/api/keys.rst +0 -0
  269. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/api/markers.rst +0 -0
  270. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/api/widgets.rst +0 -0
  271. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/benchmarking.rst +0 -0
  272. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/dev/index.rst +0 -0
  273. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/embedding.rst +0 -0
  274. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/events.rst +0 -0
  275. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/getting_started.rst +0 -0
  276. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/docs/index.rst +0 -0
  277. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/make.bat +0 -0
  278. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/upcoming_changes/.gitkeep +0 -0
  279. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/upcoming_changes/README.rst +0 -0
  280. {anyplotlib-0.7.3 → anyplotlib-0.8.0}/uv.lock +0 -0
@@ -115,7 +115,7 @@ grep -nE '^\s*(function|const|let) [A-Za-z_]' anyplotlib/figure_esm.js
115
115
  ```
116
116
 
117
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.
118
+ the 2-D function table). Both were last verified at 10,119 lines.
119
119
 
120
120
  Changelog entries: add a fragment file to `upcoming_changes/` (e.g.
121
121
  `123.new_feature.rst`) — towncrier assembles `CHANGELOG.rst` at release time.
@@ -10,6 +10,55 @@ Fragment files in ``upcoming_changes/`` are assembled into this file by
10
10
 
11
11
  .. towncrier release notes start
12
12
 
13
+ 0.8.0 (2026-08-25)
14
+ ==================
15
+
16
+ API and Behaviour Changes
17
+ -------------------------
18
+
19
+ - In tile mode, ``set_data``/``update_tile_source`` now re-derive the quantisation band
20
+ ``raw_min``/``raw_max`` from the incoming frame when the current band is unset or
21
+ degenerate (it previously stayed as first derived, even from a flat placeholder). A
22
+ band that is already valid is still never re-derived, so a contrast change keeps
23
+ re-windowing in the LUT with no pixel re-encode. One visible consequence: a tiled plot
24
+ born on a placeholder now quantises subsequent frames over the frame's own range
25
+ rather than the display window, matching what ``imshow`` of a large frame has always
26
+ done — so its wire bytes can differ by a rounding step from the equivalent untiled
27
+ plot, while the displayed image is unchanged. (`#60 <https://github.com/CSSFrancis/anyplotlib/pull/60>`_)
28
+
29
+
30
+ New Features
31
+ ------------
32
+
33
+ - Added :meth:`~anyplotlib.plot2d.Plot2D.set_tile_band` to pin the fixed quantisation
34
+ band tile bytes are encoded over, for a consumer that already knows the honest range
35
+ (a camera's bit depth, a detector's saturation point) or whose source starts flat and
36
+ so has no range to auto-derive. It re-samples the overview and any active detail tile
37
+ over the new band in a single push, replacing the practice of reaching into
38
+ ``plot._state["raw_min"/"raw_max"]`` and calling ``update_tile_source()`` by hand. (`#60 <https://github.com/CSSFrancis/anyplotlib/pull/60>`_)
39
+ - Added :meth:`~anyplotlib.plot2d.Plot2D.set_display_window`, which moves the
40
+ contrast window without re-quantising the pixels — the non-destructive
41
+ counterpart to :meth:`~anyplotlib.plot2d.Plot2D.set_clim`, and what lets a
42
+ saved page be re-windowed with no Python behind it. (`#61 <https://github.com/CSSFrancis/anyplotlib/pull/61>`_)
43
+
44
+
45
+ Bug Fixes
46
+ ---------
47
+
48
+ - Fixed a tiled 2-D image rendering solid black when tile mode was entered on a flat
49
+ placeholder frame (e.g. ``imshow`` of zeros before real data exists). The fixed
50
+ quantisation band ``raw_min``/``raw_max`` was derived from that placeholder — a
51
+ degenerate ``(0, 0)`` — and no later ``set_data`` re-derived it, leaving the two ends
52
+ of the protocol disagreeing about what it meant: the Python encoder treats a
53
+ degenerate band as unset and quantises over the display window, while the renderer
54
+ honoured ``(0, 0)`` and mapped every code below the display floor. The panel rendered
55
+ black on the WebGPU and Canvas2D paths alike, beside perfectly healthy stats and
56
+ histograms, with no warning. ``set_data`` and ``update_tile_source`` now re-derive a
57
+ degenerate band from the incoming frame, and the renderer falls back to the display
58
+ window for a degenerate band in tile mode — in the image LUT and in the colorbar
59
+ tick placement, which read the band the same way. (`#60 <https://github.com/CSSFrancis/anyplotlib/pull/60>`_)
60
+
61
+
13
62
  0.7.3 (2026-08-13)
14
63
  ==================
15
64
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: anyplotlib
3
- Version: 0.7.3
3
+ Version: 0.8.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
@@ -59,20 +59,20 @@ Rule 5 – Text never clips. Optional gutters earn real layout space:
59
59
  | `_createInsetDOM` / `_applyAllInsetStates` | 1129 / 1512 |
60
60
  | `_resizePanelDOM` | 2225 |
61
61
  | **2D drawing**: `_imgFitRect` | 2384 |
62
- | `draw2d` | 2692 |
63
- | `drawScaleBar2d` / `drawColorbar2d` | 2887 / 3125 |
64
- | **Floating keys**: `_keyEnsure` / `_keyRect` / `drawKeys` | 2986 / 3009 / 3022 |
65
- | `_drawAxes2d` (ticks, labels, title) | 3180 |
66
- | `drawOverlay2d` / `drawMarkers2d` | 3333 / 3497 |
67
- | **Image layers**: `_layerBytes` / `_layerBitmap` / `_drawLayers2d` | 2512 / 2536 / 2597 |
62
+ | `draw2d` | 2713 |
63
+ | `drawScaleBar2d` / `drawColorbar2d` | 2908 / 3188 |
64
+ | **Floating keys**: `_keyEnsure` / `_keyRect` / `drawKeys` | 3007 / 3030 / 3043 |
65
+ | `_drawAxes2d` (ticks, labels, title) | 3242 |
66
+ | `drawOverlay2d` / `drawMarkers2d` | 3395 / 3559 |
67
+ | **Image layers**: `_layerBytes` / `_layerBitmap` / `_drawLayers2d` | 2533 / 2557 / 2618 |
68
68
  | Binary-bytes splice: `_spliceBinaryBytes` / `_registerBinaryPixelListeners` | 730 / 761 |
69
- | **3D drawing**: `draw3d` | 5236 |
70
- | Event emission `_emitEvent` | 6073 |
71
- | 3D event handlers `_attachEvents3d` | 6125 |
72
- | **1D drawing**: `draw1d` | 6346 |
73
- | `_drawLine` (1D series + markers) | 6499 |
74
- | `drawOverlay1d` / `drawMarkers1d` | 6792 / 6876 |
75
- | Marker hit-test `_markerHitTest2d` | 7144 |
69
+ | **3D drawing**: `draw3d` | 5298 |
70
+ | Event emission `_emitEvent` | 6135 |
71
+ | 3D event handlers `_attachEvents3d` | 6187 |
72
+ | **1D drawing**: `draw1d` | 6408 |
73
+ | `_drawLine` (1D series + markers) | 6561 |
74
+ | `drawOverlay1d` / `drawMarkers1d` | 6854 / 6938 |
75
+ | Marker hit-test `_markerHitTest2d` | 7206 |
76
76
 
77
77
  > **`raster` marker (1D/PlotXY)** — `drawMarkers1d` has a `type==='raster'`
78
78
  > branch that blits a single RGBA image across data-coord `extent` (the fast
@@ -81,16 +81,16 @@ Rule 5 – Text never clips. Optional gutters earn real layout space:
81
81
  > redraws never re-transmit them; the decoded `OffscreenCanvas` is cached on
82
82
  > the marker set (`ms._rasterBmp`/`_rasterKey`). The shared `clip_path` block
83
83
  > clips it to a curved sector.
84
- | Panel event dispatch `_attachPanelEvents` | 7401 |
85
- | 2D events `_attachEvents2d` | 7443 |
86
- | 1D events `_attachEvents1d` | 7827 |
87
- | 2D widget drag `_ovHitTest2d` / `_doDrag2d` | 8099 / 8372 |
88
- | **Brush strokes**: `_brushLiveBegin` / `_brushCommit` / `_brushErase` / `_brushPaintAt` | 8285 / 8299 / 8328 / 8363 |
89
- | 1D widget drag `_canvasXToFrac1d` … / snapping `_snapVal` | 8495 / 8568 |
90
- | Shared-axis propagation `_getShareGroups` | 8639 |
91
- | Figure resize `_applyFigResizeDOM` | 8703 |
92
- | **Bar chart**: `_barGeom` / `drawBar` / `_attachEventsBar` | 8894 / 8957 / 9333 |
93
- | Generic redraw `_redrawPanel` | 9523 |
84
+ | Panel event dispatch `_attachPanelEvents` | 7463 |
85
+ | 2D events `_attachEvents2d` | 7505 |
86
+ | 1D events `_attachEvents1d` | 7889 |
87
+ | 2D widget drag `_ovHitTest2d` / `_doDrag2d` | 8161 / 8440 |
88
+ | **Brush strokes**: `_brushLiveBegin` / `_brushCommit` / `_brushErase` / `_brushPaintAt` | 8353 / 8367 / 8396 / 8431 |
89
+ | 1D widget drag `_canvasXToFrac1d` … / snapping `_snapVal` | 8565 / 8638 |
90
+ | Shared-axis propagation `_getShareGroups` | 8709 |
91
+ | Figure resize `_applyFigResizeDOM` | 8773 |
92
+ | **Bar chart**: `_barGeom` / `drawBar` / `_attachEventsBar` | 8964 / 9027 / 9403 |
93
+ | Generic redraw `_redrawPanel` | 9593 |
94
94
 
95
95
  > **`brush` widget (2-D)** — the one widget whose drag is *modal*, and the one
96
96
  > that must NOT write the model per tick. `_ovHitTest2d` takes an extra `mods`
@@ -269,12 +269,13 @@ st.colorbar_label_size (label font sizes; optional)
269
269
 
270
270
  | Function | Line | Purpose |
271
271
  |----------|------|---------|
272
- | **`_imgFitRect(iw,ih,cw,ch)`** | **2372** | Largest rect of aspect `iw:ih` centred in `cw×ch`; all 2-D coordinate functions derive from this |
273
- | `draw2d(p)` | 2680 | Main render: `_resizePanelDOM` → decode → LUT → ImageBitmap → blit; then mask, axes, scale bar, colorbar, overlay, markers |
274
- | `drawScaleBar2d(p)` | 2875 | Physical scale bar |
275
- | `drawColorbar2d(p)` | 2961 | Gradient strip + min/max marks + rotated label centred in the `_cbWidth` gutter |
276
- | `_drawAxes2d(p)` | 3016 | Ticks (edge labels nudged inward both axes), axis labels + title via `_drawTex` |
277
- | `drawOverlay2d(p)` / `drawMarkers2d(p)` | 3169 / 3333 | Widgets / marker groups |
272
+ | **`_imgFitRect(iw,ih,cw,ch)`** | **2384** | Largest rect of aspect `iw:ih` centred in `cw×ch`; all 2-D coordinate functions derive from this |
273
+ | `draw2d(p)` | 2713 | Main render: `_resizePanelDOM` → decode → LUT → ImageBitmap → blit; then mask, axes, scale bar, colorbar, overlay, markers |
274
+ | `drawScaleBar2d(p)` | 2908 | Physical scale bar |
275
+ | `_rawBand(st)` / `_buildLut32(st)` | 2433 / 2440 | The quantisation band the u8 bytes were encoded over, then the 256-entry LUT built from it. `_rawBand` mirrors Python `_tile_quant_clim`: a DEGENERATE band (`raw_max <= raw_min`) is UNSET and falls back to `display_min/max`. Both render paths and the colorbar go through it — honouring a `(0, 0)` band paints solid black |
276
+ | `drawColorbar2d(p)` | 3188 | Gradient strip + min/max marks (band-relative, via `_rawBand`) + rotated label centred in the `_cbWidth` gutter |
277
+ | `_drawAxes2d(p)` | 3242 | Ticks (edge labels nudged inward both axes), axis labels + title via `_drawTex` |
278
+ | `drawOverlay2d(p)` / `drawMarkers2d(p)` | 3395 / 3559 | Widgets / marker groups |
278
279
 
279
280
  Zoom model: at `zoom=1` the whole image fills the fit-rect; at `zoom=Z>1` a
280
281
  `1/Z` region fills it. `_imgToCanvas2d` / `_canvasToImg2d` must stay exact
@@ -2415,10 +2415,31 @@ function render({ model, el, onResize }) {
2415
2415
  };
2416
2416
  }
2417
2417
 
2418
+ // The quantisation band the uint8 image bytes were encoded over, as [lo, hi].
2419
+ // A DEGENERATE band (raw_max <= raw_min) means DIFFERENT things on the two
2420
+ // encoder paths, and the LUT has to read it the way the bytes were written:
2421
+ //
2422
+ // plain (_normalize_image): a constant frame quantises to ALL-ZERO bytes and
2423
+ // reports (c, c) — the band NAMES the single value code 0 stands for, and
2424
+ // hMin + raw/255 * (range||1) reconstructs it. Keep honouring it.
2425
+ // tile (_tile_quant_clim): a degenerate band is treated as UNSET and the
2426
+ // bytes are quantised over the DISPLAY window instead — so they are already
2427
+ // display-mapped and the LUT must be the identity window over them.
2428
+ //
2429
+ // Reading a tiled degenerate band the plain way is GH #60: a plot that entered
2430
+ // tile mode on a flat placeholder carries the band (0, 0), every code maps to
2431
+ // hMin + raw/255 * 1 in [0, 1], lands below any sane display floor, and the
2432
+ // panel renders solid black beside perfectly healthy stats.
2433
+ function _rawBand(st) {
2434
+ const lo=st.raw_min, hi=st.raw_max;
2435
+ if(lo==null||hi==null) return [st.display_min, st.display_max];
2436
+ if(hi>lo) return [lo,hi];
2437
+ return st.tile_enabled ? [st.display_min, st.display_max] : [lo,hi];
2438
+ }
2439
+
2418
2440
  function _buildLut32(st) {
2419
2441
  const dMin=st.display_min, dMax=st.display_max;
2420
- const hMin=st.raw_min!=null?st.raw_min:dMin;
2421
- const hMax=st.raw_max!=null?st.raw_max:dMax;
2442
+ const [hMin,hMax]=_rawBand(st);
2422
2443
  const mode=st.scale_mode||'linear';
2423
2444
  const range=hMax-hMin||1;
2424
2445
  const cmapData=st.colormap_data||[];
@@ -3198,8 +3219,7 @@ function render({ model, el, onResize }) {
3198
3219
 
3199
3220
  // display_min / display_max tick marks
3200
3221
  const dMin=st.display_min, dMax=st.display_max;
3201
- const hMin=st.raw_min!=null?st.raw_min:dMin;
3202
- const hMax=st.raw_max!=null?st.raw_max:dMax;
3222
+ const [hMin,hMax]=_rawBand(st);
3203
3223
  const vRange=(hMax-hMin)||1;
3204
3224
  function _vToY(v){return imgH-1-((v-hMin)/vRange)*(imgH-1);}
3205
3225
  ctx.strokeStyle='rgba(255,255,255,0.85)'; ctx.lineWidth=1.5;
@@ -507,11 +507,7 @@ class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
507
507
  # (raw_min/raw_max fixed, display_min/display_max move) with NO pixel re-encode
508
508
  # or re-transfer — set_clim just moves the display window (see set_clim). Set
509
509
  # once from the full-res range; keep any existing band on a re-enable.
510
- if (self._state.get("raw_min") is None
511
- or not (self._state.get("raw_max", 0) > self._state.get("raw_min", 0))):
512
- rng = self._backend_display_range(self._tile_backend)
513
- if rng is not None:
514
- self._state["raw_min"], self._state["raw_max"] = rng
510
+ self._ensure_tile_band()
515
511
  _was_on = self._tile_on
516
512
  if not self._tile_on:
517
513
  self._tile_on = True
@@ -561,6 +557,13 @@ class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
561
557
  setter = getattr(self._tile_backend, "set_array", None)
562
558
  if setter is not None:
563
559
  setter(array)
560
+ # The quantisation band is derived ONCE (enable_tile) and then held fixed, so
561
+ # a contrast change re-windows in the LUT with no pixel re-encode. But if it
562
+ # was never VALIDLY derived — tile mode started on a flat placeholder, so the
563
+ # probe refused and the band stayed degenerate — the new data in the backend
564
+ # is the first chance to get it right. Must run BEFORE the overview/detail are
565
+ # re-sampled below so they quantise over the corrected band in the SAME push.
566
+ self._ensure_tile_band()
564
567
  reg = self._state.get("detail_region") or []
565
568
  has_detail = len(reg) == 4
566
569
  # Refresh the overview base ONLY when no detail tile is shown (zoomed out) —
@@ -622,6 +625,70 @@ class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
622
625
  except Exception as e:
623
626
  _TLOG.warning("[TILEDBG] overview refresh FAILED: %s", e)
624
627
 
628
+ def _ensure_tile_band(self) -> bool:
629
+ """Derive the fixed quantisation band (raw_min/raw_max) from the CURRENT tile
630
+ backend when it is unset or DEGENERATE, by the same rule _tile_quant_clim
631
+ uses to read it back. Returns True iff the band was (re-)derived.
632
+
633
+ The degenerate case is the one that bites: a plot that entered tile mode on a
634
+ flat placeholder (e.g. zeros before real data exists) probes a range of
635
+ (0, 0) — _backend_display_range refuses it, so the band keeps the
636
+ constructor's raw min/max, which for zeros is also (0, 0). Every later
637
+ set_data then re-sampled the overview against a band the encoder treated as
638
+ unset (quantising over the display window) while the frontend LUT honoured
639
+ (0, 0) literally and painted the panel solid black. Calling this on each data
640
+ swap re-derives the band as soon as a frame with real range arrives, so the
641
+ two ends of the protocol agree on what the bytes mean.
642
+ """
643
+ lo, hi = self._state.get("raw_min"), self._state.get("raw_max")
644
+ if lo is not None and hi is not None and hi > lo:
645
+ return False # a valid band is kept, never re-derived
646
+ if self._tile_backend is None:
647
+ return False
648
+ rng = self._backend_display_range(self._tile_backend)
649
+ if rng is None:
650
+ # Still flat (an all-constant frame): leave the band unset/degenerate —
651
+ # _tile_quant_clim and the JS LUT both fall back to the display window.
652
+ return False
653
+ self._state["raw_min"], self._state["raw_max"] = rng
654
+ _TLOG.debug("[TILEDBG] tile band (re-)derived from backend → raw=(%s,%s)",
655
+ *rng)
656
+ return True
657
+
658
+ def set_tile_band(self, vmin: float, vmax: float) -> None:
659
+ """PIN the fixed quantisation band the tile bytes are encoded over.
660
+
661
+ Tile mode normally derives this band once, from the full-res data (see
662
+ ``enable_tile``), and then holds it: a contrast change re-windows in the LUT
663
+ with no pixel re-encode or re-transfer. A live consumer that already KNOWS the
664
+ honest range — a camera's bit depth, a detector's saturation point — can pin
665
+ it here instead of letting the first frame decide, so the contrast doesn't
666
+ shift when a later frame happens to contain a brighter pixel.
667
+
668
+ Pinning also survives what auto-derivation cannot: a source whose first frames
669
+ are flat (a placeholder, a closed shutter) has no range to derive from, and
670
+ the band stays unset until a frame with real content arrives.
671
+
672
+ Re-samples the overview (and any active detail tile) over the new band in a
673
+ single push, so the pixels and the band the renderer reads them with never
674
+ disagree. Raises if the plot is not in tile mode or the range is degenerate."""
675
+ if not self._tile_on:
676
+ raise RuntimeError(
677
+ "set_tile_band requires tile mode — the band is the quantisation "
678
+ "range for the TILE bytes. Call enable_tile() first (or use "
679
+ "set_clim() to change the display window on a plain plot).")
680
+ lo, hi = float(vmin), float(vmax)
681
+ if not (hi > lo):
682
+ raise ValueError(
683
+ f"set_tile_band requires vmax > vmin, got ({lo}, {hi}). A degenerate "
684
+ f"band is what 'unset' means here — both the encoder and the "
685
+ f"renderer then fall back to the display window.")
686
+ self._state["raw_min"], self._state["raw_max"] = lo, hi
687
+ # Re-encode over the new band. update_tile_source() with no array re-samples
688
+ # from the backend in place (keeping zoom/center and the detail region) and
689
+ # pushes — so the band and the bytes it describes ship together.
690
+ self.update_tile_source()
691
+
625
692
  def _tile_quant_clim(self):
626
693
  """The FIXED quantisation band (raw_min/raw_max) the tile bytes are encoded
627
694
  over, so a contrast change re-windows in the LUT without re-encoding pixels.
@@ -1470,7 +1537,11 @@ class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
1470
1537
  raw frame, not merely re-windowing the existing codes (which are saturated
1471
1538
  outside the previous band and so couldn't widen past it). For an RGB frame
1472
1539
  (no scalar quantisation) or when no raw frame is cached, fall back to a pure
1473
- display-window update."""
1540
+ display-window update.
1541
+
1542
+ See :meth:`set_display_window` for the non-destructive counterpart — the
1543
+ one to reach for when the pixels must not move (a tiled plot, or a
1544
+ serialised figure being re-windowed with no Python behind it)."""
1474
1545
  new_min = float(vmin) if vmin is not None else self._state.get("display_min")
1475
1546
  new_max = float(vmax) if vmax is not None else self._state.get("display_max")
1476
1547
 
@@ -1512,6 +1583,40 @@ class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
1512
1583
  self._state["display_max"] = float(vmax)
1513
1584
  self._push()
1514
1585
 
1586
+ def set_display_window(self, vmin=None, vmax=None) -> None:
1587
+ """Move the display window WITHOUT re-quantising the pixels.
1588
+
1589
+ The non-destructive counterpart to :meth:`set_clim`. Both change the
1590
+ contrast; they differ in what they do to the data behind it:
1591
+
1592
+ ``set_clim``
1593
+ re-encodes the cached raw frame over the new range, so the codes
1594
+ always span exactly the visible band — maximum precision for what is
1595
+ on screen, but the pixels are re-encoded and re-sent, and the old
1596
+ band is gone.
1597
+ ``set_display_window``
1598
+ leaves the codes and their ``raw_min``/``raw_max`` band alone and
1599
+ moves only the window the LUT maps through it. Nothing is re-encoded
1600
+ and nothing travels but two floats.
1601
+
1602
+ Use it when the pixels must stay put: a tiled plot, where re-quantising
1603
+ would re-encode the full-res frame on every drag tick (``set_clim``
1604
+ already routes there internally), or a figure that has been serialised
1605
+ and is being re-windowed with no Python behind it — which is how a saved
1606
+ page gets a working contrast control at all.
1607
+
1608
+ The trade is precision. Quantisation spans ``[raw_min, raw_max]``, so a
1609
+ window much narrower than that band resolves in coarse steps, and one
1610
+ WIDER than it recovers nothing: values outside the band were saturated
1611
+ to 0/255 when the frame was encoded. Quantise over the range you want to
1612
+ be able to reach.
1613
+ """
1614
+ if vmin is not None:
1615
+ self._state["display_min"] = float(vmin)
1616
+ if vmax is not None:
1617
+ self._state["display_max"] = float(vmax)
1618
+ self._push()
1619
+
1515
1620
  def set_detail(self, tile=None, x0=None, x1=None, y0=None, y1=None) -> None:
1516
1621
  """Upload a HIGH-RES detail tile covering the LOGICAL image-pixel rectangle
1517
1622
  ``[x0:x1, y0:y1]`` of the base image (in the SAME orientation as the frame
@@ -0,0 +1,135 @@
1
+ """
2
+ tests/test_plot2d/test_display_window.py
3
+ ========================================
4
+ ``Plot2D.set_display_window`` — moving the contrast window WITHOUT re-quantising.
5
+
6
+ The distinction from ``set_clim`` is the whole point, so these pin it from both
7
+ sides: ``set_clim`` re-encodes the frame and collapses ``raw_*`` onto the new
8
+ band; ``set_display_window`` leaves the codes and the band alone and moves only
9
+ the window the LUT maps through.
10
+
11
+ That difference is what decides whether a SERIALISED figure can have a working
12
+ contrast control. A page saved after ``set_clim`` holds codes saturated outside
13
+ the band it was saved with, so widening in JS recovers nothing; quantised over a
14
+ wide band and windowed with ``set_display_window``, the same page can be
15
+ re-windowed either way with no Python behind it.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import numpy as np
20
+
21
+ import anyplotlib as apl
22
+
23
+
24
+ def _plot(data=None):
25
+ fig, ax = apl.subplots(1, 1)
26
+ if data is None:
27
+ data = np.arange(64, dtype=float).reshape(8, 8)
28
+ return ax.imshow(data)
29
+
30
+
31
+ class TestWindowMoves:
32
+ def test_it_sets_both_ends(self):
33
+ p = _plot()
34
+ p.set_display_window(10.0, 40.0)
35
+ assert p._state["display_min"] == 10.0
36
+ assert p._state["display_max"] == 40.0
37
+
38
+ def test_either_end_alone_leaves_the_other(self):
39
+ p = _plot()
40
+ p.set_display_window(10.0, 40.0)
41
+ p.set_display_window(vmax=25.0)
42
+ assert p._state["display_min"] == 10.0
43
+ assert p._state["display_max"] == 25.0
44
+
45
+ def test_it_pushes_so_the_change_reaches_js(self):
46
+ # The _push() contract: a mutation that does not push never appears.
47
+ p = _plot()
48
+ seen = []
49
+ p._push = lambda *a, **k: seen.append(1)
50
+ p.set_display_window(1.0, 2.0)
51
+ assert seen, "set_display_window did not push"
52
+
53
+
54
+ class TestPixelsStayPut:
55
+ """The defining property. If the codes move, this is just a slow set_clim."""
56
+
57
+ def test_the_encoded_pixels_are_untouched(self):
58
+ p = _plot()
59
+ before = p._state["image_b64"]
60
+ p.set_display_window(10.0, 40.0)
61
+ assert p._state["image_b64"] == before
62
+
63
+ def test_the_quantisation_band_is_untouched(self):
64
+ p = _plot()
65
+ raw_before = (p._state["raw_min"], p._state["raw_max"])
66
+ p.set_display_window(10.0, 40.0)
67
+ assert (p._state["raw_min"], p._state["raw_max"]) == raw_before
68
+
69
+ def test_set_clim_by_contrast_re_encodes_and_collapses_the_band(self):
70
+ # The counterpart, asserted here so the pair cannot silently converge.
71
+ p = _plot()
72
+ before = p._state["image_b64"]
73
+ p.set_clim(10.0, 40.0)
74
+ assert p._state["image_b64"] != before
75
+ assert p._state["raw_min"] == p._state["display_min"] == 10.0
76
+ assert p._state["raw_max"] == p._state["display_max"] == 40.0
77
+
78
+
79
+ class TestHeadroomForASerialisedFigure:
80
+ def test_a_wide_band_keeps_room_to_window_in_both_directions(self):
81
+ # Quantise over the full range, then narrow: the codes still span the
82
+ # whole range, so a reader can widen back out. This is exactly what an
83
+ # exported page needs and what set_clim cannot give it.
84
+ data = np.arange(256, dtype=float).reshape(16, 16)
85
+ fig, ax = apl.subplots(1, 1)
86
+ p = ax.imshow(data, vmin=0.0, vmax=255.0)
87
+
88
+ p.set_display_window(100.0, 150.0)
89
+ assert p._state["raw_min"] == 0.0 and p._state["raw_max"] == 255.0
90
+ assert (p._state["display_min"], p._state["display_max"]) == (100.0, 150.0)
91
+
92
+ # …and back out past the narrow window, still against the full band.
93
+ p.set_display_window(0.0, 255.0)
94
+ assert (p._state["display_min"], p._state["display_max"]) == (0.0, 255.0)
95
+ assert p._state["raw_min"] == 0.0 and p._state["raw_max"] == 255.0
96
+
97
+ def test_set_clim_first_would_have_thrown_that_away(self):
98
+ data = np.arange(256, dtype=float).reshape(16, 16)
99
+ fig, ax = apl.subplots(1, 1)
100
+ p = ax.imshow(data, vmin=0.0, vmax=255.0)
101
+
102
+ p.set_clim(100.0, 150.0)
103
+ # Everything outside 100–150 is saturated in the codes now, so the band
104
+ # a serialised page could re-window within has collapsed to the window.
105
+ assert p._state["raw_min"] == 100.0 and p._state["raw_max"] == 150.0
106
+
107
+
108
+ class TestRgbAndTile:
109
+ def test_an_rgb_frame_windows_the_same_way(self):
110
+ rgb = np.zeros((8, 8, 3), np.uint8)
111
+ fig, ax = apl.subplots(1, 1)
112
+ p = ax.imshow(rgb)
113
+ p.set_display_window(0.2, 0.8)
114
+ assert (p._state["display_min"], p._state["display_max"]) == (0.2, 0.8)
115
+
116
+ def test_it_matches_what_set_clim_already_does_in_tile_mode(self):
117
+ # set_clim's tile branch is this method's behaviour, inlined. Pin that
118
+ # they agree so the two cannot drift apart.
119
+ data = np.arange(256, dtype=float).reshape(16, 16)
120
+ fig, ax = apl.subplots(1, 1)
121
+ p = ax.imshow(data, vmin=0.0, vmax=255.0)
122
+ p._tile_on = True
123
+ before = p._state["image_b64"]
124
+
125
+ p.set_clim(60.0, 90.0)
126
+ via_clim = (p._state["display_min"], p._state["display_max"],
127
+ p._state["raw_min"], p._state["raw_max"],
128
+ p._state["image_b64"] == before)
129
+
130
+ p._tile_on = False
131
+ p.set_display_window(60.0, 90.0)
132
+ via_window = (p._state["display_min"], p._state["display_max"],
133
+ p._state["raw_min"], p._state["raw_max"],
134
+ p._state["image_b64"] == before)
135
+ assert via_clim == via_window