jarvisplot 2.1.6__tar.gz → 2.1.7__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 (267) hide show
  1. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/PKG-INFO +12 -2
  2. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/SOURCES.txt +12 -0
  3. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/PKG-INFO +12 -2
  4. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/README.md +11 -1
  5. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/adapters_rect.py +0 -1
  6. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/config_runtime.py +2 -0
  7. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/figure.py +45 -21
  8. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/layout_runtime.py +0 -2
  9. jarvisplot-2.1.7/jarvisplot/Figure/legend_runtime.py +94 -0
  10. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/capabilities.py +6 -0
  11. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/dynesty_runplot.json +5 -0
  12. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rect.json +1 -0
  13. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rect5x1.json +5 -0
  14. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +1 -0
  15. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rect_marginal.json +3 -0
  16. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rectratio.json +2 -0
  17. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/ternary.json +1 -0
  18. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +1 -0
  19. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +1 -0
  20. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/corrplot/diamond.json +1 -0
  21. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/corrplot/matrix.json +1 -0
  22. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/gambit/2x1/rect_cmap.json +1 -0
  23. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/gambit/2x1/ternary.json +1 -0
  24. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +1 -0
  25. jarvisplot-2.1.7/jarvisplot/cards/legends/compact/legend-card.json +15 -0
  26. jarvisplot-2.1.7/jarvisplot/cards/legends/default/legend-card.json +68 -0
  27. jarvisplot-2.1.7/jarvisplot/cards/legends/paper/legend-card.json +11 -0
  28. jarvisplot-2.1.7/jarvisplot/cards/legends/presentation/legend-card.json +16 -0
  29. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/diagnostic_guidance.py +15 -6
  30. jarvisplot-2.1.7/jarvisplot/legend_cards.py +250 -0
  31. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/man_render_agent.py +4 -0
  32. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/man_render_human.py +13 -0
  33. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/layer-method.yaml +5 -1
  34. jarvisplot-2.1.7/jarvisplot/manual_cards/legend.yaml +117 -0
  35. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/manifest.yaml +4 -0
  36. jarvisplot-2.1.7/jarvisplot/manual_cards/style-axes.yaml +58 -0
  37. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/render_health.py +9 -50
  38. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/core/figure.json +13 -5
  39. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/core/layer.json +13 -1
  40. jarvisplot-2.1.7/jarvisplot/schema/core/legend-card.json +106 -0
  41. jarvisplot-2.1.7/jarvisplot/schema/core/legend.json +53 -0
  42. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/manifest.json +2 -0
  43. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/validation.py +43 -0
  44. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/pyproject.toml +1 -1
  45. jarvisplot-2.1.7/tests/test_layer_legend.py +186 -0
  46. jarvisplot-2.1.7/tests/test_legend_cards.py +185 -0
  47. jarvisplot-2.1.7/tests/test_legend_runtime.py +138 -0
  48. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_render_health.py +15 -13
  49. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_schema_catalog.py +5 -7
  50. jarvisplot-2.1.6/jarvisplot/manual_cards/style-axes.yaml +0 -35
  51. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
  52. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/entry_points.txt +0 -0
  53. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/requires.txt +0 -0
  54. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/top_level.txt +0 -0
  55. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/__init__.py +0 -0
  56. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/adapters_ternary.py +0 -0
  57. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/adaptive_cdf_runtime.py +0 -0
  58. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/bin_stat_runtime.py +0 -0
  59. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
  60. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/colorbar_runtime.py +0 -0
  61. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/corr_layout.py +0 -0
  62. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/corr_layout_diamond.py +0 -0
  63. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/corr_order.py +0 -0
  64. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/correlation_runtime.py +0 -0
  65. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/corrplot_runtime.py +0 -0
  66. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/data_pipelines.py +0 -0
  67. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/debug_config.py +0 -0
  68. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/density_cell_runtime.py +0 -0
  69. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/design_runtime.py +0 -0
  70. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/distribution_1d_runtime.py +0 -0
  71. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/dynesty_runtime.py +0 -0
  72. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/figure_types.py +0 -0
  73. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/helper.py +0 -0
  74. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/interp_2d_runtime.py +0 -0
  75. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/interp_natural_neighbor.py +0 -0
  76. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/interp_natural_neighbor_exact.py +0 -0
  77. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/layer_runtime.py +0 -0
  78. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/method_registry.py +0 -0
  79. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/posterior_density_runtime.py +0 -0
  80. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/posterior_hpd.py +0 -0
  81. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/posterior_mesh.py +0 -0
  82. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/preprocessor.py +0 -0
  83. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/preprocessor_runtime.py +0 -0
  84. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/profile_runtime.py +0 -0
  85. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/significance_runtime.py +0 -0
  86. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/Figure/style_runtime.py +0 -0
  87. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/__init__.py +0 -0
  88. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/agent_digest.py +0 -0
  89. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/agent_io.py +0 -0
  90. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cache_store.py +0 -0
  91. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
  92. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
  93. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
  94. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
  95. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/args.json +0 -0
  96. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/colors/colormaps.json +0 -0
  97. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/default/output.json +0 -0
  98. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/flowchart/default.json +0 -0
  99. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
  100. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
  101. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/JarvisHEP.png +0 -0
  102. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/calculator.png +0 -0
  103. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/inputfile.png +0 -0
  104. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/opera.png +0 -0
  105. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/outputfile.png +0 -0
  106. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/sampler.png +0 -0
  107. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/icons/gambit.png +0 -0
  108. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/icons/gambit_small.png +0 -0
  109. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cards/style_preference.json +0 -0
  110. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cli.py +0 -0
  111. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/cli_help.py +0 -0
  112. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/client.py +0 -0
  113. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/column_demand.py +0 -0
  114. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/column_probe.py +0 -0
  115. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/config.py +0 -0
  116. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/config_address.py +0 -0
  117. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/core.py +0 -0
  118. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/core_assets.py +0 -0
  119. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/core_runtime.py +0 -0
  120. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/data_access.py +0 -0
  121. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/data_generation_contracts.py +0 -0
  122. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/data_loader.py +0 -0
  123. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/data_loader_hdf5.py +0 -0
  124. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/data_loader_runtime.py +0 -0
  125. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/data_loader_summary.py +0 -0
  126. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/dataset_types.py +0 -0
  127. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/diagnostics.py +0 -0
  128. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/distribution_1d_config.py +0 -0
  129. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/dryrun_runtime.py +0 -0
  130. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/expr_names.py +0 -0
  131. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/fix_apply.py +0 -0
  132. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/flowchart.py +0 -0
  133. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/generated_data.py +0 -0
  134. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/inner_func.py +0 -0
  135. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/man_catalog.py +0 -0
  136. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/man_methods.py +0 -0
  137. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/man_transforms.py +0 -0
  138. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/agent-output.yaml +0 -0
  139. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/cli-map.yaml +0 -0
  140. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/data-columns.yaml +0 -0
  141. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/debug-overlay.yaml +0 -0
  142. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/diagnostics.yaml +0 -0
  143. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/dryrun-viz.yaml +0 -0
  144. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/field-interp.yaml +0 -0
  145. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/generated-data.yaml +0 -0
  146. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/method_notes.yaml +0 -0
  147. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/type-correlation-matrix.yaml +0 -0
  148. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/type-posterior-2d.yaml +0 -0
  149. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/type-profile-2d.yaml +0 -0
  150. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/validate.yaml +0 -0
  151. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/workflow.yaml +0 -0
  152. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/manual_cards/yaml-root.yaml +0 -0
  153. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/memtrace.py +0 -0
  154. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/method_contracts.py +0 -0
  155. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/core/common.json +0 -0
  156. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/core/dataset.json +0 -0
  157. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/core/distribution-1d.json +0 -0
  158. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/core/generated-data.json +0 -0
  159. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/core/transform.json +0 -0
  160. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/bar.json +0 -0
  161. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/barh.json +0 -0
  162. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/contour.json +0 -0
  163. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/contourf.json +0 -0
  164. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/corrplot.json +0 -0
  165. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/dynesty_runplot.json +0 -0
  166. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/errorbar.json +0 -0
  167. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/fill.json +0 -0
  168. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/fill_between.json +0 -0
  169. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/fill_betweenx.json +0 -0
  170. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/hist.json +0 -0
  171. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/hist2d.json +0 -0
  172. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/imshow.json +0 -0
  173. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/jpcontour.json +0 -0
  174. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/jpcontourf.json +0 -0
  175. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/jpfield.json +0 -0
  176. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/pcolor.json +0 -0
  177. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/pcolormesh.json +0 -0
  178. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/plot.json +0 -0
  179. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/quiver.json +0 -0
  180. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/scatter.json +0 -0
  181. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/stairs.json +0 -0
  182. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/step.json +0 -0
  183. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/tricontour.json +0 -0
  184. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/tricontourf.json +0 -0
  185. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/tripcolor.json +0 -0
  186. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/tripcolor_axes.json +0 -0
  187. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/triplot.json +0 -0
  188. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/voronoi.json +0 -0
  189. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/methods/voronoif.json +0 -0
  190. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema/plot-config.json +0 -0
  191. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema_catalog.py +0 -0
  192. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/schema_diagnostics.py +0 -0
  193. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/templates_catalog.py +0 -0
  194. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/transform_contracts.py +0 -0
  195. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/utils/__init__.py +0 -0
  196. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/utils/cmaps.py +0 -0
  197. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/utils/dataframes.py +0 -0
  198. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/utils/expression.py +0 -0
  199. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/utils/interpolator.py +0 -0
  200. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/utils/pathing.py +0 -0
  201. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/verbs/__init__.py +0 -0
  202. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/verbs/cap.py +0 -0
  203. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/verbs/config_cmd.py +0 -0
  204. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/verbs/data.py +0 -0
  205. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/verbs/doctor.py +0 -0
  206. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/verbs/dryrun.py +0 -0
  207. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/verbs/explain.py +0 -0
  208. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/verbs/man.py +0 -0
  209. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/verbs/suggest.py +0 -0
  210. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/verbs/template.py +0 -0
  211. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/verbs/validate.py +0 -0
  212. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/jarvisplot/yaml_io.py +0 -0
  213. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/setup.cfg +0 -0
  214. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_adaptive_cdf.py +0 -0
  215. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_agent_digest.py +0 -0
  216. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_agent_envelope.py +0 -0
  217. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_axis_title.py +0 -0
  218. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_capabilities.py +0 -0
  219. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_card_debug_shape.py +0 -0
  220. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_cli_help.py +0 -0
  221. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_cli_verbs.py +0 -0
  222. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_cmaps.py +0 -0
  223. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_column_existence.py +0 -0
  224. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_config_addressing.py +0 -0
  225. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_config_edit.py +0 -0
  226. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_config_expand.py +0 -0
  227. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_correlation_runtime.py +0 -0
  228. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_corrplot.py +0 -0
  229. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_data_access.py +0 -0
  230. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_data_describe.py +0 -0
  231. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_data_head_eval.py +0 -0
  232. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_data_loader_contracts.py +0 -0
  233. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_data_pipelines.py +0 -0
  234. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_debug_config.py +0 -0
  235. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_debug_switches.py +0 -0
  236. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_debug_yaml_override.py +0 -0
  237. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_density_cell_transform.py +0 -0
  238. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_design_runtime.py +0 -0
  239. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_diagnostic_guidance.py +0 -0
  240. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_distribution_1d_transform.py +0 -0
  241. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_docs_status_labels.py +0 -0
  242. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_doctor_partial.py +0 -0
  243. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_explain.py +0 -0
  244. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_figure_contracts.py +0 -0
  245. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_figure_type_expander.py +0 -0
  246. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_fix_application.py +0 -0
  247. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_flowchart_renderer.py +0 -0
  248. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_generated_data.py +0 -0
  249. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_imshow_adapter.py +0 -0
  250. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_interp_2d_transform.py +0 -0
  251. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_interp_natural_neighbor.py +0 -0
  252. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_interpolator_contracts.py +0 -0
  253. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_jpcontour_methods.py +0 -0
  254. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_man_cli.py +0 -0
  255. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_method_contracts.py +0 -0
  256. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_parse_data_metadata.py +0 -0
  257. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_pathing_contracts.py +0 -0
  258. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_posterior_hpd.py +0 -0
  259. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_profile_runtime.py +0 -0
  260. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_row_ledger.py +0 -0
  261. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_suggest.py +0 -0
  262. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_suggest_axes.py +0 -0
  263. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_template_catalog.py +0 -0
  264. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_template_json.py +0 -0
  265. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_ternary_debug.py +0 -0
  266. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/tests/test_transform_contracts.py +0 -0
  267. {jarvisplot-2.1.6 → jarvisplot-2.1.7}/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.1.6
3
+ Version: 2.1.7
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -160,12 +160,22 @@ The loop `jplot man` prints is:
160
160
  carry `JP-*` codes; `jplot explain JP-XXX-NNN` expands any of them.
161
161
  5. `jplot <file>` — render, only when the figure itself is needed.
162
162
 
163
- `jplot man --json` is the machine-readable manual: 15 topics, plus a
163
+ `jplot man --json` is the machine-readable manual: a topic index, plus
164
164
  live pages for every registered layer method and transform step
165
165
  (`jplot man scatter`, `jplot man transform.profile`, `jplot man PDF1D`). Method contracts come from
166
166
  the same registry `jplot cap methods` reads, so a man page cannot drift from the
167
167
  code.
168
168
 
169
+ For compact legends and keys composed from real layers, use
170
+ `jplot man legend --json`. Declare `legend: {object, label, role}` on each drawing layer; components
171
+ with the same object regroup automatically using card templates such as
172
+ `band_line`, `line_errorbar` and `error_bounds`. The role is optional for a
173
+ single-component object. `frame.<axes>.legend` owns card selection, `order`,
174
+ `ncols`, position and spacing. JSON cards provide
175
+ reusable defaults, and YAML can override them. See
176
+ [the legend contract](docs/specs/LEGEND.md) and the
177
+ [self-contained example](Example/compact_legend.yaml).
178
+
169
179
  For continuous one-dimensional distributions from raw samples, use `PDF1D`
170
180
  (CDF plus PDF) or `CDF1D` in a data transform. `coordinates.x` owns `expr`,
171
181
  `lim`, and `grid`; optional `repeat` computes independent batch means and
@@ -37,6 +37,7 @@ jarvisplot/fix_apply.py
37
37
  jarvisplot/flowchart.py
38
38
  jarvisplot/generated_data.py
39
39
  jarvisplot/inner_func.py
40
+ jarvisplot/legend_cards.py
40
41
  jarvisplot/man_catalog.py
41
42
  jarvisplot/man_methods.py
42
43
  jarvisplot/man_render_agent.py
@@ -77,6 +78,7 @@ jarvisplot/Figure/interp_natural_neighbor.py
77
78
  jarvisplot/Figure/interp_natural_neighbor_exact.py
78
79
  jarvisplot/Figure/layer_runtime.py
79
80
  jarvisplot/Figure/layout_runtime.py
81
+ jarvisplot/Figure/legend_runtime.py
80
82
  jarvisplot/Figure/method_registry.py
81
83
  jarvisplot/Figure/posterior_density_runtime.py
82
84
  jarvisplot/Figure/posterior_hpd.py
@@ -120,6 +122,10 @@ jarvisplot/cards/icons/flowchart/inputfile.png
120
122
  jarvisplot/cards/icons/flowchart/opera.png
121
123
  jarvisplot/cards/icons/flowchart/outputfile.png
122
124
  jarvisplot/cards/icons/flowchart/sampler.png
125
+ jarvisplot/cards/legends/compact/legend-card.json
126
+ jarvisplot/cards/legends/default/legend-card.json
127
+ jarvisplot/cards/legends/paper/legend-card.json
128
+ jarvisplot/cards/legends/presentation/legend-card.json
123
129
  jarvisplot/manual_cards/agent-output.yaml
124
130
  jarvisplot/manual_cards/cli-map.yaml
125
131
  jarvisplot/manual_cards/data-columns.yaml
@@ -129,6 +135,7 @@ jarvisplot/manual_cards/dryrun-viz.yaml
129
135
  jarvisplot/manual_cards/field-interp.yaml
130
136
  jarvisplot/manual_cards/generated-data.yaml
131
137
  jarvisplot/manual_cards/layer-method.yaml
138
+ jarvisplot/manual_cards/legend.yaml
132
139
  jarvisplot/manual_cards/manifest.yaml
133
140
  jarvisplot/manual_cards/method_notes.yaml
134
141
  jarvisplot/manual_cards/style-axes.yaml
@@ -146,6 +153,8 @@ jarvisplot/schema/core/distribution-1d.json
146
153
  jarvisplot/schema/core/figure.json
147
154
  jarvisplot/schema/core/generated-data.json
148
155
  jarvisplot/schema/core/layer.json
156
+ jarvisplot/schema/core/legend-card.json
157
+ jarvisplot/schema/core/legend.json
149
158
  jarvisplot/schema/core/transform.json
150
159
  jarvisplot/schema/methods/bar.json
151
160
  jarvisplot/schema/methods/barh.json
@@ -234,6 +243,9 @@ tests/test_interp_2d_transform.py
234
243
  tests/test_interp_natural_neighbor.py
235
244
  tests/test_interpolator_contracts.py
236
245
  tests/test_jpcontour_methods.py
246
+ tests/test_layer_legend.py
247
+ tests/test_legend_cards.py
248
+ tests/test_legend_runtime.py
237
249
  tests/test_man_cli.py
238
250
  tests/test_method_contracts.py
239
251
  tests/test_parse_data_metadata.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JarvisPLOT
3
- Version: 2.1.6
3
+ Version: 2.1.7
4
4
  Summary: Jarvis-PLOT: YAML-driven plotting engine
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -160,12 +160,22 @@ The loop `jplot man` prints is:
160
160
  carry `JP-*` codes; `jplot explain JP-XXX-NNN` expands any of them.
161
161
  5. `jplot <file>` — render, only when the figure itself is needed.
162
162
 
163
- `jplot man --json` is the machine-readable manual: 15 topics, plus a
163
+ `jplot man --json` is the machine-readable manual: a topic index, plus
164
164
  live pages for every registered layer method and transform step
165
165
  (`jplot man scatter`, `jplot man transform.profile`, `jplot man PDF1D`). Method contracts come from
166
166
  the same registry `jplot cap methods` reads, so a man page cannot drift from the
167
167
  code.
168
168
 
169
+ For compact legends and keys composed from real layers, use
170
+ `jplot man legend --json`. Declare `legend: {object, label, role}` on each drawing layer; components
171
+ with the same object regroup automatically using card templates such as
172
+ `band_line`, `line_errorbar` and `error_bounds`. The role is optional for a
173
+ single-component object. `frame.<axes>.legend` owns card selection, `order`,
174
+ `ncols`, position and spacing. JSON cards provide
175
+ reusable defaults, and YAML can override them. See
176
+ [the legend contract](docs/specs/LEGEND.md) and the
177
+ [self-contained example](Example/compact_legend.yaml).
178
+
169
179
  For continuous one-dimensional distributions from raw samples, use `PDF1D`
170
180
  (CDF plus PDF) or `CDF1D` in a data transform. `coordinates.x` owns `expr`,
171
181
  `lim`, and `grid`; optional `repeat` computes independent batch means and
@@ -138,12 +138,22 @@ The loop `jplot man` prints is:
138
138
  carry `JP-*` codes; `jplot explain JP-XXX-NNN` expands any of them.
139
139
  5. `jplot <file>` — render, only when the figure itself is needed.
140
140
 
141
- `jplot man --json` is the machine-readable manual: 15 topics, plus a
141
+ `jplot man --json` is the machine-readable manual: a topic index, plus
142
142
  live pages for every registered layer method and transform step
143
143
  (`jplot man scatter`, `jplot man transform.profile`, `jplot man PDF1D`). Method contracts come from
144
144
  the same registry `jplot cap methods` reads, so a man page cannot drift from the
145
145
  code.
146
146
 
147
+ For compact legends and keys composed from real layers, use
148
+ `jplot man legend --json`. Declare `legend: {object, label, role}` on each drawing layer; components
149
+ with the same object regroup automatically using card templates such as
150
+ `band_line`, `line_errorbar` and `error_bounds`. The role is optional for a
151
+ single-component object. `frame.<axes>.legend` owns card selection, `order`,
152
+ `ncols`, position and spacing. JSON cards provide
153
+ reusable defaults, and YAML can override them. See
154
+ [the legend contract](docs/specs/LEGEND.md) and the
155
+ [self-contained example](Example/compact_legend.yaml).
156
+
147
157
  For continuous one-dimensional distributions from raw samples, use `PDF1D`
148
158
  (CDF plus PDF) or `CDF1D` in a data transform. `coordinates.x` owns `expr`,
149
159
  `lim`, and `grid`; optional `repeat` computes independent batch means and
@@ -36,7 +36,6 @@ class StdAxesAdapter:
36
36
  self._defaults = defaults or {}
37
37
  self._clip_path = clip_path # None means no cropping
38
38
  self.config = self._load_internal_config()
39
- self._legend = False
40
39
  self.status = "init" # lifecycle: init -> configured -> drawn -> finalized
41
40
  self.needs_finalize = True # allow some axes (e.g., logo) to opt out
42
41
 
@@ -13,6 +13,8 @@ def apply_figure_config(fig, info: Mapping) -> bool:
13
13
  fig._setup_error = None
14
14
 
15
15
  try:
16
+ if "legend" in info:
17
+ raise ValueError("Figure-level legend is not supported; declare items on layers and overall layout under frame.<axes>.legend")
16
18
  if "name" in info:
17
19
  fig.name = info["name"]
18
20
 
@@ -14,6 +14,8 @@ from .method_registry import resolve_callable
14
14
  from .style_runtime import resolve_style_bundle_payload
15
15
  from .config_runtime import apply_figure_config
16
16
  from .design_runtime import draw_design_reference
17
+ from .legend_runtime import apply_legend
18
+ from ..legend_cards import merge_axes_legend, effective_axes_legend
17
19
  from .layer_runtime import (
18
20
  load_layer_runtime_data as runtime_load_layer_runtime_data,
19
21
  release_layer_runtime_data as runtime_release_layer_runtime_data,
@@ -356,15 +358,35 @@ class Figure:
356
358
 
357
359
  @frame.setter
358
360
  def frame(self, value) -> None:
361
+ self._legend_disabled_axes = getattr(self, "_legend_disabled_axes", set())
362
+ for name, node in (value or {}).items():
363
+ if isinstance(node, dict) and "legend" in node:
364
+ declared = node["legend"]
365
+ if declared is False or (isinstance(declared, dict) and declared.get("enabled") is False):
366
+ self._legend_disabled_axes.add(name)
367
+ else:
368
+ self._legend_disabled_axes.discard(name)
359
369
  if self._frame is None:
360
370
  self._frame = value
361
371
  else:
372
+ # Legend order/roles are replacement lists, not deepmerge's
373
+ # default append lists. Explicit YAML also enables a disabled card
374
+ # default, unless enabled:false was supplied by the user.
375
+ value = deepcopy(value)
376
+ legends = {}
377
+ for name, node in (value or {}).items():
378
+ if isinstance(node, dict) and "legend" in node:
379
+ legends[name] = merge_axes_legend(
380
+ self._frame.get(name, {}).get("legend", False), node.pop("legend")
381
+ )
362
382
  for _name, _node in (value or {}).items():
363
383
  _color = _node.get("color") if isinstance(_node, dict) else None
364
384
  if isinstance(_color, dict) and "cmap" in _color:
365
385
  self._yaml_axc_cmaps.add(str(_name))
366
386
  from deepmerge import always_merger
367
387
  self._frame = always_merger.merge(self._frame, value)
388
+ for name, legend in legends.items():
389
+ self._frame.setdefault(name, {})["legend"] = legend
368
390
 
369
391
  @property
370
392
  def yaml_colorbar_cmaps(self) -> set:
@@ -383,6 +405,7 @@ class Figure:
383
405
  raise TypeError
384
406
  family, selected, bundle = resolve_style_bundle_payload(self.jpstyles, value)
385
407
  self._frame = deepcopy(bundle["Frame"])
408
+ self._legend_disabled_axes = set()
386
409
  self._yaml_axc_cmaps = set()
387
410
  self._style = deepcopy(bundle["Style"])
388
411
  self._debug_config = deepcopy(bundle.get("Debug", {}))
@@ -616,7 +639,6 @@ class Figure:
616
639
  clip_path=Path(vertices, codes) # 用 path 做 clip,transform 使用 ax.transData 已在适配器里处理
617
640
  )
618
641
  adapter._type = 'tri'
619
- adapter._legend = False
620
642
  adapter.layers = []
621
643
  adapter.status = 'configured'
622
644
  self.axes["axtri"] = adapter
@@ -1075,7 +1097,6 @@ class Figure:
1075
1097
  adapter = StdAxesAdapter(raw_ax)
1076
1098
  adapter._type = "rect"
1077
1099
  adapter.layers = []
1078
- adapter._legend = self.frame['ax'].get("legend", False)
1079
1100
  self.axes['ax'] = adapter
1080
1101
  adapter.status = 'configured'
1081
1102
 
@@ -1185,19 +1206,13 @@ class Figure:
1185
1206
  self.logger.debug("Loaded main rectangle axes -> ax")
1186
1207
 
1187
1208
  def _apply_legend_on_axes(self, ax_name: str, ax_obj, leg_cfg: dict):
1188
- """Apply a legend on a specific axes using a YAML dict stored under frame['axes'][ax_name]['legend'].
1189
- Supports an optional 'enabled' key (default True). Any 'axes' key will be ignored here."""
1190
- if not isinstance(leg_cfg, dict):
1191
- return
1192
- if leg_cfg.get("enabled", True) is False:
1193
- return
1194
- kw = dict(leg_cfg)
1195
- kw.pop("axes", None) # per-axes legend doesn't need this
1196
- try:
1197
- (ax_obj.ax if hasattr(ax_obj, "ax") else ax_obj).legend(**kw)
1198
- except Exception as e:
1199
- if self.logger:
1200
- self.logger.warning(f"Legend apply failed on '{ax_name}': {e}")
1209
+ """Apply frame.<axes>.legend after all real layer handles are available."""
1210
+ target_ax = ax_obj.ax if hasattr(ax_obj, "ax") else ax_obj
1211
+ layer_specs = getattr(self, "_legend_layers", {}).get(ax_name, [])
1212
+ return apply_legend(
1213
+ target_ax, leg_cfg, getattr(self, "_legend_layer_handles", {}).get(ax_name, {}),
1214
+ base_dir=self._yaml_dir, layer_specs=layer_specs,
1215
+ )
1201
1216
 
1202
1217
 
1203
1218
  def _install_tri_auto_clip(self, ax):
@@ -1326,10 +1341,17 @@ class Figure:
1326
1341
  # determined by frame config + data — never by render order.
1327
1342
  self._prescan_colorbar_ranges()
1328
1343
 
1344
+ self._legend_layer_handles = {}
1345
+ self._legend_layers = {}
1346
+ axes_names = {id(ax): name for name, ax in self.axes.items()}
1329
1347
  for ax, ly in self._render_queue:
1330
1348
  runtime_load_layer_runtime_data(self, ly)
1331
1349
  try:
1332
- runtime_render_layer(self, ax, ly)
1350
+ rendered = runtime_render_layer(self, ax, ly)
1351
+ name = axes_names[id(ax)]
1352
+ specs = self._legend_layers.setdefault(name, [])
1353
+ self._legend_layer_handles.setdefault(name, {})[f"@layer:{len(specs)}"] = rendered
1354
+ specs.append(ly.get("layer_spec", {}))
1333
1355
  finally:
1334
1356
  runtime_release_layer_runtime_data(self, ly)
1335
1357
 
@@ -1340,12 +1362,14 @@ class Figure:
1340
1362
 
1341
1363
  for name, ax in self.axes.items():
1342
1364
  try:
1343
- if hasattr(ax, "_legend") and ax._legend:
1344
- target_ax = ax.ax if hasattr(ax, "ax") else ax
1345
- target_ax.legend(**ax._legend)
1365
+ legend_config = self.frame.get(name, {}).get("legend", False)
1366
+ legend_config = effective_axes_legend(
1367
+ legend_config, self._legend_layers.get(name, []),
1368
+ explicitly_disabled=name in getattr(self, "_legend_disabled_axes", set()),
1369
+ )
1370
+ self._apply_legend_on_axes(name, ax, legend_config)
1346
1371
  except Exception as e:
1347
- if self.logger:
1348
- self.logger.warning(f"Legend draw failed on axes '{name}': {e}")
1372
+ raise ValueError(f"Legend draw failed on axes '{name}': {e}") from e
1349
1373
 
1350
1374
  # Finalize all colorbar axes (axc and any named axc*)
1351
1375
  for name in list(self.axes.keys()):
@@ -164,7 +164,6 @@ def ensure_rect_axes(fig, ax_name: str, kwgs: dict):
164
164
  adapter = StdAxesAdapter(raw_ax)
165
165
  adapter._type = "rect"
166
166
  adapter.layers = []
167
- adapter._legend = fig.frame.get(ax_name, {}).get("legend", False)
168
167
  fig.axes[ax_name] = adapter
169
168
  adapter.status = "configured"
170
169
 
@@ -309,7 +308,6 @@ def ensure_corr_axes(fig, kwgs: dict):
309
308
  adapter = StdAxesAdapter(raw_ax)
310
309
  adapter._type = "rect"
311
310
  adapter.layers = []
312
- adapter._legend = False
313
311
  fig.axes[name] = adapter
314
312
  adapter.status = "configured"
315
313
 
@@ -0,0 +1,94 @@
1
+ """Native legends with reusable spacing and keys composed from real layers."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+
6
+ from matplotlib.legend import Legend
7
+ from matplotlib.legend_handler import HandlerBase, HandlerTuple
8
+ from matplotlib.transforms import Affine2D
9
+ from ..legend_cards import resolve_axes_legend
10
+
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class StackedKey:
15
+ handles: tuple
16
+
17
+
18
+ class HandlerStack(HandlerBase):
19
+ """Place constituent handles in vertical slots inside one native key."""
20
+
21
+ def create_artists(self, legend, orig_handle, xdescent, ydescent,
22
+ width, height, fontsize, trans):
23
+ artists = []
24
+ slot = height / len(orig_handle.handles)
25
+ handler_map = legend.get_legend_handler_map()
26
+ for index, handle in enumerate(orig_handle.handles):
27
+ handler = legend.get_legend_handler(handler_map, handle)
28
+ # Position the whole delegated key, rather than encoding the offset
29
+ # in ydescent: handlers interpret descent differently (Line2D halves
30
+ # it), which compresses the gaps and shifts the stack off center.
31
+ # YAML order is top to bottom inside the original handle box.
32
+ # Native line handlers center at (height - ydescent) / 2.
33
+ # Split the descent equally so increasing handleheight does not
34
+ # shift a stacked center line below adjacent native line keys.
35
+ offset = height - (index + 1) * slot - ydescent / 2
36
+ slot_transform = Affine2D().translate(0, offset) + trans
37
+ artists.extend(handler.create_artists(
38
+ legend, handle, xdescent, 0,
39
+ width, slot, fontsize, slot_transform,
40
+ ))
41
+ return artists
42
+
43
+
44
+ def representative_handle(rendered):
45
+ """Require one supported artist/container; multiple series are ambiguous."""
46
+ from matplotlib.artist import Artist
47
+ from matplotlib.container import Container
48
+
49
+ handler_map = Legend.get_default_handler_map()
50
+ def collect(value):
51
+ if isinstance(value, (Artist, Container)):
52
+ return [value] if Legend.get_legend_handler(handler_map, value) is not None else []
53
+ if isinstance(value, (list, tuple)):
54
+ return [handle for child in value for handle in collect(child)]
55
+ return []
56
+
57
+ handles = collect(rendered)
58
+ if len(handles) > 1:
59
+ raise ValueError("Layer returned multiple legend handles; split its series into separate layers with explicit legend declarations")
60
+ return handles[0] if handles else None
61
+
62
+
63
+ def apply_legend(ax, config, layer_handles=None, *, base_dir=None, layer_specs=None):
64
+ """Apply frame.<axes>.legend without modifying config or data artists."""
65
+ kw, entries = resolve_axes_legend(config, layer_specs or [], base_dir=base_dir)
66
+ if config is False or (isinstance(config, dict) and config.get("enabled", True) is False):
67
+ return None
68
+
69
+ handles, labels = [], []
70
+ layer_handles = layer_handles or {}
71
+ for entry in entries:
72
+ names, key = entry["layers"], entry["key"]
73
+ parts = []
74
+ for name in names:
75
+ if name not in layer_handles:
76
+ raise ValueError(f"Legend object {entry['object']!r} is missing its rendered component {name!r}")
77
+ handle = representative_handle(layer_handles[name])
78
+ if handle is None:
79
+ raise ValueError(f"Legend object {entry['object']!r} component {name!r} has no supported legend handle")
80
+ parts.append(handle)
81
+ if len(parts) == 1:
82
+ handles.append(parts[0])
83
+ elif key == "stack":
84
+ handles.append(StackedKey(tuple(parts)))
85
+ else:
86
+ handles.append(tuple(parts))
87
+ labels.append(entry["label"])
88
+
89
+ kw["handler_map"] = {
90
+ tuple: HandlerTuple(ndivide=1),
91
+ StackedKey: HandlerStack(),
92
+ **kw.get("handler_map", {}),
93
+ }
94
+ return ax.legend(handles=handles, labels=labels, **kw)
@@ -258,6 +258,12 @@ def _styles() -> list[dict[str, Any]]:
258
258
  axes = frame.get("axes", {})
259
259
  entry["usable"] = True
260
260
  entry["axes"] = sorted(axes) if isinstance(axes, dict) else []
261
+ entry["legend_cards"] = {
262
+ name: node["legend"]["card"]
263
+ for name, node in frame.items()
264
+ if isinstance(node, dict) and isinstance(node.get("legend"), dict)
265
+ and "card" in node["legend"]
266
+ }
261
267
  contract = card.get("Contract")
262
268
  if isinstance(contract, dict):
263
269
  entry["contract"] = {
@@ -68,6 +68,7 @@
68
68
  }
69
69
  },
70
70
  "ax0": {
71
+ "legend": {"enabled": false, "card": "compact"},
71
72
  "ticks": {
72
73
  "both": {
73
74
  "labelsize": 6,
@@ -109,6 +110,7 @@
109
110
  }
110
111
  },
111
112
  "ax1": {
113
+ "legend": {"enabled": false, "card": "compact"},
112
114
  "ticks": {
113
115
  "both": {
114
116
  "labelsize": 6,
@@ -162,6 +164,7 @@
162
164
  ]
163
165
  },
164
166
  "ax2": {
167
+ "legend": {"enabled": false, "card": "compact"},
165
168
  "ticks": {
166
169
  "both": {
167
170
  "labelsize": 6,
@@ -203,6 +206,7 @@
203
206
  }
204
207
  },
205
208
  "ax3": {
209
+ "legend": {"enabled": false, "card": "compact"},
206
210
  "ticks": {
207
211
  "both": {
208
212
  "labelsize": 6,
@@ -244,6 +248,7 @@
244
248
  }
245
249
  },
246
250
  "ax4": {
251
+ "legend": {"enabled": false, "card": "compact"},
247
252
  "ticks": {
248
253
  "both": {
249
254
  "labelsize": 6,
@@ -36,6 +36,7 @@
36
36
  }
37
37
  },
38
38
  "ax": {
39
+ "legend": {"enabled": false, "card": "compact"},
39
40
  "title_params": {
40
41
  "position": "top",
41
42
  "fontsize": "small",
@@ -68,6 +68,7 @@
68
68
  }
69
69
  },
70
70
  "ax0": {
71
+ "legend": {"enabled": false, "card": "compact"},
71
72
  "title_params": {
72
73
  "position": "top",
73
74
  "fontsize": "small",
@@ -132,6 +133,7 @@
132
133
  }
133
134
  },
134
135
  "ax1": {
136
+ "legend": {"enabled": false, "card": "compact"},
135
137
  "frame": {
136
138
  "linewidth": 0.8,
137
139
  "solid_capstyle": "projecting",
@@ -188,6 +190,7 @@
188
190
  }
189
191
  },
190
192
  "ax2": {
193
+ "legend": {"enabled": false, "card": "compact"},
191
194
  "frame": {
192
195
  "linewidth": 0.8,
193
196
  "solid_capstyle": "projecting",
@@ -244,6 +247,7 @@
244
247
  }
245
248
  },
246
249
  "ax3": {
250
+ "legend": {"enabled": false, "card": "compact"},
247
251
  "frame": {
248
252
  "linewidth": 0.8,
249
253
  "solid_capstyle": "projecting",
@@ -300,6 +304,7 @@
300
304
  }
301
305
  },
302
306
  "ax4": {
307
+ "legend": {"enabled": false, "card": "compact"},
303
308
  "frame": {
304
309
  "linewidth": 0.8,
305
310
  "solid_capstyle": "projecting",
@@ -45,6 +45,7 @@
45
45
  }
46
46
  },
47
47
  "ax": {
48
+ "legend": {"enabled": false, "card": "compact"},
48
49
  "title_params": {
49
50
  "position": "top",
50
51
  "fontsize": "small",
@@ -61,6 +61,7 @@
61
61
  }
62
62
  },
63
63
  "ax": {
64
+ "legend": {"enabled": false, "card": "compact"},
64
65
  "title_params": {
65
66
  "position": "top",
66
67
  "fontsize": "small",
@@ -132,6 +133,7 @@
132
133
  }
133
134
  },
134
135
  "axl": {
136
+ "legend": {"enabled": false, "card": "compact"},
135
137
  "title_params": {
136
138
  "position": "top",
137
139
  "fontsize": "small",
@@ -207,6 +209,7 @@
207
209
  }
208
210
  },
209
211
  "axb": {
212
+ "legend": {"enabled": false, "card": "compact"},
210
213
  "title_params": {
211
214
  "position": "top",
212
215
  "fontsize": "small",
@@ -44,6 +44,7 @@
44
44
  }
45
45
  },
46
46
  "ax": {
47
+ "legend": {"enabled": false, "card": "compact"},
47
48
  "title_params": {
48
49
  "position": "top",
49
50
  "fontsize": "small",
@@ -112,6 +113,7 @@
112
113
  }
113
114
  },
114
115
  "axr": {
116
+ "legend": {"enabled": false, "card": "compact"},
115
117
  "title_params": {
116
118
  "position": "top",
117
119
  "fontsize": "small",
@@ -48,6 +48,7 @@
48
48
  }
49
49
  },
50
50
  "axtri": {
51
+ "legend": {"enabled": false, "card": "compact"},
51
52
  "frame": {
52
53
  "linewidth": 0.8,
53
54
  "solid_capstyle": "projecting",
@@ -57,6 +57,7 @@
57
57
  }
58
58
  },
59
59
  "axtri": {
60
+ "legend": {"enabled": false, "card": "compact"},
60
61
  "frame": {
61
62
  "linewidth": 0.8,
62
63
  "solid_capstyle": "projecting",
@@ -45,6 +45,7 @@
45
45
  }
46
46
  },
47
47
  "ax": {
48
+ "legend": {"enabled": false, "card": "compact"},
48
49
  "frame": {
49
50
  "linewidth": 0.8,
50
51
  "solid_capstyle": "projecting",
@@ -62,6 +62,7 @@
62
62
  }
63
63
  },
64
64
  "axcorr": {
65
+ "legend": {"enabled": false, "card": "compact"},
65
66
  "title_params": {
66
67
  "position": "top",
67
68
  "fontsize": "small",
@@ -54,6 +54,7 @@
54
54
  }
55
55
  },
56
56
  "axcorr": {
57
+ "legend": {"enabled": false, "card": "compact"},
57
58
  "title_params": {
58
59
  "position": "top",
59
60
  "fontsize": "small",
@@ -46,6 +46,7 @@
46
46
  }
47
47
  },
48
48
  "ax": {
49
+ "legend": {"enabled": false, "card": "compact"},
49
50
  "frame": {
50
51
  "linewidth": 0.8,
51
52
  "solid_capstyle": "projecting",
@@ -48,6 +48,7 @@
48
48
  }
49
49
  },
50
50
  "axtri": {
51
+ "legend": {"enabled": false, "card": "compact"},
51
52
  "frame": {
52
53
  "linewidth": 0.8,
53
54
  "solid_capstyle": "projecting",
@@ -58,6 +58,7 @@
58
58
  }
59
59
  },
60
60
  "axtri": {
61
+ "legend": {"enabled": false, "card": "compact"},
61
62
  "frame": {
62
63
  "linewidth": 0.8,
63
64
  "solid_capstyle": "projecting",
@@ -0,0 +1,15 @@
1
+ {
2
+ "schema_version": 1,
3
+ "title": "Compact",
4
+ "extends": "default",
5
+ "legend": {
6
+ "frameon": false,
7
+ "borderpad": 0.25,
8
+ "labelspacing": 0.25,
9
+ "handlelength": 1.6,
10
+ "handleheight": 0.7,
11
+ "handletextpad": 0.5,
12
+ "columnspacing": 1.0,
13
+ "borderaxespad": 0.4
14
+ }
15
+ }