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.
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/PKG-INFO +23 -4
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/SOURCES.txt +18 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/PKG-INFO +23 -4
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/README.md +22 -3
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/adapters_rect.py +0 -1
- jarvisplot-2.1.7/jarvisplot/Figure/adaptive_cdf_runtime.py +131 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/config_runtime.py +2 -0
- jarvisplot-2.1.7/jarvisplot/Figure/distribution_1d_runtime.py +165 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/figure.py +45 -21
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/layout_runtime.py +0 -2
- jarvisplot-2.1.7/jarvisplot/Figure/legend_runtime.py +94 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/preprocessor.py +15 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/preprocessor_runtime.py +6 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/capabilities.py +6 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/dynesty_runplot.json +5 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rect.json +1 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rect5x1.json +5 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rect_cmap.json +1 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rect_marginal.json +3 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/rectratio.json +2 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/ternary.json +1 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/2x1/ternary_cmap.json +1 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/4x1/rect_cmap.json +1 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/corrplot/diamond.json +1 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/corrplot/matrix.json +1 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/gambit/2x1/rect_cmap.json +1 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/gambit/2x1/ternary.json +1 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/gambit/2x1/ternary_cmap.json +1 -0
- jarvisplot-2.1.7/jarvisplot/cards/legends/compact/legend-card.json +15 -0
- jarvisplot-2.1.7/jarvisplot/cards/legends/default/legend-card.json +68 -0
- jarvisplot-2.1.7/jarvisplot/cards/legends/paper/legend-card.json +11 -0
- jarvisplot-2.1.7/jarvisplot/cards/legends/presentation/legend-card.json +16 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/column_demand.py +10 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/data_loader.py +6 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/data_loader_runtime.py +6 -1
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/diagnostic_guidance.py +15 -6
- jarvisplot-2.1.7/jarvisplot/distribution_1d_config.py +167 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/dryrun_runtime.py +12 -3
- jarvisplot-2.1.7/jarvisplot/legend_cards.py +250 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/man_render_agent.py +4 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/man_render_human.py +13 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/man_transforms.py +8 -5
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/layer-method.yaml +5 -1
- jarvisplot-2.1.7/jarvisplot/manual_cards/legend.yaml +117 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/manifest.yaml +4 -0
- jarvisplot-2.1.7/jarvisplot/manual_cards/style-axes.yaml +58 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/render_health.py +9 -50
- jarvisplot-2.1.7/jarvisplot/schema/core/distribution-1d.json +72 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/core/figure.json +13 -5
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/core/layer.json +13 -1
- jarvisplot-2.1.7/jarvisplot/schema/core/legend-card.json +106 -0
- jarvisplot-2.1.7/jarvisplot/schema/core/legend.json +53 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/core/transform.json +8 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/manifest.json +5 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/transform_contracts.py +67 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/validation.py +43 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/pyproject.toml +1 -1
- jarvisplot-2.1.7/tests/test_adaptive_cdf.py +141 -0
- jarvisplot-2.1.7/tests/test_distribution_1d_transform.py +234 -0
- jarvisplot-2.1.7/tests/test_layer_legend.py +186 -0
- jarvisplot-2.1.7/tests/test_legend_cards.py +185 -0
- jarvisplot-2.1.7/tests/test_legend_runtime.py +138 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_render_health.py +15 -13
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_schema_catalog.py +5 -7
- jarvisplot-2.1.5/jarvisplot/manual_cards/style-axes.yaml +0 -35
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/dependency_links.txt +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/entry_points.txt +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/requires.txt +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/JarvisPLOT.egg-info/top_level.txt +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/__init__.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/adapters_ternary.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/bin_stat_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/cards/std_axes_adapter_config.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/colorbar_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/corr_layout.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/corr_layout_diamond.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/corr_order.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/correlation_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/corrplot_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/data_pipelines.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/debug_config.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/density_cell_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/design_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/dynesty_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/figure_types.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/helper.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/interp_2d_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/interp_natural_neighbor.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/interp_natural_neighbor_exact.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/layer_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/method_registry.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/posterior_density_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/posterior_hpd.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/posterior_mesh.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/profile_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/significance_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/Figure/style_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/__init__.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/agent_digest.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/agent_io.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cache_store.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/1x1/ternary.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/4x1/rect.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/4x1/ternary.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/a4paper/4x1/ternary_cmap.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/args.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/colors/colormaps.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/default/output.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/flowchart/default.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/gambit/1x1/ternary.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/JarvisHEP.png +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/JarvisHEP.png +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/calculator.png +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/inputfile.png +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/opera.png +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/outputfile.png +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/flowchart/sampler.png +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/gambit.png +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/icons/gambit_small.png +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cards/style_preference.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cli.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/cli_help.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/client.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/column_probe.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/config.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/config_address.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/core.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/core_assets.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/core_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/data_access.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/data_generation_contracts.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/data_loader_hdf5.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/data_loader_summary.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/dataset_types.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/diagnostics.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/expr_names.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/fix_apply.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/flowchart.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/generated_data.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/inner_func.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/man_catalog.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/man_methods.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/agent-output.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/cli-map.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/data-columns.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/debug-overlay.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/diagnostics.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/dryrun-viz.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/field-interp.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/generated-data.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/method_notes.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/type-correlation-matrix.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/type-posterior-2d.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/type-profile-2d.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/validate.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/workflow.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/manual_cards/yaml-root.yaml +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/memtrace.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/method_contracts.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/core/common.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/core/dataset.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/core/generated-data.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/bar.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/barh.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/contour.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/contourf.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/corrplot.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/dynesty_runplot.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/errorbar.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/fill.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/fill_between.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/fill_betweenx.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/hist.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/hist2d.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/imshow.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/jpcontour.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/jpcontourf.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/jpfield.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/pcolor.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/pcolormesh.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/plot.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/quiver.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/scatter.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/stairs.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/step.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/tricontour.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/tricontourf.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/tripcolor.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/tripcolor_axes.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/triplot.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/voronoi.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/methods/voronoif.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema/plot-config.json +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema_catalog.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/schema_diagnostics.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/templates_catalog.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/utils/__init__.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/utils/cmaps.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/utils/dataframes.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/utils/expression.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/utils/interpolator.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/utils/pathing.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/__init__.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/cap.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/config_cmd.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/data.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/doctor.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/dryrun.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/explain.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/man.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/suggest.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/template.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/verbs/validate.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/jarvisplot/yaml_io.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/setup.cfg +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_agent_digest.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_agent_envelope.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_axis_title.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_capabilities.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_card_debug_shape.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_cli_help.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_cli_verbs.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_cmaps.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_column_existence.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_config_addressing.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_config_edit.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_config_expand.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_correlation_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_corrplot.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_data_access.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_data_describe.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_data_head_eval.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_data_loader_contracts.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_data_pipelines.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_debug_config.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_debug_switches.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_debug_yaml_override.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_density_cell_transform.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_design_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_diagnostic_guidance.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_docs_status_labels.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_doctor_partial.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_explain.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_figure_contracts.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_figure_type_expander.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_fix_application.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_flowchart_renderer.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_generated_data.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_imshow_adapter.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_interp_2d_transform.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_interp_natural_neighbor.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_interpolator_contracts.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_jpcontour_methods.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_man_cli.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_method_contracts.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_parse_data_metadata.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_pathing_contracts.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_posterior_hpd.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_profile_runtime.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_row_ledger.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_suggest.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_suggest_axes.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_template_catalog.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_template_json.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_ternary_debug.py +0 -0
- {jarvisplot-2.1.5 → jarvisplot-2.1.7}/tests/test_transform_contracts.py +0 -0
- {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.
|
|
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:
|
|
164
|
-
live
|
|
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.
|
|
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:
|
|
164
|
-
live
|
|
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:
|
|
142
|
-
live
|
|
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
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
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
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
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
|
-
|
|
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()):
|