dataface 0.1.2__py3-none-any.whl
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.
- d3_format/__init__.py +14 -0
- d3_format/errors.py +19 -0
- d3_format/format.py +551 -0
- d3_format/spec.py +159 -0
- dataface/DATAFACE_SYNTAX.md +1135 -0
- dataface/__init__.py +93 -0
- dataface/_docs_site.py +20 -0
- dataface/_install_hint.py +26 -0
- dataface/agent_api/__init__.py +79 -0
- dataface/agent_api/_init_templates/__init__.py +0 -0
- dataface/agent_api/_init_templates/agents_dft_snippet.md +26 -0
- dataface/agent_api/_init_templates/dataface.yml +15 -0
- dataface/agent_api/_init_templates/faces-dataface.yml +144 -0
- dataface/agent_api/_init_templates/index.md +24 -0
- dataface/agent_api/_paths.py +118 -0
- dataface/agent_api/_project_agents_md.py +43 -0
- dataface/agent_api/_session_store.py +486 -0
- dataface/agent_api/_state.py +28 -0
- dataface/agent_api/chat.py +221 -0
- dataface/agent_api/dashboards.py +257 -0
- dataface/agent_api/describe.py +366 -0
- dataface/agent_api/describe_query.py +120 -0
- dataface/agent_api/docs/__init__.py +25 -0
- dataface/agent_api/docs/_loader.py +292 -0
- dataface/agent_api/docs/yaml-reference.md +2757 -0
- dataface/agent_api/file_refs.py +118 -0
- dataface/agent_api/init.py +126 -0
- dataface/agent_api/inspect.py +128 -0
- dataface/agent_api/mcp_install.py +170 -0
- dataface/agent_api/query.py +274 -0
- dataface/agent_api/schema.py +658 -0
- dataface/agent_api/schema_search.py +284 -0
- dataface/agent_api/search.py +270 -0
- dataface/agent_api/skill_install.py +141 -0
- dataface/agent_api/skill_render.py +90 -0
- dataface/agent_api/skills.py +293 -0
- dataface/agent_api/surface_aliases.yaml +128 -0
- dataface/agent_api/validate.py +175 -0
- dataface/agent_api/validate_query.py +84 -0
- dataface/ai/__init__.py +39 -0
- dataface/ai/agent.py +139 -0
- dataface/ai/context.py +45 -0
- dataface/ai/events.py +62 -0
- dataface/ai/external_mcp.py +610 -0
- dataface/ai/generate_sql.py +96 -0
- dataface/ai/llm.py +403 -0
- dataface/ai/mcp/__init__.py +51 -0
- dataface/ai/mcp/server.py +289 -0
- dataface/ai/memories.py +85 -0
- dataface/ai/prompts.py +177 -0
- dataface/ai/schema_context.py +138 -0
- dataface/ai/skills/before-after-comparison/SKILL.md +102 -0
- dataface/ai/skills/before-after-comparison/examples/before-after-comparison.yml +24 -0
- dataface/ai/skills/dashboard-build/SKILL.md +212 -0
- dataface/ai/skills/dashboard-build/examples/_smoke.yml +15 -0
- dataface/ai/skills/dashboard-design/SKILL.md +182 -0
- dataface/ai/skills/dashboard-review/SKILL.md +113 -0
- dataface/ai/skills/dashboard-structural-review/SKILL.md +173 -0
- dataface/ai/skills/dashboard-visual-review/SKILL.md +139 -0
- dataface/ai/skills/dataface-mcp-setup/SKILL.md +177 -0
- dataface/ai/skills/dataface-troubleshooting/SKILL.md +225 -0
- dataface/ai/skills/drill-down-link/SKILL.md +112 -0
- dataface/ai/skills/drill-down-link/examples/drill-down-link.yml +27 -0
- dataface/ai/skills/faceted-small-multiples/SKILL.md +116 -0
- dataface/ai/skills/faceted-small-multiples/examples/faceted-small-multiples.yml +33 -0
- dataface/ai/skills/filter-bar-with-variables/SKILL.md +105 -0
- dataface/ai/skills/filter-bar-with-variables/examples/filter-bar-with-variables.yml +49 -0
- dataface/ai/skills/kpi-row/SKILL.md +101 -0
- dataface/ai/skills/kpi-row/examples/kpi-row.yml +55 -0
- dataface/ai/skills/report-design/SKILL.md +184 -0
- dataface/ai/skills/single-metric-bignum/SKILL.md +90 -0
- dataface/ai/skills/single-metric-bignum/examples/single-metric-bignum.yml +27 -0
- dataface/ai/skills/table-heavy-ops-dashboard/SKILL.md +114 -0
- dataface/ai/skills/table-heavy-ops-dashboard/examples/table-heavy-ops-dashboard.yml +48 -0
- dataface/ai/skills/time-series-trend/SKILL.md +93 -0
- dataface/ai/skills/time-series-trend/examples/time-series-trend.yml +26 -0
- dataface/ai/skills/top-n-with-detail/SKILL.md +98 -0
- dataface/ai/skills/top-n-with-detail/examples/top-n-with-detail.yml +45 -0
- dataface/ai/skills/two-by-two-grid-overview/SKILL.md +78 -0
- dataface/ai/skills/two-by-two-grid-overview/examples/two-by-two-grid-overview.yml +64 -0
- dataface/ai/tool_schemas.py +132 -0
- dataface/ai/tools/__init__.py +312 -0
- dataface/ai/yaml_utils.py +57 -0
- dataface/cli/__init__.py +3 -0
- dataface/cli/_console.py +48 -0
- dataface/cli/_error_format.py +83 -0
- dataface/cli/_extras.py +190 -0
- dataface/cli/_json_output.py +8 -0
- dataface/cli/_parsing.py +17 -0
- dataface/cli/_version_info.py +56 -0
- dataface/cli/commands/__init__.py +3 -0
- dataface/cli/commands/_agent_input.py +205 -0
- dataface/cli/commands/_agent_server.py +115 -0
- dataface/cli/commands/chat.py +645 -0
- dataface/cli/commands/describe.py +107 -0
- dataface/cli/commands/docs.py +131 -0
- dataface/cli/commands/extension.py +179 -0
- dataface/cli/commands/init.py +240 -0
- dataface/cli/commands/inspect.py +94 -0
- dataface/cli/commands/mcp_init.py +167 -0
- dataface/cli/commands/query.py +386 -0
- dataface/cli/commands/render.py +291 -0
- dataface/cli/commands/schema.py +411 -0
- dataface/cli/commands/search.py +49 -0
- dataface/cli/commands/serve.py +114 -0
- dataface/cli/commands/skills.py +133 -0
- dataface/cli/commands/skills_init.py +161 -0
- dataface/cli/commands/validate.py +63 -0
- dataface/cli/main.py +1501 -0
- dataface/core/__init__.py +75 -0
- dataface/core/compile/__init__.py +244 -0
- dataface/core/compile/_jinja_helpers.py +78 -0
- dataface/core/compile/channel.py +222 -0
- dataface/core/compile/chart_focus.py +101 -0
- dataface/core/compile/chart_resolved.py +169 -0
- dataface/core/compile/chart_type_detection.py +489 -0
- dataface/core/compile/chart_update.py +261 -0
- dataface/core/compile/colors.py +64 -0
- dataface/core/compile/compiler.py +904 -0
- dataface/core/compile/config.py +823 -0
- dataface/core/compile/custom_chart_types.py +208 -0
- dataface/core/compile/data_table_attachment.py +1287 -0
- dataface/core/compile/detect.py +110 -0
- dataface/core/compile/errors.py +302 -0
- dataface/core/compile/filter_injection.py +319 -0
- dataface/core/compile/introspection.py +527 -0
- dataface/core/compile/jinja.py +511 -0
- dataface/core/compile/labels_env.py +52 -0
- dataface/core/compile/markdown.py +154 -0
- dataface/core/compile/meta.py +388 -0
- dataface/core/compile/models/__init__.py +0 -0
- dataface/core/compile/models/chart/__init__.py +0 -0
- dataface/core/compile/models/chart/authored.py +2137 -0
- dataface/core/compile/models/chart/compiled.py +398 -0
- dataface/core/compile/models/config.py +347 -0
- dataface/core/compile/models/face/__init__.py +0 -0
- dataface/core/compile/models/face/authored.py +659 -0
- dataface/core/compile/models/face/compiled.py +522 -0
- dataface/core/compile/models/factories.py +201 -0
- dataface/core/compile/models/markers.py +40 -0
- dataface/core/compile/models/palette.py +36 -0
- dataface/core/compile/models/primitives.py +415 -0
- dataface/core/compile/models/query/__init__.py +0 -0
- dataface/core/compile/models/query/authored.py +246 -0
- dataface/core/compile/models/query/compiled.py +710 -0
- dataface/core/compile/models/refs.py +137 -0
- dataface/core/compile/models/source.py +611 -0
- dataface/core/compile/models/style/__init__.py +0 -0
- dataface/core/compile/models/style/authored.py +481 -0
- dataface/core/compile/models/style/compiled.py +3399 -0
- dataface/core/compile/models/style/merged.py +1682 -0
- dataface/core/compile/models/theme.py +362 -0
- dataface/core/compile/models/variable/__init__.py +0 -0
- dataface/core/compile/models/variable/authored.py +254 -0
- dataface/core/compile/models/vega_lite/__init__.py +0 -0
- dataface/core/compile/models/vega_lite/config.py +510 -0
- dataface/core/compile/models/vega_lite/contracts.py +171 -0
- dataface/core/compile/normalize_charts.py +494 -0
- dataface/core/compile/normalize_layout.py +1000 -0
- dataface/core/compile/normalize_queries.py +297 -0
- dataface/core/compile/normalize_variables.py +489 -0
- dataface/core/compile/normalizer.py +543 -0
- dataface/core/compile/palette.py +1100 -0
- dataface/core/compile/parameterized.py +658 -0
- dataface/core/compile/parser.py +228 -0
- dataface/core/compile/schema.py +20 -0
- dataface/core/compile/schema_renderers/__init__.py +0 -0
- dataface/core/compile/schema_renderers/json_schema.py +163 -0
- dataface/core/compile/schema_renderers/prompt.py +152 -0
- dataface/core/compile/schema_renderers/vscode_schema.py +301 -0
- dataface/core/compile/sizing.py +2126 -0
- dataface/core/compile/sources.py +518 -0
- dataface/core/compile/sql_authoring_lint.py +56 -0
- dataface/core/compile/style_cascade.py +471 -0
- dataface/core/compile/typography.py +299 -0
- dataface/core/compile/validator.py +301 -0
- dataface/core/compile/variables.py +53 -0
- dataface/core/compile/vega_config.py +98 -0
- dataface/core/compile/vega_lite/__init__.py +6 -0
- dataface/core/compile/vega_lite/validation.py +95 -0
- dataface/core/compile/yaml_error_formatter.py +838 -0
- dataface/core/connections.py +38 -0
- dataface/core/dashboard.py +358 -0
- dataface/core/defaults/default_config.yml +101 -0
- dataface/core/defaults/palettes/categorical/category-10-dark.yml +32 -0
- dataface/core/defaults/palettes/categorical/category-10-light.yml +43 -0
- dataface/core/defaults/palettes/categorical/category-10.yml +31 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-blue.yml +22 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-brown.yml +29 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-green.yml +20 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-orange.yml +21 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-purple.yml +20 -0
- dataface/core/defaults/palettes/categorical/editorial-10-dark.yml +32 -0
- dataface/core/defaults/palettes/categorical/editorial-10.yml +40 -0
- dataface/core/defaults/palettes/categorical/hero-6.yml +17 -0
- dataface/core/defaults/palettes/categorical/single-blue.yml +11 -0
- dataface/core/defaults/palettes/categorical/tableau.yml +20 -0
- dataface/core/defaults/palettes/data/xkcd_colors.json +3803 -0
- dataface/core/defaults/palettes/diverging/blue-red.yml +25 -0
- dataface/core/defaults/palettes/diverging/coolwarm.yml +24 -0
- dataface/core/defaults/palettes/diverging/crimson-green.yml +23 -0
- dataface/core/defaults/palettes/diverging/orange-teal.yml +23 -0
- dataface/core/defaults/palettes/diverging/sunset.yml +24 -0
- dataface/core/defaults/palettes/scaffold/dft-creams.yml +38 -0
- dataface/core/defaults/palettes/scaffold/dft-grays.yml +53 -0
- dataface/core/defaults/palettes/sequential/amber.yml +22 -0
- dataface/core/defaults/palettes/sequential/blue.yml +22 -0
- dataface/core/defaults/palettes/sequential/brown.yml +22 -0
- dataface/core/defaults/palettes/sequential/gray.yml +22 -0
- dataface/core/defaults/palettes/sequential/green.yml +22 -0
- dataface/core/defaults/palettes/sequential/purple.yml +22 -0
- dataface/core/defaults/palettes/sequential/rust.yml +22 -0
- dataface/core/defaults/palettes/sequential/teal.yml +22 -0
- dataface/core/defaults/palettes/tone/negative.yml +32 -0
- dataface/core/defaults/palettes/tone/positive.yml +22 -0
- dataface/core/defaults/palettes/tone/warning.yml +22 -0
- dataface/core/defaults/themes/_base.yaml +786 -0
- dataface/core/defaults/themes/bi.yaml +16 -0
- dataface/core/defaults/themes/carbong100.yaml +41 -0
- dataface/core/defaults/themes/cream.yaml +122 -0
- dataface/core/defaults/themes/dark.yaml +40 -0
- dataface/core/defaults/themes/diagnostics-title-angle-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-baseline-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-baseline.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-center.yaml +8 -0
- dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +25 -0
- dataface/core/defaults/themes/diagnostics-title-left.yaml +8 -0
- dataface/core/defaults/themes/diagnostics-title-offset-extreme.yaml +9 -0
- dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +24 -0
- dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +24 -0
- dataface/core/defaults/themes/editorial.yaml +147 -0
- dataface/core/defaults/themes/light.yaml +30 -0
- dataface/core/defaults/themes/looker.yaml +17 -0
- dataface/core/defaults/themes/stark.yaml +134 -0
- dataface/core/errors/__init__.py +67 -0
- dataface/core/errors/codes_compile.py +56 -0
- dataface/core/errors/codes_execute.py +177 -0
- dataface/core/errors/codes_render.py +106 -0
- dataface/core/errors/codes_unknown.py +15 -0
- dataface/core/errors/hints.py +74 -0
- dataface/core/errors/registry.py +42 -0
- dataface/core/errors/structured.py +92 -0
- dataface/core/execute/__init__.py +91 -0
- dataface/core/execute/adapters/__init__.py +49 -0
- dataface/core/execute/adapters/adapter_registry.py +400 -0
- dataface/core/execute/adapters/base.py +245 -0
- dataface/core/execute/adapters/csv_adapter.py +239 -0
- dataface/core/execute/adapters/dbt_adapter.py +283 -0
- dataface/core/execute/adapters/dbt_adapter_factory.py +212 -0
- dataface/core/execute/adapters/dbt_macro_loader.py +95 -0
- dataface/core/execute/adapters/dbt_utils.py +150 -0
- dataface/core/execute/adapters/http_adapter.py +224 -0
- dataface/core/execute/adapters/metricflow_adapter.py +94 -0
- dataface/core/execute/adapters/schema_resolver_adapter.py +144 -0
- dataface/core/execute/adapters/sql_adapter.py +710 -0
- dataface/core/execute/adapters/values_adapter.py +58 -0
- dataface/core/execute/batch.py +744 -0
- dataface/core/execute/cache_backend.py +135 -0
- dataface/core/execute/cache_keys.py +66 -0
- dataface/core/execute/dbt_jinja.py +21 -0
- dataface/core/execute/dialects/__init__.py +121 -0
- dataface/core/execute/dialects/athena.py +75 -0
- dataface/core/execute/dialects/base.py +302 -0
- dataface/core/execute/dialects/bigquery.py +38 -0
- dataface/core/execute/dialects/databricks.py +68 -0
- dataface/core/execute/dialects/duckdb.py +35 -0
- dataface/core/execute/dialects/mysql.py +68 -0
- dataface/core/execute/dialects/postgres.py +39 -0
- dataface/core/execute/dialects/redshift.py +12 -0
- dataface/core/execute/dialects/snowflake.py +51 -0
- dataface/core/execute/dialects/sqlserver.py +92 -0
- dataface/core/execute/duckdb_cache.py +712 -0
- dataface/core/execute/duckdb_config.py +26 -0
- dataface/core/execute/errors.py +213 -0
- dataface/core/execute/executor.py +1249 -0
- dataface/core/execute/parallel.py +162 -0
- dataface/core/execute/setup_sql.py +58 -0
- dataface/core/execute/source_registry.py +72 -0
- dataface/core/execute/source_resolver.py +255 -0
- dataface/core/execute/sql_guard.py +387 -0
- dataface/core/execute/sql_literals.py +199 -0
- dataface/core/fonts.py +52 -0
- dataface/core/inspect/__init__.py +32 -0
- dataface/core/inspect/cache_factory.py +98 -0
- dataface/core/inspect/db_types.py +162 -0
- dataface/core/inspect/dbt_schema.py +96 -0
- dataface/core/inspect/defaults.yml +37 -0
- dataface/core/inspect/fanout_risk.py +109 -0
- dataface/core/inspect/manifest_utils.py +77 -0
- dataface/core/inspect/partials/categorical.yml +40 -0
- dataface/core/inspect/partials/date.yml +40 -0
- dataface/core/inspect/partials/numeric.yml +55 -0
- dataface/core/inspect/partition_types.py +38 -0
- dataface/core/inspect/query_validator.py +975 -0
- dataface/core/inspect/renderer.py +354 -0
- dataface/core/inspect/resolver.py +808 -0
- dataface/core/inspect/search.py +461 -0
- dataface/core/inspect/sources/__init__.py +32 -0
- dataface/core/inspect/sources/dbt.py +738 -0
- dataface/core/inspect/sources/duckdb_utils.py +66 -0
- dataface/core/inspect/templates/__init__.py +1 -0
- dataface/core/inspect/templates/categorical_column.yml +196 -0
- dataface/core/inspect/templates/charts.yml +109 -0
- dataface/core/inspect/templates/date_column.yml +248 -0
- dataface/core/inspect/templates/model.yml +138 -0
- dataface/core/inspect/templates/numeric_column.yml +261 -0
- dataface/core/inspect/templates/quality.yml +80 -0
- dataface/core/inspect/templates/string_column.yml +263 -0
- dataface/core/project_roots.py +165 -0
- dataface/core/render/__init__.py +87 -0
- dataface/core/render/board_links.py +176 -0
- dataface/core/render/chart/__init__.py +27 -0
- dataface/core/render/chart/arc_attached_table.py +251 -0
- dataface/core/render/chart/artifacts.py +16 -0
- dataface/core/render/chart/callout.py +225 -0
- dataface/core/render/chart/decisions.py +358 -0
- dataface/core/render/chart/geo.py +700 -0
- dataface/core/render/chart/kpi.py +916 -0
- dataface/core/render/chart/labels.py +76 -0
- dataface/core/render/chart/pipeline.py +818 -0
- dataface/core/render/chart/presentation.py +36 -0
- dataface/core/render/chart/profile.py +3438 -0
- dataface/core/render/chart/render_single.py +347 -0
- dataface/core/render/chart/renderers.py +193 -0
- dataface/core/render/chart/rendering.py +565 -0
- dataface/core/render/chart/serialization.py +90 -0
- dataface/core/render/chart/spark.py +496 -0
- dataface/core/render/chart/spark_bar.py +370 -0
- dataface/core/render/chart/spec_builders.py +154 -0
- dataface/core/render/chart/standard_renderer.py +2645 -0
- dataface/core/render/chart/table.py +2957 -0
- dataface/core/render/chart/table_support.py +1452 -0
- dataface/core/render/chart/tick_values.py +66 -0
- dataface/core/render/chart/time_unit_detect.py +809 -0
- dataface/core/render/chart/title_overflow.py +157 -0
- dataface/core/render/chart/type_inference.py +122 -0
- dataface/core/render/chart/validation.py +99 -0
- dataface/core/render/chart/vega_lite.py +125 -0
- dataface/core/render/chart/vega_lite_types.py +268 -0
- dataface/core/render/chart/vl_field_maps.py +346 -0
- dataface/core/render/chart_interactivity.py +24 -0
- dataface/core/render/control_registry.py +287 -0
- dataface/core/render/converters/__init__.py +24 -0
- dataface/core/render/converters/chart.py +276 -0
- dataface/core/render/converters/html.py +98 -0
- dataface/core/render/converters/pdf.py +40 -0
- dataface/core/render/converters/png.py +41 -0
- dataface/core/render/errors.py +144 -0
- dataface/core/render/face_api.py +160 -0
- dataface/core/render/faces.py +1194 -0
- dataface/core/render/font_measurement.py +48 -0
- dataface/core/render/font_support.py +197 -0
- dataface/core/render/fonts/DFTSansTabular-Regular.ttf +0 -0
- dataface/core/render/fonts/DFTSansTabular-Regular.woff2 +0 -0
- dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.ttf +0 -0
- dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.ttf +0 -0
- dataface/core/render/fonts/InterVariable.ttf +0 -0
- dataface/core/render/fonts/InterVariable.woff2 +0 -0
- dataface/core/render/fonts/NOTO_COLOR_EMOJI_LICENSE.txt +93 -0
- dataface/core/render/fonts/NOTO_EMOJI_LICENSE.txt +93 -0
- dataface/core/render/fonts/NotoColorEmoji-Regular.ttf +0 -0
- dataface/core/render/fonts/NotoColorEmoji-Regular.woff2 +0 -0
- dataface/core/render/fonts/NotoEmoji-Regular.ttf +0 -0
- dataface/core/render/fonts/NotoEmoji-Regular.woff2 +0 -0
- dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -0
- dataface/core/render/fonts/SOURCE_SERIF_4_LICENSE.txt +98 -0
- dataface/core/render/fonts/SourceCodePro-Regular.ttf +0 -0
- dataface/core/render/fonts/SourceSerif4-Regular.ttf +0 -0
- dataface/core/render/fonts/_emoji_font_face.css +43 -0
- dataface/core/render/fonts/source-serif-4-variable-latin.woff2 +0 -0
- dataface/core/render/format_utils.py +329 -0
- dataface/core/render/geo_defaults.yml +28 -0
- dataface/core/render/json_format.py +146 -0
- dataface/core/render/layout_sizing.py +865 -0
- dataface/core/render/layouts.py +541 -0
- dataface/core/render/markdown_defaults.yml +16 -0
- dataface/core/render/missing_vars_prompt.py +79 -0
- dataface/core/render/placeholder.py +389 -0
- dataface/core/render/render_result.py +14 -0
- dataface/core/render/renderer.py +467 -0
- dataface/core/render/script_embedding.py +16 -0
- dataface/core/render/svg_utils.py +212 -0
- dataface/core/render/template_loader.py +69 -0
- dataface/core/render/templates/controls/_styles.css +606 -0
- dataface/core/render/templates/controls/checkbox.html +16 -0
- dataface/core/render/templates/controls/date.html +16 -0
- dataface/core/render/templates/controls/number.html +19 -0
- dataface/core/render/templates/controls/readonly.html +9 -0
- dataface/core/render/templates/controls/select.html +21 -0
- dataface/core/render/templates/controls/slider.html +22 -0
- dataface/core/render/templates/controls/text.html +16 -0
- dataface/core/render/templates/scripts/chart_interactivity.js +191 -0
- dataface/core/render/templates/scripts/variables.js +976 -0
- dataface/core/render/templates/svg/grid_pattern.svg +3 -0
- dataface/core/render/templates/svg/styles.css +51 -0
- dataface/core/render/terminal.py +311 -0
- dataface/core/render/terminal_charts.py +563 -0
- dataface/core/render/terminal_defaults.yml +2 -0
- dataface/core/render/terminal_layouts.py +299 -0
- dataface/core/render/terminal_text.py +31 -0
- dataface/core/render/text/__init__.py +1 -0
- dataface/core/render/text/case.py +113 -0
- dataface/core/render/text_format.py +129 -0
- dataface/core/render/utils.py +106 -0
- dataface/core/render/variable_controls.py +946 -0
- dataface/core/render/variable_input_refinement.py +140 -0
- dataface/core/render/warnings/__init__.py +15 -0
- dataface/core/render/warnings/bar_color_1_to_1_with_x.py +80 -0
- dataface/core/render/warnings/base.py +44 -0
- dataface/core/render/warnings/fanout_risk.py +15 -0
- dataface/core/render/warnings/from_query_diagnostic.py +56 -0
- dataface/core/render/warnings/missing_join_predicate.py +13 -0
- dataface/core/render/warnings/query_parse_error.py +14 -0
- dataface/core/render/warnings/query_returned_zero_rows.py +42 -0
- dataface/core/render/warnings/reaggregation.py +14 -0
- dataface/core/render/warnings/registry.py +45 -0
- dataface/core/render/warnings/suppression.py +46 -0
- dataface/core/render/warnings/temporal_single_point.py +63 -0
- dataface/core/render/warnings/unreferenced_chart.py +15 -0
- dataface/core/render/warnings/y_encoding_mostly_null.py +76 -0
- dataface/core/render/yaml_format.py +167 -0
- dataface/core/resolve_face.py +195 -0
- dataface/core/schema/__init__.py +0 -0
- dataface/core/schema/guidance.py +151 -0
- dataface/core/scoped_paths.py +59 -0
- dataface/core/serve/__init__.py +14 -0
- dataface/core/serve/bootstrap.py +39 -0
- dataface/core/serve/embedded.py +57 -0
- dataface/core/serve/port.py +129 -0
- dataface/core/serve/server.py +938 -0
- dataface/core/serve/templates/__init__.py +0 -0
- dataface/core/serve/templates/directory.yml +6 -0
- dataface/core/serve/templates/error.html.j2 +217 -0
- dataface/core/utils.py +121 -0
- dataface/core/validate.py +64 -0
- dataface/integrations/__init__.py +0 -0
- dataface/integrations/highlighting.py +351 -0
- dataface/integrations/markdown.py +537 -0
- dataface/py.typed +0 -0
- dataface-0.1.2.dist-info/METADATA +375 -0
- dataface-0.1.2.dist-info/RECORD +455 -0
- dataface-0.1.2.dist-info/WHEEL +4 -0
- dataface-0.1.2.dist-info/entry_points.txt +2 -0
- dataface-0.1.2.dist-info/licenses/LICENSE +202 -0
- mdsvg/__init__.py +168 -0
- mdsvg/fonts.py +656 -0
- mdsvg/images.py +299 -0
- mdsvg/parser.py +629 -0
- mdsvg/playground.py +284 -0
- mdsvg/py.typed +2 -0
- mdsvg/renderer.py +1623 -0
- mdsvg/style.py +355 -0
- mdsvg/types.py +200 -0
- mdsvg/utils.py +86 -0
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
"""Data-aware layout sizing for the render pipeline.
|
|
2
|
+
|
|
3
|
+
Stage: RENDER
|
|
4
|
+
Purpose: Calculate layout dimensions using actual query data and rendered chart heights.
|
|
5
|
+
|
|
6
|
+
This module owns all data-aware sizing logic that requires an executor or vl-convert:
|
|
7
|
+
- Render-first Vega sizing (render charts to get true heights, cache SVGs)
|
|
8
|
+
- Table sizing from actual row counts
|
|
9
|
+
- Cols height alignment (re-render shorter Vega items at max height)
|
|
10
|
+
|
|
11
|
+
It injects a HeightProvider callback into the pure layout algorithms in
|
|
12
|
+
compile/sizing.py, keeping the compile module free of render/execute imports.
|
|
13
|
+
|
|
14
|
+
Entry Point:
|
|
15
|
+
- calculate_data_aware_layout(face, executor, variables, render_first)
|
|
16
|
+
Called from renderer.py after query pre-execution.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from typing import TYPE_CHECKING, Any
|
|
24
|
+
|
|
25
|
+
from dataface.core.compile.models.chart.compiled import (
|
|
26
|
+
NON_ASPECT_RATIO_TYPES,
|
|
27
|
+
Chart,
|
|
28
|
+
)
|
|
29
|
+
from dataface.core.compile.models.face.compiled import Face, Layout, LayoutItem
|
|
30
|
+
from dataface.core.compile.sizing import (
|
|
31
|
+
HeightProvider,
|
|
32
|
+
calculate_layout_height,
|
|
33
|
+
calculate_layout_items,
|
|
34
|
+
get_chart_content_height,
|
|
35
|
+
get_face_gap,
|
|
36
|
+
get_item_content_height,
|
|
37
|
+
get_title_height,
|
|
38
|
+
)
|
|
39
|
+
from dataface.core.execute.errors import ExecutionError
|
|
40
|
+
from dataface.core.render.chart.render_single import RenderCache
|
|
41
|
+
from dataface.core.render.chart.spec_builders import additive_padding
|
|
42
|
+
from dataface.core.render.errors import ChartDataError
|
|
43
|
+
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from dataface.core.compile.models.style.merged import MergedStyle
|
|
46
|
+
from dataface.core.execute.executor import Executor
|
|
47
|
+
|
|
48
|
+
_log = logging.getLogger(__name__)
|
|
49
|
+
_FIXED_ASPECT_PIE_TYPES = frozenset({"pie", "arc"})
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class SizingRenderCtx:
|
|
54
|
+
"""Render context threaded through data-aware sizing.
|
|
55
|
+
|
|
56
|
+
When present, the data-aware height provider renders Vega-Lite charts via
|
|
57
|
+
vl-convert instead of using aspect-ratio estimates. Results are cached
|
|
58
|
+
in render_cache for reuse by the render pass.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
resolved_style: MergedStyle
|
|
62
|
+
render_cache: RenderCache = field(default_factory=dict)
|
|
63
|
+
vega_config: dict[str, Any] | None = None
|
|
64
|
+
executor: Executor | None = None
|
|
65
|
+
variables: dict[str, Any] | None = None
|
|
66
|
+
# Heading level of the face containing the charts being sized (root=1, nested=2, …).
|
|
67
|
+
# Chart title uses face_level + 1; sizer and renderer must agree.
|
|
68
|
+
face_level: int = 1
|
|
69
|
+
# Maps chart_id → corrected spec.width for data_table charts.
|
|
70
|
+
# autosize:pad makes outer SVG wider than spec.width by a constant overhead;
|
|
71
|
+
# the render-first pass measures this and stores the shrunk spec.width here so
|
|
72
|
+
# _align_cols_heights can reuse it instead of item.width (which would re-overflow).
|
|
73
|
+
data_table_corrected_widths: dict[str, float] = field(default_factory=dict)
|
|
74
|
+
# Maps (chart_id, slot_width) → natural rendered height.
|
|
75
|
+
# Populated during the sizing pass; used to deduplicate renders of the same
|
|
76
|
+
# chart at the same width and to drive the close-enough skip in _align_cols_heights.
|
|
77
|
+
natural_heights: dict[tuple[str, float], float] = field(default_factory=dict)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _nested_render_ctx(
|
|
81
|
+
render_ctx: SizingRenderCtx,
|
|
82
|
+
nested_face: Face,
|
|
83
|
+
) -> SizingRenderCtx:
|
|
84
|
+
"""Build a SizingRenderCtx for a nested face, inheriting the shared cache."""
|
|
85
|
+
from dataface.core.compile.vega_config import compile_effective_vega_config
|
|
86
|
+
|
|
87
|
+
return SizingRenderCtx(
|
|
88
|
+
render_cache=render_ctx.render_cache,
|
|
89
|
+
natural_heights=render_ctx.natural_heights,
|
|
90
|
+
vega_config=compile_effective_vega_config(nested_face.theme),
|
|
91
|
+
resolved_style=nested_face.resolved_style,
|
|
92
|
+
executor=render_ctx.executor,
|
|
93
|
+
variables=render_ctx.variables,
|
|
94
|
+
face_level=nested_face.level,
|
|
95
|
+
data_table_corrected_widths=render_ctx.data_table_corrected_widths,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _get_table_height_from_data(
|
|
100
|
+
chart: Chart,
|
|
101
|
+
executor: Executor,
|
|
102
|
+
variables: dict[str, Any] | None,
|
|
103
|
+
resolved_style: MergedStyle,
|
|
104
|
+
width: float | None = None,
|
|
105
|
+
face_level: int = 1,
|
|
106
|
+
) -> float:
|
|
107
|
+
"""Calculate table height from actual row count.
|
|
108
|
+
|
|
109
|
+
``resolved_style`` is the face's resolved style (the same value the renderer
|
|
110
|
+
receives via ``board_style``). Face-level and chart-local overrides are merged
|
|
111
|
+
via ``build_resolved_style`` so sizer and renderer agree on row heights,
|
|
112
|
+
padding, and header dimensions.
|
|
113
|
+
|
|
114
|
+
``width`` drives the title font size (via ``chart_title_spec``) and must
|
|
115
|
+
match what the renderer will see at layout time. When ``None``, falls back
|
|
116
|
+
to the renderer's own ``TABLE_DEFAULT_WIDTH`` so both stay in lockstep.
|
|
117
|
+
|
|
118
|
+
If the query fails, sizes for an error message display.
|
|
119
|
+
"""
|
|
120
|
+
from dataface.core.compile.style_cascade import build_resolved_style
|
|
121
|
+
from dataface.core.render.chart.table import (
|
|
122
|
+
TABLE_DEFAULT_WIDTH,
|
|
123
|
+
compute_table_title_block_layout,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
effective_charts = build_resolved_style(resolved_style, chart.style)
|
|
127
|
+
tc = effective_charts.table # TableChartStyle — face-merged, all with defaults
|
|
128
|
+
|
|
129
|
+
row_height = int(tc.row.height)
|
|
130
|
+
# When the header row is hidden, it contributes no vertical extent —
|
|
131
|
+
# matches the renderer's `if not tc.header.visible: header_height = 0` branch.
|
|
132
|
+
header_height = int(tc.header.height) if tc.header.visible else 0
|
|
133
|
+
padding_y = int(tc.outer_padding)
|
|
134
|
+
title_height = compute_table_title_block_layout(
|
|
135
|
+
chart_title=chart.title,
|
|
136
|
+
chart_subtitle=chart.subtitle,
|
|
137
|
+
table_width=width if width is not None else TABLE_DEFAULT_WIDTH,
|
|
138
|
+
tc=tc,
|
|
139
|
+
padding=padding_y,
|
|
140
|
+
title_style=effective_charts.title,
|
|
141
|
+
resolved_chart_style=effective_charts,
|
|
142
|
+
face_level=face_level,
|
|
143
|
+
).height
|
|
144
|
+
bottom_padding = int(tc.bottom_padding)
|
|
145
|
+
|
|
146
|
+
# Chart-local pagination is pre-merged into effective_charts.pagination by
|
|
147
|
+
# build_resolved_style — read the resolved page_size off the merged value.
|
|
148
|
+
pagination = effective_charts.pagination
|
|
149
|
+
resolved_page_size = (
|
|
150
|
+
pagination.page_size if pagination is not None and pagination.enabled else None
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Header-body gap also vanishes when header is hidden — the renderer
|
|
154
|
+
# uses the same zero-out so sizer + renderer agree on total height.
|
|
155
|
+
header_body_gap = int(row_height * 0.25) if tc.header.visible else 0
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
data = executor.execute_chart(chart, variables)
|
|
159
|
+
row_count = len(data)
|
|
160
|
+
except ExecutionError as exc:
|
|
161
|
+
# Migrate-or-fail: only the query-failed signal warrants a static height
|
|
162
|
+
# fallback. ValueError/OSError/RuntimeError/LookupError are bug-class —
|
|
163
|
+
# let them surface loudly so the underlying bug is fixed, not masked.
|
|
164
|
+
_log.warning("Table height estimation failed for chart %r: %s", chart.id, exc)
|
|
165
|
+
return (
|
|
166
|
+
title_height
|
|
167
|
+
+ header_height
|
|
168
|
+
+ header_body_gap
|
|
169
|
+
+ row_height
|
|
170
|
+
+ padding_y
|
|
171
|
+
+ bottom_padding
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
multi_page = resolved_page_size is not None and row_count > resolved_page_size
|
|
175
|
+
if resolved_page_size is not None:
|
|
176
|
+
row_count = min(row_count, resolved_page_size)
|
|
177
|
+
height = (
|
|
178
|
+
title_height
|
|
179
|
+
+ header_height
|
|
180
|
+
+ header_body_gap
|
|
181
|
+
+ (row_count * row_height)
|
|
182
|
+
+ padding_y
|
|
183
|
+
+ bottom_padding
|
|
184
|
+
)
|
|
185
|
+
if multi_page:
|
|
186
|
+
from dataface.core.render.chart.table import _PAGINATION_CONTROL_HEIGHT
|
|
187
|
+
|
|
188
|
+
height += _PAGINATION_CONTROL_HEIGHT
|
|
189
|
+
return height
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _make_data_aware_height_provider(
|
|
193
|
+
render_ctx: SizingRenderCtx,
|
|
194
|
+
executor: Executor,
|
|
195
|
+
variables: dict[str, Any] | None,
|
|
196
|
+
card_padding: float,
|
|
197
|
+
) -> HeightProvider:
|
|
198
|
+
"""Create a HeightProvider that uses render-first sizing for Vega charts.
|
|
199
|
+
|
|
200
|
+
For Vega-Lite charts: renders via vl-convert, caches SVG, returns actual height.
|
|
201
|
+
For tables: uses actual row counts from executor.
|
|
202
|
+
For everything else: delegates to the static compile-time calculator.
|
|
203
|
+
|
|
204
|
+
Render-first Vega sizing (vl-convert renders) only fires when
|
|
205
|
+
render_ctx.executor is not None. When render_ctx.executor is None
|
|
206
|
+
(render_first=False), the provider falls back to static aspect-ratio
|
|
207
|
+
estimates for Vega charts while still sizing tables from row counts.
|
|
208
|
+
"""
|
|
209
|
+
from dataface.core.render.chart.render_single import render_chart_to_svg
|
|
210
|
+
|
|
211
|
+
def provider(
|
|
212
|
+
item: LayoutItem,
|
|
213
|
+
card_gap: float,
|
|
214
|
+
gap: float,
|
|
215
|
+
width: float,
|
|
216
|
+
variable_defaults: dict[str, Any] | None,
|
|
217
|
+
) -> float:
|
|
218
|
+
if item.type == "chart" and item.chart:
|
|
219
|
+
card_pad = card_padding
|
|
220
|
+
|
|
221
|
+
# Table: use actual row counts
|
|
222
|
+
if item.chart.type == "table":
|
|
223
|
+
return (
|
|
224
|
+
_get_table_height_from_data(
|
|
225
|
+
item.chart,
|
|
226
|
+
executor,
|
|
227
|
+
variables,
|
|
228
|
+
resolved_style=render_ctx.resolved_style,
|
|
229
|
+
width=width,
|
|
230
|
+
face_level=render_ctx.face_level,
|
|
231
|
+
)
|
|
232
|
+
+ 2 * card_pad
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
if item.chart.type == "callout":
|
|
236
|
+
_chart_svg, _, actual_height = render_chart_to_svg(
|
|
237
|
+
item.chart,
|
|
238
|
+
executor,
|
|
239
|
+
variables or {},
|
|
240
|
+
width,
|
|
241
|
+
height=None,
|
|
242
|
+
vega_config=render_ctx.vega_config,
|
|
243
|
+
resolved_style=render_ctx.resolved_style,
|
|
244
|
+
face_level=render_ctx.face_level,
|
|
245
|
+
)
|
|
246
|
+
render_ctx.render_cache[(item.chart.id, width, actual_height)] = (
|
|
247
|
+
_chart_svg,
|
|
248
|
+
actual_height,
|
|
249
|
+
)
|
|
250
|
+
render_ctx.natural_heights[(item.chart.id, width)] = actual_height
|
|
251
|
+
return actual_height
|
|
252
|
+
|
|
253
|
+
# spark_bar: natural-height SVG renderer — renders content-first.
|
|
254
|
+
# Returns natural_height + 2*card_pad to match SVG_LAYOUT_PADDED_TYPES behavior
|
|
255
|
+
# (card_pad inset applied via translate in render_chart_item).
|
|
256
|
+
# Not cached: rendering.py skips render_cache for SVG_LAYOUT_PADDED_TYPES,
|
|
257
|
+
# so the final render pass re-renders regardless.
|
|
258
|
+
if item.chart.type == "spark_bar":
|
|
259
|
+
_chart_svg, _, actual_height = render_chart_to_svg(
|
|
260
|
+
item.chart,
|
|
261
|
+
executor,
|
|
262
|
+
variables or {},
|
|
263
|
+
width,
|
|
264
|
+
height=None,
|
|
265
|
+
vega_config=render_ctx.vega_config,
|
|
266
|
+
resolved_style=render_ctx.resolved_style,
|
|
267
|
+
face_level=render_ctx.face_level,
|
|
268
|
+
)
|
|
269
|
+
return actual_height + 2 * card_pad
|
|
270
|
+
|
|
271
|
+
# Render-first Vega sizing.
|
|
272
|
+
# Vega charts now render at full item width with card_pad as internal
|
|
273
|
+
# Vega padding, so the returned SVG height already includes the inset.
|
|
274
|
+
# Do NOT add 2*card_pad here — it would double-count the padding.
|
|
275
|
+
if (
|
|
276
|
+
render_ctx.executor is not None
|
|
277
|
+
and item.chart.type not in NON_ASPECT_RATIO_TYPES
|
|
278
|
+
):
|
|
279
|
+
render_inner_width = width
|
|
280
|
+
chart_padding = render_ctx.resolved_style.charts.padding
|
|
281
|
+
# static_estimate approximates Vega output height: marks + Vega padding
|
|
282
|
+
static_estimate = (
|
|
283
|
+
get_chart_content_height(
|
|
284
|
+
item.chart,
|
|
285
|
+
width=render_inner_width,
|
|
286
|
+
resolved_style=render_ctx.resolved_style,
|
|
287
|
+
)
|
|
288
|
+
+ 2 * card_pad
|
|
289
|
+
)
|
|
290
|
+
# Use item.height when it has been pre-set (future: cols alignment
|
|
291
|
+
# pre-pass). In the normal sizing flow item.height is 0 here because
|
|
292
|
+
# _calculate_rows_dimensions fires the provider in its first pass before
|
|
293
|
+
# the second pass sets item.height. _fix_slot_heights_in_tree runs after
|
|
294
|
+
# calculate_layout_items to re-render at the final slot height when the
|
|
295
|
+
# two values diverge (e.g. a 600px wrapper around an 800px estimate).
|
|
296
|
+
render_height = item.height if item.height > 0 else static_estimate
|
|
297
|
+
|
|
298
|
+
# Use cached result if already rendered at this width
|
|
299
|
+
if (item.chart.id, render_inner_width) in render_ctx.natural_heights:
|
|
300
|
+
cached_height = render_ctx.natural_heights[
|
|
301
|
+
(item.chart.id, render_inner_width)
|
|
302
|
+
]
|
|
303
|
+
return max(cached_height, static_estimate)
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
chart_svg, actual_width, actual_height = render_chart_to_svg(
|
|
307
|
+
item.chart,
|
|
308
|
+
render_ctx.executor,
|
|
309
|
+
render_ctx.variables or {},
|
|
310
|
+
render_inner_width,
|
|
311
|
+
height=render_height,
|
|
312
|
+
vega_config=render_ctx.vega_config,
|
|
313
|
+
resolved_style=render_ctx.resolved_style,
|
|
314
|
+
padding=additive_padding(card_pad, chart_padding),
|
|
315
|
+
face_level=render_ctx.face_level,
|
|
316
|
+
)
|
|
317
|
+
except (ExecutionError, ChartDataError) as exc:
|
|
318
|
+
# Migrate-or-fail: only ExecutionError (query failed) and
|
|
319
|
+
# ChartDataError (vega-lite spec rejected) are expected
|
|
320
|
+
# data-side failures we estimate around. KeyError/ValueError/
|
|
321
|
+
# OSError/RuntimeError/LookupError are bug-class exceptions
|
|
322
|
+
# — let them surface loudly, do not silently render an
|
|
323
|
+
# aspect-ratio fallback that masks the bug.
|
|
324
|
+
_log.warning(
|
|
325
|
+
"Render-first sizing failed for chart %r "
|
|
326
|
+
"(will use aspect-ratio estimate): %s",
|
|
327
|
+
item.chart.id,
|
|
328
|
+
exc,
|
|
329
|
+
)
|
|
330
|
+
else:
|
|
331
|
+
# Clamp: tiny overshoot (≤2px above static_estimate) → static_estimate.
|
|
332
|
+
# Two-pass height correction (endpoint-label hconcat) can return
|
|
333
|
+
# actual_height = static_estimate + ε due to fractional-pixel rounding.
|
|
334
|
+
# Without the clamp, natural_heights differs by ε between endpoint_labels
|
|
335
|
+
# ON and OFF, causing row_height drift. Undershoots are also snapped up.
|
|
336
|
+
if actual_height - static_estimate <= 2.0:
|
|
337
|
+
actual_height = static_estimate
|
|
338
|
+
|
|
339
|
+
# autosize:pad (set by attach_data_table) makes the outer
|
|
340
|
+
# SVG wider than render_inner_width by the y-axis label +
|
|
341
|
+
# padding + legend overhead. Measure the first render's
|
|
342
|
+
# actual outer width, compute the overhead, and re-render
|
|
343
|
+
# with spec.width pre-shrunk so the second render's outer
|
|
344
|
+
# SVG fits the allocated slot.
|
|
345
|
+
# Pass height=static_estimate (not actual_height) to the
|
|
346
|
+
# re-render: under autosize:pad, spec.height is the *inner*
|
|
347
|
+
# plot rect, so Vega adds strip/axis on top — using
|
|
348
|
+
# actual_height as spec.height would inflate the output by
|
|
349
|
+
# ~strip_height on every re-render.
|
|
350
|
+
# Store the shrunk width in data_table_corrected_widths so
|
|
351
|
+
# _align_cols_heights can reuse it instead of item.width,
|
|
352
|
+
# preventing the overhead from being re-applied on alignment.
|
|
353
|
+
if item.chart.data_table is not None:
|
|
354
|
+
overhead = actual_width - render_inner_width
|
|
355
|
+
if overhead > 0:
|
|
356
|
+
shrunk_width = max(render_inner_width - overhead, 1.0)
|
|
357
|
+
try:
|
|
358
|
+
chart_svg, _, rerender_height = render_chart_to_svg(
|
|
359
|
+
item.chart,
|
|
360
|
+
render_ctx.executor,
|
|
361
|
+
render_ctx.variables or {},
|
|
362
|
+
shrunk_width,
|
|
363
|
+
height=render_height,
|
|
364
|
+
vega_config=render_ctx.vega_config,
|
|
365
|
+
resolved_style=render_ctx.resolved_style,
|
|
366
|
+
padding=additive_padding(card_pad, chart_padding),
|
|
367
|
+
face_level=render_ctx.face_level,
|
|
368
|
+
)
|
|
369
|
+
except (ExecutionError, ChartDataError) as exc:
|
|
370
|
+
_log.warning(
|
|
371
|
+
"Width-correction re-render failed for chart %r "
|
|
372
|
+
"(keeping first render): %s",
|
|
373
|
+
item.chart.id,
|
|
374
|
+
exc,
|
|
375
|
+
)
|
|
376
|
+
else:
|
|
377
|
+
actual_height = max(rerender_height, static_estimate)
|
|
378
|
+
render_ctx.data_table_corrected_widths[
|
|
379
|
+
item.chart.id
|
|
380
|
+
] = shrunk_width
|
|
381
|
+
|
|
382
|
+
render_ctx.render_cache[
|
|
383
|
+
(item.chart.id, render_inner_width, actual_height)
|
|
384
|
+
] = (chart_svg, actual_height)
|
|
385
|
+
render_ctx.natural_heights[(item.chart.id, render_inner_width)] = (
|
|
386
|
+
actual_height
|
|
387
|
+
)
|
|
388
|
+
return actual_height
|
|
389
|
+
|
|
390
|
+
# Fallback: static aspect-ratio estimate (marks + Vega padding)
|
|
391
|
+
return (
|
|
392
|
+
get_chart_content_height(
|
|
393
|
+
item.chart,
|
|
394
|
+
width=width,
|
|
395
|
+
resolved_style=render_ctx.resolved_style,
|
|
396
|
+
)
|
|
397
|
+
+ 2 * card_pad
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
if item.type == "face" and item.face:
|
|
401
|
+
data_aware = get_item_content_height(
|
|
402
|
+
item,
|
|
403
|
+
card_gap,
|
|
404
|
+
gap,
|
|
405
|
+
width,
|
|
406
|
+
variable_defaults,
|
|
407
|
+
height_provider=provider,
|
|
408
|
+
resolved_style=render_ctx.resolved_style,
|
|
409
|
+
)
|
|
410
|
+
return max(data_aware, item.height)
|
|
411
|
+
|
|
412
|
+
# Fallback for any other item types
|
|
413
|
+
return get_item_content_height(
|
|
414
|
+
item,
|
|
415
|
+
card_gap,
|
|
416
|
+
gap,
|
|
417
|
+
width,
|
|
418
|
+
variable_defaults,
|
|
419
|
+
height_provider=provider,
|
|
420
|
+
resolved_style=render_ctx.resolved_style,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
return provider
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _col_chart_body_target(
|
|
427
|
+
items: list[LayoutItem], render_ctx: SizingRenderCtx
|
|
428
|
+
) -> float:
|
|
429
|
+
"""Max natural chart body height across all items in a cols row.
|
|
430
|
+
|
|
431
|
+
For direct chart items uses the natural height from natural_heights.
|
|
432
|
+
For face items the logic depends on the face's inner layout:
|
|
433
|
+
- All-Vega rows face (every sub-item is a Vega chart): contribute
|
|
434
|
+
item.height (= sum of sub-chart heights + gaps, set by the sizing pass).
|
|
435
|
+
An adjacent direct chart must fill the same cell height.
|
|
436
|
+
- Any other face layout (mixed content, cols sub-layout, single chart):
|
|
437
|
+
contribute max(sub-chart natural heights) — the non-chart overhead must
|
|
438
|
+
not inflate adjacent chart bodies.
|
|
439
|
+
Non-aspect-ratio types (table, kpi, spark_bar) are excluded.
|
|
440
|
+
Returns 0.0 if no Vega charts are found.
|
|
441
|
+
"""
|
|
442
|
+
max_h = 0.0
|
|
443
|
+
for item in items:
|
|
444
|
+
if item.chart is not None and item.chart.type not in NON_ASPECT_RATIO_TYPES:
|
|
445
|
+
h = render_ctx.natural_heights.get((item.chart.id, item.width))
|
|
446
|
+
if h is not None:
|
|
447
|
+
max_h = max(max_h, h)
|
|
448
|
+
elif item.face is not None:
|
|
449
|
+
sub_items = item.face.layout.items
|
|
450
|
+
is_all_vega_rows = (
|
|
451
|
+
item.face.layout.type == "rows"
|
|
452
|
+
and len(sub_items) > 1
|
|
453
|
+
and all(
|
|
454
|
+
s.chart is not None and s.chart.type not in NON_ASPECT_RATIO_TYPES
|
|
455
|
+
for s in sub_items
|
|
456
|
+
)
|
|
457
|
+
)
|
|
458
|
+
if is_all_vega_rows and item.height > 0:
|
|
459
|
+
# face item.height = sum(sub-chart heights) + inter-item gaps,
|
|
460
|
+
# computed by the sizing pass. Use it as-is so the adjacent
|
|
461
|
+
# direct chart fills the same cell.
|
|
462
|
+
max_h = max(max_h, item.height)
|
|
463
|
+
else:
|
|
464
|
+
for sub in sub_items:
|
|
465
|
+
if (
|
|
466
|
+
sub.chart is not None
|
|
467
|
+
and sub.chart.type not in NON_ASPECT_RATIO_TYPES
|
|
468
|
+
):
|
|
469
|
+
h = render_ctx.natural_heights.get((sub.chart.id, sub.width))
|
|
470
|
+
if h is not None:
|
|
471
|
+
max_h = max(max_h, h)
|
|
472
|
+
return max_h
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def _effective_alignment_target_for_chart(
|
|
476
|
+
chart: Chart,
|
|
477
|
+
width: float,
|
|
478
|
+
target_height: float,
|
|
479
|
+
render_ctx: SizingRenderCtx,
|
|
480
|
+
) -> float:
|
|
481
|
+
"""Return the per-chart alignment target, capping pie/arc growth."""
|
|
482
|
+
natural_h = render_ctx.natural_heights.get((chart.id, width))
|
|
483
|
+
if (
|
|
484
|
+
chart.type in _FIXED_ASPECT_PIE_TYPES
|
|
485
|
+
and natural_h is not None
|
|
486
|
+
and target_height > natural_h
|
|
487
|
+
):
|
|
488
|
+
return natural_h
|
|
489
|
+
return target_height
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def _align_face_charts(
|
|
493
|
+
face_item: LayoutItem, target_height: float, render_ctx: SizingRenderCtx
|
|
494
|
+
) -> None:
|
|
495
|
+
"""Expand Vega charts inside a face item toward target_height.
|
|
496
|
+
|
|
497
|
+
The face bounds the expansion: charts can grow at most to face.layout.content_height
|
|
498
|
+
minus the space taken by non-chart siblings (rows layout only — stacked items share
|
|
499
|
+
vertical space). For cols layouts, items are side-by-side so non-chart siblings do
|
|
500
|
+
not reduce the Vega chart's available height.
|
|
501
|
+
|
|
502
|
+
Updates each chart's item.height so the render layer uses the correct cache key,
|
|
503
|
+
then delegates the actual re-render to _align_cols_heights.
|
|
504
|
+
"""
|
|
505
|
+
assert face_item.face is not None, "_align_face_charts requires a face LayoutItem"
|
|
506
|
+
face = face_item.face
|
|
507
|
+
vega_count = sum(
|
|
508
|
+
1
|
|
509
|
+
for sub in face.layout.items
|
|
510
|
+
if sub.chart is not None and sub.chart.type not in NON_ASPECT_RATIO_TYPES
|
|
511
|
+
)
|
|
512
|
+
# cols layout: all side-by-side charts need equalization.
|
|
513
|
+
# non-cols with exactly 1 Vega chart (e.g. chart + text caption): safe to expand.
|
|
514
|
+
# non-cols with multiple Vega charts: stacked rows — equalizing would overflow the face.
|
|
515
|
+
if face.layout.type != "cols" and vega_count != 1:
|
|
516
|
+
return
|
|
517
|
+
content_h: float = face.layout.content_height
|
|
518
|
+
if content_h <= 0:
|
|
519
|
+
return
|
|
520
|
+
|
|
521
|
+
if face.layout.type == "cols":
|
|
522
|
+
# cols: items are side-by-side — non-chart siblings don't consume Vega height.
|
|
523
|
+
face_chart_max = content_h
|
|
524
|
+
else:
|
|
525
|
+
# rows with 1 Vega chart: stacked non-chart items (captions, KPIs) do consume height.
|
|
526
|
+
non_chart_h = sum(
|
|
527
|
+
sub.height
|
|
528
|
+
for sub in face.layout.items
|
|
529
|
+
if sub.chart is None or sub.chart.type in NON_ASPECT_RATIO_TYPES
|
|
530
|
+
)
|
|
531
|
+
face_chart_max = max(content_h - non_chart_h, 0.0)
|
|
532
|
+
effective = min(target_height, face_chart_max)
|
|
533
|
+
|
|
534
|
+
for sub in face.layout.items:
|
|
535
|
+
if sub.chart is not None and sub.chart.type not in NON_ASPECT_RATIO_TYPES:
|
|
536
|
+
sub.height = _effective_alignment_target_for_chart(
|
|
537
|
+
sub.chart,
|
|
538
|
+
sub.width,
|
|
539
|
+
effective,
|
|
540
|
+
render_ctx,
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
_align_cols_heights(face.layout.items, effective, render_ctx)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _align_cols_heights(
|
|
547
|
+
items: list[LayoutItem], target_height: float, render_ctx: SizingRenderCtx
|
|
548
|
+
) -> None:
|
|
549
|
+
"""Re-render shorter Vega cols items at target_height; update their cache entries.
|
|
550
|
+
|
|
551
|
+
Handles both direct chart items and face items (delegating to
|
|
552
|
+
_align_face_charts for the latter). Non-Vega items (table, KPI) are not
|
|
553
|
+
re-rendered — their container gets the max height but their content stays
|
|
554
|
+
at its natural height.
|
|
555
|
+
"""
|
|
556
|
+
from dataface.core.render.chart.render_single import render_chart_to_svg
|
|
557
|
+
|
|
558
|
+
card_pad = float(render_ctx.resolved_style.board.card_padding)
|
|
559
|
+
chart_padding = render_ctx.resolved_style.charts.padding
|
|
560
|
+
|
|
561
|
+
if render_ctx.executor is None:
|
|
562
|
+
return
|
|
563
|
+
|
|
564
|
+
executor = render_ctx.executor
|
|
565
|
+
variables = render_ctx.variables or {}
|
|
566
|
+
|
|
567
|
+
for item in items:
|
|
568
|
+
if item.face is not None:
|
|
569
|
+
_align_face_charts(item, target_height, render_ctx)
|
|
570
|
+
continue
|
|
571
|
+
if item.chart is None or item.chart.type in NON_ASPECT_RATIO_TYPES:
|
|
572
|
+
continue
|
|
573
|
+
chart_id = item.chart.id
|
|
574
|
+
natural_h = render_ctx.natural_heights.get((chart_id, item.width))
|
|
575
|
+
effective_target = _effective_alignment_target_for_chart(
|
|
576
|
+
item.chart,
|
|
577
|
+
item.width,
|
|
578
|
+
target_height,
|
|
579
|
+
render_ctx,
|
|
580
|
+
)
|
|
581
|
+
if natural_h is None or abs(natural_h - effective_target) < 1.0:
|
|
582
|
+
continue
|
|
583
|
+
|
|
584
|
+
# Vega-family: render at item width (or the corrected shrunk width for
|
|
585
|
+
# data_table charts). autosize:pad (activated by attach_data_table) makes
|
|
586
|
+
# the outer SVG wider than spec.width by a constant overhead; using item.width
|
|
587
|
+
# as spec.width would re-apply that overhead and overflow the allocated slot.
|
|
588
|
+
render_width = render_ctx.data_table_corrected_widths.get(chart_id, item.width)
|
|
589
|
+
try:
|
|
590
|
+
chart_svg, _, actual_height = render_chart_to_svg(
|
|
591
|
+
item.chart,
|
|
592
|
+
executor,
|
|
593
|
+
variables,
|
|
594
|
+
render_width,
|
|
595
|
+
height=effective_target,
|
|
596
|
+
vega_config=render_ctx.vega_config,
|
|
597
|
+
resolved_style=render_ctx.resolved_style,
|
|
598
|
+
padding=additive_padding(card_pad, chart_padding),
|
|
599
|
+
face_level=render_ctx.face_level,
|
|
600
|
+
)
|
|
601
|
+
except ChartDataError as exc:
|
|
602
|
+
# Migrate-or-fail: only ChartDataError (vega-lite rejected the spec)
|
|
603
|
+
# is an expected per-chart failure we keep going through. Other
|
|
604
|
+
# exception types are bug-class — let them surface loudly.
|
|
605
|
+
_log.warning(
|
|
606
|
+
"Cols alignment re-render failed for chart %r; "
|
|
607
|
+
"keeping original height: %s",
|
|
608
|
+
chart_id,
|
|
609
|
+
exc,
|
|
610
|
+
)
|
|
611
|
+
continue
|
|
612
|
+
|
|
613
|
+
# autosize:pad (set by attach_data_table) makes the outer SVG taller than
|
|
614
|
+
# spec.height by the strip + axis overhead. If inflation detected, compute
|
|
615
|
+
# the overhead and re-render with spec.height pre-shrunk so the outer SVG
|
|
616
|
+
# fits within target_height.
|
|
617
|
+
if item.chart.data_table is not None:
|
|
618
|
+
overhead = actual_height - effective_target
|
|
619
|
+
if overhead > 0:
|
|
620
|
+
corrected_height = max(effective_target - overhead, 1.0)
|
|
621
|
+
try:
|
|
622
|
+
chart_svg, _, actual_height = render_chart_to_svg(
|
|
623
|
+
item.chart,
|
|
624
|
+
executor,
|
|
625
|
+
variables,
|
|
626
|
+
render_width,
|
|
627
|
+
height=corrected_height,
|
|
628
|
+
vega_config=render_ctx.vega_config,
|
|
629
|
+
resolved_style=render_ctx.resolved_style,
|
|
630
|
+
padding=additive_padding(card_pad, chart_padding),
|
|
631
|
+
face_level=render_ctx.face_level,
|
|
632
|
+
)
|
|
633
|
+
except ChartDataError as exc:
|
|
634
|
+
_log.warning(
|
|
635
|
+
"Height-correction re-render failed for chart %r "
|
|
636
|
+
"(keeping first alignment render): %s",
|
|
637
|
+
chart_id,
|
|
638
|
+
exc,
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
render_ctx.render_cache[(chart_id, item.width, effective_target)] = (
|
|
642
|
+
chart_svg,
|
|
643
|
+
actual_height,
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def _align_grid_rows(items: list[LayoutItem], render_ctx: SizingRenderCtx) -> None:
|
|
648
|
+
"""Align Vega chart heights within each row of a grid layout.
|
|
649
|
+
|
|
650
|
+
Groups items by (row, height). Items with the same row and row_span share
|
|
651
|
+
the same height and form one visual row band to align.
|
|
652
|
+
"""
|
|
653
|
+
row_groups: dict[tuple[int, float], list[LayoutItem]] = {}
|
|
654
|
+
for item in items:
|
|
655
|
+
key = (item.row or 0, item.height)
|
|
656
|
+
if key not in row_groups:
|
|
657
|
+
row_groups[key] = []
|
|
658
|
+
row_groups[key].append(item)
|
|
659
|
+
for (_, row_height), row_items in row_groups.items():
|
|
660
|
+
if len(row_items) > 1:
|
|
661
|
+
_align_cols_heights(row_items, row_height, render_ctx)
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def _fix_slot_heights_in_tree(layout: Layout, render_ctx: SizingRenderCtx) -> None:
|
|
665
|
+
"""Re-render Vega charts whose slot height differs from the cached render height.
|
|
666
|
+
|
|
667
|
+
Called after calculate_layout_items has set all item.height values.
|
|
668
|
+
When a layout wrapper declares an explicit height (e.g. height: 600 on a
|
|
669
|
+
rows wrapper), the sizing provider may have rendered at the aspect-ratio
|
|
670
|
+
estimate (because item.height was 0 at provider call time). If the layout
|
|
671
|
+
algorithm then scaled the item.height down to the slot, the cache entry
|
|
672
|
+
(chart_id, width, estimate) won't match the renderer's lookup key
|
|
673
|
+
(chart_id, width, item.height). This pass re-renders at item.height and
|
|
674
|
+
updates the cache so the render pass hits without a fresh vl-convert call.
|
|
675
|
+
|
|
676
|
+
Only fires when item.height differs from the natural height by more than 1px
|
|
677
|
+
(avoids redundant re-renders for sub-pixel rounding differences).
|
|
678
|
+
"""
|
|
679
|
+
from dataface.core.render.chart.render_single import render_chart_to_svg
|
|
680
|
+
|
|
681
|
+
if render_ctx.executor is None:
|
|
682
|
+
return
|
|
683
|
+
|
|
684
|
+
card_pad = float(render_ctx.resolved_style.board.card_padding)
|
|
685
|
+
chart_padding = render_ctx.resolved_style.charts.padding
|
|
686
|
+
|
|
687
|
+
for item in layout.items:
|
|
688
|
+
if item.face is not None:
|
|
689
|
+
nested_ctx = _nested_render_ctx(render_ctx, item.face)
|
|
690
|
+
_fix_slot_heights_in_tree(item.face.layout, nested_ctx)
|
|
691
|
+
continue
|
|
692
|
+
|
|
693
|
+
if item.chart is None or item.chart.type in NON_ASPECT_RATIO_TYPES:
|
|
694
|
+
continue
|
|
695
|
+
if item.height <= 0:
|
|
696
|
+
continue
|
|
697
|
+
|
|
698
|
+
chart_id = item.chart.id
|
|
699
|
+
natural_h = render_ctx.natural_heights.get((chart_id, item.width))
|
|
700
|
+
if natural_h is None or abs(natural_h - item.height) < 1.0:
|
|
701
|
+
continue
|
|
702
|
+
|
|
703
|
+
# Slot height differs from the rendered height — re-render at item.height.
|
|
704
|
+
render_width = render_ctx.data_table_corrected_widths.get(chart_id, item.width)
|
|
705
|
+
try:
|
|
706
|
+
chart_svg, _, actual_height = render_chart_to_svg(
|
|
707
|
+
item.chart,
|
|
708
|
+
render_ctx.executor,
|
|
709
|
+
render_ctx.variables or {},
|
|
710
|
+
render_width,
|
|
711
|
+
height=item.height,
|
|
712
|
+
vega_config=render_ctx.vega_config,
|
|
713
|
+
resolved_style=render_ctx.resolved_style,
|
|
714
|
+
padding=additive_padding(card_pad, chart_padding),
|
|
715
|
+
face_level=render_ctx.face_level,
|
|
716
|
+
)
|
|
717
|
+
except (ExecutionError, ChartDataError) as exc:
|
|
718
|
+
_log.warning(
|
|
719
|
+
"Slot-height re-render failed for chart %r "
|
|
720
|
+
"(keeping aspect-ratio render): %s",
|
|
721
|
+
chart_id,
|
|
722
|
+
exc,
|
|
723
|
+
)
|
|
724
|
+
continue
|
|
725
|
+
|
|
726
|
+
render_ctx.render_cache[(chart_id, item.width, item.height)] = (
|
|
727
|
+
chart_svg,
|
|
728
|
+
actual_height,
|
|
729
|
+
)
|
|
730
|
+
render_ctx.natural_heights[(chart_id, item.width)] = actual_height
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
def _align_all_cols_in_tree(layout: Layout, render_ctx: SizingRenderCtx) -> None:
|
|
734
|
+
"""Walk the layout tree depth-first and align cols/grid heights where needed."""
|
|
735
|
+
if render_ctx.render_cache and layout.items:
|
|
736
|
+
if layout.type == "cols":
|
|
737
|
+
chart_target = _col_chart_body_target(layout.items, render_ctx)
|
|
738
|
+
if chart_target > 0:
|
|
739
|
+
for item in layout.items:
|
|
740
|
+
if (
|
|
741
|
+
item.chart is not None
|
|
742
|
+
and item.chart.type not in NON_ASPECT_RATIO_TYPES
|
|
743
|
+
):
|
|
744
|
+
item.height = _effective_alignment_target_for_chart(
|
|
745
|
+
item.chart,
|
|
746
|
+
item.width,
|
|
747
|
+
chart_target,
|
|
748
|
+
render_ctx,
|
|
749
|
+
)
|
|
750
|
+
_align_cols_heights(layout.items, chart_target, render_ctx)
|
|
751
|
+
elif layout.type == "grid":
|
|
752
|
+
_align_grid_rows(layout.items, render_ctx)
|
|
753
|
+
|
|
754
|
+
# Nested layouts are always modeled as nested faces in the Face tree;
|
|
755
|
+
# face-only recursion is correct and complete.
|
|
756
|
+
for item in layout.items:
|
|
757
|
+
if item.face is not None and item.face.layout.items:
|
|
758
|
+
nested_ctx = _nested_render_ctx(render_ctx, item.face)
|
|
759
|
+
_align_all_cols_in_tree(item.face.layout, nested_ctx)
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def calculate_data_aware_layout(
|
|
763
|
+
face: Face,
|
|
764
|
+
executor: Executor,
|
|
765
|
+
variables: dict[str, Any] | None = None,
|
|
766
|
+
render_first: bool = True,
|
|
767
|
+
) -> tuple[Face, RenderCache]:
|
|
768
|
+
"""Calculate layout dimensions using actual query data for table sizing.
|
|
769
|
+
|
|
770
|
+
Like calculate_layout(), but uses the executor to get actual row counts
|
|
771
|
+
for table charts and renders Vega-Lite charts to get true heights.
|
|
772
|
+
|
|
773
|
+
Must be called after execute_face_batch() so query results are cached.
|
|
774
|
+
|
|
775
|
+
Args:
|
|
776
|
+
face: Face with layout structure
|
|
777
|
+
executor: Executor with cached query results
|
|
778
|
+
variables: Variable values for query execution
|
|
779
|
+
render_first: When True, Vega-Lite charts are rendered during sizing to
|
|
780
|
+
get actual heights (render-first sizing). Set False for non-SVG
|
|
781
|
+
formats (yaml/json/text) to avoid unnecessary chart rendering.
|
|
782
|
+
|
|
783
|
+
Returns:
|
|
784
|
+
(Face with calculated dimensions, render_cache mapping chart_id
|
|
785
|
+
to (svg_string, actual_height) for Vega charts rendered during sizing)
|
|
786
|
+
"""
|
|
787
|
+
board = face.resolved_style.board
|
|
788
|
+
|
|
789
|
+
container_width = float(board.width)
|
|
790
|
+
content_width = max(container_width - 2 * float(board.margin), 0.0)
|
|
791
|
+
min_height = float(board.min_height)
|
|
792
|
+
card_padding = float(board.card_padding)
|
|
793
|
+
|
|
794
|
+
card_gap = float(board.card_gap) if face.card_gap else 0.0
|
|
795
|
+
gap = get_face_gap(face)
|
|
796
|
+
|
|
797
|
+
variable_defaults = face.variable_defaults
|
|
798
|
+
|
|
799
|
+
# Create render context; executor=None disables render-first Vega sizing.
|
|
800
|
+
from dataface.core.compile.vega_config import compile_effective_vega_config
|
|
801
|
+
|
|
802
|
+
render_ctx = SizingRenderCtx(
|
|
803
|
+
resolved_style=face.resolved_style,
|
|
804
|
+
vega_config=compile_effective_vega_config(face.theme),
|
|
805
|
+
executor=executor if render_first else None,
|
|
806
|
+
variables=variables,
|
|
807
|
+
face_level=face.level,
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
# Build a data-aware height provider
|
|
811
|
+
height_provider = _make_data_aware_height_provider(
|
|
812
|
+
render_ctx,
|
|
813
|
+
executor,
|
|
814
|
+
variables,
|
|
815
|
+
card_padding=card_padding,
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
# Calculate layout height using compile sizing algorithms + data-aware provider
|
|
819
|
+
container_height = calculate_layout_height(
|
|
820
|
+
face.layout,
|
|
821
|
+
card_gap,
|
|
822
|
+
gap,
|
|
823
|
+
min_height,
|
|
824
|
+
available_width=content_width,
|
|
825
|
+
variable_defaults=variable_defaults,
|
|
826
|
+
height_provider=height_provider,
|
|
827
|
+
resolved_style=face.resolved_style,
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
if face.title:
|
|
831
|
+
title_measure_width = max(content_width - 2 * card_padding, 0.0)
|
|
832
|
+
title_height = get_title_height(
|
|
833
|
+
face.title, title_measure_width, variable_defaults
|
|
834
|
+
)
|
|
835
|
+
container_height += title_height + gap + card_gap
|
|
836
|
+
|
|
837
|
+
face.layout.width = container_width
|
|
838
|
+
face.layout.height = container_height
|
|
839
|
+
face.layout.content_width = content_width
|
|
840
|
+
face.layout.content_height = container_height
|
|
841
|
+
|
|
842
|
+
# Assign dimensions using compile sizing + data-aware provider
|
|
843
|
+
calculate_layout_items(
|
|
844
|
+
face.layout,
|
|
845
|
+
content_width,
|
|
846
|
+
container_height,
|
|
847
|
+
card_gap,
|
|
848
|
+
gap,
|
|
849
|
+
variable_defaults,
|
|
850
|
+
height_provider,
|
|
851
|
+
resolved_style=face.resolved_style,
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
# Slot-height fix: re-render charts whose cached SVG was sized by the
|
|
855
|
+
# aspect-ratio estimate but whose assigned slot height differs. This handles
|
|
856
|
+
# layout wrappers with an explicit height (rows: [- height: 600, rows: [chart]])
|
|
857
|
+
# where the sizing provider ran before item.height was set.
|
|
858
|
+
if render_ctx.render_cache:
|
|
859
|
+
_fix_slot_heights_in_tree(face.layout, render_ctx)
|
|
860
|
+
|
|
861
|
+
# Cols alignment: walk tree, re-render shorter Vega items at aligned height
|
|
862
|
+
if render_ctx.render_cache:
|
|
863
|
+
_align_all_cols_in_tree(face.layout, render_ctx)
|
|
864
|
+
|
|
865
|
+
return face, render_ctx.render_cache
|