dataface 0.3.2.dev22__py3-none-any.whl → 0.3.2.dev111__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.
Files changed (185) hide show
  1. dataface/__init__.py +70 -23
  2. dataface/agent_api/__init__.py +162 -68
  3. dataface/agent_api/_paths.py +9 -3
  4. dataface/agent_api/docs/yaml-reference.md +240 -106
  5. dataface/agent_api/init.py +1 -1
  6. dataface/agent_api/project_session.py +2 -1
  7. dataface/agent_api/serve.py +115 -0
  8. dataface/ai/agent.py +28 -2
  9. dataface/cli/_project.py +100 -1
  10. dataface/cli/commands/board_artifact.py +5 -2
  11. dataface/cli/commands/describe.py +7 -3
  12. dataface/cli/commands/docs.py +12 -3
  13. dataface/cli/commands/init.py +7 -14
  14. dataface/cli/commands/inspect.py +6 -1
  15. dataface/cli/commands/mcp_init.py +23 -43
  16. dataface/cli/commands/migrate.py +8 -3
  17. dataface/cli/commands/query.py +11 -8
  18. dataface/cli/commands/render.py +20 -9
  19. dataface/cli/commands/search.py +8 -2
  20. dataface/cli/commands/serve.py +26 -76
  21. dataface/cli/commands/skills_init.py +2 -19
  22. dataface/cli/commands/validate.py +7 -2
  23. dataface/cli/filesystem_project.py +5 -4
  24. dataface/cli/main.py +2 -19
  25. dataface/core/__init__.py +73 -28
  26. dataface/core/{compile/colors.py → colors.py} +20 -5
  27. dataface/core/compile/compiler.py +97 -34
  28. dataface/core/compile/data_table.py +7 -2
  29. dataface/core/compile/enrich.py +18 -44
  30. dataface/core/compile/face_warnings.py +46 -8
  31. dataface/core/compile/format.py +47 -1
  32. dataface/core/compile/inherit_graph.py +3 -3
  33. dataface/core/compile/inherit_registry.yaml +1 -1
  34. dataface/core/compile/migrations.py +112 -9
  35. dataface/core/compile/models/chart/normalized/kpi.py +0 -4
  36. dataface/core/compile/models/chart/resolved/_base.py +28 -10
  37. dataface/core/compile/models/chart/resolved/callout.py +2 -3
  38. dataface/core/compile/models/chart/resolved/kpi.py +7 -3
  39. dataface/core/compile/models/chart/resolved/table.py +16 -2
  40. dataface/core/compile/models/markers.py +1 -1
  41. dataface/core/compile/models/primitives.py +175 -2
  42. dataface/core/compile/models/source.py +5 -5
  43. dataface/core/compile/models/style/authored/__init__.py +32 -8
  44. dataface/core/compile/models/style/authored/_base.py +126 -69
  45. dataface/core/compile/models/style/authored/table.py +61 -3
  46. dataface/core/compile/models/style/resolved/__init__.py +12 -2
  47. dataface/core/compile/models/style/resolved/_base.py +142 -57
  48. dataface/core/compile/models/style/resolved/_cartesian.py +6 -11
  49. dataface/core/compile/models/style/resolved/_cascade.py +141 -54
  50. dataface/core/compile/models/style/resolved/area.py +1 -1
  51. dataface/core/compile/models/style/resolved/bar.py +1 -1
  52. dataface/core/compile/models/style/resolved/line.py +1 -1
  53. dataface/core/compile/models/style/resolved/scatter.py +3 -2
  54. dataface/core/compile/models/style/theme/__init__.py +36 -14
  55. dataface/core/compile/models/style/theme/_chart_base.py +17 -9
  56. dataface/core/compile/models/style/theme/axis.py +432 -312
  57. dataface/core/compile/models/style/theme/charts.py +10 -6
  58. dataface/core/compile/models/style/theme/kpi.py +2 -1
  59. dataface/core/compile/models/style/theme/page.py +5 -2
  60. dataface/core/compile/models/style/theme/table.py +2 -2
  61. dataface/core/compile/models/vega_lite/config.py +1 -1
  62. dataface/core/compile/normalize_charts.py +6 -2
  63. dataface/core/compile/normalize_queries.py +21 -7
  64. dataface/core/compile/resolve/__init__.py +2 -0
  65. dataface/core/compile/resolve/_axes.py +147 -44
  66. dataface/core/compile/resolve/_channels.py +1 -1
  67. dataface/core/compile/resolve/_dispatch.py +1 -1
  68. dataface/core/compile/resolve/_domain.py +49 -32
  69. dataface/core/compile/resolve/_kwargs.py +39 -12
  70. dataface/core/compile/resolve/_marks.py +8 -3
  71. dataface/core/compile/resolve/_palette.py +7 -3
  72. dataface/core/compile/resolve/_table.py +124 -33
  73. dataface/core/compile/resolve/area.py +48 -18
  74. dataface/core/compile/resolve/bar.py +245 -64
  75. dataface/core/compile/resolve/geo.py +10 -3
  76. dataface/core/compile/resolve/heatmap.py +16 -8
  77. dataface/core/compile/resolve/line.py +35 -12
  78. dataface/core/compile/resolve/pie.py +4 -1
  79. dataface/core/compile/resolve/pie_attachment.py +1 -1
  80. dataface/core/compile/resolve/scatter.py +25 -13
  81. dataface/core/compile/resolve/simple.py +25 -2
  82. dataface/core/compile/schema_renderers/prompt.py +1 -1
  83. dataface/core/compile/schema_renderers/textmate_grammar.py +8 -1
  84. dataface/core/compile/source_map.py +211 -28
  85. dataface/core/compile/style_cascade.py +126 -55
  86. dataface/core/compile/yaml_error_formatter.py +1 -1
  87. dataface/core/connections.py +10 -3
  88. dataface/core/dashboard.py +24 -18
  89. dataface/core/defaults/themes/_base.yaml +49 -39
  90. dataface/core/defaults/themes/cream.yaml +4 -4
  91. dataface/core/defaults/themes/diagnostics-title-baseline.yaml +1 -1
  92. dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +1 -1
  93. dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +1 -1
  94. dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +1 -1
  95. dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +1 -1
  96. dataface/core/defaults/themes/editorial.yaml +6 -6
  97. dataface/core/defaults/themes/neon.yaml +15 -8
  98. dataface/core/defaults/themes/plain.yaml +6 -4
  99. dataface/core/defaults/themes/stark.yaml +16 -7
  100. dataface/core/defaults/themes/vivid.yaml +15 -8
  101. dataface/core/diagnostics/__init__.py +2 -2
  102. dataface/core/diagnostics/codes_compile.py +13 -31
  103. dataface/core/diagnostics/codes_execute.py +79 -0
  104. dataface/core/diagnostics/codes_render.py +87 -28
  105. dataface/core/diagnostics/diagnostic.py +30 -0
  106. dataface/core/diagnostics/hints.py +19 -0
  107. dataface/core/dialects/base.py +17 -0
  108. dataface/core/dialects/postgres.py +14 -0
  109. dataface/core/dialects/redshift.py +5 -0
  110. dataface/core/execute/__init__.py +59 -14
  111. dataface/core/execute/adapters/dbt_adapter.py +4 -1
  112. dataface/core/execute/adapters/dbt_adapter_factory.py +19 -0
  113. dataface/core/execute/adapters/dbt_utils.py +84 -20
  114. dataface/core/execute/adapters/postgres_connection_manager.py +48 -0
  115. dataface/core/execute/adapters/sql_adapter.py +72 -4
  116. dataface/core/execute/dbt_jinja.py +65 -10
  117. dataface/core/execute/executor.py +69 -16
  118. dataface/core/font_measure.py +28 -0
  119. dataface/core/fonts.py +8 -5
  120. dataface/core/inspect/__init__.py +46 -6
  121. dataface/core/project.py +13 -1
  122. dataface/core/render/chart/data_table_attachment.py +14 -12
  123. dataface/core/render/chart/emitters/_cartesian.py +21 -18
  124. dataface/core/render/chart/emitters/_channels.py +56 -8
  125. dataface/core/render/chart/emitters/_label_overlap.py +18 -15
  126. dataface/core/render/chart/emitters/_measured_label_padding.py +4 -4
  127. dataface/core/render/chart/emitters/area.py +3 -3
  128. dataface/core/render/chart/emitters/bar.py +60 -35
  129. dataface/core/render/chart/emitters/geo.py +8 -13
  130. dataface/core/render/chart/emitters/heatmap.py +20 -26
  131. dataface/core/render/chart/emitters/line.py +2 -2
  132. dataface/core/render/chart/emitters/scatter.py +7 -30
  133. dataface/core/render/chart/features/baseline.py +22 -9
  134. dataface/core/render/chart/features/endpoint_labels.py +55 -32
  135. dataface/core/render/chart/features/mirror_axis.py +12 -9
  136. dataface/core/render/chart/features/value_labels.py +1 -1
  137. dataface/core/render/chart/kpi.py +11 -90
  138. dataface/core/render/chart/presentation.py +6 -8
  139. dataface/core/render/chart/rendering.py +3 -12
  140. dataface/core/render/chart/session.py +10 -0
  141. dataface/core/render/chart/spec.py +3 -0
  142. dataface/core/render/chart/spec_builders.py +18 -18
  143. dataface/core/render/chart/table.py +81 -36
  144. dataface/core/render/chart/table_support.py +2 -53
  145. dataface/core/render/chart/time_unit_detect.py +3 -2
  146. dataface/core/render/chart/translate.py +82 -44
  147. dataface/core/render/chart/type_inference.py +45 -25
  148. dataface/core/render/chart/validation.py +91 -0
  149. dataface/core/render/chart/vega_lite.py +30 -11
  150. dataface/core/render/chart/vl_field_maps.py +111 -76
  151. dataface/core/render/converters/chart.py +16 -6
  152. dataface/core/render/faces.py +3 -2
  153. dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.woff2 +0 -0
  154. dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.woff2 +0 -0
  155. dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -93
  156. dataface/core/render/fonts/_font_face.css +10 -0
  157. dataface/core/render/layout_sizing.py +267 -238
  158. dataface/core/render/numeral_expr.py +80 -0
  159. dataface/core/render/warnings/bar_band_width_too_narrow.py +64 -6
  160. dataface/core/render/warnings/layered_chart_shared_y_axis_scale_mismatch.py +25 -3
  161. dataface/core/render/warnings/likely_currency_or_percent_missing_formatter.py +12 -6
  162. dataface/core/render/warnings/palette_unsupported.py +6 -0
  163. dataface/core/render/warnings/pie_dominant_segment.py +1 -0
  164. dataface/core/render/warnings/pie_too_many_segments.py +1 -0
  165. dataface/core/render/warnings/point_map_out_of_projection.py +1 -0
  166. dataface/core/render/warnings/query_returned_zero_rows.py +1 -0
  167. dataface/core/render/warnings/redundant_encoding.py +20 -1
  168. dataface/core/render/warnings/registry.py +7 -3
  169. dataface/core/render/warnings/temporal_single_point.py +1 -0
  170. dataface/core/render/warnings/too_many_color_categories.py +1 -0
  171. dataface/core/render/warnings/too_many_x_categories.py +18 -2
  172. dataface/core/render/warnings/value_labels_crowd_width.py +9 -0
  173. dataface/core/render/warnings/y_encoding_mostly_null.py +1 -0
  174. dataface/core/render_format.py +18 -0
  175. dataface/core/text/format_d3.py +56 -0
  176. dataface/core/text/numeral_scale.py +218 -0
  177. dataface/core/utils.py +103 -10
  178. dataface/data/schemas/board-resolved/board-resolved.schema.json +9487 -7978
  179. dataface/data/schemas/yaml/0.3.2.json +1 -0
  180. dataface/data/schemas/yaml/manifest.json +7 -0
  181. {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/METADATA +1 -1
  182. {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/RECORD +185 -177
  183. {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/WHEEL +0 -0
  184. {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/entry_points.txt +0 -0
  185. {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/licenses/LICENSE +0 -0
@@ -14,9 +14,12 @@ looking for, so routing through an adapter would mean inventing a sentinel
14
14
  dataset just to construct one. The BigQuery client needs no dataset at all.
15
15
  """
16
16
 
17
- from typing import Any
17
+ from __future__ import annotations
18
18
 
19
- from dataface.core.compile.models.source import BigQuerySourceConfig, SourceConfig
19
+ from typing import TYPE_CHECKING, Any
20
+
21
+ if TYPE_CHECKING:
22
+ from dataface.core.compile.models.source import BigQuerySourceConfig, SourceConfig
20
23
 
21
24
 
22
25
  def _import_bigquery(module_name: str) -> Any:
@@ -78,6 +81,8 @@ def list_datasets(source_config: SourceConfig) -> list[str]:
78
81
  this is what the connection form calls before the user can know which
79
82
  datasets exist.
80
83
  """
84
+ from dataface.core.compile.models.source import BigQuerySourceConfig
85
+
81
86
  if not isinstance(source_config, BigQuerySourceConfig):
82
87
  raise ValueError(
83
88
  f"list_datasets is BigQuery-only, got {source_config.type!r}. "
@@ -97,6 +102,8 @@ def dataset_location(source_config: SourceConfig) -> str:
97
102
  resources without a location, so filling it for every listed dataset would
98
103
  be an N+1 across potentially hundreds of them.
99
104
  """
105
+ from dataface.core.compile.models.source import BigQuerySourceConfig
106
+
100
107
  if not isinstance(source_config, BigQuerySourceConfig):
101
108
  raise ValueError(
102
109
  f"dataset_location is BigQuery-only, got {source_config.type!r}."
@@ -133,7 +140,7 @@ def test_connection(source_config: SourceConfig) -> tuple[bool, str]:
133
140
  try:
134
141
  adapter = build_adapter(creds)
135
142
  with adapter.connection_named("test"):
136
- adapter.execute("SELECT 1", auto_begin=True, fetch=True)
143
+ adapter.execute("SELECT 1", auto_begin=False, fetch=True)
137
144
  return True, "Connection successful"
138
145
  except (
139
146
  Exception # noqa: BLE001
@@ -21,6 +21,7 @@ from __future__ import annotations
21
21
  import importlib.util as _importlib_util
22
22
  import logging
23
23
  import time
24
+ from datetime import datetime
24
25
  from pathlib import PurePosixPath
25
26
  from typing import Any, Literal, get_args
26
27
 
@@ -81,10 +82,7 @@ from dataface.core.render import RenderError, render
81
82
  from dataface.core.render.board_links import LinkContext
82
83
  from dataface.core.render.dir_context import lazy_dir_context
83
84
  from dataface.core.render.face_to_dict import NO_ROW_CAP
84
-
85
- RenderFormat = Literal[
86
- "svg", "html", "png", "pdf", "terminal", "json", "text", "yaml", "data"
87
- ]
85
+ from dataface.core.render_format import RenderFormat
88
86
 
89
87
  # "failed" — no output produced (face_error, or validation_errors from a failed
90
88
  # compile).
@@ -120,9 +118,9 @@ class RenderedDashboard(BaseModel):
120
118
  # concept of. Cloud resolves agent-facing dashboard URLs through its own
121
119
  # router instead (`search_dashboards`).
122
120
  url: str | None = None
123
- # Resolved per-query cache policies — populated only on success, consumed by
124
- # Cloud's snapshot writer to compute expires_at without recompiling the board.
125
- board_cache_policies: dict[str, CachePolicy] = {}
121
+ # Per-query (name, data_as_of, policy) records populated on success. Consumed
122
+ # by Cloud's snapshot writer to compute expires_at anchored on actual data age.
123
+ board_query_data_ages: list[tuple[str, datetime, CachePolicy]] = []
126
124
 
127
125
 
128
126
  def raise_on_dashboard_failure(result: RenderedDashboard) -> None:
@@ -394,19 +392,29 @@ def render_dashboard(
394
392
  # the compile-time source map here: render never re-parses the
395
393
  # authored text, it reuses the map compile already built.
396
394
  stamp_diagnostics(
397
- render_result.chart_errors, result.source_map, result.sql_blocks
395
+ render_result.chart_errors,
396
+ result.source_map,
397
+ result.sql_blocks,
398
+ result.container_paths,
399
+ )
400
+ stamp_diagnostics(
401
+ render_result.warnings,
402
+ result.source_map,
403
+ result.sql_blocks,
404
+ result.container_paths,
398
405
  )
399
- stamp_diagnostics(render_result.warnings, result.source_map, result.sql_blocks)
400
406
  stamp_diagnostics(
401
407
  render_result.suppressed_warnings,
402
408
  result.source_map,
403
409
  result.sql_blocks,
410
+ result.container_paths,
404
411
  )
405
412
  if render_result.face_error is not None:
406
413
  stamp_diagnostics(
407
414
  [render_result.face_error],
408
415
  result.source_map,
409
416
  result.sql_blocks,
417
+ result.container_paths,
410
418
  )
411
419
  _logger.debug(
412
420
  "render_dashboard %s compile=%.0fms execute+render=%.0fms total=%.0fms cache=%s",
@@ -472,7 +480,12 @@ def render_dashboard(
472
480
  fatal_diagnostic = e.to_diagnostic(
473
481
  file=file_path.relpath if file_path is not None else None
474
482
  )
475
- stamp_diagnostics([fatal_diagnostic], result.source_map, result.sql_blocks)
483
+ stamp_diagnostics(
484
+ [fatal_diagnostic],
485
+ result.source_map,
486
+ result.sql_blocks,
487
+ result.container_paths,
488
+ )
476
489
  return RenderedDashboard(
477
490
  status="failed",
478
491
  face_error=fatal_diagnostic,
@@ -495,12 +508,5 @@ def render_dashboard(
495
508
  *render_result.suppressed_warnings,
496
509
  ],
497
510
  url=url,
498
- # The registry, not `face.queries`: inline chart/layer queries and
499
- # `#`-anchored refs are normalized straight into the registry and never
500
- # get a `queries:` entry, so keying off the named section hands Cloud a
501
- # short map — or an empty one for a board authored entirely inline,
502
- # which its `min(..., default=inf)` reads as "nothing expires".
503
- board_cache_policies={
504
- name: q.cache for name, q in result.query_registry.items()
505
- },
511
+ board_query_data_ages=executor.query_data_ages,
506
512
  )
@@ -145,27 +145,25 @@ style:
145
145
  radius: 0.0
146
146
 
147
147
  axis:
148
- domain:
148
+ line:
149
149
  visible: false
150
150
  width: 1
151
151
  ticks:
152
152
  visible: false
153
- size: 6 # default tick size cascades to all axes; matches VL's geometric idiom
153
+ length: 6 # default tick length cascades to all axes; matches VL's geometric idiom
154
154
  grid:
155
155
  visible: true
156
156
  opacity: 1
157
157
  width: 1.5
158
- zero:
159
- width: 2
160
- label:
158
+ labels:
161
159
  font:
162
160
  size: &size_label 11
163
161
  padding: 6
164
162
  overlap: # default: all strategies enabled; the ONE home for this setting
165
163
  tilt: true
166
164
  skip: true
167
- separation: 16 # 16px minimum gap between adjacent labels
168
- # bound, flush, offset, line_height, anchor intentionally absent — VL per-axis defaults apply.
165
+ min_gap: 16 # 16px minimum gap between adjacent labels
166
+ # bound, flush, offset intentionally absent — VL per-axis defaults apply.
169
167
  title:
170
168
  visible: false
171
169
  font:
@@ -173,13 +171,6 @@ style:
173
171
  weight: &weight_bold 600
174
172
  case: title
175
173
  padding: 10
176
- # fill is semantically only active on axis_x (ordinal bucketed-time);
177
- # required on all AxisStyle instances since the model enforces it.
178
- fill: "null"
179
- # fiscal_year_start_month: same story as fill — only meaningful on
180
- # axis_x (year/yearquarter bucketing), required everywhere the model
181
- # enforces it. 1 = January = calendar-quarter convention.
182
- fiscal_year_start_month: 1
183
174
 
184
175
  axis_x:
185
176
  # Only intentional differences from `axis` — everything else cascades.
@@ -188,7 +179,8 @@ style:
188
179
  ticks:
189
180
  visible: true
190
181
  width: 1
191
- label:
182
+ offset: 1
183
+ labels:
192
184
  # Tilt angle candidates for the "tilt" step in the label overlap
193
185
  # strategy walk. Picker chooses the first angle where labels fit.
194
186
  # When no angle fits, the strategy walk continues to the next step
@@ -196,7 +188,6 @@ style:
196
188
  tilt_increments: [0, -30, -45, -60, -90]
197
189
  scale:
198
190
  padding: 0 # band/point padding around categorical x-axis values
199
- offset: 1
200
191
  # Engine fills missing time buckets on ordinal bucketed-time x-axes.
201
192
  # Bar/line/area inherit this global default. Scatter pins null under
202
193
  # style.charts.scatter.axis_x (layer 3.5) — scatter should never zero-fill or interpolate.
@@ -205,23 +196,28 @@ style:
205
196
 
206
197
  axis_y:
207
198
  # Y-specific tweaks only — orientation and tick width. Empty
208
- # grid/domain/label/title cascade from the canonical `axis`
199
+ # grid/line/labels/title cascade from the canonical `axis`
209
200
  # block via the InheritSlot graph at resolve time, so face-level patches on
210
201
  # `style.axis.*` reach axis_y through the same path.
211
202
  # "auto" → "right" by default; flips to "left" when endpoint labels are
212
203
  # on the right edge (line charts with style.line.endpoint_labels.visible).
204
+ # Also serves the categorical-orient role for horizontal bar's swapped
205
+ # categorical axis (categorical_orient was deleted 2026-08 trim).
213
206
  position: auto
214
- categorical_orient: left
215
207
  ticks:
216
208
  width: 1.5
217
- fill: "null"
218
- fiscal_year_start_month: 1
209
+ grid:
210
+ # zero-baseline gridline — measure axis only. Moved from axis.grid.zero
211
+ # (BaseAxisGridStyle has no zero; only MeasureGridStyle does).
212
+ zero:
213
+ width: 2
219
214
  scale:
220
- # Measure-axis domain stays pinned to the chart query's full unfiltered
221
- # extent across filter-variable changes (editorial default — a filter
222
- # changes data, not axes). "auto" opts a chart back into rescaling the
223
- # domain to the current filtered subset on every variable change.
224
- domain_strategy: stable
215
+ continuous:
216
+ # Measure-axis domain stays pinned to the chart query's full unfiltered
217
+ # extent across filter-variable changes (editorial default — a filter
218
+ # changes data, not axes). "auto" opts a chart back into rescaling the
219
+ # domain to the current filtered subset on every variable change.
220
+ domain_strategy: stable
225
221
  # Editorial default (settled 2026-07-16): data marks sit ~8% from
226
222
  # the visible axis edges. Zero-anchored: domain_max = data_max * 1.08.
227
223
  # Zoomed axes: both edges expand by 8% of the visible span, symmetric.
@@ -234,16 +230,14 @@ style:
234
230
  # tick width matching axis_y. Colors cascade from `axis` via the inherit graph.
235
231
  ticks:
236
232
  width: 1.5
237
- label:
233
+ labels:
238
234
  font: {}
239
- fill: "null"
240
- fiscal_year_start_month: 1
241
- # References the shared number_default alias (.3~s) so quantitative axes
242
- # AND the value labels that inherit this format read a bounded 3-sig-fig
243
- # SI number (9.18, 21.5M) — not the unbounded 6-sig-fig 9.18039 a bare ~s
244
- # gives on real data. Axis ticks are round, so the bound is invisible there.
245
- # Authors override per-chart via chart.format or style.axis_y.format.
246
- format: "number_default"
235
+ # References the shared number_default alias (.3~s) so quantitative axes
236
+ # AND the value labels that inherit this format read a bounded 3-sig-fig
237
+ # SI number (9.18, 21.5M) — not the unbounded 6-sig-fig 9.18039 a bare ~s
238
+ # gives on real data. Axis ticks are round, so the bound is invisible there.
239
+ # Authors override per-chart via chart.format or style.axis_y.labels.format.
240
+ format: "number_default"
247
241
 
248
242
  tooltip:
249
243
  format: ",.2~f"
@@ -563,7 +557,8 @@ style:
563
557
  # "auto" runs the Dataface smart-zero heuristic at render time.
564
558
  # Set to true/false to pin the zero-baseline for all bar charts.
565
559
  scale:
566
- zero: auto
560
+ continuous:
561
+ zero: auto
567
562
  endpoint_labels:
568
563
  visible: false
569
564
  label_offset: 6.0
@@ -573,12 +568,13 @@ style:
573
568
  visible: false
574
569
  ticks:
575
570
  visible: false
576
- label:
571
+ labels:
577
572
  padding: 6.0
578
573
 
579
574
  line:
580
575
  scale:
581
- zero: auto
576
+ continuous:
577
+ zero: auto
582
578
  endpoint_labels:
583
579
  visible: false
584
580
  label_offset: 6.0
@@ -588,7 +584,8 @@ style:
588
584
  # Authors who want a stacked area chart opt in via ``chart.style.stack: "zero"``.
589
585
  stack: none
590
586
  scale:
591
- zero: auto
587
+ continuous:
588
+ zero: auto
592
589
  endpoint_labels:
593
590
  visible: false
594
591
  label_offset: 6.0
@@ -739,7 +736,8 @@ style:
739
736
  # _ChartStyleBase, so mypy can't verify zero-arg construction. These stay required.
740
737
  scatter:
741
738
  scale:
742
- zero: auto
739
+ continuous:
740
+ zero: auto
743
741
  # Scatter always breaks at missing buckets (honest dots). Pinned here so a
744
742
  # future change to style.charts.axis_x.fill does not connect gaps on point marks.
745
743
  # Bar is intentionally NOT pinned: an author who opts into fill: linear on a bar
@@ -811,6 +809,18 @@ style:
811
809
  axis_y:
812
810
  grid:
813
811
  visible: false
812
+ # Heatmap's color channel carries the whole measure — a reader can't
813
+ # decode dark-vs-light without a key. Keep this a family default so
814
+ # themes that globally suppress legends (editorial direct-labeling)
815
+ # still inherit an honest gradient legend here, just as scatter opts
816
+ # back in.
817
+ legend:
818
+ visible: true
819
+ # The click-to-mute toggle mutes every mark whose color differs from
820
+ # the clicked one; a heatmap's color is its measure, near-unique per
821
+ # cell, so one click drops the whole chart to muted opacity — and a
822
+ # gradient key has no symbols to click.
823
+ interactive_legend: false
814
824
 
815
825
  # Series-label primitive. Typography for Cartesian text marks that name a
816
826
  # data series — endpoint labels and direct stack labels. Placement is
@@ -62,15 +62,13 @@ style:
62
62
  font:
63
63
  color: dft-creams.heading
64
64
  axis:
65
- domain:
65
+ line:
66
66
  color: dft-creams.grid
67
67
  grid:
68
68
  color: dft-creams.grid
69
- zero:
70
- color: dft-creams.subtitle
71
69
  ticks:
72
70
  color: dft-creams.grid
73
- label:
71
+ labels:
74
72
  font:
75
73
  color: dft-creams.muted
76
74
  title:
@@ -82,6 +80,8 @@ style:
82
80
  axis_y:
83
81
  grid:
84
82
  color: dft-creams.grid-emphasis # softer grid for the value axis (overrides default gray)
83
+ zero:
84
+ color: dft-creams.subtitle
85
85
  ticks:
86
86
  color: dft-creams.grid-emphasis
87
87
  axis_quantitative:
@@ -4,7 +4,7 @@ extends: stark
4
4
  style:
5
5
  charts:
6
6
  axis:
7
- label:
7
+ labels:
8
8
  font:
9
9
  color: dft-grays.muted
10
10
  title:
@@ -4,7 +4,7 @@ extends: stark
4
4
  style:
5
5
  charts:
6
6
  axis:
7
- label:
7
+ labels:
8
8
  font:
9
9
  color: dft-grays.muted
10
10
  title:
@@ -4,7 +4,7 @@ extends: stark
4
4
  style:
5
5
  charts:
6
6
  axis:
7
- label:
7
+ labels:
8
8
  font:
9
9
  color: dft-grays.muted
10
10
  title:
@@ -4,7 +4,7 @@ extends: stark
4
4
  style:
5
5
  charts:
6
6
  axis:
7
- label:
7
+ labels:
8
8
  font:
9
9
  color: dft-grays.muted
10
10
  title:
@@ -4,7 +4,7 @@ extends: stark
4
4
  style:
5
5
  charts:
6
6
  axis:
7
- label:
7
+ labels:
8
8
  font:
9
9
  color: dft-grays.muted
10
10
  title:
@@ -69,7 +69,7 @@ style:
69
69
  # dataface/src/dataface/core/defaults/palettes/categorical/editorial-10.yml.
70
70
  palette: editorial-10
71
71
  axis:
72
- label:
72
+ labels:
73
73
  font:
74
74
  color: dft-grays.muted
75
75
  title:
@@ -80,15 +80,15 @@ style:
80
80
  color: dft-grays.subtitle
81
81
  grid:
82
82
  color: dft-grays.grid
83
- zero:
84
- color: dft-grays.subtitle
85
83
  ticks:
86
84
  color: dft-grays.grid
87
- domain:
85
+ line:
88
86
  color: dft-grays.grid
89
87
  axis_y:
90
88
  grid:
91
89
  color: dft-grays.grid-emphasis
90
+ zero:
91
+ color: dft-grays.subtitle
92
92
  ticks:
93
93
  color: dft-grays.grid-emphasis
94
94
  axis_quantitative:
@@ -152,12 +152,12 @@ style:
152
152
  # left/right for a horizontal bar — vertical bars' bottom labels have
153
153
  # no edge to resolve against and stay untouched.
154
154
  axis_x:
155
- label:
155
+ labels:
156
156
  align: inward
157
157
  heatmap:
158
158
  # Inward-align the row labels (heatmap's y is always the nominal row axis).
159
159
  axis_y:
160
- label:
160
+ labels:
161
161
  align: inward
162
162
  line:
163
163
  endpoint_labels:
@@ -64,16 +64,14 @@ style:
64
64
  bar:
65
65
  background: *faint
66
66
  axis:
67
- domain:
67
+ line:
68
68
  color: *faint
69
69
  grid:
70
70
  color: *faint
71
71
  width: 1
72
- zero:
73
- color: &value dft-grays.canvas # baseline anchors at max contrast
74
72
  ticks:
75
73
  color: *faint
76
- label:
74
+ labels:
77
75
  font:
78
76
  color: *mid
79
77
  title:
@@ -82,8 +80,12 @@ style:
82
80
  axis_x:
83
81
  grid:
84
82
  visible: false
83
+ axis_y:
84
+ grid:
85
+ zero:
86
+ color: &value dft-grays.canvas # baseline anchors at max contrast
85
87
  axis_quantitative:
86
- label:
88
+ labels:
87
89
  font:
88
90
  color: *value # numeric readouts POP
89
91
  ticks:
@@ -104,9 +106,14 @@ style:
104
106
  visible: true
105
107
  bar:
106
108
  legend:
107
- visible: true # bars can't endpoint-label → keep legend
109
+ # A bar that direct-labels has this suppressed again by resolve. It is
110
+ # load-bearing for the bars that cannot — grouped, or stacked but
111
+ # disqualified by `multiples:`, a negative measure, or a series absent
112
+ # from the last column — which would otherwise carry no series key at
113
+ # all, since the theme hides legends globally.
114
+ visible: true
108
115
  axis_x: # inward-align horizontal-bar row labels (resolve scopes to left/right edge)
109
- label:
116
+ labels:
110
117
  align: inward
111
118
  scatter:
112
119
  legend:
@@ -141,7 +148,7 @@ style:
141
148
  gradient:
142
149
  palette: viridis
143
150
  axis_y: # inward-align heatmap row labels (y is always nominal)
144
- label:
151
+ labels:
145
152
  align: inward
146
153
  table:
147
154
  border:
@@ -35,20 +35,22 @@ style:
35
35
  single_series_palette: ['editorial-10.1'] # editorial denim single series
36
36
  # Generic BI chrome: medium-gray, visible — nothing recedes dramatically.
37
37
  axis:
38
- domain:
38
+ line:
39
39
  color: dft-grays.grid-emphasis
40
40
  grid:
41
41
  color: dft-grays.grid-emphasis # visible, medium (the BI-default look)
42
- zero:
43
- color: dft-grays.subtitle
44
42
  ticks:
45
43
  color: dft-grays.grid-emphasis
46
- label:
44
+ labels:
47
45
  font:
48
46
  color: dft-grays.muted
49
47
  title:
50
48
  font:
51
49
  color: dft-grays.subtitle
50
+ axis_y:
51
+ grid:
52
+ zero:
53
+ color: dft-grays.subtitle
52
54
  # Soft light tooltip, mirroring editorial's look -- stark (the structural
53
55
  # root plain extends directly) now sets a sharp/thick/no-shadow/triangle
54
56
  # tooltip for its own utilitarian voice; plain wants the same soft BI-report
@@ -45,20 +45,22 @@ style:
45
45
  # inherit. dark/editorial/cream override.
46
46
  single_series_palette: [dft-grays.black]
47
47
  axis:
48
- domain:
48
+ line:
49
49
  color: *color_ink
50
50
  ticks:
51
51
  color: *color_ink
52
52
  grid:
53
53
  color: *color_ink
54
- zero:
55
- color: *color_ink
56
- label:
54
+ labels:
57
55
  font:
58
56
  color: *color_ink
59
57
  title:
60
58
  font:
61
59
  color: *color_ink
60
+ axis_y:
61
+ grid:
62
+ zero:
63
+ color: *color_ink
62
64
 
63
65
  legend:
64
66
  label:
@@ -155,12 +157,12 @@ style:
155
157
  color: *color_ink
156
158
 
157
159
  axis_quantitative:
158
- label:
160
+ labels:
159
161
  font:
160
162
  family: '''DFT Sans Tabular'', Inter, system-ui, sans-serif'
161
163
 
162
- # Direct-label multi-series lines/areas at their endpoints rather than
163
- # leaning on a side legend. Set on the structural root so the plain
164
+ # Direct-label multi-series lines/areas/bars at their endpoints rather
165
+ # than leaning on a side legend. Set on the structural root so the plain
164
166
  # theme inherits it; the crafted themes (editorial/vivid/neon) already
165
167
  # set their own true.
166
168
  line:
@@ -169,6 +171,13 @@ style:
169
171
  area:
170
172
  endpoint_labels:
171
173
  visible: true
174
+ # A vertical bar's labels anchor to the last column's per-series stack
175
+ # midpoints; a horizontal bar's take the top-row rail instead. Resolve
176
+ # turns this back off wherever the rail cannot name every series — see
177
+ # _bar_endpoint_labels_for_stack.
178
+ bar:
179
+ endpoint_labels:
180
+ visible: true
172
181
 
173
182
  pie:
174
183
  total:
@@ -35,16 +35,14 @@ style:
35
35
  categorical:
36
36
  single_series_palette: ['vivid-10.1'] # brand-blue single series
37
37
  axis:
38
- domain:
38
+ line:
39
39
  color: *faint
40
40
  grid:
41
41
  color: *faint
42
42
  width: 1
43
- zero:
44
- color: &value dft-grays.ink # baseline anchors at max contrast
45
43
  ticks:
46
44
  color: *faint
47
- label:
45
+ labels:
48
46
  font:
49
47
  color: *mid # category/time labels = MID
50
48
  title:
@@ -53,9 +51,13 @@ style:
53
51
  axis_x:
54
52
  grid:
55
53
  visible: false
54
+ axis_y:
55
+ grid:
56
+ zero:
57
+ color: &value dft-grays.ink # baseline anchors at max contrast
56
58
  # Numeric axis readouts POP at the max VALUE tier (TradingView scale-text).
57
59
  axis_quantitative:
58
- label:
60
+ labels:
59
61
  font:
60
62
  color: *value
61
63
  ticks:
@@ -76,13 +78,18 @@ style:
76
78
  visible: true
77
79
  bar:
78
80
  legend:
79
- visible: true # bars can't endpoint-label → keep legend
81
+ # A bar that direct-labels has this suppressed again by resolve. It is
82
+ # load-bearing for the bars that cannot — grouped, or stacked but
83
+ # disqualified by `multiples:`, a negative measure, or a series absent
84
+ # from the last column — which would otherwise carry no series key at
85
+ # all, since the theme hides legends globally.
86
+ visible: true
80
87
  axis_x: # inward-align horizontal-bar row labels (resolve scopes to left/right edge)
81
- label:
88
+ labels:
82
89
  align: inward
83
90
  heatmap: # inward-align heatmap row labels (y is always nominal)
84
91
  axis_y:
85
- label:
92
+ labels:
86
93
  align: inward
87
94
  scatter:
88
95
  legend:
@@ -20,7 +20,6 @@ from dataface.core.diagnostics.codes_compile import (
20
20
  ERR_EXTRA_FIELD,
21
21
  ERR_FILE_NOT_FOUND,
22
22
  ERR_JINJA_ERROR,
23
- ERR_LABEL_VALUES_MEASURE_AXIS,
24
23
  ERR_LAYERS_AMBIGUOUS_Y_DOMAIN,
25
24
  ERR_LINE_Y_NOT_NUMERIC,
26
25
  ERR_LOG_SCALE_REQUIRES_POSITIVE_DATA,
@@ -114,6 +113,7 @@ from dataface.core.diagnostics.codes_unknown import ERR_INTERNAL
114
113
  from dataface.core.diagnostics.diagnostic import (
115
114
  ColumnSpan,
116
115
  Diagnostic,
116
+ RelatedLocation,
117
117
  SourceRange,
118
118
  build_diagnostic,
119
119
  display_message,
@@ -137,6 +137,7 @@ __all__ = [
137
137
  "Diagnostic",
138
138
  "SourceRange",
139
139
  "ColumnSpan",
140
+ "RelatedLocation",
140
141
  "build_diagnostic",
141
142
  "display_message",
142
143
  "build_doc_url",
@@ -166,7 +167,6 @@ __all__ = [
166
167
  "ERR_LOG_SCALE_REQUIRES_POSITIVE_DATA",
167
168
  "ERR_AREA_STACKED_LOG_SCALE_NOT_SUPPORTED",
168
169
  "ERR_FILE_NOT_FOUND",
169
- "ERR_LABEL_VALUES_MEASURE_AXIS",
170
170
  "ERR_TICKS_INTERVAL_MEASURE_AXIS",
171
171
  # Compile warnings
172
172
  "WARN_UNREFERENCED_CHART",