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.
- dataface/__init__.py +70 -23
- dataface/agent_api/__init__.py +162 -68
- dataface/agent_api/_paths.py +9 -3
- dataface/agent_api/docs/yaml-reference.md +240 -106
- dataface/agent_api/init.py +1 -1
- dataface/agent_api/project_session.py +2 -1
- dataface/agent_api/serve.py +115 -0
- dataface/ai/agent.py +28 -2
- dataface/cli/_project.py +100 -1
- dataface/cli/commands/board_artifact.py +5 -2
- dataface/cli/commands/describe.py +7 -3
- dataface/cli/commands/docs.py +12 -3
- dataface/cli/commands/init.py +7 -14
- dataface/cli/commands/inspect.py +6 -1
- dataface/cli/commands/mcp_init.py +23 -43
- dataface/cli/commands/migrate.py +8 -3
- dataface/cli/commands/query.py +11 -8
- dataface/cli/commands/render.py +20 -9
- dataface/cli/commands/search.py +8 -2
- dataface/cli/commands/serve.py +26 -76
- dataface/cli/commands/skills_init.py +2 -19
- dataface/cli/commands/validate.py +7 -2
- dataface/cli/filesystem_project.py +5 -4
- dataface/cli/main.py +2 -19
- dataface/core/__init__.py +73 -28
- dataface/core/{compile/colors.py → colors.py} +20 -5
- dataface/core/compile/compiler.py +97 -34
- dataface/core/compile/data_table.py +7 -2
- dataface/core/compile/enrich.py +18 -44
- dataface/core/compile/face_warnings.py +46 -8
- dataface/core/compile/format.py +47 -1
- dataface/core/compile/inherit_graph.py +3 -3
- dataface/core/compile/inherit_registry.yaml +1 -1
- dataface/core/compile/migrations.py +112 -9
- dataface/core/compile/models/chart/normalized/kpi.py +0 -4
- dataface/core/compile/models/chart/resolved/_base.py +28 -10
- dataface/core/compile/models/chart/resolved/callout.py +2 -3
- dataface/core/compile/models/chart/resolved/kpi.py +7 -3
- dataface/core/compile/models/chart/resolved/table.py +16 -2
- dataface/core/compile/models/markers.py +1 -1
- dataface/core/compile/models/primitives.py +175 -2
- dataface/core/compile/models/source.py +5 -5
- dataface/core/compile/models/style/authored/__init__.py +32 -8
- dataface/core/compile/models/style/authored/_base.py +126 -69
- dataface/core/compile/models/style/authored/table.py +61 -3
- dataface/core/compile/models/style/resolved/__init__.py +12 -2
- dataface/core/compile/models/style/resolved/_base.py +142 -57
- dataface/core/compile/models/style/resolved/_cartesian.py +6 -11
- dataface/core/compile/models/style/resolved/_cascade.py +141 -54
- dataface/core/compile/models/style/resolved/area.py +1 -1
- dataface/core/compile/models/style/resolved/bar.py +1 -1
- dataface/core/compile/models/style/resolved/line.py +1 -1
- dataface/core/compile/models/style/resolved/scatter.py +3 -2
- dataface/core/compile/models/style/theme/__init__.py +36 -14
- dataface/core/compile/models/style/theme/_chart_base.py +17 -9
- dataface/core/compile/models/style/theme/axis.py +432 -312
- dataface/core/compile/models/style/theme/charts.py +10 -6
- dataface/core/compile/models/style/theme/kpi.py +2 -1
- dataface/core/compile/models/style/theme/page.py +5 -2
- dataface/core/compile/models/style/theme/table.py +2 -2
- dataface/core/compile/models/vega_lite/config.py +1 -1
- dataface/core/compile/normalize_charts.py +6 -2
- dataface/core/compile/normalize_queries.py +21 -7
- dataface/core/compile/resolve/__init__.py +2 -0
- dataface/core/compile/resolve/_axes.py +147 -44
- dataface/core/compile/resolve/_channels.py +1 -1
- dataface/core/compile/resolve/_dispatch.py +1 -1
- dataface/core/compile/resolve/_domain.py +49 -32
- dataface/core/compile/resolve/_kwargs.py +39 -12
- dataface/core/compile/resolve/_marks.py +8 -3
- dataface/core/compile/resolve/_palette.py +7 -3
- dataface/core/compile/resolve/_table.py +124 -33
- dataface/core/compile/resolve/area.py +48 -18
- dataface/core/compile/resolve/bar.py +245 -64
- dataface/core/compile/resolve/geo.py +10 -3
- dataface/core/compile/resolve/heatmap.py +16 -8
- dataface/core/compile/resolve/line.py +35 -12
- dataface/core/compile/resolve/pie.py +4 -1
- dataface/core/compile/resolve/pie_attachment.py +1 -1
- dataface/core/compile/resolve/scatter.py +25 -13
- dataface/core/compile/resolve/simple.py +25 -2
- dataface/core/compile/schema_renderers/prompt.py +1 -1
- dataface/core/compile/schema_renderers/textmate_grammar.py +8 -1
- dataface/core/compile/source_map.py +211 -28
- dataface/core/compile/style_cascade.py +126 -55
- dataface/core/compile/yaml_error_formatter.py +1 -1
- dataface/core/connections.py +10 -3
- dataface/core/dashboard.py +24 -18
- dataface/core/defaults/themes/_base.yaml +49 -39
- dataface/core/defaults/themes/cream.yaml +4 -4
- dataface/core/defaults/themes/diagnostics-title-baseline.yaml +1 -1
- dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +1 -1
- dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +1 -1
- dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +1 -1
- dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +1 -1
- dataface/core/defaults/themes/editorial.yaml +6 -6
- dataface/core/defaults/themes/neon.yaml +15 -8
- dataface/core/defaults/themes/plain.yaml +6 -4
- dataface/core/defaults/themes/stark.yaml +16 -7
- dataface/core/defaults/themes/vivid.yaml +15 -8
- dataface/core/diagnostics/__init__.py +2 -2
- dataface/core/diagnostics/codes_compile.py +13 -31
- dataface/core/diagnostics/codes_execute.py +79 -0
- dataface/core/diagnostics/codes_render.py +87 -28
- dataface/core/diagnostics/diagnostic.py +30 -0
- dataface/core/diagnostics/hints.py +19 -0
- dataface/core/dialects/base.py +17 -0
- dataface/core/dialects/postgres.py +14 -0
- dataface/core/dialects/redshift.py +5 -0
- dataface/core/execute/__init__.py +59 -14
- dataface/core/execute/adapters/dbt_adapter.py +4 -1
- dataface/core/execute/adapters/dbt_adapter_factory.py +19 -0
- dataface/core/execute/adapters/dbt_utils.py +84 -20
- dataface/core/execute/adapters/postgres_connection_manager.py +48 -0
- dataface/core/execute/adapters/sql_adapter.py +72 -4
- dataface/core/execute/dbt_jinja.py +65 -10
- dataface/core/execute/executor.py +69 -16
- dataface/core/font_measure.py +28 -0
- dataface/core/fonts.py +8 -5
- dataface/core/inspect/__init__.py +46 -6
- dataface/core/project.py +13 -1
- dataface/core/render/chart/data_table_attachment.py +14 -12
- dataface/core/render/chart/emitters/_cartesian.py +21 -18
- dataface/core/render/chart/emitters/_channels.py +56 -8
- dataface/core/render/chart/emitters/_label_overlap.py +18 -15
- dataface/core/render/chart/emitters/_measured_label_padding.py +4 -4
- dataface/core/render/chart/emitters/area.py +3 -3
- dataface/core/render/chart/emitters/bar.py +60 -35
- dataface/core/render/chart/emitters/geo.py +8 -13
- dataface/core/render/chart/emitters/heatmap.py +20 -26
- dataface/core/render/chart/emitters/line.py +2 -2
- dataface/core/render/chart/emitters/scatter.py +7 -30
- dataface/core/render/chart/features/baseline.py +22 -9
- dataface/core/render/chart/features/endpoint_labels.py +55 -32
- dataface/core/render/chart/features/mirror_axis.py +12 -9
- dataface/core/render/chart/features/value_labels.py +1 -1
- dataface/core/render/chart/kpi.py +11 -90
- dataface/core/render/chart/presentation.py +6 -8
- dataface/core/render/chart/rendering.py +3 -12
- dataface/core/render/chart/session.py +10 -0
- dataface/core/render/chart/spec.py +3 -0
- dataface/core/render/chart/spec_builders.py +18 -18
- dataface/core/render/chart/table.py +81 -36
- dataface/core/render/chart/table_support.py +2 -53
- dataface/core/render/chart/time_unit_detect.py +3 -2
- dataface/core/render/chart/translate.py +82 -44
- dataface/core/render/chart/type_inference.py +45 -25
- dataface/core/render/chart/validation.py +91 -0
- dataface/core/render/chart/vega_lite.py +30 -11
- dataface/core/render/chart/vl_field_maps.py +111 -76
- dataface/core/render/converters/chart.py +16 -6
- dataface/core/render/faces.py +3 -2
- dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.woff2 +0 -0
- dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.woff2 +0 -0
- dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -93
- dataface/core/render/fonts/_font_face.css +10 -0
- dataface/core/render/layout_sizing.py +267 -238
- dataface/core/render/numeral_expr.py +80 -0
- dataface/core/render/warnings/bar_band_width_too_narrow.py +64 -6
- dataface/core/render/warnings/layered_chart_shared_y_axis_scale_mismatch.py +25 -3
- dataface/core/render/warnings/likely_currency_or_percent_missing_formatter.py +12 -6
- dataface/core/render/warnings/palette_unsupported.py +6 -0
- dataface/core/render/warnings/pie_dominant_segment.py +1 -0
- dataface/core/render/warnings/pie_too_many_segments.py +1 -0
- dataface/core/render/warnings/point_map_out_of_projection.py +1 -0
- dataface/core/render/warnings/query_returned_zero_rows.py +1 -0
- dataface/core/render/warnings/redundant_encoding.py +20 -1
- dataface/core/render/warnings/registry.py +7 -3
- dataface/core/render/warnings/temporal_single_point.py +1 -0
- dataface/core/render/warnings/too_many_color_categories.py +1 -0
- dataface/core/render/warnings/too_many_x_categories.py +18 -2
- dataface/core/render/warnings/value_labels_crowd_width.py +9 -0
- dataface/core/render/warnings/y_encoding_mostly_null.py +1 -0
- dataface/core/render_format.py +18 -0
- dataface/core/text/format_d3.py +56 -0
- dataface/core/text/numeral_scale.py +218 -0
- dataface/core/utils.py +103 -10
- dataface/data/schemas/board-resolved/board-resolved.schema.json +9487 -7978
- dataface/data/schemas/yaml/0.3.2.json +1 -0
- dataface/data/schemas/yaml/manifest.json +7 -0
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/METADATA +1 -1
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/RECORD +185 -177
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/WHEEL +0 -0
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/entry_points.txt +0 -0
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/licenses/LICENSE +0 -0
dataface/core/font_measure.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import math
|
|
6
|
+
|
|
5
7
|
from dataface.core.fonts import (
|
|
6
8
|
SOURCE_CODE_PRO_FONT_FAMILY,
|
|
7
9
|
SOURCE_SERIF_4_FONT_FAMILY,
|
|
@@ -43,6 +45,32 @@ def get_font_measurer(
|
|
|
43
45
|
return _load_measurer(get_font_path(font_family, numeric=numeric))
|
|
44
46
|
|
|
45
47
|
|
|
48
|
+
def suffix_reservation_slots(suffix: str, font_family: str) -> int:
|
|
49
|
+
"""Digit-width slots a magnitude suffix needs against ``font_family``.
|
|
50
|
+
|
|
51
|
+
``ceil(suffix_advance / digit_advance)`` — not character count. A
|
|
52
|
+
suffix's advance is not an integer multiple of the digit width even in a
|
|
53
|
+
tabular face, so the slot count is quantised and leaves a sub-digit
|
|
54
|
+
residual: the reservation makes a column scan, it does not make the
|
|
55
|
+
digits share an exact x. Character count under-reserves ``mn``: its
|
|
56
|
+
advance is 2.26 digit-widths, so a 2-character reservation would leave
|
|
57
|
+
the suffix overrunning the column by a quarter digit.
|
|
58
|
+
|
|
59
|
+
Both digit and suffix are measured through
|
|
60
|
+
``get_font_measurer(font_family, numeric=True)`` — the same numeric-face
|
|
61
|
+
resolution every render call site uses, which always resolves to the
|
|
62
|
+
tabular face digits actually paint in regardless of the surrounding body
|
|
63
|
+
font, and degrades an unvendored family the same way every other numeral
|
|
64
|
+
measurement does.
|
|
65
|
+
"""
|
|
66
|
+
if not suffix:
|
|
67
|
+
return 0
|
|
68
|
+
measurer = get_font_measurer(font_family, numeric=True)
|
|
69
|
+
digit_advance = measurer.measure("0", 1.0)
|
|
70
|
+
suffix_advance = measurer.measure(suffix, 1.0)
|
|
71
|
+
return math.ceil(suffix_advance / digit_advance)
|
|
72
|
+
|
|
73
|
+
|
|
46
74
|
def css_weight_to_axis(weight: str | float) -> float:
|
|
47
75
|
"""Convert a CSS ``font-weight`` value to a variable-font ``wght`` axis value."""
|
|
48
76
|
if isinstance(weight, (int, float)):
|
dataface/core/fonts.py
CHANGED
|
@@ -151,7 +151,7 @@ FONT_REGISTRY: tuple[VendoredFace, ...] = (
|
|
|
151
151
|
web_file="NotoColorEmoji-Regular.woff2",
|
|
152
152
|
unicode_range=_EMOJI_UNICODE_RANGES,
|
|
153
153
|
),
|
|
154
|
-
#
|
|
154
|
+
# Source Code Pro is the only measure-only row. See VendoredFace.web_file.
|
|
155
155
|
VendoredFace(
|
|
156
156
|
family=SOURCE_CODE_PRO_FONT_FAMILY,
|
|
157
157
|
style="normal",
|
|
@@ -164,16 +164,19 @@ FONT_REGISTRY: tuple[VendoredFace, ...] = (
|
|
|
164
164
|
VendoredFace(
|
|
165
165
|
family=DFT_SERIF_OLDSTYLE_TABULAR_FONT_FAMILY,
|
|
166
166
|
style="normal",
|
|
167
|
-
#
|
|
168
|
-
#
|
|
167
|
+
# No built-in theme emits either oldstyle face; both are reachable by naming
|
|
168
|
+
# them in a theme font stack. That is why they are served rather than
|
|
169
|
+
# measure-only — selecting oldstyle figures through `font-feature-settings`
|
|
170
|
+
# instead does not survive static export (see fonts/README.md), so the family
|
|
171
|
+
# is the only route, and a family the browser cannot download is not a route.
|
|
169
172
|
measure_file="DFTSerifOldstyleTabular-Regular.ttf",
|
|
170
|
-
web_file=
|
|
173
|
+
web_file="DFTSerifOldstyleTabular-Regular.woff2",
|
|
171
174
|
),
|
|
172
175
|
VendoredFace(
|
|
173
176
|
family=DFT_SERIF_OLDSTYLE_PROPORTIONAL_FONT_FAMILY,
|
|
174
177
|
style="normal",
|
|
175
178
|
measure_file="DFTSerifOldstyleProportional-Regular.ttf",
|
|
176
|
-
web_file=
|
|
179
|
+
web_file="DFTSerifOldstyleProportional-Regular.woff2",
|
|
177
180
|
),
|
|
178
181
|
)
|
|
179
182
|
|
|
@@ -6,12 +6,18 @@ open-source components: DbtSchemaSource, LayeredSchemaResolver, schema
|
|
|
6
6
|
search, and the HTML inspect templates.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import importlib
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from dataface.core.inspect.renderer import (
|
|
16
|
+
InspectProfileCompileError,
|
|
17
|
+
render_inspect_dashboard,
|
|
18
|
+
resolve_source_name,
|
|
19
|
+
validate_inspect_variables,
|
|
20
|
+
)
|
|
15
21
|
|
|
16
22
|
# Static list of available inspect templates.
|
|
17
23
|
# Template files live in dataface/core/inspect/templates/{name}.yml
|
|
@@ -32,3 +38,37 @@ __all__ = [
|
|
|
32
38
|
"validate_inspect_variables",
|
|
33
39
|
"INSPECT_TEMPLATES",
|
|
34
40
|
]
|
|
41
|
+
|
|
42
|
+
# Submodule (sources/, db_types, etc.) imports run this __init__ first, so
|
|
43
|
+
# these re-exports must not eagerly pull in renderer's (and transitively
|
|
44
|
+
# core.compile's) heavy import chain just to expose INSPECT_TEMPLATES.
|
|
45
|
+
_LAZY_ATTRS: dict[str, tuple[str, str]] = {
|
|
46
|
+
"InspectProfileCompileError": (
|
|
47
|
+
"dataface.core.inspect.renderer",
|
|
48
|
+
"InspectProfileCompileError",
|
|
49
|
+
),
|
|
50
|
+
"render_inspect_dashboard": (
|
|
51
|
+
"dataface.core.inspect.renderer",
|
|
52
|
+
"render_inspect_dashboard",
|
|
53
|
+
),
|
|
54
|
+
"resolve_source_name": ("dataface.core.inspect.renderer", "resolve_source_name"),
|
|
55
|
+
"validate_inspect_variables": (
|
|
56
|
+
"dataface.core.inspect.renderer",
|
|
57
|
+
"validate_inspect_variables",
|
|
58
|
+
),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def __getattr__(name: str) -> Any:
|
|
63
|
+
try:
|
|
64
|
+
module_path, attr_name = _LAZY_ATTRS[name]
|
|
65
|
+
except KeyError:
|
|
66
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None
|
|
67
|
+
module = importlib.import_module(module_path)
|
|
68
|
+
value = getattr(module, attr_name)
|
|
69
|
+
globals()[name] = value # cache: repeated access is O(1) and `is` identity holds
|
|
70
|
+
return value
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def __dir__() -> list[str]:
|
|
74
|
+
return sorted(set(globals()) | set(_LAZY_ATTRS))
|
dataface/core/project.py
CHANGED
|
@@ -5,7 +5,7 @@ import re
|
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
from collections.abc import Iterable, Iterator
|
|
7
7
|
from functools import cached_property
|
|
8
|
-
from pathlib import PurePosixPath, PureWindowsPath
|
|
8
|
+
from pathlib import PurePath, PurePosixPath, PureWindowsPath
|
|
9
9
|
from posixpath import normpath
|
|
10
10
|
from typing import TYPE_CHECKING, Any, NamedTuple
|
|
11
11
|
|
|
@@ -56,6 +56,18 @@ def assert_relpath(ref: str, anchor: str = ".") -> None:
|
|
|
56
56
|
raise ValueError(f"file ref {ref!r} would escape above the project root")
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def posix_relpath(path: PurePath, root: PurePath) -> str:
|
|
60
|
+
"""Project-relative POSIX identity for *path* under *root*.
|
|
61
|
+
|
|
62
|
+
``str(Path.relative_to(...))`` emits the OS-native separator — a no-op on
|
|
63
|
+
POSIX, but backslashes on Windows, which ``assert_relpath`` then rejects.
|
|
64
|
+
File identity is always a POSIX relpath, so normalize with ``as_posix()``.
|
|
65
|
+
*path* and *root* are typed ``PurePath`` (not ``PurePosixPath``) because
|
|
66
|
+
real callers pass OS-native ``Path`` objects from disk.
|
|
67
|
+
"""
|
|
68
|
+
return path.relative_to(root).as_posix()
|
|
69
|
+
|
|
70
|
+
|
|
59
71
|
# Directories skipped when walking the project tree for files.
|
|
60
72
|
# A host that overrides Project's file-access methods (e.g. CloudManagedProject's
|
|
61
73
|
# git-blob reader) applies the same filter without duplicating the set.
|
|
@@ -903,7 +903,7 @@ def _label_stub_left_layer(
|
|
|
903
903
|
) -> dict[str, Any]:
|
|
904
904
|
"""Left-gutter label layer anchored to the left y-axis tick column.
|
|
905
905
|
|
|
906
|
-
x = -axis_y.
|
|
906
|
+
x = -axis_y.labels.padding (same column as left-oriented y-axis tick labels,
|
|
907
907
|
which extend leftward from x=0 with text-anchor end / align right).
|
|
908
908
|
mark.limit caps the text width so VL's autosize does not shrink the plot.
|
|
909
909
|
|
|
@@ -912,7 +912,8 @@ def _label_stub_left_layer(
|
|
|
912
912
|
"""
|
|
913
913
|
axis_label_padding = resolved_axis_style(
|
|
914
914
|
charts_style, "axis_y", "quantitative"
|
|
915
|
-
).
|
|
915
|
+
).labels.padding
|
|
916
|
+
assert axis_label_padding is not None, "axis_y.labels.padding unset"
|
|
916
917
|
x_pixel = -axis_label_padding
|
|
917
918
|
mark = _label_mark_props(style, align="right")
|
|
918
919
|
if limit is not None:
|
|
@@ -944,7 +945,7 @@ def _label_stub_right_layer(
|
|
|
944
945
|
) -> dict[str, Any]:
|
|
945
946
|
"""Right-cap label layer anchored to the right y-axis tick column.
|
|
946
947
|
|
|
947
|
-
x = spec_width + axis_y.
|
|
948
|
+
x = spec_width + axis_y.labels.padding (same column as right-oriented y-axis
|
|
948
949
|
tick labels, which extend rightward from that anchor with text-anchor start /
|
|
949
950
|
align left). mark.limit caps the text width so VL's autosize does not shrink
|
|
950
951
|
the plot.
|
|
@@ -954,7 +955,8 @@ def _label_stub_right_layer(
|
|
|
954
955
|
"""
|
|
955
956
|
axis_label_padding = resolved_axis_style(
|
|
956
957
|
charts_style, "axis_y", "quantitative"
|
|
957
|
-
).
|
|
958
|
+
).labels.padding
|
|
959
|
+
assert axis_label_padding is not None, "axis_y.labels.padding unset"
|
|
958
960
|
x_pixel = spec_width + axis_label_padding
|
|
959
961
|
mark = _label_mark_props(style, align="left")
|
|
960
962
|
if limit is not None:
|
|
@@ -1042,8 +1044,8 @@ def attach_data_table(
|
|
|
1042
1044
|
axis_y_orient: Resolved y-axis orient — ``"right"`` or ``"left"``
|
|
1043
1045
|
(line charts with right-edge endpoint labels). Determines which label
|
|
1044
1046
|
emitter fires: right-oriented → label at ``spec_width +
|
|
1045
|
-
axis_y.
|
|
1046
|
-
``-axis_y.
|
|
1047
|
+
axis_y.labels.padding`` with ``align: left``; left-oriented → label at
|
|
1048
|
+
``-axis_y.labels.padding`` with ``align: right``. Required — callers must
|
|
1047
1049
|
resolve this from the chart's axis_y.orient before calling.
|
|
1048
1050
|
|
|
1049
1051
|
Returns:
|
|
@@ -1643,13 +1645,13 @@ def _label_period_filter_expr(
|
|
|
1643
1645
|
axis_st = resolved_axis_style(
|
|
1644
1646
|
resolved_chart_style, "axis_x", x_type or "temporal", ct_axis_x
|
|
1645
1647
|
)
|
|
1646
|
-
raw_label_tu = axis_st.
|
|
1648
|
+
raw_label_tu = axis_st.labels.time_unit
|
|
1647
1649
|
# Chart-local authored label.time_unit wins when the board-level style
|
|
1648
1650
|
# has no override — the resolver merges chart-local style into the chart's
|
|
1649
1651
|
# resolved style object, not back into charts_style, so the fallback reads
|
|
1650
1652
|
# directly from the chart-local axis_x that was baked into the resolved chart.
|
|
1651
1653
|
if raw_label_tu is None and chart_local_axis_x is not None:
|
|
1652
|
-
raw_label_tu = chart_local_axis_x.
|
|
1654
|
+
raw_label_tu = chart_local_axis_x.labels.time_unit
|
|
1653
1655
|
format_tu = resolve_label_time_unit(dft_time_unit, raw_label_tu)
|
|
1654
1656
|
if not format_tu:
|
|
1655
1657
|
return None
|
|
@@ -1670,14 +1672,14 @@ def _label_period_filter_expr(
|
|
|
1670
1672
|
if not x_distinct or spec_width / len(x_distinct) >= _LABEL_STRIP_MIN_BAND_PX:
|
|
1671
1673
|
return None
|
|
1672
1674
|
safe_field = x_field.replace("'", "\\'")
|
|
1673
|
-
font = axis_st.
|
|
1675
|
+
font = axis_st.labels.font
|
|
1674
1676
|
measurer = get_font_measurer(font.family)
|
|
1675
1677
|
dates = sorted(datetime.date.fromisoformat(str(v)[:10]) for v in x_distinct)
|
|
1676
1678
|
band = (spec_width * resolved_chart_style.label_usable_ratio) / len(dates)
|
|
1677
1679
|
flush_axis = (
|
|
1678
1680
|
chart_local_axis_x
|
|
1679
1681
|
if chart_local_axis_x is not None
|
|
1680
|
-
and chart_local_axis_x.
|
|
1682
|
+
and chart_local_axis_x.labels.flush is not None
|
|
1681
1683
|
else axis_st
|
|
1682
1684
|
)
|
|
1683
1685
|
visibility_tu, _ = resolve_temporal_label_visibility(
|
|
@@ -1689,8 +1691,8 @@ def _label_period_filter_expr(
|
|
|
1689
1691
|
band,
|
|
1690
1692
|
axis_st.fiscal_year_start_month,
|
|
1691
1693
|
allow_skip=(
|
|
1692
|
-
axis_st.
|
|
1693
|
-
if axis_st.
|
|
1694
|
+
axis_st.labels.overlap.skip
|
|
1695
|
+
if axis_st.labels.overlap is not None
|
|
1694
1696
|
else False
|
|
1695
1697
|
),
|
|
1696
1698
|
edge_labels_flushed=temporal_edge_labels_flushed("temporal", flush_axis),
|
|
@@ -15,8 +15,10 @@ from typing import Any, NamedTuple
|
|
|
15
15
|
|
|
16
16
|
from dataface.core.compile.models.chart.authored._annotations import ChartSort
|
|
17
17
|
from dataface.core.compile.models.style.resolved import ResolvedChartsStyle
|
|
18
|
-
from dataface.core.compile.models.style.resolved._base import
|
|
19
|
-
|
|
18
|
+
from dataface.core.compile.models.style.resolved._base import (
|
|
19
|
+
ResolvedAxisStyle,
|
|
20
|
+
ResolvedScaleStyle,
|
|
21
|
+
)
|
|
20
22
|
from dataface.core.compile.style_cascade import (
|
|
21
23
|
chart_type_axis_patch,
|
|
22
24
|
resolved_axis_style,
|
|
@@ -37,9 +39,8 @@ from dataface.core.render.chart.type_inference import (
|
|
|
37
39
|
y_zero_scale,
|
|
38
40
|
)
|
|
39
41
|
from dataface.core.render.chart.vl_field_maps import (
|
|
40
|
-
SCALE_FIELD_MAP,
|
|
41
42
|
axis_to_vl,
|
|
42
|
-
|
|
43
|
+
emit_resolved_scale_vl,
|
|
43
44
|
)
|
|
44
45
|
from dataface.core.render.utils import slug_to_text
|
|
45
46
|
from dataface.core.text.case import format_display_text
|
|
@@ -56,7 +57,7 @@ _CONTINUOUS_VL_TYPES = frozenset({"temporal", "quantitative"})
|
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
def cartesian_x_scale_domain(
|
|
59
|
-
scale:
|
|
60
|
+
scale: ResolvedScaleStyle | None, vl_type: str, chart_id: str
|
|
60
61
|
) -> VLDict:
|
|
61
62
|
"""Return the VL x-scale ``domain`` entry for an authored cartesian x scale.
|
|
62
63
|
|
|
@@ -66,7 +67,8 @@ def cartesian_x_scale_domain(
|
|
|
66
67
|
a band scale as exactly two categories, collapsing every mark onto the
|
|
67
68
|
first one, rather than extending a continuous range.
|
|
68
69
|
"""
|
|
69
|
-
if scale is None
|
|
70
|
+
_sc_cont = scale.continuous if scale is not None else None
|
|
71
|
+
if _sc_cont is None or _sc_cont.domain is None:
|
|
70
72
|
return {}
|
|
71
73
|
if vl_type not in _CONTINUOUS_VL_TYPES:
|
|
72
74
|
raise ChartDataError.from_code(
|
|
@@ -74,7 +76,7 @@ def cartesian_x_scale_domain(
|
|
|
74
76
|
chart_id=chart_id,
|
|
75
77
|
vl_type=vl_type,
|
|
76
78
|
)
|
|
77
|
-
return {"domain": list(
|
|
79
|
+
return {"domain": list(_sc_cont.domain)}
|
|
78
80
|
|
|
79
81
|
|
|
80
82
|
def chart_sort_to_vl(sort: ChartSort | None) -> VLDict | None:
|
|
@@ -272,24 +274,24 @@ def min_height_for_horizontal_bar_categories(
|
|
|
272
274
|
"ordinal",
|
|
273
275
|
chart_type_axis_patch(resolved_charts, "bar", "axis_x"),
|
|
274
276
|
)
|
|
275
|
-
label = axis_x.
|
|
277
|
+
label = axis_x.labels
|
|
276
278
|
bar_mark = resolved_charts.marks.bar
|
|
277
279
|
font_size = label.font.size
|
|
278
|
-
|
|
280
|
+
min_gap = label.min_gap
|
|
279
281
|
padding = label.padding
|
|
280
282
|
bar_size = bar_mark.size
|
|
281
283
|
assert (
|
|
282
284
|
font_size is not None
|
|
283
|
-
), "axis_x.
|
|
285
|
+
), "axis_x.labels.font.size unset — theme cascade must populate it"
|
|
284
286
|
assert (
|
|
285
|
-
|
|
286
|
-
), "axis_x.
|
|
287
|
+
min_gap is not None
|
|
288
|
+
), "axis_x.labels.min_gap unset — theme cascade must populate it"
|
|
287
289
|
assert (
|
|
288
290
|
padding is not None
|
|
289
|
-
), "axis_x.
|
|
291
|
+
), "axis_x.labels.padding unset — theme cascade must populate it"
|
|
290
292
|
assert bar_size is not None, "marks.bar.size unset — theme cascade must populate it"
|
|
291
293
|
label_line = font_size + 2 * padding
|
|
292
|
-
band_step = max(bar_size, label_line) +
|
|
294
|
+
band_step = max(bar_size, label_line) + min_gap
|
|
293
295
|
return _HORIZONTAL_BAR_LAYOUT_CHROME_PX + n_categories * band_step
|
|
294
296
|
|
|
295
297
|
|
|
@@ -326,13 +328,14 @@ def build_cartesian_y_encoding(
|
|
|
326
328
|
|
|
327
329
|
Tick values from ay.tick_values are baked into ay_vl["values"] when present.
|
|
328
330
|
Authored domain replaces the data-driven zero/domainMin computation, but
|
|
329
|
-
still carries type/base/exponent/constant (etc.) via
|
|
330
|
-
domain and a scale type are not mutually exclusive (e.g. log needs both).
|
|
331
|
+
still carries type/base/exponent/constant (etc.) via emit_resolved_scale_vl
|
|
332
|
+
— a domain and a scale type are not mutually exclusive (e.g. log needs both).
|
|
331
333
|
|
|
332
334
|
Callers that need a VL ``stack`` key (area charts) mutate the returned dict.
|
|
333
335
|
"""
|
|
336
|
+
_ay_cont = ay.scale.continuous if ay.scale is not None else None
|
|
334
337
|
authored_domain = numeric_domain_bounds(
|
|
335
|
-
|
|
338
|
+
_ay_cont.domain if _ay_cont is not None else None
|
|
336
339
|
)
|
|
337
340
|
y_ticks: list[float] | None = list(ay.tick_values) if ay.tick_values else None
|
|
338
341
|
if y_ticks and "values" not in ay_vl:
|
|
@@ -340,7 +343,7 @@ def build_cartesian_y_encoding(
|
|
|
340
343
|
if authored_domain is not None:
|
|
341
344
|
y_scale: VLDict = {"domain": list(authored_domain)}
|
|
342
345
|
if ay.scale is not None:
|
|
343
|
-
y_scale.update(
|
|
346
|
+
y_scale.update(emit_resolved_scale_vl(ay.scale))
|
|
344
347
|
# An authored domain already IS the exact bounds — "zero" is a
|
|
345
348
|
# domain-inference hint (VL's "extend the domain to include 0"),
|
|
346
349
|
# meaningless once the domain is pinned explicitly, and its
|
|
@@ -5,10 +5,15 @@ from __future__ import annotations
|
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from dataface.core.compile.models.chart.resolved import ResolvedStyleChannel
|
|
8
|
+
from dataface.core.compile.models.primitives import (
|
|
9
|
+
VEGA_SCHEME_NAMES,
|
|
10
|
+
ScaleTargetConfig,
|
|
11
|
+
)
|
|
8
12
|
from dataface.core.compile.models.style.resolved import (
|
|
9
13
|
ResolvedAxisStyle,
|
|
10
14
|
ResolvedLegendStyle,
|
|
11
15
|
)
|
|
16
|
+
from dataface.core.render.chart._types import VLDict
|
|
12
17
|
from dataface.core.render.chart.time_unit_detect import (
|
|
13
18
|
BUCKETED_CALENDAR_UNITS,
|
|
14
19
|
canonicalize_and_sort_ordinal_x,
|
|
@@ -24,6 +29,48 @@ from dataface.core.render.chart.vl_conditions import resolved_channel_to_vl_cond
|
|
|
24
29
|
from dataface.core.render.chart.vl_field_maps import axis_to_vl, legend_to_vl
|
|
25
30
|
|
|
26
31
|
|
|
32
|
+
def gradient_scale_to_vl(scale: ScaleTargetConfig) -> VLDict:
|
|
33
|
+
"""Build a VL continuous-scale dict from a resolved gradient ScaleTargetConfig.
|
|
34
|
+
|
|
35
|
+
``palette`` is either a Vega scheme name (forwarded as VL ``scheme``) or a
|
|
36
|
+
Dataface named palette / explicit color list, resolved to hex ``range``
|
|
37
|
+
stops via ``resolve_palette_stops`` — the same resolution table/KPI
|
|
38
|
+
conditional formatting already use for this field. Forwarding a Dataface
|
|
39
|
+
name straight through as a VL scheme string (the pre-fix behavior) is a
|
|
40
|
+
silent no-op: Vega logs an unrecognized-scheme warning and paints with its
|
|
41
|
+
default coloring instead of erroring, so the chart looks fine while
|
|
42
|
+
ignoring the palette the author asked for.
|
|
43
|
+
|
|
44
|
+
A ``list[float]`` (the opacity/stroke_width channel default) is not a
|
|
45
|
+
color palette and passes straight through as ``range`` unchanged —
|
|
46
|
+
``resolve_palette_stops`` is color-stop-only.
|
|
47
|
+
|
|
48
|
+
Shared by ``channel_to_encoding``'s gradient mode and the geoshape/heatmap
|
|
49
|
+
chart-style gradient paths (``geo.py``, ``heatmap.py``) so the three
|
|
50
|
+
render sites decide scheme-vs-stops the same way instead of each
|
|
51
|
+
reimplementing the branch.
|
|
52
|
+
"""
|
|
53
|
+
palette = scale.palette
|
|
54
|
+
if isinstance(palette, str):
|
|
55
|
+
if palette in VEGA_SCHEME_NAMES:
|
|
56
|
+
vl_scale: VLDict = {"scheme": palette}
|
|
57
|
+
else:
|
|
58
|
+
from dataface.core.render.chart.table_support import ( # noqa: PLC0415
|
|
59
|
+
resolve_palette_stops,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
vl_scale = {"range": resolve_palette_stops(palette)}
|
|
63
|
+
else:
|
|
64
|
+
vl_scale = {"range": list(palette)}
|
|
65
|
+
if scale.min is not None and scale.max is not None:
|
|
66
|
+
vl_scale["domain"] = [scale.min, scale.max]
|
|
67
|
+
elif scale.min is not None:
|
|
68
|
+
vl_scale["domainMin"] = scale.min
|
|
69
|
+
elif scale.max is not None:
|
|
70
|
+
vl_scale["domainMax"] = scale.max
|
|
71
|
+
return vl_scale
|
|
72
|
+
|
|
73
|
+
|
|
27
74
|
def gap_fill_ordinal_time(
|
|
28
75
|
ax: ResolvedAxisStyle,
|
|
29
76
|
x_field: str,
|
|
@@ -105,6 +152,9 @@ def gap_fill_ordinal_time(
|
|
|
105
152
|
return canonicalize_and_sort_ordinal_x(data, x_field)
|
|
106
153
|
|
|
107
154
|
dim_fields = [color_field] if color_field else []
|
|
155
|
+
assert (
|
|
156
|
+
ax.fill is not None
|
|
157
|
+
), "x-axis fill must be resolved before ordinal time-series completion"
|
|
108
158
|
return complete_ordinal_time_series(
|
|
109
159
|
data, x_field, time_unit, dim_fields, ax.fill, ax.fiscal_year_start_month
|
|
110
160
|
)
|
|
@@ -167,14 +217,11 @@ def channel_to_encoding(
|
|
|
167
217
|
|
|
168
218
|
if ch.mode == "gradient":
|
|
169
219
|
assert ch.scale is not None
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
elif ch.scale.max is not None:
|
|
176
|
-
gradient_scale["domainMax"] = ch.scale.max
|
|
177
|
-
enc = {"field": ch.data_field, "type": "quantitative", "scale": gradient_scale}
|
|
220
|
+
enc = {
|
|
221
|
+
"field": ch.data_field,
|
|
222
|
+
"type": "quantitative",
|
|
223
|
+
"scale": gradient_scale_to_vl(ch.scale),
|
|
224
|
+
}
|
|
178
225
|
if axis_style is not None:
|
|
179
226
|
ax = axis_to_vl(axis_style)
|
|
180
227
|
if ax:
|
|
@@ -242,6 +289,7 @@ __all__ = [
|
|
|
242
289
|
"apply_color_legend",
|
|
243
290
|
"channel_to_encoding",
|
|
244
291
|
"field_encoding",
|
|
292
|
+
"gradient_scale_to_vl",
|
|
245
293
|
"infer_vega_type_from_data",
|
|
246
294
|
"pin_legend_display_order",
|
|
247
295
|
]
|
|
@@ -24,6 +24,7 @@ from dataface.core.render.chart.time_unit_detect import (
|
|
|
24
24
|
resolve_temporal_label_visibility,
|
|
25
25
|
)
|
|
26
26
|
from dataface.core.render.chart.type_inference import infer_vega_type_from_data
|
|
27
|
+
from dataface.core.text.format_d3 import portable_strftime
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
class AxisLabelLayout(NamedTuple):
|
|
@@ -63,7 +64,7 @@ def _generic_temporal_labels(
|
|
|
63
64
|
"yearquarter": lambda value: f"Q{(value.month - 1) // 3 + 1}",
|
|
64
65
|
"yearmonth": lambda value: value.strftime("%b"),
|
|
65
66
|
"yearweek": lambda value: value.strftime("W%V"),
|
|
66
|
-
"yearmonthdate": lambda value: value
|
|
67
|
+
"yearmonthdate": lambda value: portable_strftime(value, "%-d %b"),
|
|
67
68
|
"monthofyear": lambda value: value.strftime("%b"),
|
|
68
69
|
"dayofweek": lambda value: value.strftime("%a"),
|
|
69
70
|
"dayofmonth": lambda value: str(value.day),
|
|
@@ -94,7 +95,7 @@ def _generic_layout(
|
|
|
94
95
|
if _fits_flat(considered_widths, usable_width):
|
|
95
96
|
return AxisLabelLayout(directive, 0.0, None, 0, "")
|
|
96
97
|
if overlap.tilt:
|
|
97
|
-
angle, _ = _pick_tilt_for_widths(axis.
|
|
98
|
+
angle, _ = _pick_tilt_for_widths(axis.labels, considered_widths, usable_width)
|
|
98
99
|
return AxisLabelLayout(directive, angle, None, 0, "")
|
|
99
100
|
return AxisLabelLayout(directive, 0.0, None, 0, "")
|
|
100
101
|
|
|
@@ -203,14 +204,16 @@ def _temporal_layout(
|
|
|
203
204
|
if encoding_time_unit is None:
|
|
204
205
|
return AxisLabelLayout("allow", 0.0, None, 0, "")
|
|
205
206
|
|
|
206
|
-
font = axis.
|
|
207
|
+
font = axis.labels.font
|
|
207
208
|
measurer = get_font_measurer(font.family)
|
|
208
209
|
usable_width = chart_width * label_usable_ratio
|
|
209
|
-
format_time_unit = resolve_label_time_unit(
|
|
210
|
+
format_time_unit = resolve_label_time_unit(
|
|
211
|
+
encoding_time_unit, axis.labels.time_unit
|
|
212
|
+
)
|
|
210
213
|
dates = [datetime.date.fromisoformat(value[:10]) for value in values]
|
|
211
214
|
if (
|
|
212
215
|
encoding_time_unit in {"yearweek", "yearmonthdate"}
|
|
213
|
-
and axis.
|
|
216
|
+
and axis.labels.time_unit in (None, "auto")
|
|
214
217
|
and bucket_aligned_temporal
|
|
215
218
|
):
|
|
216
219
|
candidates = (
|
|
@@ -252,7 +255,7 @@ def _temporal_layout(
|
|
|
252
255
|
measurer.measure(str(dates[index].day), font.size) for index in indices
|
|
253
256
|
]
|
|
254
257
|
angle = (
|
|
255
|
-
_pick_tilt_for_widths(axis.
|
|
258
|
+
_pick_tilt_for_widths(axis.labels, widths, usable_width)[0]
|
|
256
259
|
if overlap.tilt
|
|
257
260
|
else 0.0
|
|
258
261
|
)
|
|
@@ -266,7 +269,7 @@ def _temporal_layout(
|
|
|
266
269
|
elif encoding_time_unit in {
|
|
267
270
|
"yearweek",
|
|
268
271
|
"yearmonthdate",
|
|
269
|
-
} and axis.
|
|
272
|
+
} and axis.labels.time_unit in (None, "auto"):
|
|
270
273
|
native_labels = _generic_temporal_labels(
|
|
271
274
|
values, encoding_time_unit, encoding_time_unit
|
|
272
275
|
)
|
|
@@ -361,14 +364,14 @@ def _temporal_layout(
|
|
|
361
364
|
"yearquarter": lambda date: f"Q{(date.month - 1) // 3 + 1}",
|
|
362
365
|
"yearmonth": lambda date: date.strftime("%b"),
|
|
363
366
|
"yearweek": lambda date: date.strftime("W%V"),
|
|
364
|
-
"yearmonthdate": lambda date: date
|
|
367
|
+
"yearmonthdate": lambda date: portable_strftime(date, "%-d %b"),
|
|
365
368
|
}
|
|
366
369
|
widths = [
|
|
367
370
|
measurer.measure(label_texts[format_time_unit](date), font.size)
|
|
368
371
|
for date in visible_dates
|
|
369
372
|
]
|
|
370
373
|
if overlap.tilt:
|
|
371
|
-
angle, _ = _pick_tilt_for_widths(axis.
|
|
374
|
+
angle, _ = _pick_tilt_for_widths(axis.labels, widths, usable_width)
|
|
372
375
|
return AxisLabelLayout(
|
|
373
376
|
directive, angle, visibility, anchor_index, format_time_unit
|
|
374
377
|
)
|
|
@@ -387,11 +390,11 @@ def resolve_axis_x_overlap(
|
|
|
387
390
|
chart_width: float,
|
|
388
391
|
) -> AxisLabelLayout:
|
|
389
392
|
"""Return render-local overlap, angle, and temporal visibility choices."""
|
|
390
|
-
overlap = axis.
|
|
393
|
+
overlap = axis.labels.overlap
|
|
391
394
|
if overlap is None:
|
|
392
|
-
return AxisLabelLayout(None, axis.
|
|
393
|
-
if axis.
|
|
394
|
-
return AxisLabelLayout("allow", axis.
|
|
395
|
+
return AxisLabelLayout(None, axis.labels.angle, None, 0, "")
|
|
396
|
+
if axis.labels.angle is not None:
|
|
397
|
+
return AxisLabelLayout("allow", axis.labels.angle, None, 0, "")
|
|
395
398
|
if is_horizontal_bar:
|
|
396
399
|
return AxisLabelLayout("allow", 0.0, None, 0, "")
|
|
397
400
|
if not x_field or not data:
|
|
@@ -420,7 +423,7 @@ def resolve_axis_x_overlap(
|
|
|
420
423
|
edge_labels_flushed,
|
|
421
424
|
)
|
|
422
425
|
|
|
423
|
-
font = axis.
|
|
426
|
+
font = axis.labels.font
|
|
424
427
|
measurer = get_font_measurer(font.family)
|
|
425
428
|
widths = [measurer.measure(value, font.size) for value in values]
|
|
426
429
|
gap = get_chart_rendering().axis.label_gap_spaces * measurer.measure(" ", font.size)
|
|
@@ -428,7 +431,7 @@ def resolve_axis_x_overlap(
|
|
|
428
431
|
usable_width = chart_width * label_usable_ratio
|
|
429
432
|
if overlap.tilt and not _fits_flat(widths, usable_width):
|
|
430
433
|
angle, _ = _pick_tilt(
|
|
431
|
-
axis.
|
|
434
|
+
axis.labels,
|
|
432
435
|
x_field,
|
|
433
436
|
data,
|
|
434
437
|
chart_width,
|
|
@@ -21,7 +21,7 @@ Two tick-content scopes are safe to measure, both covered here:
|
|
|
21
21
|
|
|
22
22
|
1. **Baked** — ``tick_values`` + a concrete ``format`` (quantitative axes on
|
|
23
23
|
a theme that sets ``axis.ticks.count``). Dataface already knows the exact
|
|
24
|
-
values VL will render (``axis.values`` is emitted explicitly); measuring
|
|
24
|
+
values VL will render (``axis.scale.values`` is emitted explicitly); measuring
|
|
25
25
|
is exact, not an estimate. ``quantitative_tick_labels``.
|
|
26
26
|
2. **Unbaked** — no ``tick_values`` (e.g. the ``stark``/``plain`` themes,
|
|
27
27
|
which leave ``axis.ticks.count`` unset so VL picks its own "nice" ticks).
|
|
@@ -64,7 +64,7 @@ _BREATHING_ROOM_PX = 4.0
|
|
|
64
64
|
DEFAULT_VL_TICK_SIZE: float = 5.0
|
|
65
65
|
|
|
66
66
|
# Vega-Lite's own default axis.labelLimit when the theme leaves
|
|
67
|
-
# axis.
|
|
67
|
+
# axis.labels.max_width unset — the pixel width VL truncates a rendered label
|
|
68
68
|
# to (with an ellipsis) before this module ever measures it.
|
|
69
69
|
DEFAULT_VL_LABEL_LIMIT: float = 180.0
|
|
70
70
|
|
|
@@ -101,7 +101,7 @@ def measured_label_padding(
|
|
|
101
101
|
|
|
102
102
|
Returns 0.0 for an empty label list (nothing to reserve for). Callers
|
|
103
103
|
resolve VL's own tickSize default (``DEFAULT_VL_TICK_SIZE``) themselves
|
|
104
|
-
when the theme leaves ``axis.ticks.
|
|
104
|
+
when the theme leaves ``axis.ticks.length`` unset.
|
|
105
105
|
"""
|
|
106
106
|
if not labels:
|
|
107
107
|
return 0.0
|
|
@@ -117,7 +117,7 @@ def cap_padding_to_label_limit(
|
|
|
117
117
|
|
|
118
118
|
``measured_label_padding`` sizes the gutter to the widest label's full,
|
|
119
119
|
untruncated text width. Vega-Lite truncates any rendered label wider than
|
|
120
|
-
``labelLimit`` (``axis.
|
|
120
|
+
``labelLimit`` (``axis.labels.max_width``, or its own ``DEFAULT_VL_LABEL_LIMIT``
|
|
121
121
|
when unset) to that width plus an ellipsis — so reserving more than
|
|
122
122
|
``label_limit + tick_size + breathing_room`` leaves dead gutter space no
|
|
123
123
|
rendered label ever fills. Capping the final padding value is equivalent
|
|
@@ -282,7 +282,7 @@ def _emit_multi_metric_area(
|
|
|
282
282
|
style.area_mark,
|
|
283
283
|
style.line_mark,
|
|
284
284
|
style.point_mark,
|
|
285
|
-
|
|
285
|
+
chart.background,
|
|
286
286
|
style.single_series_fill,
|
|
287
287
|
True,
|
|
288
288
|
wide.color,
|
|
@@ -344,7 +344,7 @@ class AreaEmitter:
|
|
|
344
344
|
style.area_mark,
|
|
345
345
|
style.line_mark,
|
|
346
346
|
style.point_mark,
|
|
347
|
-
|
|
347
|
+
chart.background,
|
|
348
348
|
style.single_series_fill,
|
|
349
349
|
color_ch is not None,
|
|
350
350
|
top_encoding["color"] if color_ch is not None else {},
|
|
@@ -380,7 +380,7 @@ class AreaEmitter:
|
|
|
380
380
|
chart_id=chart.id,
|
|
381
381
|
axis_y=style.axis_y,
|
|
382
382
|
tooltip_format=style.tooltip_format,
|
|
383
|
-
background=
|
|
383
|
+
background=chart.background,
|
|
384
384
|
single_series_fill=style.single_series_fill,
|
|
385
385
|
legend=chart.legend,
|
|
386
386
|
config=build_palette_config(chart.palette),
|