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,541 @@
|
|
|
1
|
+
"""Layout rendering functions for SVG and HTML.
|
|
2
|
+
|
|
3
|
+
Stage: RENDER
|
|
4
|
+
Purpose: Render different layout types (rows, cols, grid, tabs) to SVG and HTML.
|
|
5
|
+
|
|
6
|
+
This module provides functions to render layouts based on their type:
|
|
7
|
+
- Rows: Vertical stacking
|
|
8
|
+
- Cols: Horizontal distribution
|
|
9
|
+
- Grid: Positioned grid cells
|
|
10
|
+
- Tabs: Tabbed container (active tab only for SVG)
|
|
11
|
+
|
|
12
|
+
Dependencies:
|
|
13
|
+
- compile.models.face.compiled (Layout, LayoutItem, Face)
|
|
14
|
+
- compile.models.chart.compiled (Chart)
|
|
15
|
+
- .renderer (for rendering charts and nested faces)
|
|
16
|
+
|
|
17
|
+
OPTIMIZATION OPPORTUNITY: Batch Vega-Lite Rendering
|
|
18
|
+
---------------------------------------------------
|
|
19
|
+
Currently, each chart in a layout is rendered individually via separate
|
|
20
|
+
vl-convert calls. This could be optimized by:
|
|
21
|
+
|
|
22
|
+
1. Collecting all Vega-Lite charts in the layout
|
|
23
|
+
2. Combining them into a single vconcat/hconcat spec
|
|
24
|
+
3. Making ONE vl-convert call for all charts
|
|
25
|
+
4. Extracting individual SVGs from the combined result
|
|
26
|
+
|
|
27
|
+
Expected improvement: ~30% faster rendering for datafaces with 4+ charts.
|
|
28
|
+
See: plans/archive/VEGA_SVG_BATCH_CONVERSION_ANALYSIS.md
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import html
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
from dataface.core.compile.models.face.compiled import (
|
|
35
|
+
Face,
|
|
36
|
+
LayoutItem,
|
|
37
|
+
ResolvedLayoutItem,
|
|
38
|
+
VariableValues,
|
|
39
|
+
)
|
|
40
|
+
from dataface.core.compile.models.style.merged import MergedStyle
|
|
41
|
+
from dataface.core.errors import StructuredError
|
|
42
|
+
from dataface.core.execute.executor import Executor
|
|
43
|
+
from dataface.core.render.chart.rendering import render_layout_item
|
|
44
|
+
from dataface.core.render.svg_utils import _px
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"is_details_expanded",
|
|
48
|
+
"render_rows_layout",
|
|
49
|
+
"render_cols_layout",
|
|
50
|
+
"render_grid_layout",
|
|
51
|
+
"render_tabs_layout",
|
|
52
|
+
"render_details_summary",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _bg_rect(
|
|
57
|
+
width: float,
|
|
58
|
+
height: float,
|
|
59
|
+
fill: str,
|
|
60
|
+
rx: float = 4,
|
|
61
|
+
x: float = 0,
|
|
62
|
+
y: float = 0,
|
|
63
|
+
stroke: str | None = None,
|
|
64
|
+
stroke_width: float = 1,
|
|
65
|
+
) -> str:
|
|
66
|
+
"""Return a background rect SVG element."""
|
|
67
|
+
stroke_attr = (
|
|
68
|
+
f' stroke="{html.escape(stroke)}" stroke-width="{stroke_width}"'
|
|
69
|
+
if stroke
|
|
70
|
+
else ""
|
|
71
|
+
)
|
|
72
|
+
return f'<rect x="{x}" y="{y}" width="{width}" height="{height}" fill="{html.escape(fill)}"{stroke_attr} rx="{rx}"/>'
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def render_rows_layout(
|
|
76
|
+
items: list[LayoutItem] | tuple[ResolvedLayoutItem, ...],
|
|
77
|
+
executor: Executor,
|
|
78
|
+
variables: VariableValues,
|
|
79
|
+
available_width: float,
|
|
80
|
+
available_height: float,
|
|
81
|
+
card_gap: float,
|
|
82
|
+
gap: float,
|
|
83
|
+
background: str | None = None,
|
|
84
|
+
resolved_style: MergedStyle | None = None,
|
|
85
|
+
interactive: bool = True,
|
|
86
|
+
render_cache: dict[tuple[str, float, float], tuple[str, float]] | None = None,
|
|
87
|
+
*,
|
|
88
|
+
error_collector: list[StructuredError] | None = None,
|
|
89
|
+
face_level: int = 1,
|
|
90
|
+
vega_config: dict[str, Any] | None = None,
|
|
91
|
+
) -> tuple[str, float]:
|
|
92
|
+
"""Render items in vertical stack.
|
|
93
|
+
|
|
94
|
+
In a rows layout, items stack vertically. Heights are determined by:
|
|
95
|
+
1. Pre-calculated dimensions from sizing module (preferred)
|
|
96
|
+
2. Content-aware fallback if not pre-calculated
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
items: Layout items to render
|
|
100
|
+
executor: Executor for query execution
|
|
101
|
+
variables: Variable values for queries
|
|
102
|
+
available_width: Available container width
|
|
103
|
+
available_height: Available container height
|
|
104
|
+
gap: Gap between items
|
|
105
|
+
background: Optional background color
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
(svg_elements_string, total_actual_height) — no <svg> wrapper.
|
|
109
|
+
"""
|
|
110
|
+
if not items:
|
|
111
|
+
return "", 0.0
|
|
112
|
+
|
|
113
|
+
rendered_items: list[str] = []
|
|
114
|
+
current_y = 0.0
|
|
115
|
+
actual_total_height = 0.0
|
|
116
|
+
|
|
117
|
+
for item in items:
|
|
118
|
+
# Use pre-calculated item dimensions for the render call.
|
|
119
|
+
# Actual rendered height is read back from the item SVG.
|
|
120
|
+
item_height = item.height if item.height > 0 else available_height
|
|
121
|
+
item_width = item.width if item.width > 0 else available_width
|
|
122
|
+
|
|
123
|
+
item_svg, actual_item_height = render_layout_item(
|
|
124
|
+
item,
|
|
125
|
+
executor,
|
|
126
|
+
variables,
|
|
127
|
+
card_gap=card_gap,
|
|
128
|
+
available_width=item_width,
|
|
129
|
+
available_height=item_height,
|
|
130
|
+
gap=gap,
|
|
131
|
+
resolved_style=resolved_style,
|
|
132
|
+
interactive=interactive,
|
|
133
|
+
render_cache=render_cache,
|
|
134
|
+
error_collector=error_collector,
|
|
135
|
+
face_level=face_level,
|
|
136
|
+
vega_config=vega_config,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if item_svg:
|
|
140
|
+
rendered_items.append(
|
|
141
|
+
f'<g transform="translate(0, {_px(current_y)})">{item_svg}</g>'
|
|
142
|
+
)
|
|
143
|
+
actual_total_height = current_y + actual_item_height
|
|
144
|
+
current_y += actual_item_height + gap + card_gap
|
|
145
|
+
|
|
146
|
+
bg_rect = ""
|
|
147
|
+
if background:
|
|
148
|
+
bg_rect = _bg_rect(available_width, actual_total_height, background)
|
|
149
|
+
|
|
150
|
+
return f"{bg_rect}\n{''.join(rendered_items)}", actual_total_height
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def render_cols_layout(
|
|
154
|
+
items: list[LayoutItem] | tuple[ResolvedLayoutItem, ...],
|
|
155
|
+
executor: Executor,
|
|
156
|
+
variables: VariableValues,
|
|
157
|
+
available_width: float,
|
|
158
|
+
available_height: float,
|
|
159
|
+
card_gap: float,
|
|
160
|
+
gap: float,
|
|
161
|
+
background: str | None = None,
|
|
162
|
+
resolved_style: MergedStyle | None = None,
|
|
163
|
+
interactive: bool = True,
|
|
164
|
+
render_cache: dict[tuple[str, float, float], tuple[str, float]] | None = None,
|
|
165
|
+
*,
|
|
166
|
+
error_collector: list[StructuredError] | None = None,
|
|
167
|
+
face_level: int = 1,
|
|
168
|
+
vega_config: dict[str, Any] | None = None,
|
|
169
|
+
) -> tuple[str, float]:
|
|
170
|
+
"""Render items in horizontal distribution.
|
|
171
|
+
|
|
172
|
+
Trusts the normalizer for all sizing. Uses pre-calculated item.x, item.width,
|
|
173
|
+
and item.height values.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
items: Layout items to render (with pre-calculated dimensions from normalizer)
|
|
177
|
+
executor: Executor for query execution
|
|
178
|
+
variables: Variable values for queries
|
|
179
|
+
available_width: Available container width
|
|
180
|
+
available_height: Available container height (upper bound)
|
|
181
|
+
gap: Gap between items (unused - normalizer already applied it)
|
|
182
|
+
background: Optional background color
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
(svg_elements_string, max_actual_item_height) — no <svg> wrapper.
|
|
186
|
+
"""
|
|
187
|
+
if not items:
|
|
188
|
+
return "", 0.0
|
|
189
|
+
|
|
190
|
+
# Render items using pre-calculated positions from normalizer
|
|
191
|
+
rendered_items: list[str] = []
|
|
192
|
+
max_actual_height = 0.0
|
|
193
|
+
|
|
194
|
+
for item in items:
|
|
195
|
+
# Trust normalizer for dimensions
|
|
196
|
+
item_w = item.width if item.width > 0 else available_width
|
|
197
|
+
item_h = item.height if item.height > 0 else available_height
|
|
198
|
+
x_pos = item.x # Use pre-calculated x position from normalizer
|
|
199
|
+
|
|
200
|
+
item_svg, actual_item_height = render_layout_item(
|
|
201
|
+
item,
|
|
202
|
+
executor,
|
|
203
|
+
variables,
|
|
204
|
+
card_gap=card_gap,
|
|
205
|
+
available_width=item_w,
|
|
206
|
+
available_height=item_h,
|
|
207
|
+
gap=gap,
|
|
208
|
+
resolved_style=resolved_style,
|
|
209
|
+
interactive=interactive,
|
|
210
|
+
render_cache=render_cache,
|
|
211
|
+
error_collector=error_collector,
|
|
212
|
+
face_level=face_level,
|
|
213
|
+
vega_config=vega_config,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
if item_svg:
|
|
217
|
+
rendered_items.append(
|
|
218
|
+
f'<g transform="translate({_px(x_pos)}, 0)">{item_svg}</g>'
|
|
219
|
+
)
|
|
220
|
+
max_actual_height = max(max_actual_height, actual_item_height)
|
|
221
|
+
|
|
222
|
+
bg_rect = ""
|
|
223
|
+
if background:
|
|
224
|
+
bg_rect = _bg_rect(available_width, max_actual_height, background)
|
|
225
|
+
|
|
226
|
+
return f"{bg_rect}\n{''.join(rendered_items)}", max_actual_height
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def render_grid_layout(
|
|
230
|
+
items: list[LayoutItem] | tuple[ResolvedLayoutItem, ...],
|
|
231
|
+
executor: Executor,
|
|
232
|
+
variables: VariableValues,
|
|
233
|
+
available_width: float,
|
|
234
|
+
available_height: float,
|
|
235
|
+
columns: int,
|
|
236
|
+
card_gap: float,
|
|
237
|
+
gap: float,
|
|
238
|
+
background: str | None = None,
|
|
239
|
+
resolved_style: MergedStyle | None = None,
|
|
240
|
+
interactive: bool = True,
|
|
241
|
+
render_cache: dict[tuple[str, float, float], tuple[str, float]] | None = None,
|
|
242
|
+
*,
|
|
243
|
+
error_collector: list[StructuredError] | None = None,
|
|
244
|
+
face_level: int = 1,
|
|
245
|
+
vega_config: dict[str, Any] | None = None,
|
|
246
|
+
) -> tuple[str, float]:
|
|
247
|
+
"""Render items in positioned grid.
|
|
248
|
+
|
|
249
|
+
Grid items have explicit x, y positions and width, height spans.
|
|
250
|
+
Each item's dimensions are calculated based on the grid columns/rows.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
items: Layout items with grid positions (x, y, width, height)
|
|
254
|
+
executor: Executor for query execution
|
|
255
|
+
variables: Variable values for queries
|
|
256
|
+
available_width: Available container width
|
|
257
|
+
available_height: Available container height
|
|
258
|
+
columns: Number of grid columns
|
|
259
|
+
gap: Gap between grid cells
|
|
260
|
+
background: Optional background color
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
(svg_elements_string, max_actual_bottom_edge) — no <svg> wrapper.
|
|
264
|
+
"""
|
|
265
|
+
if not items:
|
|
266
|
+
return "", 0.0
|
|
267
|
+
|
|
268
|
+
# Trust the normalizer - sizing.py calculates all grid positions and dimensions
|
|
269
|
+
rendered_items: list[str] = []
|
|
270
|
+
max_bottom_edge = 0.0
|
|
271
|
+
|
|
272
|
+
for item in items:
|
|
273
|
+
# Use pre-calculated pixel positions and dimensions from sizing.py
|
|
274
|
+
pixel_x = item.x
|
|
275
|
+
pixel_y = item.y
|
|
276
|
+
item_w = item.width if item.width > 0 else available_width
|
|
277
|
+
item_h = item.height if item.height > 0 else available_height
|
|
278
|
+
|
|
279
|
+
item_svg, actual_item_height = render_layout_item(
|
|
280
|
+
item,
|
|
281
|
+
executor,
|
|
282
|
+
variables,
|
|
283
|
+
card_gap=card_gap,
|
|
284
|
+
available_width=item_w,
|
|
285
|
+
available_height=item_h,
|
|
286
|
+
gap=gap,
|
|
287
|
+
resolved_style=resolved_style,
|
|
288
|
+
interactive=interactive,
|
|
289
|
+
render_cache=render_cache,
|
|
290
|
+
error_collector=error_collector,
|
|
291
|
+
face_level=face_level,
|
|
292
|
+
vega_config=vega_config,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
if item_svg:
|
|
296
|
+
rendered_items.append(
|
|
297
|
+
f'<g transform="translate({_px(pixel_x)}, {_px(pixel_y)})">{item_svg}</g>'
|
|
298
|
+
)
|
|
299
|
+
max_bottom_edge = max(max_bottom_edge, pixel_y + actual_item_height)
|
|
300
|
+
|
|
301
|
+
bg_rect = ""
|
|
302
|
+
if background:
|
|
303
|
+
bg_rect = _bg_rect(available_width, max_bottom_edge, background)
|
|
304
|
+
|
|
305
|
+
return f"{bg_rect}\n{''.join(rendered_items)}", max_bottom_edge
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _build_toggle_url(
|
|
309
|
+
variables: VariableValues, param_name: str, new_value: str
|
|
310
|
+
) -> str:
|
|
311
|
+
"""Build URL that changes one param while preserving all others.
|
|
312
|
+
|
|
313
|
+
Only includes non-default variable values to keep URLs clean.
|
|
314
|
+
"""
|
|
315
|
+
from urllib.parse import urlencode
|
|
316
|
+
|
|
317
|
+
params = {k: str(v) for k, v in variables.items() if v is not None}
|
|
318
|
+
params[param_name] = new_value
|
|
319
|
+
return "?" + urlencode(params)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def render_tabs_layout(
|
|
323
|
+
items: list[LayoutItem] | tuple[ResolvedLayoutItem, ...],
|
|
324
|
+
executor: Executor,
|
|
325
|
+
variables: VariableValues,
|
|
326
|
+
available_width: float,
|
|
327
|
+
available_height: float,
|
|
328
|
+
card_gap: float = 0.0,
|
|
329
|
+
tab_titles: list[str] | None = None,
|
|
330
|
+
tab_slugs: list[str] | None = None,
|
|
331
|
+
tab_variable: str | None = None,
|
|
332
|
+
active_tab: int = 0,
|
|
333
|
+
tab_position: str = "top",
|
|
334
|
+
background: str | None = None,
|
|
335
|
+
*,
|
|
336
|
+
resolved_style: MergedStyle,
|
|
337
|
+
interactive: bool = True,
|
|
338
|
+
render_cache: dict[tuple[str, float, float], tuple[str, float]] | None = None,
|
|
339
|
+
error_collector: list[StructuredError] | None = None,
|
|
340
|
+
face_level: int = 1,
|
|
341
|
+
vega_config: dict[str, Any] | None = None,
|
|
342
|
+
) -> tuple[str, float]:
|
|
343
|
+
"""Render tabbed container (active tab only).
|
|
344
|
+
|
|
345
|
+
In a tabs layout, each tab gets the full container size minus the tab bar.
|
|
346
|
+
Only the active tab is rendered in SVG output. The tab bar is rendered as
|
|
347
|
+
clickable SVG <a href> links that update URL params for server re-rendering.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
items: Layout items (one per tab)
|
|
351
|
+
executor: Executor for query execution
|
|
352
|
+
variables: Variable values for queries
|
|
353
|
+
available_width: Available container width
|
|
354
|
+
available_height: Available container height
|
|
355
|
+
tab_titles: Display titles for tabs
|
|
356
|
+
tab_slugs: URL-safe slugs for tabs (used in URL params)
|
|
357
|
+
tab_variable: Variable name for tab selection (URL param name)
|
|
358
|
+
active_tab: Index of active tab (0-based)
|
|
359
|
+
tab_position: Position of tabs ("top" or "left")
|
|
360
|
+
background: Optional background color
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
(svg_elements_string, actual_height) — no <svg> wrapper.
|
|
364
|
+
"""
|
|
365
|
+
if not items:
|
|
366
|
+
return "", 0.0
|
|
367
|
+
|
|
368
|
+
# Resolve active tab from variable value (URL param overrides default)
|
|
369
|
+
if tab_variable and tab_slugs:
|
|
370
|
+
var_value = variables.get(tab_variable)
|
|
371
|
+
if var_value and str(var_value) in tab_slugs:
|
|
372
|
+
active_tab = tab_slugs.index(str(var_value))
|
|
373
|
+
|
|
374
|
+
tabs_config = resolved_style.layout.tabs
|
|
375
|
+
tab_bar_height = tabs_config.bar_height
|
|
376
|
+
|
|
377
|
+
# Render only active tab
|
|
378
|
+
content_height = (
|
|
379
|
+
available_height - tab_bar_height if tab_position == "top" else available_height
|
|
380
|
+
)
|
|
381
|
+
content_y = tab_bar_height if tab_position == "top" else 0.0
|
|
382
|
+
|
|
383
|
+
active_item = items[min(active_tab, len(items) - 1)]
|
|
384
|
+
item_svg, actual_item_height = render_layout_item(
|
|
385
|
+
active_item,
|
|
386
|
+
executor,
|
|
387
|
+
variables,
|
|
388
|
+
card_gap=0.0,
|
|
389
|
+
available_width=available_width,
|
|
390
|
+
available_height=content_height,
|
|
391
|
+
resolved_style=resolved_style,
|
|
392
|
+
interactive=interactive,
|
|
393
|
+
render_cache=render_cache,
|
|
394
|
+
error_collector=error_collector,
|
|
395
|
+
face_level=face_level,
|
|
396
|
+
vega_config=vega_config,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Compute tab titles once - use provided titles or generate defaults
|
|
400
|
+
titles = (
|
|
401
|
+
tab_titles
|
|
402
|
+
if tab_titles and len(tab_titles) == len(items)
|
|
403
|
+
else [f"Tab {idx + 1}" for idx in range(len(items))]
|
|
404
|
+
)
|
|
405
|
+
slugs = tab_slugs or [f"tab_{idx}" for idx in range(len(items))]
|
|
406
|
+
|
|
407
|
+
# Render tab bar as clickable SVG links
|
|
408
|
+
tab_width = available_width / len(titles)
|
|
409
|
+
tab_bar_parts: list[str] = []
|
|
410
|
+
# header_background / row_stripe are optional in the universal default
|
|
411
|
+
# ("header rule only"). When the theme doesn't define them, fall back
|
|
412
|
+
# to the page background so the tab chrome still reads as a flat band
|
|
413
|
+
# rather than emitting fill="" (which browsers treat as invalid →
|
|
414
|
+
# initial value = black). Mirrors the same fallback in geo.py.
|
|
415
|
+
_active_fill = (
|
|
416
|
+
resolved_style.charts.table.header.background or resolved_style.background
|
|
417
|
+
)
|
|
418
|
+
_inactive_fill = (
|
|
419
|
+
resolved_style.charts.table.row.stripe.color
|
|
420
|
+
if resolved_style.charts.table.row.stripe
|
|
421
|
+
else None
|
|
422
|
+
) or resolved_style.background
|
|
423
|
+
for idx, (title, slug) in enumerate(zip(titles, slugs, strict=True)):
|
|
424
|
+
is_active = idx == active_tab
|
|
425
|
+
x = idx * tab_width
|
|
426
|
+
weight = tabs_config.active_weight if is_active else tabs_config.inactive_weight
|
|
427
|
+
|
|
428
|
+
tab_svg = (
|
|
429
|
+
f'<rect x="{x}" y="0" width="{tab_width}" height="{tab_bar_height}" '
|
|
430
|
+
f'fill="{_active_fill if is_active else _inactive_fill}" '
|
|
431
|
+
f'stroke="{resolved_style.border.color}" stroke-width="{tabs_config.border.width}"/>'
|
|
432
|
+
f'<text x="{x + tab_width / 2}" y="{tab_bar_height / 2 + tabs_config.title_baseline_offset}" '
|
|
433
|
+
f'text-anchor="middle" font-size="{tabs_config.font.size}" fill="{resolved_style.title.font.color if is_active else resolved_style.variables.font.color}" '
|
|
434
|
+
f'font-weight="{weight}">{html.escape(title)}</text>'
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
if tab_variable and not is_active:
|
|
438
|
+
href = _build_toggle_url(variables, tab_variable, slug)
|
|
439
|
+
tab_bar_parts.append(f'<a href="{html.escape(href)}">{tab_svg}</a>')
|
|
440
|
+
else:
|
|
441
|
+
tab_bar_parts.append(tab_svg)
|
|
442
|
+
|
|
443
|
+
tab_bar_svg = "\n".join(tab_bar_parts)
|
|
444
|
+
|
|
445
|
+
content_svg = ""
|
|
446
|
+
if item_svg:
|
|
447
|
+
content_svg = f'<g transform="translate(0, {_px(content_y)})">{item_svg}</g>'
|
|
448
|
+
|
|
449
|
+
actual_height = content_y + actual_item_height
|
|
450
|
+
bg_rect = ""
|
|
451
|
+
if background:
|
|
452
|
+
bg_rect = _bg_rect(available_width, actual_height, background)
|
|
453
|
+
|
|
454
|
+
return f"{bg_rect}\n{tab_bar_svg}\n{content_svg}", actual_height
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def is_details_expanded(
|
|
458
|
+
item: LayoutItem | ResolvedLayoutItem, variables: VariableValues
|
|
459
|
+
) -> bool:
|
|
460
|
+
"""Check if a details section is expanded based on its variable value.
|
|
461
|
+
|
|
462
|
+
Resolution order:
|
|
463
|
+
1. Live variable value (user has toggled the section, URL overrides).
|
|
464
|
+
2. `face.meta["details_expanded_default"]` when the item still carries
|
|
465
|
+
a `Face` (compile-time path used by the primary SVG
|
|
466
|
+
renderer).
|
|
467
|
+
|
|
468
|
+
NOTE: `MergedFace` does not carry `meta`, so a resolved layout item
|
|
469
|
+
with `details_expanded_default: true` that has not been lifted into a
|
|
470
|
+
variable default will render collapsed here. In practice the
|
|
471
|
+
variable-normalization pass (`normalize_variables`) should seed the
|
|
472
|
+
details variable default from meta so the first branch covers it; if
|
|
473
|
+
a future call site exercises this with Resolved items and finds details
|
|
474
|
+
stuck collapsed, wire `details_expanded_default` into
|
|
475
|
+
`ResolvedLayoutItem` instead of adding a second meta dict.
|
|
476
|
+
"""
|
|
477
|
+
if item.details_variable in variables:
|
|
478
|
+
return str(variables[item.details_variable]).lower() == "true"
|
|
479
|
+
face = item.face
|
|
480
|
+
if isinstance(face, Face) and face.meta:
|
|
481
|
+
return bool(face.meta.get("details_expanded_default", False))
|
|
482
|
+
return False
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def render_details_summary(
|
|
486
|
+
item: LayoutItem | ResolvedLayoutItem,
|
|
487
|
+
variables: VariableValues,
|
|
488
|
+
available_width: float,
|
|
489
|
+
expanded: bool | None = None,
|
|
490
|
+
*,
|
|
491
|
+
resolved_style: MergedStyle,
|
|
492
|
+
) -> str:
|
|
493
|
+
"""Render a collapsible section's summary bar as clickable SVG.
|
|
494
|
+
|
|
495
|
+
The summary bar shows a disclosure triangle (▶/▼) and text.
|
|
496
|
+
Clicking navigates to a URL that toggles the expanded state.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
item: LayoutItem with details metadata
|
|
500
|
+
variables: Current variable values (for building toggle URL)
|
|
501
|
+
available_width: Width of the summary bar
|
|
502
|
+
expanded: Pre-computed expanded state (avoids recomputing)
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
SVG string for the summary bar
|
|
506
|
+
"""
|
|
507
|
+
var_name = item.details_variable
|
|
508
|
+
is_expanded = (
|
|
509
|
+
expanded if expanded is not None else is_details_expanded(item, variables)
|
|
510
|
+
)
|
|
511
|
+
arrow = "▼" if is_expanded else "▶"
|
|
512
|
+
label = item.details_expanded_summary if is_expanded else item.details_summary
|
|
513
|
+
new_value = "false" if is_expanded else "true"
|
|
514
|
+
if not var_name:
|
|
515
|
+
raise ValueError(
|
|
516
|
+
"Details summary rendering requires item.details_variable to be set"
|
|
517
|
+
)
|
|
518
|
+
href = _build_toggle_url(variables, var_name, new_value)
|
|
519
|
+
|
|
520
|
+
# Fall back to page background when the theme doesn't set header fill /
|
|
521
|
+
# row stripe — see the comment in render_tabs_layout above.
|
|
522
|
+
_expanded_fill = (
|
|
523
|
+
resolved_style.charts.table.header.background or resolved_style.background
|
|
524
|
+
)
|
|
525
|
+
_collapsed_fill = (
|
|
526
|
+
resolved_style.charts.table.row.stripe.color
|
|
527
|
+
if resolved_style.charts.table.row.stripe
|
|
528
|
+
else None
|
|
529
|
+
) or resolved_style.background
|
|
530
|
+
details_config = resolved_style.layout.details
|
|
531
|
+
summary_height = float(details_config.summary_height)
|
|
532
|
+
return (
|
|
533
|
+
f'<a href="{html.escape(href)}">'
|
|
534
|
+
f'<rect x="0" y="0" width="{available_width}" height="{summary_height}" '
|
|
535
|
+
f'fill="{_expanded_fill if is_expanded else _collapsed_fill}" '
|
|
536
|
+
f'stroke="{resolved_style.border.color}" stroke-width="{details_config.border.width}" rx="{details_config.border.radius}" style="cursor: pointer;"/>'
|
|
537
|
+
f'<text x="{details_config.arrow.x}" y="{summary_height / 2 + details_config.text_baseline_offset}" font-size="{details_config.arrow.font.size}" fill="{resolved_style.title.font.color}">{arrow}</text>'
|
|
538
|
+
f'<text x="{details_config.label_x}" y="{summary_height / 2 + details_config.text_baseline_offset}" font-size="{details_config.font.size}" '
|
|
539
|
+
f'fill="{resolved_style.title.font.color}" font-weight="500">{html.escape(label or "")}</text>'
|
|
540
|
+
f"</a>"
|
|
541
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
markdown:
|
|
2
|
+
light:
|
|
3
|
+
text_color: "#1a1a1a"
|
|
4
|
+
heading_color: "#111111"
|
|
5
|
+
link_color: "#2563eb"
|
|
6
|
+
code_color: "#be185d"
|
|
7
|
+
code_background: "#f3f4f6"
|
|
8
|
+
blockquote_color: "#6b7280"
|
|
9
|
+
|
|
10
|
+
dark:
|
|
11
|
+
text_color: "#e5e7eb"
|
|
12
|
+
link_color: "#6eb5ff"
|
|
13
|
+
code_color: "#f472b6"
|
|
14
|
+
code_background: "#2a2a3e"
|
|
15
|
+
blockquote_color: "#aaaaaa"
|
|
16
|
+
heading_color: "#ffffff"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""HTML rendering for the blocking required-variables prompt.
|
|
2
|
+
|
|
3
|
+
Shared by dft serve (standalone page) and Cloud face viewer (JSON fragment).
|
|
4
|
+
Both surfaces call `render_missing_variables_prompt` to produce the prompt card
|
|
5
|
+
HTML; each surface wraps it in its own chrome and supplies the appropriate
|
|
6
|
+
form action / persistence mechanism.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from html import escape as _esc
|
|
10
|
+
|
|
11
|
+
from dataface.core.render.errors import MissingVariable
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def render_missing_variables_prompt(
|
|
15
|
+
missing: list[MissingVariable],
|
|
16
|
+
*,
|
|
17
|
+
form_action: str,
|
|
18
|
+
form_method: str = "get",
|
|
19
|
+
) -> str:
|
|
20
|
+
"""Return the blocking prompt card HTML for a list of missing required variables.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
missing: Variables that were absent at render time.
|
|
24
|
+
form_action: Form action URL. For dft serve this is the face URL (GET).
|
|
25
|
+
For Cloud the JS overrides submission, so any stable identifier works.
|
|
26
|
+
form_method: HTML form method ("get" or "post").
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
HTML string for the prompt card (not a full page).
|
|
30
|
+
"""
|
|
31
|
+
inputs_html = "".join(_variable_input(mv) for mv in missing)
|
|
32
|
+
return f"""\
|
|
33
|
+
<div class="missing-variables-prompt">
|
|
34
|
+
<div class="missing-variables-card">
|
|
35
|
+
<h2>This dashboard requires configuration</h2>
|
|
36
|
+
<p>The following variable{' is' if len(missing) == 1 else 's are'} required before the dashboard can render.</p>
|
|
37
|
+
<form class="missing-variables-form" action="{_esc(form_action, quote=True)}" method="{_esc(form_method)}">
|
|
38
|
+
{inputs_html}\
|
|
39
|
+
<button type="submit" class="missing-variables-submit">Apply</button>
|
|
40
|
+
</form>
|
|
41
|
+
</div>
|
|
42
|
+
</div>"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _variable_input(mv: MissingVariable) -> str:
|
|
46
|
+
key = _esc(mv.key, quote=True)
|
|
47
|
+
label = _esc(mv.label or mv.key)
|
|
48
|
+
description = (
|
|
49
|
+
f'<p class="missing-var-description">{_esc(mv.description)}</p>'
|
|
50
|
+
if mv.description
|
|
51
|
+
else ""
|
|
52
|
+
)
|
|
53
|
+
input_type = mv.input_type or "text"
|
|
54
|
+
# text/number/date map to native HTML primitives that don't need an options
|
|
55
|
+
# list. Other authored types (select, multiselect, slider, daterange, ...)
|
|
56
|
+
# need richer MissingVariable metadata that the engine doesn't carry yet —
|
|
57
|
+
# fall back to a text input AND surface an inline note so the choice is
|
|
58
|
+
# loud, not silent. Raising would 500 the very surface this prompt exists
|
|
59
|
+
# to keep alive.
|
|
60
|
+
if input_type in ("text", "number", "date"):
|
|
61
|
+
html_type = input_type
|
|
62
|
+
fallback_note = ""
|
|
63
|
+
else:
|
|
64
|
+
html_type = "text"
|
|
65
|
+
fallback_note = (
|
|
66
|
+
'<p class="missing-var-description">'
|
|
67
|
+
f"Variable type {_esc(input_type)} not yet wired through; "
|
|
68
|
+
"enter the value as plain text."
|
|
69
|
+
"</p>"
|
|
70
|
+
)
|
|
71
|
+
control = f'<input type="{_esc(html_type)}" name="{key}" id="var-{key}" class="missing-var-input" placeholder="{label}" required>'
|
|
72
|
+
return f"""\
|
|
73
|
+
<div class="missing-var-field">
|
|
74
|
+
<label for="var-{key}">{label}</label>
|
|
75
|
+
{description}
|
|
76
|
+
{fallback_note}
|
|
77
|
+
{control}
|
|
78
|
+
</div>
|
|
79
|
+
"""
|