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