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,565 @@
|
|
|
1
|
+
"""Chart and layout-item rendering entrypoints.
|
|
2
|
+
|
|
3
|
+
This module owns:
|
|
4
|
+
- executing a chart query
|
|
5
|
+
- resolving the chart into render-ready semantics
|
|
6
|
+
- dispatching to chart vs nested-face rendering
|
|
7
|
+
|
|
8
|
+
UI chrome such as menus is handled outside core rendering.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import html
|
|
14
|
+
from typing import TYPE_CHECKING, Any
|
|
15
|
+
|
|
16
|
+
from dataface.core.compile.config import get_config
|
|
17
|
+
from dataface.core.compile.errors import DatafaceError
|
|
18
|
+
from dataface.core.compile.models.chart.compiled import (
|
|
19
|
+
SVG_LAYOUT_PADDED_TYPES,
|
|
20
|
+
Chart,
|
|
21
|
+
)
|
|
22
|
+
from dataface.core.compile.models.face.compiled import (
|
|
23
|
+
Face,
|
|
24
|
+
LayoutItem,
|
|
25
|
+
MergedFace,
|
|
26
|
+
ResolvedLayoutItem,
|
|
27
|
+
VariableValues,
|
|
28
|
+
)
|
|
29
|
+
from dataface.core.compile.models.style.merged import MergedStyle
|
|
30
|
+
from dataface.core.errors import DF_RENDER_INTERNAL, StructuredError
|
|
31
|
+
from dataface.core.execute.executor import Executor
|
|
32
|
+
from dataface.core.render.chart.spec_builders import additive_padding
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from dataface.core.execute.adapters.base import SchemaStatus
|
|
36
|
+
from dataface.core.render.svg_utils import (
|
|
37
|
+
_px,
|
|
38
|
+
extract_svg_dimensions,
|
|
39
|
+
extract_svg_inner_content,
|
|
40
|
+
)
|
|
41
|
+
from dataface.core.render.variable_controls import evaluate_visible
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"render_layout_item",
|
|
45
|
+
"render_chart_item",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _uses_svg_family_layout_padding(chart_type: str) -> bool:
|
|
50
|
+
"""Return whether the chart should be inset by layout card padding."""
|
|
51
|
+
return chart_type in SVG_LAYOUT_PADDED_TYPES
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _ensure_resolved_face(face: Face | MergedFace) -> MergedFace:
|
|
55
|
+
"""Return the face as a MergedFace, resolving it if it's a Face."""
|
|
56
|
+
if isinstance(face, MergedFace):
|
|
57
|
+
return face
|
|
58
|
+
from dataface.core.resolve_face import resolve_nested_face
|
|
59
|
+
|
|
60
|
+
return resolve_nested_face(face)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _coerce_border_radius(value: object) -> float:
|
|
64
|
+
"""Return a numeric border radius from numeric or CSS-ish values."""
|
|
65
|
+
if value is None:
|
|
66
|
+
return 0.0
|
|
67
|
+
if isinstance(value, (int, float)):
|
|
68
|
+
return float(value)
|
|
69
|
+
|
|
70
|
+
text = str(value).strip()
|
|
71
|
+
if not text:
|
|
72
|
+
return 0.0
|
|
73
|
+
if text.endswith("px"):
|
|
74
|
+
text = text[:-2].strip()
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
return float(text)
|
|
78
|
+
except ValueError as exc:
|
|
79
|
+
raise ValueError(f"Invalid border_radius value: {value!r}") from exc
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def render_layout_item(
|
|
83
|
+
item: LayoutItem | ResolvedLayoutItem,
|
|
84
|
+
executor: Executor,
|
|
85
|
+
variables: VariableValues,
|
|
86
|
+
card_gap: float,
|
|
87
|
+
available_width: float,
|
|
88
|
+
available_height: float = 300.0,
|
|
89
|
+
gap: float = 0.0,
|
|
90
|
+
resolved_style: MergedStyle | None = None,
|
|
91
|
+
interactive: bool = True,
|
|
92
|
+
render_cache: dict[tuple[str, float, float], tuple[str, float]] | None = None,
|
|
93
|
+
error_collector: list[StructuredError] | None = None,
|
|
94
|
+
face_level: int = 1,
|
|
95
|
+
vega_config: dict[str, Any] | None = None,
|
|
96
|
+
) -> tuple[str, float]:
|
|
97
|
+
"""Render a single layout item (chart, nested face, or details section).
|
|
98
|
+
|
|
99
|
+
If the item has details metadata, renders a collapsible section:
|
|
100
|
+
- Summary bar (always rendered, clickable)
|
|
101
|
+
- Content (only rendered when the details variable is true)
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
item: Layout item to render
|
|
105
|
+
executor: Executor for query execution
|
|
106
|
+
variables: Variable values for queries
|
|
107
|
+
available_width: Width in pixels for the item
|
|
108
|
+
available_height: Height in pixels for the item
|
|
109
|
+
resolved_style: Board-scoped MergedStyle for effective chart style.
|
|
110
|
+
interactive: Whether to render interactive variable controls (foreignObject).
|
|
111
|
+
face_level: Heading level of the parent face (root=1, nested=2, …).
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
(svg_string, actual_height) — actual_height is the true rendered height.
|
|
115
|
+
"""
|
|
116
|
+
# Evaluate visible expression before doing any work — hidden items cost nothing.
|
|
117
|
+
if not evaluate_visible(item.visible, variables, executor):
|
|
118
|
+
return "", 0.0
|
|
119
|
+
|
|
120
|
+
# Use calculated dimensions if available, otherwise use passed dimensions
|
|
121
|
+
width = item.width if item.width > 0 else available_width
|
|
122
|
+
height = item.height if item.height > 0 else available_height
|
|
123
|
+
|
|
124
|
+
# Details (collapsible section): render summary bar + conditionally render content
|
|
125
|
+
if item.details_variable and item.details_summary:
|
|
126
|
+
from dataface.core.compile.models.style.merged import resolve_style
|
|
127
|
+
from dataface.core.render.layouts import (
|
|
128
|
+
is_details_expanded,
|
|
129
|
+
render_details_summary,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
resolved_style = resolved_style or resolve_style(get_config().style)
|
|
133
|
+
details_config = resolved_style.layout.details
|
|
134
|
+
summary_height = float(details_config.summary_height)
|
|
135
|
+
|
|
136
|
+
expanded = is_details_expanded(item, variables)
|
|
137
|
+
summary_svg = render_details_summary(
|
|
138
|
+
item,
|
|
139
|
+
variables,
|
|
140
|
+
width,
|
|
141
|
+
expanded=expanded,
|
|
142
|
+
resolved_style=resolved_style, # keyword-only, required
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if expanded and item.face:
|
|
146
|
+
from dataface.core.compile.sizing import details_chrome_height
|
|
147
|
+
from dataface.core.render.faces import render_nested_face
|
|
148
|
+
|
|
149
|
+
content_height = height - details_chrome_height(
|
|
150
|
+
item, gap=gap, card_gap=card_gap
|
|
151
|
+
)
|
|
152
|
+
content_svg, face_actual_height = render_nested_face(
|
|
153
|
+
_ensure_resolved_face(item.face),
|
|
154
|
+
executor,
|
|
155
|
+
variables,
|
|
156
|
+
width,
|
|
157
|
+
content_height,
|
|
158
|
+
card_gap,
|
|
159
|
+
interactive,
|
|
160
|
+
render_cache,
|
|
161
|
+
error_collector=error_collector,
|
|
162
|
+
)
|
|
163
|
+
actual_height = float(
|
|
164
|
+
_px(summary_height + details_config.content_y_offset)
|
|
165
|
+
+ face_actual_height
|
|
166
|
+
)
|
|
167
|
+
section_border = (
|
|
168
|
+
f'<rect x="0" y="0" width="{width}" height="{actual_height}" '
|
|
169
|
+
f'fill="none" stroke="{resolved_style.border.color}" stroke-width="{details_config.border.width}" rx="{details_config.border.radius}"/>'
|
|
170
|
+
)
|
|
171
|
+
rendered = (
|
|
172
|
+
f"{section_border}"
|
|
173
|
+
f"{summary_svg}"
|
|
174
|
+
f'<g transform="translate(0, {_px(summary_height + details_config.content_y_offset)})">'
|
|
175
|
+
f"{content_svg}</g>"
|
|
176
|
+
)
|
|
177
|
+
else:
|
|
178
|
+
rendered = summary_svg
|
|
179
|
+
actual_height = summary_height
|
|
180
|
+
else:
|
|
181
|
+
rendered = ""
|
|
182
|
+
actual_height = height
|
|
183
|
+
if item.type == "chart" and item.chart:
|
|
184
|
+
# Card padding strategy differs by renderer family:
|
|
185
|
+
# - SVG-family (table/kpi/spark_bar): render at reduced size, wrap in
|
|
186
|
+
# SVG translate so content is inset by card_padding on all sides.
|
|
187
|
+
# - Vega-family: render at full item size, forward card_padding as
|
|
188
|
+
# Vega's internal padding dict so Vega handles the inset itself.
|
|
189
|
+
# This aligns Vega content (marks, axis) with face titles which are
|
|
190
|
+
# also positioned at x_offset + card_padding.
|
|
191
|
+
from dataface.core.compile.models.style.merged import resolve_style
|
|
192
|
+
|
|
193
|
+
resolved_style = resolved_style or resolve_style(get_config().style)
|
|
194
|
+
card_padding = float(resolved_style.board.card_padding)
|
|
195
|
+
is_svg_family = _uses_svg_family_layout_padding(item.chart.type)
|
|
196
|
+
|
|
197
|
+
if is_svg_family:
|
|
198
|
+
chart_svg, chart_actual_height = render_chart_item(
|
|
199
|
+
item.chart,
|
|
200
|
+
executor,
|
|
201
|
+
variables,
|
|
202
|
+
width,
|
|
203
|
+
height,
|
|
204
|
+
resolved_style=resolved_style,
|
|
205
|
+
render_cache=render_cache,
|
|
206
|
+
svg_layout_padding=card_padding,
|
|
207
|
+
error_collector=error_collector,
|
|
208
|
+
face_level=face_level,
|
|
209
|
+
vega_config=vega_config,
|
|
210
|
+
)
|
|
211
|
+
if chart_svg:
|
|
212
|
+
rendered = chart_svg
|
|
213
|
+
actual_height = chart_actual_height
|
|
214
|
+
else:
|
|
215
|
+
# Vega-family: full item dimensions, padding forwarded to Vega spec.
|
|
216
|
+
chart_svg, chart_actual_height = render_chart_item(
|
|
217
|
+
item.chart,
|
|
218
|
+
executor,
|
|
219
|
+
variables,
|
|
220
|
+
width,
|
|
221
|
+
height,
|
|
222
|
+
resolved_style=resolved_style,
|
|
223
|
+
render_cache=render_cache,
|
|
224
|
+
padding=additive_padding(
|
|
225
|
+
card_padding, resolved_style.charts.padding
|
|
226
|
+
),
|
|
227
|
+
error_collector=error_collector,
|
|
228
|
+
face_level=face_level,
|
|
229
|
+
vega_config=vega_config,
|
|
230
|
+
)
|
|
231
|
+
if chart_svg:
|
|
232
|
+
rendered = chart_svg
|
|
233
|
+
actual_height = chart_actual_height
|
|
234
|
+
elif item.type == "face" and item.face:
|
|
235
|
+
# Import here to avoid circular imports
|
|
236
|
+
from dataface.core.render.faces import render_nested_face
|
|
237
|
+
|
|
238
|
+
rendered, actual_height = render_nested_face(
|
|
239
|
+
_ensure_resolved_face(item.face),
|
|
240
|
+
executor,
|
|
241
|
+
variables,
|
|
242
|
+
width,
|
|
243
|
+
available_height=height,
|
|
244
|
+
card_gap=card_gap,
|
|
245
|
+
interactive=interactive,
|
|
246
|
+
render_cache=render_cache,
|
|
247
|
+
error_collector=error_collector,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
if rendered and item.description:
|
|
251
|
+
escaped_description = html.escape(item.description)
|
|
252
|
+
return (
|
|
253
|
+
f'<g class="dft-layout-item" data-layout-description="{escaped_description}"'
|
|
254
|
+
f' aria-label="{escaped_description}">'
|
|
255
|
+
f"{rendered}</g>",
|
|
256
|
+
actual_height,
|
|
257
|
+
)
|
|
258
|
+
return rendered, actual_height
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _render_callout_block(
|
|
262
|
+
exc: DatafaceError,
|
|
263
|
+
chart: Chart,
|
|
264
|
+
executor: Executor,
|
|
265
|
+
variables: VariableValues,
|
|
266
|
+
available_width: float,
|
|
267
|
+
available_height: float,
|
|
268
|
+
resolved_style: MergedStyle | None,
|
|
269
|
+
error_collector: list[StructuredError] | None,
|
|
270
|
+
) -> tuple[str, float]:
|
|
271
|
+
"""Build an inline error SVG block and optionally collect the StructuredError.
|
|
272
|
+
|
|
273
|
+
Single call site for chart-error materialisation — the streaming task can
|
|
274
|
+
wrap this as a yield point without hunting across multiple files.
|
|
275
|
+
"""
|
|
276
|
+
from dataface.core.render.chart.callout import render_callout_svg
|
|
277
|
+
|
|
278
|
+
# Stamp chart_id on fields so to_structured() carries it.
|
|
279
|
+
exc.fields["chart_id"] = chart.id
|
|
280
|
+
structured = exc.to_structured()
|
|
281
|
+
|
|
282
|
+
if error_collector is not None:
|
|
283
|
+
error_collector.append(structured)
|
|
284
|
+
|
|
285
|
+
chart_svg = render_callout_svg(
|
|
286
|
+
chart_id=chart.id,
|
|
287
|
+
message=structured.message,
|
|
288
|
+
width=available_width,
|
|
289
|
+
height=available_height,
|
|
290
|
+
title=f"Chart Error: {chart.id}",
|
|
291
|
+
resolved_style=resolved_style,
|
|
292
|
+
code=structured.code,
|
|
293
|
+
hint=structured.hint,
|
|
294
|
+
doc_url=structured.doc_url,
|
|
295
|
+
tone="negative",
|
|
296
|
+
)
|
|
297
|
+
svg, actual_height = _wrap_rendered_chart_svg(
|
|
298
|
+
chart,
|
|
299
|
+
chart_svg,
|
|
300
|
+
available_width=available_width,
|
|
301
|
+
executor=executor,
|
|
302
|
+
resolved_title=chart.id,
|
|
303
|
+
extra_classes=("dft-chart-callout",),
|
|
304
|
+
is_error_fallback=True,
|
|
305
|
+
)
|
|
306
|
+
return svg, actual_height
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def render_chart_item(
|
|
310
|
+
chart: Chart,
|
|
311
|
+
executor: Executor,
|
|
312
|
+
variables: VariableValues,
|
|
313
|
+
available_width: float,
|
|
314
|
+
available_height: float = 300.0,
|
|
315
|
+
*,
|
|
316
|
+
resolved_style: MergedStyle | None = None,
|
|
317
|
+
render_cache: dict[tuple[str, float, float], tuple[str, float]] | None = None,
|
|
318
|
+
padding: dict[str, Any] | None = None,
|
|
319
|
+
svg_layout_padding: float = 0.0,
|
|
320
|
+
error_collector: list[StructuredError] | None = None,
|
|
321
|
+
face_level: int = 1,
|
|
322
|
+
vega_config: dict[str, Any] | None = None,
|
|
323
|
+
) -> tuple[str, float]:
|
|
324
|
+
"""Render a chart item with explicit dimensions.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
chart: Chart to render
|
|
328
|
+
executor: Executor for query execution
|
|
329
|
+
variables: Variable values for queries
|
|
330
|
+
available_width: Width in pixels for the chart
|
|
331
|
+
available_height: Height in pixels for the chart
|
|
332
|
+
resolved_style: Board-scoped MergedStyle for effective chart style.
|
|
333
|
+
render_cache: Optional cache of pre-rendered SVGs from sizing pass.
|
|
334
|
+
padding: Vega-Lite padding dict forwarded from layout (Vega-family only).
|
|
335
|
+
error_collector: When provided, per-chart StructuredErrors are appended here.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
(svg_string, actual_height) — actual_height is the true rendered height.
|
|
339
|
+
"""
|
|
340
|
+
from dataface.core.execute.errors import ExecutionError
|
|
341
|
+
from dataface.core.render.errors import RenderError
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
render_width = available_width
|
|
345
|
+
render_height = available_height
|
|
346
|
+
if _uses_svg_family_layout_padding(chart.type):
|
|
347
|
+
render_width = max(available_width - 2 * svg_layout_padding, 0.0)
|
|
348
|
+
render_height = max(available_height - 2 * svg_layout_padding, 0.0)
|
|
349
|
+
|
|
350
|
+
svg, actual_height = _render_chart_item_inner(
|
|
351
|
+
chart,
|
|
352
|
+
executor,
|
|
353
|
+
variables,
|
|
354
|
+
render_width,
|
|
355
|
+
render_height,
|
|
356
|
+
resolved_style=resolved_style,
|
|
357
|
+
render_cache=render_cache,
|
|
358
|
+
padding=padding,
|
|
359
|
+
face_level=face_level,
|
|
360
|
+
vega_config=vega_config,
|
|
361
|
+
)
|
|
362
|
+
if svg_layout_padding and _uses_svg_family_layout_padding(chart.type):
|
|
363
|
+
padding_px = _px(svg_layout_padding)
|
|
364
|
+
svg = f'<g transform="translate({padding_px}, {padding_px})">' f"{svg}</g>"
|
|
365
|
+
actual_height += 2 * padding_px
|
|
366
|
+
return svg, actual_height
|
|
367
|
+
except (RenderError, ExecutionError, DatafaceError) as e:
|
|
368
|
+
# RenderError subsumes ChartDataError — existing KPI multirow path preserved.
|
|
369
|
+
# DatafaceError subsumes JinjaError and other compile-stage errors that
|
|
370
|
+
# escape into render (e.g. jinja in a chart title evaluated at render time).
|
|
371
|
+
return _render_callout_block(
|
|
372
|
+
e,
|
|
373
|
+
chart,
|
|
374
|
+
executor,
|
|
375
|
+
variables,
|
|
376
|
+
available_width,
|
|
377
|
+
available_height,
|
|
378
|
+
resolved_style,
|
|
379
|
+
error_collector,
|
|
380
|
+
)
|
|
381
|
+
except Exception as e: # noqa: BLE001
|
|
382
|
+
wrapped = RenderError.from_code(DF_RENDER_INTERNAL, inner_message=str(e))
|
|
383
|
+
return _render_callout_block(
|
|
384
|
+
wrapped,
|
|
385
|
+
chart,
|
|
386
|
+
executor,
|
|
387
|
+
variables,
|
|
388
|
+
available_width,
|
|
389
|
+
available_height,
|
|
390
|
+
resolved_style,
|
|
391
|
+
error_collector,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _render_chart_item_inner(
|
|
396
|
+
chart: Chart,
|
|
397
|
+
executor: Executor,
|
|
398
|
+
variables: VariableValues,
|
|
399
|
+
available_width: float,
|
|
400
|
+
available_height: float = 300.0,
|
|
401
|
+
resolved_style: MergedStyle | None = None,
|
|
402
|
+
render_cache: dict[tuple[str, float, float], tuple[str, float]] | None = None,
|
|
403
|
+
padding: dict[str, Any] | None = None,
|
|
404
|
+
face_level: int = 1,
|
|
405
|
+
vega_config: dict[str, Any] | None = None,
|
|
406
|
+
) -> tuple[str, float]:
|
|
407
|
+
"""Internal chart rendering — raises on error.
|
|
408
|
+
|
|
409
|
+
Delegates chart-resolution and vl-convert rendering to render_chart_to_svg()
|
|
410
|
+
so the pipeline (query-execute → resolve → render) lives in one place.
|
|
411
|
+
SVG-family charts (table/kpi/spark_bar) use an explicit height; Vega-family
|
|
412
|
+
charts auto-size vertically (height=None). Cache is only used for Vega-family.
|
|
413
|
+
"""
|
|
414
|
+
from dataface.core.compile.jinja import resolve_jinja_template
|
|
415
|
+
from dataface.core.render.chart.render_single import render_chart_to_svg
|
|
416
|
+
|
|
417
|
+
# SVG-family charts own their height; Vega-family auto-sizes vertically.
|
|
418
|
+
# Cache is only valid for Vega-family (SVG-family always re-renders).
|
|
419
|
+
is_svg_family = chart.type in SVG_LAYOUT_PADDED_TYPES
|
|
420
|
+
height_arg: float | None = available_height if is_svg_family else None
|
|
421
|
+
|
|
422
|
+
cache_key = (chart.id, available_width, available_height)
|
|
423
|
+
if render_cache is not None and not is_svg_family and cache_key in render_cache:
|
|
424
|
+
chart_svg, _ = render_cache[cache_key]
|
|
425
|
+
else:
|
|
426
|
+
# render_chart_to_svg owns the full pipeline: execute → resolve → vl-convert.
|
|
427
|
+
# Note: Renders individually; batch rendering could improve performance.
|
|
428
|
+
# See: plans/archive/VEGA_SVG_BATCH_CONVERSION_ANALYSIS.md
|
|
429
|
+
chart_svg, _, _ = render_chart_to_svg(
|
|
430
|
+
chart,
|
|
431
|
+
executor,
|
|
432
|
+
variables,
|
|
433
|
+
available_width,
|
|
434
|
+
height=height_arg,
|
|
435
|
+
resolved_style=resolved_style,
|
|
436
|
+
padding=padding,
|
|
437
|
+
face_level=face_level,
|
|
438
|
+
vega_config=vega_config,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
# Resolve chart title for data attributes (Jinja only — no full resolve needed).
|
|
442
|
+
# KPI charts carry their authored text in ``label``; every other chart type
|
|
443
|
+
# uses ``title``. The wire-level ``data-chart-title`` attribute stays under
|
|
444
|
+
# one name regardless (it is an accessibility/identification handle, not
|
|
445
|
+
# tied to the YAML field name).
|
|
446
|
+
chart_title_source = chart.label if chart.type == "kpi" else chart.title
|
|
447
|
+
chart_title = (
|
|
448
|
+
resolve_jinja_template(chart_title_source, variables, strict=False)
|
|
449
|
+
if chart_title_source
|
|
450
|
+
else ""
|
|
451
|
+
) or chart.id
|
|
452
|
+
|
|
453
|
+
# Apply chart-local background wrapper. ``style.background`` is a
|
|
454
|
+
# chart-local-only field — read it directly off the authored ``chart.style``.
|
|
455
|
+
# ``style.border`` is now eagerly merged into ``resolved_style.charts.border``
|
|
456
|
+
# by the cascade, so border.radius comes from there.
|
|
457
|
+
chart_style = getattr(chart, "style", None)
|
|
458
|
+
bg_color = getattr(chart_style, "background", None) if chart_style else None
|
|
459
|
+
if bg_color:
|
|
460
|
+
inner_content = extract_svg_inner_content(chart_svg)
|
|
461
|
+
dims = extract_svg_dimensions(chart_svg)
|
|
462
|
+
assert resolved_style is not None
|
|
463
|
+
chart_border_radius = _coerce_border_radius(resolved_style.charts.border.radius)
|
|
464
|
+
bg_rect = f'<rect x="0" y="0" width="{dims.width}" height="{dims.height}" fill="{bg_color}" rx="{chart_border_radius}"/>'
|
|
465
|
+
chart_svg = f'<svg xmlns="http://www.w3.org/2000/svg" width="{dims.width}" height="{dims.height}" viewBox="0 0 {dims.width} {dims.height}">{bg_rect}{inner_content}</svg>'
|
|
466
|
+
|
|
467
|
+
return _wrap_rendered_chart_svg(
|
|
468
|
+
chart,
|
|
469
|
+
chart_svg,
|
|
470
|
+
available_width=available_width,
|
|
471
|
+
executor=executor,
|
|
472
|
+
resolved_title=chart_title,
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def _wrap_rendered_chart_svg(
|
|
477
|
+
chart: Chart,
|
|
478
|
+
chart_svg: str,
|
|
479
|
+
available_width: float,
|
|
480
|
+
executor: Executor,
|
|
481
|
+
resolved_title: str | None = None,
|
|
482
|
+
extra_classes: tuple[str, ...] = (),
|
|
483
|
+
is_error_fallback: bool = False,
|
|
484
|
+
) -> tuple[str, float]:
|
|
485
|
+
dims = extract_svg_dimensions(chart_svg)
|
|
486
|
+
chart_height_actual = dims.height
|
|
487
|
+
# Internal SVG-family renderers embed directly in the chart wrapper <g>;
|
|
488
|
+
# extract their inner content to avoid a redundant nested <svg> viewport.
|
|
489
|
+
# is_error_fallback marks the ChartDataError path, where the payload is a
|
|
490
|
+
# render_callout_svg card on top of any chart.type (including Vega).
|
|
491
|
+
# Vega/Vega-Lite output otherwise remains as a standalone nested <svg>.
|
|
492
|
+
is_internal_svg = (
|
|
493
|
+
chart.type in SVG_LAYOUT_PADDED_TYPES
|
|
494
|
+
or chart.type == "callout"
|
|
495
|
+
or is_error_fallback
|
|
496
|
+
)
|
|
497
|
+
if is_internal_svg:
|
|
498
|
+
chart_content = extract_svg_inner_content(chart_svg)
|
|
499
|
+
else:
|
|
500
|
+
chart_content = chart_svg
|
|
501
|
+
|
|
502
|
+
var_deps = chart.variable_dependencies or set()
|
|
503
|
+
var_attrs = (
|
|
504
|
+
" ".join(f'data-var-{html.escape(v)}="true"' for v in sorted(var_deps))
|
|
505
|
+
if var_deps
|
|
506
|
+
else ""
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
chart_title = (resolved_title or chart.id) or chart.id
|
|
510
|
+
escaped_title = html.escape(chart_title)
|
|
511
|
+
escaped_id = html.escape(chart.id)
|
|
512
|
+
escaped_description = html.escape(chart.description) if chart.description else ""
|
|
513
|
+
accessible_label = chart_title
|
|
514
|
+
if chart.description:
|
|
515
|
+
accessible_label = f"{chart_title} — {chart.description}"
|
|
516
|
+
escaped_accessible_label = html.escape(accessible_label)
|
|
517
|
+
|
|
518
|
+
schema_status = _resolve_schema_status(executor, chart.query_name)
|
|
519
|
+
|
|
520
|
+
classes = ["dft-chart", *extra_classes]
|
|
521
|
+
if chart.type == "callout" and "dft-chart-callout" not in classes:
|
|
522
|
+
classes.append("dft-chart-callout")
|
|
523
|
+
if schema_status:
|
|
524
|
+
classes.append(f"dft-schema-{schema_status}")
|
|
525
|
+
class_attr = " ".join(classes)
|
|
526
|
+
attrs_parts = [
|
|
527
|
+
f'class="{class_attr}"',
|
|
528
|
+
f'id="chart-{escaped_id}"',
|
|
529
|
+
f'data-chart-id="{escaped_id}"',
|
|
530
|
+
f'data-chart-title="{escaped_title}"',
|
|
531
|
+
f'data-chart-width="{available_width}"',
|
|
532
|
+
f'data-chart-height="{chart_height_actual}"',
|
|
533
|
+
f'aria-label="{escaped_accessible_label}"',
|
|
534
|
+
]
|
|
535
|
+
if schema_status:
|
|
536
|
+
attrs_parts.append(f'data-schema-status="{schema_status}"')
|
|
537
|
+
if escaped_description:
|
|
538
|
+
attrs_parts.append(f'data-chart-description="{escaped_description}"')
|
|
539
|
+
if var_attrs:
|
|
540
|
+
attrs_parts.append(var_attrs)
|
|
541
|
+
|
|
542
|
+
attrs_str = " " + " ".join(attrs_parts)
|
|
543
|
+
return f"<g{attrs_str}>{chart_content}</g>", chart_height_actual
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _resolve_schema_status(
|
|
547
|
+
executor: Executor, query_name: str | None
|
|
548
|
+
) -> SchemaStatus | None:
|
|
549
|
+
"""Derive aggregate schema status for a chart from its query's provenance.
|
|
550
|
+
|
|
551
|
+
Returns "changed" if any relation used a dev/branch schema,
|
|
552
|
+
"unchanged" if all relations matched prod, "unknown" if provenance
|
|
553
|
+
exists but classification is uncertain, or None if no provenance.
|
|
554
|
+
"""
|
|
555
|
+
if not query_name:
|
|
556
|
+
return None
|
|
557
|
+
relations = executor.get_query_provenance(query_name)
|
|
558
|
+
if not relations:
|
|
559
|
+
return None
|
|
560
|
+
statuses = {r.status for r in relations}
|
|
561
|
+
if "changed" in statuses:
|
|
562
|
+
return "changed"
|
|
563
|
+
if "unknown" in statuses:
|
|
564
|
+
return "unknown"
|
|
565
|
+
return "unchanged"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Serialization helpers for chart rendering outputs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from dataface.core.compile.models.chart.compiled import (
|
|
8
|
+
Chart,
|
|
9
|
+
)
|
|
10
|
+
from dataface.core.render.utils import normalize_data_types
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build_dataface_json(
|
|
14
|
+
chart: Chart | Any,
|
|
15
|
+
data: list[dict[str, Any]],
|
|
16
|
+
width: float | None = None,
|
|
17
|
+
height: float | None = None,
|
|
18
|
+
) -> dict[str, Any]:
|
|
19
|
+
"""Build the normalized Dataface JSON representation for a chart."""
|
|
20
|
+
normalized_data = normalize_data_types(data)
|
|
21
|
+
result: dict[str, Any] = {"type": chart.type}
|
|
22
|
+
|
|
23
|
+
if chart.title:
|
|
24
|
+
result["title"] = chart.title
|
|
25
|
+
if getattr(chart, "label", None):
|
|
26
|
+
result["label"] = chart.label
|
|
27
|
+
if chart.subtitle:
|
|
28
|
+
result["subtitle"] = chart.subtitle
|
|
29
|
+
if chart.description:
|
|
30
|
+
result["description"] = chart.description
|
|
31
|
+
|
|
32
|
+
for field in ["x", "y", "color", "size", "shape"]:
|
|
33
|
+
value = getattr(chart, field, None)
|
|
34
|
+
if value is not None:
|
|
35
|
+
result[field] = value
|
|
36
|
+
|
|
37
|
+
for field in ["x_label", "y_label"]:
|
|
38
|
+
value = getattr(chart, field, None)
|
|
39
|
+
if value is not None:
|
|
40
|
+
result[field] = value
|
|
41
|
+
|
|
42
|
+
if chart.format is not None:
|
|
43
|
+
result["format"] = (
|
|
44
|
+
chart.format.model_dump(exclude_none=True)
|
|
45
|
+
if hasattr(chart.format, "model_dump")
|
|
46
|
+
else chart.format
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# KPI quantitative-text-object fields. `support` is a Pydantic model so it
|
|
50
|
+
# needs ``model_dump`` to be JSON-serializable.
|
|
51
|
+
support = getattr(chart, "support", None)
|
|
52
|
+
if support is not None:
|
|
53
|
+
result["support"] = support.model_dump(exclude_none=True)
|
|
54
|
+
|
|
55
|
+
for field in [
|
|
56
|
+
"geo",
|
|
57
|
+
"geo_source",
|
|
58
|
+
"lookup",
|
|
59
|
+
"value",
|
|
60
|
+
"projection",
|
|
61
|
+
"latitude",
|
|
62
|
+
"longitude",
|
|
63
|
+
"background",
|
|
64
|
+
]:
|
|
65
|
+
value = getattr(chart, field, None)
|
|
66
|
+
if value is not None:
|
|
67
|
+
result[field] = value
|
|
68
|
+
|
|
69
|
+
if chart.filters is not None:
|
|
70
|
+
result["filters"] = {
|
|
71
|
+
col: fd.to_yaml_form() for col, fd in chart.filters.items()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if chart.style:
|
|
75
|
+
result["style"] = chart.style.model_dump(exclude_none=True)
|
|
76
|
+
|
|
77
|
+
link = getattr(chart, "link", None)
|
|
78
|
+
if link:
|
|
79
|
+
result["link"] = link
|
|
80
|
+
|
|
81
|
+
if chart.type == "table":
|
|
82
|
+
result["columns"] = list(normalized_data[0].keys()) if normalized_data else []
|
|
83
|
+
|
|
84
|
+
result["data"] = normalized_data
|
|
85
|
+
if width is not None:
|
|
86
|
+
result["width"] = width
|
|
87
|
+
if height is not None:
|
|
88
|
+
result["height"] = height
|
|
89
|
+
|
|
90
|
+
return result
|