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,471 @@
|
|
|
1
|
+
"""Per-chart style cascade — merges chart-local ChartStylePatch into MergedChartsStyle.
|
|
2
|
+
|
|
3
|
+
Stage: COMPILE (pure — no render imports)
|
|
4
|
+
Purpose: Given a board MergedStyle and a chart-local ChartStylePatch, produce
|
|
5
|
+
the fully-merged MergedChartsStyle that the render layer consumes.
|
|
6
|
+
|
|
7
|
+
Architectural directive: render code reads only resolved_style.* — it never sees
|
|
8
|
+
the chart-local Patch. After build_resolved_style runs, every authored override
|
|
9
|
+
(axis, legend, scale, mark, palette, title, …) is baked into the resolved
|
|
10
|
+
fields. The renderer reads `resolved_style.axis_x.label.padding` and gets the
|
|
11
|
+
final value with no authored-vs-cascaded discrimination.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import dataclasses
|
|
17
|
+
from typing import Any, Literal
|
|
18
|
+
|
|
19
|
+
from dataface.core.compile.config import get_config
|
|
20
|
+
from dataface.core.compile.models.style.authored import (
|
|
21
|
+
AreaChartStylePatch,
|
|
22
|
+
BarChartStylePatch,
|
|
23
|
+
ChartStylePatch,
|
|
24
|
+
LayeredChartStyle,
|
|
25
|
+
LegendStylePatch,
|
|
26
|
+
LineChartStylePatch,
|
|
27
|
+
)
|
|
28
|
+
from dataface.core.compile.models.style.compiled import (
|
|
29
|
+
AxisStyle,
|
|
30
|
+
AxisStylePatch,
|
|
31
|
+
InferenceStyle,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Family sub-patch keys in author-precedence order. The cascade reads global
|
|
35
|
+
# fields (palette, background, inference, …) from the FIRST non-null sub-patch.
|
|
36
|
+
# For non-layered charts exactly one key is set; for layered charts the first
|
|
37
|
+
# non-null entry wins for shared fields.
|
|
38
|
+
_ALL_FAMILY_KEYS: tuple[str, ...] = (
|
|
39
|
+
"bar",
|
|
40
|
+
"line",
|
|
41
|
+
"area",
|
|
42
|
+
"scatter",
|
|
43
|
+
"pie",
|
|
44
|
+
"kpi",
|
|
45
|
+
"spark_bar",
|
|
46
|
+
"spark",
|
|
47
|
+
"table",
|
|
48
|
+
"heatmap",
|
|
49
|
+
"geoshape",
|
|
50
|
+
"point_map",
|
|
51
|
+
)
|
|
52
|
+
from dataface.core.compile.models.style.merged import (
|
|
53
|
+
MergedChartsStyle,
|
|
54
|
+
MergedLegendStyle,
|
|
55
|
+
MergedStyle,
|
|
56
|
+
_resolve_color_tokens,
|
|
57
|
+
deep_merge,
|
|
58
|
+
resolve_style,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _get_primary_patch(chart_style: ChartStylePatch) -> Any:
|
|
63
|
+
"""Return the first non-null family sub-patch, or None.
|
|
64
|
+
|
|
65
|
+
For non-layered charts exactly one family slot is set; that patch carries
|
|
66
|
+
both the shared fields (palette, background, inference, …) and any
|
|
67
|
+
family-specific overrides. For layered charts ``layered`` is checked first;
|
|
68
|
+
it carries all shared cartesian fields directly on the flat surface.
|
|
69
|
+
"""
|
|
70
|
+
layered = getattr(chart_style, "layered", None)
|
|
71
|
+
if layered is not None:
|
|
72
|
+
return layered
|
|
73
|
+
for key in _ALL_FAMILY_KEYS:
|
|
74
|
+
patch = getattr(chart_style, key, None)
|
|
75
|
+
if patch is not None:
|
|
76
|
+
return patch
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _chart_style_has_overrides(style: ChartStylePatch | None) -> bool:
|
|
81
|
+
if style is None:
|
|
82
|
+
return False
|
|
83
|
+
return any(v is not None for v in style.model_dump().values())
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _has_family_legend_patch(
|
|
87
|
+
base_charts: MergedChartsStyle, chart_type: str | None
|
|
88
|
+
) -> bool:
|
|
89
|
+
"""Return True if chart_type has a non-None legend patch in the theme family style.
|
|
90
|
+
|
|
91
|
+
Used to bypass the fast path when a per-family legend override exists in the theme
|
|
92
|
+
but no chart-local style override is authored.
|
|
93
|
+
"""
|
|
94
|
+
if not chart_type:
|
|
95
|
+
return False
|
|
96
|
+
family = getattr(base_charts, chart_type, None)
|
|
97
|
+
if family is None:
|
|
98
|
+
return False
|
|
99
|
+
return getattr(family, "legend", None) is not None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _merge_legend(
|
|
103
|
+
base: MergedLegendStyle, patch: LegendStylePatch | None
|
|
104
|
+
) -> MergedLegendStyle:
|
|
105
|
+
"""Merge a LegendStylePatch onto a MergedLegendStyle.
|
|
106
|
+
|
|
107
|
+
Patch is a typed all-Optional model — direct attribute access; no
|
|
108
|
+
``getattr`` defensiveness. Non-None patch fields win; None preserves base.
|
|
109
|
+
"""
|
|
110
|
+
if patch is None:
|
|
111
|
+
return base
|
|
112
|
+
updates: dict[str, Any] = {}
|
|
113
|
+
if patch.orient is not None:
|
|
114
|
+
updates["orient"] = patch.orient
|
|
115
|
+
if patch.direction is not None:
|
|
116
|
+
updates["direction"] = patch.direction
|
|
117
|
+
if patch.disable is not None:
|
|
118
|
+
updates["disable"] = patch.disable
|
|
119
|
+
for sub_name in ("label", "title"):
|
|
120
|
+
sub_patch = getattr(patch, sub_name)
|
|
121
|
+
if sub_patch is None:
|
|
122
|
+
continue
|
|
123
|
+
sub_base = getattr(base, sub_name)
|
|
124
|
+
sub_updates: dict[str, Any] = {}
|
|
125
|
+
if sub_patch.padding is not None:
|
|
126
|
+
sub_updates["padding"] = sub_patch.padding
|
|
127
|
+
if sub_patch.font is not None:
|
|
128
|
+
font_updates = {
|
|
129
|
+
f: v
|
|
130
|
+
for f in ("family", "color", "size", "weight")
|
|
131
|
+
if (v := getattr(sub_patch.font, f)) is not None
|
|
132
|
+
}
|
|
133
|
+
if font_updates:
|
|
134
|
+
sub_updates["font"] = dataclasses.replace(sub_base.font, **font_updates)
|
|
135
|
+
if sub_updates:
|
|
136
|
+
updates[sub_name] = dataclasses.replace(sub_base, **sub_updates)
|
|
137
|
+
return dataclasses.replace(base, **updates) if updates else base
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def build_resolved_style(
|
|
141
|
+
board_resolved: MergedStyle | None,
|
|
142
|
+
chart_style: Any,
|
|
143
|
+
chart_type: str | None = None,
|
|
144
|
+
) -> MergedChartsStyle:
|
|
145
|
+
"""Build the fully-merged MergedChartsStyle for a single chart.
|
|
146
|
+
|
|
147
|
+
Fast path: chart has no local style overrides and no per-family theme patches
|
|
148
|
+
for chart_type → return board.charts unchanged.
|
|
149
|
+
|
|
150
|
+
Merge path: apply chart_style on top of board.charts. Every chart-local
|
|
151
|
+
override (axis_x/axis_y/axis/axis_quantitative/axis_band, legend, scale,
|
|
152
|
+
palette, range, inference, table, data_table, background, title, x_temporal_mode,
|
|
153
|
+
bar/line/area/scatter/arc/kpi/spark/spark_bar) is applied here so the renderer
|
|
154
|
+
reads a single, fully-resolved value without ever touching the Patch.
|
|
155
|
+
|
|
156
|
+
chart_type: the chart family name ("bar", "line", etc.) used to apply per-family
|
|
157
|
+
theme patches (e.g. editorial bar.legend.disable: false). When given, per-family
|
|
158
|
+
theme legend patches override the global resolved legend before face-local patches.
|
|
159
|
+
"""
|
|
160
|
+
base_resolved = board_resolved or resolve_style(get_config().style)
|
|
161
|
+
base_charts = base_resolved.charts
|
|
162
|
+
|
|
163
|
+
# Coerce dict / unknown shape (MockChart in tests) to a typed patch.
|
|
164
|
+
if (
|
|
165
|
+
isinstance(chart_style, dict)
|
|
166
|
+
or chart_style is not None
|
|
167
|
+
and not isinstance(chart_style, ChartStylePatch)
|
|
168
|
+
):
|
|
169
|
+
# LayeredChartStyle passed directly: wrap it in the monolithic container.
|
|
170
|
+
if isinstance(chart_style, LayeredChartStyle):
|
|
171
|
+
chart_style = ChartStylePatch(layered=chart_style)
|
|
172
|
+
else:
|
|
173
|
+
# Auto-wrap table-specific style fields in the table sub-patch if needed
|
|
174
|
+
if isinstance(chart_style, dict):
|
|
175
|
+
table_fields = {
|
|
176
|
+
"columns",
|
|
177
|
+
"pagination",
|
|
178
|
+
"column_defaults",
|
|
179
|
+
"row",
|
|
180
|
+
"header",
|
|
181
|
+
"row_numbers",
|
|
182
|
+
"wrap",
|
|
183
|
+
"background",
|
|
184
|
+
"color",
|
|
185
|
+
"rule",
|
|
186
|
+
"outer_padding",
|
|
187
|
+
"bottom_padding",
|
|
188
|
+
"title",
|
|
189
|
+
"border",
|
|
190
|
+
"header_overflow",
|
|
191
|
+
"column_layout",
|
|
192
|
+
}
|
|
193
|
+
has_table_fields = any(k in chart_style for k in table_fields)
|
|
194
|
+
if has_table_fields and "table" not in chart_style:
|
|
195
|
+
# Wrap table fields in table={...}
|
|
196
|
+
table_style = {}
|
|
197
|
+
other_style = {}
|
|
198
|
+
for k, v in chart_style.items():
|
|
199
|
+
if k in table_fields:
|
|
200
|
+
table_style[k] = v
|
|
201
|
+
else:
|
|
202
|
+
other_style[k] = v
|
|
203
|
+
if table_style:
|
|
204
|
+
other_style["table"] = table_style
|
|
205
|
+
chart_style = other_style
|
|
206
|
+
chart_style = ChartStylePatch.model_validate(chart_style)
|
|
207
|
+
|
|
208
|
+
# Resolve palette tokens (e.g. "dft-creams.cream-50" → "#918878") across
|
|
209
|
+
# all color-bearing fields of the chart-local style patch — before any field
|
|
210
|
+
# is stored as a sentinel or merged into the resolved tree. Raises
|
|
211
|
+
# UnknownColorError on unknown tokens, matching validate-and-error-fast.
|
|
212
|
+
if chart_style is not None:
|
|
213
|
+
chart_style = _resolve_color_tokens(chart_style)
|
|
214
|
+
|
|
215
|
+
if not _chart_style_has_overrides(chart_style) and not _has_family_legend_patch(
|
|
216
|
+
base_charts, chart_type
|
|
217
|
+
):
|
|
218
|
+
return base_charts
|
|
219
|
+
assert chart_style is not None or chart_type is not None
|
|
220
|
+
|
|
221
|
+
overrides: dict[str, Any] = {}
|
|
222
|
+
|
|
223
|
+
# Primary family sub-patch: the single non-null family for non-layered charts,
|
|
224
|
+
# or the first non-null for layered charts. Shared global fields (palette,
|
|
225
|
+
# background, inference, axis overrides, …) are read from this patch.
|
|
226
|
+
primary = _get_primary_patch(chart_style)
|
|
227
|
+
|
|
228
|
+
# --- Palette / range ---
|
|
229
|
+
# Named palette (e.g. ``style.palette: dft-seq-rust``) resolves through
|
|
230
|
+
# the palette resolver into the stops list that MergedChartsStyle.palette
|
|
231
|
+
# holds. Explicit ``style.range.category`` (a list of CSS color strings)
|
|
232
|
+
# wins over the named form when both are authored.
|
|
233
|
+
_palette = getattr(primary, "palette", None)
|
|
234
|
+
if _palette is not None:
|
|
235
|
+
from dataface.core.compile.palette import palette as resolve_palette
|
|
236
|
+
|
|
237
|
+
# Patch models accept both string names and resolved lists; compiled
|
|
238
|
+
# models always materialise as list[str] via the field_validator.
|
|
239
|
+
overrides["palette"] = (
|
|
240
|
+
resolve_palette(_palette) if isinstance(_palette, str) else _palette
|
|
241
|
+
)
|
|
242
|
+
_range = getattr(primary, "range", None)
|
|
243
|
+
if _range is not None and _range.category is not None:
|
|
244
|
+
overrides["palette"] = _range.category
|
|
245
|
+
|
|
246
|
+
# --- Inference ---
|
|
247
|
+
_inference = getattr(primary, "inference", None)
|
|
248
|
+
if _inference is not None:
|
|
249
|
+
board_inf = base_charts.inference
|
|
250
|
+
merged_inf = {
|
|
251
|
+
f: (
|
|
252
|
+
pv
|
|
253
|
+
if (pv := getattr(_inference, f)) is not None
|
|
254
|
+
else getattr(board_inf, f)
|
|
255
|
+
)
|
|
256
|
+
for f in InferenceStyle.model_fields
|
|
257
|
+
}
|
|
258
|
+
overrides["inference"] = InferenceStyle(**merged_inf)
|
|
259
|
+
|
|
260
|
+
# --- data_table (cartesian families only via _CartesianChartStyle) ---
|
|
261
|
+
_data_table = getattr(primary, "data_table", None)
|
|
262
|
+
if _data_table is not None:
|
|
263
|
+
overrides["data_table"] = deep_merge(base_charts.data_table, _data_table)
|
|
264
|
+
|
|
265
|
+
# --- Per-chart-type overlays ---
|
|
266
|
+
# deep_merge handles every family-specific field including orientation, stack,
|
|
267
|
+
# color_scheme (geoshape), column_layout/columns/column_defaults (table), etc.
|
|
268
|
+
for ct in _ALL_FAMILY_KEYS:
|
|
269
|
+
patch = getattr(chart_style, ct, None)
|
|
270
|
+
if patch is not None:
|
|
271
|
+
overrides[ct] = deep_merge(getattr(base_charts, ct), patch)
|
|
272
|
+
|
|
273
|
+
# --- Layered mark distribution ---
|
|
274
|
+
# LayeredChartStyle.marks distributes per-mark-type overrides to the bar/line/area
|
|
275
|
+
# family slots that the renderer reads (eff.bar.marks.bar, eff.line.marks.line, etc.).
|
|
276
|
+
_layered = getattr(chart_style, "layered", None)
|
|
277
|
+
if _layered is not None and _layered.marks is not None:
|
|
278
|
+
_lm = _layered.marks
|
|
279
|
+
if _lm.bar is not None:
|
|
280
|
+
overrides["bar"] = deep_merge(
|
|
281
|
+
overrides.get("bar", base_charts.bar),
|
|
282
|
+
BarChartStylePatch.model_validate(
|
|
283
|
+
{"marks": {"bar": _lm.bar.model_dump(exclude_none=True)}}
|
|
284
|
+
),
|
|
285
|
+
)
|
|
286
|
+
if _lm.line is not None or _lm.point is not None:
|
|
287
|
+
_line_marks: dict[str, Any] = {}
|
|
288
|
+
if _lm.line is not None:
|
|
289
|
+
_line_marks["line"] = _lm.line.model_dump(exclude_none=True)
|
|
290
|
+
if _lm.point is not None:
|
|
291
|
+
_line_marks["point"] = _lm.point.model_dump(exclude_none=True)
|
|
292
|
+
overrides["line"] = deep_merge(
|
|
293
|
+
overrides.get("line", base_charts.line),
|
|
294
|
+
LineChartStylePatch.model_validate({"marks": _line_marks}),
|
|
295
|
+
)
|
|
296
|
+
if _lm.area is not None or _lm.point is not None:
|
|
297
|
+
# Point marks are distributed to both line and area families because
|
|
298
|
+
# area+line combos can render overlaid point marks via either family slot.
|
|
299
|
+
_area_marks: dict[str, Any] = {}
|
|
300
|
+
if _lm.area is not None:
|
|
301
|
+
_area_marks["area"] = _lm.area.model_dump(exclude_none=True)
|
|
302
|
+
if _lm.point is not None:
|
|
303
|
+
_area_marks["point"] = _lm.point.model_dump(exclude_none=True)
|
|
304
|
+
overrides["area"] = deep_merge(
|
|
305
|
+
overrides.get("area", base_charts.area),
|
|
306
|
+
AreaChartStylePatch.model_validate({"marks": _area_marks}),
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# --- Callout tone override ---
|
|
310
|
+
# MergedCalloutChartStyle is a frozen dataclass (not a BaseModel), so it
|
|
311
|
+
# cannot use the deep_merge loop above. Only tone is chart-locally authored.
|
|
312
|
+
_callout_patch = getattr(chart_style, "callout", None)
|
|
313
|
+
if _callout_patch is not None and _callout_patch.tone is not None:
|
|
314
|
+
overrides["callout"] = dataclasses.replace(
|
|
315
|
+
base_charts.callout, tone=_callout_patch.tone
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
# --- Top-level Vega-facing ---
|
|
319
|
+
_background = getattr(primary, "background", None)
|
|
320
|
+
if _background is not None:
|
|
321
|
+
overrides["background"] = _background
|
|
322
|
+
_color = getattr(primary, "color", None)
|
|
323
|
+
if isinstance(_color, str):
|
|
324
|
+
# Only string (static paint) flows to MergedChartsStyle.color.
|
|
325
|
+
# StyleColorConfig (gradient scale) is consumed by normalize_chart_channels
|
|
326
|
+
# and never stored in the style cascade.
|
|
327
|
+
overrides["color"] = _color
|
|
328
|
+
_title = getattr(primary, "title", None)
|
|
329
|
+
if _title is not None:
|
|
330
|
+
overrides["title"] = deep_merge(base_charts.title, _title)
|
|
331
|
+
|
|
332
|
+
# --- Chart-local axis overrides ---
|
|
333
|
+
# Stored as typed AxisStylePatch sentinels on resolved_chart_style
|
|
334
|
+
# so the renderer's resolved_axis_style helper can apply Layers 4/5/6 in
|
|
335
|
+
# the canonical cascade order (after Layer 3 axis_quantitative and Layer
|
|
336
|
+
# 3.5 chart-type axis). Baking them into base.axis_x/y here would invert
|
|
337
|
+
# the precedence relative to axis_quantitative — the renderer must keep
|
|
338
|
+
# control of the layering.
|
|
339
|
+
_axis = getattr(primary, "axis", None)
|
|
340
|
+
if _axis is not None:
|
|
341
|
+
overrides["axis_overrides_global"] = _axis
|
|
342
|
+
_axis_x = getattr(primary, "axis_x", None)
|
|
343
|
+
if _axis_x is not None:
|
|
344
|
+
overrides["axis_overrides_x"] = _axis_x
|
|
345
|
+
_axis_y = getattr(primary, "axis_y", None)
|
|
346
|
+
if _axis_y is not None:
|
|
347
|
+
overrides["axis_overrides_y"] = _axis_y
|
|
348
|
+
_axis_quantitative = getattr(primary, "axis_quantitative", None)
|
|
349
|
+
if _axis_quantitative is not None:
|
|
350
|
+
overrides["axis_overrides_quantitative"] = _axis_quantitative
|
|
351
|
+
_axis_band = getattr(primary, "axis_band", None)
|
|
352
|
+
if _axis_band is not None:
|
|
353
|
+
overrides["axis_overrides_band"] = _axis_band
|
|
354
|
+
|
|
355
|
+
# --- Legend ---
|
|
356
|
+
# Apply per-family theme patch first (e.g. editorial bar.legend.disable: false),
|
|
357
|
+
# then face-local patch on top. Merge order: global → family theme → face-local.
|
|
358
|
+
_effective_legend = base_charts.legend
|
|
359
|
+
if chart_type is not None:
|
|
360
|
+
family_style = getattr(base_charts, chart_type, None)
|
|
361
|
+
family_legend_patch = (
|
|
362
|
+
getattr(family_style, "legend", None) if family_style is not None else None
|
|
363
|
+
)
|
|
364
|
+
if family_legend_patch is not None:
|
|
365
|
+
_effective_legend = _merge_legend(base_charts.legend, family_legend_patch)
|
|
366
|
+
_legend = getattr(primary, "legend", None) if primary is not None else None
|
|
367
|
+
if _legend is not None:
|
|
368
|
+
_effective_legend = _merge_legend(_effective_legend, _legend)
|
|
369
|
+
if _effective_legend is not base_charts.legend:
|
|
370
|
+
overrides["legend"] = _effective_legend
|
|
371
|
+
|
|
372
|
+
# --- Scale (chart-level global; per-axis already lands on axis_*.scale above) ---
|
|
373
|
+
_scale = getattr(primary, "scale", None)
|
|
374
|
+
if _scale is not None:
|
|
375
|
+
overrides["scale"] = _scale
|
|
376
|
+
|
|
377
|
+
# --- Pagination (table charts expose this via TableChartStyle.pagination) ---
|
|
378
|
+
# Chart-local pagination layers on top of the face default. ``enabled``
|
|
379
|
+
# is required on the authored Patch so it always sets; ``page_size`` is
|
|
380
|
+
# optional — when unset the cascade falls back to the face-level
|
|
381
|
+
# page_size so the renderer reads a single resolved value off
|
|
382
|
+
# ``effective.pagination`` without re-running the merge.
|
|
383
|
+
_pagination = getattr(primary, "pagination", None)
|
|
384
|
+
if _pagination is not None:
|
|
385
|
+
face = base_charts.pagination
|
|
386
|
+
merged_pagination = _pagination
|
|
387
|
+
if (
|
|
388
|
+
merged_pagination.page_size is None
|
|
389
|
+
and face is not None
|
|
390
|
+
and face.page_size is not None
|
|
391
|
+
):
|
|
392
|
+
merged_pagination = merged_pagination.model_copy(
|
|
393
|
+
update={"page_size": face.page_size}
|
|
394
|
+
)
|
|
395
|
+
overrides["pagination"] = merged_pagination
|
|
396
|
+
|
|
397
|
+
if not overrides:
|
|
398
|
+
return base_charts
|
|
399
|
+
|
|
400
|
+
return dataclasses.replace(base_charts, **overrides)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
# ── Axis-cascade emit helper ──────────────────────────────────────────
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _chart_type_axis_patch(
|
|
407
|
+
effective: MergedChartsStyle,
|
|
408
|
+
chart_type: str,
|
|
409
|
+
axis_name: Literal["axis_x", "axis_y"],
|
|
410
|
+
) -> AxisStylePatch | None:
|
|
411
|
+
"""Return the chart-type-specific axis patch (Layer 3.5 of the cascade).
|
|
412
|
+
|
|
413
|
+
Pie charts have no cartesian axes, so this always returns None for them.
|
|
414
|
+
"""
|
|
415
|
+
chart_type_style = getattr(effective, chart_type, None)
|
|
416
|
+
return (
|
|
417
|
+
getattr(chart_type_style, axis_name, None)
|
|
418
|
+
if chart_type_style is not None
|
|
419
|
+
else None
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def resolved_axis_style(
|
|
424
|
+
effective: MergedChartsStyle,
|
|
425
|
+
axis_name: Literal["axis_x", "axis_y"],
|
|
426
|
+
channel_type: str,
|
|
427
|
+
chart_type_axis_patch: AxisStylePatch | None = None,
|
|
428
|
+
) -> AxisStyle:
|
|
429
|
+
"""Walk the 7-layer axis cascade and return a merged AxisStyle.
|
|
430
|
+
|
|
431
|
+
Single source of truth for axis state at emit time. The renderer reads
|
|
432
|
+
every axis field (ticks, grid, domain, orient, offset, ...) from this
|
|
433
|
+
return value — never from ``effective.axis_x`` / ``axis_y`` directly,
|
|
434
|
+
since those reflect only Layers 1+2 and silently miss type-conditional,
|
|
435
|
+
chart-type, and chart-local overrides.
|
|
436
|
+
|
|
437
|
+
Layer order (each overrides the previous):
|
|
438
|
+
1+2. theme global + channel-specific (effective.axis_{x,y})
|
|
439
|
+
3. theme type-conditional (effective.axis_quantitative / band)
|
|
440
|
+
3.5. theme chart-type-specific (chart_type_axis_patch)
|
|
441
|
+
4. chart-local global (effective.axis_overrides_global)
|
|
442
|
+
5. chart-local type-conditional (effective.axis_overrides_quantitative / _band)
|
|
443
|
+
6. chart-local channel-specific (effective.axis_overrides_{x,y}) — wins
|
|
444
|
+
|
|
445
|
+
Chart-local layers come from typed Patch sentinels stored on
|
|
446
|
+
``effective`` by ``build_resolved_style`` — the chart's ChartStylePatch
|
|
447
|
+
is consumed by the cascade and never reaches the renderer.
|
|
448
|
+
"""
|
|
449
|
+
base = AxisStyle.model_validate(dataclasses.asdict(getattr(effective, axis_name)))
|
|
450
|
+
|
|
451
|
+
if channel_type == "quantitative":
|
|
452
|
+
base = deep_merge( # type: ignore[assignment]
|
|
453
|
+
base,
|
|
454
|
+
AxisStyle.model_validate(dataclasses.asdict(effective.axis_quantitative)),
|
|
455
|
+
)
|
|
456
|
+
# axis_band has no theme-resolved representation; only chart-local applies.
|
|
457
|
+
|
|
458
|
+
base = deep_merge(base, chart_type_axis_patch) # type: ignore[assignment]
|
|
459
|
+
|
|
460
|
+
base = deep_merge(base, effective.axis_overrides_global) # type: ignore[assignment]
|
|
461
|
+
if channel_type == "quantitative":
|
|
462
|
+
base = deep_merge( # type: ignore[assignment]
|
|
463
|
+
base, effective.axis_overrides_quantitative
|
|
464
|
+
)
|
|
465
|
+
elif channel_type in ("ordinal", "nominal"):
|
|
466
|
+
base = deep_merge(base, effective.axis_overrides_band) # type: ignore[assignment]
|
|
467
|
+
base = deep_merge( # type: ignore[assignment]
|
|
468
|
+
base, getattr(effective, f"axis_overrides_{axis_name[-1]}")
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
return base
|