jarvisplot 2.1.5__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.5 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/PKG-INFO +23 -4
  2. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/SOURCES.txt +18 -0
  3. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/PKG-INFO +23 -4
  4. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/README.md +22 -3
  5. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/adapters_rect.py +0 -1
  6. jarvisplot-2.1.7/jarvisplot/Figure/adaptive_cdf_runtime.py +131 -0
  7. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/config_runtime.py +2 -0
  8. jarvisplot-2.1.7/jarvisplot/Figure/distribution_1d_runtime.py +165 -0
  9. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/figure.py +45 -21
  10. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/layout_runtime.py +0 -2
  11. jarvisplot-2.1.7/jarvisplot/Figure/legend_runtime.py +94 -0
  12. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/preprocessor.py +15 -0
  13. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/preprocessor_runtime.py +6 -0
  14. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/capabilities.py +6 -0
  15. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/dynesty_runplot.json +5 -0
  16. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rect.json +1 -0
  17. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rect5x1.json +5 -0
  18. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +1 -0
  19. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rect_marginal.json +3 -0
  20. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rectratio.json +2 -0
  21. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/ternary.json +1 -0
  22. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +1 -0
  23. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +1 -0
  24. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/corrplot/diamond.json +1 -0
  25. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/corrplot/matrix.json +1 -0
  26. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/gambit/2x1/rect_cmap.json +1 -0
  27. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/gambit/2x1/ternary.json +1 -0
  28. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +1 -0
  29. jarvisplot-2.1.7/jarvisplot/cards/legends/compact/legend-card.json +15 -0
  30. jarvisplot-2.1.7/jarvisplot/cards/legends/default/legend-card.json +68 -0
  31. jarvisplot-2.1.7/jarvisplot/cards/legends/paper/legend-card.json +11 -0
  32. jarvisplot-2.1.7/jarvisplot/cards/legends/presentation/legend-card.json +16 -0
  33. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/column_demand.py +10 -0
  34. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/data_loader.py +6 -0
  35. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/data_loader_runtime.py +6 -1
  36. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/diagnostic_guidance.py +15 -6
  37. jarvisplot-2.1.7/jarvisplot/distribution_1d_config.py +167 -0
  38. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/dryrun_runtime.py +12 -3
  39. jarvisplot-2.1.7/jarvisplot/legend_cards.py +250 -0
  40. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/man_render_agent.py +4 -0
  41. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/man_render_human.py +13 -0
  42. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/man_transforms.py +8 -5
  43. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/layer-method.yaml +5 -1
  44. jarvisplot-2.1.7/jarvisplot/manual_cards/legend.yaml +117 -0
  45. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/manifest.yaml +4 -0
  46. jarvisplot-2.1.7/jarvisplot/manual_cards/style-axes.yaml +58 -0
  47. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/render_health.py +9 -50
  48. jarvisplot-2.1.7/jarvisplot/schema/core/distribution-1d.json +72 -0
  49. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/core/figure.json +13 -5
  50. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/core/layer.json +13 -1
  51. jarvisplot-2.1.7/jarvisplot/schema/core/legend-card.json +106 -0
  52. jarvisplot-2.1.7/jarvisplot/schema/core/legend.json +53 -0
  53. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/core/transform.json +8 -0
  54. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/manifest.json +5 -0
  55. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/transform_contracts.py +67 -0
  56. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/validation.py +43 -0
  57. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/pyproject.toml +1 -1
  58. jarvisplot-2.1.7/tests/test_adaptive_cdf.py +141 -0
  59. jarvisplot-2.1.7/tests/test_distribution_1d_transform.py +234 -0
  60. jarvisplot-2.1.7/tests/test_layer_legend.py +186 -0
  61. jarvisplot-2.1.7/tests/test_legend_cards.py +185 -0
  62. jarvisplot-2.1.7/tests/test_legend_runtime.py +138 -0
  63. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_render_health.py +15 -13
  64. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_schema_catalog.py +5 -7
  65. jarvisplot-2.1.5/jarvisplot/manual_cards/style-axes.yaml +0 -35
  66. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
  67. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/entry_points.txt +0 -0
  68. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/requires.txt +0 -0
  69. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/top_level.txt +0 -0
  70. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/__init__.py +0 -0
  71. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/adapters_ternary.py +0 -0
  72. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/bin_stat_runtime.py +0 -0
  73. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
  74. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/colorbar_runtime.py +0 -0
  75. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/corr_layout.py +0 -0
  76. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/corr_layout_diamond.py +0 -0
  77. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/corr_order.py +0 -0
  78. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/correlation_runtime.py +0 -0
  79. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/corrplot_runtime.py +0 -0
  80. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/data_pipelines.py +0 -0
  81. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/debug_config.py +0 -0
  82. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/density_cell_runtime.py +0 -0
  83. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/design_runtime.py +0 -0
  84. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/dynesty_runtime.py +0 -0
  85. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/figure_types.py +0 -0
  86. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/helper.py +0 -0
  87. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/interp_2d_runtime.py +0 -0
  88. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/interp_natural_neighbor.py +0 -0
  89. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/interp_natural_neighbor_exact.py +0 -0
  90. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/layer_runtime.py +0 -0
  91. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/method_registry.py +0 -0
  92. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/posterior_density_runtime.py +0 -0
  93. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/posterior_hpd.py +0 -0
  94. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/posterior_mesh.py +0 -0
  95. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/profile_runtime.py +0 -0
  96. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/significance_runtime.py +0 -0
  97. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/style_runtime.py +0 -0
  98. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/__init__.py +0 -0
  99. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/agent_digest.py +0 -0
  100. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/agent_io.py +0 -0
  101. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cache_store.py +0 -0
  102. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
  103. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
  104. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
  105. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
  106. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/args.json +0 -0
  107. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/colors/colormaps.json +0 -0
  108. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/default/output.json +0 -0
  109. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/flowchart/default.json +0 -0
  110. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
  111. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
  112. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/JarvisHEP.png +0 -0
  113. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/calculator.png +0 -0
  114. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/inputfile.png +0 -0
  115. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/opera.png +0 -0
  116. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/outputfile.png +0 -0
  117. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/sampler.png +0 -0
  118. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/gambit.png +0 -0
  119. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/gambit_small.png +0 -0
  120. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/style_preference.json +0 -0
  121. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cli.py +0 -0
  122. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cli_help.py +0 -0
  123. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/client.py +0 -0
  124. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/column_probe.py +0 -0
  125. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/config.py +0 -0
  126. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/config_address.py +0 -0
  127. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/core.py +0 -0
  128. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/core_assets.py +0 -0
  129. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/core_runtime.py +0 -0
  130. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/data_access.py +0 -0
  131. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/data_generation_contracts.py +0 -0
  132. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/data_loader_hdf5.py +0 -0
  133. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/data_loader_summary.py +0 -0
  134. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/dataset_types.py +0 -0
  135. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/diagnostics.py +0 -0
  136. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/expr_names.py +0 -0
  137. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/fix_apply.py +0 -0
  138. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/flowchart.py +0 -0
  139. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/generated_data.py +0 -0
  140. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/inner_func.py +0 -0
  141. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/man_catalog.py +0 -0
  142. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/man_methods.py +0 -0
  143. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/agent-output.yaml +0 -0
  144. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/cli-map.yaml +0 -0
  145. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/data-columns.yaml +0 -0
  146. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/debug-overlay.yaml +0 -0
  147. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/diagnostics.yaml +0 -0
  148. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/dryrun-viz.yaml +0 -0
  149. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/field-interp.yaml +0 -0
  150. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/generated-data.yaml +0 -0
  151. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/method_notes.yaml +0 -0
  152. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/type-correlation-matrix.yaml +0 -0
  153. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/type-posterior-2d.yaml +0 -0
  154. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/type-profile-2d.yaml +0 -0
  155. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/validate.yaml +0 -0
  156. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/workflow.yaml +0 -0
  157. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/yaml-root.yaml +0 -0
  158. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/memtrace.py +0 -0
  159. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/method_contracts.py +0 -0
  160. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/core/common.json +0 -0
  161. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/core/dataset.json +0 -0
  162. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/core/generated-data.json +0 -0
  163. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/bar.json +0 -0
  164. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/barh.json +0 -0
  165. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/contour.json +0 -0
  166. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/contourf.json +0 -0
  167. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/corrplot.json +0 -0
  168. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/dynesty_runplot.json +0 -0
  169. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/errorbar.json +0 -0
  170. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/fill.json +0 -0
  171. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/fill_between.json +0 -0
  172. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/fill_betweenx.json +0 -0
  173. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/hist.json +0 -0
  174. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/hist2d.json +0 -0
  175. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/imshow.json +0 -0
  176. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/jpcontour.json +0 -0
  177. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/jpcontourf.json +0 -0
  178. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/jpfield.json +0 -0
  179. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/pcolor.json +0 -0
  180. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/pcolormesh.json +0 -0
  181. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/plot.json +0 -0
  182. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/quiver.json +0 -0
  183. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/scatter.json +0 -0
  184. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/stairs.json +0 -0
  185. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/step.json +0 -0
  186. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/tricontour.json +0 -0
  187. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/tricontourf.json +0 -0
  188. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/tripcolor.json +0 -0
  189. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/tripcolor_axes.json +0 -0
  190. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/triplot.json +0 -0
  191. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/voronoi.json +0 -0
  192. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/voronoif.json +0 -0
  193. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/plot-config.json +0 -0
  194. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema_catalog.py +0 -0
  195. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema_diagnostics.py +0 -0
  196. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/templates_catalog.py +0 -0
  197. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/utils/__init__.py +0 -0
  198. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/utils/cmaps.py +0 -0
  199. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/utils/dataframes.py +0 -0
  200. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/utils/expression.py +0 -0
  201. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/utils/interpolator.py +0 -0
  202. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/utils/pathing.py +0 -0
  203. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/__init__.py +0 -0
  204. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/cap.py +0 -0
  205. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/config_cmd.py +0 -0
  206. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/data.py +0 -0
  207. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/doctor.py +0 -0
  208. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/dryrun.py +0 -0
  209. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/explain.py +0 -0
  210. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/man.py +0 -0
  211. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/suggest.py +0 -0
  212. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/template.py +0 -0
  213. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/validate.py +0 -0
  214. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/yaml_io.py +0 -0
  215. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/setup.cfg +0 -0
  216. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_agent_digest.py +0 -0
  217. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_agent_envelope.py +0 -0
  218. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_axis_title.py +0 -0
  219. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_capabilities.py +0 -0
  220. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_card_debug_shape.py +0 -0
  221. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_cli_help.py +0 -0
  222. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_cli_verbs.py +0 -0
  223. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_cmaps.py +0 -0
  224. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_column_existence.py +0 -0
  225. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_config_addressing.py +0 -0
  226. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_config_edit.py +0 -0
  227. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_config_expand.py +0 -0
  228. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_correlation_runtime.py +0 -0
  229. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_corrplot.py +0 -0
  230. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_data_access.py +0 -0
  231. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_data_describe.py +0 -0
  232. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_data_head_eval.py +0 -0
  233. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_data_loader_contracts.py +0 -0
  234. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_data_pipelines.py +0 -0
  235. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_debug_config.py +0 -0
  236. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_debug_switches.py +0 -0
  237. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_debug_yaml_override.py +0 -0
  238. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_density_cell_transform.py +0 -0
  239. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_design_runtime.py +0 -0
  240. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_diagnostic_guidance.py +0 -0
  241. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_docs_status_labels.py +0 -0
  242. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_doctor_partial.py +0 -0
  243. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_explain.py +0 -0
  244. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_figure_contracts.py +0 -0
  245. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_figure_type_expander.py +0 -0
  246. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_fix_application.py +0 -0
  247. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_flowchart_renderer.py +0 -0
  248. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_generated_data.py +0 -0
  249. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_imshow_adapter.py +0 -0
  250. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_interp_2d_transform.py +0 -0
  251. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_interp_natural_neighbor.py +0 -0
  252. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_interpolator_contracts.py +0 -0
  253. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_jpcontour_methods.py +0 -0
  254. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_man_cli.py +0 -0
  255. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_method_contracts.py +0 -0
  256. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_parse_data_metadata.py +0 -0
  257. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_pathing_contracts.py +0 -0
  258. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_posterior_hpd.py +0 -0
  259. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_profile_runtime.py +0 -0
  260. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_row_ledger.py +0 -0
  261. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_suggest.py +0 -0
  262. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_suggest_axes.py +0 -0
  263. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_template_catalog.py +0 -0
  264. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_template_json.py +0 -0
  265. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_ternary_debug.py +0 -0
  266. {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_transform_contracts.py +0 -0
  267. {jarvisplot-2.1.5 → 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.5
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,31 @@ 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
164
- live page for every one of the 28 layer methods and 11 transform steps
165
- (`jplot man scatter`, `jplot man transform.profile`). Method contracts come from
163
+ `jplot man --json` is the machine-readable manual: a topic index, plus
164
+ live pages for every registered layer method and transform step
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
+
179
+ For continuous one-dimensional distributions from raw samples, use `PDF1D`
180
+ (CDF plus PDF) or `CDF1D` in a data transform. `coordinates.x` owns `expr`,
181
+ `lim`, and `grid`; optional `repeat` computes independent batch means and
182
+ standard deviations. See [the contract](docs/specs/PDF_CDF_1D.md) and the
183
+ [runnable example](Example/pdf_cdf_1d.yaml).
184
+ Reconstruction defaults to adaptive true CDF anchors and `monotone_c2`
185
+ interpolation (C2 CDF / C1 PDF). Set `anchors: all` and `interpolation: pchip`
186
+ to reproduce the original all-node reconstruction.
187
+
169
188
  Two things worth knowing before trusting a config:
170
189
 
171
190
  - `jplot validate` checks shape and contracts and never touches the data;
@@ -30,12 +30,14 @@ jarvisplot/data_loader_summary.py
30
30
  jarvisplot/dataset_types.py
31
31
  jarvisplot/diagnostic_guidance.py
32
32
  jarvisplot/diagnostics.py
33
+ jarvisplot/distribution_1d_config.py
33
34
  jarvisplot/dryrun_runtime.py
34
35
  jarvisplot/expr_names.py
35
36
  jarvisplot/fix_apply.py
36
37
  jarvisplot/flowchart.py
37
38
  jarvisplot/generated_data.py
38
39
  jarvisplot/inner_func.py
40
+ jarvisplot/legend_cards.py
39
41
  jarvisplot/man_catalog.py
40
42
  jarvisplot/man_methods.py
41
43
  jarvisplot/man_render_agent.py
@@ -53,6 +55,7 @@ jarvisplot/yaml_io.py
53
55
  jarvisplot/Figure/__init__.py
54
56
  jarvisplot/Figure/adapters_rect.py
55
57
  jarvisplot/Figure/adapters_ternary.py
58
+ jarvisplot/Figure/adaptive_cdf_runtime.py
56
59
  jarvisplot/Figure/bin_stat_runtime.py
57
60
  jarvisplot/Figure/colorbar_runtime.py
58
61
  jarvisplot/Figure/config_runtime.py
@@ -65,6 +68,7 @@ jarvisplot/Figure/data_pipelines.py
65
68
  jarvisplot/Figure/debug_config.py
66
69
  jarvisplot/Figure/density_cell_runtime.py
67
70
  jarvisplot/Figure/design_runtime.py
71
+ jarvisplot/Figure/distribution_1d_runtime.py
68
72
  jarvisplot/Figure/dynesty_runtime.py
69
73
  jarvisplot/Figure/figure.py
70
74
  jarvisplot/Figure/figure_types.py
@@ -74,6 +78,7 @@ jarvisplot/Figure/interp_natural_neighbor.py
74
78
  jarvisplot/Figure/interp_natural_neighbor_exact.py
75
79
  jarvisplot/Figure/layer_runtime.py
76
80
  jarvisplot/Figure/layout_runtime.py
81
+ jarvisplot/Figure/legend_runtime.py
77
82
  jarvisplot/Figure/method_registry.py
78
83
  jarvisplot/Figure/posterior_density_runtime.py
79
84
  jarvisplot/Figure/posterior_hpd.py
@@ -117,6 +122,10 @@ jarvisplot/cards/icons/flowchart/inputfile.png
117
122
  jarvisplot/cards/icons/flowchart/opera.png
118
123
  jarvisplot/cards/icons/flowchart/outputfile.png
119
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
120
129
  jarvisplot/manual_cards/agent-output.yaml
121
130
  jarvisplot/manual_cards/cli-map.yaml
122
131
  jarvisplot/manual_cards/data-columns.yaml
@@ -126,6 +135,7 @@ jarvisplot/manual_cards/dryrun-viz.yaml
126
135
  jarvisplot/manual_cards/field-interp.yaml
127
136
  jarvisplot/manual_cards/generated-data.yaml
128
137
  jarvisplot/manual_cards/layer-method.yaml
138
+ jarvisplot/manual_cards/legend.yaml
129
139
  jarvisplot/manual_cards/manifest.yaml
130
140
  jarvisplot/manual_cards/method_notes.yaml
131
141
  jarvisplot/manual_cards/style-axes.yaml
@@ -139,9 +149,12 @@ jarvisplot/schema/manifest.json
139
149
  jarvisplot/schema/plot-config.json
140
150
  jarvisplot/schema/core/common.json
141
151
  jarvisplot/schema/core/dataset.json
152
+ jarvisplot/schema/core/distribution-1d.json
142
153
  jarvisplot/schema/core/figure.json
143
154
  jarvisplot/schema/core/generated-data.json
144
155
  jarvisplot/schema/core/layer.json
156
+ jarvisplot/schema/core/legend-card.json
157
+ jarvisplot/schema/core/legend.json
145
158
  jarvisplot/schema/core/transform.json
146
159
  jarvisplot/schema/methods/bar.json
147
160
  jarvisplot/schema/methods/barh.json
@@ -190,6 +203,7 @@ jarvisplot/verbs/man.py
190
203
  jarvisplot/verbs/suggest.py
191
204
  jarvisplot/verbs/template.py
192
205
  jarvisplot/verbs/validate.py
206
+ tests/test_adaptive_cdf.py
193
207
  tests/test_agent_digest.py
194
208
  tests/test_agent_envelope.py
195
209
  tests/test_axis_title.py
@@ -215,6 +229,7 @@ tests/test_debug_yaml_override.py
215
229
  tests/test_density_cell_transform.py
216
230
  tests/test_design_runtime.py
217
231
  tests/test_diagnostic_guidance.py
232
+ tests/test_distribution_1d_transform.py
218
233
  tests/test_docs_status_labels.py
219
234
  tests/test_doctor_partial.py
220
235
  tests/test_explain.py
@@ -228,6 +243,9 @@ tests/test_interp_2d_transform.py
228
243
  tests/test_interp_natural_neighbor.py
229
244
  tests/test_interpolator_contracts.py
230
245
  tests/test_jpcontour_methods.py
246
+ tests/test_layer_legend.py
247
+ tests/test_legend_cards.py
248
+ tests/test_legend_runtime.py
231
249
  tests/test_man_cli.py
232
250
  tests/test_method_contracts.py
233
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.5
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,31 @@ 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
164
- live page for every one of the 28 layer methods and 11 transform steps
165
- (`jplot man scatter`, `jplot man transform.profile`). Method contracts come from
163
+ `jplot man --json` is the machine-readable manual: a topic index, plus
164
+ live pages for every registered layer method and transform step
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
+
179
+ For continuous one-dimensional distributions from raw samples, use `PDF1D`
180
+ (CDF plus PDF) or `CDF1D` in a data transform. `coordinates.x` owns `expr`,
181
+ `lim`, and `grid`; optional `repeat` computes independent batch means and
182
+ standard deviations. See [the contract](docs/specs/PDF_CDF_1D.md) and the
183
+ [runnable example](Example/pdf_cdf_1d.yaml).
184
+ Reconstruction defaults to adaptive true CDF anchors and `monotone_c2`
185
+ interpolation (C2 CDF / C1 PDF). Set `anchors: all` and `interpolation: pchip`
186
+ to reproduce the original all-node reconstruction.
187
+
169
188
  Two things worth knowing before trusting a config:
170
189
 
171
190
  - `jplot validate` checks shape and contracts and never touches the data;
@@ -138,12 +138,31 @@ 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
142
- live page for every one of the 28 layer methods and 11 transform steps
143
- (`jplot man scatter`, `jplot man transform.profile`). Method contracts come from
141
+ `jplot man --json` is the machine-readable manual: a topic index, plus
142
+ live pages for every registered layer method and transform step
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
+
157
+ For continuous one-dimensional distributions from raw samples, use `PDF1D`
158
+ (CDF plus PDF) or `CDF1D` in a data transform. `coordinates.x` owns `expr`,
159
+ `lim`, and `grid`; optional `repeat` computes independent batch means and
160
+ standard deviations. See [the contract](docs/specs/PDF_CDF_1D.md) and the
161
+ [runnable example](Example/pdf_cdf_1d.yaml).
162
+ Reconstruction defaults to adaptive true CDF anchors and `monotone_c2`
163
+ interpolation (C2 CDF / C1 PDF). Set `anchors: all` and `interpolation: pchip`
164
+ to reproduce the original all-node reconstruction.
165
+
147
166
  Two things worth knowing before trusting a config:
148
167
 
149
168
  - `jplot validate` checks shape and contracts and never touches the data;
@@ -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
 
@@ -0,0 +1,131 @@
1
+ """Adaptive empirical-CDF anchors and a local, monotone C2 quintic interpolant.
2
+
3
+ No bins, kernels, or nonlinear solver. The quartic PDF is represented in the
4
+ Bernstein basis; nonnegative coefficients certify nonnegativity on each whole
5
+ interval, rather than only at sampled output points.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from heapq import heappop, heappush
11
+
12
+ import numpy as np
13
+ from scipy.interpolate import BPoly, CubicSpline, PchipInterpolator
14
+
15
+ from ..distribution_1d_config import AnchorSettings
16
+
17
+
18
+ def monotone_c2(x: np.ndarray, y: np.ndarray) -> BPoly:
19
+ """Exact C2 Hermite quintics with locally limited slopes and curvatures.
20
+
21
+ On an interval of width h and secant m, the quartic derivative's Bernstein
22
+ coefficients are [d0, d0+h*a0/4, 5*m-2*(d0+d1)+h*(a1-a0)/4,
23
+ d1-h*a1/4, d1]. Limit curvatures first, then shrink each node's (d,a)
24
+ together so all five coefficients are nonnegative. Each shared node has
25
+ one value, slope, and curvature: the assembled CDF is C2 and PDF is C1.
26
+ The support endpoints have zero slope/curvature, allowing constant CDF
27
+ extension and zero PDF extension without a boundary derivative jump.
28
+ """
29
+ x, y = np.asarray(x, dtype=float), np.asarray(y, dtype=float)
30
+ if x.ndim != 1 or len(x) < 2 or y.shape != x.shape or not np.all(np.isfinite(x)) or not np.all(np.isfinite(y)):
31
+ raise ValueError("monotone_c2 needs matching finite 1D anchor arrays")
32
+ h, mass = np.diff(x), np.diff(y)
33
+ if np.any(h <= 0) or np.any(mass < 0):
34
+ raise ValueError("monotone_c2 requires strictly increasing x and nondecreasing CDF anchors")
35
+ # A clamped cubic estimates smooth slopes/curvatures in linear time. Its
36
+ # unconstrained interpolant is never returned: the quintic limiter below
37
+ # certifies monotonicity even where the cubic estimate would overshoot.
38
+ seed = CubicSpline(x, y, bc_type=((1, 0.0), (1, 0.0)))
39
+ slopes = np.maximum(seed(x, 1), 0.0)
40
+ slopes[[0, -1]] = 0.0
41
+ curvature = seed(x, 2)
42
+ # Adjacent interval constraints for the first/last PDF control coefficients.
43
+ lower, upper = np.full(len(x), -np.inf), np.full(len(x), np.inf)
44
+ lower[:-1] = -4 * slopes[:-1] / h
45
+ upper[1:] = 4 * slopes[1:] / h
46
+ curvature = np.clip(curvature, lower, upper)
47
+ curvature[[0, -1]] = 0.0
48
+ # Both contributions are nonnegative after clipping. Reducing either node
49
+ # cannot violate another interval's constraint, so one local pass suffices.
50
+ left = 2 * slopes[:-1] + h * curvature[:-1] / 4
51
+ right = 2 * slopes[1:] - h * curvature[1:] / 4
52
+ available = 5 * mass / h
53
+ total = left + right
54
+ factors = np.ones(len(h))
55
+ active = total > available
56
+ factors[active] = (available[active] / total[active]) * (1 - 32 * np.finfo(float).eps)
57
+ node_factors = np.ones(len(x))
58
+ node_factors[:-1] = np.minimum(node_factors[:-1], factors)
59
+ node_factors[1:] = np.minimum(node_factors[1:], factors)
60
+ slopes *= node_factors
61
+ curvature *= node_factors
62
+ if not np.all(np.isfinite(slopes)) or not np.all(np.isfinite(curvature)):
63
+ raise ValueError("monotone_c2 cannot resolve this anchor spacing; inspect support and score precision")
64
+ return BPoly.from_derivatives(x, np.column_stack((y, slopes, curvature)), extrapolate=False)
65
+
66
+
67
+ def interpolate_cdf(x: np.ndarray, y: np.ndarray, method: str):
68
+ if method == "monotone_c2":
69
+ return monotone_c2(x, y)
70
+ if method == "pchip":
71
+ return PchipInterpolator(x, y, extrapolate=False)
72
+ raise ValueError(f"unknown CDF interpolation {method!r}")
73
+
74
+
75
+ @dataclass(frozen=True)
76
+ class AnchorFit:
77
+ curve: BPoly | PchipInterpolator
78
+ indices: np.ndarray
79
+ max_error: float
80
+ stop_reason: str
81
+
82
+
83
+ def fit_anchors(x: np.ndarray, y: np.ndarray, config: AnchorSettings, interpolation: str) -> AnchorFit:
84
+ """Select only real empirical nodes plus the supplied support endpoints.
85
+
86
+ A bounded set of probability seeds starts the fit. Each pass evaluates
87
+ residuals against all empirical knots once, then scans disjoint intervals
88
+ and refines up to eight worst intervals. min_mass is the required observed
89
+ cumulative mass on EACH side of a proposed new node; it stops the selector
90
+ from chasing individual sample jumps. No output grid participates.
91
+ """
92
+ if config.method == "all":
93
+ curve = interpolate_cdf(x, y, interpolation)
94
+ return AnchorFit(curve, np.arange(len(x)), 0.0, "all")
95
+ # Retain the first empirical value and the first empirical value reaching
96
+ # probability one, so the fit preserves the observed onset and upper tail.
97
+ last = min(int(np.searchsorted(y, 1.0)), len(x) - 1)
98
+ selected = {0, min(1, len(x) - 1), last, len(x) - 1}
99
+ seed_count = min(9, config.max_points - len(selected) + 1)
100
+ for target in np.linspace(0, 1, seed_count)[1:-1]:
101
+ index = min(int(np.searchsorted(y, target)), len(x) - 1)
102
+ ordered = sorted(selected)
103
+ slot = int(np.searchsorted(ordered, index))
104
+ if index in selected or slot == 0 or slot == len(ordered):
105
+ continue
106
+ if min(y[index] - y[ordered[slot - 1]], y[ordered[slot]] - y[index]) >= config.min_mass:
107
+ selected.add(index)
108
+ while True:
109
+ indices = np.array(sorted(selected), dtype=int)
110
+ curve = interpolate_cdf(x[indices], y[indices], interpolation)
111
+ residual = np.abs(curve(x) - y)
112
+ error = float(np.max(residual))
113
+ if error <= config.tolerance:
114
+ return AnchorFit(curve, indices, error, "tolerance")
115
+ if len(selected) >= config.max_points:
116
+ return AnchorFit(curve, indices, error, "max_points")
117
+ queue = []
118
+ for lo, hi in zip(indices[:-1], indices[1:]):
119
+ # Search only nodes with enough probability mass on both sides.
120
+ start = max(lo + 1, int(np.searchsorted(y, y[lo] + config.min_mass, side="left")))
121
+ end = min(hi, int(np.searchsorted(y, y[hi] - config.min_mass, side="right")))
122
+ if start >= end:
123
+ continue
124
+ index = start + int(np.argmax(residual[start:end]))
125
+ if residual[index] > config.tolerance:
126
+ heappush(queue, (-float(residual[index]), index))
127
+ if not queue:
128
+ return AnchorFit(curve, indices, error, "min_mass")
129
+ for _ in range(min(8, config.max_points - len(selected), len(queue))):
130
+ _, index = heappop(queue)
131
+ selected.add(index)
@@ -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
 
@@ -0,0 +1,165 @@
1
+ """Raw weighted samples -> continuous CDF, optionally its analytic derivative.
2
+
3
+ PDF1D and CDF1D share the reconstruction. No histogram or sample-grid
4
+ renormalisation participates in fitting: the grid only evaluates the interpolant.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from typing import Any, Mapping
9
+
10
+ import numpy as np
11
+ import pandas as pd
12
+ from scipy.interpolate import PchipInterpolator
13
+
14
+ from ..distribution_1d_config import KINDS, settings
15
+ from ..memtrace import memtrace_checkpoint, memtrace_object_inventory
16
+ from ..utils.expression import eval_dataframe_expression
17
+ from .adaptive_cdf_runtime import fit_anchors
18
+
19
+ ALGORITHM_REVISION = "weighted-ecdf-adaptive-bernstein-c2-v3"
20
+
21
+
22
+ def _values(df: pd.DataFrame, expr: Any, logger=None) -> np.ndarray:
23
+ values = np.asarray(eval_dataframe_expression(df, expr, logger=logger), dtype=float)
24
+ if values.ndim == 0:
25
+ return np.full(len(df), float(values))
26
+ if values.shape != (len(df),):
27
+ raise ValueError(f"expression {expr!r} must return one value per input row, got {values.shape}")
28
+ return values
29
+
30
+
31
+ def _infer_limits(x: np.ndarray, scale: str) -> tuple[float, float]:
32
+ unique = np.unique(x)
33
+ if len(unique) < 2:
34
+ raise ValueError("one unique sample cannot define a continuous support; set coordinates.x.lim")
35
+ lo = unique[0] - 0.5 * (unique[1] - unique[0])
36
+ hi = unique[-1] + 0.5 * (unique[-1] - unique[-2])
37
+ if scale == "log":
38
+ if unique[0] <= 0:
39
+ raise ValueError("coordinates.x.scale: log requires positive samples")
40
+ lo = max(lo, unique[0] * 0.5)
41
+ if not np.isfinite(lo) or not np.isfinite(hi) or not lo < hi:
42
+ raise ValueError("cannot infer finite support; set coordinates.x.lim")
43
+ return float(lo), float(hi)
44
+
45
+
46
+ def empirical_knots(x: np.ndarray, weights: np.ndarray, limits: tuple[float, float]):
47
+ """Inclusive empirical nodes plus support endpoints; return largest atom too."""
48
+ lo, hi = limits
49
+ order = np.argsort(x, kind="stable")
50
+ x, weights = x[order], weights[order]
51
+ unique, first = np.unique(x, return_index=True)
52
+ # Rescaling before summation avoids overflow without changing probability.
53
+ scaled = weights / weights.max()
54
+ mass = np.add.reduceat(scaled, first)
55
+ mass /= mass.sum()
56
+ cumulative = np.minimum(np.cumsum(mass), 1.0)
57
+ cumulative[-1] = 1.0
58
+ interior = (unique > lo) & (unique < hi)
59
+ knots = np.r_[lo, unique[interior], hi]
60
+ probabilities = np.r_[0.0, cumulative[interior], 1.0]
61
+ return knots, probabilities, float(mass.max())
62
+
63
+
64
+ def build_cdf(x: np.ndarray, weights: np.ndarray, limits: tuple[float, float]) -> PchipInterpolator:
65
+ """Original all-anchor PCHIP path, useful for exact empirical-node comparisons.
66
+
67
+ A sample exactly at lo cannot retain its jump with F(lo)=0; its mass is
68
+ carried into the first interval. Other empirical nodes retain their CDF.
69
+ Production reconstruction uses fit_anchors with the selected settings.
70
+ """
71
+ knots, probabilities, _ = empirical_knots(x, weights, limits)
72
+ return PchipInterpolator(knots, probabilities, extrapolate=False)
73
+
74
+
75
+ def distribution_1d(df: pd.DataFrame, cfg: Mapping[str, Any], kind: str, logger=None) -> pd.DataFrame:
76
+ if kind not in KINDS:
77
+ raise ValueError(f"unknown distribution transform {kind!r}")
78
+ if not isinstance(df, pd.DataFrame) or df.empty:
79
+ raise ValueError(f"{kind} needs a nonempty pandas sample table")
80
+ opts = settings(cfg)
81
+ memtrace_checkpoint(logger, f"pipeline.{kind}.before", df)
82
+ x = _values(df, opts.x["expr"], logger)
83
+ weights = _values(df, opts.weight["expr"], logger) if opts.weight else np.ones(len(df))
84
+ if not np.all(np.isfinite(x)) or not np.all(np.isfinite(weights)):
85
+ raise ValueError(f"{kind} needs finite samples and weights; filter invalid rows first")
86
+ if np.any(weights < 0):
87
+ raise ValueError(f"{kind} needs nonnegative probability weights; signed event weights are not a CDF")
88
+ work = pd.DataFrame({"x": x, "weight": weights})
89
+ if opts.repeat is not None:
90
+ if opts.repeat not in df.columns:
91
+ raise KeyError(f"{kind} missing repeat column {opts.repeat!r}")
92
+ if df[opts.repeat].isna().any():
93
+ raise ValueError(f"{kind} repeat identifiers must not be missing")
94
+ work["repeat"] = df[opts.repeat].to_numpy()
95
+ positive = weights > 0
96
+ if not positive.any():
97
+ raise ValueError(f"{kind} weights sum to zero")
98
+ limits = opts.limits or _infer_limits(x[positive], opts.scale)
99
+ lo, hi = limits
100
+ if np.any((x[positive] < lo) | (x[positive] > hi)):
101
+ raise ValueError(f"{kind} coordinates.x.lim must contain all positive-weight samples; filter first to truncate")
102
+ grid = np.geomspace(lo, hi, opts.grid) if opts.scale == "log" else np.linspace(lo, hi, opts.grid)
103
+ memtrace_object_inventory(logger, f"pipeline.{kind}.samples", {"samples": work})
104
+ curves = ("cdf", "pdf") if kind == "PDF1D" else ("cdf",)
105
+ mean = {key: np.zeros(opts.grid) for key in curves}
106
+ m2 = {key: np.zeros(opts.grid) for key in curves}
107
+ groups = work.groupby("repeat", sort=False, observed=True) if opts.repeat else [(None, work)]
108
+ n = 0
109
+ boundary_mass = False
110
+ diagnostics = []
111
+ for label, block in groups:
112
+ block = block.loc[block.weight > 0]
113
+ if block.empty:
114
+ raise ValueError(f"{kind} repeat {label!r} weights sum to zero")
115
+ bx, bw = block.x.to_numpy(), block.weight.to_numpy()
116
+ boundary_mass |= bool(np.any(bx == lo))
117
+ knots, probabilities, largest_jump = empirical_knots(bx, bw, limits)
118
+ fit = fit_anchors(knots, probabilities, opts.anchors, opts.interpolation)
119
+ cdf = fit.curve
120
+ diagnostics.append({
121
+ "repeat": label.item() if isinstance(label, np.generic) else label,
122
+ "n_anchors": len(fit.indices), "n_candidates": len(knots),
123
+ "max_cdf_error": fit.max_error, "stop_reason": fit.stop_reason,
124
+ "largest_empirical_jump": largest_jump,
125
+ })
126
+ if logger is not None:
127
+ logger.debug(f"{kind} repeat={label!r}: anchors={len(fit.indices)}/{len(knots)}, "
128
+ f"max CDF node error={fit.max_error:.6g}, stopped={fit.stop_reason}")
129
+ if opts.anchors.method == "adaptive" and fit.max_error > opts.anchors.tolerance:
130
+ logger.warning(f"{kind} repeat={label!r}: CDF error {fit.max_error:.6g} exceeds "
131
+ f"tolerance {opts.anchors.tolerance:g}; stopped at {fit.stop_reason}. "
132
+ "Adjust anchors settings if finer CDF reconstruction is needed.")
133
+ if opts.anchors.method == "adaptive" and largest_jump > max(2 * opts.anchors.tolerance, opts.anchors.min_mass):
134
+ logger.warning(f"{kind} repeat={label!r}: empirical probability jump {largest_jump:.6g}; "
135
+ "a continuous density spreads this discrete mass over an interval")
136
+ values = {"cdf": cdf(grid)}
137
+ if kind == "PDF1D":
138
+ pdf = cdf.derivative()(grid)
139
+ tolerance = 128 * np.finfo(float).eps * max(1.0, float(np.max(np.abs(pdf))))
140
+ if np.min(pdf) < -tolerance:
141
+ raise ValueError(f"{kind} produced a negative density beyond floating-point tolerance")
142
+ values["pdf"] = np.maximum(pdf, 0.0)
143
+ n += 1
144
+ for key in curves:
145
+ if not np.all(np.isfinite(values[key])):
146
+ raise ValueError(f"{kind} produced nonfinite {key}; inspect sample spacing/support")
147
+ delta = values[key] - mean[key]
148
+ mean[key] += delta / n
149
+ m2[key] += delta * (values[key] - mean[key])
150
+ out = {opts.name: grid}
151
+ for key in curves:
152
+ out[key] = mean[key]
153
+ out[f"{key}_std"] = np.sqrt(np.maximum(m2[key], 0.0) / (n - 1)) if n > 1 else np.full(opts.grid, np.nan)
154
+ out["n_repeats"] = n
155
+ table = pd.DataFrame(out)
156
+ table.attrs["distribution_1d"] = {
157
+ "algorithm": ALGORITHM_REVISION, "interpolation": opts.interpolation,
158
+ "anchor_method": opts.anchors.method, "support": limits, "repeats": diagnostics,
159
+ }
160
+ if logger is not None:
161
+ if boundary_mass:
162
+ logger.warning(f"{kind}: mass at the lower support boundary is spread over the first CDF interval")
163
+ logger.debug(f"{kind}: {len(df)} samples -> {opts.grid} points, {n} repeats, support={limits}, std ddof=1")
164
+ memtrace_checkpoint(logger, f"pipeline.{kind}.after", table)
165
+ return table
@@ -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()):