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,43 @@
|
|
|
1
|
+
@font-face {
|
|
2
|
+
font-family: 'Inter Variable';
|
|
3
|
+
src: url('/static/fonts/InterVariable.woff2') format('woff2-variations');
|
|
4
|
+
font-weight: 100 900;
|
|
5
|
+
font-style: normal;
|
|
6
|
+
font-display: swap;
|
|
7
|
+
}
|
|
8
|
+
@font-face {
|
|
9
|
+
font-family: 'DFT Sans Tabular';
|
|
10
|
+
src: url('/static/fonts/DFTSansTabular-Regular.woff2') format('woff2-variations');
|
|
11
|
+
font-weight: 100 900;
|
|
12
|
+
font-style: normal;
|
|
13
|
+
font-display: swap;
|
|
14
|
+
}
|
|
15
|
+
@font-face {
|
|
16
|
+
font-family: 'Source Serif 4';
|
|
17
|
+
src: url('/static/fonts/source-serif-4-variable-latin.woff2') format('woff2-variations');
|
|
18
|
+
font-weight: 200 900;
|
|
19
|
+
font-style: normal;
|
|
20
|
+
font-display: swap;
|
|
21
|
+
}
|
|
22
|
+
@font-face {
|
|
23
|
+
font-family: 'Noto Emoji';
|
|
24
|
+
src: url('/static/fonts/NotoEmoji-Regular.woff2') format('woff2');
|
|
25
|
+
font-display: swap;
|
|
26
|
+
unicode-range:
|
|
27
|
+
U+1F000-1F02F, U+1F0A0-1F0FF, U+1F100-1F1FF, U+1F200-1F2FF,
|
|
28
|
+
U+1F300-1F5FF, U+1F600-1F64F, U+1F650-1F67F, U+1F680-1F6FF,
|
|
29
|
+
U+1F700-1F77F, U+1F780-1F7FF, U+1F800-1F8FF, U+1F900-1F9FF,
|
|
30
|
+
U+1FA00-1FA6F, U+1FA70-1FAFF, U+2600-26FF, U+2700-27BF,
|
|
31
|
+
U+FE00-FE0F, U+1F1E6-1F1FF;
|
|
32
|
+
}
|
|
33
|
+
@font-face {
|
|
34
|
+
font-family: 'Noto Color Emoji';
|
|
35
|
+
src: url('/static/fonts/NotoColorEmoji-Regular.woff2') format('woff2');
|
|
36
|
+
font-display: swap;
|
|
37
|
+
unicode-range:
|
|
38
|
+
U+1F000-1F02F, U+1F0A0-1F0FF, U+1F100-1F1FF, U+1F200-1F2FF,
|
|
39
|
+
U+1F300-1F5FF, U+1F600-1F64F, U+1F650-1F67F, U+1F680-1F6FF,
|
|
40
|
+
U+1F700-1F77F, U+1F780-1F7FF, U+1F800-1F8FF, U+1F900-1F9FF,
|
|
41
|
+
U+1FA00-1FA6F, U+1FA70-1FAFF, U+2600-26FF, U+2700-27BF,
|
|
42
|
+
U+FE00-FE0F, U+1F1E6-1F1FF;
|
|
43
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""D3 formatting utilities for value display.
|
|
2
|
+
|
|
3
|
+
Stage: RENDER
|
|
4
|
+
Purpose: Resolve format aliases from the theme cascade and format values
|
|
5
|
+
using D3-style format strings.
|
|
6
|
+
|
|
7
|
+
The format alias vocabulary lives in theme YAML (style.formats). Callers
|
|
8
|
+
pass compiled_style.formats to resolve_format(); unknown keys and raw d3
|
|
9
|
+
specs pass through unchanged for d3 to parse.
|
|
10
|
+
|
|
11
|
+
The d3-format spec parsing and number formatting is delegated to the
|
|
12
|
+
``d3_format`` library (``libs/d3-format/``), which implements byte-for-byte
|
|
13
|
+
parity with d3.js for the supported type subset.
|
|
14
|
+
|
|
15
|
+
Dataface-specific extensions over d3-format:
|
|
16
|
+
- ``analytic`` notation: SI ``k/M/G/T`` suffixes mapped to ``K/M/B/T`` with a
|
|
17
|
+
space separator (e.g. d3's ``"1.5G"`` becomes ``"1.5 B"``).
|
|
18
|
+
- ``narrative`` notation: ``k/M/G/T`` mapped to ``k/mn/bn/tr`` (no space,
|
|
19
|
+
journalistic abbreviations — not part of the d3-format standard).
|
|
20
|
+
- ``None`` value: rendered as ``"—"`` (em dash) regardless of spec.
|
|
21
|
+
|
|
22
|
+
See also:
|
|
23
|
+
- https://d3js.org/d3-format
|
|
24
|
+
- vega_lite.py: Uses these utilities for KPI chart formatting
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from d3_format import format as _d3_format, parse as _d3_parse
|
|
30
|
+
|
|
31
|
+
# Import FormatConfig from types to avoid duplication
|
|
32
|
+
# Re-export for convenience
|
|
33
|
+
from dataface.core.compile.models.primitives import FormatConfig
|
|
34
|
+
|
|
35
|
+
# ============================================================================
|
|
36
|
+
# NATIVE (NON-D3) FORMATTERS
|
|
37
|
+
# ============================================================================
|
|
38
|
+
# These handle format aliases that cannot be expressed as D3 format strings.
|
|
39
|
+
# Keyed by the alias name (same string an author writes in YAML format: field).
|
|
40
|
+
# Checked in format_value / format_kpi_parts before the D3 path.
|
|
41
|
+
#
|
|
42
|
+
# percent_number / percent_number_delta: the value IS the whole-number percent
|
|
43
|
+
# (12.8 means 12.8%). D3's "%" type multiplies by 100, so it can't express this.
|
|
44
|
+
|
|
45
|
+
_NATIVE_FORMATTERS: dict[str, Any] = {
|
|
46
|
+
"percent_number": lambda v: "—" if v is None else f"{float(v):.1f}%",
|
|
47
|
+
"percent_number_delta": lambda v: "—" if v is None else f"{float(v):+.1f}%",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# ============================================================================
|
|
51
|
+
# FORMAT RESOLUTION
|
|
52
|
+
# ============================================================================
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def resolve_format(
|
|
56
|
+
format_input: str | FormatConfig | dict[str, Any] | None,
|
|
57
|
+
formats: dict[str, str] | None = None,
|
|
58
|
+
) -> str:
|
|
59
|
+
"""Convert format input to D3 format string.
|
|
60
|
+
|
|
61
|
+
Resolution order:
|
|
62
|
+
1. formats[key] — theme/face-defined alias lookup (key-sensitive)
|
|
63
|
+
2. Raw d3 passthrough — author typed a literal d3 spec
|
|
64
|
+
|
|
65
|
+
If an alias key is not in formats and is not a valid d3 spec, d3 itself
|
|
66
|
+
will raise at render time. That is the intended contract.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
format_input: Format specification (string, FormatConfig, dict, or None)
|
|
70
|
+
formats: The resolved format alias dict from the theme cascade
|
|
71
|
+
(compiled_style.formats). None = no aliases defined.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
D3 format string, or "" for null/empty input.
|
|
75
|
+
"""
|
|
76
|
+
if format_input is None:
|
|
77
|
+
return ""
|
|
78
|
+
|
|
79
|
+
# Extract spec from FormatConfig or dict
|
|
80
|
+
if isinstance(format_input, FormatConfig):
|
|
81
|
+
format_str = format_input.spec or ""
|
|
82
|
+
elif isinstance(format_input, dict):
|
|
83
|
+
format_str = format_input.get("spec", "")
|
|
84
|
+
else:
|
|
85
|
+
format_str = str(format_input)
|
|
86
|
+
|
|
87
|
+
if not format_str:
|
|
88
|
+
return ""
|
|
89
|
+
|
|
90
|
+
# Theme/face alias lookup (case-sensitive, key-exact)
|
|
91
|
+
if formats and format_str in formats:
|
|
92
|
+
return formats[format_str]
|
|
93
|
+
|
|
94
|
+
# Raw d3 passthrough — let d3 parse or raise
|
|
95
|
+
return format_str
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_format_prefix_suffix(
|
|
99
|
+
format_input: str | FormatConfig | dict[str, Any] | None,
|
|
100
|
+
) -> tuple[str, str]:
|
|
101
|
+
"""Extract prefix and suffix from format configuration.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
format_input: Format specification
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Tuple of (prefix, suffix) strings
|
|
108
|
+
"""
|
|
109
|
+
if format_input is None:
|
|
110
|
+
return "", ""
|
|
111
|
+
|
|
112
|
+
if isinstance(format_input, FormatConfig):
|
|
113
|
+
return format_input.prefix or "", format_input.suffix or ""
|
|
114
|
+
elif isinstance(format_input, dict):
|
|
115
|
+
return format_input.get("prefix", ""), format_input.get("suffix", "")
|
|
116
|
+
|
|
117
|
+
# String format has no prefix/suffix
|
|
118
|
+
return "", ""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _get_notation(
|
|
122
|
+
format_input: str | FormatConfig | dict[str, Any] | None,
|
|
123
|
+
) -> str | None:
|
|
124
|
+
"""Extract notation family from format configuration."""
|
|
125
|
+
if isinstance(format_input, FormatConfig):
|
|
126
|
+
return format_input.notation
|
|
127
|
+
elif isinstance(format_input, dict):
|
|
128
|
+
return format_input.get("notation")
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ============================================================================
|
|
133
|
+
# D3 FORMATTING IMPLEMENTATION
|
|
134
|
+
# ============================================================================
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def format_d3(
|
|
138
|
+
value: int | float | None,
|
|
139
|
+
format_spec: str,
|
|
140
|
+
prefix: str = "",
|
|
141
|
+
suffix: str = "",
|
|
142
|
+
notation: str | None = None,
|
|
143
|
+
) -> str:
|
|
144
|
+
"""Format a value using D3-style format specification.
|
|
145
|
+
|
|
146
|
+
Delegates to ``libs/d3-format/`` for spec parsing and formatting.
|
|
147
|
+
Applies Dataface-specific analytic/narrative notation post-processing for
|
|
148
|
+
SI (``s``-type) specs after the lib produces d3-standard output.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
value: Numeric value to format
|
|
152
|
+
format_spec: D3 format string (e.g., "$,.2f", ".1~%", "~s")
|
|
153
|
+
prefix: Custom prefix to prepend
|
|
154
|
+
suffix: Custom suffix to append
|
|
155
|
+
notation: SI-prefix notation family ("analytic" or "narrative")
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Formatted string
|
|
159
|
+
"""
|
|
160
|
+
if value is None:
|
|
161
|
+
return f"{prefix}—{suffix}"
|
|
162
|
+
|
|
163
|
+
if not format_spec:
|
|
164
|
+
return f"{prefix}{value}{suffix}"
|
|
165
|
+
|
|
166
|
+
formatted = _d3_format(format_spec)(float(value))
|
|
167
|
+
|
|
168
|
+
# Dataface-specific SI notation post-processing.
|
|
169
|
+
# d3 emits SI standard: k/M/G/T with no space. Dataface surfaces show
|
|
170
|
+
# K/M/B/T with a space (analytic) or k/mn/bn/tr (narrative).
|
|
171
|
+
if _is_si_spec(format_spec):
|
|
172
|
+
effective_notation = notation or "analytic"
|
|
173
|
+
if effective_notation == "analytic":
|
|
174
|
+
formatted = _apply_analytic_notation(formatted)
|
|
175
|
+
elif effective_notation == "narrative":
|
|
176
|
+
formatted = _apply_narrative_notation(formatted)
|
|
177
|
+
else:
|
|
178
|
+
raise ValueError(f"Unknown notation: {effective_notation!r}")
|
|
179
|
+
|
|
180
|
+
return f"{prefix}{formatted}{suffix}"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _is_si_spec(spec: str) -> bool:
|
|
184
|
+
"""True when spec's type character is 's' (SI prefix)."""
|
|
185
|
+
return _d3_parse(spec).type == "s"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# d3 SI prefix → Dataface analytic suffix mapping
|
|
189
|
+
_D3_TO_ANALYTIC: dict[str, str] = {
|
|
190
|
+
"k": " K",
|
|
191
|
+
"M": " M",
|
|
192
|
+
"G": " B",
|
|
193
|
+
"T": " T",
|
|
194
|
+
"P": " P",
|
|
195
|
+
"E": " E",
|
|
196
|
+
"Z": " Z",
|
|
197
|
+
"Y": " Y",
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
# d3 SI prefix → Dataface narrative suffix mapping
|
|
201
|
+
_D3_TO_NARRATIVE: dict[str, str] = {
|
|
202
|
+
"k": "k",
|
|
203
|
+
"M": "mn",
|
|
204
|
+
"G": "bn",
|
|
205
|
+
"T": "tr",
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _apply_analytic_notation(d3_str: str) -> str:
|
|
210
|
+
"""Convert d3 SI string (e.g. '1.5M') to analytic notation (e.g. '1.5 M')."""
|
|
211
|
+
for d3_sfx, analytic_sfx in _D3_TO_ANALYTIC.items():
|
|
212
|
+
if d3_str.endswith(d3_sfx):
|
|
213
|
+
return d3_str[: -len(d3_sfx)] + analytic_sfx
|
|
214
|
+
return d3_str
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _apply_narrative_notation(d3_str: str) -> str:
|
|
218
|
+
"""Convert d3 SI string (e.g. '1.5G') to narrative notation (e.g. '1.5bn')."""
|
|
219
|
+
for d3_sfx, narrative_sfx in _D3_TO_NARRATIVE.items():
|
|
220
|
+
if d3_str.endswith(d3_sfx):
|
|
221
|
+
return d3_str[: -len(d3_sfx)] + narrative_sfx
|
|
222
|
+
return d3_str
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def format_value(
|
|
226
|
+
value: int | float | None,
|
|
227
|
+
format_input: str | FormatConfig | dict[str, Any] | None,
|
|
228
|
+
formats: dict[str, str] | None = None,
|
|
229
|
+
) -> str:
|
|
230
|
+
"""Format a value using the given format configuration.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
value: Numeric value to format
|
|
234
|
+
format_input: Format specification (string, FormatConfig, dict, or None)
|
|
235
|
+
formats: Theme format alias dict from compiled_style.formats
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Formatted string
|
|
239
|
+
"""
|
|
240
|
+
format_spec = resolve_format(format_input, formats)
|
|
241
|
+
if format_spec in _NATIVE_FORMATTERS:
|
|
242
|
+
return _NATIVE_FORMATTERS[format_spec](value)
|
|
243
|
+
prefix, suffix = get_format_prefix_suffix(format_input)
|
|
244
|
+
notation = _get_notation(format_input)
|
|
245
|
+
|
|
246
|
+
return format_d3(value, format_spec, prefix, suffix, notation=notation)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def format_kpi_parts(
|
|
250
|
+
value: int | float | None,
|
|
251
|
+
format_input: str | FormatConfig | dict[str, Any] | None,
|
|
252
|
+
formats: dict[str, str] | None = None,
|
|
253
|
+
) -> tuple[str, str, str]:
|
|
254
|
+
"""Format a KPI value into (prefix, number, suffix) parts.
|
|
255
|
+
|
|
256
|
+
Separates prefix symbols ($), magnitude suffixes (M, K, bn), and unit
|
|
257
|
+
suffixes (%, USD) from the formatted number so they can be rendered or
|
|
258
|
+
positioned independently.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
value: Numeric value to format
|
|
262
|
+
format_input: Format specification
|
|
263
|
+
formats: Theme format alias dict from compiled_style.formats
|
|
264
|
+
"""
|
|
265
|
+
if value is None:
|
|
266
|
+
return "", "—", ""
|
|
267
|
+
|
|
268
|
+
explicit_prefix, explicit_suffix = get_format_prefix_suffix(format_input)
|
|
269
|
+
format_spec = resolve_format(format_input, formats)
|
|
270
|
+
|
|
271
|
+
# Native formatters bypass D3: split formatted string into number + % suffix.
|
|
272
|
+
if format_spec in _NATIVE_FORMATTERS:
|
|
273
|
+
formatted = _NATIVE_FORMATTERS[format_spec](value)
|
|
274
|
+
if formatted.endswith("%"):
|
|
275
|
+
return "", formatted[:-1], "%"
|
|
276
|
+
return "", formatted, ""
|
|
277
|
+
|
|
278
|
+
d3_prefix = ""
|
|
279
|
+
d3_suffix = ""
|
|
280
|
+
magnitude_suffix = ""
|
|
281
|
+
|
|
282
|
+
if format_spec:
|
|
283
|
+
# Pull $ out of the D3 spec so it renders as a separate element
|
|
284
|
+
if "$" in format_spec:
|
|
285
|
+
d3_prefix = "$"
|
|
286
|
+
format_spec_clean = format_spec.replace("$", "")
|
|
287
|
+
else:
|
|
288
|
+
format_spec_clean = format_spec
|
|
289
|
+
|
|
290
|
+
notation = _get_notation(format_input)
|
|
291
|
+
|
|
292
|
+
# SI/compact: split magnitude suffix into its own field
|
|
293
|
+
if _is_si_spec(format_spec_clean):
|
|
294
|
+
# Format via the lib (without notation post-process), then split suffix
|
|
295
|
+
raw = _d3_format(format_spec_clean)(float(value))
|
|
296
|
+
number_str = raw
|
|
297
|
+
effective_notation = notation or "analytic"
|
|
298
|
+
if effective_notation == "analytic":
|
|
299
|
+
for d3_sfx, analytic_sfx in _D3_TO_ANALYTIC.items():
|
|
300
|
+
if raw.endswith(d3_sfx):
|
|
301
|
+
number_str = raw[: -len(d3_sfx)]
|
|
302
|
+
magnitude_suffix = analytic_sfx.strip()
|
|
303
|
+
break
|
|
304
|
+
elif effective_notation == "narrative":
|
|
305
|
+
for d3_sfx, narrative_sfx in _D3_TO_NARRATIVE.items():
|
|
306
|
+
if raw.endswith(d3_sfx):
|
|
307
|
+
number_str = raw[: -len(d3_sfx)]
|
|
308
|
+
magnitude_suffix = narrative_sfx
|
|
309
|
+
break
|
|
310
|
+
else:
|
|
311
|
+
raise ValueError(f"Unknown notation: {effective_notation!r}")
|
|
312
|
+
else:
|
|
313
|
+
number_str = format_d3(value, format_spec_clean, notation=notation)
|
|
314
|
+
|
|
315
|
+
# Pull trailing % that format_d3 appended
|
|
316
|
+
if "%" in format_spec and number_str.endswith("%"):
|
|
317
|
+
d3_suffix = "%"
|
|
318
|
+
number_str = number_str[:-1]
|
|
319
|
+
else:
|
|
320
|
+
v = float(value)
|
|
321
|
+
if v == int(v) and abs(v) < 1e15:
|
|
322
|
+
number_str = f"{int(v):,}"
|
|
323
|
+
else:
|
|
324
|
+
number_str = f"{v:,.2f}"
|
|
325
|
+
|
|
326
|
+
prefix = ((explicit_prefix or "") + d3_prefix).strip()
|
|
327
|
+
suffix = (magnitude_suffix + d3_suffix + (explicit_suffix or "").strip()).strip()
|
|
328
|
+
|
|
329
|
+
return prefix, number_str.strip(), suffix
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
geo_sources:
|
|
2
|
+
us-states:
|
|
3
|
+
url: "https://vega.github.io/vega-datasets/data/us-10m.json"
|
|
4
|
+
format: topojson
|
|
5
|
+
feature: states
|
|
6
|
+
projection: albersUsa
|
|
7
|
+
key: id
|
|
8
|
+
|
|
9
|
+
us-counties:
|
|
10
|
+
url: "https://vega.github.io/vega-datasets/data/us-10m.json"
|
|
11
|
+
format: topojson
|
|
12
|
+
feature: counties
|
|
13
|
+
projection: albersUsa
|
|
14
|
+
key: id
|
|
15
|
+
|
|
16
|
+
world-countries:
|
|
17
|
+
url: "https://vega.github.io/vega-datasets/data/world-110m.json"
|
|
18
|
+
format: topojson
|
|
19
|
+
feature: countries
|
|
20
|
+
projection: equalEarth
|
|
21
|
+
key: id
|
|
22
|
+
|
|
23
|
+
world-50m:
|
|
24
|
+
url: "https://vega.github.io/vega-datasets/data/world-50m.json"
|
|
25
|
+
format: topojson
|
|
26
|
+
feature: countries
|
|
27
|
+
projection: equalEarth
|
|
28
|
+
key: id
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""JSON render output format.
|
|
2
|
+
|
|
3
|
+
Walks the layout tree, executes queries, resolves charts, and returns
|
|
4
|
+
the resolved objects plus executed data as a JSON string.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import dataclasses
|
|
8
|
+
import json
|
|
9
|
+
from collections.abc import Mapping
|
|
10
|
+
from datetime import date, datetime
|
|
11
|
+
from decimal import Decimal
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel
|
|
15
|
+
|
|
16
|
+
from dataface.core.compile.errors import DatafaceError
|
|
17
|
+
from dataface.core.compile.models.face.compiled import Face, VariableValues
|
|
18
|
+
from dataface.core.errors import DF_RENDER_INTERNAL, StructuredError
|
|
19
|
+
from dataface.core.execute.executor import Executor
|
|
20
|
+
from dataface.core.render.chart.pipeline import resolve_chart
|
|
21
|
+
from dataface.core.render.utils import resolve_field_names
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _json_default(obj: Any) -> Any:
|
|
25
|
+
"""Handle non-serializable types."""
|
|
26
|
+
if isinstance(obj, BaseModel):
|
|
27
|
+
return obj.model_dump(exclude_none=True)
|
|
28
|
+
if isinstance(obj, Mapping):
|
|
29
|
+
return dict(obj)
|
|
30
|
+
if isinstance(obj, set):
|
|
31
|
+
return sorted(obj)
|
|
32
|
+
if isinstance(obj, Decimal):
|
|
33
|
+
return str(obj)
|
|
34
|
+
if isinstance(obj, (date, datetime)):
|
|
35
|
+
return obj.isoformat()
|
|
36
|
+
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _resolved_chart_to_dict(resolved: Any) -> dict[str, Any]:
|
|
40
|
+
"""Convert a ResolvedChart dataclass to a dict, skipping source_chart."""
|
|
41
|
+
d = dataclasses.asdict(resolved)
|
|
42
|
+
d.pop("source_chart", None)
|
|
43
|
+
return d
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _render_chart_item(
|
|
47
|
+
chart: Any,
|
|
48
|
+
executor: Executor,
|
|
49
|
+
variables: VariableValues,
|
|
50
|
+
error_collector: list[StructuredError] | None = None,
|
|
51
|
+
) -> dict[str, Any]:
|
|
52
|
+
"""Execute, resolve, and serialize a single chart item."""
|
|
53
|
+
from dataface.core.execute.errors import ExecutionError
|
|
54
|
+
from dataface.core.render.errors import RenderError
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
data = executor.execute_chart(chart, variables)
|
|
58
|
+
resolved_chart = resolve_field_names(chart, data)
|
|
59
|
+
col_descs = (
|
|
60
|
+
executor.get_column_descriptions(chart.query_name)
|
|
61
|
+
if chart.query_name
|
|
62
|
+
else None
|
|
63
|
+
)
|
|
64
|
+
presentation = resolve_chart(resolved_chart, data, col_descs)
|
|
65
|
+
return {
|
|
66
|
+
"type": "chart",
|
|
67
|
+
"chart": _resolved_chart_to_dict(presentation),
|
|
68
|
+
"data": data,
|
|
69
|
+
}
|
|
70
|
+
except (RenderError, ExecutionError, DatafaceError) as e:
|
|
71
|
+
e.fields["chart_id"] = chart.id
|
|
72
|
+
structured = e.to_structured()
|
|
73
|
+
if error_collector is not None:
|
|
74
|
+
error_collector.append(structured)
|
|
75
|
+
return {"type": "chart", "_error": structured.model_dump(exclude_none=True)}
|
|
76
|
+
except Exception as e: # noqa: BLE001
|
|
77
|
+
wrapped = RenderError.from_code(DF_RENDER_INTERNAL, inner_message=str(e))
|
|
78
|
+
wrapped.fields["chart_id"] = chart.id
|
|
79
|
+
structured = wrapped.to_structured()
|
|
80
|
+
if error_collector is not None:
|
|
81
|
+
error_collector.append(structured)
|
|
82
|
+
return {"type": "chart", "_error": structured.model_dump(exclude_none=True)}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _render_face_items(
|
|
86
|
+
face: Face,
|
|
87
|
+
executor: Executor,
|
|
88
|
+
variables: VariableValues,
|
|
89
|
+
error_collector: list[StructuredError] | None = None,
|
|
90
|
+
) -> list[dict[str, Any]]:
|
|
91
|
+
"""Walk layout items and serialize each."""
|
|
92
|
+
results: list[dict[str, Any]] = []
|
|
93
|
+
for item in face.layout.items:
|
|
94
|
+
if item.type == "chart" and item.chart:
|
|
95
|
+
results.append(
|
|
96
|
+
_render_chart_item(item.chart, executor, variables, error_collector)
|
|
97
|
+
)
|
|
98
|
+
elif item.type == "face" and item.face:
|
|
99
|
+
results.append(
|
|
100
|
+
{
|
|
101
|
+
"type": "face",
|
|
102
|
+
"face": face_to_dict(
|
|
103
|
+
item.face, executor, variables, error_collector
|
|
104
|
+
),
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
return results
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def face_to_dict(
|
|
111
|
+
face: Face,
|
|
112
|
+
executor: Executor,
|
|
113
|
+
variables: VariableValues,
|
|
114
|
+
error_collector: list[StructuredError] | None = None,
|
|
115
|
+
) -> dict[str, Any]:
|
|
116
|
+
"""Convert a face to a JSON-serializable dict."""
|
|
117
|
+
result: dict[str, Any] = {
|
|
118
|
+
"id": face.id,
|
|
119
|
+
"title": face.title,
|
|
120
|
+
"items": _render_face_items(face, executor, variables, error_collector),
|
|
121
|
+
}
|
|
122
|
+
if face.variables:
|
|
123
|
+
result["variables"] = {
|
|
124
|
+
name: var.model_dump(exclude_none=True)
|
|
125
|
+
for name, var in face.variables.items()
|
|
126
|
+
}
|
|
127
|
+
return result
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def render_face_json(
|
|
131
|
+
face: Face,
|
|
132
|
+
executor: Executor,
|
|
133
|
+
variables: VariableValues,
|
|
134
|
+
error_collector: list[StructuredError] | None = None,
|
|
135
|
+
) -> str:
|
|
136
|
+
"""Render a compiled face to JSON.
|
|
137
|
+
|
|
138
|
+
Walks the layout tree, executes queries, resolves charts
|
|
139
|
+
(auto type, auto fields), and returns a JSON string with
|
|
140
|
+
the resolved chart semantics and executed data.
|
|
141
|
+
"""
|
|
142
|
+
return json.dumps(
|
|
143
|
+
face_to_dict(face, executor, variables, error_collector),
|
|
144
|
+
default=_json_default,
|
|
145
|
+
indent=2,
|
|
146
|
+
)
|