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,700 @@
|
|
|
1
|
+
"""Geographic and map chart Vega-Lite spec generators."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from dataface.core.compile.chart_resolved import ResolvedChart, effective_color_field
|
|
8
|
+
from dataface.core.compile.models.style.merged import MergedStyle, resolve_mark
|
|
9
|
+
from dataface.core.compile.models.vega_lite.contracts import Projection
|
|
10
|
+
from dataface.core.render.chart.presentation import to_plain_dict
|
|
11
|
+
from dataface.core.render.chart.spec_builders import (
|
|
12
|
+
new_chart_spec,
|
|
13
|
+
set_chart_dimensions,
|
|
14
|
+
set_chart_title,
|
|
15
|
+
tooltip_entry,
|
|
16
|
+
)
|
|
17
|
+
from dataface.core.render.chart.type_inference import infer_vega_type_from_data
|
|
18
|
+
from dataface.core.render.format_utils import resolve_format
|
|
19
|
+
from dataface.core.render.text.case import format_display_text
|
|
20
|
+
from dataface.core.render.utils import normalize_data_types, slug_to_text
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _add_data_join(
|
|
24
|
+
spec: dict[str, Any],
|
|
25
|
+
data: list[dict[str, Any]],
|
|
26
|
+
geo_key: str,
|
|
27
|
+
lookup_field: str | None,
|
|
28
|
+
value_field: str | None,
|
|
29
|
+
handle_missing: bool = False,
|
|
30
|
+
default_value: Any | None = None,
|
|
31
|
+
) -> dict[str, Any]:
|
|
32
|
+
"""Add lookup transform to join user data to geographic features.
|
|
33
|
+
|
|
34
|
+
This function creates a Vega-Lite lookup transform that joins user data
|
|
35
|
+
to geographic features (states, countries, etc.) based on matching keys.
|
|
36
|
+
|
|
37
|
+
The geo data (from URL) becomes the primary data source, and user data
|
|
38
|
+
is joined via lookup. This ensures all geographic regions are displayed,
|
|
39
|
+
with unmatched regions showing null values.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
spec: Vega-Lite spec to modify (will be modified in place)
|
|
43
|
+
data: User data to join to geographic features
|
|
44
|
+
geo_key: Key field in geographic data (e.g., "id" in TopoJSON)
|
|
45
|
+
lookup_field: Key field in user data to match against geo_key
|
|
46
|
+
value_field: Field in user data to use for encoding (color, etc.)
|
|
47
|
+
handle_missing: If True, add handling for missing/unmatched data
|
|
48
|
+
default_value: Default value for unmatched geographic features
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Modified Vega-Lite spec with lookup transform added
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
>>> spec = {"data": {"url": "us-10m.json"}}
|
|
55
|
+
>>> data = [{"state_code": "06", "population": 39500000}]
|
|
56
|
+
>>> result = _add_data_join(spec, data, "id", "state_code", "population")
|
|
57
|
+
>>> result["transform"][0]["lookup"]
|
|
58
|
+
'id'
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
# Return spec unchanged if no data or no lookup field
|
|
62
|
+
if not data or not lookup_field:
|
|
63
|
+
return spec
|
|
64
|
+
|
|
65
|
+
# Get all fields from data to include in lookup
|
|
66
|
+
data_fields = list(data[0].keys()) if data else []
|
|
67
|
+
|
|
68
|
+
# Create lookup transform
|
|
69
|
+
lookup_transform: dict[str, Any] = {
|
|
70
|
+
"lookup": geo_key,
|
|
71
|
+
"from": {
|
|
72
|
+
"data": {"values": data},
|
|
73
|
+
"key": lookup_field,
|
|
74
|
+
"fields": data_fields,
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Initialize transforms list if not present
|
|
79
|
+
if "transform" not in spec:
|
|
80
|
+
spec["transform"] = []
|
|
81
|
+
|
|
82
|
+
# Add the lookup transform
|
|
83
|
+
spec["transform"].insert(0, lookup_transform)
|
|
84
|
+
|
|
85
|
+
# Handle missing/unmatched data if requested
|
|
86
|
+
if handle_missing and default_value is not None and value_field:
|
|
87
|
+
# Add a calculate transform to replace nulls with default value
|
|
88
|
+
# This ensures unmatched regions show with the default value
|
|
89
|
+
calculate_transform = {
|
|
90
|
+
"calculate": f"datum.{value_field} === null ? {default_value} : datum.{value_field}",
|
|
91
|
+
"as": f"{value_field}_filled",
|
|
92
|
+
}
|
|
93
|
+
spec["transform"].append(calculate_transform)
|
|
94
|
+
|
|
95
|
+
return spec
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ============================================================================
|
|
99
|
+
# GEOGRAPHIC DATA SOURCES
|
|
100
|
+
# ============================================================================
|
|
101
|
+
# Built-in geographic data sources from Vega Datasets CDN
|
|
102
|
+
|
|
103
|
+
GEO_SOURCES: dict[str, dict[str, Any]] = {
|
|
104
|
+
# US boundaries
|
|
105
|
+
"us-states": {
|
|
106
|
+
"url": "https://vega.github.io/vega-datasets/data/us-10m.json",
|
|
107
|
+
"format": {"type": "topojson", "feature": "states"},
|
|
108
|
+
"projection": "albersUsa",
|
|
109
|
+
"key": "id",
|
|
110
|
+
},
|
|
111
|
+
"us-counties": {
|
|
112
|
+
"url": "https://vega.github.io/vega-datasets/data/us-10m.json",
|
|
113
|
+
"format": {"type": "topojson", "feature": "counties"},
|
|
114
|
+
"projection": "albersUsa",
|
|
115
|
+
"key": "id",
|
|
116
|
+
},
|
|
117
|
+
# World boundaries
|
|
118
|
+
"world-countries": {
|
|
119
|
+
"url": "https://vega.github.io/vega-datasets/data/world-110m.json",
|
|
120
|
+
"format": {"type": "topojson", "feature": "countries"},
|
|
121
|
+
"projection": "equalEarth",
|
|
122
|
+
"key": "id",
|
|
123
|
+
},
|
|
124
|
+
"world-50m": {
|
|
125
|
+
"url": "https://vega.github.io/vega-datasets/data/world-50m.json",
|
|
126
|
+
"format": {"type": "topojson", "feature": "countries"},
|
|
127
|
+
"projection": "equalEarth",
|
|
128
|
+
"key": "id",
|
|
129
|
+
},
|
|
130
|
+
# City neighborhoods - San Francisco
|
|
131
|
+
"sf-neighborhoods": {
|
|
132
|
+
"url": "https://data.sfgov.org/api/geospatial/p5b7-5n3h?method=export&format=GeoJSON",
|
|
133
|
+
"format": {"type": "json", "property": "features"},
|
|
134
|
+
"projection": {
|
|
135
|
+
"type": "mercator",
|
|
136
|
+
"center": [-122.4194, 37.7749],
|
|
137
|
+
"scale": 80000,
|
|
138
|
+
},
|
|
139
|
+
"key": "nhood",
|
|
140
|
+
},
|
|
141
|
+
# City neighborhoods - New York City
|
|
142
|
+
"nyc-neighborhoods": {
|
|
143
|
+
"url": "https://data.cityofnewyork.us/api/geospatial/cpf4-rkhq?method=export&format=GeoJSON",
|
|
144
|
+
"format": {"type": "json", "property": "features"},
|
|
145
|
+
"projection": {
|
|
146
|
+
"type": "mercator",
|
|
147
|
+
"center": [-73.9857, 40.7484],
|
|
148
|
+
"scale": 40000,
|
|
149
|
+
},
|
|
150
|
+
"key": "ntaname",
|
|
151
|
+
},
|
|
152
|
+
# City neighborhoods - Chicago
|
|
153
|
+
"chicago-neighborhoods": {
|
|
154
|
+
"url": "https://data.cityofchicago.org/api/geospatial/bbvz-uum9?method=export&format=GeoJSON",
|
|
155
|
+
"format": {"type": "json", "property": "features"},
|
|
156
|
+
"projection": {
|
|
157
|
+
"type": "mercator",
|
|
158
|
+
"center": [-87.6298, 41.8781],
|
|
159
|
+
"scale": 50000,
|
|
160
|
+
},
|
|
161
|
+
"key": "pri_neigh",
|
|
162
|
+
},
|
|
163
|
+
# City neighborhoods - Los Angeles
|
|
164
|
+
"la-neighborhoods": {
|
|
165
|
+
"url": "https://data.lacity.org/api/geospatial/r3ta-u4d6?method=export&format=GeoJSON",
|
|
166
|
+
"format": {"type": "json", "property": "features"},
|
|
167
|
+
"projection": {
|
|
168
|
+
"type": "mercator",
|
|
169
|
+
"center": [-118.2437, 34.0522],
|
|
170
|
+
"scale": 35000,
|
|
171
|
+
},
|
|
172
|
+
"key": "name",
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# Supported projections with their default settings
|
|
177
|
+
PROJECTIONS: dict[str, dict[str, Any]] = {
|
|
178
|
+
"mercator": {"type": "mercator"},
|
|
179
|
+
"albersUsa": {"type": "albersUsa"},
|
|
180
|
+
"equalEarth": {"type": "equalEarth"},
|
|
181
|
+
"naturalEarth1": {"type": "naturalEarth1"},
|
|
182
|
+
"orthographic": {"type": "orthographic"},
|
|
183
|
+
"stereographic": {"type": "stereographic"},
|
|
184
|
+
"albers": {"type": "albers"},
|
|
185
|
+
"conicEqualArea": {"type": "conicEqualArea"},
|
|
186
|
+
"equirectangular": {"type": "equirectangular"},
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _resolve_geo_source(
|
|
191
|
+
geo_source: str | None,
|
|
192
|
+
geo_feature: str | None = None,
|
|
193
|
+
) -> tuple[str, dict[str, Any], str | dict[str, Any]]:
|
|
194
|
+
"""Resolve a geo source name or URL to URL, format, and default projection."""
|
|
195
|
+
if geo_source in GEO_SOURCES:
|
|
196
|
+
geo_info = GEO_SOURCES[geo_source]
|
|
197
|
+
resolved_feature = geo_feature or geo_info["format"].get("feature")
|
|
198
|
+
geo_format = dict(geo_info["format"])
|
|
199
|
+
if resolved_feature:
|
|
200
|
+
geo_format["feature"] = resolved_feature
|
|
201
|
+
return geo_info["url"], geo_format, geo_info["projection"]
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
geo_source or "",
|
|
205
|
+
{"type": "topojson", "feature": geo_feature or "features"},
|
|
206
|
+
"mercator",
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _resolve_projection(
|
|
211
|
+
projection_config: str | Projection | None,
|
|
212
|
+
default_projection: str | dict[str, Any],
|
|
213
|
+
fallback: str,
|
|
214
|
+
) -> dict[str, Any]:
|
|
215
|
+
"""Resolve the explicit or default projection config."""
|
|
216
|
+
if isinstance(projection_config, str):
|
|
217
|
+
return PROJECTIONS.get(projection_config, {"type": projection_config})
|
|
218
|
+
if isinstance(projection_config, Projection):
|
|
219
|
+
return to_plain_dict(projection_config)
|
|
220
|
+
if isinstance(default_projection, dict):
|
|
221
|
+
return default_projection
|
|
222
|
+
return PROJECTIONS.get(default_projection, {"type": fallback})
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _build_point_position_encoding(
|
|
226
|
+
lat_field: str | None,
|
|
227
|
+
lng_field: str | None,
|
|
228
|
+
) -> dict[str, Any]:
|
|
229
|
+
"""Build latitude and longitude encodings for point maps."""
|
|
230
|
+
encoding: dict[str, Any] = {}
|
|
231
|
+
if lng_field:
|
|
232
|
+
encoding["longitude"] = {"field": lng_field, "type": "quantitative"}
|
|
233
|
+
if lat_field:
|
|
234
|
+
encoding["latitude"] = {"field": lat_field, "type": "quantitative"}
|
|
235
|
+
return encoding
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _build_point_tooltip_fields(
|
|
239
|
+
data: list[dict[str, Any]],
|
|
240
|
+
*,
|
|
241
|
+
lat_field: str | None = None,
|
|
242
|
+
lng_field: str | None = None,
|
|
243
|
+
size_field: str | None = None,
|
|
244
|
+
color_field: str | None = None,
|
|
245
|
+
max_fields: int = 6,
|
|
246
|
+
tooltip_format: str,
|
|
247
|
+
) -> list[dict[str, Any]]:
|
|
248
|
+
"""Build tooltip fields for point and bubble maps."""
|
|
249
|
+
tooltip_fields: list[dict[str, Any]] = []
|
|
250
|
+
|
|
251
|
+
if lat_field:
|
|
252
|
+
tooltip_fields.append(
|
|
253
|
+
tooltip_entry(lat_field, "quantitative", title="Latitude"),
|
|
254
|
+
)
|
|
255
|
+
if lng_field:
|
|
256
|
+
tooltip_fields.append(
|
|
257
|
+
tooltip_entry(lng_field, "quantitative", title="Longitude"),
|
|
258
|
+
)
|
|
259
|
+
if size_field:
|
|
260
|
+
tooltip_fields.append(
|
|
261
|
+
tooltip_entry(
|
|
262
|
+
size_field,
|
|
263
|
+
"quantitative",
|
|
264
|
+
title=slug_to_text(size_field),
|
|
265
|
+
format=tooltip_format,
|
|
266
|
+
),
|
|
267
|
+
)
|
|
268
|
+
if color_field:
|
|
269
|
+
tooltip_fields.append(
|
|
270
|
+
tooltip_entry(
|
|
271
|
+
color_field,
|
|
272
|
+
infer_vega_type_from_data(data, color_field),
|
|
273
|
+
title=slug_to_text(color_field),
|
|
274
|
+
),
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
if data:
|
|
278
|
+
for field in data[0]:
|
|
279
|
+
if field in {lat_field, lng_field, size_field, color_field}:
|
|
280
|
+
continue
|
|
281
|
+
tooltip_fields.append(
|
|
282
|
+
tooltip_entry(
|
|
283
|
+
field,
|
|
284
|
+
infer_vega_type_from_data(data, field),
|
|
285
|
+
title=slug_to_text(field),
|
|
286
|
+
),
|
|
287
|
+
)
|
|
288
|
+
if len(tooltip_fields) >= max_fields:
|
|
289
|
+
break
|
|
290
|
+
|
|
291
|
+
return tooltip_fields
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _generate_map_spec(
|
|
295
|
+
chart: ResolvedChart,
|
|
296
|
+
data: list[dict[str, Any]],
|
|
297
|
+
chart_type: str,
|
|
298
|
+
width: float | None = None,
|
|
299
|
+
height: float | None = None,
|
|
300
|
+
*,
|
|
301
|
+
board_style: MergedStyle,
|
|
302
|
+
) -> dict[str, Any]:
|
|
303
|
+
"""Generate geographic map specification (map, geoshape, etc.).
|
|
304
|
+
|
|
305
|
+
Creates Vega-Lite specs for geographic visualizations including:
|
|
306
|
+
- Filled-region maps (colored by data)
|
|
307
|
+
- Base geoshape maps (outline only)
|
|
308
|
+
|
|
309
|
+
Uses the _add_data_join() helper for consistent data joining across
|
|
310
|
+
all map types.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
chart: Chart definition with geo configuration
|
|
314
|
+
data: List of data points to join to geographic features
|
|
315
|
+
chart_type: Type of map ('map', 'geoshape')
|
|
316
|
+
width: Optional explicit width in pixels
|
|
317
|
+
height: Optional explicit height in pixels
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
Vega-Lite specification for geographic visualization
|
|
321
|
+
|
|
322
|
+
"""
|
|
323
|
+
data = normalize_data_types(data)
|
|
324
|
+
map_config = chart.resolved_style.geoshape
|
|
325
|
+
|
|
326
|
+
# Get geo configuration from chart
|
|
327
|
+
geo_config = chart.geo
|
|
328
|
+
geo_source = chart.geo_source
|
|
329
|
+
|
|
330
|
+
# If geo_config is a dict, extract source
|
|
331
|
+
if isinstance(geo_config, dict):
|
|
332
|
+
geo_source = geo_config.get("source", geo_source)
|
|
333
|
+
geo_key = geo_config.get("key", "id")
|
|
334
|
+
geo_feature = geo_config.get("feature", None)
|
|
335
|
+
else:
|
|
336
|
+
geo_key = "id"
|
|
337
|
+
geo_feature = None
|
|
338
|
+
|
|
339
|
+
geo_url, geo_format, default_projection = _resolve_geo_source(
|
|
340
|
+
geo_source,
|
|
341
|
+
geo_feature,
|
|
342
|
+
)
|
|
343
|
+
projection = _resolve_projection(chart.projection, default_projection, "mercator")
|
|
344
|
+
|
|
345
|
+
color_field = effective_color_field(chart)
|
|
346
|
+
value_field: str | None = chart.value or color_field
|
|
347
|
+
lookup_field = chart.lookup
|
|
348
|
+
|
|
349
|
+
# Resolve the geoshape mark (global tier + optional family override).
|
|
350
|
+
charts = chart.resolved_style
|
|
351
|
+
geoshape_mark = resolve_mark(
|
|
352
|
+
charts.marks.geoshape,
|
|
353
|
+
map_config.marks.geoshape,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
# Build the spec
|
|
357
|
+
spec = new_chart_spec(
|
|
358
|
+
data,
|
|
359
|
+
mark={
|
|
360
|
+
"type": "geoshape",
|
|
361
|
+
"stroke": geoshape_mark.stroke.color if geoshape_mark.stroke else None,
|
|
362
|
+
"strokeWidth": geoshape_mark.stroke.width if geoshape_mark.stroke else None,
|
|
363
|
+
"tooltip": True,
|
|
364
|
+
},
|
|
365
|
+
)
|
|
366
|
+
spec["data"] = {"url": geo_url, "format": geo_format}
|
|
367
|
+
spec["projection"] = projection
|
|
368
|
+
set_chart_dimensions(spec, width, height)
|
|
369
|
+
|
|
370
|
+
# If we have data to join, use the _add_data_join helper
|
|
371
|
+
if data and lookup_field and value_field:
|
|
372
|
+
# Use the data join helper for consistent joining logic
|
|
373
|
+
spec = _add_data_join(
|
|
374
|
+
spec=spec,
|
|
375
|
+
data=data,
|
|
376
|
+
geo_key=geo_key,
|
|
377
|
+
lookup_field=lookup_field,
|
|
378
|
+
value_field=value_field,
|
|
379
|
+
handle_missing=True, # Handle missing data gracefully
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# Color scheme: chart-local style.color_scheme is pre-merged into
|
|
383
|
+
# map_config.color_scheme by build_resolved_style.
|
|
384
|
+
spec["encoding"]["color"] = {
|
|
385
|
+
"field": value_field,
|
|
386
|
+
"type": "quantitative",
|
|
387
|
+
"title": format_display_text(
|
|
388
|
+
value_field, from_slug=True, font=chart.resolved_style.legend.title.font
|
|
389
|
+
),
|
|
390
|
+
"scale": {"scheme": map_config.color_scheme},
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
# Build tooltip from data fields
|
|
394
|
+
data_fields = list(data[0].keys()) if data else []
|
|
395
|
+
tooltip_fields: list[dict[str, Any]] = []
|
|
396
|
+
if lookup_field in data_fields:
|
|
397
|
+
tooltip_fields.append(
|
|
398
|
+
tooltip_entry(
|
|
399
|
+
lookup_field,
|
|
400
|
+
"nominal",
|
|
401
|
+
title=slug_to_text(lookup_field),
|
|
402
|
+
),
|
|
403
|
+
)
|
|
404
|
+
if value_field and value_field != lookup_field:
|
|
405
|
+
tooltip_fields.append(
|
|
406
|
+
tooltip_entry(
|
|
407
|
+
value_field,
|
|
408
|
+
"quantitative",
|
|
409
|
+
title=slug_to_text(value_field),
|
|
410
|
+
format=resolve_format(
|
|
411
|
+
chart.resolved_style.tooltip.format,
|
|
412
|
+
chart.resolved_style.formats,
|
|
413
|
+
),
|
|
414
|
+
),
|
|
415
|
+
)
|
|
416
|
+
if tooltip_fields:
|
|
417
|
+
spec["encoding"]["tooltip"] = tooltip_fields
|
|
418
|
+
else:
|
|
419
|
+
# No data - render the base map with a neutral fill. Prefer the
|
|
420
|
+
# theme's header background if set; otherwise fall back to the
|
|
421
|
+
# theme background (universal default has no header fill).
|
|
422
|
+
_ms = board_style
|
|
423
|
+
spec["mark"]["fill"] = _ms.charts.table.header.background or _ms.background
|
|
424
|
+
|
|
425
|
+
set_chart_title(spec, chart.title, chart.subtitle, style=chart.resolved_style)
|
|
426
|
+
|
|
427
|
+
return spec
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def _generate_point_map_spec(
|
|
431
|
+
chart: ResolvedChart,
|
|
432
|
+
data: list[dict[str, Any]],
|
|
433
|
+
chart_type: str,
|
|
434
|
+
width: float | None = None,
|
|
435
|
+
height: float | None = None,
|
|
436
|
+
*,
|
|
437
|
+
board_style: MergedStyle,
|
|
438
|
+
) -> dict[str, Any]:
|
|
439
|
+
"""Generate point/bubble map specification with markers at lat/lng coordinates.
|
|
440
|
+
|
|
441
|
+
Creates Vega-Lite specs for geographic point visualizations including:
|
|
442
|
+
- Point maps (markers at coordinates)
|
|
443
|
+
- Bubble maps (sized markers based on data values)
|
|
444
|
+
|
|
445
|
+
Optionally layers points over a background geoshape (state/country outlines).
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
chart: Chart definition with lat/lng configuration
|
|
449
|
+
data: List of data points with latitude/longitude fields
|
|
450
|
+
chart_type: Type of map ('point_map', 'bubble_map')
|
|
451
|
+
width: Optional explicit width in pixels
|
|
452
|
+
height: Optional explicit height in pixels
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
Vega-Lite specification for point/bubble map
|
|
456
|
+
|
|
457
|
+
"""
|
|
458
|
+
data = normalize_data_types(data)
|
|
459
|
+
|
|
460
|
+
# Get lat/lng field names
|
|
461
|
+
lat_field = chart.latitude
|
|
462
|
+
lng_field = chart.longitude
|
|
463
|
+
|
|
464
|
+
# Fallback to common field names if not specified
|
|
465
|
+
if not lat_field:
|
|
466
|
+
for field in ["latitude", "lat", "Latitude", "Lat", "y"]:
|
|
467
|
+
if data and field in data[0]:
|
|
468
|
+
lat_field = field
|
|
469
|
+
break
|
|
470
|
+
if not lng_field:
|
|
471
|
+
for field in ["longitude", "lng", "lon", "Longitude", "Lng", "Lon", "x"]:
|
|
472
|
+
if data and field in data[0]:
|
|
473
|
+
lng_field = field
|
|
474
|
+
break
|
|
475
|
+
|
|
476
|
+
size_field = chart.size
|
|
477
|
+
color_field = effective_color_field(chart)
|
|
478
|
+
|
|
479
|
+
# Get basemap (tile-layer) configuration
|
|
480
|
+
background_config: dict[str, Any] | None = chart.basemap
|
|
481
|
+
geo_source = chart.geo_source
|
|
482
|
+
_scatter = chart.resolved_style.scatter
|
|
483
|
+
point_mark = resolve_mark(
|
|
484
|
+
chart.resolved_style.marks.point,
|
|
485
|
+
_scatter.marks.point,
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
# Get projection configuration
|
|
489
|
+
# User-provided projection takes precedence, otherwise use geo_source default
|
|
490
|
+
default_projection: str | dict[str, Any] = "albersUsa"
|
|
491
|
+
if geo_source and geo_source in GEO_SOURCES:
|
|
492
|
+
default_projection = GEO_SOURCES[geo_source].get("projection", "albersUsa")
|
|
493
|
+
projection = _resolve_projection(chart.projection, default_projection, "albersUsa")
|
|
494
|
+
|
|
495
|
+
# Determine if we need a layered spec (background + points)
|
|
496
|
+
has_background = background_config or geo_source
|
|
497
|
+
|
|
498
|
+
if has_background:
|
|
499
|
+
# Create layered spec with background geoshape and points
|
|
500
|
+
return _generate_layered_point_map_spec(
|
|
501
|
+
chart,
|
|
502
|
+
data,
|
|
503
|
+
lat_field,
|
|
504
|
+
lng_field,
|
|
505
|
+
size_field,
|
|
506
|
+
color_field,
|
|
507
|
+
projection,
|
|
508
|
+
background_config,
|
|
509
|
+
geo_source,
|
|
510
|
+
width,
|
|
511
|
+
height,
|
|
512
|
+
board_style=board_style,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
# Simple point map without background
|
|
516
|
+
mark_type = "circle"
|
|
517
|
+
|
|
518
|
+
mark_dict: dict[str, Any] = {"type": mark_type, "tooltip": True}
|
|
519
|
+
if point_mark.opacity is not None:
|
|
520
|
+
mark_dict["opacity"] = point_mark.opacity
|
|
521
|
+
spec = new_chart_spec(data, mark=mark_dict)
|
|
522
|
+
spec["projection"] = projection
|
|
523
|
+
spec["encoding"].update(_build_point_position_encoding(lat_field, lng_field))
|
|
524
|
+
set_chart_dimensions(spec, width, height)
|
|
525
|
+
|
|
526
|
+
# Size encoding for bubble maps
|
|
527
|
+
if size_field:
|
|
528
|
+
spec["encoding"]["size"] = {
|
|
529
|
+
"field": size_field,
|
|
530
|
+
"type": "quantitative",
|
|
531
|
+
"title": format_display_text(
|
|
532
|
+
size_field, from_slug=True, font=chart.resolved_style.legend.title.font
|
|
533
|
+
),
|
|
534
|
+
"scale": {"range": [50, 1000]},
|
|
535
|
+
}
|
|
536
|
+
else:
|
|
537
|
+
spec["mark"]["size"] = point_mark.size
|
|
538
|
+
|
|
539
|
+
# Color encoding
|
|
540
|
+
if color_field:
|
|
541
|
+
# Infer type from data
|
|
542
|
+
color_type = infer_vega_type_from_data(data, color_field)
|
|
543
|
+
spec["encoding"]["color"] = {
|
|
544
|
+
"field": color_field,
|
|
545
|
+
"type": color_type,
|
|
546
|
+
"title": format_display_text(
|
|
547
|
+
color_field, from_slug=True, font=chart.resolved_style.legend.title.font
|
|
548
|
+
),
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
tooltip_fields = _build_point_tooltip_fields(
|
|
552
|
+
data,
|
|
553
|
+
lat_field=lat_field,
|
|
554
|
+
lng_field=lng_field,
|
|
555
|
+
size_field=size_field,
|
|
556
|
+
color_field=color_field,
|
|
557
|
+
tooltip_format=resolve_format(
|
|
558
|
+
chart.resolved_style.tooltip.format, chart.resolved_style.formats
|
|
559
|
+
),
|
|
560
|
+
)
|
|
561
|
+
if tooltip_fields:
|
|
562
|
+
spec["encoding"]["tooltip"] = tooltip_fields
|
|
563
|
+
|
|
564
|
+
set_chart_title(spec, chart.title, chart.subtitle, style=chart.resolved_style)
|
|
565
|
+
|
|
566
|
+
return spec
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def _generate_layered_point_map_spec(
|
|
570
|
+
chart: ResolvedChart,
|
|
571
|
+
data: list[dict[str, Any]],
|
|
572
|
+
lat_field: str | None,
|
|
573
|
+
lng_field: str | None,
|
|
574
|
+
size_field: str | None,
|
|
575
|
+
color_field: str | None,
|
|
576
|
+
projection: dict[str, Any],
|
|
577
|
+
background_config: dict[str, Any] | None,
|
|
578
|
+
geo_source: str | None,
|
|
579
|
+
width: float | None = None,
|
|
580
|
+
height: float | None = None,
|
|
581
|
+
*,
|
|
582
|
+
board_style: MergedStyle,
|
|
583
|
+
) -> dict[str, Any]:
|
|
584
|
+
"""Generate layered point map with background geoshape.
|
|
585
|
+
|
|
586
|
+
Creates a Vega-Lite layered spec with:
|
|
587
|
+
- Layer 1: Background geoshape (state/country outlines)
|
|
588
|
+
- Layer 2: Point/circle markers at lat/lng coordinates
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
chart: Chart definition
|
|
592
|
+
data: Point data with lat/lng
|
|
593
|
+
lat_field: Latitude field name
|
|
594
|
+
lng_field: Longitude field name
|
|
595
|
+
size_field: Optional size encoding field
|
|
596
|
+
color_field: Optional color encoding field
|
|
597
|
+
projection: Projection configuration
|
|
598
|
+
background_config: Background layer configuration
|
|
599
|
+
geo_source: Built-in geo source name
|
|
600
|
+
width: Optional width
|
|
601
|
+
height: Optional height
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
Layered Vega-Lite specification
|
|
605
|
+
|
|
606
|
+
"""
|
|
607
|
+
_ms = board_style
|
|
608
|
+
_scatter = chart.resolved_style.scatter
|
|
609
|
+
resolved_point = resolve_mark(
|
|
610
|
+
chart.resolved_style.marks.point,
|
|
611
|
+
_scatter.marks.point,
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
# Determine background geo source. For the fill, fall back to the theme
|
|
615
|
+
# background when the theme doesn't define a header fill (universal
|
|
616
|
+
# default is "header rule only").
|
|
617
|
+
_bg_fill_default = _ms.charts.table.header.background or _ms.background
|
|
618
|
+
if isinstance(background_config, dict):
|
|
619
|
+
bg_source = background_config.get("source", geo_source)
|
|
620
|
+
bg_fill = background_config.get("fill", _bg_fill_default)
|
|
621
|
+
bg_stroke = background_config.get("stroke", _ms.border.color)
|
|
622
|
+
else:
|
|
623
|
+
bg_source = geo_source
|
|
624
|
+
bg_fill = _bg_fill_default
|
|
625
|
+
bg_stroke = _ms.border.color
|
|
626
|
+
|
|
627
|
+
geo_url, geo_format, _ = _resolve_geo_source(bg_source)
|
|
628
|
+
spec = new_chart_spec(data, layer=True)
|
|
629
|
+
spec["projection"] = projection
|
|
630
|
+
set_chart_dimensions(spec, width, height)
|
|
631
|
+
|
|
632
|
+
# Layer 1: Background geoshape
|
|
633
|
+
background_layer: dict[str, Any] = {
|
|
634
|
+
"data": {
|
|
635
|
+
"url": geo_url,
|
|
636
|
+
"format": geo_format,
|
|
637
|
+
},
|
|
638
|
+
"mark": {
|
|
639
|
+
"type": "geoshape",
|
|
640
|
+
"fill": bg_fill,
|
|
641
|
+
"stroke": bg_stroke,
|
|
642
|
+
"strokeWidth": 0.5,
|
|
643
|
+
},
|
|
644
|
+
}
|
|
645
|
+
spec["layer"].append(background_layer)
|
|
646
|
+
|
|
647
|
+
# Layer 2: Points
|
|
648
|
+
mark_type = "circle"
|
|
649
|
+
point_mark: dict[str, Any] = {"type": mark_type, "tooltip": True}
|
|
650
|
+
if resolved_point.opacity is not None:
|
|
651
|
+
point_mark["opacity"] = resolved_point.opacity
|
|
652
|
+
point_layer: dict[str, Any] = {
|
|
653
|
+
"data": {"values": data},
|
|
654
|
+
"mark": point_mark,
|
|
655
|
+
"encoding": _build_point_position_encoding(lat_field, lng_field),
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
# Size encoding
|
|
659
|
+
point_size = resolved_point.size
|
|
660
|
+
if size_field:
|
|
661
|
+
point_layer["encoding"]["size"] = {
|
|
662
|
+
"field": size_field,
|
|
663
|
+
"type": "quantitative",
|
|
664
|
+
"title": format_display_text(
|
|
665
|
+
size_field, from_slug=True, font=chart.resolved_style.legend.title.font
|
|
666
|
+
),
|
|
667
|
+
"scale": {"range": [50, 1000]},
|
|
668
|
+
}
|
|
669
|
+
else:
|
|
670
|
+
# Use point size from config (slightly larger for map overlays)
|
|
671
|
+
point_layer["mark"]["size"] = int(point_size * 1.33)
|
|
672
|
+
|
|
673
|
+
# Color encoding
|
|
674
|
+
if color_field:
|
|
675
|
+
color_type = infer_vega_type_from_data(data, color_field)
|
|
676
|
+
point_layer["encoding"]["color"] = {
|
|
677
|
+
"field": color_field,
|
|
678
|
+
"type": color_type,
|
|
679
|
+
"title": format_display_text(
|
|
680
|
+
color_field, from_slug=True, font=chart.resolved_style.legend.title.font
|
|
681
|
+
),
|
|
682
|
+
}
|
|
683
|
+
else:
|
|
684
|
+
point_layer["mark"]["color"] = chart.resolved_style.palette[0]
|
|
685
|
+
|
|
686
|
+
tooltip_fields = _build_point_tooltip_fields(
|
|
687
|
+
data,
|
|
688
|
+
max_fields=6,
|
|
689
|
+
tooltip_format=resolve_format(
|
|
690
|
+
chart.resolved_style.tooltip.format, chart.resolved_style.formats
|
|
691
|
+
),
|
|
692
|
+
)
|
|
693
|
+
if tooltip_fields:
|
|
694
|
+
point_layer["encoding"]["tooltip"] = tooltip_fields
|
|
695
|
+
|
|
696
|
+
spec["layer"].append(point_layer)
|
|
697
|
+
|
|
698
|
+
set_chart_title(spec, chart.title, chart.subtitle, style=chart.resolved_style)
|
|
699
|
+
|
|
700
|
+
return spec
|