ggaction 0.0.16 → 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 +66 -0
- package/README.md +3 -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 +10 -4
- 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/bin.js +1 -1
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/create.js +18 -5
- package/src/actions/data/density.js +1 -1
- package/src/actions/data/derived.js +15 -1
- package/src/actions/data/edit.js +20 -7
- package/src/actions/data/filter.js +15 -4
- package/src/actions/data/index.js +3 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/regression.js +6 -2
- package/src/actions/data/revise.js +37 -5
- package/src/actions/data/shared.js +33 -4
- package/src/actions/data/sort.js +21 -0
- package/src/actions/data/stack.js +3 -1
- package/src/actions/data/summary.js +1 -1
- package/src/actions/encodings/angle.js +4 -1
- package/src/actions/encodings/appearance.js +43 -31
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +5 -2
- package/src/actions/encodings/color/index.js +5 -2
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/offset.js +4 -1
- 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 +9 -3
- package/src/actions/encodings/ranged.js +23 -1
- package/src/actions/encodings/remove.js +3 -2
- package/src/actions/encodings/ruleAppearance.js +4 -1
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/stroke.js +4 -1
- package/src/actions/encodings/strokeDash.js +6 -1
- package/src/actions/encodings/text.js +12 -4
- 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 +17 -4
- package/src/actions/marks/bar/edit.js +18 -7
- 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/create.js +8 -1
- package/src/actions/marks/point/edit.js +13 -6
- package/src/actions/marks/point/materialize.js +4 -2
- package/src/actions/marks/rect/actions.js +26 -11
- package/src/actions/marks/remove.js +2 -0
- package/src/actions/marks/rule/actions.js +31 -12
- package/src/actions/marks/text/actions.js +42 -11
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/marks/tick/actions.js +23 -8
- package/src/actions/primitives/semanticAction.js +37 -3
- package/src/actions/primitives/semanticValidation/dataset.js +13 -1
- package/src/actions/primitives/semanticValidation/layer.js +26 -2
- package/src/actions/primitives/semanticValidation/scale.js +6 -0
- package/src/actions/regression/create.js +26 -5
- package/src/actions/regression/edit.js +50 -15
- package/src/actions/regression/follow.js +22 -0
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +20 -15
- package/src/actions/scales/consumers/common.js +2 -1
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +10 -4
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/editPolicy.js +9 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/parallel.js +1 -1
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +49 -2
- package/src/actions/scales/quantitativeColor.js +3 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +83 -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/action.js +3 -2
- package/src/core/compositionState.js +4 -1
- package/src/core/font.js +9 -2
- package/src/core/optionRequirements.js +1 -0
- package/src/core/programState.js +5 -1
- 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 +41 -22
- 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 +382 -0
- package/src/grammar/density.js +53 -5
- 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/filter.js +133 -39
- 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 +34 -10
- package/src/grammar/itemMissing.js +59 -0
- 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 +63 -16
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/models.js +116 -59
- package/src/grammar/regression/parameters.js +69 -6
- 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 -190
- 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 +5 -3
- package/src/grammar/sort.js +88 -0
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +120 -45
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +16 -7
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transformTopology.js +1 -0
- package/src/grammar/transforms.js +25 -12
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspection.js +495 -0
- package/src/inspectionDescriptors.js +3 -0
- 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 +26 -3
- 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/revisionIdentity.js +19 -0
- 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 +72 -7
- 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 +17 -6
- 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 +94 -8
- 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/inspection.d.ts +17 -0
- package/types/program.d.ts +494 -112
|
@@ -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,11 +1,12 @@
|
|
|
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,
|
|
6
6
|
setThemeStateOverrides
|
|
7
7
|
} from "./state.js";
|
|
8
8
|
import { findLayer } from "../../selectors/layers.js";
|
|
9
|
+
import { reconcileFollowingRegressions } from "../regression/follow.js";
|
|
9
10
|
|
|
10
11
|
let rematerializeThemeHighlights = program => program;
|
|
11
12
|
|
|
@@ -20,7 +21,7 @@ const THEME_STYLE_PROPERTIES = Object.freeze([
|
|
|
20
21
|
"background",
|
|
21
22
|
"fill",
|
|
22
23
|
"stroke",
|
|
23
|
-
"fontFamily"
|
|
24
|
+
"fontFamily", "fontSize"
|
|
24
25
|
]);
|
|
25
26
|
const COLOR_TOKENS = Object.freeze([
|
|
26
27
|
"mark",
|
|
@@ -50,15 +51,38 @@ function tokenFor(value, sourceTokens, targetTokens) {
|
|
|
50
51
|
return matches[0];
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
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) {
|
|
54
78
|
return new Set([
|
|
55
|
-
sourceTokens
|
|
56
|
-
themeTokens("light")
|
|
79
|
+
roleToken(sourceTokens, role, family),
|
|
80
|
+
roleToken(themeTokens("light"), role, family),
|
|
57
81
|
...legacy.map(token => themeTokens("light")[token] ?? token)
|
|
58
82
|
]);
|
|
59
83
|
}
|
|
60
84
|
|
|
61
|
-
function mapRoleValue(value, role, sourceTokens, targetTokens, legacy) {
|
|
85
|
+
function mapRoleValue(value, role, sourceTokens, targetTokens, legacy, family) {
|
|
62
86
|
if (Array.isArray(value)) {
|
|
63
87
|
let changed = false;
|
|
64
88
|
const next = value.map(item => {
|
|
@@ -67,17 +91,18 @@ function mapRoleValue(value, role, sourceTokens, targetTokens, legacy) {
|
|
|
67
91
|
role,
|
|
68
92
|
sourceTokens,
|
|
69
93
|
targetTokens,
|
|
70
|
-
legacy
|
|
94
|
+
legacy, family
|
|
71
95
|
);
|
|
72
96
|
changed ||= mapped.changed;
|
|
73
97
|
return mapped.value;
|
|
74
98
|
});
|
|
75
99
|
return { changed, value: changed ? next : value };
|
|
76
100
|
}
|
|
77
|
-
if (!roleValues(role, sourceTokens, legacy).has(value)) {
|
|
101
|
+
if (!roleValues(role, sourceTokens, legacy, family).has(value)) {
|
|
78
102
|
return { changed: false, value };
|
|
79
103
|
}
|
|
80
|
-
|
|
104
|
+
const target = roleToken(targetTokens, role, family);
|
|
105
|
+
return { changed: value !== target, value: target };
|
|
81
106
|
}
|
|
82
107
|
|
|
83
108
|
function mapColorValue(value, sourceTokens, targetTokens) {
|
|
@@ -157,6 +182,9 @@ function componentRole(program, id, property) {
|
|
|
157
182
|
}
|
|
158
183
|
|
|
159
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;
|
|
160
188
|
if (property === "fontFamily") return "fontFamily";
|
|
161
189
|
if (id === "canvas" && property === "background") return "background";
|
|
162
190
|
if (id === `${program.compositionSpec?.id}-headers` && property === "fill") {
|
|
@@ -197,6 +225,7 @@ function graphicRole(program, id, property) {
|
|
|
197
225
|
}
|
|
198
226
|
|
|
199
227
|
function roleLegacy(role) {
|
|
228
|
+
if (role === "axisLabel") return ["text", "axis"];
|
|
200
229
|
if (role === "axis") return ["text"];
|
|
201
230
|
if (role === "axisTitle") return ["text", "strongText"];
|
|
202
231
|
if (role === "strongText") return ["text"];
|
|
@@ -237,7 +266,7 @@ function mapGraphicProperties(
|
|
|
237
266
|
role,
|
|
238
267
|
sourceTokens,
|
|
239
268
|
targetTokens,
|
|
240
|
-
roleLegacy(role)
|
|
269
|
+
roleLegacy(role), axisFamily(id)
|
|
241
270
|
);
|
|
242
271
|
if (mapped.changed) {
|
|
243
272
|
next[property] = mapped.value;
|
|
@@ -303,6 +332,11 @@ function recolorGraphic(
|
|
|
303
332
|
|
|
304
333
|
function configRole(program, path) {
|
|
305
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
|
+
}
|
|
306
340
|
if (property === "fontFamily") return "fontFamily";
|
|
307
341
|
if (path[0] === "facets" && path.includes("headers") &&
|
|
308
342
|
property === "color") {
|
|
@@ -368,7 +402,7 @@ function recolorConfig(
|
|
|
368
402
|
}
|
|
369
403
|
if (value === null || typeof value !== "object") {
|
|
370
404
|
const property = path.at(-1);
|
|
371
|
-
if (!["color", "fill", "stroke", "background", "fontFamily"]
|
|
405
|
+
if (!["color", "fill", "stroke", "background", "fontFamily", "fontSize"]
|
|
372
406
|
.includes(property)) {
|
|
373
407
|
return { changed: false, value };
|
|
374
408
|
}
|
|
@@ -386,7 +420,7 @@ function recolorConfig(
|
|
|
386
420
|
role,
|
|
387
421
|
sourceTokens,
|
|
388
422
|
targetTokens,
|
|
389
|
-
roleLegacy(role)
|
|
423
|
+
roleLegacy(role), axisFamily(String(path[2] ?? ""))
|
|
390
424
|
);
|
|
391
425
|
}
|
|
392
426
|
if (["theme", "highlights", "selections"].includes(path[0])) {
|
|
@@ -721,10 +755,11 @@ function addAxisOverrides(overrides, op, args) {
|
|
|
721
755
|
`g:${idPrefix}Axis${match[2]}.${component === "line" || component === "ticks" ? "stroke" : "fill"}`
|
|
722
756
|
);
|
|
723
757
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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
|
+
}
|
|
728
763
|
}
|
|
729
764
|
}
|
|
730
765
|
|
|
@@ -749,15 +784,22 @@ function addCompleteAxisOverride(overrides, name, args) {
|
|
|
749
784
|
`g:${idPrefix}Axis${suffix}.${component === "line" || component === "ticks" ? "stroke" : "fill"}`
|
|
750
785
|
);
|
|
751
786
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
+
}
|
|
756
792
|
}
|
|
757
793
|
}
|
|
758
794
|
}
|
|
759
795
|
|
|
760
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
|
+
}
|
|
761
803
|
const match = /^(?:create|edit)(X|Y|Theta|Radial)Axis$/u.exec(op);
|
|
762
804
|
if (match !== null) addCompleteAxisOverride(overrides, match[1], args);
|
|
763
805
|
const group = /^(?:create|edit)(X|Y)AxisTicksAndLabels$/u.exec(op);
|
|
@@ -822,11 +864,10 @@ function addParallelOverrides(overrides, program, op, args) {
|
|
|
822
864
|
`c:guides.axis.parallel.axes.dimensions.${index}.${component}.color`
|
|
823
865
|
);
|
|
824
866
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
);
|
|
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
|
+
}
|
|
830
871
|
}
|
|
831
872
|
}
|
|
832
873
|
}
|
|
@@ -920,7 +961,7 @@ function explicitOverrides(program, node) {
|
|
|
920
961
|
const overrides = new Set();
|
|
921
962
|
if (op === "editGraphics" &&
|
|
922
963
|
typeof args.target === "string" &&
|
|
923
|
-
["background", "fill", "stroke", "fontFamily"].includes(args.property)) {
|
|
964
|
+
["background", "fill", "stroke", "fontFamily", "fontSize"].includes(args.property)) {
|
|
924
965
|
overrides.add(`g:${args.target}.${args.property}`);
|
|
925
966
|
}
|
|
926
967
|
if (["createCanvas", "editCanvas"].includes(op) &&
|
|
@@ -961,6 +1002,13 @@ function explicitOverrides(program, node) {
|
|
|
961
1002
|
if (op === "editFacetHeaders") {
|
|
962
1003
|
addFacetOverrides(overrides, program, args);
|
|
963
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
|
+
}
|
|
964
1012
|
return overrides;
|
|
965
1013
|
}
|
|
966
1014
|
|
|
@@ -978,7 +1026,14 @@ function collectOverrides(program) {
|
|
|
978
1026
|
return overrides;
|
|
979
1027
|
}
|
|
980
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
|
+
|
|
981
1035
|
export function reconcileProgramTheme(program, { source, metadata }) {
|
|
1036
|
+
program = reconcileFollowingRegressions(program);
|
|
982
1037
|
const storedState = program.materializationConfigs.theme;
|
|
983
1038
|
if (storedState === undefined) return program;
|
|
984
1039
|
// Data/context/trace-only transitions cannot change authored appearance.
|
|
@@ -1031,7 +1086,8 @@ export function reconcileProgramTheme(program, { source, metadata }) {
|
|
|
1031
1086
|
|
|
1032
1087
|
const facetsChanged = configs.value.facets !==
|
|
1033
1088
|
themed.materializationConfigs.facets;
|
|
1034
|
-
const typographyChanged = sourceTokens.fontFamily !== targetTokens.fontFamily
|
|
1089
|
+
const typographyChanged = sourceTokens.fontFamily !== targetTokens.fontFamily ||
|
|
1090
|
+
axisTypographyChanged(themed.guideConfigs.axis, configs.value.guides.axis);
|
|
1035
1091
|
if (facetsChanged && next.compositionSpec?.type === "facet") {
|
|
1036
1092
|
next = next.materializeComposition();
|
|
1037
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")
|
package/src/core/action.js
CHANGED
|
@@ -192,7 +192,8 @@ export function action(metadata, implementation) {
|
|
|
192
192
|
const ownedMetadata = freezeOwned({
|
|
193
193
|
op: metadata.op,
|
|
194
194
|
description: metadata.description,
|
|
195
|
-
scope
|
|
195
|
+
scope,
|
|
196
|
+
...(Array.isArray(metadata.options) ? { options: [...metadata.options] } : {})
|
|
196
197
|
});
|
|
197
198
|
|
|
198
199
|
const wrappedAction = function wrappedAction(args = {}) {
|
|
@@ -240,7 +241,7 @@ export function action(metadata, implementation) {
|
|
|
240
241
|
// Private built-in convenience: action() owns the object boundary and trace,
|
|
241
242
|
// while each definition supplies its closed option vocabulary exactly once.
|
|
242
243
|
export function closedAction(metadata, options, implementation) {
|
|
243
|
-
return action(metadata, function (args = {}) {
|
|
244
|
+
return action({ ...metadata, options }, function (args = {}) {
|
|
244
245
|
if (options !== undefined) validateKeys(args, options, metadata.op);
|
|
245
246
|
return implementation.call(this, args);
|
|
246
247
|
});
|
|
@@ -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"]),
|
package/src/core/programState.js
CHANGED
|
@@ -34,12 +34,16 @@ export function ownChildPrograms(children, ProgramClass) {
|
|
|
34
34
|
// domain's dependency validation and before the primitive deletion.
|
|
35
35
|
export function releaseNamedResourceOwnership(program, { kind, id, dataOwner }) {
|
|
36
36
|
if (kind !== "data") return program;
|
|
37
|
-
|
|
37
|
+
let materializationConfigs = dataOwner === undefined
|
|
38
38
|
? program.materializationConfigs
|
|
39
39
|
: removeMaterializationConfig(
|
|
40
40
|
program.materializationConfigs,
|
|
41
41
|
["data", dataOwner.family, dataOwner.owner]
|
|
42
42
|
).value;
|
|
43
|
+
materializationConfigs = removeMaterializationConfig(
|
|
44
|
+
materializationConfigs,
|
|
45
|
+
["calculations", "datasets", dataOwner?.current ?? id]
|
|
46
|
+
).value;
|
|
43
47
|
const context = program.context.currentData === id
|
|
44
48
|
? freezeOwned({ ...program.context, currentData: undefined })
|
|
45
49
|
: program.context;
|
|
@@ -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(".");
|