glplot 0.1.7__tar.gz → 0.1.9__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 (189) hide show
  1. {glplot-0.1.7 → glplot-0.1.9}/CITATION.cff +14 -7
  2. {glplot-0.1.7 → glplot-0.1.9}/PKG-INFO +41 -21
  3. {glplot-0.1.7 → glplot-0.1.9}/README.md +33 -15
  4. {glplot-0.1.7 → glplot-0.1.9}/glplot/__init__.py +12 -0
  5. {glplot-0.1.7 → glplot-0.1.9}/glplot/anim/processes.py +5 -0
  6. {glplot-0.1.7 → glplot-0.1.9}/glplot/core/panel.py +81 -1
  7. {glplot-0.1.7 → glplot-0.1.9}/glplot/engine.py +96 -30
  8. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/actions.py +1 -1
  9. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/icons.py +28 -29
  10. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/keys.py +84 -17
  11. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/mathops.py +6 -1
  12. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/annotate.py +53 -57
  13. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/data_editor.py +81 -86
  14. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/dynamics.py +9 -7
  15. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/functions.py +16 -15
  16. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/help.py +21 -23
  17. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/mathlab.py +19 -19
  18. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/objects3d.py +4 -5
  19. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/palette.py +37 -42
  20. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/pipeline.py +17 -17
  21. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/presentation.py +7 -7
  22. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/scene.py +26 -37
  23. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/selection.py +10 -13
  24. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/style.py +70 -56
  25. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/timeline.py +28 -23
  26. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/view3d.py +1 -1
  27. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/theme.py +60 -61
  28. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/widgets.py +64 -60
  29. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/workspace.py +36 -29
  30. {glplot-0.1.7 → glplot-0.1.9}/glplot/managers/hud.py +68 -56
  31. {glplot-0.1.7 → glplot-0.1.9}/glplot/policy.py +1 -1
  32. {glplot-0.1.7 → glplot-0.1.9}/glplot/pyplot.py +721 -65
  33. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/axes3d.py +6 -6
  34. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/axis.py +61 -68
  35. glplot-0.1.9/glplot/renderers/colorbar.py +415 -0
  36. glplot-0.1.9/glplot/renderers/contour_labels.py +274 -0
  37. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/legend.py +35 -38
  38. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/scatter.py +27 -8
  39. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/text.py +5 -5
  40. {glplot-0.1.7 → glplot-0.1.9}/glplot/utils/preview.py +238 -13
  41. {glplot-0.1.7 → glplot-0.1.9}/glplot/utils/shaders.py +66 -1
  42. {glplot-0.1.7 → glplot-0.1.9}/pyproject.toml +27 -10
  43. {glplot-0.1.7 → glplot-0.1.9}/tests/conftest.py +1 -1
  44. {glplot-0.1.7 → glplot-0.1.9}/tests/test_anim_processes.py +9 -2
  45. {glplot-0.1.7 → glplot-0.1.9}/tests/test_animation_api.py +1 -1
  46. {glplot-0.1.7 → glplot-0.1.9}/tests/test_axes.py +12 -13
  47. {glplot-0.1.7 → glplot-0.1.9}/tests/test_axes3d.py +7 -8
  48. glplot-0.1.9/tests/test_contour_labels.py +132 -0
  49. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gallery_integration.py +9 -0
  50. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_3d_panels.py +2 -3
  51. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_actions.py +63 -5
  52. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_annotate.py +6 -4
  53. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_import_safety.py +31 -13
  54. glplot-0.1.9/tests/test_gui_input_regressions.py +183 -0
  55. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_keys.py +71 -14
  56. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_legend.py +11 -15
  57. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_mathlab.py +13 -12
  58. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_pipeline.py +4 -6
  59. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_scene_delete.py +2 -3
  60. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_style_layer_editor.py +3 -4
  61. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_timeline_panel.py +7 -5
  62. {glplot-0.1.7 → glplot-0.1.9}/tests/test_mpl_compat.py +276 -12
  63. {glplot-0.1.7 → glplot-0.1.9}/tests/test_mpl_parity_surface.py +7 -0
  64. {glplot-0.1.7 → glplot-0.1.9}/tests/test_objects3d_preview.py +9 -7
  65. {glplot-0.1.7 → glplot-0.1.9}/tests/test_outline_2d.py +1 -0
  66. {glplot-0.1.7 → glplot-0.1.9}/.gitignore +0 -0
  67. {glplot-0.1.7 → glplot-0.1.9}/CHANGELOG.md +0 -0
  68. {glplot-0.1.7 → glplot-0.1.9}/LICENSE +0 -0
  69. {glplot-0.1.7 → glplot-0.1.9}/glplot/__main__.py +0 -0
  70. {glplot-0.1.7 → glplot-0.1.9}/glplot/anim/__init__.py +0 -0
  71. {glplot-0.1.7 → glplot-0.1.9}/glplot/anim/applier.py +0 -0
  72. {glplot-0.1.7 → glplot-0.1.9}/glplot/anim/primitives.py +0 -0
  73. {glplot-0.1.7 → glplot-0.1.9}/glplot/animation.py +0 -0
  74. {glplot-0.1.7 → glplot-0.1.9}/glplot/backend.py +0 -0
  75. {glplot-0.1.7 → glplot-0.1.9}/glplot/controllers.py +0 -0
  76. {glplot-0.1.7 → glplot-0.1.9}/glplot/core/__init__.py +0 -0
  77. {glplot-0.1.7 → glplot-0.1.9}/glplot/core/camera3d.py +0 -0
  78. {glplot-0.1.7 → glplot-0.1.9}/glplot/core/context.py +0 -0
  79. {glplot-0.1.7 → glplot-0.1.9}/glplot/core/layers.py +0 -0
  80. {glplot-0.1.7 → glplot-0.1.9}/glplot/core/layout.py +0 -0
  81. {glplot-0.1.7 → glplot-0.1.9}/glplot/core/legacy.py +0 -0
  82. {glplot-0.1.7 → glplot-0.1.9}/glplot/core/timeline.py +0 -0
  83. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/__init__.py +0 -0
  84. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/app.py +0 -0
  85. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/clipboard.py +0 -0
  86. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/commands.py +0 -0
  87. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/dataio.py +0 -0
  88. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/datasets.py +0 -0
  89. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/dynamics.py +0 -0
  90. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/expressions.py +0 -0
  91. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/fuzzy.py +0 -0
  92. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/generators3d.py +0 -0
  93. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/history.py +0 -0
  94. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/layerops.py +0 -0
  95. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/layerops3d.py +0 -0
  96. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/__init__.py +0 -0
  97. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/panels/base.py +0 -0
  98. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/pipeline.py +0 -0
  99. {glplot-0.1.7 → glplot-0.1.9}/glplot/gui/styles.py +0 -0
  100. {glplot-0.1.7 → glplot-0.1.9}/glplot/managers/__init__.py +0 -0
  101. {glplot-0.1.7 → glplot-0.1.9}/glplot/managers/axis.py +0 -0
  102. {glplot-0.1.7 → glplot-0.1.9}/glplot/managers/effects.py +0 -0
  103. {glplot-0.1.7 → glplot-0.1.9}/glplot/managers/hud_state.py +0 -0
  104. {glplot-0.1.7 → glplot-0.1.9}/glplot/managers/picking.py +0 -0
  105. {glplot-0.1.7 → glplot-0.1.9}/glplot/managers/renderer_manager.py +0 -0
  106. {glplot-0.1.7 → glplot-0.1.9}/glplot/options.py +0 -0
  107. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/__init__.py +0 -0
  108. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/base.py +0 -0
  109. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/density.py +0 -0
  110. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/exact.py +0 -0
  111. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/fractal.py +0 -0
  112. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/geometry3d.py +0 -0
  113. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/interaction.py +0 -0
  114. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/line_family.py +0 -0
  115. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/patch.py +0 -0
  116. {glplot-0.1.7 → glplot-0.1.9}/glplot/renderers/polyline.py +0 -0
  117. {glplot-0.1.7 → glplot-0.1.9}/glplot/utils/__init__.py +0 -0
  118. {glplot-0.1.7 → glplot-0.1.9}/glplot/utils/anim_export.py +0 -0
  119. {glplot-0.1.7 → glplot-0.1.9}/glplot/utils/blending.py +0 -0
  120. {glplot-0.1.7 → glplot-0.1.9}/glplot/utils/export.py +0 -0
  121. {glplot-0.1.7 → glplot-0.1.9}/glplot/utils/gl_utils.py +0 -0
  122. {glplot-0.1.7 → glplot-0.1.9}/glplot/utils/mpl_bridge.py +0 -0
  123. {glplot-0.1.7 → glplot-0.1.9}/glplot/utils/mpl_process.py +0 -0
  124. {glplot-0.1.7 → glplot-0.1.9}/glplot/utils/scale.py +0 -0
  125. {glplot-0.1.7 → glplot-0.1.9}/tests/test_anim_applier.py +0 -0
  126. {glplot-0.1.7 → glplot-0.1.9}/tests/test_anim_export.py +0 -0
  127. {glplot-0.1.7 → glplot-0.1.9}/tests/test_anim_primitives.py +0 -0
  128. {glplot-0.1.7 → glplot-0.1.9}/tests/test_animation_integration.py +0 -0
  129. {glplot-0.1.7 → glplot-0.1.9}/tests/test_api_consistency.py +0 -0
  130. {glplot-0.1.7 → glplot-0.1.9}/tests/test_axis_margins.py +0 -0
  131. {glplot-0.1.7 → glplot-0.1.9}/tests/test_blending_3d.py +0 -0
  132. {glplot-0.1.7 → glplot-0.1.9}/tests/test_camera3d.py +0 -0
  133. {glplot-0.1.7 → glplot-0.1.9}/tests/test_camera_anisotropy.py +0 -0
  134. {glplot-0.1.7 → glplot-0.1.9}/tests/test_camera_projections.py +0 -0
  135. {glplot-0.1.7 → glplot-0.1.9}/tests/test_compositing_3d.py +0 -0
  136. {glplot-0.1.7 → glplot-0.1.9}/tests/test_compositing_api.py +0 -0
  137. {glplot-0.1.7 → glplot-0.1.9}/tests/test_dataio.py +0 -0
  138. {glplot-0.1.7 → glplot-0.1.9}/tests/test_density_tint.py +0 -0
  139. {glplot-0.1.7 → glplot-0.1.9}/tests/test_edge_cases.py +0 -0
  140. {glplot-0.1.7 → glplot-0.1.9}/tests/test_effects_post.py +0 -0
  141. {glplot-0.1.7 → glplot-0.1.9}/tests/test_encodings.py +0 -0
  142. {glplot-0.1.7 → glplot-0.1.9}/tests/test_engine.py +0 -0
  143. {glplot-0.1.7 → glplot-0.1.9}/tests/test_export.py +0 -0
  144. {glplot-0.1.7 → glplot-0.1.9}/tests/test_fractal.py +0 -0
  145. {glplot-0.1.7 → glplot-0.1.9}/tests/test_function_layer.py +0 -0
  146. {glplot-0.1.7 → glplot-0.1.9}/tests/test_generators3d.py +0 -0
  147. {glplot-0.1.7 → glplot-0.1.9}/tests/test_generators3d_extra.py +0 -0
  148. {glplot-0.1.7 → glplot-0.1.9}/tests/test_geometry3d.py +0 -0
  149. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_clipboard.py +0 -0
  150. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_commands.py +0 -0
  151. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_data_editor.py +0 -0
  152. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_datasets.py +0 -0
  153. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_dynamics.py +0 -0
  154. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_engine_integration.py +0 -0
  155. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_expressions.py +0 -0
  156. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_fields.py +0 -0
  157. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_fuzzy.py +0 -0
  158. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_history.py +0 -0
  159. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_impostor_alignment.py +0 -0
  160. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_keyboard_capture.py +0 -0
  161. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_layer_colormaps.py +0 -0
  162. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_layer_kinds.py +0 -0
  163. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_layer_params.py +0 -0
  164. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_mathops.py +0 -0
  165. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_regression_r2.py +0 -0
  166. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_scene_order.py +0 -0
  167. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_styles.py +0 -0
  168. {glplot-0.1.7 → glplot-0.1.9}/tests/test_gui_styles_extra.py +0 -0
  169. {glplot-0.1.7 → glplot-0.1.9}/tests/test_helpers.py +0 -0
  170. {glplot-0.1.7 → glplot-0.1.9}/tests/test_impostor_scatter_fidelity.py +0 -0
  171. {glplot-0.1.7 → glplot-0.1.9}/tests/test_layerops3d.py +0 -0
  172. {glplot-0.1.7 → glplot-0.1.9}/tests/test_layers.py +0 -0
  173. {glplot-0.1.7 → glplot-0.1.9}/tests/test_mpl_bridge.py +0 -0
  174. {glplot-0.1.7 → glplot-0.1.9}/tests/test_mpl_compat_3d.py +0 -0
  175. {glplot-0.1.7 → glplot-0.1.9}/tests/test_mpl_process.py +0 -0
  176. {glplot-0.1.7 → glplot-0.1.9}/tests/test_options.py +0 -0
  177. {glplot-0.1.7 → glplot-0.1.9}/tests/test_outline.py +0 -0
  178. {glplot-0.1.7 → glplot-0.1.9}/tests/test_panels.py +0 -0
  179. {glplot-0.1.7 → glplot-0.1.9}/tests/test_performance_benchmarks.py +0 -0
  180. {glplot-0.1.7 → glplot-0.1.9}/tests/test_plot_style_api.py +0 -0
  181. {glplot-0.1.7 → glplot-0.1.9}/tests/test_polyline_lod.py +0 -0
  182. {glplot-0.1.7 → glplot-0.1.9}/tests/test_pyplot.py +0 -0
  183. {glplot-0.1.7 → glplot-0.1.9}/tests/test_pyplot_3d.py +0 -0
  184. {glplot-0.1.7 → glplot-0.1.9}/tests/test_pyplot_integration.py +0 -0
  185. {glplot-0.1.7 → glplot-0.1.9}/tests/test_regression.py +0 -0
  186. {glplot-0.1.7 → glplot-0.1.9}/tests/test_robustness.py +0 -0
  187. {glplot-0.1.7 → glplot-0.1.9}/tests/test_scatter_renderer.py +0 -0
  188. {glplot-0.1.7 → glplot-0.1.9}/tests/test_selection_model.py +0 -0
  189. {glplot-0.1.7 → glplot-0.1.9}/tests/test_timeline.py +0 -0
@@ -1,21 +1,28 @@
1
1
  cff-version: 1.2.0
2
2
  title: GLPlot - High-Performance GPU-Accelerated Plotting Library for Python
3
- message: "If you use GLPlot in your research, please cite it as below. Scientific paper with full benchmarks and technical details is in preparation."
3
+ message: >-
4
+ If you use GLPlot in your research, please cite it as below. Felix Riccius, Julian
5
+ Holland and Gianmarco Ducci contributed equally to this work.
4
6
  type: software
5
7
  authors:
6
8
  - family-names: Lombardi
7
9
  given-names: Juan Manuel
8
10
  email: lombardi@fhi-berlin.mpg.de
9
11
  affiliation: "Fritz Haber Institute of the Max Planck Society"
10
- orcid: "https://orcid.org/0000-0000-0000-0000"
12
+ orcid: "https://orcid.org/0009-0002-3961-1426"
13
+ - family-names: Riccius
14
+ given-names: Felix
15
+ orcid: "https://orcid.org/0000-0002-1331-4469"
16
+ - family-names: Holland
17
+ given-names: Julian
18
+ orcid: "https://orcid.org/0000-0003-0134-1790"
19
+ - family-names: Ducci
20
+ given-names: Gianmarco
11
21
  identifiers:
12
22
  - type: url
13
23
  value: "https://github.com/AkarisDimitry/GLPlot"
14
24
  - type: url
15
25
  value: "https://gitlab.mpcdf.mpg.de/jmlombardi/glplot"
16
- - type: doi
17
- description: "Zenodo deposit (pending - will be assigned upon publication)"
18
- value: "10.5281/zenodo.pending"
19
26
  repository-code: "https://github.com/AkarisDimitry/GLPlot"
20
27
  repository: "https://github.com/AkarisDimitry/GLPlot"
21
28
  url: "https://github.com/AkarisDimitry/GLPlot"
@@ -41,6 +48,6 @@ keywords:
41
48
  - data visualization
42
49
  license: MIT
43
50
  status: "active development"
44
- version: 0.1.3
45
- date-released: 2026-07-07
51
+ version: 0.1.7
52
+ date-released: 2026-08-18
46
53
  development-status: "Beta"
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: glplot
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: High-performance OpenGL plotting library for Python
5
5
  Project-URL: Homepage, https://github.com/AkarisDimitry/GLPlot
6
6
  Project-URL: Repository, https://github.com/AkarisDimitry/GLPlot
7
7
  Project-URL: Issues, https://github.com/AkarisDimitry/GLPlot/issues
8
+ Author: Felix Riccius, Julian Holland, Gianmarco Ducci
8
9
  Author-email: Juan Manuel Lombardi <lombardi@fhi-berlin.mpg.de>
9
10
  License: MIT
10
11
  License-File: LICENSE
@@ -15,16 +16,15 @@ Classifier: Intended Audience :: Science/Research
15
16
  Classifier: License :: OSI Approved :: MIT License
16
17
  Classifier: Operating System :: OS Independent
17
18
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.9
19
- Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: Topic :: Scientific/Engineering :: Visualization
23
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
- Requires-Python: >=3.9
24
+ Requires-Python: >=3.11
25
25
  Requires-Dist: glfw>=2.5
26
- Requires-Dist: imgui[glfw]>=2.0
27
- Requires-Dist: matplotlib>=3.6
26
+ Requires-Dist: imgui-bundle>=1.92
27
+ Requires-Dist: matplotlib<3.10,>=3.6
28
28
  Requires-Dist: numpy>=1.23
29
29
  Requires-Dist: pyopengl>=3.1.6
30
30
  Requires-Dist: scipy>=1.9
@@ -32,11 +32,13 @@ Provides-Extra: dev
32
32
  Requires-Dist: build>=1.0; extra == 'dev'
33
33
  Requires-Dist: pytest-cov>=4.0; extra == 'dev'
34
34
  Requires-Dist: pytest-mock>=3.10; extra == 'dev'
35
+ Requires-Dist: pytest-timeout>=2.1; extra == 'dev'
35
36
  Requires-Dist: pytest>=7.0; extra == 'dev'
36
37
  Requires-Dist: twine>=4.0; extra == 'dev'
37
38
  Provides-Extra: test
38
39
  Requires-Dist: pytest-cov>=4.0; extra == 'test'
39
40
  Requires-Dist: pytest-mock>=3.10; extra == 'test'
41
+ Requires-Dist: pytest-timeout>=2.1; extra == 'test'
40
42
  Requires-Dist: pytest>=7.0; extra == 'test'
41
43
  Description-Content-Type: text/markdown
42
44
 
@@ -46,7 +48,7 @@ Description-Content-Type: text/markdown
46
48
  [![Lint](https://github.com/AkarisDimitry/GLPlot/workflows/Lint/badge.svg)](https://github.com/AkarisDimitry/GLPlot/actions/workflows/lint.yml)
47
49
  [![Build](https://github.com/AkarisDimitry/GLPlot/workflows/Build/badge.svg)](https://github.com/AkarisDimitry/GLPlot/actions/workflows/build.yml)
48
50
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
49
- [![Python Version](https://img.shields.io/badge/Python-3.9+-blue.svg)](pyproject.toml)
51
+ [![Python Version](https://img.shields.io/badge/Python-3.11+-blue.svg)](pyproject.toml)
50
52
 
51
53
  GLPlot is a Python plotting library with a Matplotlib-like API that renders on the GPU
52
54
  through OpenGL instead of the CPU. Plots that make Matplotlib chug — millions of points,
@@ -68,8 +70,8 @@ cd GLPlot
68
70
  pip install -e .
69
71
  ```
70
72
 
71
- Requires Python 3.9+. Core dependencies: numpy, scipy, matplotlib, glfw, PyOpenGL,
72
- `imgui[glfw]` (for the on-screen control panel).
73
+ Requires Python 3.11+. Core dependencies: numpy, scipy, matplotlib, glfw, PyOpenGL,
74
+ `imgui-bundle` (for the on-screen control panel).
73
75
 
74
76
  ## 30-second example
75
77
 
@@ -92,16 +94,18 @@ That's it — a real window opens, fully interactive (pan/zoom/rotate) from the
92
94
 
93
95
  ## Interactive Examples Gallery (Click any figure to view code)
94
96
 
95
- Explore 2D plots, 3D scenes, and real-time animations. **Click on any figure or title below to expand its full Python code.**
97
+ Explore 2D plots, 3D scenes, and real-time animations. **Click on any figure or title below to expand its full Python code, or click the file link to view the script.**
96
98
 
97
99
  ### 📊 2D Visualizations
98
100
 
99
101
  <details>
100
102
  <summary>
101
103
  <img src="examples/gallery/results/02_scatter_fill.png" alt="10M-point spiral scatter" width="650"/>
102
- <br><b>🔍 Click to view code: 10-Million Point Spiral Scatter (<code>02_scatter_fill.py</code>)</b>
104
+ <br><b>🔍 Click to view code: 10-Million Point Spiral Scatter (<a href="examples/gallery/02_scatter_fill.py"><code>02_scatter_fill.py</code></a>)</b>
103
105
  </summary>
104
106
 
107
+ 📄 **Full script:** [`examples/gallery/02_scatter_fill.py`](examples/gallery/02_scatter_fill.py)
108
+
105
109
  ```python
106
110
  import numpy as np
107
111
  import glplot.pyplot as plt
@@ -126,9 +130,11 @@ plt.show()
126
130
  <details>
127
131
  <summary>
128
132
  <img src="examples/gallery/results/10_massive_hist2d_density.png" alt="10M-sample 2D density histogram" width="650"/>
129
- <br><b>🔍 Click to view code: 10M-Sample 2D Density Histogram (<code>10_massive_hist2d_density.py</code>)</b>
133
+ <br><b>🔍 Click to view code: 10M-Sample 2D Density Histogram (<a href="examples/gallery/10_massive_hist2d_density.py"><code>10_massive_hist2d_density.py</code></a>)</b>
130
134
  </summary>
131
135
 
136
+ 📄 **Full script:** [`examples/gallery/10_massive_hist2d_density.py`](examples/gallery/10_massive_hist2d_density.py)
137
+
132
138
  ```python
133
139
  import numpy as np
134
140
  import glplot.pyplot as plt
@@ -162,9 +168,11 @@ plt.show()
162
168
  <details>
163
169
  <summary>
164
170
  <img src="examples/gallery/results/01_line_plot.png" alt="10,072-line oscilloscope ensemble" width="650"/>
165
- <br><b>🔍 Click to view code: 10,072-Line Oscilloscope Ensemble (<code>01_line_plot.py</code>)</b>
171
+ <br><b>🔍 Click to view code: 10,072-Line Oscilloscope Ensemble (<a href="examples/gallery/01_line_plot.py"><code>01_line_plot.py</code></a>)</b>
166
172
  </summary>
167
173
 
174
+ 📄 **Full script:** [`examples/gallery/01_line_plot.py`](examples/gallery/01_line_plot.py)
175
+
168
176
  ```python
169
177
  import colorsys
170
178
  import numpy as np
@@ -212,9 +220,11 @@ plt.show()
212
220
  <details>
213
221
  <summary>
214
222
  <img src="examples/gallery/results/07_projected_3d_cloud.png" alt="1M-point 3D Point Cloud" width="650"/>
215
- <br><b>🔍 Click to view code: 1M-Point Circumstellar Dust Halo (<code>07_projected_3d_cloud.py</code>)</b>
223
+ <br><b>🔍 Click to view code: 1M-Point Circumstellar Dust Halo (<a href="examples/gallery/07_projected_3d_cloud.py"><code>07_projected_3d_cloud.py</code></a>)</b>
216
224
  </summary>
217
225
 
226
+ 📄 **Full script:** [`examples/gallery/07_projected_3d_cloud.py`](examples/gallery/07_projected_3d_cloud.py)
227
+
218
228
  ```python
219
229
  import numpy as np
220
230
  import glplot.pyplot as plt
@@ -253,9 +263,11 @@ plt.show()
253
263
  <details>
254
264
  <summary>
255
265
  <img src="examples/gallery/results/13_volumetric_nebula.png" alt="1.75M-Point Volumetric Nebula" width="650"/>
256
- <br><b>🔍 Click to view code: 1.75M-Point Volumetric Nebula (<code>13_volumetric_nebula.py</code>)</b>
266
+ <br><b>🔍 Click to view code: 1.75M-Point Volumetric Nebula (<a href="examples/gallery/13_volumetric_nebula.py"><code>13_volumetric_nebula.py</code></a>)</b>
257
267
  </summary>
258
268
 
269
+ 📄 **Full script:** [`examples/gallery/13_volumetric_nebula.py`](examples/gallery/13_volumetric_nebula.py)
270
+
259
271
  ```python
260
272
  import numpy as np
261
273
  import glplot.pyplot as plt
@@ -299,9 +311,11 @@ plt.show()
299
311
  <details>
300
312
  <summary>
301
313
  <img src="examples/gallery/results/19_turbulent_vector_field_3d.png" alt="3D Turbulent Vector Field" width="650"/>
302
- <br><b>🔍 Click to view code: 3D Turbulent Vector Field (<code>19_turbulent_vector_field_3d.py</code>)</b>
314
+ <br><b>🔍 Click to view code: 3D Turbulent Vector Field (<a href="examples/gallery/19_turbulent_vector_field_3d.py"><code>19_turbulent_vector_field_3d.py</code></a>)</b>
303
315
  </summary>
304
316
 
317
+ 📄 **Full script:** [`examples/gallery/19_turbulent_vector_field_3d.py`](examples/gallery/19_turbulent_vector_field_3d.py)
318
+
305
319
  ```python
306
320
  import numpy as np
307
321
  import glplot.pyplot as plt
@@ -342,9 +356,11 @@ plt.show()
342
356
  <details>
343
357
  <summary>
344
358
  <img src="examples/gallery/results/28_chladni_wave_animation.gif" alt="Chladni wave animation" width="650"/>
345
- <br><b>🔍 Click to view code: Animated Standing-Wave Interference (<code>28_chladni_wave_animation.py</code>)</b>
359
+ <br><b>🔍 Click to view code: Animated Standing-Wave Interference (<a href="examples/gallery/28_chladni_wave_animation.py"><code>28_chladni_wave_animation.py</code></a>)</b>
346
360
  </summary>
347
361
 
362
+ 📄 **Full script:** [`examples/gallery/28_chladni_wave_animation.py`](examples/gallery/28_chladni_wave_animation.py)
363
+
348
364
  ```python
349
365
  import numpy as np
350
366
  import glplot.animation as animation
@@ -394,9 +410,11 @@ plt.show()
394
410
  <details>
395
411
  <summary>
396
412
  <img src="examples/gallery/animations/results/03_spectrum_analyzer_bars.gif" alt="Audio spectrum analyzer bars" width="650"/>
397
- <br><b>🔍 Click to view code: Real-Time Audio Spectrum Analyzer (<code>03_spectrum_analyzer_bars.py</code>)</b>
413
+ <br><b>🔍 Click to view code: Real-Time Audio Spectrum Analyzer (<a href="examples/gallery/animations/03_spectrum_analyzer_bars.py"><code>03_spectrum_analyzer_bars.py</code></a>)</b>
398
414
  </summary>
399
415
 
416
+ 📄 **Full script:** [`examples/gallery/animations/03_spectrum_analyzer_bars.py`](examples/gallery/animations/03_spectrum_analyzer_bars.py)
417
+
400
418
  ```python
401
419
  import numpy as np
402
420
  import glplot.animation as animation
@@ -428,9 +446,11 @@ plt.show()
428
446
  <details>
429
447
  <summary>
430
448
  <img src="examples/gallery/animations/results/01_orbiting_star_cluster.gif" alt="Orbiting star cluster" width="650"/>
431
- <br><b>🔍 Click to view code: Differential Keplerian Star Cluster (<code>01_orbiting_star_cluster.py</code>)</b>
449
+ <br><b>🔍 Click to view code: Differential Keplerian Star Cluster (<a href="examples/gallery/animations/01_orbiting_star_cluster.py"><code>01_orbiting_star_cluster.py</code></a>)</b>
432
450
  </summary>
433
451
 
452
+ 📄 **Full script:** [`examples/gallery/animations/01_orbiting_star_cluster.py`](examples/gallery/animations/01_orbiting_star_cluster.py)
453
+
434
454
  ```python
435
455
  import numpy as np
436
456
  import glplot.animation as animation
@@ -540,7 +560,7 @@ pytest tests/test_pyplot.py::test_plot_accepts_y_only_and_returns_artists # one
540
560
  ```
541
561
 
542
562
  6,800+ tests, run fully headless (no window is ever displayed) so they work in CI. The
543
- matrix covers Python 3.9–3.12 on Ubuntu, macOS, and Windows; `black`, `isort`, and `flake8`
563
+ matrix covers Python 3.11–3.13 on Ubuntu, macOS, and Windows; `black`, `isort`, and `flake8`
544
564
  are enforced on every push. See `examples/benchmark/` for reproducible performance
545
565
  comparisons against Matplotlib, VisPy, fastplotlib, Datashader, and hvPlot, and `tools/`
546
566
  for GPU/environment diagnostics.
@@ -557,7 +577,7 @@ for GPU/environment diagnostics.
557
577
  ```bibtex
558
578
  @software{lombardi2026glplot,
559
579
  title={GLPlot: High-Performance GPU-Accelerated Plotting Library for Python},
560
- author={Lombardi, Juan Manuel},
580
+ author={Lombardi, Juan Manuel and Riccius, Felix and Holland, Julian and Ducci, Gianmarco},
561
581
  year={2026},
562
582
  url={https://github.com/AkarisDimitry/GLPlot},
563
583
  doi={10.5281/zenodo.PLACEHOLDER}
@@ -4,7 +4,7 @@
4
4
  [![Lint](https://github.com/AkarisDimitry/GLPlot/workflows/Lint/badge.svg)](https://github.com/AkarisDimitry/GLPlot/actions/workflows/lint.yml)
5
5
  [![Build](https://github.com/AkarisDimitry/GLPlot/workflows/Build/badge.svg)](https://github.com/AkarisDimitry/GLPlot/actions/workflows/build.yml)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
- [![Python Version](https://img.shields.io/badge/Python-3.9+-blue.svg)](pyproject.toml)
7
+ [![Python Version](https://img.shields.io/badge/Python-3.11+-blue.svg)](pyproject.toml)
8
8
 
9
9
  GLPlot is a Python plotting library with a Matplotlib-like API that renders on the GPU
10
10
  through OpenGL instead of the CPU. Plots that make Matplotlib chug — millions of points,
@@ -26,8 +26,8 @@ cd GLPlot
26
26
  pip install -e .
27
27
  ```
28
28
 
29
- Requires Python 3.9+. Core dependencies: numpy, scipy, matplotlib, glfw, PyOpenGL,
30
- `imgui[glfw]` (for the on-screen control panel).
29
+ Requires Python 3.11+. Core dependencies: numpy, scipy, matplotlib, glfw, PyOpenGL,
30
+ `imgui-bundle` (for the on-screen control panel).
31
31
 
32
32
  ## 30-second example
33
33
 
@@ -50,16 +50,18 @@ That's it — a real window opens, fully interactive (pan/zoom/rotate) from the
50
50
 
51
51
  ## Interactive Examples Gallery (Click any figure to view code)
52
52
 
53
- Explore 2D plots, 3D scenes, and real-time animations. **Click on any figure or title below to expand its full Python code.**
53
+ Explore 2D plots, 3D scenes, and real-time animations. **Click on any figure or title below to expand its full Python code, or click the file link to view the script.**
54
54
 
55
55
  ### 📊 2D Visualizations
56
56
 
57
57
  <details>
58
58
  <summary>
59
59
  <img src="examples/gallery/results/02_scatter_fill.png" alt="10M-point spiral scatter" width="650"/>
60
- <br><b>🔍 Click to view code: 10-Million Point Spiral Scatter (<code>02_scatter_fill.py</code>)</b>
60
+ <br><b>🔍 Click to view code: 10-Million Point Spiral Scatter (<a href="examples/gallery/02_scatter_fill.py"><code>02_scatter_fill.py</code></a>)</b>
61
61
  </summary>
62
62
 
63
+ 📄 **Full script:** [`examples/gallery/02_scatter_fill.py`](examples/gallery/02_scatter_fill.py)
64
+
63
65
  ```python
64
66
  import numpy as np
65
67
  import glplot.pyplot as plt
@@ -84,9 +86,11 @@ plt.show()
84
86
  <details>
85
87
  <summary>
86
88
  <img src="examples/gallery/results/10_massive_hist2d_density.png" alt="10M-sample 2D density histogram" width="650"/>
87
- <br><b>🔍 Click to view code: 10M-Sample 2D Density Histogram (<code>10_massive_hist2d_density.py</code>)</b>
89
+ <br><b>🔍 Click to view code: 10M-Sample 2D Density Histogram (<a href="examples/gallery/10_massive_hist2d_density.py"><code>10_massive_hist2d_density.py</code></a>)</b>
88
90
  </summary>
89
91
 
92
+ 📄 **Full script:** [`examples/gallery/10_massive_hist2d_density.py`](examples/gallery/10_massive_hist2d_density.py)
93
+
90
94
  ```python
91
95
  import numpy as np
92
96
  import glplot.pyplot as plt
@@ -120,9 +124,11 @@ plt.show()
120
124
  <details>
121
125
  <summary>
122
126
  <img src="examples/gallery/results/01_line_plot.png" alt="10,072-line oscilloscope ensemble" width="650"/>
123
- <br><b>🔍 Click to view code: 10,072-Line Oscilloscope Ensemble (<code>01_line_plot.py</code>)</b>
127
+ <br><b>🔍 Click to view code: 10,072-Line Oscilloscope Ensemble (<a href="examples/gallery/01_line_plot.py"><code>01_line_plot.py</code></a>)</b>
124
128
  </summary>
125
129
 
130
+ 📄 **Full script:** [`examples/gallery/01_line_plot.py`](examples/gallery/01_line_plot.py)
131
+
126
132
  ```python
127
133
  import colorsys
128
134
  import numpy as np
@@ -170,9 +176,11 @@ plt.show()
170
176
  <details>
171
177
  <summary>
172
178
  <img src="examples/gallery/results/07_projected_3d_cloud.png" alt="1M-point 3D Point Cloud" width="650"/>
173
- <br><b>🔍 Click to view code: 1M-Point Circumstellar Dust Halo (<code>07_projected_3d_cloud.py</code>)</b>
179
+ <br><b>🔍 Click to view code: 1M-Point Circumstellar Dust Halo (<a href="examples/gallery/07_projected_3d_cloud.py"><code>07_projected_3d_cloud.py</code></a>)</b>
174
180
  </summary>
175
181
 
182
+ 📄 **Full script:** [`examples/gallery/07_projected_3d_cloud.py`](examples/gallery/07_projected_3d_cloud.py)
183
+
176
184
  ```python
177
185
  import numpy as np
178
186
  import glplot.pyplot as plt
@@ -211,9 +219,11 @@ plt.show()
211
219
  <details>
212
220
  <summary>
213
221
  <img src="examples/gallery/results/13_volumetric_nebula.png" alt="1.75M-Point Volumetric Nebula" width="650"/>
214
- <br><b>🔍 Click to view code: 1.75M-Point Volumetric Nebula (<code>13_volumetric_nebula.py</code>)</b>
222
+ <br><b>🔍 Click to view code: 1.75M-Point Volumetric Nebula (<a href="examples/gallery/13_volumetric_nebula.py"><code>13_volumetric_nebula.py</code></a>)</b>
215
223
  </summary>
216
224
 
225
+ 📄 **Full script:** [`examples/gallery/13_volumetric_nebula.py`](examples/gallery/13_volumetric_nebula.py)
226
+
217
227
  ```python
218
228
  import numpy as np
219
229
  import glplot.pyplot as plt
@@ -257,9 +267,11 @@ plt.show()
257
267
  <details>
258
268
  <summary>
259
269
  <img src="examples/gallery/results/19_turbulent_vector_field_3d.png" alt="3D Turbulent Vector Field" width="650"/>
260
- <br><b>🔍 Click to view code: 3D Turbulent Vector Field (<code>19_turbulent_vector_field_3d.py</code>)</b>
270
+ <br><b>🔍 Click to view code: 3D Turbulent Vector Field (<a href="examples/gallery/19_turbulent_vector_field_3d.py"><code>19_turbulent_vector_field_3d.py</code></a>)</b>
261
271
  </summary>
262
272
 
273
+ 📄 **Full script:** [`examples/gallery/19_turbulent_vector_field_3d.py`](examples/gallery/19_turbulent_vector_field_3d.py)
274
+
263
275
  ```python
264
276
  import numpy as np
265
277
  import glplot.pyplot as plt
@@ -300,9 +312,11 @@ plt.show()
300
312
  <details>
301
313
  <summary>
302
314
  <img src="examples/gallery/results/28_chladni_wave_animation.gif" alt="Chladni wave animation" width="650"/>
303
- <br><b>🔍 Click to view code: Animated Standing-Wave Interference (<code>28_chladni_wave_animation.py</code>)</b>
315
+ <br><b>🔍 Click to view code: Animated Standing-Wave Interference (<a href="examples/gallery/28_chladni_wave_animation.py"><code>28_chladni_wave_animation.py</code></a>)</b>
304
316
  </summary>
305
317
 
318
+ 📄 **Full script:** [`examples/gallery/28_chladni_wave_animation.py`](examples/gallery/28_chladni_wave_animation.py)
319
+
306
320
  ```python
307
321
  import numpy as np
308
322
  import glplot.animation as animation
@@ -352,9 +366,11 @@ plt.show()
352
366
  <details>
353
367
  <summary>
354
368
  <img src="examples/gallery/animations/results/03_spectrum_analyzer_bars.gif" alt="Audio spectrum analyzer bars" width="650"/>
355
- <br><b>🔍 Click to view code: Real-Time Audio Spectrum Analyzer (<code>03_spectrum_analyzer_bars.py</code>)</b>
369
+ <br><b>🔍 Click to view code: Real-Time Audio Spectrum Analyzer (<a href="examples/gallery/animations/03_spectrum_analyzer_bars.py"><code>03_spectrum_analyzer_bars.py</code></a>)</b>
356
370
  </summary>
357
371
 
372
+ 📄 **Full script:** [`examples/gallery/animations/03_spectrum_analyzer_bars.py`](examples/gallery/animations/03_spectrum_analyzer_bars.py)
373
+
358
374
  ```python
359
375
  import numpy as np
360
376
  import glplot.animation as animation
@@ -386,9 +402,11 @@ plt.show()
386
402
  <details>
387
403
  <summary>
388
404
  <img src="examples/gallery/animations/results/01_orbiting_star_cluster.gif" alt="Orbiting star cluster" width="650"/>
389
- <br><b>🔍 Click to view code: Differential Keplerian Star Cluster (<code>01_orbiting_star_cluster.py</code>)</b>
405
+ <br><b>🔍 Click to view code: Differential Keplerian Star Cluster (<a href="examples/gallery/animations/01_orbiting_star_cluster.py"><code>01_orbiting_star_cluster.py</code></a>)</b>
390
406
  </summary>
391
407
 
408
+ 📄 **Full script:** [`examples/gallery/animations/01_orbiting_star_cluster.py`](examples/gallery/animations/01_orbiting_star_cluster.py)
409
+
392
410
  ```python
393
411
  import numpy as np
394
412
  import glplot.animation as animation
@@ -498,7 +516,7 @@ pytest tests/test_pyplot.py::test_plot_accepts_y_only_and_returns_artists # one
498
516
  ```
499
517
 
500
518
  6,800+ tests, run fully headless (no window is ever displayed) so they work in CI. The
501
- matrix covers Python 3.9–3.12 on Ubuntu, macOS, and Windows; `black`, `isort`, and `flake8`
519
+ matrix covers Python 3.11–3.13 on Ubuntu, macOS, and Windows; `black`, `isort`, and `flake8`
502
520
  are enforced on every push. See `examples/benchmark/` for reproducible performance
503
521
  comparisons against Matplotlib, VisPy, fastplotlib, Datashader, and hvPlot, and `tools/`
504
522
  for GPU/environment diagnostics.
@@ -515,7 +533,7 @@ for GPU/environment diagnostics.
515
533
  ```bibtex
516
534
  @software{lombardi2026glplot,
517
535
  title={GLPlot: High-Performance GPU-Accelerated Plotting Library for Python},
518
- author={Lombardi, Juan Manuel},
536
+ author={Lombardi, Juan Manuel and Riccius, Felix and Holland, Julian and Ducci, Gianmarco},
519
537
  year={2026},
520
538
  url={https://github.com/AkarisDimitry/GLPlot},
521
539
  doi={10.5281/zenodo.PLACEHOLDER}
@@ -23,6 +23,18 @@ Author: Juan Manuel Lombardi
23
23
  Repository: https://github.com/AkarisDimitry/GLPlot
24
24
  """
25
25
 
26
+ try:
27
+ # Must run before anything imports the `glfw` package (`.engine` does, right below).
28
+ # imgui-bundle points pip's `glfw` at its own bundled libglfw via the PYGLFW_LIBRARY
29
+ # env var, but only if it gets a chance to set that *before* `glfw` loads its own
30
+ # copy -- otherwise both libglfw.3.dylib's end up loaded in the same process, and
31
+ # macOS logs "Class GLFWWindow is implemented in both ..." for every GLFW ObjC class
32
+ # (verified: importing imgui_bundle first eliminates the warning entirely; importing
33
+ # it after `glfw`, as a naive import order would, does not).
34
+ import imgui_bundle as _imgui_bundle # noqa: F401
35
+ except Exception: # pragma: no cover - imgui_bundle is optional at import time (GL-less use)
36
+ pass
37
+
26
38
  from .engine import GPULinePlot
27
39
  from .options import BlendMode, EngineOptions, RenderMode
28
40
 
@@ -111,6 +111,11 @@ per-quartile mean settles at -29.767, -29.772, -29.773, -29.773 and stays there.
111
111
  the property that matters, and it is the property forward Euler does not have -- an Euler
112
112
  two-body orbit gains energy monotonically and unwinds into a spiral within a few hundred
113
113
  steps, which is not a small quantitative error but a qualitatively wrong picture.
114
+
115
+ The NBODY row is measured on Accelerate/arm64; six mutually gravitating bodies is a
116
+ chaotic system, so a different libm/BLAS (OpenBLAS/x86_64, as in Windows and Linux CI)
117
+ lands on a different point of the same bounded orbit family and measures a band around
118
+ 5x larger. The other rows are far enough from chaos, or short enough, not to show this.
114
119
  """
115
120
 
116
121
  from __future__ import annotations
@@ -18,7 +18,8 @@ origin, which is what ``glViewport`` wants).
18
18
 
19
19
  from __future__ import annotations
20
20
 
21
- from typing import TYPE_CHECKING, Optional, Tuple
21
+ from dataclasses import dataclass
22
+ from typing import TYPE_CHECKING, Any, List, Optional, Tuple
22
23
 
23
24
  from ..controllers import CameraController
24
25
  from .camera3d import Axes3DOptions, Camera3D
@@ -29,6 +30,61 @@ if TYPE_CHECKING:
29
30
  from ..options import EngineOptions
30
31
 
31
32
 
33
+ @dataclass
34
+ class ColorbarSpec:
35
+ """One colorbar attached to a :class:`Panel`, drawn as a screen-space overlay.
36
+
37
+ Deliberately not a :class:`Panel` itself: any rect registered on ``fig.panels`` is
38
+ click-to-activate and pan/zoom-able (see ``GPULinePlot._panel_index_at``), so a
39
+ colorbar built that way would silently steal "current axes" the moment a script did
40
+ ``im = plt.imshow(...); plt.colorbar(); plt.title(...)`` -- the title would land on
41
+ the colorbar. A ``ColorbarSpec`` instead just describes a strip of the figure and is
42
+ drawn by :func:`glplot.renderers.colorbar.draw_colorbars`, in the same whole-window,
43
+ camera-free pixel space :meth:`GPULinePlot._draw_panel_borders` already draws in.
44
+
45
+ ``norm`` is always a concrete, bounded ``matplotlib.colors.Normalize`` instance (never
46
+ a bare scale name and never unbounded) -- the single source of truth the gradient fill,
47
+ the tick locator, and the headless ``savefig()`` reconstruction all read alike, so the
48
+ live bar and the exported PNG cannot independently drift from what was actually asked
49
+ for.
50
+ """
51
+
52
+ #: (x0, y0, w, h) in figure fractions, bottom-left origin -- same convention as
53
+ #: :attr:`Panel.rect_frac`, and the strip vacated from the host panel's own rect.
54
+ rect_frac: Tuple[float, float, float, float]
55
+ #: "vertical" for a right/left bar, "horizontal" for a top/bottom one.
56
+ orientation: str
57
+ #: Which edge of the host panel this bar sits against: "right", "left", "top" or
58
+ #: "bottom". Distinct from ``orientation`` because a caller can ask for either
59
+ #: independently, as matplotlib's own ``colorbar()`` does.
60
+ location: str
61
+ #: Colormap name, resolved (never ``None``) at ``colorbar()`` call time.
62
+ cmap: str
63
+ #: A concrete ``matplotlib.colors.Normalize`` (or subclass) instance with real
64
+ #: ``vmin``/``vmax`` already resolved.
65
+ norm: Any
66
+ #: Explicit tick positions, or ``None`` to auto-locate from ``norm``'s type.
67
+ ticks: Optional[Any] = None
68
+ #: A tick label format (matplotlib ``Formatter``, format string, or ``None`` to
69
+ #: auto-format from the locator).
70
+ format: Optional[Any] = None
71
+ #: The colorbar's own axis label (``Colorbar.set_label``), or ``None``.
72
+ label: Optional[str] = None
73
+ #: Matplotlib-parity placement knobs, stored for the headless reconstruction (which
74
+ #: rebuilds a real matplotlib colorbar and can honour them precisely); the live
75
+ #: renderer's own bar geometry does not re-derive a precise aspect-ratio width from
76
+ #: these -- see ``colorbar()``'s docstring.
77
+ fraction: float = 0.15
78
+ pad: float = 0.05
79
+ shrink: float = 1.0
80
+ aspect: float = 20.0
81
+ #: When True, the bar sits *inside* the host panel's own rect (over the plotted
82
+ #: content, near ``location``'s edge) instead of shrinking the panel to make room
83
+ #: outside it. Ticks/label flip to point inward, toward the panel's centre, since
84
+ #: there is no room outside the panel for them to point into.
85
+ inset: bool = False
86
+
87
+
32
88
  class Panel:
33
89
  """One plotting region: its scene, camera, interaction state, and window rectangle."""
34
90
 
@@ -64,6 +120,30 @@ class Panel:
64
120
  #: panel that is never animated costs one object and no frame time.
65
121
  self.timeline = Timeline()
66
122
 
123
+ #: Colorbars drawn against this panel's edges. Metadata only -- never a
124
+ #: :class:`Panel` of its own, never registered on ``fig.panels`` -- so a
125
+ #: colorbar can never become the active/pannable axes. See
126
+ #: :class:`ColorbarSpec` for why.
127
+ self.colorbars: List["ColorbarSpec"] = []
128
+
129
+ #: This panel's own axis names and axes title.
130
+ #:
131
+ #: Per panel for the same reason the cameras and scenes are: a split figure plots
132
+ #: four different quantities and has to be able to *name* them four different
133
+ #: ways. These were figure-global until they lived here, so a 2x2 grid could only
134
+ #: ever carry one x-name, one y-name and one title between all four panels --
135
+ #: whichever call ran last won, and the other three axes went unlabelled.
136
+ #:
137
+ #: The engine forwards ``xlabel``/``ylabel`` to the active panel's copy (the same
138
+ #: delegation ``scene``/``camera``/``cache`` use), so ``gplt.xlabel(...)`` keeps
139
+ #: writing "the current axes' x-name" and every existing single-panel caller is
140
+ #: unaffected. ``title`` is *not* forwarded that way -- ``engine.title`` is also
141
+ #: the GLFW window caption and is assigned in ``__init__`` before any panel
142
+ #: exists -- so ``set_title`` writes both, and readers prefer this one.
143
+ self.xlabel: str = ""
144
+ self.ylabel: str = ""
145
+ self.title: str = ""
146
+
67
147
  #: Explicit dimensionality: 2, 3, or ``None`` for "infer from the layers".
68
148
  #:
69
149
  #: None is the default and reproduces GLPlot's historical behaviour exactly — a