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,157 @@
|
|
|
1
|
+
"""Shared chart title overflow helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
from dataface.core.render.font_measurement import get_font_measurer
|
|
9
|
+
from mdsvg.fonts import truncate_text_precise, wrap_text_precise
|
|
10
|
+
|
|
11
|
+
TitleOverflowMode = Literal["clip", "truncate", "wrap-two", "wrap"]
|
|
12
|
+
DEFAULT_TITLE_OVERFLOW: TitleOverflowMode = "wrap-two"
|
|
13
|
+
|
|
14
|
+
# Match the outer Vega chart-group translate, e.g. translate(20,53)
|
|
15
|
+
_MAIN_GROUP_X_RE = re.compile(
|
|
16
|
+
r'<g fill="none" stroke-miterlimit="10" transform="translate\(([0-9.]+),'
|
|
17
|
+
)
|
|
18
|
+
# Match the title inner-group translate, e.g. translate(-71.21,-48)
|
|
19
|
+
_TITLE_GROUP_RE = re.compile(
|
|
20
|
+
r'(class="mark-group role-title"><g transform="translate\()(-?[0-9.]+)(,)'
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def resolve_title_overflow(title_style: Any | None) -> TitleOverflowMode:
|
|
25
|
+
"""Return the effective title overflow mode."""
|
|
26
|
+
raw = getattr(title_style, "overflow", None) if title_style is not None else None
|
|
27
|
+
if raw in {"clip", "truncate", "wrap-two", "wrap"}:
|
|
28
|
+
return raw
|
|
29
|
+
return DEFAULT_TITLE_OVERFLOW
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def compute_title_limit(width: Any, padding: Any) -> int | None:
|
|
33
|
+
"""Return the usable title width after horizontal padding."""
|
|
34
|
+
if not isinstance(width, (int, float)) or width <= 0:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
left = right = 0.0
|
|
38
|
+
if isinstance(padding, dict):
|
|
39
|
+
left = float(padding.get("left", 0) or 0)
|
|
40
|
+
right = float(padding.get("right", 0) or 0)
|
|
41
|
+
elif isinstance(padding, (int, float)):
|
|
42
|
+
left = right = float(padding)
|
|
43
|
+
|
|
44
|
+
limit = int(width - left - right)
|
|
45
|
+
return max(limit, 1) if limit > 0 else None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def prepare_title_text(
|
|
49
|
+
text: str,
|
|
50
|
+
*,
|
|
51
|
+
overflow: TitleOverflowMode,
|
|
52
|
+
limit: int | None,
|
|
53
|
+
font_size: float,
|
|
54
|
+
font_family: str | None = None,
|
|
55
|
+
) -> str:
|
|
56
|
+
"""Return title text with explicit line breaks/clipping applied."""
|
|
57
|
+
if not text or limit is None:
|
|
58
|
+
return text
|
|
59
|
+
|
|
60
|
+
measurer = get_font_measurer(font_family)
|
|
61
|
+
if overflow == "clip":
|
|
62
|
+
return truncate_text_precise(
|
|
63
|
+
text,
|
|
64
|
+
limit,
|
|
65
|
+
font_size,
|
|
66
|
+
measurer,
|
|
67
|
+
ellipsis=False,
|
|
68
|
+
)
|
|
69
|
+
if overflow == "truncate":
|
|
70
|
+
return truncate_text_precise(
|
|
71
|
+
text,
|
|
72
|
+
limit,
|
|
73
|
+
font_size,
|
|
74
|
+
measurer,
|
|
75
|
+
ellipsis=True,
|
|
76
|
+
)
|
|
77
|
+
if overflow == "wrap":
|
|
78
|
+
return "\n".join(
|
|
79
|
+
wrap_text_precise(
|
|
80
|
+
text,
|
|
81
|
+
limit,
|
|
82
|
+
font_size,
|
|
83
|
+
measurer,
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
return "\n".join(
|
|
87
|
+
wrap_text_precise(
|
|
88
|
+
text,
|
|
89
|
+
limit,
|
|
90
|
+
font_size,
|
|
91
|
+
measurer,
|
|
92
|
+
max_lines=2,
|
|
93
|
+
ellipsis=True,
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def apply_title_overflow_to_spec(
|
|
99
|
+
spec: dict[str, Any],
|
|
100
|
+
title_style: Any | None,
|
|
101
|
+
*,
|
|
102
|
+
title_font_size: float | None = None,
|
|
103
|
+
title_font_family: str | None = None,
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Apply title overflow handling to a Vega-Lite spec in place."""
|
|
106
|
+
title_block = spec.get("title")
|
|
107
|
+
if not isinstance(title_block, dict):
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
limit = compute_title_limit(spec.get("width"), spec.get("padding"))
|
|
111
|
+
if limit is None:
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
title_block["limit"] = limit
|
|
115
|
+
text = title_block.get("text")
|
|
116
|
+
if isinstance(text, str) and text:
|
|
117
|
+
style_font_size = (
|
|
118
|
+
getattr(title_style, "font_size", None) if title_style is not None else None
|
|
119
|
+
)
|
|
120
|
+
effective_font_size = float(
|
|
121
|
+
style_font_size
|
|
122
|
+
or title_font_size
|
|
123
|
+
or spec.get("config", {}).get("title", {}).get("fontSize")
|
|
124
|
+
or 18
|
|
125
|
+
)
|
|
126
|
+
result = prepare_title_text(
|
|
127
|
+
text,
|
|
128
|
+
overflow=resolve_title_overflow(title_style),
|
|
129
|
+
limit=limit,
|
|
130
|
+
font_size=effective_font_size,
|
|
131
|
+
font_family=title_font_family,
|
|
132
|
+
)
|
|
133
|
+
lines = result.split("\n")
|
|
134
|
+
title_block["text"] = lines if len(lines) > 1 else result
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def fix_title_alignment(svg: str, padding_left: float) -> str:
|
|
138
|
+
"""Move the Vega-rendered title right to ``padding_left`` when leftward drift occurs."""
|
|
139
|
+
main_match = _MAIN_GROUP_X_RE.search(svg)
|
|
140
|
+
if not main_match:
|
|
141
|
+
return svg
|
|
142
|
+
|
|
143
|
+
title_match = _TITLE_GROUP_RE.search(svg)
|
|
144
|
+
if not title_match:
|
|
145
|
+
return svg
|
|
146
|
+
|
|
147
|
+
main_x = float(main_match.group(1))
|
|
148
|
+
title_x = float(title_match.group(2))
|
|
149
|
+
title_svg_x = main_x + title_x
|
|
150
|
+
|
|
151
|
+
if title_svg_x >= padding_left:
|
|
152
|
+
return svg
|
|
153
|
+
|
|
154
|
+
new_title_x = padding_left - main_x
|
|
155
|
+
old = title_match.group(1) + title_match.group(2) + title_match.group(3)
|
|
156
|
+
new = title_match.group(1) + f"{new_title_x:.2f}" + title_match.group(3)
|
|
157
|
+
return svg.replace(old, new, 1)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Shared helpers for Vega-Lite type inference from query result data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import datetime as dt
|
|
6
|
+
import re
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
DATE_LIKE_PATTERNS = [
|
|
10
|
+
r"^\d{4}-Q[1-4]$",
|
|
11
|
+
r"^Q[1-4]\s*\d{4}$",
|
|
12
|
+
r"^\d{4}Q[1-4]$",
|
|
13
|
+
r"^\d{4}-\d{2}$",
|
|
14
|
+
r"^(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{4}$",
|
|
15
|
+
r"^FY\d{4}$",
|
|
16
|
+
r"^H[12]\s*\d{4}$",
|
|
17
|
+
r"^\d{4}-H[12]$",
|
|
18
|
+
r"^W(?:eek\s*)?\d{1,2}\s*\d{4}$",
|
|
19
|
+
r"^\d{4}-W\d{2}$", # ISO 8601 week: 2024-W01
|
|
20
|
+
r"^\d{2}/\d{4}$", # MM/YYYY: 01/2024
|
|
21
|
+
r"^\d{2}/\d{2}/\d{4}$", # MM/DD/YYYY: 01/15/2024
|
|
22
|
+
r"^(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2},?\s+\d{4}$", # Mon DD, YYYY
|
|
23
|
+
r"^\d{4}-\d{2}-\d{2}$", # YYYY-MM-DD: 2024-01-15
|
|
24
|
+
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?$", # ISO 8601 naive timestamp: 2024-01-15T13:30:00
|
|
25
|
+
]
|
|
26
|
+
DATE_LIKE_REGEXES = [
|
|
27
|
+
re.compile(pattern, re.IGNORECASE) for pattern in DATE_LIKE_PATTERNS
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def is_date_like_string(value: str) -> bool:
|
|
32
|
+
"""Return True when a string looks like an ordered date bucket."""
|
|
33
|
+
return any(pattern.match(value) for pattern in DATE_LIKE_REGEXES)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Year-leading patterns whose lexicographic ascending order equals chronological
|
|
37
|
+
# order. Safe to use as a window-sort field for x-cell sampling.
|
|
38
|
+
# Patterns NOT listed here (e.g. "Jan 2024", "01/2024", "Q1 2024") sort
|
|
39
|
+
# lex-incorrectly and must NOT trigger sampling — wrong cells would be selected.
|
|
40
|
+
# Every entry must also appear in DATE_LIKE_PATTERNS (the module-level assert
|
|
41
|
+
# below enforces this so the two lists cannot silently drift).
|
|
42
|
+
_LEX_SORTABLE_DATE_LIKE_PATTERNS = [
|
|
43
|
+
r"^\d{4}-Q[1-4]$", # 2024-Q1 lex == chron
|
|
44
|
+
r"^\d{4}Q[1-4]$", # 2024Q1 lex == chron
|
|
45
|
+
r"^\d{4}-\d{2}$", # 2024-01 lex == chron
|
|
46
|
+
r"^FY\d{4}$", # FY2024 lex == chron
|
|
47
|
+
r"^\d{4}-H[12]$", # 2024-H1 lex == chron
|
|
48
|
+
r"^\d{4}-W\d{2}$", # 2024-W01 lex == chron (ISO 8601 week)
|
|
49
|
+
r"^\d{4}-\d{2}-\d{2}$", # 2024-01-15 lex == chron
|
|
50
|
+
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?$", # naive ISO timestamp lex == chron (no tz suffix)
|
|
51
|
+
]
|
|
52
|
+
# Guard against drift: every lex-sortable pattern must be a subset of the
|
|
53
|
+
# full date-like list so a future edit to DATE_LIKE_PATTERNS can't leave
|
|
54
|
+
# is_lex_sortable_date_like silently matching patterns that were tightened.
|
|
55
|
+
# Use RuntimeError (not assert) so the guard survives python -O.
|
|
56
|
+
if not set(_LEX_SORTABLE_DATE_LIKE_PATTERNS) <= set(DATE_LIKE_PATTERNS):
|
|
57
|
+
raise RuntimeError(
|
|
58
|
+
"_LEX_SORTABLE_DATE_LIKE_PATTERNS contains patterns not in DATE_LIKE_PATTERNS; "
|
|
59
|
+
"update or remove the stale entries"
|
|
60
|
+
)
|
|
61
|
+
_LEX_SORTABLE_DATE_LIKE_REGEXES = [
|
|
62
|
+
re.compile(p, re.IGNORECASE) for p in _LEX_SORTABLE_DATE_LIKE_PATTERNS
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def is_lex_sortable_date_like(value: str) -> bool:
|
|
67
|
+
"""Return True when the string is a date-like bucket whose lex sort is chronological.
|
|
68
|
+
|
|
69
|
+
Only year-leading ISO-prefix patterns qualify. Month-name, MM/YYYY, and
|
|
70
|
+
quarter-first patterns sort incorrectly under string comparison and must
|
|
71
|
+
not be used to drive the sampling window's ascending sort.
|
|
72
|
+
"""
|
|
73
|
+
return any(p.match(value) for p in _LEX_SORTABLE_DATE_LIKE_REGEXES)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def infer_vega_type_from_data(data: list[dict[str, Any]], field: str) -> str:
|
|
77
|
+
"""Infer the most appropriate Vega-Lite type for a field."""
|
|
78
|
+
if not data or field not in data[0]:
|
|
79
|
+
return "nominal"
|
|
80
|
+
|
|
81
|
+
sample_values = [
|
|
82
|
+
row.get(field) for row in data[: min(10, len(data))] if field in row
|
|
83
|
+
]
|
|
84
|
+
if not sample_values:
|
|
85
|
+
return "nominal"
|
|
86
|
+
|
|
87
|
+
all_numeric = True
|
|
88
|
+
all_temporal = True
|
|
89
|
+
all_date_like = True
|
|
90
|
+
|
|
91
|
+
for value in sample_values:
|
|
92
|
+
if value is None:
|
|
93
|
+
continue
|
|
94
|
+
if not isinstance(value, (int, float)):
|
|
95
|
+
all_numeric = False
|
|
96
|
+
if isinstance(value, (dt.date, dt.datetime)):
|
|
97
|
+
# date/datetime objects are always temporal; date_like too
|
|
98
|
+
pass
|
|
99
|
+
elif isinstance(value, str):
|
|
100
|
+
# Require an actual date-like structure: YYYY-MM-DD, YYYY/MM/DD,
|
|
101
|
+
# MM/DD/YYYY, or ISO timestamps. The old check (`count("-") >= 2`)
|
|
102
|
+
# was too broad and misidentified strings like "claude-opus-4-7"
|
|
103
|
+
# (3 hyphens, no digits in date positions) as temporal.
|
|
104
|
+
is_standard_date = bool(
|
|
105
|
+
re.match(r"^\d{4}[-/]\d{2}[-/]\d{2}(T[\d:.].*)?$", value)
|
|
106
|
+
or re.match(r"^\d{2}/\d{2}/\d{4}$", value)
|
|
107
|
+
)
|
|
108
|
+
if not is_standard_date:
|
|
109
|
+
all_temporal = False
|
|
110
|
+
if not is_standard_date and not is_date_like_string(value):
|
|
111
|
+
all_date_like = False
|
|
112
|
+
else:
|
|
113
|
+
all_temporal = False
|
|
114
|
+
all_date_like = False
|
|
115
|
+
|
|
116
|
+
if all_numeric:
|
|
117
|
+
return "quantitative"
|
|
118
|
+
if all_temporal:
|
|
119
|
+
return "temporal"
|
|
120
|
+
if all_date_like:
|
|
121
|
+
return "ordinal"
|
|
122
|
+
return "nominal"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Chart data validation helpers.
|
|
2
|
+
|
|
3
|
+
Keeps data-shape policy separate from mechanical spec assembly.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from collections import Counter
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from dataface.core.compile.chart_resolved import ResolvedChart, effective_color_field
|
|
12
|
+
from dataface.core.errors.codes_render import DF_RENDER_BAR_DUPLICATE_ROWS
|
|
13
|
+
from dataface.core.render.chart.type_inference import infer_vega_type_from_data
|
|
14
|
+
from dataface.core.render.errors import ChartDataError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _format_duplicate_key(fields: list[str], key: tuple[Any, ...]) -> str:
|
|
18
|
+
parts = [f"{field}={value!r}" for field, value in zip(fields, key, strict=False)]
|
|
19
|
+
return ", ".join(parts)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _plot_key_fields(chart: ResolvedChart, data: list[dict[str, Any]]) -> list[str]:
|
|
23
|
+
chart_type = chart.chart_type
|
|
24
|
+
color = effective_color_field(chart)
|
|
25
|
+
|
|
26
|
+
if chart_type == "bar":
|
|
27
|
+
x_field = chart.x
|
|
28
|
+
y_field = chart.y if isinstance(chart.y, str) else None
|
|
29
|
+
if not x_field or not y_field:
|
|
30
|
+
return []
|
|
31
|
+
|
|
32
|
+
x_type = infer_vega_type_from_data(data, x_field)
|
|
33
|
+
y_type = infer_vega_type_from_data(data, y_field)
|
|
34
|
+
if x_type in {"nominal", "ordinal"} and y_type == "quantitative":
|
|
35
|
+
fields = [x_field]
|
|
36
|
+
elif y_type in {"nominal", "ordinal"} and x_type == "quantitative":
|
|
37
|
+
fields = [y_field]
|
|
38
|
+
else:
|
|
39
|
+
return []
|
|
40
|
+
|
|
41
|
+
if color:
|
|
42
|
+
fields.append(color)
|
|
43
|
+
return fields
|
|
44
|
+
|
|
45
|
+
if chart_type in {"line", "area"}:
|
|
46
|
+
x_field = chart.x
|
|
47
|
+
y_field = chart.y if isinstance(chart.y, str) else None
|
|
48
|
+
if not x_field or not y_field:
|
|
49
|
+
return []
|
|
50
|
+
if infer_vega_type_from_data(data, y_field) != "quantitative":
|
|
51
|
+
return []
|
|
52
|
+
|
|
53
|
+
fields = [x_field]
|
|
54
|
+
for series_field in (color, chart.shape):
|
|
55
|
+
if series_field:
|
|
56
|
+
fields.append(series_field)
|
|
57
|
+
return fields
|
|
58
|
+
|
|
59
|
+
if chart_type in {"arc", "pie"}:
|
|
60
|
+
return [color] if color else []
|
|
61
|
+
|
|
62
|
+
if chart_type in {"rect", "square", "heatmap"}:
|
|
63
|
+
x_field = chart.x
|
|
64
|
+
y_field = chart.y if isinstance(chart.y, str) else None
|
|
65
|
+
if x_field and y_field:
|
|
66
|
+
return [x_field, y_field]
|
|
67
|
+
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def validate_preaggregated_data(
|
|
72
|
+
chart: ResolvedChart,
|
|
73
|
+
data: list[dict[str, Any]],
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Reject raw detail rows when a chart expects one value per plotted key."""
|
|
76
|
+
if not data:
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
key_fields = _plot_key_fields(chart, data)
|
|
80
|
+
if not key_fields:
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
counts = Counter(tuple(row.get(field) for field in key_fields) for row in data)
|
|
84
|
+
duplicate_keys = [key for key, count in counts.items() if count > 1]
|
|
85
|
+
if not duplicate_keys:
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
duplicate_preview = "; ".join(
|
|
89
|
+
_format_duplicate_key(key_fields, key) for key in duplicate_keys[:3]
|
|
90
|
+
)
|
|
91
|
+
chart_id = chart.id or "unknown"
|
|
92
|
+
field_list = ", ".join(key_fields)
|
|
93
|
+
raise ChartDataError.from_code(
|
|
94
|
+
DF_RENDER_BAR_DUPLICATE_ROWS,
|
|
95
|
+
chart_type=chart.chart_type.title(),
|
|
96
|
+
chart_id=chart_id,
|
|
97
|
+
field_list=field_list,
|
|
98
|
+
duplicate_preview=duplicate_preview,
|
|
99
|
+
)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Public chart rendering entrypoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from dataface.core.compile.models.style.merged import MergedStyle
|
|
9
|
+
|
|
10
|
+
from dataface.core.compile.chart_resolved import ResolvedChart
|
|
11
|
+
from dataface.core.compile.models.chart.compiled import (
|
|
12
|
+
Chart,
|
|
13
|
+
)
|
|
14
|
+
from dataface.core.render.chart.pipeline import resolve_chart
|
|
15
|
+
from dataface.core.render.chart.renderers import RendererRegistry, build_chart_json
|
|
16
|
+
from dataface.core.render.chart.standard_renderer import render_standard_vega_spec
|
|
17
|
+
from dataface.core.render.converters.chart import render_chart_artifact
|
|
18
|
+
|
|
19
|
+
renderer_registry = RendererRegistry()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def generate_vega_lite_spec(
|
|
23
|
+
chart: Chart | ResolvedChart | Any,
|
|
24
|
+
data: list[dict[str, Any]],
|
|
25
|
+
width: float | None = None,
|
|
26
|
+
height: float | None = None,
|
|
27
|
+
theme: str | None = None,
|
|
28
|
+
resolved_style: MergedStyle | None = None,
|
|
29
|
+
) -> dict[str, Any]:
|
|
30
|
+
"""Generate a Vega-Lite spec for charts that render as Vega-Lite."""
|
|
31
|
+
# Synthesise a default at the public-API boundary so callers that don't
|
|
32
|
+
# have a face-level MergedStyle (tests, CLI tools) still get a valid spec.
|
|
33
|
+
# Geo/map chart types require a concrete board_style for stroke/projection
|
|
34
|
+
# reads; this resolves to the compiled default when the caller omits it.
|
|
35
|
+
if resolved_style is None:
|
|
36
|
+
from dataface.core.compile.config import get_config
|
|
37
|
+
from dataface.core.compile.models.style.merged import resolve_style as _resolve
|
|
38
|
+
|
|
39
|
+
resolved_style = _resolve(get_config().style)
|
|
40
|
+
resolved_chart = (
|
|
41
|
+
chart
|
|
42
|
+
if isinstance(chart, ResolvedChart)
|
|
43
|
+
else resolve_chart(chart, data, board_style=resolved_style)
|
|
44
|
+
)
|
|
45
|
+
if resolved_chart.chart_type == "table":
|
|
46
|
+
return {"data": {"values": data}}
|
|
47
|
+
if resolved_chart.renderer_family == "svg":
|
|
48
|
+
# KPI / table / spark_bar / error are custom-SVG; they have no
|
|
49
|
+
# Vega-Lite spec equivalent. Callers asking for a VL spec on these
|
|
50
|
+
# types are almost always confused — fail loudly instead of returning
|
|
51
|
+
# a partial spec.
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"Chart type '{resolved_chart.chart_type}' does not render to a Vega-Lite spec"
|
|
54
|
+
)
|
|
55
|
+
return render_standard_vega_spec(
|
|
56
|
+
resolved_chart,
|
|
57
|
+
data,
|
|
58
|
+
width=width,
|
|
59
|
+
height=height,
|
|
60
|
+
theme=theme,
|
|
61
|
+
resolved_style=resolved_style,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def render_chart(
|
|
66
|
+
chart: Chart | ResolvedChart | Any,
|
|
67
|
+
data: list[dict[str, Any]],
|
|
68
|
+
format: str = "json",
|
|
69
|
+
width: float | None = None,
|
|
70
|
+
height: float | None = None,
|
|
71
|
+
is_placeholder: bool = False,
|
|
72
|
+
datasets: dict[str, list[dict[str, Any]]] | None = None,
|
|
73
|
+
variables: dict[str, Any] | None = None,
|
|
74
|
+
padding: dict[str, Any] | None = None,
|
|
75
|
+
resolved_style: MergedStyle | None = None,
|
|
76
|
+
face_level: int = 1,
|
|
77
|
+
effective_vega_config: dict[str, Any] | None = None,
|
|
78
|
+
) -> str:
|
|
79
|
+
"""Render a chart to JSON, SVG, PNG, or PDF."""
|
|
80
|
+
resolved_chart = (
|
|
81
|
+
chart
|
|
82
|
+
if isinstance(chart, ResolvedChart)
|
|
83
|
+
else resolve_chart(chart, data, board_style=resolved_style)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if format == "json":
|
|
87
|
+
artifact = build_chart_json(resolved_chart, data, width=width, height=height)
|
|
88
|
+
return render_chart_artifact(
|
|
89
|
+
artifact,
|
|
90
|
+
format,
|
|
91
|
+
width=width,
|
|
92
|
+
height=height,
|
|
93
|
+
is_placeholder=is_placeholder,
|
|
94
|
+
resolved_style=resolved_style,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
render_data = data
|
|
98
|
+
if is_placeholder and not data:
|
|
99
|
+
from dataface.core.render.placeholder import generate_placeholder_data
|
|
100
|
+
|
|
101
|
+
render_data = generate_placeholder_data(
|
|
102
|
+
resolved_chart.chart_type, resolved_chart
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
artifact = renderer_registry.render(
|
|
106
|
+
resolved_chart,
|
|
107
|
+
render_data,
|
|
108
|
+
width=width,
|
|
109
|
+
height=height,
|
|
110
|
+
is_placeholder=is_placeholder,
|
|
111
|
+
datasets=datasets,
|
|
112
|
+
variables=variables,
|
|
113
|
+
padding=padding,
|
|
114
|
+
resolved_style=resolved_style,
|
|
115
|
+
face_level=face_level,
|
|
116
|
+
effective_vega_config=effective_vega_config,
|
|
117
|
+
)
|
|
118
|
+
return render_chart_artifact(
|
|
119
|
+
artifact,
|
|
120
|
+
format,
|
|
121
|
+
width=width,
|
|
122
|
+
height=height,
|
|
123
|
+
is_placeholder=is_placeholder,
|
|
124
|
+
resolved_style=resolved_style,
|
|
125
|
+
)
|