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,347 @@
|
|
|
1
|
+
"""Single-chart render utility shared by sizing pass and render pass."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from dataface.core.compile.models.chart.compiled import (
|
|
8
|
+
Chart,
|
|
9
|
+
)
|
|
10
|
+
from dataface.core.compile.models.face.compiled import VariableValues
|
|
11
|
+
from dataface.core.render.svg_utils import extract_svg_dimensions
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from dataface.core.compile.chart_resolved import ResolvedChart
|
|
15
|
+
from dataface.core.compile.models.style.merged import MergedStyle
|
|
16
|
+
from dataface.core.execute.executor import Executor
|
|
17
|
+
|
|
18
|
+
# Type alias for the SVG render cache used across sizing and render passes.
|
|
19
|
+
# Maps (chart_id, slot_width, slot_height) → (svg_string, actual_height_px).
|
|
20
|
+
RenderCache = dict[tuple[str, float, float], tuple[str, float]]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def render_chart_to_svg(
|
|
24
|
+
chart: Chart,
|
|
25
|
+
executor: Executor,
|
|
26
|
+
variables: VariableValues,
|
|
27
|
+
width: float,
|
|
28
|
+
height: float | None = None,
|
|
29
|
+
resolved_style: MergedStyle | None = None,
|
|
30
|
+
padding: dict[str, Any] | None = None,
|
|
31
|
+
face_level: int = 1,
|
|
32
|
+
vega_config: dict[str, Any] | None = None,
|
|
33
|
+
) -> tuple[str, float, float]:
|
|
34
|
+
"""Render a Vega-Lite chart and return (svg_string, actual_width, actual_height).
|
|
35
|
+
|
|
36
|
+
Executes the query, resolves the chart, and calls vl-convert.
|
|
37
|
+
When height=None, Vega auto-sizes vertically (used for initial sizing pass).
|
|
38
|
+
When height is given, the chart is constrained to that height (used for
|
|
39
|
+
cols alignment re-renders).
|
|
40
|
+
|
|
41
|
+
Shared by both Vega-family and SVG-family chart render paths.
|
|
42
|
+
Does NOT wrap the SVG in data-attribute groups — that stays in rendering.py.
|
|
43
|
+
|
|
44
|
+
``face_level`` is the heading level of the parent face (root=1, nested=2, …).
|
|
45
|
+
Chart title uses face_level + 1.
|
|
46
|
+
"""
|
|
47
|
+
from dataface.core.compile.jinja import resolve_jinja_template
|
|
48
|
+
from dataface.core.render.chart.pipeline import resolve_chart
|
|
49
|
+
from dataface.core.render.chart.vega_lite import render_chart as vega_render
|
|
50
|
+
from dataface.core.render.placeholder import is_placeholder_needed
|
|
51
|
+
from dataface.core.render.utils import resolve_field_names
|
|
52
|
+
|
|
53
|
+
data = [] if chart.type == "callout" else executor.execute_chart(chart, variables)
|
|
54
|
+
|
|
55
|
+
# Per-layer datasets for layered charts
|
|
56
|
+
datasets: dict[str, list[dict[str, Any]]] | None = None
|
|
57
|
+
if chart.type == "layered" and chart.layers:
|
|
58
|
+
layer_query_names: set[str] = set()
|
|
59
|
+
for layer in chart.layers:
|
|
60
|
+
q = layer.query
|
|
61
|
+
if isinstance(q, str):
|
|
62
|
+
layer_query_names.add(q)
|
|
63
|
+
if layer_query_names:
|
|
64
|
+
datasets = {}
|
|
65
|
+
if chart.query_name:
|
|
66
|
+
datasets[chart.query_name] = data
|
|
67
|
+
for qn in layer_query_names:
|
|
68
|
+
if qn not in datasets:
|
|
69
|
+
datasets[qn] = executor.execute_query(qn, variables)
|
|
70
|
+
|
|
71
|
+
use_placeholder = is_placeholder_needed(chart, data)
|
|
72
|
+
|
|
73
|
+
updates: dict[str, str] = {}
|
|
74
|
+
if chart.title:
|
|
75
|
+
updates["title"] = resolve_jinja_template(chart.title, variables, strict=False)
|
|
76
|
+
if chart.label:
|
|
77
|
+
updates["label"] = resolve_jinja_template(chart.label, variables, strict=False)
|
|
78
|
+
if chart.subtitle:
|
|
79
|
+
updates["subtitle"] = resolve_jinja_template(
|
|
80
|
+
chart.subtitle, variables, strict=False
|
|
81
|
+
)
|
|
82
|
+
if chart.message:
|
|
83
|
+
updates["message"] = resolve_jinja_template(
|
|
84
|
+
chart.message, variables, strict=False
|
|
85
|
+
)
|
|
86
|
+
# KPI link is a static URL template resolved against variables (e.g.
|
|
87
|
+
# "/region/{{ region }}"). Vega-family charts resolve link per datum at
|
|
88
|
+
# render time via _build_href_calc_expr — do not pre-resolve those.
|
|
89
|
+
if chart.type == "kpi" and chart.link:
|
|
90
|
+
updates["link"] = resolve_jinja_template(chart.link, variables, strict=False)
|
|
91
|
+
resolved_chart = chart.model_copy(update=updates) if updates else chart
|
|
92
|
+
resolved_chart = resolve_field_names(resolved_chart, data)
|
|
93
|
+
|
|
94
|
+
detect_data = data
|
|
95
|
+
if use_placeholder and not data:
|
|
96
|
+
from dataface.core.render.placeholder import generate_placeholder_data
|
|
97
|
+
|
|
98
|
+
detect_data = generate_placeholder_data("bar", resolved_chart)
|
|
99
|
+
|
|
100
|
+
col_descs = (
|
|
101
|
+
executor.get_column_descriptions(chart.query_name) if chart.query_name else None
|
|
102
|
+
)
|
|
103
|
+
presentation_chart = resolve_chart(
|
|
104
|
+
resolved_chart, detect_data, col_descs, board_style=resolved_style
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Attached-table dispatch — arc charts (only "pie" survives the
|
|
108
|
+
# normalizer; "donut" → "pie + style.inner_radius") at a tier ×
|
|
109
|
+
# cardinality where direct labels can't fit cleanly render as donut
|
|
110
|
+
# SVG + swatch-keyed table SVG composed into one outer <svg>. See
|
|
111
|
+
# arc_attached_table.py for the trigger rule.
|
|
112
|
+
if resolved_style is not None and chart.type == "pie" and data:
|
|
113
|
+
from dataface.core.render.chart.arc_attached_table import (
|
|
114
|
+
compute_shares,
|
|
115
|
+
should_attach_table,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
shares = compute_shares(chart.theta or "", data)
|
|
119
|
+
if should_attach_table(width, shares):
|
|
120
|
+
return _render_arc_with_attached_table(
|
|
121
|
+
presentation_chart=presentation_chart,
|
|
122
|
+
chart=chart,
|
|
123
|
+
data=data,
|
|
124
|
+
shares=shares,
|
|
125
|
+
width=width,
|
|
126
|
+
height=height,
|
|
127
|
+
is_placeholder=use_placeholder,
|
|
128
|
+
datasets=datasets,
|
|
129
|
+
variables=variables,
|
|
130
|
+
padding=padding,
|
|
131
|
+
resolved_style=resolved_style,
|
|
132
|
+
face_level=face_level,
|
|
133
|
+
vega_config=vega_config,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# height=None → Vega auto-sizes vertically; actual height read back from SVG.
|
|
137
|
+
# height=<value> → fixed height constraint (used for cols alignment re-renders).
|
|
138
|
+
chart_svg = vega_render(
|
|
139
|
+
presentation_chart,
|
|
140
|
+
data,
|
|
141
|
+
format="svg",
|
|
142
|
+
width=width,
|
|
143
|
+
height=height,
|
|
144
|
+
is_placeholder=use_placeholder,
|
|
145
|
+
datasets=datasets,
|
|
146
|
+
variables=variables,
|
|
147
|
+
padding=padding,
|
|
148
|
+
resolved_style=resolved_style,
|
|
149
|
+
face_level=face_level,
|
|
150
|
+
effective_vega_config=vega_config,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
dims = extract_svg_dimensions(chart_svg)
|
|
154
|
+
return chart_svg, dims.width, dims.height
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _render_arc_with_attached_table(
|
|
158
|
+
presentation_chart: ResolvedChart,
|
|
159
|
+
chart: Chart,
|
|
160
|
+
data: list[dict[str, Any]],
|
|
161
|
+
shares: list[float],
|
|
162
|
+
width: float,
|
|
163
|
+
height: float | None,
|
|
164
|
+
is_placeholder: bool,
|
|
165
|
+
datasets: dict[str, list[dict[str, Any]]] | None,
|
|
166
|
+
variables: VariableValues,
|
|
167
|
+
padding: dict[str, Any] | None,
|
|
168
|
+
resolved_style: MergedStyle,
|
|
169
|
+
face_level: int,
|
|
170
|
+
vega_config: dict[str, Any] | None,
|
|
171
|
+
) -> tuple[str, float, float]:
|
|
172
|
+
"""Render donut + attached-table composition.
|
|
173
|
+
|
|
174
|
+
Donut renders without direct labels (table carries them). Table uses
|
|
175
|
+
the swatch column + header-hidden config from the β-1 foundation.
|
|
176
|
+
Palette is read from ``presentation_chart.resolved_style.palette``
|
|
177
|
+
(chart-local merged style) so any per-chart palette override survives
|
|
178
|
+
into the swatches — same source the arc layer paints from.
|
|
179
|
+
|
|
180
|
+
The ``height`` parameter is intentionally ignored — the composed
|
|
181
|
+
SVG's natural height (donut + gap + table) flows back through the
|
|
182
|
+
data-aware sizer so the surrounding layout grows the card to fit.
|
|
183
|
+
A cols-alignment re-render that tried to constrain height here
|
|
184
|
+
would clip the table.
|
|
185
|
+
"""
|
|
186
|
+
import dataclasses
|
|
187
|
+
from types import SimpleNamespace
|
|
188
|
+
|
|
189
|
+
from dataface.core.compile.models.style.authored import (
|
|
190
|
+
ChartStylePatch,
|
|
191
|
+
TableChartStylePatch,
|
|
192
|
+
)
|
|
193
|
+
from dataface.core.compile.style_cascade import build_resolved_style
|
|
194
|
+
from dataface.core.render.chart.arc_attached_table import (
|
|
195
|
+
build_attached_table_columns,
|
|
196
|
+
compose_attached_table_svg,
|
|
197
|
+
)
|
|
198
|
+
from dataface.core.render.chart.table import render_table_svg
|
|
199
|
+
from dataface.core.render.chart.vega_lite import render_chart as vega_render
|
|
200
|
+
|
|
201
|
+
# Donut: replace labels with None so the labels layer doesn't emit.
|
|
202
|
+
# The arc `order` encoding still fires (data_override + order are
|
|
203
|
+
# hoisted out of the labels branch in profile.py) so wedge order
|
|
204
|
+
# stays pinned to data insertion.
|
|
205
|
+
donut_chart = dataclasses.replace(presentation_chart, labels=None)
|
|
206
|
+
donut_svg = vega_render(
|
|
207
|
+
donut_chart,
|
|
208
|
+
data,
|
|
209
|
+
format="svg",
|
|
210
|
+
width=width,
|
|
211
|
+
height=width, # donut renders square at the slot width
|
|
212
|
+
is_placeholder=is_placeholder,
|
|
213
|
+
datasets=datasets,
|
|
214
|
+
variables=variables,
|
|
215
|
+
padding=padding,
|
|
216
|
+
resolved_style=resolved_style,
|
|
217
|
+
face_level=face_level,
|
|
218
|
+
effective_vega_config=vega_config,
|
|
219
|
+
)
|
|
220
|
+
donut_dims = extract_svg_dimensions(donut_svg)
|
|
221
|
+
|
|
222
|
+
# Series rows for the table — palette from chart-local resolved
|
|
223
|
+
# style (same source the arc layer paints from), pre-computed shares
|
|
224
|
+
# from the trigger gate (no duplicate arithmetic).
|
|
225
|
+
series_rows = _extract_arc_rows_for_table(presentation_chart, data, shares)
|
|
226
|
+
# Pre-format share as "42%" string — sidesteps the three-lane
|
|
227
|
+
# numeric layout's %-suffix-drop bug (filed as follow-up).
|
|
228
|
+
table_data = [
|
|
229
|
+
{
|
|
230
|
+
"swatch": r["swatch"],
|
|
231
|
+
"share": f"{round(r['share'] * 100)}%",
|
|
232
|
+
"name": r["name"],
|
|
233
|
+
"value": r["value"],
|
|
234
|
+
}
|
|
235
|
+
for r in series_rows
|
|
236
|
+
]
|
|
237
|
+
# Value format — read directly from the chart's format hint
|
|
238
|
+
# (``str | FormatConfig | None``). ``TableColumnConfig.format``
|
|
239
|
+
# accepts all three shapes; passing the raw value through preserves
|
|
240
|
+
# any authored prefix/suffix on a FormatConfig.
|
|
241
|
+
value_format = presentation_chart.format
|
|
242
|
+
|
|
243
|
+
# Width-sizing uses the same font the table will render with —
|
|
244
|
+
# threaded from the resolved table style (theme-cascaded) so a
|
|
245
|
+
# theme that retunes table type doesn't desync measurement.
|
|
246
|
+
table_style = resolved_style.charts.table
|
|
247
|
+
table_font_size = (
|
|
248
|
+
float(table_style.font.size) if table_style.font.size is not None else 11.0
|
|
249
|
+
)
|
|
250
|
+
table_font_family = table_style.font.family or "Inter Variable"
|
|
251
|
+
columns, table_target_width = build_attached_table_columns(
|
|
252
|
+
table_data,
|
|
253
|
+
value_format=value_format,
|
|
254
|
+
font_size=table_font_size,
|
|
255
|
+
font_family=table_font_family,
|
|
256
|
+
)
|
|
257
|
+
table_patch = ChartStylePatch(
|
|
258
|
+
table=TableChartStylePatch.model_validate(
|
|
259
|
+
{
|
|
260
|
+
"header": {"visible": False},
|
|
261
|
+
"pagination": {"enabled": False},
|
|
262
|
+
"columns": columns,
|
|
263
|
+
}
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
# SimpleNamespace stand-in — render_table_svg reads chart attributes
|
|
267
|
+
# by attribute access. Stdlib ad-hoc carrier; no mock framework in
|
|
268
|
+
# production code. ``chart.id`` is guaranteed on a normalized Chart
|
|
269
|
+
# so we read it directly.
|
|
270
|
+
table_chart = SimpleNamespace(
|
|
271
|
+
id=f"{chart.id}__attached_table",
|
|
272
|
+
title=None,
|
|
273
|
+
subtitle=None,
|
|
274
|
+
style=table_patch,
|
|
275
|
+
columns=None,
|
|
276
|
+
column_defaults=None,
|
|
277
|
+
link=None,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
table_resolved = build_resolved_style(resolved_style, table_patch)
|
|
281
|
+
table_render_width = min(table_target_width, float(width))
|
|
282
|
+
table_svg = render_table_svg(
|
|
283
|
+
table_chart,
|
|
284
|
+
table_data,
|
|
285
|
+
width=table_render_width,
|
|
286
|
+
resolved_style=table_resolved,
|
|
287
|
+
board_style=resolved_style,
|
|
288
|
+
)
|
|
289
|
+
table_dims = extract_svg_dimensions(table_svg)
|
|
290
|
+
|
|
291
|
+
composed, w, h = compose_attached_table_svg(
|
|
292
|
+
donut_svg=donut_svg,
|
|
293
|
+
table_svg=table_svg,
|
|
294
|
+
donut_width=donut_dims.width,
|
|
295
|
+
donut_height=donut_dims.height,
|
|
296
|
+
table_width=table_dims.width,
|
|
297
|
+
table_height=table_dims.height,
|
|
298
|
+
)
|
|
299
|
+
return composed, w, h
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _extract_arc_rows_for_table(
|
|
303
|
+
presentation_chart: ResolvedChart,
|
|
304
|
+
data: list[dict[str, Any]],
|
|
305
|
+
shares: list[float],
|
|
306
|
+
) -> list[dict[str, Any]]:
|
|
307
|
+
"""Build per-wedge rows for the attached table.
|
|
308
|
+
|
|
309
|
+
Palette source is the **chart-local** merged style
|
|
310
|
+
(``presentation_chart.resolved_style.palette``) — same source the
|
|
311
|
+
arc layer paints wedges from. Reading the board-level palette
|
|
312
|
+
instead would silently desync swatches from wedges whenever a chart
|
|
313
|
+
overrides ``style.palette`` or ``style.range.category``.
|
|
314
|
+
|
|
315
|
+
Shares are passed in (already computed at the trigger gate) rather
|
|
316
|
+
than recomputed here. ``data`` and ``shares`` are expected to have
|
|
317
|
+
the same length (``compute_shares`` returns one share per data row);
|
|
318
|
+
``zip(..., strict=True)`` enforces that invariant.
|
|
319
|
+
"""
|
|
320
|
+
from dataface.core.render.errors import ChartDataError
|
|
321
|
+
|
|
322
|
+
theta_field = presentation_chart.theta or ""
|
|
323
|
+
color_ch = presentation_chart.resolved_channels.get("color")
|
|
324
|
+
color_field = (
|
|
325
|
+
color_ch.data_field
|
|
326
|
+
if color_ch is not None and color_ch.data_field is not None
|
|
327
|
+
else None
|
|
328
|
+
)
|
|
329
|
+
palette = list(presentation_chart.resolved_style.palette)
|
|
330
|
+
if not palette:
|
|
331
|
+
raise ChartDataError(
|
|
332
|
+
f"Arc chart {presentation_chart.id!r} attached-table render "
|
|
333
|
+
"needs a resolved palette, but the chart-local style produced "
|
|
334
|
+
"an empty list.",
|
|
335
|
+
chart_id=presentation_chart.id,
|
|
336
|
+
)
|
|
337
|
+
rows: list[dict[str, Any]] = []
|
|
338
|
+
for idx, (row, share) in enumerate(zip(data, shares, strict=True)):
|
|
339
|
+
rows.append(
|
|
340
|
+
{
|
|
341
|
+
"swatch": palette[idx % len(palette)],
|
|
342
|
+
"share": share,
|
|
343
|
+
"name": row.get(color_field) if color_field else None,
|
|
344
|
+
"value": row.get(theta_field, 0) or 0,
|
|
345
|
+
}
|
|
346
|
+
)
|
|
347
|
+
return rows
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Renderer registry for resolved charts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Protocol
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from dataface.core.compile.models.style.merged import MergedStyle
|
|
11
|
+
|
|
12
|
+
from dataface.core.compile.chart_resolved import ResolvedChart
|
|
13
|
+
from dataface.core.render.chart.artifacts import RenderArtifact
|
|
14
|
+
from dataface.core.render.chart.callout import render_callout_chart_svg
|
|
15
|
+
from dataface.core.render.chart.kpi import render_kpi_svg
|
|
16
|
+
from dataface.core.render.chart.serialization import build_dataface_json
|
|
17
|
+
from dataface.core.render.chart.spark_bar import render_spark_bar_svg
|
|
18
|
+
from dataface.core.render.chart.standard_renderer import render_standard_vega_spec
|
|
19
|
+
from dataface.core.render.chart.table import render_table_svg
|
|
20
|
+
|
|
21
|
+
SVG_RENDERERS: dict[str, Callable[..., str]] = {
|
|
22
|
+
"table": render_table_svg,
|
|
23
|
+
"spark_bar": render_spark_bar_svg,
|
|
24
|
+
"kpi": render_kpi_svg,
|
|
25
|
+
"callout": render_callout_chart_svg,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# SVG chart types that consume `variables` for pagination state.
|
|
29
|
+
_VARIABLE_AWARE_SVG_TYPES = {"table"}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ChartRenderer(Protocol):
|
|
33
|
+
def render(
|
|
34
|
+
self,
|
|
35
|
+
chart: ResolvedChart,
|
|
36
|
+
data: list[dict[str, Any]],
|
|
37
|
+
width: float | None = None,
|
|
38
|
+
height: float | None = None,
|
|
39
|
+
is_placeholder: bool = False,
|
|
40
|
+
datasets: dict[str, list[dict[str, Any]]] | None = None,
|
|
41
|
+
variables: dict[str, Any] | None = None,
|
|
42
|
+
padding: dict[str, Any] | None = None,
|
|
43
|
+
face_level: int = 1,
|
|
44
|
+
) -> RenderArtifact: ...
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class StandardVegaLiteRenderer:
|
|
49
|
+
def render(
|
|
50
|
+
self,
|
|
51
|
+
chart: ResolvedChart,
|
|
52
|
+
data: list[dict[str, Any]],
|
|
53
|
+
width: float | None = None,
|
|
54
|
+
height: float | None = None,
|
|
55
|
+
is_placeholder: bool = False,
|
|
56
|
+
datasets: dict[str, list[dict[str, Any]]] | None = None,
|
|
57
|
+
variables: dict[str, Any] | None = None,
|
|
58
|
+
padding: dict[str, Any] | None = None,
|
|
59
|
+
face_level: int = 1,
|
|
60
|
+
resolved_style: MergedStyle | None = None,
|
|
61
|
+
effective_vega_config: dict[str, Any] | None = None,
|
|
62
|
+
) -> RenderArtifact:
|
|
63
|
+
return RenderArtifact(
|
|
64
|
+
kind="vega_spec",
|
|
65
|
+
payload=render_standard_vega_spec(
|
|
66
|
+
chart,
|
|
67
|
+
data,
|
|
68
|
+
width,
|
|
69
|
+
height,
|
|
70
|
+
datasets=datasets,
|
|
71
|
+
padding=padding,
|
|
72
|
+
resolved_style=resolved_style,
|
|
73
|
+
face_level=face_level,
|
|
74
|
+
effective_vega_config=effective_vega_config,
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class SvgRenderer:
|
|
81
|
+
def render(
|
|
82
|
+
self,
|
|
83
|
+
chart: ResolvedChart,
|
|
84
|
+
data: list[dict[str, Any]],
|
|
85
|
+
width: float | None = None,
|
|
86
|
+
height: float | None = None,
|
|
87
|
+
is_placeholder: bool = False,
|
|
88
|
+
datasets: dict[str, list[dict[str, Any]]] | None = None,
|
|
89
|
+
variables: dict[str, Any] | None = None,
|
|
90
|
+
padding: (
|
|
91
|
+
dict[str, Any] | None
|
|
92
|
+
) = None, # intentionally unused: SVG-family uses SVG translate
|
|
93
|
+
face_level: int = 1,
|
|
94
|
+
resolved_style: MergedStyle | None = None,
|
|
95
|
+
) -> RenderArtifact:
|
|
96
|
+
# KPI/spark/spark_bar/table take chart-level MergedChartsStyle as
|
|
97
|
+
# ``resolved_style``; callout takes the board-level MergedStyle for
|
|
98
|
+
# callout card styling. The two surfaces don't overlap on a single
|
|
99
|
+
# call, so a per-type swap is enough — no separate "chart_style" kwarg.
|
|
100
|
+
kwargs: dict[str, Any] = {
|
|
101
|
+
"width": width,
|
|
102
|
+
"height": height,
|
|
103
|
+
"is_placeholder": is_placeholder,
|
|
104
|
+
"face_level": face_level,
|
|
105
|
+
}
|
|
106
|
+
if chart.chart_type == "callout":
|
|
107
|
+
assert (
|
|
108
|
+
resolved_style is not None
|
|
109
|
+
), f"{chart.chart_type} renderer requires resolved_style"
|
|
110
|
+
kwargs["resolved_style"] = resolved_style
|
|
111
|
+
else:
|
|
112
|
+
if resolved_style is None:
|
|
113
|
+
raise ValueError(
|
|
114
|
+
f"SvgRenderer.render requires resolved_style (board-level MergedStyle) "
|
|
115
|
+
f"for chart type '{chart.chart_type}'. "
|
|
116
|
+
"Thread the face's MergedStyle from the render pipeline."
|
|
117
|
+
)
|
|
118
|
+
kwargs["resolved_style"] = chart.resolved_style
|
|
119
|
+
kwargs["board_style"] = resolved_style
|
|
120
|
+
if chart.chart_type in _VARIABLE_AWARE_SVG_TYPES:
|
|
121
|
+
kwargs["variables"] = variables
|
|
122
|
+
svg = SVG_RENDERERS[chart.chart_type](chart, data, **kwargs)
|
|
123
|
+
return RenderArtifact(kind="svg", payload=svg)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass
|
|
127
|
+
class RendererRegistry:
|
|
128
|
+
standard_renderer: ChartRenderer = field(default_factory=StandardVegaLiteRenderer)
|
|
129
|
+
geo_renderer: ChartRenderer = field(default_factory=StandardVegaLiteRenderer)
|
|
130
|
+
svg_renderer: ChartRenderer = field(default_factory=SvgRenderer)
|
|
131
|
+
|
|
132
|
+
def select(self, chart: ResolvedChart) -> ChartRenderer:
|
|
133
|
+
if chart.renderer_family == "svg":
|
|
134
|
+
return self.svg_renderer
|
|
135
|
+
if chart.renderer_family == "geo":
|
|
136
|
+
return self.geo_renderer
|
|
137
|
+
return self.standard_renderer
|
|
138
|
+
|
|
139
|
+
def render(
|
|
140
|
+
self,
|
|
141
|
+
chart: ResolvedChart,
|
|
142
|
+
data: list[dict[str, Any]],
|
|
143
|
+
width: float | None = None,
|
|
144
|
+
height: float | None = None,
|
|
145
|
+
is_placeholder: bool = False,
|
|
146
|
+
datasets: dict[str, list[dict[str, Any]]] | None = None,
|
|
147
|
+
variables: dict[str, Any] | None = None,
|
|
148
|
+
padding: dict[str, Any] | None = None,
|
|
149
|
+
resolved_style: MergedStyle | None = None,
|
|
150
|
+
face_level: int = 1,
|
|
151
|
+
effective_vega_config: dict[str, Any] | None = None,
|
|
152
|
+
) -> RenderArtifact:
|
|
153
|
+
renderer = self.select(chart)
|
|
154
|
+
# SVG-family always needs resolved_style; VL/geo families take it
|
|
155
|
+
# too so the chart's halo/knockout/spec background match the face's
|
|
156
|
+
# *actual* resolved background rather than re-deriving from theme name
|
|
157
|
+
# (which can mismatch when the face has chart-local style overrides).
|
|
158
|
+
extra: dict[str, Any] = (
|
|
159
|
+
{"resolved_style": resolved_style}
|
|
160
|
+
if chart.renderer_family in ("svg", "vega", "geo")
|
|
161
|
+
else {}
|
|
162
|
+
)
|
|
163
|
+
if (
|
|
164
|
+
chart.renderer_family in ("vega", "geo")
|
|
165
|
+
and effective_vega_config is not None
|
|
166
|
+
):
|
|
167
|
+
extra["effective_vega_config"] = effective_vega_config
|
|
168
|
+
return renderer.render(
|
|
169
|
+
chart,
|
|
170
|
+
data,
|
|
171
|
+
width,
|
|
172
|
+
height,
|
|
173
|
+
is_placeholder,
|
|
174
|
+
datasets=datasets,
|
|
175
|
+
variables=variables,
|
|
176
|
+
padding=padding,
|
|
177
|
+
face_level=face_level,
|
|
178
|
+
**extra,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def build_chart_json(
|
|
183
|
+
chart: ResolvedChart,
|
|
184
|
+
data: list[dict[str, Any]],
|
|
185
|
+
width: float | None = None,
|
|
186
|
+
height: float | None = None,
|
|
187
|
+
) -> RenderArtifact:
|
|
188
|
+
return RenderArtifact(
|
|
189
|
+
kind="json",
|
|
190
|
+
payload=build_dataface_json(
|
|
191
|
+
chart.source_chart, data, width=width, height=height
|
|
192
|
+
),
|
|
193
|
+
)
|