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,467 @@
|
|
|
1
|
+
"""Dataface renderer module.
|
|
2
|
+
|
|
3
|
+
Stage: RENDER
|
|
4
|
+
Purpose: Render compiled datafaces to various output formats.
|
|
5
|
+
|
|
6
|
+
Entry Points:
|
|
7
|
+
- render(face, executor, format, variables, **options) -> str | bytes
|
|
8
|
+
- render_chart(chart, data, **options) -> str
|
|
9
|
+
|
|
10
|
+
This is the main rendering orchestration module. It:
|
|
11
|
+
1. Takes a Face from compile stage
|
|
12
|
+
2. Delegates SVG rendering to faces.py
|
|
13
|
+
3. Delegates format conversion to converters/
|
|
14
|
+
4. Produces output in requested format
|
|
15
|
+
|
|
16
|
+
Dependencies:
|
|
17
|
+
- dataface.compile (for Face, Chart)
|
|
18
|
+
- dataface.execute (for Executor)
|
|
19
|
+
- .faces (for SVG rendering)
|
|
20
|
+
- .charts (for chart rendering)
|
|
21
|
+
- .converters (for format conversion)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import contextlib
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from dataface.core.compile.config import (
|
|
29
|
+
get_config,
|
|
30
|
+
get_project_warnings_ignore,
|
|
31
|
+
get_rendering_config,
|
|
32
|
+
)
|
|
33
|
+
from dataface.core.compile.errors import DatafaceError
|
|
34
|
+
from dataface.core.compile.models.face.compiled import Face, VariableValues
|
|
35
|
+
from dataface.core.compile.normalizer import sync_face_resolved_style
|
|
36
|
+
from dataface.core.compile.variables import parse_variable_json_strings
|
|
37
|
+
from dataface.core.errors import StructuredError
|
|
38
|
+
from dataface.core.execute.batch import collect_layout_chart_query_names
|
|
39
|
+
from dataface.core.execute.executor import Executor
|
|
40
|
+
from dataface.core.execute.parallel import (
|
|
41
|
+
execute_queries_parallel,
|
|
42
|
+
execute_queries_sequential,
|
|
43
|
+
plan_query_execution_phases,
|
|
44
|
+
)
|
|
45
|
+
from dataface.core.render.chart.renderers import SVG_RENDERERS
|
|
46
|
+
from dataface.core.render.converters import to_html, to_pdf, to_png
|
|
47
|
+
from dataface.core.render.errors import (
|
|
48
|
+
FormatError,
|
|
49
|
+
MissingRequiredVariablesError,
|
|
50
|
+
MissingVariable,
|
|
51
|
+
RenderError,
|
|
52
|
+
)
|
|
53
|
+
from dataface.core.render.faces import render_face_svg
|
|
54
|
+
from dataface.core.render.layout_sizing import calculate_data_aware_layout
|
|
55
|
+
from dataface.core.render.render_result import RenderResult
|
|
56
|
+
from dataface.core.render.warnings import (
|
|
57
|
+
RenderWarning,
|
|
58
|
+
WarningContext,
|
|
59
|
+
registry as _warnings_registry,
|
|
60
|
+
run_all,
|
|
61
|
+
)
|
|
62
|
+
from dataface.core.render.warnings.suppression import partition as _partition_warnings
|
|
63
|
+
from dataface.core.resolve_face import resolve_face
|
|
64
|
+
|
|
65
|
+
# Formats that produce SVG output (require vl-convert chart rendering).
|
|
66
|
+
_SVG_FORMATS = frozenset({"svg", "html", "png", "pdf"})
|
|
67
|
+
|
|
68
|
+
# Chart types that do NOT produce a Vega-Lite spec (own SVG renderer).
|
|
69
|
+
# vega_specs in WarningContext is sparse: these chart ids are omitted.
|
|
70
|
+
_NON_VEGA_CHART_TYPES = frozenset(SVG_RENDERERS.keys())
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _collect_render_warnings(
|
|
74
|
+
face: Face,
|
|
75
|
+
executor: Executor,
|
|
76
|
+
variables: VariableValues,
|
|
77
|
+
) -> list[RenderWarning]:
|
|
78
|
+
"""Build WarningContext from cached query results and run all detectors.
|
|
79
|
+
|
|
80
|
+
Called after queries have already executed (results are in the executor
|
|
81
|
+
cache). Only charts reachable from the layout tree are included — orphan
|
|
82
|
+
charts (present in face.charts but absent from the layout) were never
|
|
83
|
+
pre-executed and are intentionally excluded so this function never
|
|
84
|
+
triggers a fresh adapter call.
|
|
85
|
+
"""
|
|
86
|
+
# Fast path: skip all spec/query work when no detectors are registered.
|
|
87
|
+
# Avoids double-building vega specs on every SVG render for zero benefit.
|
|
88
|
+
if not _warnings_registry.DETECTORS:
|
|
89
|
+
return []
|
|
90
|
+
|
|
91
|
+
# Restrict to charts the layout actually renders — same set that
|
|
92
|
+
# execute_queries_parallel / execute_queries_sequential pre-executed.
|
|
93
|
+
# Orphan charts (in face.charts but absent from the layout) were never
|
|
94
|
+
# pre-cached; including them would trigger a fresh adapter call.
|
|
95
|
+
pre_executed_query_names = collect_layout_chart_query_names(face)
|
|
96
|
+
|
|
97
|
+
chart_results: dict[str, list[dict[str, Any]]] = {}
|
|
98
|
+
for chart_id, chart in face.charts.items():
|
|
99
|
+
if chart.query_name not in pre_executed_query_names:
|
|
100
|
+
continue
|
|
101
|
+
# Omit failed charts so detectors can distinguish failure from genuine
|
|
102
|
+
# zero rows: absent from chart_results = failed execute.
|
|
103
|
+
with contextlib.suppress(
|
|
104
|
+
Exception
|
|
105
|
+
): # noqa: BLE001 — query failure already recorded elsewhere
|
|
106
|
+
chart_results[chart_id] = executor.execute_chart(chart, variables)
|
|
107
|
+
|
|
108
|
+
from dataface.core.render.chart import generate_vega_lite_spec
|
|
109
|
+
|
|
110
|
+
vega_specs: dict[str, dict[str, Any]] = {}
|
|
111
|
+
for chart_id, chart in face.charts.items():
|
|
112
|
+
if chart.query_name not in pre_executed_query_names:
|
|
113
|
+
continue
|
|
114
|
+
if chart.type in _NON_VEGA_CHART_TYPES:
|
|
115
|
+
continue
|
|
116
|
+
data = chart_results.get(chart_id, [])
|
|
117
|
+
# Spec generation failure must not block the render — omit from vega_specs.
|
|
118
|
+
with contextlib.suppress(
|
|
119
|
+
Exception
|
|
120
|
+
): # noqa: BLE001 — spec failure must not block render
|
|
121
|
+
vega_specs[chart_id] = generate_vega_lite_spec(chart, data)
|
|
122
|
+
|
|
123
|
+
ctx = WarningContext(
|
|
124
|
+
face_spec=face, chart_results=chart_results, vega_specs=vega_specs
|
|
125
|
+
)
|
|
126
|
+
return run_all(ctx)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def render(
|
|
130
|
+
face: Face,
|
|
131
|
+
executor: Executor,
|
|
132
|
+
format: str = "svg",
|
|
133
|
+
variables: VariableValues | None = None,
|
|
134
|
+
ignore_codes: set[str] | None = None,
|
|
135
|
+
project_dir: Path | None = None,
|
|
136
|
+
**options: Any,
|
|
137
|
+
) -> RenderResult:
|
|
138
|
+
"""Render a compiled dataface.
|
|
139
|
+
|
|
140
|
+
Stage: RENDER (Main Entry Point)
|
|
141
|
+
|
|
142
|
+
This is the main rendering function. It walks the layout structure,
|
|
143
|
+
renders each chart (triggering lazy query execution), and produces
|
|
144
|
+
output in the requested format.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
face: Compiled dataface to render
|
|
148
|
+
executor: Executor for query execution
|
|
149
|
+
format: Output format (svg, html, png, pdf, terminal, json, text, yaml)
|
|
150
|
+
variables: Variable values for queries
|
|
151
|
+
ignore_codes: Caller-supplied set of warning codes to suppress (CLI seam).
|
|
152
|
+
project_dir: Project directory for reading dataface.yml warnings.ignore.
|
|
153
|
+
**options: Format-specific options
|
|
154
|
+
- background: Background color
|
|
155
|
+
- scale: Scale factor (for png)
|
|
156
|
+
- grid: Show grid overlay (for debugging)
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
RenderResult with:
|
|
160
|
+
- output: rendered content (str or bytes)
|
|
161
|
+
- chart_errors: per-chart runtime failures (face still rendered)
|
|
162
|
+
- face_error: post-validation fatal (None when render succeeded)
|
|
163
|
+
- render_warnings: active (non-suppressed) warnings
|
|
164
|
+
- suppressed_warnings: warnings dropped by any ignore layer
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
RenderError: If a face-level invariant is violated before rendering starts
|
|
168
|
+
FormatError: If format is unknown
|
|
169
|
+
"""
|
|
170
|
+
# Compile warns on orphans; render hard-fails the empty-layout-with-charts
|
|
171
|
+
# case so the dashboard never silently renders with nothing visible.
|
|
172
|
+
if face.charts and not face.layout.items:
|
|
173
|
+
from dataface.core.errors import DF_RENDER_NO_LAYOUT
|
|
174
|
+
|
|
175
|
+
chart_list = ", ".join(sorted(face.charts.keys()))
|
|
176
|
+
raise RenderError.from_code(DF_RENDER_NO_LAYOUT, charts=chart_list)
|
|
177
|
+
|
|
178
|
+
# Trust the normalizer - use pre-computed variable_defaults
|
|
179
|
+
variable_registry = face.variable_registry or {}
|
|
180
|
+
|
|
181
|
+
# Merge variables: start with None for all vars, then defaults, then user values
|
|
182
|
+
all_variables: dict[str, Any] = dict.fromkeys(variable_registry)
|
|
183
|
+
all_variables.update(face.variable_defaults) # Pre-computed by normalizer
|
|
184
|
+
# Parse JSON strings in variables (from URL parameters) and merge
|
|
185
|
+
parsed_variables = parse_variable_json_strings(variables or {})
|
|
186
|
+
merged_variables = {**all_variables, **parsed_variables}
|
|
187
|
+
|
|
188
|
+
# Face-level precondition: required variables must have a value before any query runs.
|
|
189
|
+
# None, empty string, and empty list all count as "not provided" — URL params arrive
|
|
190
|
+
# as strings so ?var= produces "" which is as unscoped as no value at all.
|
|
191
|
+
if variable_registry:
|
|
192
|
+
|
|
193
|
+
def _is_absent(v: Any) -> bool:
|
|
194
|
+
if v is None:
|
|
195
|
+
return True
|
|
196
|
+
if isinstance(v, str):
|
|
197
|
+
return not v.strip()
|
|
198
|
+
if isinstance(v, (list, tuple, set)):
|
|
199
|
+
return len(v) == 0
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
missing = [
|
|
203
|
+
MissingVariable(
|
|
204
|
+
key=key,
|
|
205
|
+
label=var.label,
|
|
206
|
+
description=var.description,
|
|
207
|
+
input_type=var.input,
|
|
208
|
+
)
|
|
209
|
+
for key, var in variable_registry.items()
|
|
210
|
+
if var.required is True and _is_absent(merged_variables.get(key))
|
|
211
|
+
]
|
|
212
|
+
if missing:
|
|
213
|
+
raise MissingRequiredVariablesError(missing)
|
|
214
|
+
|
|
215
|
+
# Pre-render query execution — authoritative, not a fallback.
|
|
216
|
+
# Queries run BEFORE calculate_data_aware_layout so the sizing pass can
|
|
217
|
+
# use cached results (render-first sizing reads query data from the executor
|
|
218
|
+
# cache without re-executing).
|
|
219
|
+
# Phase 1: chart-direct queries without {{ results.X }} run in parallel.
|
|
220
|
+
# Phase 2: chart-direct {{ results.X }} queries run sequentially once
|
|
221
|
+
# their upstream results are cached.
|
|
222
|
+
# Errors are stored on the executor (executor._query_errors) so that
|
|
223
|
+
# execute_chart() during the render walk raises the stored error
|
|
224
|
+
# instead of re-executing. The render walk never retries a failed query.
|
|
225
|
+
query_names = collect_layout_chart_query_names(face)
|
|
226
|
+
parallel_query_names, sequential_query_names = plan_query_execution_phases(
|
|
227
|
+
face, query_names
|
|
228
|
+
)
|
|
229
|
+
execute_queries_parallel(executor, face, parallel_query_names, merged_variables)
|
|
230
|
+
execute_queries_sequential(executor, sequential_query_names, merged_variables)
|
|
231
|
+
|
|
232
|
+
# Run warning detectors now that queries are cached.
|
|
233
|
+
# Partition into active vs suppressed using the union of three ignore layers.
|
|
234
|
+
_all_warnings = _collect_render_warnings(face, executor, merged_variables)
|
|
235
|
+
_project_codes = get_project_warnings_ignore(project_dir)
|
|
236
|
+
_per_chart_codes: dict[str, set[str]] = {
|
|
237
|
+
chart_id: set(chart.warnings_ignore)
|
|
238
|
+
for chart_id, chart in face.charts.items()
|
|
239
|
+
if chart.warnings_ignore
|
|
240
|
+
}
|
|
241
|
+
render_warnings, suppressed_warnings = _partition_warnings(
|
|
242
|
+
_all_warnings,
|
|
243
|
+
cli_codes=ignore_codes or set(),
|
|
244
|
+
project_codes=set(_project_codes),
|
|
245
|
+
per_chart_codes=_per_chart_codes,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Sync face.resolved_style (and all nested sub-board faces) from face.theme
|
|
249
|
+
# before layout sizing so calculate_data_aware_layout reads the live value.
|
|
250
|
+
sync_face_resolved_style(face)
|
|
251
|
+
|
|
252
|
+
# Calculate layout with data awareness — table heights use actual row counts,
|
|
253
|
+
# and Vega-Lite charts are rendered to get true heights (render-first sizing).
|
|
254
|
+
# Render-first sizing is skipped for non-SVG formats (yaml/json/text) since
|
|
255
|
+
# those formats don't render charts and don't benefit from actual heights.
|
|
256
|
+
# Returns (face, render_cache) where render_cache holds pre-rendered SVGs.
|
|
257
|
+
face, render_cache = calculate_data_aware_layout(
|
|
258
|
+
face,
|
|
259
|
+
executor,
|
|
260
|
+
merged_variables,
|
|
261
|
+
render_first=format in _SVG_FORMATS,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Resolve the sized face: bake board config constants, style, and Vega config.
|
|
265
|
+
# Called after data-aware sizing so resolved dimensions reflect actual heights.
|
|
266
|
+
config = get_config()
|
|
267
|
+
resolved_face = resolve_face(face, config)
|
|
268
|
+
|
|
269
|
+
# Resolve SVG canvas background: API override wins, otherwise use the
|
|
270
|
+
# cascaded face background (theme default or authored override, already merged).
|
|
271
|
+
override = options.get("background")
|
|
272
|
+
if override is not None:
|
|
273
|
+
background = None if override == "transparent" else override
|
|
274
|
+
else:
|
|
275
|
+
resolved_bg = resolved_face.style.background
|
|
276
|
+
background = None if resolved_bg == "transparent" else resolved_bg
|
|
277
|
+
|
|
278
|
+
# Per-chart error collector: single append site in render_chart_item.
|
|
279
|
+
# All formats share this collector so callers get chart_errors regardless of format.
|
|
280
|
+
error_collector: list[StructuredError] = []
|
|
281
|
+
|
|
282
|
+
# JSON/text formats: skip SVG rendering entirely — walk layout tree directly
|
|
283
|
+
if format == "json":
|
|
284
|
+
from dataface.core.render.json_format import render_face_json
|
|
285
|
+
|
|
286
|
+
output = render_face_json(
|
|
287
|
+
face, executor, merged_variables, error_collector=error_collector
|
|
288
|
+
)
|
|
289
|
+
return RenderResult(
|
|
290
|
+
output=output,
|
|
291
|
+
chart_errors=error_collector,
|
|
292
|
+
render_warnings=render_warnings,
|
|
293
|
+
suppressed_warnings=suppressed_warnings,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
if format == "text":
|
|
297
|
+
from dataface.core.render.text_format import render_face_text
|
|
298
|
+
|
|
299
|
+
output = render_face_text(
|
|
300
|
+
face, executor, merged_variables, error_collector=error_collector
|
|
301
|
+
)
|
|
302
|
+
return RenderResult(
|
|
303
|
+
output=output,
|
|
304
|
+
chart_errors=error_collector,
|
|
305
|
+
render_warnings=render_warnings,
|
|
306
|
+
suppressed_warnings=suppressed_warnings,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
if format == "yaml":
|
|
310
|
+
from dataface.core.render.yaml_format import render_face_yaml
|
|
311
|
+
|
|
312
|
+
output = render_face_yaml(
|
|
313
|
+
face, executor, merged_variables, error_collector=error_collector
|
|
314
|
+
)
|
|
315
|
+
return RenderResult(
|
|
316
|
+
output=output,
|
|
317
|
+
chart_errors=error_collector,
|
|
318
|
+
render_warnings=render_warnings,
|
|
319
|
+
suppressed_warnings=suppressed_warnings,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Render layout to SVG
|
|
323
|
+
# Format determines whether variables are interactive (foreignObject) or read-only (static text)
|
|
324
|
+
# PNG/PDF export requires read-only because svglib doesn't support foreignObject
|
|
325
|
+
# HTML format supports foreignObject, so it should also get interactive variables
|
|
326
|
+
interactive = format in ("svg", "html")
|
|
327
|
+
|
|
328
|
+
# Board link rewriting: set context for the duration of this render pass
|
|
329
|
+
from dataface.core.render.board_links import set_link_context
|
|
330
|
+
|
|
331
|
+
link_context = options.get("link_context")
|
|
332
|
+
set_link_context(link_context)
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
grid_enabled = options.get("grid", False)
|
|
336
|
+
margins_enabled = options.get("margins", False)
|
|
337
|
+
svg_content = render_face_svg(
|
|
338
|
+
resolved_face,
|
|
339
|
+
executor,
|
|
340
|
+
merged_variables,
|
|
341
|
+
background,
|
|
342
|
+
grid_enabled,
|
|
343
|
+
interactive,
|
|
344
|
+
render_cache=render_cache,
|
|
345
|
+
margins=margins_enabled,
|
|
346
|
+
error_collector=error_collector,
|
|
347
|
+
)
|
|
348
|
+
except DatafaceError as e:
|
|
349
|
+
# Face-level fatal: a DF-RENDER-* code escaped chart isolation
|
|
350
|
+
# (e.g. DF_RENDER_NO_LAYOUT, MissingRequiredVariablesError). Surface as
|
|
351
|
+
# face_error so callers see the structured code unwrapped.
|
|
352
|
+
set_link_context(None)
|
|
353
|
+
return RenderResult(
|
|
354
|
+
output=None,
|
|
355
|
+
chart_errors=error_collector,
|
|
356
|
+
face_error=e.to_structured(),
|
|
357
|
+
render_warnings=render_warnings,
|
|
358
|
+
suppressed_warnings=suppressed_warnings,
|
|
359
|
+
)
|
|
360
|
+
except Exception as e: # noqa: BLE001
|
|
361
|
+
from dataface.core.errors import DF_RENDER_INTERNAL
|
|
362
|
+
|
|
363
|
+
set_link_context(None)
|
|
364
|
+
wrapped = RenderError.from_code(DF_RENDER_INTERNAL, inner_message=str(e))
|
|
365
|
+
return RenderResult(
|
|
366
|
+
output=None,
|
|
367
|
+
chart_errors=error_collector,
|
|
368
|
+
face_error=wrapped.to_structured(),
|
|
369
|
+
render_warnings=render_warnings,
|
|
370
|
+
suppressed_warnings=suppressed_warnings,
|
|
371
|
+
)
|
|
372
|
+
finally:
|
|
373
|
+
set_link_context(None)
|
|
374
|
+
|
|
375
|
+
# Convert to requested format
|
|
376
|
+
if format == "svg":
|
|
377
|
+
return RenderResult(
|
|
378
|
+
output=svg_content,
|
|
379
|
+
chart_errors=error_collector,
|
|
380
|
+
render_warnings=render_warnings,
|
|
381
|
+
suppressed_warnings=suppressed_warnings,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
elif format == "html":
|
|
385
|
+
html_output = to_html(
|
|
386
|
+
face,
|
|
387
|
+
svg_content,
|
|
388
|
+
background,
|
|
389
|
+
executor,
|
|
390
|
+
merged_variables,
|
|
391
|
+
)
|
|
392
|
+
return RenderResult(
|
|
393
|
+
output=html_output,
|
|
394
|
+
chart_errors=error_collector,
|
|
395
|
+
render_warnings=render_warnings,
|
|
396
|
+
suppressed_warnings=suppressed_warnings,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
elif format == "png":
|
|
400
|
+
png_scale = options.get("scale")
|
|
401
|
+
if png_scale is None:
|
|
402
|
+
png_scale = get_rendering_config().png.scale
|
|
403
|
+
return RenderResult(
|
|
404
|
+
output=to_png(svg_content, png_scale),
|
|
405
|
+
chart_errors=error_collector,
|
|
406
|
+
render_warnings=render_warnings,
|
|
407
|
+
suppressed_warnings=suppressed_warnings,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
elif format == "pdf":
|
|
411
|
+
return RenderResult(
|
|
412
|
+
output=to_pdf(svg_content),
|
|
413
|
+
chart_errors=error_collector,
|
|
414
|
+
render_warnings=render_warnings,
|
|
415
|
+
suppressed_warnings=suppressed_warnings,
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
elif format == "terminal":
|
|
419
|
+
return RenderResult(
|
|
420
|
+
output=_to_terminal(face, executor, merged_variables, **options),
|
|
421
|
+
chart_errors=error_collector,
|
|
422
|
+
render_warnings=render_warnings,
|
|
423
|
+
suppressed_warnings=suppressed_warnings,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
else:
|
|
427
|
+
from dataface.core.errors import DF_RENDER_FORMAT_UNSUPPORTED
|
|
428
|
+
|
|
429
|
+
raise FormatError.from_code(DF_RENDER_FORMAT_UNSUPPORTED, format=format)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _to_terminal(
|
|
433
|
+
face: Face,
|
|
434
|
+
executor: Executor,
|
|
435
|
+
variables: VariableValues,
|
|
436
|
+
**options: Any,
|
|
437
|
+
) -> str:
|
|
438
|
+
"""Render face to terminal output.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
face: Face to render
|
|
442
|
+
executor: Executor for query execution
|
|
443
|
+
variables: Variable values for queries
|
|
444
|
+
**options: Terminal-specific options
|
|
445
|
+
- width: Terminal width in characters
|
|
446
|
+
- height: Terminal height in characters
|
|
447
|
+
- colors: Whether to use ANSI colors (default: True)
|
|
448
|
+
|
|
449
|
+
Returns:
|
|
450
|
+
Terminal-formatted string
|
|
451
|
+
"""
|
|
452
|
+
from dataface.core.render.terminal import render_face_terminal
|
|
453
|
+
|
|
454
|
+
return render_face_terminal(
|
|
455
|
+
face,
|
|
456
|
+
executor,
|
|
457
|
+
variables=variables,
|
|
458
|
+
width=options.get("width"),
|
|
459
|
+
height=options.get("height"),
|
|
460
|
+
colors=options.get("colors", True),
|
|
461
|
+
**{k: v for k, v in options.items() if k not in ("width", "height", "colors")},
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
__all__ = [
|
|
466
|
+
"render",
|
|
467
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Helpers for embedding JavaScript into rendered SVG output."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def escape_cdata(content: str) -> str:
|
|
5
|
+
"""Escape sequences that would terminate a CDATA section."""
|
|
6
|
+
return content.replace("]]>", "]]]]><![CDATA[>")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def embed_svg_script(script: str) -> str:
|
|
10
|
+
"""Wrap JavaScript for safe inline SVG execution."""
|
|
11
|
+
script = escape_cdata(script)
|
|
12
|
+
return f"""<script type="text/javascript">
|
|
13
|
+
<![CDATA[
|
|
14
|
+
{script}
|
|
15
|
+
]]>
|
|
16
|
+
</script>"""
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""SVG utility functions for rendering.
|
|
2
|
+
|
|
3
|
+
Stage: RENDER
|
|
4
|
+
Purpose: Provide SVG-specific utilities for dataface rendering.
|
|
5
|
+
|
|
6
|
+
This module handles:
|
|
7
|
+
- Grid pattern generation (loaded from template file)
|
|
8
|
+
- SVG styles generation (loaded from template file)
|
|
9
|
+
- Title rendering (using mdsvg)
|
|
10
|
+
- Error message rendering (using template file)
|
|
11
|
+
- SVG content extraction and dimension parsing
|
|
12
|
+
|
|
13
|
+
Dependencies:
|
|
14
|
+
- .template_loader (for Jinja2 template loading)
|
|
15
|
+
- .themes (for theme colors)
|
|
16
|
+
- mdsvg (required, for markdown rendering and text wrapping)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from typing import TYPE_CHECKING, Literal
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Pixel snapping
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _px(v: float) -> int:
|
|
29
|
+
"""Snap a float coordinate to the nearest integer pixel.
|
|
30
|
+
|
|
31
|
+
SVG transforms are applied before pixel rasterization: a sub-pixel ancestor
|
|
32
|
+
translate pushes a 1px-tall rule onto fractional rows and the rasterizer
|
|
33
|
+
splits it across two pixels at reduced opacity — reading visually as "thin"
|
|
34
|
+
or "faded". Rounding each emitted translate keeps every cumulative transform
|
|
35
|
+
on an integer boundary, so rules, rects, and other 1px structural marks stay
|
|
36
|
+
crisp.
|
|
37
|
+
|
|
38
|
+
Uses Python's built-in ``round()``, which applies banker's rounding
|
|
39
|
+
(round-half-to-even): ``_px(24.5) == 24``, ``_px(25.5) == 26``. Values
|
|
40
|
+
that fall exactly on a .5 boundary may round either direction; this is
|
|
41
|
+
non-monotonic but produces the least cumulative drift over long sequences.
|
|
42
|
+
"""
|
|
43
|
+
return int(round(v))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
from dataface.core.compile.models.style.merged import MergedStyle
|
|
48
|
+
|
|
49
|
+
from dataface.core.render.template_loader import render_template
|
|
50
|
+
|
|
51
|
+
# Compiled regex patterns for SVG parsing - avoids recompilation on each call
|
|
52
|
+
_SVG_CONTENT_RE = re.compile(r"<svg[^>]*>(.*)</svg>", re.DOTALL)
|
|
53
|
+
_SVG_WIDTH_RE = re.compile(r'width=["\'](\d+(?:\.\d+)?)["\']')
|
|
54
|
+
_SVG_HEIGHT_RE = re.compile(r'height=["\'](\d+(?:\.\d+)?)["\']')
|
|
55
|
+
_SVG_VIEWBOX_RE = re.compile(
|
|
56
|
+
r'viewBox=["\']0\s+0\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)["\']'
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class SVGDimensions:
|
|
62
|
+
"""Dimensions extracted from an SVG string."""
|
|
63
|
+
|
|
64
|
+
width: float
|
|
65
|
+
height: float
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def extract_svg_inner_content(svg: str) -> str:
|
|
69
|
+
"""Extract inner content from an SVG string (strip the <svg> wrapper).
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
svg: Full SVG string with wrapper
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Inner content without <svg>...</svg> wrapper
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
ValueError: If SVG content cannot be extracted
|
|
79
|
+
|
|
80
|
+
TODO(mdsvg#10): Replace with structured RenderResult when available.
|
|
81
|
+
See: https://github.com/davefowler/markdown-svg/issues/10
|
|
82
|
+
"""
|
|
83
|
+
match = _SVG_CONTENT_RE.search(svg)
|
|
84
|
+
if not match:
|
|
85
|
+
raise ValueError(f"Failed to extract SVG content from: {svg[:100]}...")
|
|
86
|
+
return match.group(1)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def extract_svg_dimensions(
|
|
90
|
+
svg: str, default_width: float = 0.0, default_height: float = 0.0
|
|
91
|
+
) -> SVGDimensions:
|
|
92
|
+
"""Extract width and height from an SVG string.
|
|
93
|
+
|
|
94
|
+
Checks viewBox first (more reliable for Vega-Lite output), then falls back
|
|
95
|
+
to width/height attributes.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
svg: SVG string to parse
|
|
99
|
+
default_width: Default width if not found
|
|
100
|
+
default_height: Default height if not found
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
SVGDimensions with extracted or default values
|
|
104
|
+
"""
|
|
105
|
+
# Prefer viewBox dimensions (more reliable for Vega-Lite output)
|
|
106
|
+
viewbox_match = _SVG_VIEWBOX_RE.search(svg)
|
|
107
|
+
if viewbox_match:
|
|
108
|
+
return SVGDimensions(
|
|
109
|
+
width=float(viewbox_match.group(1)),
|
|
110
|
+
height=float(viewbox_match.group(2)),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Fall back to explicit width/height attributes
|
|
114
|
+
width = default_width
|
|
115
|
+
height = default_height
|
|
116
|
+
|
|
117
|
+
width_match = _SVG_WIDTH_RE.search(svg)
|
|
118
|
+
if width_match:
|
|
119
|
+
width = float(width_match.group(1))
|
|
120
|
+
|
|
121
|
+
height_match = _SVG_HEIGHT_RE.search(svg)
|
|
122
|
+
if height_match:
|
|
123
|
+
height = float(height_match.group(1))
|
|
124
|
+
|
|
125
|
+
return SVGDimensions(width=width, height=height)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def create_grid_pattern() -> str:
|
|
129
|
+
"""Create an SVG pattern definition for a 100x100 grid.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
SVG <pattern> element for 100x100 grid overlay.
|
|
133
|
+
"""
|
|
134
|
+
return render_template("svg/grid_pattern.svg")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def generate_svg_styles(
|
|
138
|
+
emoji_mode: Literal["monochrome", "color", "system-default", "disabled"],
|
|
139
|
+
) -> str:
|
|
140
|
+
"""Generate CSS styles for SVG charts and interactions.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
emoji_mode: One of monochrome / color / system-default / disabled.
|
|
144
|
+
Controls whether the @font-face emoji partial and font-variant-emoji
|
|
145
|
+
rule are included in the embedded SVG stylesheet.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
SVG <style> element with CSS rules
|
|
149
|
+
"""
|
|
150
|
+
return render_template("svg/styles.css", emoji_mode=emoji_mode)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def render_title(
|
|
154
|
+
title: str,
|
|
155
|
+
width: float,
|
|
156
|
+
text_align: Literal["left", "center", "right"] = "left",
|
|
157
|
+
*,
|
|
158
|
+
level: int = 1,
|
|
159
|
+
prose: bool = False,
|
|
160
|
+
resolved_style: "MergedStyle | None" = None,
|
|
161
|
+
) -> str:
|
|
162
|
+
"""Render face title using mdsvg for proper text handling.
|
|
163
|
+
|
|
164
|
+
Font size is determined by card pixel width (width-based tier).
|
|
165
|
+
When ``prose=True`` the title uses the serif family (Source Serif 4)
|
|
166
|
+
regardless of width, matching the prose body text.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
title: Title text to render
|
|
170
|
+
width: Available width (drives tier selection: narrow/medium/wide)
|
|
171
|
+
text_align: Text alignment ("left", "center", "right")
|
|
172
|
+
prose: When True, render in serif to match prose body text.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
SVG string for the title
|
|
176
|
+
"""
|
|
177
|
+
from dataface.core.compile.config import get_config
|
|
178
|
+
from dataface.core.compile.models.style.merged import resolve_style
|
|
179
|
+
from dataface.core.compile.sizing import get_compact_style
|
|
180
|
+
from dataface.core.compile.typography import face_title_markdown
|
|
181
|
+
from dataface.core.fonts import get_inter_font_path
|
|
182
|
+
from mdsvg import render as render_markdown
|
|
183
|
+
|
|
184
|
+
markdown_title, h1_size, heading_weight = face_title_markdown(
|
|
185
|
+
title, width, level=level
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if prose:
|
|
189
|
+
assert resolved_style is not None
|
|
190
|
+
font_family = str(resolved_style.title.font.family)
|
|
191
|
+
else:
|
|
192
|
+
font_family = None
|
|
193
|
+
_ms = (
|
|
194
|
+
resolved_style
|
|
195
|
+
if resolved_style is not None
|
|
196
|
+
else resolve_style(get_config().style)
|
|
197
|
+
)
|
|
198
|
+
style = get_compact_style(
|
|
199
|
+
_ms,
|
|
200
|
+
text_align=text_align,
|
|
201
|
+
h1_size=h1_size,
|
|
202
|
+
heading_font_weight=heading_weight,
|
|
203
|
+
font_family=font_family,
|
|
204
|
+
)
|
|
205
|
+
font_path = str(get_inter_font_path())
|
|
206
|
+
return render_markdown(
|
|
207
|
+
markdown_title,
|
|
208
|
+
width=width,
|
|
209
|
+
padding=0.0,
|
|
210
|
+
style=style,
|
|
211
|
+
font_path=font_path,
|
|
212
|
+
)
|