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,370 @@
|
|
|
1
|
+
"""Compact horizontal spark bar chart SVG rendering."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import html as html_module
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from dataface.core.compile.config import get_chart_rendering
|
|
9
|
+
from dataface.core.compile.models.primitives import FontStyle
|
|
10
|
+
from dataface.core.compile.models.style.merged import (
|
|
11
|
+
MergedChartsStyle,
|
|
12
|
+
MergedStyle,
|
|
13
|
+
)
|
|
14
|
+
from dataface.core.compile.typography import chart_title_spec
|
|
15
|
+
from dataface.core.render.utils import normalize_data_types
|
|
16
|
+
|
|
17
|
+
_DEFAULT_WIDTH = 200.0
|
|
18
|
+
_MIN_WIDTH = 150.0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _auto_detect_spark_bar_fields(
|
|
22
|
+
data: list[dict[str, Any]],
|
|
23
|
+
x_field: str | None,
|
|
24
|
+
y_field: str | None,
|
|
25
|
+
) -> tuple[str | None, str | None]:
|
|
26
|
+
"""Auto-detect x (frequency) and y (category) fields for spark bar charts.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
data: List of data rows
|
|
30
|
+
x_field: Explicitly specified x field (frequency)
|
|
31
|
+
y_field: Explicitly specified y field (category)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Tuple of (x_field, y_field) with auto-detected values if not specified
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
if not data:
|
|
38
|
+
return x_field, y_field
|
|
39
|
+
|
|
40
|
+
# Auto-detect y field (category)
|
|
41
|
+
if not y_field:
|
|
42
|
+
for key in data[0]:
|
|
43
|
+
if key.lower() in ("value", "label", "category", "name"):
|
|
44
|
+
y_field = key
|
|
45
|
+
break
|
|
46
|
+
if not y_field:
|
|
47
|
+
# Use first string column as y
|
|
48
|
+
for key, val in data[0].items():
|
|
49
|
+
if isinstance(val, str):
|
|
50
|
+
y_field = key
|
|
51
|
+
break
|
|
52
|
+
|
|
53
|
+
# Auto-detect x field (frequency)
|
|
54
|
+
if not x_field:
|
|
55
|
+
for key in data[0]:
|
|
56
|
+
if key.lower() in ("frequency", "count", "freq", "n", "total"):
|
|
57
|
+
x_field = key
|
|
58
|
+
break
|
|
59
|
+
if not x_field:
|
|
60
|
+
# Use first numeric column as x
|
|
61
|
+
for key, val in data[0].items():
|
|
62
|
+
if isinstance(val, (int, float)) and not isinstance(val, bool):
|
|
63
|
+
x_field = key
|
|
64
|
+
break
|
|
65
|
+
|
|
66
|
+
return x_field, y_field
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _render_spark_bar_row(
|
|
70
|
+
row: dict[str, Any],
|
|
71
|
+
row_index: int,
|
|
72
|
+
row_y: float,
|
|
73
|
+
x_field: str | None,
|
|
74
|
+
y_field: str | None,
|
|
75
|
+
bar_height: int,
|
|
76
|
+
bar_area_width: float,
|
|
77
|
+
max_value: float,
|
|
78
|
+
left_padding: int,
|
|
79
|
+
chart_width: float,
|
|
80
|
+
text_color: str,
|
|
81
|
+
bar_color: str,
|
|
82
|
+
bar_background: str,
|
|
83
|
+
labels_visible: bool,
|
|
84
|
+
counts_visible: bool,
|
|
85
|
+
spark_config: Any,
|
|
86
|
+
spark_rendering: Any,
|
|
87
|
+
font: FontStyle,
|
|
88
|
+
) -> list[str]:
|
|
89
|
+
"""Render a single bar row for spark bar chart.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
row: Data row dict
|
|
93
|
+
row_index: Index of this row (0-based)
|
|
94
|
+
row_y: Y position for this row
|
|
95
|
+
x_field: Field name for frequency/count values
|
|
96
|
+
y_field: Field name for category labels
|
|
97
|
+
bar_height: Height of each bar (may be overridden from default)
|
|
98
|
+
bar_area_width: Width of the bar area
|
|
99
|
+
max_value: Maximum value for scaling bars
|
|
100
|
+
left_padding: Left padding before bar starts
|
|
101
|
+
chart_width: Total chart width
|
|
102
|
+
text_color: Color for text labels
|
|
103
|
+
bar_color: Fill color for bars (may be overridden from default)
|
|
104
|
+
bar_background: Background color for bar track
|
|
105
|
+
labels_visible: Whether to show category labels
|
|
106
|
+
counts_visible: Whether to show count labels
|
|
107
|
+
spark_config: Config object for non-overridable values (border_radius, font sizes, label.width)
|
|
108
|
+
font: FontStyle for text elements (reads .family)
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
List of SVG element strings for this row
|
|
112
|
+
|
|
113
|
+
"""
|
|
114
|
+
svg_parts: list[str] = []
|
|
115
|
+
|
|
116
|
+
# Get values
|
|
117
|
+
label_value = str(row.get(y_field, "")) if y_field else f"Item {row_index + 1}"
|
|
118
|
+
count_value = row.get(x_field, 0) if x_field else 0
|
|
119
|
+
if not isinstance(count_value, (int, float)):
|
|
120
|
+
count_value = 0
|
|
121
|
+
|
|
122
|
+
# Calculate bar width
|
|
123
|
+
bar_width = (float(count_value) / max_value) * bar_area_width
|
|
124
|
+
|
|
125
|
+
# Truncate label if too long (use config for label.width)
|
|
126
|
+
max_label_chars = int(spark_config.label.width / spark_rendering.avg_char_width_px)
|
|
127
|
+
display_label = label_value
|
|
128
|
+
if len(display_label) > max_label_chars:
|
|
129
|
+
display_label = display_label[: max_label_chars - 2] + "..."
|
|
130
|
+
escaped_label = html_module.escape(display_label)
|
|
131
|
+
|
|
132
|
+
# Format count
|
|
133
|
+
if isinstance(count_value, float) and count_value == int(count_value):
|
|
134
|
+
display_count = str(int(count_value))
|
|
135
|
+
elif isinstance(count_value, float):
|
|
136
|
+
display_count = f"{count_value:,.1f}"
|
|
137
|
+
else:
|
|
138
|
+
display_count = f"{count_value:,}"
|
|
139
|
+
|
|
140
|
+
# Render label
|
|
141
|
+
if labels_visible:
|
|
142
|
+
label_y = row_y + (bar_height / 2) + spark_rendering.text_baseline_offset
|
|
143
|
+
svg_parts.append(
|
|
144
|
+
f'<text x="0" y="{label_y:.1f}" '
|
|
145
|
+
f'font-size="{spark_config.font.size}" fill="{text_color}" '
|
|
146
|
+
f'font-family="{font.family}">'
|
|
147
|
+
f"{escaped_label}</text>",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Render bar background
|
|
151
|
+
bar_x = left_padding
|
|
152
|
+
svg_parts.append(
|
|
153
|
+
f'<rect x="{bar_x}" y="{row_y:.1f}" '
|
|
154
|
+
f'width="{bar_area_width}" height="{bar_height}" '
|
|
155
|
+
f'fill="{bar_background}" rx="{spark_config.border.radius}"/>',
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Render bar fill
|
|
159
|
+
if bar_width > 0:
|
|
160
|
+
svg_parts.append(
|
|
161
|
+
f'<rect x="{bar_x}" y="{row_y:.1f}" '
|
|
162
|
+
f'width="{bar_width:.1f}" height="{bar_height}" '
|
|
163
|
+
f'fill="{bar_color}" rx="{spark_config.border.radius}"/>',
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Render count
|
|
167
|
+
if counts_visible:
|
|
168
|
+
count_x = chart_width - spark_rendering.side_padding
|
|
169
|
+
count_y = row_y + (bar_height / 2) + spark_rendering.text_baseline_offset
|
|
170
|
+
svg_parts.append(
|
|
171
|
+
f'<text x="{count_x:.1f}" y="{count_y:.1f}" '
|
|
172
|
+
f'font-size="{spark_config.font.size}" fill="{text_color}" text-anchor="end" '
|
|
173
|
+
f'font-family="{font.family}" '
|
|
174
|
+
f'style="font-variant-numeric: tabular-nums lining-nums;">'
|
|
175
|
+
f"{display_count}</text>",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
return svg_parts
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def render_spark_bar_svg(
|
|
182
|
+
chart: Any,
|
|
183
|
+
data: list[dict[str, Any]],
|
|
184
|
+
width: float | None = None,
|
|
185
|
+
height: float | None = None,
|
|
186
|
+
is_placeholder: bool = False,
|
|
187
|
+
*,
|
|
188
|
+
resolved_style: MergedChartsStyle,
|
|
189
|
+
face_level: int = 1,
|
|
190
|
+
board_style: MergedStyle,
|
|
191
|
+
) -> str:
|
|
192
|
+
"""Render a spark bar chart as SVG.
|
|
193
|
+
|
|
194
|
+
Creates a compact horizontal bar chart for profiler column cards with:
|
|
195
|
+
- Category labels on the left
|
|
196
|
+
- Horizontal bars showing frequency/count
|
|
197
|
+
- Optional count labels on the right
|
|
198
|
+
- Truncated labels for long text
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
chart: Chart definition with x (frequency), y (category) fields
|
|
202
|
+
data: List of dicts containing bar data
|
|
203
|
+
width: Optional explicit width in pixels
|
|
204
|
+
height: Optional explicit height in pixels
|
|
205
|
+
is_placeholder: If True, render with placeholder styling
|
|
206
|
+
face_level: Heading level of the parent face (root=1, nested=2, …).
|
|
207
|
+
Chart title uses face_level + 1.
|
|
208
|
+
board_style: Board-level MergedStyle for theme color reads.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
SVG string representing the spark bar chart
|
|
212
|
+
|
|
213
|
+
"""
|
|
214
|
+
data = normalize_data_types(data)
|
|
215
|
+
|
|
216
|
+
# spark_config comes from resolved_style, which is already merged with any
|
|
217
|
+
# chart-local ChartStylePatch.spark_bar overlay by _build_resolved_style.
|
|
218
|
+
spark_config = resolved_style.spark_bar
|
|
219
|
+
spark_rendering = (
|
|
220
|
+
get_chart_rendering().spark_bar
|
|
221
|
+
) # designer-tunable layout constants
|
|
222
|
+
|
|
223
|
+
assert spark_config.font.family is not None, "style.font.family must be configured"
|
|
224
|
+
text_color = board_style.font.color
|
|
225
|
+
secondary_color = board_style.variables.font.color
|
|
226
|
+
subtitle_text = chart.subtitle or ""
|
|
227
|
+
|
|
228
|
+
# Extract overridable values (post-patch)
|
|
229
|
+
bar_height = spark_config.bar.height
|
|
230
|
+
max_bars = spark_config.max_bars
|
|
231
|
+
labels_visible = spark_config.label.visible
|
|
232
|
+
counts_visible = spark_config.count.visible
|
|
233
|
+
bar_color = spark_config.bar.color
|
|
234
|
+
assert (
|
|
235
|
+
bar_color is not None
|
|
236
|
+
), "spark_bar.bar.color requires resolved style (pass resolved_style)"
|
|
237
|
+
# board_style.variables.input.background carries the active board theme's
|
|
238
|
+
# track color — always use it.
|
|
239
|
+
bar_background = board_style.variables.input.background
|
|
240
|
+
|
|
241
|
+
# Get field names from chart, auto-detecting if not specified
|
|
242
|
+
x_field, y_field = _auto_detect_spark_bar_fields(data, chart.x, chart.y)
|
|
243
|
+
|
|
244
|
+
# Limit data to max_bars
|
|
245
|
+
visible_data = data[:max_bars] if data else []
|
|
246
|
+
|
|
247
|
+
# Calculate dimensions
|
|
248
|
+
num_bars = len(visible_data)
|
|
249
|
+
row_height = bar_height + spark_config.bar.padding
|
|
250
|
+
chart_width = width or _DEFAULT_WIDTH
|
|
251
|
+
chart_width = max(chart_width, _MIN_WIDTH)
|
|
252
|
+
# Width-aware title spec: size, weight, family based on card width
|
|
253
|
+
chart_title_size, chart_title_weight, chart_title_family = chart_title_spec(
|
|
254
|
+
chart_width, level=face_level + 1, resolved_chart_style=resolved_style
|
|
255
|
+
)
|
|
256
|
+
title_height = spark_rendering.title_height if chart.title else 0
|
|
257
|
+
if chart.title and subtitle_text:
|
|
258
|
+
title_height += chart_title_size
|
|
259
|
+
chart_height = height or (
|
|
260
|
+
title_height + (num_bars * row_height) + spark_config.bar.padding
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Calculate bar area dimensions
|
|
264
|
+
left_padding = (
|
|
265
|
+
spark_config.label.width if labels_visible else spark_rendering.side_padding
|
|
266
|
+
)
|
|
267
|
+
right_padding = (
|
|
268
|
+
spark_config.count.width if counts_visible else spark_rendering.side_padding
|
|
269
|
+
)
|
|
270
|
+
bar_area_width = max(chart_width - left_padding - right_padding, 20)
|
|
271
|
+
|
|
272
|
+
# Find max value for scaling
|
|
273
|
+
max_value = 0.0
|
|
274
|
+
if visible_data and x_field:
|
|
275
|
+
for row in visible_data:
|
|
276
|
+
val = row.get(x_field, 0)
|
|
277
|
+
if isinstance(val, (int, float)):
|
|
278
|
+
max_value = max(max_value, float(val))
|
|
279
|
+
if max_value == 0:
|
|
280
|
+
max_value = 1.0 # Prevent division by zero
|
|
281
|
+
|
|
282
|
+
# Build SVG elements
|
|
283
|
+
svg_parts: list[str] = []
|
|
284
|
+
current_y = 0.0
|
|
285
|
+
|
|
286
|
+
# Title
|
|
287
|
+
if chart.title:
|
|
288
|
+
escaped_title = html_module.escape(str(chart.title))
|
|
289
|
+
svg_parts.append(
|
|
290
|
+
f'<text x="0" y="{spark_rendering.title_baseline_y}" '
|
|
291
|
+
f'font-size="{chart_title_size}" font-weight="{chart_title_weight}" fill="{text_color}" '
|
|
292
|
+
f'font-family="{chart_title_family}">'
|
|
293
|
+
f"{escaped_title}</text>",
|
|
294
|
+
)
|
|
295
|
+
if subtitle_text:
|
|
296
|
+
escaped_subtitle = html_module.escape(subtitle_text)
|
|
297
|
+
assert (
|
|
298
|
+
spark_config.subtitle.font.size is not None
|
|
299
|
+
), "theme must supply spark_bar.subtitle.font.size"
|
|
300
|
+
subtitle_font_size = float(spark_config.subtitle.font.size)
|
|
301
|
+
svg_parts.append(
|
|
302
|
+
f'<text x="0" y="{spark_rendering.title_baseline_y + chart_title_size}" '
|
|
303
|
+
f'font-size="{subtitle_font_size}" fill="{secondary_color}" '
|
|
304
|
+
f'font-family="{spark_config.font.family}">'
|
|
305
|
+
f"{escaped_subtitle}</text>",
|
|
306
|
+
)
|
|
307
|
+
current_y = title_height
|
|
308
|
+
|
|
309
|
+
# Render bars
|
|
310
|
+
for i, row in enumerate(visible_data):
|
|
311
|
+
row_y = current_y + (i * row_height)
|
|
312
|
+
svg_parts.extend(
|
|
313
|
+
_render_spark_bar_row(
|
|
314
|
+
row=row,
|
|
315
|
+
row_index=i,
|
|
316
|
+
row_y=row_y,
|
|
317
|
+
x_field=x_field,
|
|
318
|
+
y_field=y_field,
|
|
319
|
+
bar_height=int(bar_height),
|
|
320
|
+
bar_area_width=bar_area_width,
|
|
321
|
+
max_value=max_value,
|
|
322
|
+
left_padding=int(left_padding),
|
|
323
|
+
chart_width=chart_width,
|
|
324
|
+
text_color=text_color,
|
|
325
|
+
bar_color=bar_color,
|
|
326
|
+
bar_background=bar_background,
|
|
327
|
+
labels_visible=labels_visible,
|
|
328
|
+
counts_visible=counts_visible,
|
|
329
|
+
spark_config=spark_config,
|
|
330
|
+
spark_rendering=spark_rendering,
|
|
331
|
+
font=spark_config.font,
|
|
332
|
+
),
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# Show "more" indicator if data was truncated
|
|
336
|
+
if len(data) > len(visible_data):
|
|
337
|
+
more_count = len(data) - len(visible_data)
|
|
338
|
+
more_y = (
|
|
339
|
+
current_y + (num_bars * row_height) + spark_rendering.more_rows_offset_y
|
|
340
|
+
)
|
|
341
|
+
svg_parts.append(
|
|
342
|
+
f'<text x="{chart_width / 2}" y="{more_y:.1f}" '
|
|
343
|
+
f'font-size="{spark_rendering.more_rows_font_size}" fill="{secondary_color}" text-anchor="middle" font-style="italic" '
|
|
344
|
+
f'font-family="{spark_config.font.family}" '
|
|
345
|
+
f'style="font-variant-numeric: tabular-nums lining-nums;">'
|
|
346
|
+
f"+ {more_count} more</text>",
|
|
347
|
+
)
|
|
348
|
+
chart_height = more_y + spark_rendering.more_rows_bottom_padding
|
|
349
|
+
|
|
350
|
+
# Wrap in SVG
|
|
351
|
+
svg_result = f"""<svg xmlns="http://www.w3.org/2000/svg" width="{chart_width}" height="{chart_height}" viewBox="0 0 {chart_width} {chart_height}">
|
|
352
|
+
{"".join(svg_parts)}
|
|
353
|
+
</svg>"""
|
|
354
|
+
|
|
355
|
+
# Apply placeholder styling if needed
|
|
356
|
+
if is_placeholder:
|
|
357
|
+
from dataface.core.render.placeholder import (
|
|
358
|
+
add_placeholder_overlay,
|
|
359
|
+
apply_placeholder_opacity,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
svg_result = apply_placeholder_opacity(svg_result)
|
|
363
|
+
svg_result = add_placeholder_overlay(
|
|
364
|
+
svg_result,
|
|
365
|
+
chart_width,
|
|
366
|
+
chart_height,
|
|
367
|
+
font=spark_config.font,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
return svg_result
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Shared Vega-Lite spec builder helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from dataface.core.compile.config import get_vega_config
|
|
8
|
+
from dataface.core.render.text.case import apply_case
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from dataface.core.compile.models.style.compiled import PaddingStyle
|
|
12
|
+
from dataface.core.compile.models.style.merged import MergedChartsStyle
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def new_chart_spec(
|
|
16
|
+
data: list[dict[str, Any]],
|
|
17
|
+
*,
|
|
18
|
+
mark: dict[str, Any] | None = None,
|
|
19
|
+
layer: bool = False,
|
|
20
|
+
datasets: dict[str, list[dict[str, Any]]] | None = None,
|
|
21
|
+
) -> dict[str, Any]:
|
|
22
|
+
"""Build the common base for Vega-Lite chart specs.
|
|
23
|
+
|
|
24
|
+
When ``datasets`` is provided (per-layer queries), the spec uses
|
|
25
|
+
top-level ``"datasets"`` instead of ``"data": {"values": ...}``.
|
|
26
|
+
Each layer then references its dataset via ``"data": {"name": ...}``.
|
|
27
|
+
"""
|
|
28
|
+
spec: dict[str, Any] = {
|
|
29
|
+
"$schema": get_vega_config().schema,
|
|
30
|
+
"background": None,
|
|
31
|
+
"autosize": {"type": "fit", "contains": "padding"},
|
|
32
|
+
# Zero-padding initial state. The board layer replaces this via padding=
|
|
33
|
+
# additive_padding(card_pad, charts.padding). On direct-render paths (no board
|
|
34
|
+
# layer), bump_padding_bottom() can safely increment the bottom field.
|
|
35
|
+
"padding": {"left": 0, "right": 0, "top": 0, "bottom": 0},
|
|
36
|
+
}
|
|
37
|
+
if datasets is not None:
|
|
38
|
+
spec["datasets"] = datasets
|
|
39
|
+
else:
|
|
40
|
+
spec["data"] = {"values": data}
|
|
41
|
+
if mark is not None:
|
|
42
|
+
spec["mark"] = mark
|
|
43
|
+
if layer:
|
|
44
|
+
spec["layer"] = []
|
|
45
|
+
else:
|
|
46
|
+
spec["encoding"] = {}
|
|
47
|
+
return spec
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def uniform_padding(value: float) -> dict[str, float]:
|
|
51
|
+
"""Return a Vega-Lite padding dict with equal inset on all sides."""
|
|
52
|
+
return {"left": value, "right": value, "top": value, "bottom": value}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def additive_padding(card_pad: float, chart_padding: PaddingStyle) -> dict[str, float]:
|
|
56
|
+
"""Add card_padding to per-chart padding on each side independently.
|
|
57
|
+
|
|
58
|
+
Per-chart padding (style.charts.padding) stacks ON TOP of card_padding so
|
|
59
|
+
author-specified insets compose with the global card layout rather than
|
|
60
|
+
replacing it. The theme default is {0,0,0,0}, making this a no-op for
|
|
61
|
+
boards that don't override per-chart padding.
|
|
62
|
+
"""
|
|
63
|
+
return {
|
|
64
|
+
"left": card_pad + chart_padding.left,
|
|
65
|
+
"right": card_pad + chart_padding.right,
|
|
66
|
+
"top": card_pad + chart_padding.top,
|
|
67
|
+
"bottom": card_pad + chart_padding.bottom,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def bump_padding_bottom(spec: dict[str, Any], add_px: float) -> None:
|
|
72
|
+
"""Increase spec-level `padding.bottom` by add_px in place.
|
|
73
|
+
|
|
74
|
+
Assumes the spec already carries a 4-key padding dict, which is the
|
|
75
|
+
invariant ``new_chart_spec`` (the only Dataface entry point that
|
|
76
|
+
builds chart-body specs) sets. Other sides are left alone.
|
|
77
|
+
"""
|
|
78
|
+
padding = dict(spec["padding"])
|
|
79
|
+
padding["bottom"] = float(padding.get("bottom", 0)) + add_px
|
|
80
|
+
spec["padding"] = padding
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def bump_padding_top(spec: dict[str, Any], add_px: float) -> None:
|
|
84
|
+
"""Increase spec-level `padding.top` by add_px in place.
|
|
85
|
+
|
|
86
|
+
Symmetric counterpart to ``bump_padding_bottom`` — used when a strip
|
|
87
|
+
is attached above the plot (``style.data_table.position: top``).
|
|
88
|
+
"""
|
|
89
|
+
padding = dict(spec["padding"])
|
|
90
|
+
padding["top"] = float(padding.get("top", 0)) + add_px
|
|
91
|
+
spec["padding"] = padding
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def set_chart_title(
|
|
95
|
+
spec: dict[str, Any],
|
|
96
|
+
title: str | None,
|
|
97
|
+
subtitle: str | None = None,
|
|
98
|
+
*,
|
|
99
|
+
style: MergedChartsStyle | None = None,
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Apply a standard title block when a title is present.
|
|
102
|
+
|
|
103
|
+
When *style* is provided, ``style.title.font.case`` is applied to the
|
|
104
|
+
title text. Subtitle is not yet case-transformed here — chart subtitles
|
|
105
|
+
have always been emitted raw.
|
|
106
|
+
"""
|
|
107
|
+
if title and style is not None:
|
|
108
|
+
case = style.title.font.case
|
|
109
|
+
if case is not None and case != "none":
|
|
110
|
+
title = apply_case(title, case)
|
|
111
|
+
if title or subtitle:
|
|
112
|
+
title_block: dict[str, Any] = {"text": title or ""}
|
|
113
|
+
if subtitle:
|
|
114
|
+
title_block["subtitle"] = subtitle
|
|
115
|
+
spec["title"] = title_block
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def set_chart_dimensions(
|
|
119
|
+
spec: dict[str, Any],
|
|
120
|
+
width: float | None = None,
|
|
121
|
+
height: float | None = None,
|
|
122
|
+
*,
|
|
123
|
+
width_offset: float = 0,
|
|
124
|
+
min_width: float | None = None,
|
|
125
|
+
height_offset: float = 0,
|
|
126
|
+
min_height: float | None = None,
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Apply width and height with optional offsets and minimums."""
|
|
129
|
+
if width is not None and width > 0:
|
|
130
|
+
computed_width = width - width_offset
|
|
131
|
+
spec["width"] = max(computed_width, min_width) if min_width else computed_width
|
|
132
|
+
if height is not None and height > 0:
|
|
133
|
+
computed_height = height - height_offset
|
|
134
|
+
spec["height"] = (
|
|
135
|
+
max(computed_height, min_height) if min_height else computed_height
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def tooltip_entry(
|
|
140
|
+
field: str,
|
|
141
|
+
field_type: str,
|
|
142
|
+
*,
|
|
143
|
+
title: str | None = None,
|
|
144
|
+
format: str | None = None,
|
|
145
|
+
**extra: Any,
|
|
146
|
+
) -> dict[str, Any]:
|
|
147
|
+
"""Build a tooltip field definition."""
|
|
148
|
+
entry: dict[str, Any] = {"field": field, "type": field_type}
|
|
149
|
+
if title is not None:
|
|
150
|
+
entry["title"] = title
|
|
151
|
+
if format is not None:
|
|
152
|
+
entry["format"] = format
|
|
153
|
+
entry.update(extra)
|
|
154
|
+
return entry
|