ggaction 0.0.17 → 0.0.18
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.
- package/CHANGELOG.md +46 -0
- package/README.md +2 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +3 -3
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +3 -2
- package/src/actions/boxPlots/create.js +14 -4
- package/src/actions/boxPlots/edit.js +39 -33
- package/src/actions/boxPlots/materialize.js +7 -3
- package/src/actions/boxPlots/options.js +37 -11
- package/src/actions/canvas/actions.js +70 -95
- package/src/actions/canvas/fitting.js +16 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +28 -0
- package/src/actions/charts/area.js +2 -2
- package/src/actions/charts/bar.js +34 -7
- package/src/actions/charts/ecdf.js +4 -18
- package/src/actions/charts/endpoints.js +4 -18
- package/src/actions/charts/heatmap.js +4 -4
- package/src/actions/charts/index.js +2 -0
- package/src/actions/charts/interval-regression.js +2 -2
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/pie.js +3 -47
- package/src/actions/charts/radial.js +34 -12
- package/src/actions/charts/scatter.js +16 -2
- package/src/actions/charts/shared.js +2 -2
- package/src/actions/charts/text.js +35 -0
- package/src/actions/composition/actions.js +6 -2
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/edit.js +1 -1
- package/src/actions/data/regression.js +2 -1
- package/src/actions/data/shared.js +3 -2
- package/src/actions/data/stack.js +3 -1
- package/src/actions/encodings/appearance.js +37 -29
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +1 -1
- package/src/actions/encodings/color/index.js +1 -1
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/position/policies/arc.js +12 -10
- package/src/actions/encodings/position/policies/area.js +3 -3
- package/src/actions/encodings/position/policies/bar.js +15 -6
- package/src/actions/encodings/position/policies/line.js +17 -6
- package/src/actions/encodings/position/resolve.js +3 -2
- package/src/actions/encodings/ranged.js +17 -0
- package/src/actions/encodings/remove.js +3 -2
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/text.js +6 -3
- package/src/actions/errorBands/create.js +2 -2
- package/src/actions/errorBands/edit.js +2 -1
- package/src/actions/facets/actions.js +158 -240
- package/src/actions/gradientPlots/resolve.js +2 -17
- package/src/actions/guides/applicability.js +1 -1
- package/src/actions/guides/axes/axis.js +16 -4
- package/src/actions/guides/axes/edit.js +4 -4
- package/src/actions/guides/axes/labels.js +120 -46
- package/src/actions/guides/axes/lines.js +6 -5
- package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
- package/src/actions/guides/axes/parallel/policy.js +8 -7
- package/src/actions/guides/axes/remove.js +3 -2
- package/src/actions/guides/axes/tickGroups.js +5 -2
- package/src/actions/guides/axes/ticks.js +6 -3
- package/src/actions/guides/axes/titles.js +71 -26
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facadeLegend.js +10 -2
- package/src/actions/guides/grids/grid.js +7 -1
- package/src/actions/guides/grids/resolve.js +59 -12
- package/src/actions/guides/legends/blocks.js +3 -1
- package/src/actions/guides/legends/categorical/actions.js +70 -17
- package/src/actions/guides/legends/categorical/components.js +12 -8
- package/src/actions/guides/legends/categorical/index.js +6 -0
- package/src/actions/guides/legends/categorical/layout.js +37 -8
- package/src/actions/guides/legends/categorical/options.js +31 -20
- package/src/actions/guides/legends/categorical/resolve.js +2 -2
- package/src/actions/guides/legends/categorical/symbols.js +18 -12
- package/src/actions/guides/legends/categorical/validation.js +1 -6
- package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
- package/src/actions/guides/legends/continuous/common.js +1 -447
- package/src/actions/guides/legends/continuous/gradient.js +47 -34
- package/src/actions/guides/legends/continuous/interval.js +4 -4
- package/src/actions/guides/legends/continuous/opacity.js +19 -12
- package/src/actions/guides/legends/edit.js +1 -604
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/group.js +1 -0
- package/src/actions/guides/legends/lane.js +1 -516
- package/src/actions/guides/legends/remove.js +3 -0
- package/src/actions/guides/legends/sampling.js +1 -1
- package/src/actions/guides/legends/size.js +51 -43
- package/src/actions/guides/legends/strokeWidth.js +11 -29
- package/src/actions/guides/legends/target.js +3 -3
- package/src/actions/guides/legends/transition.js +21 -7
- package/src/actions/guides/polar/axes/labels.js +4 -2
- package/src/actions/guides/polar/axes/titles.js +7 -5
- package/src/actions/guides/polar/resolve.js +6 -14
- package/src/actions/index.js +16 -16
- package/src/actions/marks/arc/actions.js +3 -10
- package/src/actions/marks/area/actions.js +8 -5
- package/src/actions/marks/area/materialize.js +8 -9
- package/src/actions/marks/bar/create.js +10 -4
- package/src/actions/marks/bar/edit.js +8 -4
- package/src/actions/marks/bar/materialize.js +9 -6
- package/src/actions/marks/line/actions.js +11 -4
- package/src/actions/marks/line/materialize.js +9 -5
- package/src/actions/marks/point/edit.js +1 -1
- package/src/actions/marks/rect/actions.js +3 -3
- package/src/actions/marks/remove.js +2 -0
- package/src/actions/marks/rule/actions.js +7 -4
- package/src/actions/marks/text/actions.js +17 -5
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/primitives/semanticValidation/layer.js +4 -0
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +3 -3
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +9 -4
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +8 -3
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +2 -2
- package/src/actions/scales/editPolicy.js +9 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/preview.js +21 -1
- package/src/actions/scales/quantitativeColor.js +3 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +81 -27
- package/src/actions/theme/state.js +12 -0
- package/src/actions/titles/actions.js +5 -18
- package/src/actions/titles/resolve.js +41 -19
- package/src/actions/violinPlots/edit.js +3 -20
- package/src/core/compositionState.js +4 -1
- package/src/core/font.js +9 -2
- package/src/core/optionRequirements.js +1 -0
- package/src/core/resourceReferences.js +3 -0
- package/src/core/textMetricProfile.js +3 -2
- package/src/core/textMetrics.js +41 -4
- package/src/core/validation.js +26 -0
- package/src/core/vocabulary.js +4 -0
- package/src/grammar/aggregate.js +4 -18
- package/src/grammar/arcs.js +1 -386
- package/src/grammar/areaPaint.js +10 -0
- package/src/grammar/areaSeries.js +18 -10
- package/src/grammar/bars/geometry.js +30 -3
- package/src/grammar/bars/policy.js +12 -1
- package/src/grammar/bin.js +2 -12
- package/src/grammar/bin2d.js +1 -6
- package/src/grammar/boxPlot.js +56 -9
- package/src/grammar/categoryOrder.js +1 -6
- package/src/grammar/complete.js +15 -19
- package/src/grammar/computed.js +92 -25
- package/src/grammar/curveCommands.js +29 -16
- package/src/grammar/datasetSchema.js +3 -7
- package/src/grammar/displayLabels.js +28 -0
- package/src/grammar/ecdf.js +2 -9
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +5 -0
- package/src/grammar/facets/index.js +2 -5
- package/src/grammar/fold.js +2 -12
- package/src/grammar/gradientProfile.js +1 -6
- package/src/grammar/impute.js +21 -48
- package/src/grammar/interval.js +2 -9
- package/src/grammar/legendGradient.js +18 -0
- package/src/grammar/legendGroups.js +1 -0
- package/src/grammar/legendTitleStyle.js +22 -0
- package/src/grammar/lineSeries.js +32 -17
- package/src/grammar/markLabels.js +5 -2
- package/src/grammar/normalize.js +4 -54
- package/src/grammar/numeric.js +1 -290
- package/src/grammar/parallelCoordinates.js +1 -6
- package/src/grammar/parseTime.js +41 -0
- package/src/grammar/pathCommands.js +11 -4
- package/src/grammar/polar.js +1 -184
- package/src/grammar/positionCompatibility.js +3 -3
- package/src/grammar/regression/derive.js +7 -1
- package/src/grammar/regression/models.js +83 -55
- package/src/grammar/regression/parameters.js +14 -3
- package/src/grammar/roundedRect.js +42 -54
- package/src/grammar/scales/appearance.js +1 -206
- package/src/grammar/scales/color.js +1 -382
- package/src/grammar/scales/colorConsumers.js +5 -2
- package/src/grammar/scales/definition.js +1 -197
- package/src/grammar/scales/discretized.js +1 -195
- package/src/grammar/scales/size.js +1 -456
- package/src/grammar/scales/temporal.js +2 -13
- package/src/grammar/scales/transformed.js +1 -383
- package/src/grammar/scales/types.js +1 -1
- package/src/grammar/scales/validation.js +1 -66
- package/src/grammar/schemas/concreteGraphic.js +11 -0
- package/src/grammar/schemas/graphic.js +3 -0
- package/src/grammar/schemas/semanticPath.js +2 -1
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +3 -26
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +16 -7
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transforms.js +4 -1
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -110
- package/src/layout/facets.js +1 -244
- package/src/layout/labels.js +1 -0
- package/src/layout/legend.js +13 -0
- package/src/layout/legendGroup.js +1 -0
- package/src/layout/legendItems.js +59 -15
- package/src/layout/legendLane.js +1 -441
- package/src/layout/legendText.js +1 -0
- package/src/layout/title.js +35 -13
- package/src/materialization/bars/aggregate.js +11 -5
- package/src/materialization/bars/histogram.js +4 -1
- package/src/materialization/bars/ranged.js +19 -6
- package/src/materialization/bars/resolve.js +22 -1
- package/src/materialization/coordinateBounds.js +8 -0
- package/src/materialization/dependencies.js +4 -44
- package/src/materialization/facetGuides/layout.js +1 -0
- package/src/materialization/facetGuides/placement.js +19 -8
- package/src/materialization/facetGuides/preparation.js +15 -7
- package/src/materialization/facetHeaders.js +1 -347
- package/src/materialization/facets.js +1 -185
- package/src/materialization/graphicHierarchy.js +1 -1
- package/src/materialization/guides/layout.js +1 -57
- package/src/materialization/guides/resources.js +1 -123
- package/src/materialization/layout.js +53 -0
- package/src/materialization/legendGroups.js +1 -0
- package/src/materialization/legendText.js +1 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/capabilities.js +5 -6
- package/src/materialization/rect.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +1 -1
- package/src/materialization/scales/map.js +3 -0
- package/src/materialization/scales/policies/arc.js +8 -7
- package/src/materialization/scales/policies/bar.js +19 -7
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +5 -3
- package/src/materialization/selection/items/arc.js +1 -1
- package/src/materialization/selection/items/bar.js +7 -6
- package/src/materialization/selection/items/path.js +7 -6
- package/src/materialization/selection/items/rect.js +2 -2
- package/src/materialization/text.js +13 -4
- package/src/materialization/textLines.js +15 -0
- package/src/persistence/codec.js +37 -6
- package/src/persistence/validation.js +47 -0
- package/src/persistence.js +18 -5
- package/src/renderers/canvas/circle.js +6 -3
- package/src/renderers/canvas/fill.js +7 -1
- package/src/renderers/canvas/index.js +2 -1
- package/src/renderers/canvas/line.js +1 -0
- package/src/renderers/canvas/path.js +1 -1
- package/src/renderers/canvas/rect.js +11 -8
- package/src/renderers/canvas/stroke.js +2 -1
- package/src/renderers/canvas/text.js +5 -3
- package/src/renderers/svg.js +4 -1
- package/src/selectors/layers.js +19 -0
- package/src/theme/defaults.js +8 -2
- package/src/version.js +1 -1
- package/types/basic.d.ts +1 -0
- package/types/index.d.ts +7 -0
- package/types/program.d.ts +347 -85
|
@@ -7,7 +7,7 @@ const OPTIONS = Object.freeze([
|
|
|
7
7
|
"type",
|
|
8
8
|
"domain",
|
|
9
9
|
"range",
|
|
10
|
-
"interpolate", "midpoint",
|
|
10
|
+
"interpolate", "midpoint", "base", "constant",
|
|
11
11
|
"clamp",
|
|
12
12
|
"reverse",
|
|
13
13
|
"unknown"
|
|
@@ -19,7 +19,7 @@ export const setQuantitativeColorScale = /* @__PURE__ */ closedAction(
|
|
|
19
19
|
description: "Create or update an internal quantitative color scale."
|
|
20
20
|
}, OPTIONS,
|
|
21
21
|
function (args = {}) {
|
|
22
|
-
if (!["sequential", "quantize", "quantile", "threshold"].includes(args.type)) {
|
|
22
|
+
if (!["sequential", "log", "sqrt", "symlog", "quantize", "quantile", "threshold"].includes(args.type)) {
|
|
23
23
|
throw new Error(`Unsupported quantitative color scale type "${args.type}".`);
|
|
24
24
|
}
|
|
25
25
|
const existing = findSemanticScale(this, args.id);
|
|
@@ -34,7 +34,7 @@ export const setQuantitativeColorScale = /* @__PURE__ */ closedAction(
|
|
|
34
34
|
next = next.editSemantic({ property: `scale[${args.id}].midpoint`, remove: true });
|
|
35
35
|
}
|
|
36
36
|
for (const property of [
|
|
37
|
-
"type", "domain", "range", "interpolate", "midpoint", "clamp", "reverse", "unknown"
|
|
37
|
+
"type", "domain", "range", "interpolate", "midpoint", "base", "constant", "clamp", "reverse", "unknown"
|
|
38
38
|
]) {
|
|
39
39
|
if (!Object.hasOwn(args, property)) continue;
|
|
40
40
|
if (existing?.[property] === args[property]) continue;
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
findSelectionPolicy,
|
|
20
20
|
requireSelectionPolicy
|
|
21
21
|
} from "../../materialization/selection/policies/index.js";
|
|
22
|
-
import { legendGraphicIds } from "../../materialization/guides/resources.js";
|
|
23
22
|
import {
|
|
24
23
|
normalizeDimOthers,
|
|
25
24
|
validateUnitInterval
|
|
@@ -84,18 +83,7 @@ function categoricalLegendKinds(program, target) {
|
|
|
84
83
|
function resetCategoricalLegendSymbols(program, target) {
|
|
85
84
|
const kinds = categoricalLegendKinds(program, target);
|
|
86
85
|
if (kinds.length === 0) return program;
|
|
87
|
-
|
|
88
|
-
for (const kind of kinds) {
|
|
89
|
-
for (const id of legendGraphicIds(kind).filter(id => id.includes("Symbol"))) {
|
|
90
|
-
const graphic = next.graphicSpec.objects[id];
|
|
91
|
-
if (graphic === undefined) continue;
|
|
92
|
-
next = graphic.type === "collection"
|
|
93
|
-
? next.editGraphics({ target: id, property: "items", value: [] })
|
|
94
|
-
: next.editGraphics({ target: id, property: "length", value: 0 });
|
|
95
|
-
}
|
|
96
|
-
next = next.rematerializeLegendSymbols({ kind });
|
|
97
|
-
}
|
|
98
|
-
return next;
|
|
86
|
+
return program.rematerializeLegend();
|
|
99
87
|
}
|
|
100
88
|
|
|
101
89
|
function targetHighlightEntries(program, target) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { rematerializeTypography } from "../../materialization/typography.js";
|
|
2
|
-
import { themeTokens } from "../../theme/defaults.js";
|
|
2
|
+
import { AXIS_LABEL_FONT_SIZES, AXIS_TITLE_FONT_SIZE, themeTokens } from "../../theme/defaults.js";
|
|
3
3
|
import {
|
|
4
4
|
normalizeThemeState,
|
|
5
5
|
resolveEffectiveThemeTokens,
|
|
@@ -21,7 +21,7 @@ const THEME_STYLE_PROPERTIES = Object.freeze([
|
|
|
21
21
|
"background",
|
|
22
22
|
"fill",
|
|
23
23
|
"stroke",
|
|
24
|
-
"fontFamily"
|
|
24
|
+
"fontFamily", "fontSize"
|
|
25
25
|
]);
|
|
26
26
|
const COLOR_TOKENS = Object.freeze([
|
|
27
27
|
"mark",
|
|
@@ -51,15 +51,38 @@ function tokenFor(value, sourceTokens, targetTokens) {
|
|
|
51
51
|
return matches[0];
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function
|
|
54
|
+
function axisFamily(name) {
|
|
55
|
+
return name.startsWith("parallel") ? "parallel"
|
|
56
|
+
: /^(theta|radial|radius)/u.test(name) ? "polar" : "cartesian";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function axisTextRole(property, component) {
|
|
60
|
+
if (!["labels", "title"].includes(component)) return undefined;
|
|
61
|
+
const role = component === "labels" ? "axisLabel" : "axisTitle";
|
|
62
|
+
if (property === "fontFamily") return `${role}FontFamily`;
|
|
63
|
+
if (property === "fontSize") return `${role}FontSize`;
|
|
64
|
+
if (["fill", "color"].includes(property)) return role;
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function roleToken(tokens, role, family = "cartesian") {
|
|
69
|
+
if (tokens[role] !== undefined) return tokens[role];
|
|
70
|
+
if (role === "axisLabel") return family === "parallel" ? tokens.axis : tokens.text;
|
|
71
|
+
if (["axisLabelFontFamily", "axisTitleFontFamily"].includes(role)) return tokens.fontFamily;
|
|
72
|
+
if (role === "axisLabelFontSize") return AXIS_LABEL_FONT_SIZES[family];
|
|
73
|
+
if (role === "axisTitleFontSize") return AXIS_TITLE_FONT_SIZE;
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function roleValues(role, sourceTokens, legacy = [], family) {
|
|
55
78
|
return new Set([
|
|
56
|
-
sourceTokens
|
|
57
|
-
themeTokens("light")
|
|
79
|
+
roleToken(sourceTokens, role, family),
|
|
80
|
+
roleToken(themeTokens("light"), role, family),
|
|
58
81
|
...legacy.map(token => themeTokens("light")[token] ?? token)
|
|
59
82
|
]);
|
|
60
83
|
}
|
|
61
84
|
|
|
62
|
-
function mapRoleValue(value, role, sourceTokens, targetTokens, legacy) {
|
|
85
|
+
function mapRoleValue(value, role, sourceTokens, targetTokens, legacy, family) {
|
|
63
86
|
if (Array.isArray(value)) {
|
|
64
87
|
let changed = false;
|
|
65
88
|
const next = value.map(item => {
|
|
@@ -68,17 +91,18 @@ function mapRoleValue(value, role, sourceTokens, targetTokens, legacy) {
|
|
|
68
91
|
role,
|
|
69
92
|
sourceTokens,
|
|
70
93
|
targetTokens,
|
|
71
|
-
legacy
|
|
94
|
+
legacy, family
|
|
72
95
|
);
|
|
73
96
|
changed ||= mapped.changed;
|
|
74
97
|
return mapped.value;
|
|
75
98
|
});
|
|
76
99
|
return { changed, value: changed ? next : value };
|
|
77
100
|
}
|
|
78
|
-
if (!roleValues(role, sourceTokens, legacy).has(value)) {
|
|
101
|
+
if (!roleValues(role, sourceTokens, legacy, family).has(value)) {
|
|
79
102
|
return { changed: false, value };
|
|
80
103
|
}
|
|
81
|
-
|
|
104
|
+
const target = roleToken(targetTokens, role, family);
|
|
105
|
+
return { changed: value !== target, value: target };
|
|
82
106
|
}
|
|
83
107
|
|
|
84
108
|
function mapColorValue(value, sourceTokens, targetTokens) {
|
|
@@ -158,6 +182,9 @@ function componentRole(program, id, property) {
|
|
|
158
182
|
}
|
|
159
183
|
|
|
160
184
|
function graphicRole(program, id, property) {
|
|
185
|
+
const axisPart = /Axis(Labels|Titles?)$/u.exec(id);
|
|
186
|
+
const scoped = axisPart === null ? undefined : axisTextRole(property, axisPart[1] === "Labels" ? "labels" : "title");
|
|
187
|
+
if (scoped !== undefined) return scoped;
|
|
161
188
|
if (property === "fontFamily") return "fontFamily";
|
|
162
189
|
if (id === "canvas" && property === "background") return "background";
|
|
163
190
|
if (id === `${program.compositionSpec?.id}-headers` && property === "fill") {
|
|
@@ -198,6 +225,7 @@ function graphicRole(program, id, property) {
|
|
|
198
225
|
}
|
|
199
226
|
|
|
200
227
|
function roleLegacy(role) {
|
|
228
|
+
if (role === "axisLabel") return ["text", "axis"];
|
|
201
229
|
if (role === "axis") return ["text"];
|
|
202
230
|
if (role === "axisTitle") return ["text", "strongText"];
|
|
203
231
|
if (role === "strongText") return ["text"];
|
|
@@ -238,7 +266,7 @@ function mapGraphicProperties(
|
|
|
238
266
|
role,
|
|
239
267
|
sourceTokens,
|
|
240
268
|
targetTokens,
|
|
241
|
-
roleLegacy(role)
|
|
269
|
+
roleLegacy(role), axisFamily(id)
|
|
242
270
|
);
|
|
243
271
|
if (mapped.changed) {
|
|
244
272
|
next[property] = mapped.value;
|
|
@@ -304,6 +332,11 @@ function recolorGraphic(
|
|
|
304
332
|
|
|
305
333
|
function configRole(program, path) {
|
|
306
334
|
const property = path.at(-1);
|
|
335
|
+
if (path[0] === "guides" && path[1] === "axis") {
|
|
336
|
+
const component = path[2] === "parallel" ? path.at(-2) : path[3];
|
|
337
|
+
const scoped = axisTextRole(property, component);
|
|
338
|
+
if (scoped !== undefined) return scoped;
|
|
339
|
+
}
|
|
307
340
|
if (property === "fontFamily") return "fontFamily";
|
|
308
341
|
if (path[0] === "facets" && path.includes("headers") &&
|
|
309
342
|
property === "color") {
|
|
@@ -369,7 +402,7 @@ function recolorConfig(
|
|
|
369
402
|
}
|
|
370
403
|
if (value === null || typeof value !== "object") {
|
|
371
404
|
const property = path.at(-1);
|
|
372
|
-
if (!["color", "fill", "stroke", "background", "fontFamily"]
|
|
405
|
+
if (!["color", "fill", "stroke", "background", "fontFamily", "fontSize"]
|
|
373
406
|
.includes(property)) {
|
|
374
407
|
return { changed: false, value };
|
|
375
408
|
}
|
|
@@ -387,7 +420,7 @@ function recolorConfig(
|
|
|
387
420
|
role,
|
|
388
421
|
sourceTokens,
|
|
389
422
|
targetTokens,
|
|
390
|
-
roleLegacy(role)
|
|
423
|
+
roleLegacy(role), axisFamily(String(path[2] ?? ""))
|
|
391
424
|
);
|
|
392
425
|
}
|
|
393
426
|
if (["theme", "highlights", "selections"].includes(path[0])) {
|
|
@@ -722,10 +755,11 @@ function addAxisOverrides(overrides, op, args) {
|
|
|
722
755
|
`g:${idPrefix}Axis${match[2]}.${component === "line" || component === "ticks" ? "stroke" : "fill"}`
|
|
723
756
|
);
|
|
724
757
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
758
|
+
for (const property of ["fontFamily", "fontSize"]) {
|
|
759
|
+
if (["labels", "title"].includes(component) && Object.hasOwn(args, property)) {
|
|
760
|
+
overrides.add(`c:guides.axis.${channel}.${component}.${property}`);
|
|
761
|
+
overrides.add(`g:${idPrefix}Axis${match[2]}.${property}`);
|
|
762
|
+
}
|
|
729
763
|
}
|
|
730
764
|
}
|
|
731
765
|
|
|
@@ -750,15 +784,22 @@ function addCompleteAxisOverride(overrides, name, args) {
|
|
|
750
784
|
`g:${idPrefix}Axis${suffix}.${component === "line" || component === "ticks" ? "stroke" : "fill"}`
|
|
751
785
|
);
|
|
752
786
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
787
|
+
for (const property of ["fontFamily", "fontSize"]) {
|
|
788
|
+
if (["labels", "title"].includes(component) && Object.hasOwn(options ?? {}, property)) {
|
|
789
|
+
overrides.add(`c:guides.axis.${channel}.${component}.${property}`);
|
|
790
|
+
overrides.add(`g:${idPrefix}Axis${suffix}.${property}`);
|
|
791
|
+
}
|
|
757
792
|
}
|
|
758
793
|
}
|
|
759
794
|
}
|
|
760
795
|
|
|
761
796
|
function addCompleteAxisOverrides(overrides, op, args) {
|
|
797
|
+
if (args.guides?.axes !== null && typeof args.guides?.axes === "object") {
|
|
798
|
+
addCompleteAxisOverrides(overrides, "createAxes", args.guides.axes);
|
|
799
|
+
}
|
|
800
|
+
if (op === "createGuides" && args.axes !== null && typeof args.axes === "object") {
|
|
801
|
+
addCompleteAxisOverrides(overrides, "createAxes", args.axes);
|
|
802
|
+
}
|
|
762
803
|
const match = /^(?:create|edit)(X|Y|Theta|Radial)Axis$/u.exec(op);
|
|
763
804
|
if (match !== null) addCompleteAxisOverride(overrides, match[1], args);
|
|
764
805
|
const group = /^(?:create|edit)(X|Y)AxisTicksAndLabels$/u.exec(op);
|
|
@@ -823,11 +864,10 @@ function addParallelOverrides(overrides, program, op, args) {
|
|
|
823
864
|
`c:guides.axis.parallel.axes.dimensions.${index}.${component}.color`
|
|
824
865
|
);
|
|
825
866
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
);
|
|
867
|
+
for (const property of ["fontFamily", "fontSize"]) {
|
|
868
|
+
if (["labels", "title"].includes(component) && Object.hasOwn(options ?? {}, property)) {
|
|
869
|
+
overrides.add(`c:guides.axis.parallel.axes.dimensions.${index}.${component}.${property}`);
|
|
870
|
+
}
|
|
831
871
|
}
|
|
832
872
|
}
|
|
833
873
|
}
|
|
@@ -921,7 +961,7 @@ function explicitOverrides(program, node) {
|
|
|
921
961
|
const overrides = new Set();
|
|
922
962
|
if (op === "editGraphics" &&
|
|
923
963
|
typeof args.target === "string" &&
|
|
924
|
-
["background", "fill", "stroke", "fontFamily"].includes(args.property)) {
|
|
964
|
+
["background", "fill", "stroke", "fontFamily", "fontSize"].includes(args.property)) {
|
|
925
965
|
overrides.add(`g:${args.target}.${args.property}`);
|
|
926
966
|
}
|
|
927
967
|
if (["createCanvas", "editCanvas"].includes(op) &&
|
|
@@ -962,6 +1002,13 @@ function explicitOverrides(program, node) {
|
|
|
962
1002
|
if (op === "editFacetHeaders") {
|
|
963
1003
|
addFacetOverrides(overrides, program, args);
|
|
964
1004
|
}
|
|
1005
|
+
if (["facet", "facetGrid", "repeatCharts"].includes(op) && args.headers !== undefined) {
|
|
1006
|
+
const { row, column, ...common } = args.headers;
|
|
1007
|
+
addFacetOverrides(overrides, program, common);
|
|
1008
|
+
for (const [role, patch] of [["row", row], ["column", column]]) {
|
|
1009
|
+
if (patch !== undefined) addFacetOverrides(overrides, program, { ...patch, role });
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
965
1012
|
return overrides;
|
|
966
1013
|
}
|
|
967
1014
|
|
|
@@ -979,6 +1026,12 @@ function collectOverrides(program) {
|
|
|
979
1026
|
return overrides;
|
|
980
1027
|
}
|
|
981
1028
|
|
|
1029
|
+
function axisTypographyChanged(before, after) {
|
|
1030
|
+
if (before === after || after === null || typeof after !== "object") return false;
|
|
1031
|
+
return Object.entries(after).some(([key, value]) => ["fontFamily", "fontSize"].includes(key)
|
|
1032
|
+
? value !== before?.[key] : axisTypographyChanged(before?.[key], value));
|
|
1033
|
+
}
|
|
1034
|
+
|
|
982
1035
|
export function reconcileProgramTheme(program, { source, metadata }) {
|
|
983
1036
|
program = reconcileFollowingRegressions(program);
|
|
984
1037
|
const storedState = program.materializationConfigs.theme;
|
|
@@ -1033,7 +1086,8 @@ export function reconcileProgramTheme(program, { source, metadata }) {
|
|
|
1033
1086
|
|
|
1034
1087
|
const facetsChanged = configs.value.facets !==
|
|
1035
1088
|
themed.materializationConfigs.facets;
|
|
1036
|
-
const typographyChanged = sourceTokens.fontFamily !== targetTokens.fontFamily
|
|
1089
|
+
const typographyChanged = sourceTokens.fontFamily !== targetTokens.fontFamily ||
|
|
1090
|
+
axisTypographyChanged(themed.guideConfigs.axis, configs.value.guides.axis);
|
|
1037
1091
|
if (facetsChanged && next.compositionSpec?.type === "facet") {
|
|
1038
1092
|
next = next.materializeComposition();
|
|
1039
1093
|
} else if (typographyChanged) {
|
|
@@ -158,3 +158,15 @@ export function setThemeStateOverrides(state, overrides) {
|
|
|
158
158
|
overrides: cloneAndFreeze([...overrides].sort())
|
|
159
159
|
});
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
// Resolve active font defaults before guide layout validates their occupied bounds.
|
|
163
|
+
export function axisThemeTypography(state, component) {
|
|
164
|
+
if (resolveEffectiveThemeFrame(state) === undefined) return {};
|
|
165
|
+
const tokens = resolveEffectiveThemeTokens(state);
|
|
166
|
+
const role = component === "labels" ? "axisLabel" : "axisTitle";
|
|
167
|
+
const fontSize = tokens[`${role}FontSize`];
|
|
168
|
+
return {
|
|
169
|
+
fontFamily: tokens[`${role}FontFamily`] ?? tokens.fontFamily,
|
|
170
|
+
...(fontSize === undefined ? {} : { fontSize })
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { paintedTitleComponent, titleTextProperties } from "../../layout/title.js";
|
|
1
2
|
import { withGuideLayoutValidation } from "../../materialization/guides/layout.js";
|
|
2
3
|
import { action } from "../../core/action.js";
|
|
3
4
|
import { noOptions } from "../../core/validation.js";
|
|
@@ -56,24 +57,9 @@ function ensureTextShape(program, id, component) {
|
|
|
56
57
|
return program;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
function distributed(value, count) {
|
|
60
|
-
return count === 1 && Array.isArray(value) ? value[0] : value;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
60
|
function editTextGraphic(program, id, component, style) {
|
|
64
|
-
const count = component.lines.length;
|
|
65
61
|
let next = ensureTextShape(program, id, component);
|
|
66
|
-
for (const [property, value] of Object.entries({
|
|
67
|
-
x: distributed(component.x, count),
|
|
68
|
-
y: distributed(component.y, count),
|
|
69
|
-
text: distributed(component.lines, count),
|
|
70
|
-
fill: style.color,
|
|
71
|
-
fontSize: style.fontSize,
|
|
72
|
-
fontFamily: style.fontFamily,
|
|
73
|
-
fontWeight: style.fontWeight,
|
|
74
|
-
textAlign: component.textAlign,
|
|
75
|
-
textBaseline: "middle"
|
|
76
|
-
})) {
|
|
62
|
+
for (const [property, value] of Object.entries(titleTextProperties(component, style))) {
|
|
77
63
|
next = next.editGraphics({ target: id, property, value });
|
|
78
64
|
}
|
|
79
65
|
if (component.explicitRotation) {
|
|
@@ -99,10 +85,11 @@ function titleTextAction(kind, create) {
|
|
|
99
85
|
if (create && this.graphicSpec.objects[id] !== undefined) {
|
|
100
86
|
throw new Error(`${op} requires a missing chart ${kind} graphic.`);
|
|
101
87
|
}
|
|
102
|
-
const
|
|
103
|
-
if (
|
|
88
|
+
const layout = resolveTitleLayout(this, config)[kind];
|
|
89
|
+
if (layout === undefined) {
|
|
104
90
|
throw new Error(`${op} requires semantic subtitle text.`);
|
|
105
91
|
}
|
|
92
|
+
const component = paintedTitleComponent(layout);
|
|
106
93
|
if (!create) {
|
|
107
94
|
return editTextGraphic(this, id, component, config[`${kind}Style`]);
|
|
108
95
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { textBoundsFitCanvas } from "../../core/textMetrics.js";
|
|
2
|
+
import { resolveGuideCollisionBlocks } from "../../materialization/guides/resources.js";
|
|
3
|
+
import { canvasOverflowError } from "../../layout/canvas.js";
|
|
4
|
+
import { validateFontStyle } from "../../core/font.js";
|
|
1
5
|
import { isPlainObject } from "../../core/immutable.js";
|
|
2
6
|
import {
|
|
3
7
|
validateKeys,
|
|
@@ -19,11 +23,11 @@ import { findCanvasGraphic } from
|
|
|
19
23
|
"../../materialization/graphicHierarchy.js";
|
|
20
24
|
|
|
21
25
|
const OPTIONS = Object.freeze([
|
|
22
|
-
"text", "subtitle", "position", "align", "offset", "gap", "titleStyle",
|
|
26
|
+
"text", "subtitle", "position", "align", "offset", "alongOffset", "gap", "titleStyle",
|
|
23
27
|
"subtitleStyle", "maxWidth", "wrap", "lineHeight"
|
|
24
28
|
]);
|
|
25
29
|
const STYLE_OPTIONS = Object.freeze([
|
|
26
|
-
"color", "fontSize", "fontFamily", "fontWeight"
|
|
30
|
+
"color", "fontSize", "fontFamily", "fontWeight", "fontStyle"
|
|
27
31
|
]);
|
|
28
32
|
const POSITIONS = Object.freeze(["top", "bottom", "left", "right"]);
|
|
29
33
|
const ALIGNS = Object.freeze(["left", "center", "right"]);
|
|
@@ -32,6 +36,7 @@ const DEFAULTS = Object.freeze({
|
|
|
32
36
|
position: "top",
|
|
33
37
|
align: "left",
|
|
34
38
|
offset: 0,
|
|
39
|
+
alongOffset: 0,
|
|
35
40
|
gap: 8,
|
|
36
41
|
titleStyle: Object.freeze({
|
|
37
42
|
color: DEFAULT_COLORS.strongText,
|
|
@@ -47,9 +52,12 @@ const DEFAULTS = Object.freeze({
|
|
|
47
52
|
})
|
|
48
53
|
});
|
|
49
54
|
|
|
50
|
-
export function validateTitleString(value, label) {
|
|
55
|
+
export function validateTitleString(value, label, multiline = false) {
|
|
51
56
|
validateNonEmptyString(value, label);
|
|
52
|
-
if (/\r|\n/u.
|
|
57
|
+
if (multiline && value.split(/\r\n|\r|\n/u).every(line => line === "")) {
|
|
58
|
+
throw new Error(`${label} requires at least one non-empty line.`);
|
|
59
|
+
}
|
|
60
|
+
if (!multiline && /\r|\n/u.test(value)) {
|
|
53
61
|
throw new Error(`${label} does not accept explicit newlines.`);
|
|
54
62
|
}
|
|
55
63
|
return value;
|
|
@@ -70,6 +78,7 @@ function normalizeStyle(value, defaults, label) {
|
|
|
70
78
|
)) {
|
|
71
79
|
throw new TypeError(`${label} fontWeight must be a non-empty string or number.`);
|
|
72
80
|
}
|
|
81
|
+
if (style.fontStyle !== undefined) validateFontStyle(style.fontStyle, label);
|
|
73
82
|
return style;
|
|
74
83
|
}
|
|
75
84
|
|
|
@@ -78,6 +87,7 @@ function normalizeConfig(args, defaults, { hasSubtitle }) {
|
|
|
78
87
|
const align = args.align ?? defaults.align;
|
|
79
88
|
const offset = args.offset ?? defaults.offset;
|
|
80
89
|
const gap = args.gap ?? defaults.gap;
|
|
90
|
+
const alongOffset = args.alongOffset ?? defaults.alongOffset ?? 0;
|
|
81
91
|
const titleStyle = normalizeStyle(
|
|
82
92
|
args.titleStyle,
|
|
83
93
|
defaults.titleStyle,
|
|
@@ -100,6 +110,7 @@ function normalizeConfig(args, defaults, { hasSubtitle }) {
|
|
|
100
110
|
if (!Number.isFinite(offset)) {
|
|
101
111
|
throw new TypeError("Chart title offset must be a finite number.");
|
|
102
112
|
}
|
|
113
|
+
if (!Number.isFinite(alongOffset)) throw new TypeError("Chart title alongOffset must be a finite number.");
|
|
103
114
|
validateNonNegativeFinite(gap, "Chart title gap");
|
|
104
115
|
if (maxWidth !== undefined) {
|
|
105
116
|
validatePositiveFinite(maxWidth, "Chart title maxWidth");
|
|
@@ -121,6 +132,7 @@ function normalizeConfig(args, defaults, { hasSubtitle }) {
|
|
|
121
132
|
position,
|
|
122
133
|
align,
|
|
123
134
|
offset,
|
|
135
|
+
alongOffset,
|
|
124
136
|
gap,
|
|
125
137
|
titleStyle,
|
|
126
138
|
subtitleStyle,
|
|
@@ -132,10 +144,10 @@ function normalizeConfig(args, defaults, { hasSubtitle }) {
|
|
|
132
144
|
|
|
133
145
|
export function normalizeTitleOptions(args) {
|
|
134
146
|
validateOptionObject(args, OPTIONS, "createTitle");
|
|
135
|
-
const text = validateTitleString(args.text, "Chart title text");
|
|
147
|
+
const text = validateTitleString(args.text, "Chart title text", true);
|
|
136
148
|
const subtitle = args.subtitle === undefined
|
|
137
149
|
? undefined
|
|
138
|
-
: validateTitleString(args.subtitle, "Chart subtitle");
|
|
150
|
+
: validateTitleString(args.subtitle, "Chart subtitle", true);
|
|
139
151
|
return {
|
|
140
152
|
text,
|
|
141
153
|
subtitle,
|
|
@@ -151,12 +163,12 @@ export function normalizeTitleEditOptions(args, previous, semanticTitle) {
|
|
|
151
163
|
});
|
|
152
164
|
const text = args.text === undefined
|
|
153
165
|
? semanticTitle.text
|
|
154
|
-
: validateTitleString(args.text, "Chart title text");
|
|
166
|
+
: validateTitleString(args.text, "Chart title text", true);
|
|
155
167
|
const subtitle = args.subtitle === false
|
|
156
168
|
? undefined
|
|
157
169
|
: args.subtitle === undefined
|
|
158
170
|
? semanticTitle.subtitle
|
|
159
|
-
: validateTitleString(args.subtitle, "Chart subtitle");
|
|
171
|
+
: validateTitleString(args.subtitle, "Chart subtitle", true);
|
|
160
172
|
if (args.subtitle !== undefined && args.subtitle !== false && typeof args.subtitle !== "string") {
|
|
161
173
|
throw new TypeError("Chart subtitle must be a non-empty string or false.");
|
|
162
174
|
}
|
|
@@ -175,11 +187,8 @@ export function requireTitleConfig(program) {
|
|
|
175
187
|
}
|
|
176
188
|
|
|
177
189
|
function validateLayout(position, titleBounds, plot, canvas) {
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
-
titleBounds.right > canvas.width || titleBounds.bottom > canvas.height
|
|
181
|
-
) {
|
|
182
|
-
throw new Error(`Chart title requires more ${position}-margin space.`);
|
|
190
|
+
if (!textBoundsFitCanvas(titleBounds, canvas)) {
|
|
191
|
+
throw canvasOverflowError(`Chart title requires more ${position}-margin space.`, [titleBounds], canvas);
|
|
183
192
|
}
|
|
184
193
|
const outsidePlot = {
|
|
185
194
|
top: titleBounds.bottom <= plot.y,
|
|
@@ -205,19 +214,31 @@ export function resolveTitleLayout(program, config) {
|
|
|
205
214
|
throw new Error("Chart title layout requires Canvas bounds and dimensions.");
|
|
206
215
|
}
|
|
207
216
|
const block = buildTitleReadingBlock({
|
|
208
|
-
text: validateTitleString(program.semanticSpec.title.text, "Chart title text"),
|
|
217
|
+
text: validateTitleString(program.semanticSpec.title.text, "Chart title text", true),
|
|
209
218
|
subtitle: program.semanticSpec.title.subtitle === undefined
|
|
210
219
|
? undefined
|
|
211
220
|
: validateTitleString(
|
|
212
221
|
program.semanticSpec.title.subtitle,
|
|
213
|
-
"Chart subtitle"
|
|
222
|
+
"Chart subtitle", true
|
|
214
223
|
)
|
|
215
224
|
}, config, program.materializationConfigs.textMetrics);
|
|
225
|
+
const automatic = program.materializationConfigs.canvas?.plot !== undefined;
|
|
226
|
+
const near = ["top", "left"].includes(config.position);
|
|
216
227
|
const horizontal = ["top", "bottom"].includes(config.position);
|
|
228
|
+
let titleEdge = horizontal ? plot.y + (near ? 0 : plot.height)
|
|
229
|
+
: plot.x + (near ? 0 : plot.width);
|
|
230
|
+
if (automatic) {
|
|
231
|
+
const occupied = resolveGuideCollisionBlocks(program.graphicSpec, program.guideConfigs,
|
|
232
|
+
undefined, program.materializationConfigs.textMetrics).filter(item => item.position === config.position);
|
|
233
|
+
const key = horizontal ? (near ? "top" : "bottom") : (near ? "left" : "right");
|
|
234
|
+
for (const item of occupied) titleEdge = near ? Math.min(titleEdge, item.bounds[key]) : Math.max(titleEdge, item.bounds[key]);
|
|
235
|
+
titleEdge += (near ? -1 : 1) * (16 + config.offset);
|
|
236
|
+
}
|
|
217
237
|
let component;
|
|
218
238
|
if (horizontal) {
|
|
219
|
-
const x = alignedTextAnchor(plot.x, plot.width, config.align);
|
|
220
|
-
const blockTop =
|
|
239
|
+
const x = alignedTextAnchor(plot.x, plot.width, config.align) + (config.alongOffset ?? 0);
|
|
240
|
+
const blockTop = automatic ? titleEdge - (near ? block.height : 0)
|
|
241
|
+
: config.position === "top"
|
|
221
242
|
? 16 + config.offset
|
|
222
243
|
: plot.y + plot.height + config.offset;
|
|
223
244
|
component = (lines, centers) => ({
|
|
@@ -230,8 +251,9 @@ export function resolveTitleLayout(program, config) {
|
|
|
230
251
|
});
|
|
231
252
|
} else {
|
|
232
253
|
const rotation = config.position === "left" ? -Math.PI / 2 : Math.PI / 2;
|
|
233
|
-
const y = alignedTitleAnchor(plot.y, plot.height, block.width, config.align);
|
|
234
|
-
const edge =
|
|
254
|
+
const y = alignedTitleAnchor(plot.y, plot.height, block.width, config.align) + (config.alongOffset ?? 0);
|
|
255
|
+
const edge = automatic ? titleEdge + (near ? -block.height : block.height)
|
|
256
|
+
: config.position === "left"
|
|
235
257
|
? 16 + config.offset
|
|
236
258
|
: canvas.properties.width - 16 + config.offset;
|
|
237
259
|
const mapX = value => config.position === "left" ? edge + value : edge - value;
|
|
@@ -2,7 +2,7 @@ import { action } from "../../core/action.js";
|
|
|
2
2
|
import { validateUserId } from "../../core/identifiers.js";
|
|
3
3
|
import { validateKeys } from "../../core/validation.js";
|
|
4
4
|
import { findDataset } from "../../selectors/datasets.js";
|
|
5
|
-
import { findLayer } from "../../selectors/layers.js";
|
|
5
|
+
import { findLayer, resolveConfiguredOwner } from "../../selectors/layers.js";
|
|
6
6
|
import { rebindDistributionGuides } from "../distributions/revision.js";
|
|
7
7
|
import {
|
|
8
8
|
resolveViolinDensity,
|
|
@@ -15,24 +15,7 @@ const OPTIONS = Object.freeze([
|
|
|
15
15
|
"target", "data", "x", "y", "split", "density"
|
|
16
16
|
]);
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
const eligible = program.semanticSpec.layers.filter(
|
|
20
|
-
layer => program.markConfigs[layer.id]?.violinPlot?.materialized === true
|
|
21
|
-
);
|
|
22
|
-
if (requested !== undefined) {
|
|
23
|
-
const id = validateUserId(requested, "Violin-plot owner id");
|
|
24
|
-
const layer = findLayer(program, id);
|
|
25
|
-
if (layer === undefined || !eligible.includes(layer)) {
|
|
26
|
-
throw new Error(`Unknown violin-plot owner "${id}".`);
|
|
27
|
-
}
|
|
28
|
-
return layer;
|
|
29
|
-
}
|
|
30
|
-
const current = findLayer(program, program.context.currentMark);
|
|
31
|
-
if (current !== undefined && eligible.includes(current)) return current;
|
|
32
|
-
if (eligible.length === 1) return eligible[0];
|
|
33
|
-
if (eligible.length === 0) throw new Error(`${OPERATION} requires a violin plot.`);
|
|
34
|
-
throw new Error(`${OPERATION} target is ambiguous; provide target.`);
|
|
35
|
-
}
|
|
18
|
+
|
|
36
19
|
|
|
37
20
|
function currentPosition(owner, current, channel) {
|
|
38
21
|
const categorical = current.orientation === "vertical"
|
|
@@ -84,7 +67,7 @@ export const editViolinPlot = /* @__PURE__ */ action(
|
|
|
84
67
|
if (!OPTIONS.slice(1).some(key => Object.hasOwn(args, key))) {
|
|
85
68
|
throw new Error(`${OPERATION} requires at least one violin-plot option.`);
|
|
86
69
|
}
|
|
87
|
-
const owner =
|
|
70
|
+
const owner = resolveConfiguredOwner(this, args.target, { config: "violinPlot", operation: OPERATION, kind: "violin", materialized: true }, validateUserId);
|
|
88
71
|
const current = this.markConfigs[owner.id].violinPlot;
|
|
89
72
|
const source = Object.hasOwn(args, "data")
|
|
90
73
|
? validateUserId(args.data, "Violin-plot data id")
|
|
@@ -272,7 +272,7 @@ export function ownCompositionSpec(compositionSpec, children) {
|
|
|
272
272
|
}
|
|
273
273
|
const allowed = [
|
|
274
274
|
"id", "type", "direction", "children", "columns", "gap", "align",
|
|
275
|
-
"padding", "facet"
|
|
275
|
+
"padding", "facet", "spacing"
|
|
276
276
|
];
|
|
277
277
|
const unknown = Object.keys(compositionSpec).find(key => !allowed.includes(key));
|
|
278
278
|
if (unknown !== undefined) {
|
|
@@ -282,6 +282,9 @@ export function ownCompositionSpec(compositionSpec, children) {
|
|
|
282
282
|
throw new TypeError("compositionSpec.id must be a non-empty string.");
|
|
283
283
|
}
|
|
284
284
|
const facet = compositionSpec.type === "facet";
|
|
285
|
+
if (compositionSpec.spacing !== undefined && (!facet || !["plot", "canvas"].includes(compositionSpec.spacing))) {
|
|
286
|
+
throw new Error("compositionSpec.spacing requires a facet and canvas or plot spacing.");
|
|
287
|
+
}
|
|
285
288
|
if (compositionSpec.type !== undefined && !facet) {
|
|
286
289
|
throw new Error(`Unknown compositionSpec type "${compositionSpec.type}".`);
|
|
287
290
|
}
|
package/src/core/font.js
CHANGED
|
@@ -5,8 +5,15 @@ export function normalizeRendererFontWeight(fontWeight) {
|
|
|
5
5
|
return Math.min(900, Math.max(100, Math.round(fontWeight / 100) * 100));
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export function
|
|
9
|
-
|
|
8
|
+
export function validateFontStyle(value, label = "Text") {
|
|
9
|
+
if (!["normal", "italic"].includes(value)) {
|
|
10
|
+
throw new Error(`${label} fontStyle must be normal or italic.`);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function textMetricKey({ text, fontFamily, fontSize, fontWeight, fontStyle = "normal" }) {
|
|
16
|
+
return JSON.stringify([text, fontFamily, fontSize, fontWeight, fontStyle]);
|
|
10
17
|
}
|
|
11
18
|
|
|
12
19
|
export function textMetricFontWeight(value = "normal") {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The method's value-producing contract is shared by runtime validation and
|
|
2
2
|
// authoring knowledge; editing can inherit these values from stored state.
|
|
3
3
|
export const IMPUTE_REQUIRED_OPTIONS = Object.freeze({
|
|
4
|
+
mean: Object.freeze([]),
|
|
4
5
|
constant: Object.freeze(["value"]),
|
|
5
6
|
forward: Object.freeze(["sortBy"]),
|
|
6
7
|
backward: Object.freeze(["sortBy"]),
|
|
@@ -199,6 +199,9 @@ export function collectMarkConfigReferences(program) {
|
|
|
199
199
|
|
|
200
200
|
export function collectGuideConfigReferences(program) {
|
|
201
201
|
const references = [];
|
|
202
|
+
for (const target of Object.keys(program.guideConfigs?.legendGroups ?? {})) {
|
|
203
|
+
append(references, reference("mark", target, "legend", target, ["legendGroups", target]));
|
|
204
|
+
}
|
|
202
205
|
for (const [family, configs] of Object.entries(program.guideConfigs ?? {})) {
|
|
203
206
|
collectKnownKeys(configs, [], (key, value, path, parentPath) => {
|
|
204
207
|
const ownerId = parentPath.length === 0 ? family : parentPath.join(".");
|
|
@@ -2,7 +2,7 @@ import { cloneAndFreeze } from "./immutable.js";
|
|
|
2
2
|
import { validateUserId } from "./identifiers.js";
|
|
3
3
|
import { validateOptionObject, validatePositiveFinite, validateNonNegativeFinite, validateNonEmptyString } from "./validation.js";
|
|
4
4
|
|
|
5
|
-
import { textMetricKey } from "./font.js";
|
|
5
|
+
import { textMetricKey, validateFontStyle } from "./font.js";
|
|
6
6
|
|
|
7
7
|
export function normalizeTextMetricProfile(profile) {
|
|
8
8
|
validateOptionObject(profile, ["schemaVersion", "id", "measurements"], "Text metrics profile");
|
|
@@ -11,7 +11,7 @@ export function normalizeTextMetricProfile(profile) {
|
|
|
11
11
|
if (!Array.isArray(profile.measurements)) throw new TypeError("Text metrics measurements must be an array.");
|
|
12
12
|
const seen = new Set();
|
|
13
13
|
for (const measurement of profile.measurements) {
|
|
14
|
-
validateOptionObject(measurement, ["text", "fontFamily", "fontSize", "fontWeight", "width"], "Text measurement");
|
|
14
|
+
validateOptionObject(measurement, ["text", "fontFamily", "fontSize", "fontWeight", "fontStyle", "width"], "Text measurement");
|
|
15
15
|
if (typeof measurement.text !== "string") throw new TypeError("Text measurement text must be a string.");
|
|
16
16
|
validateNonEmptyString(measurement.fontFamily, "Text measurement fontFamily");
|
|
17
17
|
validatePositiveFinite(measurement.fontSize, "Text measurement fontSize");
|
|
@@ -20,6 +20,7 @@ export function normalizeTextMetricProfile(profile) {
|
|
|
20
20
|
measurement.fontWeight > 900 || measurement.fontWeight % 100 !== 0) {
|
|
21
21
|
throw new RangeError("Text measurement fontWeight must be 100–900 in steps of 100.");
|
|
22
22
|
}
|
|
23
|
+
if (measurement.fontStyle !== undefined) validateFontStyle(measurement.fontStyle, "Text measurement");
|
|
23
24
|
const key = textMetricKey(measurement);
|
|
24
25
|
if (seen.has(key)) throw new Error("Duplicate text measurement combination.");
|
|
25
26
|
seen.add(key);
|