jarvisplot 2.0.3__tar.gz → 2.0.4__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 (216) hide show
  1. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/JarvisPLOT.egg-info/PKG-INFO +1 -1
  2. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/JarvisPLOT.egg-info/SOURCES.txt +1 -0
  3. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/PKG-INFO +1 -1
  4. jarvisplot-2.0.4/jarvisplot/Figure/design_runtime.py +342 -0
  5. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/figure.py +133 -35
  6. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/a4paper/2x1/rect.json +3 -3
  7. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/pyproject.toml +1 -1
  8. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_axis_title.py +10 -0
  9. jarvisplot-2.0.4/tests/test_design_runtime.py +85 -0
  10. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_figure_contracts.py +90 -0
  11. jarvisplot-2.0.3/jarvisplot/Figure/design_runtime.py +0 -137
  12. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
  13. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/JarvisPLOT.egg-info/entry_points.txt +0 -0
  14. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/JarvisPLOT.egg-info/requires.txt +0 -0
  15. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/JarvisPLOT.egg-info/top_level.txt +0 -0
  16. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/README.md +0 -0
  17. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/__init__.py +0 -0
  18. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/adapters_rect.py +0 -0
  19. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/adapters_ternary.py +0 -0
  20. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
  21. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/colorbar_runtime.py +0 -0
  22. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/config_runtime.py +0 -0
  23. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/data_pipelines.py +0 -0
  24. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/density_cell_runtime.py +0 -0
  25. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/dynesty_runtime.py +0 -0
  26. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/figure_types.py +0 -0
  27. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/helper.py +0 -0
  28. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/interp_2d_runtime.py +0 -0
  29. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/interp_natural_neighbor.py +0 -0
  30. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/interp_natural_neighbor_exact.py +0 -0
  31. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/layer_runtime.py +0 -0
  32. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/layout_runtime.py +0 -0
  33. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/method_registry.py +0 -0
  34. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/posterior_density_runtime.py +0 -0
  35. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/posterior_hpd.py +0 -0
  36. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/posterior_mesh.py +0 -0
  37. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/preprocessor.py +0 -0
  38. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/preprocessor_runtime.py +0 -0
  39. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/profile_runtime.py +0 -0
  40. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/Figure/style_runtime.py +0 -0
  41. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/__init__.py +0 -0
  42. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/agent_digest.py +0 -0
  43. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/agent_io.py +0 -0
  44. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cache_store.py +0 -0
  45. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/capabilities.py +0 -0
  46. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
  47. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/a4paper/2x1/dynesty_runplot.json +0 -0
  48. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/a4paper/2x1/rect5x1.json +0 -0
  49. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +0 -0
  50. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/a4paper/2x1/ternary.json +0 -0
  51. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +0 -0
  52. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
  53. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +0 -0
  54. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
  55. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
  56. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/args.json +0 -0
  57. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/colors/colormaps.json +0 -0
  58. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/default/output.json +0 -0
  59. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/flowchart/default.json +0 -0
  60. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
  61. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/gambit/2x1/rect_cmap.json +0 -0
  62. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/gambit/2x1/ternary.json +0 -0
  63. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +0 -0
  64. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
  65. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/icons/flowchart/JarvisHEP.png +0 -0
  66. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/icons/flowchart/calculator.png +0 -0
  67. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/icons/flowchart/inputfile.png +0 -0
  68. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/icons/flowchart/opera.png +0 -0
  69. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/icons/flowchart/outputfile.png +0 -0
  70. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/icons/flowchart/sampler.png +0 -0
  71. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/icons/gambit.png +0 -0
  72. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/icons/gambit_small.png +0 -0
  73. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cards/style_preference.json +0 -0
  74. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cli.py +0 -0
  75. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/cli_help.py +0 -0
  76. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/client.py +0 -0
  77. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/column_demand.py +0 -0
  78. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/column_probe.py +0 -0
  79. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/config.py +0 -0
  80. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/config_address.py +0 -0
  81. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/core.py +0 -0
  82. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/core_assets.py +0 -0
  83. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/core_runtime.py +0 -0
  84. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/data_access.py +0 -0
  85. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/data_loader.py +0 -0
  86. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/data_loader_hdf5.py +0 -0
  87. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/data_loader_runtime.py +0 -0
  88. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/data_loader_summary.py +0 -0
  89. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/diagnostic_guidance.py +0 -0
  90. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/diagnostics.py +0 -0
  91. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/dryrun_runtime.py +0 -0
  92. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/expr_names.py +0 -0
  93. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/fix_apply.py +0 -0
  94. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/flowchart.py +0 -0
  95. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/inner_func.py +0 -0
  96. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/man_catalog.py +0 -0
  97. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/man_methods.py +0 -0
  98. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/man_render_agent.py +0 -0
  99. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/man_render_human.py +0 -0
  100. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/man_transforms.py +0 -0
  101. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/manual_cards/agent-output.yaml +0 -0
  102. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/manual_cards/cli-map.yaml +0 -0
  103. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/manual_cards/data-columns.yaml +0 -0
  104. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/manual_cards/diagnostics.yaml +0 -0
  105. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/manual_cards/dryrun-viz.yaml +0 -0
  106. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/manual_cards/layer-method.yaml +0 -0
  107. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/manual_cards/manifest.yaml +0 -0
  108. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/manual_cards/method_notes.yaml +0 -0
  109. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/manual_cards/style-axes.yaml +0 -0
  110. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/manual_cards/type-posterior-2d.yaml +0 -0
  111. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/manual_cards/type-profile-2d.yaml +0 -0
  112. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/manual_cards/validate.yaml +0 -0
  113. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/manual_cards/workflow.yaml +0 -0
  114. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/manual_cards/yaml-root.yaml +0 -0
  115. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/memtrace.py +0 -0
  116. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/method_contracts.py +0 -0
  117. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/render_health.py +0 -0
  118. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/core/common.json +0 -0
  119. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/core/dataset.json +0 -0
  120. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/core/figure.json +0 -0
  121. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/core/layer.json +0 -0
  122. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/core/transform.json +0 -0
  123. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/manifest.json +0 -0
  124. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/bar.json +0 -0
  125. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/barh.json +0 -0
  126. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/contour.json +0 -0
  127. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/contourf.json +0 -0
  128. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/dynesty_runplot.json +0 -0
  129. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/errorbar.json +0 -0
  130. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/fill.json +0 -0
  131. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/fill_between.json +0 -0
  132. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/fill_betweenx.json +0 -0
  133. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/hist.json +0 -0
  134. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/imshow.json +0 -0
  135. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/jpcontour.json +0 -0
  136. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/jpcontourf.json +0 -0
  137. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/jpfield.json +0 -0
  138. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/pcolor.json +0 -0
  139. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/pcolormesh.json +0 -0
  140. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/plot.json +0 -0
  141. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/quiver.json +0 -0
  142. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/scatter.json +0 -0
  143. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/step.json +0 -0
  144. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/tricontour.json +0 -0
  145. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/tricontourf.json +0 -0
  146. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/tripcolor.json +0 -0
  147. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/tripcolor_axes.json +0 -0
  148. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/triplot.json +0 -0
  149. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/voronoi.json +0 -0
  150. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/methods/voronoif.json +0 -0
  151. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema/plot-config.json +0 -0
  152. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema_catalog.py +0 -0
  153. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/schema_diagnostics.py +0 -0
  154. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/templates_catalog.py +0 -0
  155. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/transform_contracts.py +0 -0
  156. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/utils/__init__.py +0 -0
  157. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/utils/cmaps.py +0 -0
  158. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/utils/dataframes.py +0 -0
  159. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/utils/expression.py +0 -0
  160. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/utils/interpolator.py +0 -0
  161. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/utils/pathing.py +0 -0
  162. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/validation.py +0 -0
  163. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/verbs/__init__.py +0 -0
  164. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/verbs/cap.py +0 -0
  165. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/verbs/config_cmd.py +0 -0
  166. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/verbs/data.py +0 -0
  167. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/verbs/doctor.py +0 -0
  168. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/verbs/dryrun.py +0 -0
  169. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/verbs/explain.py +0 -0
  170. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/verbs/man.py +0 -0
  171. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/verbs/suggest.py +0 -0
  172. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/verbs/template.py +0 -0
  173. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/verbs/validate.py +0 -0
  174. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/jarvisplot/yaml_io.py +0 -0
  175. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/setup.cfg +0 -0
  176. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_agent_digest.py +0 -0
  177. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_agent_envelope.py +0 -0
  178. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_capabilities.py +0 -0
  179. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_cli_help.py +0 -0
  180. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_cli_verbs.py +0 -0
  181. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_cmaps.py +0 -0
  182. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_column_existence.py +0 -0
  183. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_config_addressing.py +0 -0
  184. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_config_edit.py +0 -0
  185. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_config_expand.py +0 -0
  186. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_data_access.py +0 -0
  187. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_data_describe.py +0 -0
  188. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_data_head_eval.py +0 -0
  189. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_data_loader_contracts.py +0 -0
  190. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_data_pipelines.py +0 -0
  191. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_density_cell_transform.py +0 -0
  192. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_diagnostic_guidance.py +0 -0
  193. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_docs_status_labels.py +0 -0
  194. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_doctor_partial.py +0 -0
  195. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_explain.py +0 -0
  196. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_figure_type_expander.py +0 -0
  197. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_fix_application.py +0 -0
  198. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_flowchart_renderer.py +0 -0
  199. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_interp_2d_transform.py +0 -0
  200. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_interp_natural_neighbor.py +0 -0
  201. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_interpolator_contracts.py +0 -0
  202. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_jpcontour_methods.py +0 -0
  203. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_man_cli.py +0 -0
  204. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_method_contracts.py +0 -0
  205. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_parse_data_metadata.py +0 -0
  206. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_pathing_contracts.py +0 -0
  207. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_posterior_hpd.py +0 -0
  208. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_render_health.py +0 -0
  209. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_row_ledger.py +0 -0
  210. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_schema_catalog.py +0 -0
  211. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_suggest.py +0 -0
  212. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_suggest_axes.py +0 -0
  213. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_template_catalog.py +0 -0
  214. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_template_json.py +0 -0
  215. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_transform_contracts.py +0 -0
  216. {jarvisplot-2.0.3 → jarvisplot-2.0.4}/tests/test_validate_no_matplotlib.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JarvisPLOT
3
- Version: 2.0.3
3
+ Version: 2.0.4
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -184,6 +184,7 @@ tests/test_data_head_eval.py
184
184
  tests/test_data_loader_contracts.py
185
185
  tests/test_data_pipelines.py
186
186
  tests/test_density_cell_transform.py
187
+ tests/test_design_runtime.py
187
188
  tests/test_diagnostic_guidance.py
188
189
  tests/test_docs_status_labels.py
189
190
  tests/test_doctor_partial.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JarvisPLOT
3
- Version: 2.0.3
3
+ Version: 2.0.4
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -0,0 +1,342 @@
1
+ #!/usr/bin/env python3
2
+ """Design-reference debug overlay for Jarvis-PLOT figures.
3
+
4
+ When a figure is rendered with ``debug: true`` in the YAML, this module draws a
5
+ dimension overlay on top of the finished figure: every axes defined by the style
6
+ card is outlined and annotated with
7
+
8
+ - its name (``ax`` / ``axc`` / ``axtri`` / ``ax0`` ...),
9
+ - its position rect ``[left, bottom, width, height]`` in figure fractions
10
+ (exactly the ``Frame.axes.<name>.rect`` value from the style JSON card), and
11
+ - its width / height converted to centimetres from ``figure.figsize``.
12
+
13
+ The primary axes additionally gets margin dimension lines (left / top / bottom
14
+ insets), and colorbar axes get a gap dimension to the primary axes. The
15
+ per-axes geometry is collected into one compact, ordered panel inside the
16
+ primary ``ax`` (or the first available primary-style axes). Keeping this
17
+ information in one place prevents small helper axes such as ``axlogo`` from
18
+ being covered by their own annotations.
19
+
20
+ The overlay never participates in data rendering; it is a read-only annotation
21
+ layer added just before the figure is saved. All drawing is wrapped so a
22
+ failure here can never break a normal plot.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import matplotlib as mpl
28
+
29
+
30
+ # overlay palette
31
+ _DIM = "#0A7FB6" # blue dimension lines + labels
32
+ _BOX = "#111111" # axes outline
33
+ _NAME = "#1A237E" # axes name + rect text
34
+ _MARGIN = "#FF3FA4" # pink figure border / caption
35
+
36
+ # order in which we look for the "primary" axes to fully dimension
37
+ _PRIMARY_ORDER = ("ax", "ax0", "ax1", "axtri")
38
+
39
+
40
+ def _raw(ax):
41
+ """Return the underlying matplotlib Axes for an adapter or raw Axes."""
42
+ return getattr(ax, "ax", ax)
43
+
44
+
45
+ def _position(ax):
46
+ try:
47
+ bb = _raw(ax).get_position()
48
+ return float(bb.x0), float(bb.y0), float(bb.width), float(bb.height)
49
+ except Exception:
50
+ return None
51
+
52
+
53
+ def _axis_info_lines(name, pos, w_cm, h_cm):
54
+ """Return the three-line layout entry used in the information panel."""
55
+ l, b, w, h = pos
56
+ return (
57
+ name,
58
+ f" rect=[{l:.3f}, {b:.3f}, {w:.3f}, {h:.3f}]",
59
+ f" width: {w * w_cm:.3f} cm height: {h * h_cm:.3f} cm",
60
+ )
61
+
62
+
63
+ def draw_design_reference(fig) -> None:
64
+ """Draw the dimension overlay onto ``fig`` (a JarvisPLOT Figure wrapper)."""
65
+ try:
66
+ _draw(fig)
67
+ except Exception as exc: # never break a real plot because of the overlay
68
+ logger = getattr(fig, "logger", None)
69
+ if logger is not None:
70
+ try:
71
+ logger.debug(f"design-reference overlay skipped: {exc}")
72
+ except Exception:
73
+ pass
74
+
75
+
76
+ def _draw(fig) -> None:
77
+ F = fig.fig
78
+ w_in, h_in = (float(v) for v in F.get_size_inches())
79
+ w_cm, h_cm = w_in * 2.54, h_in * 2.54
80
+
81
+ ov = F.add_axes([0.0, 0.0, 1.0, 1.0], zorder=10_000)
82
+ ov.set_xlim(0.0, 1.0)
83
+ ov.set_ylim(0.0, 1.0)
84
+ ov.set_axis_off()
85
+ ov.patch.set_alpha(0.0)
86
+
87
+ def label(x, y, text, *, color=_DIM, size=5.0, ha="center", va="center",
88
+ rotation=0, weight="normal", family="monospace", boxed=True,
89
+ box_alpha=0.82):
90
+ bbox = dict(boxstyle="square,pad=0.12", fc="white", ec="none", alpha=box_alpha) if boxed else None
91
+ ov.text(x, y, text, color=color, fontsize=size, ha=ha, va=va,
92
+ rotation=rotation, fontweight=weight, family=family, bbox=bbox,
93
+ zorder=10_002, clip_on=False)
94
+
95
+ def dim(x0, y0, x1, y1, text, *, vertical=False, line_offset=0.0):
96
+ """Draw a dimension line offset from the measured edge.
97
+
98
+ Labels sit just beside the line instead of being centered on top of
99
+ the arrow, so the blue dimension line remains visible.
100
+ """
101
+ if vertical:
102
+ x0 += line_offset
103
+ x1 += line_offset
104
+ else:
105
+ y0 += line_offset
106
+ y1 += line_offset
107
+
108
+ ov.annotate("", xy=(x1, y1), xytext=(x0, y0),
109
+ arrowprops=dict(arrowstyle="<|-|>", color=_DIM, lw=0.7,
110
+ shrinkA=0, shrinkB=0, mutation_scale=6),
111
+ zorder=10_001)
112
+ label_gap = 0.008
113
+ if vertical:
114
+ label(
115
+ (x0 + x1) / 2.0 + label_gap,
116
+ (y0 + y1) / 2.0,
117
+ text,
118
+ rotation=90,
119
+ ha="left",
120
+ boxed=True,
121
+ box_alpha=0.5,
122
+ )
123
+ else:
124
+ label(
125
+ (x0 + x1) / 2.0,
126
+ (y0 + y1) / 2.0 - label_gap,
127
+ text,
128
+ va="top",
129
+ boxed=True,
130
+ box_alpha=0.5,
131
+ )
132
+
133
+ # figure border + size caption
134
+ ov.add_patch(mpl.patches.Rectangle((0, 0), 1, 1, fill=False, ec=_MARGIN,
135
+ lw=0.9, zorder=10_001))
136
+ label(0.5, 0.995, f"design reference · figure {w_cm:.2f} cm × {h_cm:.2f} cm",
137
+ color=_MARGIN, size=6.0, va="top", weight="bold", family="DejaVu Sans")
138
+
139
+ # total figure height marker on the far left
140
+ dim(0.035, 0.0, 0.035, 1.0, f"{h_cm:.2f} cm", vertical=True)
141
+
142
+ # Resolve positions once. ``fig.axes`` is the Jarvis-PLOT named-axis
143
+ # mapping, not matplotlib's list of raw axes (which also gains ``ov``).
144
+ axis_positions = []
145
+ for name, ax in fig.axes.items():
146
+ pos = _position(ax)
147
+ if pos is not None:
148
+ axis_positions.append((name, pos))
149
+
150
+ # Pick the primary axes to fully dimension and to host the ordered info
151
+ # panel. The fallback keeps debug rendering useful for unusual cards that
152
+ # do not define the conventional ``ax``/``ax0`` names.
153
+ primary = next(
154
+ (n for n in _PRIMARY_ORDER if any(name == n for name, _ in axis_positions)),
155
+ axis_positions[0][0] if axis_positions else None,
156
+ )
157
+ positions_by_name = dict(axis_positions)
158
+ primary_pos = positions_by_name.get(primary) if primary else None
159
+
160
+ for name, pos in axis_positions:
161
+ l, b, w, h = pos
162
+
163
+ # Outline only, so the real plot underneath keeps its true colors.
164
+ # Axes without a real frame (for example axlogo) get a pink design
165
+ # outline; framed axes retain the ordinary black reference outline.
166
+ raw_axis = _raw(fig.axes[name])
167
+ get_frame_on = getattr(raw_axis, "get_frame_on", None)
168
+ frame_on = bool(get_frame_on()) if callable(get_frame_on) else True
169
+ outline_color = _BOX if frame_on else _MARGIN
170
+ ov.add_patch(mpl.patches.Rectangle((l, b), w, h, fill=False,
171
+ ec=outline_color, lw=0.45,
172
+ zorder=10_001))
173
+
174
+ if not frame_on:
175
+ # Only frameless axes get the hash-like endpoint treatment.
176
+ # Framed axes already have their own complete border.
177
+ extension = min(0.018, max(0.006, 0.09 * min(w, h)))
178
+ for xa, xb, y in (
179
+ (l - extension, l, b),
180
+ (l + w, l + w + extension, b),
181
+ (l - extension, l, b + h),
182
+ (l + w, l + w + extension, b + h),
183
+ ):
184
+ ov.plot([xa, xb], [y, y], color=outline_color, lw=0.45,
185
+ solid_capstyle="projecting", zorder=10_001,
186
+ clip_on=False)
187
+ for ya, yb, x in (
188
+ (b - extension, b, l),
189
+ (b + h, b + h + extension, l),
190
+ (b - extension, b, l + w),
191
+ (b + h, b + h + extension, l + w),
192
+ ):
193
+ ov.plot([x, x], [ya, yb], color=outline_color, lw=0.45,
194
+ solid_capstyle="projecting", zorder=10_001,
195
+ clip_on=False)
196
+
197
+ if name == primary:
198
+ yt = b + h
199
+ # Keep the two margin dimensions off the shared top-left corner:
200
+ # the horizontal one lands on the left spine below the corner,
201
+ # while the vertical ones land on the top/bottom spines to its
202
+ # right.
203
+ corner_gap = 0.018
204
+ dim(0.0, yt, l, yt, f"{l * w_cm:.3f} cm", line_offset=-corner_gap)
205
+ # top inset (axes top edge → figure top)
206
+ dim(
207
+ l,
208
+ yt,
209
+ l,
210
+ 1.0,
211
+ f"{(1.0 - yt) * h_cm:.3f} cm",
212
+ vertical=True,
213
+ line_offset=corner_gap,
214
+ )
215
+ # bottom inset (figure bottom → axes bottom edge)
216
+ dim(
217
+ l,
218
+ 0.0,
219
+ l,
220
+ b,
221
+ f"{b * h_cm:.3f} cm",
222
+ vertical=True,
223
+ line_offset=corner_gap,
224
+ )
225
+ elif name.startswith("axc") and primary_pos is not None:
226
+ # gap between the primary axes' right edge and this colorbar's left edge
227
+ pl, pb, pw, ph = primary_pos
228
+ ygap = b + h / 2.0
229
+ if l > pl + pw:
230
+ dim(
231
+ pl + pw,
232
+ ygap,
233
+ l,
234
+ ygap,
235
+ f"{(l - (pl + pw)) * w_cm:.3f} cm",
236
+ line_offset=0.024,
237
+ )
238
+
239
+ # Put every axes' identifying information in the main plotting area. The
240
+ # primary axis is listed first, followed by the remaining axes in the
241
+ # style-card order, so the panel is deterministic and easy to scan.
242
+ if primary_pos is not None:
243
+ pl, pb, pw, ph = primary_pos
244
+ ordered_names = [primary] + [
245
+ name for name, _ in axis_positions if name != primary
246
+ ]
247
+ header = "axes layout"
248
+ entries = [
249
+ _axis_info_lines(name, positions_by_name[name], w_cm, h_cm)
250
+ for name in ordered_names
251
+ ]
252
+
253
+ pad_x = min(0.018, max(0.004, pw * 0.025))
254
+ pad_y = min(0.018, max(0.004, ph * 0.035))
255
+ panel_x = pl + pad_x
256
+ panel_y = pb + ph - pad_y
257
+ # Keep the layout information card compact by default. This is the
258
+ # overlay card's bound only; it does not change the real axes limits.
259
+ panel_right = min(pl + pw - pad_x, 0.70)
260
+ panel_w = panel_right - panel_x
261
+ panel_h = ph - 2.0 * pad_y
262
+
263
+ if panel_w > 0.0 and panel_h > 0.0:
264
+ available_w_pt = panel_w * w_in * 72.0
265
+ available_h_pt = panel_h * h_in * 72.0
266
+ max_detail_chars = max(
267
+ len(line)
268
+ for entry in entries
269
+ for line in entry[1:]
270
+ )
271
+
272
+ # Preserve the old visual hierarchy: the axis name was 7 pt and
273
+ # bold, while its rect and cm dimensions were 4.6 pt. Shrink the
274
+ # complete group only when a short multi-panel axis cannot fit all
275
+ # entries at those sizes.
276
+ header_target = 6.0
277
+ name_target = 7.0
278
+ detail_target = 4.6
279
+ entry_gap_target = 5.0
280
+ header_leading = 1.20
281
+ name_leading = 1.15
282
+ detail_leading = 1.08
283
+ target_height = (
284
+ header_target * header_leading
285
+ + len(entries)
286
+ * (name_target * name_leading + 2.0 * detail_target * detail_leading)
287
+ + max(0, len(entries) - 1) * entry_gap_target
288
+ )
289
+ size_from_height = available_h_pt / max(1.0, target_height)
290
+ size_from_width = available_w_pt / max(
291
+ 1.0, 0.62 * detail_target * max_detail_chars
292
+ )
293
+ scale = min(1.0, size_from_height, size_from_width)
294
+ header_size = max(2.2, header_target * scale)
295
+ name_size = max(2.2, name_target * scale)
296
+ detail_size = max(1.8, detail_target * scale)
297
+ entry_gap = entry_gap_target * scale
298
+
299
+ ov.add_patch(
300
+ mpl.patches.FancyBboxPatch(
301
+ (panel_x, panel_y - panel_h),
302
+ panel_w,
303
+ panel_h,
304
+ boxstyle="round,pad=0.004",
305
+ facecolor="white",
306
+ edgecolor="none",
307
+ linewidth=0.0,
308
+ alpha=0.88,
309
+ zorder=10_001,
310
+ )
311
+ )
312
+ text_x = panel_x + min(0.008, panel_w * 0.02)
313
+ cursor_y = panel_y - min(0.006, panel_h * 0.03)
314
+ figure_height_pt = h_in * 72.0
315
+
316
+ def panel_text(text, size, *, weight="normal"):
317
+ nonlocal cursor_y
318
+ ov.text(
319
+ text_x,
320
+ cursor_y,
321
+ text,
322
+ color=_NAME,
323
+ fontsize=size,
324
+ ha="left",
325
+ va="top",
326
+ fontweight=weight,
327
+ family="monospace",
328
+ zorder=10_002,
329
+ clip_on=True,
330
+ )
331
+
332
+ panel_text(header, header_size, weight="bold")
333
+ cursor_y -= header_size * header_leading / figure_height_pt
334
+ for index, (name, rect_line, size_line) in enumerate(entries):
335
+ panel_text(name, name_size, weight="bold")
336
+ cursor_y -= name_size * name_leading / figure_height_pt
337
+ panel_text(rect_line, detail_size)
338
+ cursor_y -= detail_size * detail_leading / figure_height_pt
339
+ panel_text(size_line, detail_size)
340
+ cursor_y -= detail_size * detail_leading / figure_height_pt
341
+ if index < len(entries) - 1:
342
+ cursor_y -= entry_gap / figure_height_pt
@@ -53,6 +53,51 @@ except Exception:
53
53
  _JPLOT_VERSION_CACHE = None
54
54
 
55
55
 
56
+ def _resolve_colorbar_tick_position(
57
+ ticks_cfg: Mapping, default: str, *, horizontal: bool = False
58
+ ) -> str:
59
+ """Resolve a colorbar tick side from the merged JSON configuration.
60
+
61
+ ``ticks_position`` is the explicit contract when present. Older cards
62
+ only expressed the side through ``ticks.both.left/right``; preserve that
63
+ form instead of letting the vertical default move those labels to the
64
+ right.
65
+ """
66
+ raw_position = ticks_cfg.get("ticks_position")
67
+ valid_positions = (
68
+ {"top", "bottom", "both", "default", "none"}
69
+ if horizontal
70
+ else {"left", "right", "both", "default", "none"}
71
+ )
72
+ if raw_position is not None:
73
+ position = str(raw_position).strip().lower()
74
+ if position in valid_positions:
75
+ return position
76
+
77
+ both_cfg = ticks_cfg.get("both", {})
78
+ if isinstance(both_cfg, Mapping):
79
+ if horizontal:
80
+ top = bool(both_cfg.get("top", False))
81
+ bottom = bool(both_cfg.get("bottom", False))
82
+ if top and not bottom:
83
+ return "top"
84
+ if bottom and not top:
85
+ return "bottom"
86
+ if top and bottom:
87
+ return "both"
88
+ else:
89
+ left = bool(both_cfg.get("left", False))
90
+ right = bool(both_cfg.get("right", False))
91
+ if left and not right:
92
+ return "left"
93
+ if right and not left:
94
+ return "right"
95
+ if left and right:
96
+ return "both"
97
+
98
+ return default
99
+
100
+
56
101
  def _resolve_jplot_version() -> str:
57
102
  """Return the source/package version used for figure metadata."""
58
103
  global _JPLOT_VERSION_CACHE
@@ -616,9 +661,95 @@ class Figure:
616
661
  "used": False,
617
662
  }
618
663
  self.axes[name] = axc_obj
664
+ # Apply the card's tick/label contract immediately. Design-reference
665
+ # figures can intentionally have no coloured layers, so waiting for
666
+ # colorbar finalization would leave this visible axes at Matplotlib's
667
+ # default (left-side labels for a raw vertical axes).
668
+ self._apply_axc_tick_config(name, axc_obj)
619
669
  if self.logger:
620
670
  self.logger.debug(f"Created named colorbar axes -> {name}")
621
671
 
672
+ def _apply_axc_tick_config(self, name: str, axc_obj) -> None:
673
+ """Apply the merged JSON tick and label configuration to an axc axis."""
674
+ cb_frame = self.frame.get(name, {})
675
+ is_h = axc_is_horizontal(self.frame, name)
676
+ ticks_cfg = cb_frame.get("ticks", {}) if isinstance(cb_frame, dict) else {}
677
+ if not isinstance(ticks_cfg, dict):
678
+ ticks_cfg = {}
679
+
680
+ if not is_h:
681
+ _tp = _resolve_colorbar_tick_position(ticks_cfg, "right")
682
+ axc_obj.yaxis.set_ticks_position(_tp)
683
+ if _tp in {"left", "right"}:
684
+ axc_obj.yaxis.set_label_position(_tp)
685
+ axc_obj.tick_params(**ticks_cfg.get("both", {}))
686
+ axc_obj.tick_params(**ticks_cfg.get("major", {}))
687
+ axc_obj.tick_params(**ticks_cfg.get("minor", {}))
688
+ # Re-assert the JSON-selected side after all style tick_params;
689
+ # this prevents a broad ``both`` config from moving labels back
690
+ # to the opposite side of a vertical colorbar.
691
+ if _tp == "left":
692
+ axc_obj.tick_params(
693
+ axis="y", which="both",
694
+ left=True, right=False, labelleft=True, labelright=False,
695
+ )
696
+ elif _tp == "right":
697
+ axc_obj.tick_params(
698
+ axis="y", which="both",
699
+ left=False, right=True, labelleft=False, labelright=True,
700
+ )
701
+ elif _tp == "both":
702
+ axc_obj.tick_params(axis="y", which="both", left=True, right=True)
703
+ elif _tp == "none":
704
+ axc_obj.tick_params(
705
+ axis="y", which="both",
706
+ left=False, right=False, labelleft=False, labelright=False,
707
+ )
708
+ label_cfg = cb_frame.get("label", {}) if isinstance(cb_frame, dict) else {}
709
+ if not isinstance(label_cfg, dict):
710
+ label_cfg = {}
711
+ if label_cfg:
712
+ label_kwargs = dict(label_cfg)
713
+ label_text = label_kwargs.pop("ylabel", "")
714
+ axc_obj.set_ylabel(label_text if label_text is not None else "", **label_kwargs)
715
+ ylabel_coords = cb_frame.get("ylabel_coords") if isinstance(cb_frame, dict) else None
716
+ if ylabel_coords:
717
+ axc_obj.yaxis.set_label_coords(ylabel_coords["x"], ylabel_coords["y"])
718
+ self._apply_manual_ticks(axc_obj, "y", ticks_cfg.get("y", {}))
719
+ else:
720
+ _tp = _resolve_colorbar_tick_position(ticks_cfg, "top", horizontal=True)
721
+ axc_obj.xaxis.set_ticks_position(_tp)
722
+ if _tp in {"top", "bottom"}:
723
+ axc_obj.xaxis.set_label_position(_tp)
724
+ axc_obj.tick_params(**ticks_cfg.get("both", {}))
725
+ axc_obj.tick_params(**ticks_cfg.get("major", {}))
726
+ axc_obj.tick_params(**ticks_cfg.get("minor", {}))
727
+ if _tp == "top":
728
+ axc_obj.tick_params(
729
+ axis="x", which="both",
730
+ top=True, bottom=False, labeltop=True, labelbottom=False,
731
+ )
732
+ elif _tp == "bottom":
733
+ axc_obj.tick_params(
734
+ axis="x", which="both",
735
+ top=False, bottom=True, labeltop=False, labelbottom=True,
736
+ )
737
+ elif _tp == "both":
738
+ axc_obj.tick_params(axis="x", which="both", top=True, bottom=True)
739
+ elif _tp == "none":
740
+ axc_obj.tick_params(
741
+ axis="x", which="both",
742
+ top=False, bottom=False, labeltop=False, labelbottom=False,
743
+ )
744
+ label_cfg = cb_frame.get("label", {}) if isinstance(cb_frame, dict) else {}
745
+ if not isinstance(label_cfg, dict):
746
+ label_cfg = {}
747
+ if cb_frame.get("isxlabel") and label_cfg:
748
+ label_kwargs = dict(label_cfg)
749
+ label_text = label_kwargs.pop("xlabel", "")
750
+ axc_obj.set_xlabel(label_text if label_text is not None else "", **label_kwargs)
751
+ self._apply_manual_ticks(axc_obj, "x", ticks_cfg.get("x", {}))
752
+
622
753
  def _finalize_axc(self, name: str) -> None:
623
754
  """Draw the colorbar for a named axc* axes using its pre-built _cb state."""
624
755
  axc_obj = self.axes.get(name)
@@ -627,11 +758,7 @@ class Figure:
627
758
  if not axc_obj._cb.get("used"):
628
759
  return
629
760
 
630
- cb_frame = self.frame.get(name, {})
631
761
  is_h = axc_is_horizontal(self.frame, name)
632
- ticks_cfg = cb_frame.get("ticks", {}) if isinstance(cb_frame, dict) else {}
633
- if not isinstance(ticks_cfg, dict):
634
- ticks_cfg = {}
635
762
 
636
763
  mappable = mpl.cm.ScalarMappable(
637
764
  cmap=axc_obj._cb.get("cmap") or mpl.rcParams.get("image.cmap", "rainbow"),
@@ -650,23 +777,7 @@ class Figure:
650
777
  else:
651
778
  from matplotlib.ticker import AutoMinorLocator
652
779
  axc_obj.yaxis.set_minor_locator(AutoMinorLocator())
653
- _tp = ticks_cfg.get("ticks_position", "right")
654
- axc_obj.yaxis.set_ticks_position(_tp)
655
- axc_obj.yaxis.set_label_position("left" if _tp == "left" else "right")
656
- axc_obj.tick_params(**ticks_cfg.get("both", {}))
657
- axc_obj.tick_params(**ticks_cfg.get("major", {}))
658
- axc_obj.tick_params(**ticks_cfg.get("minor", {}))
659
- label_cfg = cb_frame.get("label", {}) if isinstance(cb_frame, dict) else {}
660
- if not isinstance(label_cfg, dict):
661
- label_cfg = {}
662
- if label_cfg:
663
- label_kwargs = dict(label_cfg)
664
- label_text = label_kwargs.pop("ylabel", "")
665
- axc_obj.set_ylabel(label_text if label_text is not None else "", **label_kwargs)
666
- ylabel_coords = cb_frame.get("ylabel_coords") if isinstance(cb_frame, dict) else None
667
- if ylabel_coords:
668
- axc_obj.yaxis.set_label_coords(ylabel_coords["x"], ylabel_coords["y"])
669
- self._apply_manual_ticks(axc_obj, "y", ticks_cfg.get("y", {}))
780
+ self._apply_axc_tick_config(name, axc_obj)
670
781
  else:
671
782
  cbar = self.fig.colorbar(mappable, cax=axc_obj, orientation="horizontal")
672
783
  cbar.minorticks_on()
@@ -678,20 +789,7 @@ class Figure:
678
789
  else:
679
790
  from matplotlib.ticker import AutoMinorLocator
680
791
  axc_obj.xaxis.set_minor_locator(AutoMinorLocator())
681
- _tp = ticks_cfg.get("ticks_position", "top")
682
- axc_obj.xaxis.set_ticks_position(_tp)
683
- axc_obj.xaxis.set_label_position("top" if _tp == "top" else "bottom")
684
- axc_obj.tick_params(**ticks_cfg.get("both", {}))
685
- axc_obj.tick_params(**ticks_cfg.get("major", {}))
686
- axc_obj.tick_params(**ticks_cfg.get("minor", {}))
687
- label_cfg = cb_frame.get("label", {}) if isinstance(cb_frame, dict) else {}
688
- if not isinstance(label_cfg, dict):
689
- label_cfg = {}
690
- if cb_frame.get("isxlabel") and label_cfg:
691
- label_kwargs = dict(label_cfg)
692
- label_text = label_kwargs.pop("xlabel", "")
693
- axc_obj.set_xlabel(label_text if label_text is not None else "", **label_kwargs)
694
- self._apply_manual_ticks(axc_obj, "x", ticks_cfg.get("x", {}))
792
+ self._apply_axc_tick_config(name, axc_obj)
695
793
 
696
794
  if self.logger:
697
795
  self.logger.debug(f"Finalized colorbar axes -> {name}")
@@ -86,18 +86,18 @@
86
86
  },
87
87
  "labels": {
88
88
  "xlabel": {
89
- "loc": "right",
89
+ "loc": "center",
90
90
  "fontsize": 12,
91
91
  "fontfamily": "STIXGeneral"
92
92
  },
93
93
  "ylabel": {
94
- "loc": "top",
94
+ "loc": "center",
95
95
  "fontsize": 12,
96
96
  "fontfamily": "STIXGeneral"
97
97
  },
98
98
  "ylabel_coords": {
99
99
  "x": -0.1,
100
- "y": 1.0
100
+ "y": 0.5
101
101
  }
102
102
  }
103
103
  },
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "JarvisPLOT"
7
- version = "2.0.3"
7
+ version = "2.0.4"
8
8
  description = "Jarvis-PLOT: YAML-driven plotting engine"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -74,6 +74,16 @@ def test_a4paper_2x1_rect_cards_define_default_title_params():
74
74
  assert not {"x", "y", "ha", "va"}.intersection(title_params)
75
75
 
76
76
 
77
+ def test_a4paper_2x1_rect_default_axis_labels_are_centered():
78
+ cards_dir = Path(__file__).resolve().parents[1] / "jarvisplot" / "cards" / "a4paper" / "2x1"
79
+ card = json.loads((cards_dir / "rect.json").read_text(encoding="utf-8"))
80
+ labels = card["Frame"]["ax"]["labels"]
81
+
82
+ assert labels["xlabel"]["loc"] == "center"
83
+ assert labels["ylabel"]["loc"] == "center"
84
+ assert labels["ylabel_coords"]["y"] == 0.5
85
+
86
+
77
87
  @pytest.mark.parametrize("axis_name", ["ax1", "ax2", "ax3", "ax4"])
78
88
  def test_numbered_axis_titles_only_render_on_ax0(axis_name):
79
89
  warnings = []