dataface 0.2.1.dev490__py3-none-any.whl → 0.2.1.dev516__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.
- dataface/agent_api/docs/yaml-reference.md +24 -7
- dataface/agent_api/project_session.py +1 -24
- dataface/core/compile/compiler.py +25 -0
- dataface/core/compile/errors.py +16 -3
- dataface/core/compile/models/chart/normalized/_base.py +2 -2
- dataface/core/compile/models/chart/normalized/callout.py +2 -2
- dataface/core/compile/models/face/normalized.py +4 -0
- dataface/core/compile/models/face/resolved.py +1 -0
- dataface/core/compile/models/primitives.py +0 -10
- dataface/core/compile/models/style/resolved/_base.py +5 -4
- dataface/core/compile/models/style/resolved/_cascade.py +6 -7
- dataface/core/compile/models/style/theme/__init__.py +2 -0
- dataface/core/compile/models/style/theme/axis.py +11 -12
- dataface/core/compile/models/style/theme/board.py +4 -0
- dataface/core/compile/models/style/theme/charts.py +3 -3
- dataface/core/compile/models/style/theme/legend.py +11 -0
- dataface/core/compile/models/style/theme/marks.py +18 -6
- dataface/core/compile/normalize_charts.py +14 -4
- dataface/core/compile/normalize_layout.py +36 -5
- dataface/core/compile/resolve/_resolve.py +65 -18
- dataface/core/compile/typography.py +26 -15
- dataface/core/defaults/themes/_base.yaml +10 -10
- dataface/core/errors/execution.py +11 -2
- dataface/core/errors/structured.py +1 -1
- dataface/core/execute/adapters/base.py +2 -0
- dataface/core/execute/executor.py +19 -21
- dataface/core/render/chart/callout.py +13 -24
- dataface/core/render/chart/data_table_attachment.py +195 -106
- dataface/core/render/chart/emitters/_cartesian.py +8 -2
- dataface/core/render/chart/emitters/_label_overlap.py +162 -235
- dataface/core/render/chart/emitters/_measured_label_padding.py +1 -1
- dataface/core/render/chart/emitters/bar.py +3 -3
- dataface/core/render/chart/emitters/heatmap.py +1 -1
- dataface/core/render/chart/rendering.py +44 -2
- dataface/core/render/chart/time_unit_detect.py +336 -54
- dataface/core/render/chart/type_inference.py +103 -31
- dataface/core/render/chart/vl_field_maps.py +2 -0
- dataface/core/render/comment_stripping.py +37 -0
- dataface/core/render/face_resolve.py +2 -0
- dataface/core/render/faces.py +53 -8
- dataface/core/render/layouts.py +16 -0
- dataface/core/render/script_embedding.py +9 -1
- dataface/core/render/sizing.py +22 -4
- dataface/core/render/templates/controls/_styles.css +92 -96
- dataface/core/render/templates/scripts/chart_interactivity.js +174 -174
- dataface/core/render/templates/scripts/variables.js +205 -208
- dataface/core/render/templates/svg/styles.css +12 -13
- dataface/core/render/warnings/value_labels_crowd_width.py +3 -3
- {dataface-0.2.1.dev490.dist-info → dataface-0.2.1.dev516.dist-info}/METADATA +1 -1
- {dataface-0.2.1.dev490.dist-info → dataface-0.2.1.dev516.dist-info}/RECORD +53 -52
- {dataface-0.2.1.dev490.dist-info → dataface-0.2.1.dev516.dist-info}/WHEEL +0 -0
- {dataface-0.2.1.dev490.dist-info → dataface-0.2.1.dev516.dist-info}/entry_points.txt +0 -0
- {dataface-0.2.1.dev490.dist-info → dataface-0.2.1.dev516.dist-info}/licenses/LICENSE +0 -0
|
@@ -1077,7 +1077,6 @@ Authored overlay for RootFontStyle. Root-level font configuration: FontStyle fie
|
|
|
1077
1077
|
| `decoration` | enum: "none", "line-through", "underline" | ✓ | Text decoration (underline, line-through, or none). |
|
|
1078
1078
|
| `case` | enum: "none", "sentence", "title", "upper", "lower", "slug", "camel" | ✓ | Letter-case transform applied at render time. 'title' uses Chicago/Gruber rules and preserves tokens with internal capitals (ARR, iPhone). 'sentence' uppercases only the first character. 'none' (default) emits the string without any letter-case change. |
|
|
1079
1079
|
| `line_height` | float | ✓ | Line height as a unitless multiple of font size. Cascades through Style.font to all text roles that carry a FontStyle slot. Body prose defaults to 1.25; titles override tighter via their font patch in theme YAML. |
|
|
1080
|
-
| `min_size` | float | ✓ | Minimum font size in pixels; used by the axis label shrink strategy. |
|
|
1081
1080
|
| `emoji` | enum: "monochrome", "color", "system-default", "disabled" | ✓ | Emoji rendering mode for the dashboard font stack. |
|
|
1082
1081
|
|
|
1083
1082
|
<a id="borderstyle"></a>
|
|
@@ -1097,6 +1096,7 @@ Authored overlay for TitleStyle. Board and face titles.
|
|
|
1097
1096
|
| Field | Type | Optional | Description |
|
|
1098
1097
|
|-------|------|:--------:|-------------|
|
|
1099
1098
|
| `font` | [FontStyle](#fontstyle) | ✓ | Title font style overrides. Unset fields fall back to [`style.font`](#style). |
|
|
1099
|
+
| `compact_weight` | str \| float | ✓ | Object-title font weight on tiny cards. |
|
|
1100
1100
|
| `sizes` | list[float] | ✓ | Font sizes for the H1–H6 heading ramp, indexed by ``face.level - 1``. Face/prose titles index this ramp directly (no width input). Object titles (chart/table/spark) combine ``width_offsets`` with a fixed object-title anchor to pick a slot. |
|
|
1101
1101
|
| `width_offsets` | [TitleWidthOffsetsStyle](#titlewidthoffsetsstyle) | ✓ | Additive level offsets by card width (tiny/narrow/medium/wide). Consumed by ``chart_title_spec`` only — object titles add the tier offset to a fixed anchor to pick an H slot from ``sizes``. Face/prose titles are level-only and do not consult these. |
|
|
1102
1102
|
| `min_height` | float | ✓ | Minimum title row height in pixels. |
|
|
@@ -1166,7 +1166,7 @@ Authored overlay for ChartsStyle. Registry of all chart-type styles plus shared
|
|
|
1166
1166
|
| `geoshape` | [GeoshapeChartStyle](#geoshapechartstyle) | ✓ | Geoshape (choropleth) chart style. |
|
|
1167
1167
|
| `point_map` | [PointMapChartStyle](#pointmapchartstyle) | ✓ | Point map chart style. |
|
|
1168
1168
|
| `pie` | [PieChartStyle](#piechartstyle) | ✓ | Pie/donut chart style. |
|
|
1169
|
-
| `series_label` | [SeriesLabelStyle](#serieslabelstyle) | ✓ | Shared series-label typography for endpoint
|
|
1169
|
+
| `series_label` | [SeriesLabelStyle](#serieslabelstyle) | ✓ | Shared series-label typography for endpoint and stack labels. |
|
|
1170
1170
|
| `kpi` | [KpiChartStyle](#kpichartstyle) | ✓ | KPI card chart style. |
|
|
1171
1171
|
| `table` | [TableChartStyle](#tablechartstyle) | ✓ | Table chart style. |
|
|
1172
1172
|
| `spark_bar` | [SparkBarChartStyle](#sparkbarchartstyle) | ✓ | Spark_bar (full-chart horizontal bar) style. |
|
|
@@ -1323,7 +1323,6 @@ Text appearance. Merged as a unit.
|
|
|
1323
1323
|
| `decoration` | enum: "none", "line-through", "underline" | ✓ | Text decoration (underline, line-through, or none). |
|
|
1324
1324
|
| `case` | enum: "none", "sentence", "title", "upper", "lower", "slug", "camel" | ✓ | Letter-case transform applied at render time. 'title' uses Chicago/Gruber rules and preserves tokens with internal capitals (ARR, iPhone). 'sentence' uppercases only the first character. 'none' (default) emits the string without any letter-case change. |
|
|
1325
1325
|
| `line_height` | float | ✓ | Line height as a unitless multiple of font size. Cascades through Style.font to all text roles that carry a FontStyle slot. Body prose defaults to 1.25; titles override tighter via their font patch in theme YAML. |
|
|
1326
|
-
| `min_size` | float | ✓ | Minimum font size in pixels; used by the axis label shrink strategy. |
|
|
1327
1326
|
|
|
1328
1327
|
<a id="paddingstyle"></a>
|
|
1329
1328
|
## PaddingStyle
|
|
@@ -1344,6 +1343,8 @@ Authored overlay for LegendStyle.
|
|
|
1344
1343
|
|-------|------|:--------:|-------------|
|
|
1345
1344
|
| `position` | str | ✓ | Legend position (e.g. 'right', 'bottom'). |
|
|
1346
1345
|
| `direction` | str | ✓ | Legend layout direction ('vertical' or 'horizontal'). |
|
|
1346
|
+
| `columns` | int | ✓ | Legend entry columns. Zero keeps the renderer default; positive values set Vega-Lite legend columns. |
|
|
1347
|
+
| `compact_columns` | int | ✓ | Entry columns for an automatic compact top-horizontal legend. |
|
|
1347
1348
|
| `label` | [LegendLabelStyle](#legendlabelstyle) | ✓ | Legend label style. |
|
|
1348
1349
|
| `title` | [LegendTitleStyle](#legendtitlestyle) | ✓ | Legend title style. |
|
|
1349
1350
|
| `visible` | bool | ✓ | Show the legend. None = legend visible; False = explicitly suppressed. |
|
|
@@ -2006,7 +2007,7 @@ Authored overlay for SeriesLabelStyle. Series-label primitive — typography for
|
|
|
2006
2007
|
|
|
2007
2008
|
| Field | Type | Optional | Description |
|
|
2008
2009
|
|-------|------|:--------:|-------------|
|
|
2009
|
-
| `font` | [
|
|
2010
|
+
| `font` | [SeriesLabelFontStyle](#serieslabelfontstyle) | ✓ | Series label font style overrides; cascade fills missing fields from charts.font. Unset fields fall back to [`style.charts.font`](#chartsstyle). |
|
|
2010
2011
|
|
|
2011
2012
|
<a id="layoutgapstyle"></a>
|
|
2012
2013
|
## LayoutGapStyle
|
|
@@ -2227,7 +2228,7 @@ Authored overlay for AxisElementStyle. Axis label or title: font + padding + VL-
|
|
|
2227
2228
|
| `angle` | float | ✓ | Label rotation angle in degrees; None uses Vega-Lite's default. |
|
|
2228
2229
|
| `align` | str | ✓ | Horizontal text alignment of labels; None uses Vega-Lite's default. |
|
|
2229
2230
|
| `baseline` | str | ✓ | Vertical text baseline of labels; None uses Vega-Lite's default. |
|
|
2230
|
-
| `overlap` | [AxisLabelOverlapConfig](#axislabeloverlapconfig) | ✓ | Label overlap strategy enablement. None inherits from the theme cascade. Set individual bools to enable/disable; the resolver applies them in fixed order
|
|
2231
|
+
| `overlap` | [AxisLabelOverlapConfig](#axislabeloverlapconfig) | ✓ | Label overlap strategy enablement. None inherits from the theme cascade. Set individual bools to enable/disable; the resolver applies them in fixed order tilt→skip. Temporal-bucketed axes ignore this and step the label cadence instead. |
|
|
2231
2232
|
| `separation` | float | ✓ | Minimum pixel separation between labels; None uses Vega-Lite's default (0px). |
|
|
2232
2233
|
| `visible` | bool | ✓ | Show axis labels; None uses Vega-Lite's default (labels shown). |
|
|
2233
2234
|
| `time_unit` | enum: "auto", "year", "yearquarter", "yearmonth", "yearweek", "yearmonthdate", "monthofyear", "dayofweek", "dayofmonth", "dayofyear", "hourofday", "none" | ✓ | Label cadence for temporal axes; None inherits from the parent axis time_unit. |
|
|
@@ -2584,6 +2585,23 @@ Authored overlay for HistogramChartMarksStyle. Histogram-family mark overrides.
|
|
|
2584
2585
|
| `bar` | [BarMarkStyle](#barmarkstyle) | ✓ | Bar mark overrides; inherits from global. |
|
|
2585
2586
|
| `rule` | [RuleMarkStyle](#rulemarkstyle) | ✓ | Rule mark overrides; None inherits global. |
|
|
2586
2587
|
|
|
2588
|
+
<a id="serieslabelfontstyle"></a>
|
|
2589
|
+
## SeriesLabelFontStyle
|
|
2590
|
+
Authored overlay for SeriesLabelFontStyle. Series-label typography with a compact width-tier size.
|
|
2591
|
+
|
|
2592
|
+
| Field | Type | Optional | Description |
|
|
2593
|
+
|-------|------|:--------:|-------------|
|
|
2594
|
+
| `family` | str | ✓ | Font family name (e.g., 'sans-serif', 'Roboto'). |
|
|
2595
|
+
| `color` | str | ✓ | Text color as a CSS color string. |
|
|
2596
|
+
| `size` | float | ✓ | Font size in pixels. |
|
|
2597
|
+
| `weight` | str \| float | ✓ | Font weight (e.g., 'bold', 400, 700). |
|
|
2598
|
+
| `style` | enum: "normal", "italic" | ✓ | Font style (normal or italic). |
|
|
2599
|
+
| `decoration` | enum: "none", "line-through", "underline" | ✓ | Text decoration (underline, line-through, or none). |
|
|
2600
|
+
| `case` | enum: "none", "sentence", "title", "upper", "lower", "slug", "camel" | ✓ | Letter-case transform applied at render time. 'title' uses Chicago/Gruber rules and preserves tokens with internal capitals (ARR, iPhone). 'sentence' uppercases only the first character. 'none' (default) emits the string without any letter-case change. |
|
|
2601
|
+
| `line_height` | float | ✓ | Line height as a unitless multiple of font size. Cascades through Style.font to all text roles that carry a FontStyle slot. Body prose defaults to 1.25; titles override tighter via their font patch in theme YAML. |
|
|
2602
|
+
| `compact_size` | float | ✓ | Series-label font size in pixels on tiny and narrow cards. |
|
|
2603
|
+
| `compact_weight` | str \| float | ✓ | Series-label font weight on tiny and narrow cards. |
|
|
2604
|
+
|
|
2587
2605
|
<a id="detailsarrowstyle"></a>
|
|
2588
2606
|
## DetailsArrowStyle
|
|
2589
2607
|
Authored overlay for DetailsArrowStyle. Layout and font style for the expand/collapse arrow chevron.
|
|
@@ -2627,11 +2645,10 @@ Authored overlay for AxisGridZeroStyle. Zero-baseline gridline color and width o
|
|
|
2627
2645
|
|
|
2628
2646
|
<a id="axislabeloverlapconfig"></a>
|
|
2629
2647
|
## AxisLabelOverlapConfig
|
|
2630
|
-
Authored overlay for AxisLabelOverlapConfig.
|
|
2648
|
+
Authored overlay for AxisLabelOverlapConfig. Two-bool overlap strategy enablement for CATEGORICAL axis labels.
|
|
2631
2649
|
|
|
2632
2650
|
| Field | Type | Optional | Description |
|
|
2633
2651
|
|-------|------|:--------:|-------------|
|
|
2634
|
-
| `shrink` | bool | ✓ | Enable font-size shrink strategy; None inherits from parent axis. |
|
|
2635
2652
|
| `tilt` | bool | ✓ | Enable label tilt strategy; None inherits from parent axis. |
|
|
2636
2653
|
| `skip` | bool | ✓ | Enable parity-skip strategy (drops every other label); None inherits from parent axis. |
|
|
2637
2654
|
|
|
@@ -36,7 +36,6 @@ from dataface.core.execute.adapters.adapter_registry import (
|
|
|
36
36
|
build_adapter_registry,
|
|
37
37
|
)
|
|
38
38
|
from dataface.core.execute.cache_backend import QueryResultCache
|
|
39
|
-
from dataface.core.execute.observability import WarehouseObserver
|
|
40
39
|
from dataface.core.inspect.query_validator import (
|
|
41
40
|
QueryDiagnostic,
|
|
42
41
|
validate_query as _core_validate_query,
|
|
@@ -75,11 +74,9 @@ class ProjectSession:
|
|
|
75
74
|
_read_only: bool
|
|
76
75
|
_connection_string: str | None
|
|
77
76
|
_dialect: str
|
|
78
|
-
_target: str
|
|
79
77
|
_duckdb_config: dict[str, Any] | None
|
|
80
78
|
_allow_external_access_in_readonly: bool
|
|
81
79
|
_resolver: SourceResolver | None
|
|
82
|
-
_observers: list[WarehouseObserver]
|
|
83
80
|
_file_materializer: FileSourceMaterializer | None
|
|
84
81
|
|
|
85
82
|
def __init__(
|
|
@@ -88,7 +85,6 @@ class ProjectSession:
|
|
|
88
85
|
cache: QueryResultCache | None = None,
|
|
89
86
|
adapter_registry: AdapterRegistry | None = None,
|
|
90
87
|
read_only: bool = True,
|
|
91
|
-
observers: list[WarehouseObserver] | None = None,
|
|
92
88
|
file_materializer: FileSourceMaterializer | None = None,
|
|
93
89
|
) -> None:
|
|
94
90
|
self.project = project
|
|
@@ -103,11 +99,9 @@ class ProjectSession:
|
|
|
103
99
|
self._read_only = read_only
|
|
104
100
|
self._connection_string = None
|
|
105
101
|
self._dialect = "duckdb"
|
|
106
|
-
self._target = "dev"
|
|
107
102
|
self._duckdb_config = None
|
|
108
103
|
self._allow_external_access_in_readonly = False
|
|
109
104
|
self._resolver = None
|
|
110
|
-
self._observers = list(observers) if observers else []
|
|
111
105
|
self._file_materializer = file_materializer
|
|
112
106
|
|
|
113
107
|
@classmethod
|
|
@@ -119,11 +113,9 @@ class ProjectSession:
|
|
|
119
113
|
read_only: bool = True,
|
|
120
114
|
connection_string: str | None = None,
|
|
121
115
|
dialect: str = "duckdb",
|
|
122
|
-
target: str = "dev",
|
|
123
116
|
duckdb_config: dict[str, Any] | None = None,
|
|
124
117
|
allow_external_access_in_readonly: bool = False,
|
|
125
118
|
resolver: SourceResolver | None = None,
|
|
126
|
-
observers: list[WarehouseObserver] | None = None,
|
|
127
119
|
) -> Self:
|
|
128
120
|
"""Construct a ProjectSession rooted at *project_dir*.
|
|
129
121
|
|
|
@@ -142,11 +134,9 @@ class ProjectSession:
|
|
|
142
134
|
project=FilesystemProject(Path(project_dir).resolve()),
|
|
143
135
|
cache=cache,
|
|
144
136
|
read_only=read_only,
|
|
145
|
-
observers=observers,
|
|
146
137
|
)
|
|
147
138
|
session._connection_string = connection_string
|
|
148
139
|
session._dialect = dialect
|
|
149
|
-
session._target = target
|
|
150
140
|
session._duckdb_config = duckdb_config
|
|
151
141
|
session._allow_external_access_in_readonly = allow_external_access_in_readonly
|
|
152
142
|
session._resolver = resolver
|
|
@@ -160,13 +150,8 @@ class ProjectSession:
|
|
|
160
150
|
cache: QueryResultCache | None = None,
|
|
161
151
|
adapter_registry: AdapterRegistry | None = None,
|
|
162
152
|
read_only: bool = True,
|
|
163
|
-
connection_string: str | None = None,
|
|
164
|
-
dialect: str = "duckdb",
|
|
165
|
-
target: str = "dev",
|
|
166
153
|
duckdb_config: dict[str, Any] | None = None,
|
|
167
154
|
allow_external_access_in_readonly: bool = False,
|
|
168
|
-
resolver: SourceResolver | None = None,
|
|
169
|
-
observers: list[WarehouseObserver] | None = None,
|
|
170
155
|
file_materializer: FileSourceMaterializer | None = None,
|
|
171
156
|
) -> Self:
|
|
172
157
|
"""Construct a ProjectSession from a pre-built Project.
|
|
@@ -185,15 +170,10 @@ class ProjectSession:
|
|
|
185
170
|
cache=cache,
|
|
186
171
|
adapter_registry=adapter_registry,
|
|
187
172
|
read_only=read_only,
|
|
188
|
-
observers=observers,
|
|
189
173
|
file_materializer=file_materializer,
|
|
190
174
|
)
|
|
191
|
-
session._connection_string = connection_string
|
|
192
|
-
session._dialect = dialect
|
|
193
|
-
session._target = target
|
|
194
175
|
session._duckdb_config = duckdb_config
|
|
195
176
|
session._allow_external_access_in_readonly = allow_external_access_in_readonly
|
|
196
|
-
session._resolver = resolver
|
|
197
177
|
return session
|
|
198
178
|
|
|
199
179
|
@classmethod
|
|
@@ -201,13 +181,12 @@ class ProjectSession:
|
|
|
201
181
|
cls,
|
|
202
182
|
face_file: Path | str,
|
|
203
183
|
*,
|
|
204
|
-
read_only: bool = True,
|
|
205
184
|
cache: QueryResultCache | None = None,
|
|
206
185
|
) -> Self:
|
|
207
186
|
"""Open a ProjectSession rooted at the project directory discovered upward from face_file."""
|
|
208
187
|
resolved = Path(face_file).resolve()
|
|
209
188
|
project_root = find_project_root(resolved.parent, boundary=None)
|
|
210
|
-
return cls.open(project_root,
|
|
189
|
+
return cls.open(project_root, cache=cache)
|
|
211
190
|
|
|
212
191
|
def __enter__(self) -> Self:
|
|
213
192
|
return self
|
|
@@ -241,11 +220,9 @@ class ProjectSession:
|
|
|
241
220
|
read_only=self._read_only,
|
|
242
221
|
connection_string=self._connection_string,
|
|
243
222
|
profile_type=self._dialect,
|
|
244
|
-
target=self._target,
|
|
245
223
|
duckdb_config=self._duckdb_config,
|
|
246
224
|
allow_external_access_in_readonly=self._allow_external_access_in_readonly,
|
|
247
225
|
resolver=self._resolver,
|
|
248
|
-
observers=self._observers,
|
|
249
226
|
)
|
|
250
227
|
|
|
251
228
|
def refresh(self) -> None:
|
|
@@ -82,8 +82,10 @@ from dataface.core.compile.parser import (
|
|
|
82
82
|
)
|
|
83
83
|
from dataface.core.compile.validator import validate_face
|
|
84
84
|
from dataface.core.compile.yaml_error_formatter import (
|
|
85
|
+
find_yaml_line_number,
|
|
85
86
|
format_validation_errors_structured,
|
|
86
87
|
)
|
|
88
|
+
from dataface.core.errors.codes_compile import DF_COMPILE_UNKNOWN_QUERY
|
|
87
89
|
from dataface.core.errors.structured import StructuredError
|
|
88
90
|
from dataface.core.warnings import unreferenced_chart
|
|
89
91
|
from dataface.core.warnings.base import RenderWarning
|
|
@@ -187,6 +189,18 @@ def compile_authored_face(
|
|
|
187
189
|
# ════════════════════════════════════════════════════════════════════
|
|
188
190
|
validation_errors = validate_face(authored)
|
|
189
191
|
if validation_errors:
|
|
192
|
+
for error in validation_errors:
|
|
193
|
+
if error.code is not DF_COMPILE_UNKNOWN_QUERY:
|
|
194
|
+
continue
|
|
195
|
+
chart_name = error.fields["chart_name"]
|
|
196
|
+
if authored.charts is None or chart_name not in authored.charts:
|
|
197
|
+
continue
|
|
198
|
+
ref_path = ["charts", chart_name, "query"]
|
|
199
|
+
line = find_yaml_line_number(_yaml_content, ref_path)
|
|
200
|
+
if line is None:
|
|
201
|
+
continue
|
|
202
|
+
error.line = line
|
|
203
|
+
error.field_path = ".".join(ref_path)
|
|
190
204
|
return CompileResult(errors=[e.to_structured() for e in validation_errors])
|
|
191
205
|
|
|
192
206
|
# ════════════════════════════════════════════════════════════════════
|
|
@@ -362,6 +376,17 @@ def compile_authored_face(
|
|
|
362
376
|
errors.extend(e.structured_errors)
|
|
363
377
|
return CompileResult(errors=errors)
|
|
364
378
|
except ReferenceError as e:
|
|
379
|
+
if (
|
|
380
|
+
e.ref_path
|
|
381
|
+
and len(e.ref_path) >= 2
|
|
382
|
+
and e.ref_path[0] == "charts"
|
|
383
|
+
and authored.charts is not None
|
|
384
|
+
and e.ref_path[1] in authored.charts
|
|
385
|
+
):
|
|
386
|
+
line = find_yaml_line_number(_yaml_content, list(e.ref_path))
|
|
387
|
+
if line is not None:
|
|
388
|
+
e.line = line
|
|
389
|
+
e.field_path = ".".join(e.ref_path)
|
|
365
390
|
return CompileResult(errors=[e.to_structured()])
|
|
366
391
|
except CompilationError as e:
|
|
367
392
|
return CompileResult(errors=[e.to_structured()])
|
dataface/core/compile/errors.py
CHANGED
|
@@ -21,6 +21,7 @@ Refs #94
|
|
|
21
21
|
|
|
22
22
|
from __future__ import annotations
|
|
23
23
|
|
|
24
|
+
from collections.abc import Sequence
|
|
24
25
|
from typing import Any
|
|
25
26
|
|
|
26
27
|
from dataface.core.errors.base import DatafaceError
|
|
@@ -37,6 +38,7 @@ class CompilationError(DatafaceError):
|
|
|
37
38
|
location: Optional location in source (line number, path, etc.)
|
|
38
39
|
line: Optional line number in the YAML file
|
|
39
40
|
column: Optional column number in the YAML file
|
|
41
|
+
field_path: Optional dotted path to the field that failed
|
|
40
42
|
context: Optional YAML snippet showing the error location
|
|
41
43
|
suggestion: Optional helpful suggestion for fixing the error
|
|
42
44
|
"""
|
|
@@ -48,6 +50,7 @@ class CompilationError(DatafaceError):
|
|
|
48
50
|
location: str | None = None
|
|
49
51
|
line: int | None = None
|
|
50
52
|
column: int | None = None
|
|
53
|
+
field_path: str = ""
|
|
51
54
|
context: str | None = None
|
|
52
55
|
suggestion: str | None = None
|
|
53
56
|
|
|
@@ -57,6 +60,7 @@ class CompilationError(DatafaceError):
|
|
|
57
60
|
location: str | None = None,
|
|
58
61
|
line: int | None = None,
|
|
59
62
|
column: int | None = None,
|
|
63
|
+
field_path: str = "",
|
|
60
64
|
context: str | None = None,
|
|
61
65
|
suggestion: str | None = None,
|
|
62
66
|
):
|
|
@@ -64,6 +68,7 @@ class CompilationError(DatafaceError):
|
|
|
64
68
|
self.location = location
|
|
65
69
|
self.line = line
|
|
66
70
|
self.column = column
|
|
71
|
+
self.field_path = field_path
|
|
67
72
|
self.context = context
|
|
68
73
|
self.suggestion = suggestion
|
|
69
74
|
self.fields: dict[str, Any] = {}
|
|
@@ -132,7 +137,7 @@ class ValidationError(CompilationError):
|
|
|
132
137
|
... print(f"Validation failed at line {e.line}: {e}")
|
|
133
138
|
"""
|
|
134
139
|
|
|
135
|
-
field_path: str
|
|
140
|
+
field_path: str = ""
|
|
136
141
|
invalid_value: str | None = None
|
|
137
142
|
valid_values: list[str] | None = None
|
|
138
143
|
|
|
@@ -147,7 +152,7 @@ class ValidationError(CompilationError):
|
|
|
147
152
|
invalid_value: str | None = None,
|
|
148
153
|
valid_values: list[str] | None = None,
|
|
149
154
|
):
|
|
150
|
-
self.field_path = field_path
|
|
155
|
+
self.field_path = field_path if field_path is not None else ""
|
|
151
156
|
self.invalid_value = invalid_value
|
|
152
157
|
self.valid_values = valid_values
|
|
153
158
|
|
|
@@ -155,6 +160,7 @@ class ValidationError(CompilationError):
|
|
|
155
160
|
message,
|
|
156
161
|
location=location if not context else None,
|
|
157
162
|
line=line,
|
|
163
|
+
field_path=field_path if field_path is not None else "",
|
|
158
164
|
context=context,
|
|
159
165
|
suggestion=suggestion,
|
|
160
166
|
)
|
|
@@ -181,11 +187,18 @@ class ReferenceError(CompilationError):
|
|
|
181
187
|
"""
|
|
182
188
|
|
|
183
189
|
ref: str | None = None
|
|
190
|
+
ref_path: tuple[str, ...] = ()
|
|
184
191
|
|
|
185
|
-
def __init__(
|
|
192
|
+
def __init__(
|
|
193
|
+
self,
|
|
194
|
+
ref: str,
|
|
195
|
+
context: str | None = None,
|
|
196
|
+
ref_path: Sequence[str] = (),
|
|
197
|
+
):
|
|
186
198
|
from dataface.core.errors.codes_compile import DF_COMPILE_UNRESOLVED_REFERENCE
|
|
187
199
|
|
|
188
200
|
self.ref = ref
|
|
201
|
+
self.ref_path = tuple(ref_path)
|
|
189
202
|
ctx_suffix = f" in {context}" if context else ""
|
|
190
203
|
message = DF_COMPILE_UNRESOLVED_REFERENCE.message_template.format(
|
|
191
204
|
ref=ref, context=ctx_suffix
|
|
@@ -51,8 +51,8 @@ class _BaseChartFields(BaseModel):
|
|
|
51
51
|
default_factory=set,
|
|
52
52
|
description="Variable names this chart depends on.",
|
|
53
53
|
)
|
|
54
|
-
source_path: str
|
|
55
|
-
default=
|
|
54
|
+
source_path: str = Field(
|
|
55
|
+
default="",
|
|
56
56
|
description="YAML path for edit-back support (e.g. 'charts.revenue').",
|
|
57
57
|
)
|
|
58
58
|
query_is_inline: bool = Field(
|
|
@@ -31,8 +31,8 @@ class CalloutChart(BaseModel):
|
|
|
31
31
|
style: CalloutChartStylePatch | None = Field(
|
|
32
32
|
default=None, description="Chart-local style overrides."
|
|
33
33
|
)
|
|
34
|
-
source_path: str
|
|
35
|
-
default=
|
|
34
|
+
source_path: str = Field(
|
|
35
|
+
default="",
|
|
36
36
|
description="YAML path for edit-back support.",
|
|
37
37
|
)
|
|
38
38
|
variable_dependencies: set[str] = Field(
|
|
@@ -66,6 +66,10 @@ class LayoutItem(BaseModel):
|
|
|
66
66
|
face: "Face | None" = Field(
|
|
67
67
|
default=None, description="Compiled face, present when type == 'face'."
|
|
68
68
|
)
|
|
69
|
+
source_path: str = Field(
|
|
70
|
+
default="",
|
|
71
|
+
description="YAML path to this layout item, e.g. rows[0] or cols[1].",
|
|
72
|
+
)
|
|
69
73
|
|
|
70
74
|
# Grid positioning (optional) - 0-based, in grid units
|
|
71
75
|
row: int | None = Field(
|
|
@@ -213,13 +213,6 @@ class FontStyle(_PatchBase):
|
|
|
213
213
|
"defaults to 1.25; titles override tighter via their font patch in theme YAML."
|
|
214
214
|
),
|
|
215
215
|
)
|
|
216
|
-
# None on most font slots; set only on axis label fonts via theme YAML.
|
|
217
|
-
# Bounds how far the shrink strategy may reduce the font before falling
|
|
218
|
-
# through to tilt or skip.
|
|
219
|
-
min_size: float | None = Field(
|
|
220
|
-
default=None,
|
|
221
|
-
description="Minimum font size in pixels; used by the axis label shrink strategy.",
|
|
222
|
-
)
|
|
223
216
|
|
|
224
217
|
|
|
225
218
|
_CSS_NAMED_COLORS = frozenset(
|
|
@@ -263,9 +256,6 @@ class ResolvedFontStyle(BaseModel):
|
|
|
263
256
|
decoration: Literal["none", "line-through", "underline"]
|
|
264
257
|
case: Literal["none", "sentence", "title", "upper", "lower", "slug", "camel"]
|
|
265
258
|
line_height: float
|
|
266
|
-
# None on most resolved fonts; populated only when the cascaded FontStyle
|
|
267
|
-
# carries a min_size (axis label fonts via theme YAML).
|
|
268
|
-
min_size: float | None = None
|
|
269
259
|
|
|
270
260
|
|
|
271
261
|
class RuleStyle(BaseModel):
|
|
@@ -79,10 +79,10 @@ class ResolvedAxisLabelOverlapConfig:
|
|
|
79
79
|
"""Resolved axis label overlap strategy enablement — all fields required.
|
|
80
80
|
|
|
81
81
|
True = strategy enabled; False = disabled. Fixed application order in the
|
|
82
|
-
resolver:
|
|
82
|
+
resolver: tilt → skip. Categorical axes only — temporal-bucketed axes
|
|
83
|
+
step the label cadence instead (see emitters/_label_overlap.py).
|
|
83
84
|
"""
|
|
84
85
|
|
|
85
|
-
shrink: bool
|
|
86
86
|
tilt: bool
|
|
87
87
|
skip: bool
|
|
88
88
|
|
|
@@ -238,6 +238,8 @@ class ResolvedLegendStyle(BaseModel):
|
|
|
238
238
|
|
|
239
239
|
position: str
|
|
240
240
|
direction: str
|
|
241
|
+
columns: int
|
|
242
|
+
compact_columns: int
|
|
241
243
|
label: ResolvedLegendElementStyle
|
|
242
244
|
title: ResolvedLegendElementStyle
|
|
243
245
|
interactive_legend: bool
|
|
@@ -307,8 +309,7 @@ class ResolvedChartsStyle:
|
|
|
307
309
|
area: AreaChartStyle
|
|
308
310
|
scatter: ScatterChartStyle
|
|
309
311
|
view: ViewStyle
|
|
310
|
-
# Series-label primitive — shared typography for endpoint labels
|
|
311
|
-
# direct stack labels, and slice callouts.
|
|
312
|
+
# Series-label primitive — shared typography for endpoint and stack labels.
|
|
312
313
|
series_label: SeriesLabelStyle
|
|
313
314
|
pie: PieChartStyle
|
|
314
315
|
geoshape: GeoshapeChartStyle
|
|
@@ -101,7 +101,6 @@ def resolve_cascaded_font(font: FontStyle, path: str = "") -> ResolvedFontStyle:
|
|
|
101
101
|
decoration=font.decoration,
|
|
102
102
|
case=font.case,
|
|
103
103
|
line_height=font.line_height,
|
|
104
|
-
min_size=font.min_size,
|
|
105
104
|
)
|
|
106
105
|
|
|
107
106
|
|
|
@@ -469,20 +468,18 @@ def _resolve_overlap_config(
|
|
|
469
468
|
|
|
470
469
|
Returns None when the cascade did not populate overlap (resolver will skip).
|
|
471
470
|
Raises if the cascade yielded a partial struct with any None fields — the
|
|
472
|
-
theme must supply
|
|
471
|
+
theme must supply both bools via _base.yaml so this never fires in normal
|
|
473
472
|
operation; it fires only when overlap is present but incomplete, which signals
|
|
474
473
|
a broken theme.
|
|
475
474
|
"""
|
|
476
475
|
if overlap is None:
|
|
477
476
|
return None
|
|
478
|
-
if overlap.
|
|
477
|
+
if overlap.tilt is None or overlap.skip is None:
|
|
479
478
|
raise ValueError(
|
|
480
479
|
f"axis.label.overlap has None fields after cascade: {overlap!r}; "
|
|
481
|
-
"theme must supply
|
|
480
|
+
"theme must supply both bools (tilt, skip) via _base.yaml"
|
|
482
481
|
)
|
|
483
|
-
return ResolvedAxisLabelOverlapConfig(
|
|
484
|
-
shrink=overlap.shrink, tilt=overlap.tilt, skip=overlap.skip
|
|
485
|
-
)
|
|
482
|
+
return ResolvedAxisLabelOverlapConfig(tilt=overlap.tilt, skip=overlap.skip)
|
|
486
483
|
|
|
487
484
|
|
|
488
485
|
def build_resolved_axis(axis: AxisStyle) -> ResolvedAxisStyle:
|
|
@@ -556,6 +553,8 @@ def _build_resolved_legend(legend: LegendStyle) -> ResolvedLegendStyle:
|
|
|
556
553
|
return ResolvedLegendStyle(
|
|
557
554
|
position=legend.position,
|
|
558
555
|
direction=legend.direction,
|
|
556
|
+
columns=legend.columns,
|
|
557
|
+
compact_columns=legend.compact_columns,
|
|
559
558
|
visible=legend.visible,
|
|
560
559
|
interactive_legend=legend.interactive_legend,
|
|
561
560
|
symbol_limit=legend.symbol_limit,
|
|
@@ -158,6 +158,7 @@ from dataface.core.compile.models.style.theme.marks import (
|
|
|
158
158
|
ProjectionStyle,
|
|
159
159
|
RectMarkStyle,
|
|
160
160
|
RuleMarkStyle,
|
|
161
|
+
SeriesLabelFontStyle,
|
|
161
162
|
SeriesLabelStyle,
|
|
162
163
|
SliceLabelsStyle,
|
|
163
164
|
SliceMarkStyle,
|
|
@@ -321,6 +322,7 @@ __all__ = [
|
|
|
321
322
|
"ScatterChartMarksStyle",
|
|
322
323
|
"ScatterChartStyle",
|
|
323
324
|
"ScatterLayerStyle",
|
|
325
|
+
"SeriesLabelFontStyle",
|
|
324
326
|
"SeriesLabelStyle",
|
|
325
327
|
"SliceLabelsStyle",
|
|
326
328
|
"SliceMarkStyle",
|
|
@@ -16,30 +16,27 @@ from dataface.core.compile.models.markers import (
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class AxisLabelOverlapConfig(BaseModel):
|
|
19
|
-
"""
|
|
19
|
+
"""Two-bool overlap strategy enablement for CATEGORICAL axis labels.
|
|
20
20
|
|
|
21
21
|
Each bool enables (True) or disables (False) a strategy step.
|
|
22
22
|
None means "not authored — inherit from the theme cascade."
|
|
23
|
-
The resolver applies enabled strategies in fixed order:
|
|
23
|
+
The resolver applies enabled strategies in fixed order: tilt → skip.
|
|
24
|
+
Temporal-bucketed axes don't use this walk at all — they step the label
|
|
25
|
+
cadence instead (coarsen month → quarter → year at a constant font size
|
|
26
|
+
and angle=0); see resolve_axis_x_overlap in emitters/_label_overlap.py.
|
|
24
27
|
|
|
25
28
|
Attributes:
|
|
26
|
-
shrink: Reduce font size to the type-stack floor (min_size) if needed.
|
|
27
29
|
tilt: Walk tilt_increments and apply the first angle that fits.
|
|
28
30
|
skip: Emit labelOverlap:parity (every other label dropped).
|
|
29
31
|
|
|
30
32
|
Example YAML:
|
|
31
33
|
overlap:
|
|
32
|
-
shrink: true
|
|
33
34
|
tilt: true
|
|
34
35
|
skip: true
|
|
35
36
|
"""
|
|
36
37
|
|
|
37
38
|
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
38
39
|
|
|
39
|
-
shrink: bool | None = Field(
|
|
40
|
-
default=None,
|
|
41
|
-
description="Enable font-size shrink strategy; None inherits from parent axis.",
|
|
42
|
-
)
|
|
43
40
|
tilt: bool | None = Field(
|
|
44
41
|
default=None,
|
|
45
42
|
description="Enable label tilt strategy; None inherits from parent axis.",
|
|
@@ -261,12 +258,13 @@ class AxisElementStyle(BaseModel):
|
|
|
261
258
|
default=None,
|
|
262
259
|
description="Vertical text baseline of labels; None uses Vega-Lite's default.",
|
|
263
260
|
)
|
|
264
|
-
#
|
|
261
|
+
# Two-bool struct enabling/disabling each overlap strategy (categorical axes
|
|
262
|
+
# only — temporal-bucketed axes step the label cadence instead).
|
|
265
263
|
# None = not authored — inherit the parent axis's setting from the cascade.
|
|
266
264
|
# SkipInheritSlots(cascade=True): if overlap is None on the child axis,
|
|
267
265
|
# the whole struct is copied from the parent; if present, individual None
|
|
268
266
|
# fields are filled from the parent (enabling partial authoring like
|
|
269
|
-
# `overlap: { tilt: false }` while inheriting
|
|
267
|
+
# `overlap: { tilt: false }` while inheriting skip).
|
|
270
268
|
overlap: Annotated[
|
|
271
269
|
AxisLabelOverlapConfig | None,
|
|
272
270
|
SkipInheritSlots(cascade=True),
|
|
@@ -275,7 +273,8 @@ class AxisElementStyle(BaseModel):
|
|
|
275
273
|
description=(
|
|
276
274
|
"Label overlap strategy enablement. None inherits from the theme cascade. "
|
|
277
275
|
"Set individual bools to enable/disable; the resolver applies them in fixed "
|
|
278
|
-
"order
|
|
276
|
+
"order tilt→skip. Temporal-bucketed axes ignore this and step the label "
|
|
277
|
+
"cadence instead."
|
|
279
278
|
),
|
|
280
279
|
)
|
|
281
280
|
separation: float | None = Field(
|
|
@@ -892,7 +891,7 @@ class AxisStyle(BaseModel):
|
|
|
892
891
|
# Fiscal-year-start month for year/yearquarter bucket anchoring on axis_x.
|
|
893
892
|
# 1 (January) is the calendar-quarter convention; any other value shifts
|
|
894
893
|
# both the bucket boundaries (_enumerate_buckets) and the Q1..Q4 numbering
|
|
895
|
-
# / year-boundary detection (
|
|
894
|
+
# / year-boundary detection (is_label_opener, opens_label_period,
|
|
896
895
|
# _quarter_label) by the same offset. Required (like fill) since the model
|
|
897
896
|
# enforces it on every axis variant; semantically only active on axis_x
|
|
898
897
|
# for the year/yearquarter grains — yearmonth/yearweek/yearmonthdate
|
|
@@ -10,6 +10,7 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_valida
|
|
|
10
10
|
from dataface.core.compile.models.markers import (
|
|
11
11
|
InheritSlot,
|
|
12
12
|
Merge,
|
|
13
|
+
SkipInheritSlots,
|
|
13
14
|
Strategy,
|
|
14
15
|
)
|
|
15
16
|
from dataface.core.compile.models.primitives import (
|
|
@@ -312,6 +313,9 @@ class TitleStyle(BaseModel):
|
|
|
312
313
|
font: Annotated[FontStyle, InheritSlot(from_path="Style.font")] = Field(
|
|
313
314
|
default_factory=FontStyle, description="Title font style overrides."
|
|
314
315
|
)
|
|
316
|
+
compact_weight: Annotated[str | float, SkipInheritSlots()] = Field(
|
|
317
|
+
description="Object-title font weight on tiny cards."
|
|
318
|
+
)
|
|
315
319
|
sizes: Annotated[list[float], Merge(Strategy.OVERRIDE)] = Field(
|
|
316
320
|
description=(
|
|
317
321
|
"Font sizes for the H1–H6 heading ramp, indexed by ``face.level - 1``. "
|
|
@@ -204,10 +204,10 @@ class ChartsStyle(_ChartStyleBase):
|
|
|
204
204
|
point_map: PointMapChartStyle = Field(description="Point map chart style.")
|
|
205
205
|
pie: PieChartStyle = Field(description="Pie/donut chart style.")
|
|
206
206
|
|
|
207
|
-
# Series-label primitive — typography for series-naming text marks
|
|
208
|
-
# (endpoint labels
|
|
207
|
+
# Series-label primitive — typography for Cartesian series-naming text marks
|
|
208
|
+
# (endpoint labels and direct stack labels).
|
|
209
209
|
series_label: SeriesLabelStyle = Field(
|
|
210
|
-
description="Shared series-label typography for endpoint
|
|
210
|
+
description="Shared series-label typography for endpoint and stack labels."
|
|
211
211
|
)
|
|
212
212
|
|
|
213
213
|
# Non-Vega chart types (required — theme pipeline always populates)
|
|
@@ -49,6 +49,17 @@ class LegendStyle(BaseModel):
|
|
|
49
49
|
direction: str = Field(
|
|
50
50
|
description="Legend layout direction ('vertical' or 'horizontal')."
|
|
51
51
|
)
|
|
52
|
+
columns: int = Field(
|
|
53
|
+
ge=0,
|
|
54
|
+
description=(
|
|
55
|
+
"Legend entry columns. Zero keeps the renderer default; positive values "
|
|
56
|
+
"set Vega-Lite legend columns."
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
compact_columns: int = Field(
|
|
60
|
+
ge=1,
|
|
61
|
+
description="Entry columns for an automatic compact top-horizontal legend.",
|
|
62
|
+
)
|
|
52
63
|
label: LegendLabelStyle = Field(description="Legend label style.")
|
|
53
64
|
title: LegendTitleStyle = Field(description="Legend title style.")
|
|
54
65
|
visible: bool | None = Field(
|