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
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { freezeOwned, isPlainObject } from "../core/immutable.js";
|
|
2
|
-
import {
|
|
3
|
-
import { buildLinearPathCommands } from "./pathCommands.js";
|
|
2
|
+
import { buildLinearPathCommands, validatePathCommandCount } from "./pathCommands.js";
|
|
4
3
|
|
|
5
4
|
export const CURVE_INTERPOLATIONS = Object.freeze([
|
|
6
5
|
"linear",
|
|
@@ -42,6 +41,15 @@ function validatePoints(points) {
|
|
|
42
41
|
return points;
|
|
43
42
|
}
|
|
44
43
|
|
|
44
|
+
export function validateCardinalTension(tension, curve) {
|
|
45
|
+
if (tension === undefined) return 0;
|
|
46
|
+
if (curve !== "cardinal") throw new Error("Line tension requires cardinal interpolation.");
|
|
47
|
+
if (!Number.isFinite(tension) || tension < 0 || tension > 1) {
|
|
48
|
+
throw new RangeError("Cardinal tension must be between 0 and 1.");
|
|
49
|
+
}
|
|
50
|
+
return tension;
|
|
51
|
+
}
|
|
52
|
+
|
|
45
53
|
export function validateCurveInterpolation(curve) {
|
|
46
54
|
if (!CURVE_INTERPOLATIONS.includes(curve)) {
|
|
47
55
|
throw new Error(`Unsupported curve interpolation "${curve}".`);
|
|
@@ -134,7 +142,7 @@ function buildBasisCommands(points) {
|
|
|
134
142
|
return ownCommands(commands);
|
|
135
143
|
}
|
|
136
144
|
|
|
137
|
-
function buildCardinalCommands(points) {
|
|
145
|
+
function buildCardinalCommands(points, tension = 0) {
|
|
138
146
|
const commands = [{ op: "M", x: points[0].x, y: points[0].y }];
|
|
139
147
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
140
148
|
const before = points[index - 1] ?? points[index];
|
|
@@ -143,10 +151,10 @@ function buildCardinalCommands(points) {
|
|
|
143
151
|
const after = points[index + 2] ?? next;
|
|
144
152
|
commands.push({
|
|
145
153
|
op: "C",
|
|
146
|
-
x1: current.x + (next.x - before.x) / 6,
|
|
147
|
-
y1: current.y + (next.y - before.y) / 6,
|
|
148
|
-
x2: next.x - (after.x - current.x) / 6,
|
|
149
|
-
y2: next.y - (after.y - current.y) / 6,
|
|
154
|
+
x1: current.x + (next.x - before.x) / 6 * (1 - tension),
|
|
155
|
+
y1: current.y + (next.y - before.y) / 6 * (1 - tension),
|
|
156
|
+
x2: next.x - (after.x - current.x) / 6 * (1 - tension),
|
|
157
|
+
y2: next.y - (after.y - current.y) / 6 * (1 - tension),
|
|
150
158
|
x: next.x,
|
|
151
159
|
y: next.y
|
|
152
160
|
});
|
|
@@ -265,7 +273,7 @@ function buildNaturalCommands(points) {
|
|
|
265
273
|
return ownCommands(commands);
|
|
266
274
|
}
|
|
267
275
|
|
|
268
|
-
function buildValidatedCurvePathCommands(points, curve) {
|
|
276
|
+
function buildValidatedCurvePathCommands(points, curve, tension) {
|
|
269
277
|
if (curve === "linear" ||
|
|
270
278
|
(SMOOTH_CURVES.has(curve) &&
|
|
271
279
|
curve !== "monotone" &&
|
|
@@ -277,7 +285,7 @@ function buildValidatedCurvePathCommands(points, curve) {
|
|
|
277
285
|
}
|
|
278
286
|
if (curve === "basis") return buildBasisCommands(points);
|
|
279
287
|
if (curve === "cardinal") {
|
|
280
|
-
return buildCardinalCommands(points);
|
|
288
|
+
return buildCardinalCommands(points, tension);
|
|
281
289
|
}
|
|
282
290
|
if (curve === "monotone") {
|
|
283
291
|
return buildMonotoneCommands(points);
|
|
@@ -301,17 +309,19 @@ function curveCommandCount(pointCount, curve) {
|
|
|
301
309
|
: pointCount;
|
|
302
310
|
}
|
|
303
311
|
|
|
304
|
-
export function buildCurvePathCommands(points, curve = "linear") {
|
|
312
|
+
export function buildCurvePathCommands(points, curve = "linear", tension) {
|
|
313
|
+
validateCardinalTension(tension, curve);
|
|
305
314
|
const validatedPoints = validatePoints(points);
|
|
306
315
|
const validatedCurve = validateCurveInterpolation(curve);
|
|
307
|
-
|
|
316
|
+
validatePathCommandCount(
|
|
308
317
|
curveCommandCount(validatedPoints.length, validatedCurve),
|
|
309
318
|
"Curve path command count"
|
|
310
319
|
);
|
|
311
320
|
const direct = buildValidatedCurvePathCommands(
|
|
312
321
|
validatedPoints,
|
|
313
|
-
validatedCurve
|
|
322
|
+
validatedCurve, tension
|
|
314
323
|
);
|
|
324
|
+
validatePathCommandCount(direct.length, "Curve path command count");
|
|
315
325
|
if (finiteCommands(direct)) return direct;
|
|
316
326
|
|
|
317
327
|
let xScale = 0;
|
|
@@ -327,7 +337,7 @@ export function buildCurvePathCommands(points, curve = "linear") {
|
|
|
327
337
|
x: point.x / xScale,
|
|
328
338
|
y: point.y / yScale
|
|
329
339
|
})),
|
|
330
|
-
validatedCurve
|
|
340
|
+
validatedCurve, tension
|
|
331
341
|
);
|
|
332
342
|
const restored = normalized.map(command => {
|
|
333
343
|
const copy = {};
|
|
@@ -341,6 +351,7 @@ export function buildCurvePathCommands(points, curve = "linear") {
|
|
|
341
351
|
if (!finiteCommands(restored)) {
|
|
342
352
|
throw new RangeError("Curve interpolation exceeds the finite numeric range.");
|
|
343
353
|
}
|
|
354
|
+
validatePathCommandCount(restored.length, "Curve path command count");
|
|
344
355
|
return ownCommands(restored);
|
|
345
356
|
}
|
|
346
357
|
|
|
@@ -403,7 +414,7 @@ export function buildAreaCurvePathCommands(
|
|
|
403
414
|
const validatedCurve = validateCurveInterpolation(curve);
|
|
404
415
|
const validatedLower = validatePoints(lowerPoints);
|
|
405
416
|
const validatedUpper = validatePoints(upperPoints);
|
|
406
|
-
|
|
417
|
+
validatePathCommandCount(
|
|
407
418
|
curveCommandCount(validatedLower.length, validatedCurve) +
|
|
408
419
|
curveCommandCount(validatedUpper.length, validatedCurve) + 1,
|
|
409
420
|
"Area path command count"
|
|
@@ -416,10 +427,12 @@ export function buildAreaCurvePathCommands(
|
|
|
416
427
|
const upper = reverseOpenPathCommands(
|
|
417
428
|
buildOrientedCurvePathCommands(validatedUpper, validatedCurve, independentAxis)
|
|
418
429
|
);
|
|
419
|
-
|
|
430
|
+
const commands = [
|
|
420
431
|
...lower,
|
|
421
432
|
{ op: "L", x: upper[0].x, y: upper[0].y },
|
|
422
433
|
...upper.slice(1),
|
|
423
434
|
{ op: "Z" }
|
|
424
|
-
]
|
|
435
|
+
];
|
|
436
|
+
validatePathCommandCount(commands.length, "Area path command count");
|
|
437
|
+
return ownCommands(commands);
|
|
425
438
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requireStringValue as requireName } from "../core/validation.js";
|
|
1
2
|
import { annotateError } from "../core/diagnostics.js";
|
|
2
3
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
3
4
|
|
|
@@ -19,12 +20,6 @@ function rejectUnknownKeys(value, keys, label) {
|
|
|
19
20
|
if (unknown !== undefined) throw new Error(`Unknown ${label} property "${unknown}".`);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
function requireName(value, label) {
|
|
23
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
24
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
25
|
-
}
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
28
23
|
|
|
29
24
|
function valueStorageType(value) {
|
|
30
25
|
if (typeof value === "number") return "number";
|
|
@@ -61,7 +56,8 @@ function normalizeField(value, index, { declared = false } = {}) {
|
|
|
61
56
|
declared ? SOURCE_FIELD_KEYS : FIELD_KEYS,
|
|
62
57
|
`dataset schema field ${index}`
|
|
63
58
|
);
|
|
64
|
-
|
|
59
|
+
// Stored row keys are lossless; action field references still require non-empty names.
|
|
60
|
+
const name = value.name === "" ? "" : requireName(value.name, `Dataset schema field ${index} name`);
|
|
65
61
|
const allowed = declared ? DECLARED_STORAGE_TYPES : STORAGE_TYPES;
|
|
66
62
|
if (!allowed.has(value.storageType)) {
|
|
67
63
|
throw new Error(`Unsupported dataset schema storageType "${value.storageType}".`);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isPlainObject } from "../core/immutable.js";
|
|
2
|
+
import { validateNonEmptyString } from "../core/validation.js";
|
|
2
3
|
|
|
3
4
|
const ENTRY_KEYS = Object.freeze(["value", "label"]);
|
|
4
5
|
|
|
@@ -55,3 +56,30 @@ export function resolveDisplayLabel(value, map, fallback) {
|
|
|
55
56
|
}
|
|
56
57
|
return resolved;
|
|
57
58
|
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
export function normalizeDisplayColorMap(value, label = "colorMap") {
|
|
62
|
+
if (!Array.isArray(value)) throw new TypeError(`${label} must be an array.`);
|
|
63
|
+
const normalized = [];
|
|
64
|
+
for (const [index, entry] of value.entries()) {
|
|
65
|
+
if (!isPlainObject(entry)) throw new TypeError(`${label}[${index}] must be a plain object.`);
|
|
66
|
+
if (Reflect.ownKeys(entry).length !== 2 ||
|
|
67
|
+
!Object.hasOwn(entry, "value") || !Object.hasOwn(entry, "color")) {
|
|
68
|
+
throw new Error(`${label}[${index}] must contain exactly value and color.`);
|
|
69
|
+
}
|
|
70
|
+
if (!isDisplayLabelValue(entry.value)) {
|
|
71
|
+
throw new TypeError(`${label}[${index}].value must be a string, boolean, null, or finite number.`);
|
|
72
|
+
}
|
|
73
|
+
validateNonEmptyString(entry.color, `${label}[${index}].color`);
|
|
74
|
+
if (normalized.some(previous => sameDisplayLabelValue(previous.value, entry.value))) {
|
|
75
|
+
throw new Error(`${label} contains a duplicate typed value.`);
|
|
76
|
+
}
|
|
77
|
+
normalized.push(Object.freeze({ value: entry.value, color: entry.color }));
|
|
78
|
+
}
|
|
79
|
+
return Object.freeze(normalized);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function resolveDisplayLabelColor(value, map, fallback) {
|
|
83
|
+
const entry = map?.find(entry => sameDisplayLabelValue(entry.value, value));
|
|
84
|
+
return entry?.color ?? fallback;
|
|
85
|
+
}
|
package/src/grammar/ecdf.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { requireStringValue as nonEmptyString } from "../core/validation.js";
|
|
2
|
+
import { isGroupValue as groupValue } from "./transformKeys.js";
|
|
1
3
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
4
|
import { stableDecimal } from "./numeric.js";
|
|
3
5
|
|
|
@@ -6,11 +8,6 @@ const TRANSFORM_KEYS = Object.freeze([
|
|
|
6
8
|
]);
|
|
7
9
|
const OUTPUT_KEYS = Object.freeze(["value", "cumulative", "probability"]);
|
|
8
10
|
|
|
9
|
-
function nonEmptyString(value, label) {
|
|
10
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
11
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
11
|
|
|
15
12
|
function normalizeGroupBy(groupBy) {
|
|
16
13
|
const fields = groupBy === undefined
|
|
@@ -101,10 +98,6 @@ export function normalizeECDFTransform({ field, groupBy, weight, missing = "drop
|
|
|
101
98
|
return cloneAndFreeze(transform);
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
function groupValue(value) {
|
|
105
|
-
return typeof value === "string" || typeof value === "boolean" ||
|
|
106
|
-
(typeof value === "number" && Number.isFinite(value));
|
|
107
|
-
}
|
|
108
101
|
|
|
109
102
|
function rejectOrDrop(transform, index, reason) {
|
|
110
103
|
if (transform.missing === "error") {
|
|
@@ -6,12 +6,13 @@ import { resolveRectMode } from "../rects.js";
|
|
|
6
6
|
import { resolveRuleMode } from "../rules.js";
|
|
7
7
|
|
|
8
8
|
const CARTESIAN_MARKS = new Set([
|
|
9
|
-
"point", "line", "area", "bar", "rule", "tick", "rect"
|
|
9
|
+
"point", "line", "area", "bar", "rule", "tick", "rect", "text"
|
|
10
10
|
]);
|
|
11
11
|
const FACET_BAR_GRAINS = new Set([
|
|
12
12
|
BAR_GRAINS.histogram,
|
|
13
13
|
BAR_GRAINS.aggregate,
|
|
14
|
-
BAR_GRAINS.ranged
|
|
14
|
+
BAR_GRAINS.ranged,
|
|
15
|
+
BAR_GRAINS.centered
|
|
15
16
|
]);
|
|
16
17
|
const ORDINARY_SCALE_CHANNELS = Object.freeze([
|
|
17
18
|
["x", "x"], ["y", "y"], ["xOffset", "xOffset"], ["yOffset", "yOffset"],
|
|
@@ -66,6 +67,9 @@ function classifyPrimaryLayer(semanticSpec, layer) {
|
|
|
66
67
|
`facet requires bar mark "${layer.id}" to be a complete histogram, aggregate, or ranged bar.`
|
|
67
68
|
);
|
|
68
69
|
}
|
|
70
|
+
if (type === "text" && layer.encoding?.text === undefined) {
|
|
71
|
+
throw new Error(`Facet text layer "${layer.id}" requires a text encoding.`);
|
|
72
|
+
}
|
|
69
73
|
const complete = (
|
|
70
74
|
layer.encoding?.x?.scale !== undefined &&
|
|
71
75
|
layer.encoding?.y?.scale !== undefined
|
|
@@ -165,6 +165,11 @@ function scaleIds(config) {
|
|
|
165
165
|
export function resolveSharedFacetLegends(children) {
|
|
166
166
|
const normalized = requireLegendChildren(children);
|
|
167
167
|
const first = normalized[0];
|
|
168
|
+
for (const child of normalized.slice(1)) {
|
|
169
|
+
if (!sameValue(first.guideConfigs.legendGroups, child.guideConfigs.legendGroups)) {
|
|
170
|
+
throw new Error(`Facet child "${child.id}" has incompatible legend group placement.`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
168
173
|
const kinds = Object.keys(first.legends);
|
|
169
174
|
const entries = kinds.map(kind => {
|
|
170
175
|
const family = LEGEND_FAMILIES[kind];
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
} from "../../core/validation.js";
|
|
7
7
|
import { planFacetDependencies, resolveFacetFamily } from "./dependencies.js";
|
|
8
8
|
import { readNominalField } from "../scales/index.js";
|
|
9
|
-
const MAX_FACET_CHILDREN = 100;
|
|
10
9
|
|
|
11
10
|
function requireFacetField(field, label = "facet") {
|
|
12
11
|
if (typeof field !== "string" || field.length === 0) {
|
|
@@ -100,8 +99,7 @@ export function resolveFacetDefinition(semanticSpec, options = {}) {
|
|
|
100
99
|
const values = resolveValues(observed, options.values);
|
|
101
100
|
validateGeneratedItemLimit(
|
|
102
101
|
values.length,
|
|
103
|
-
"Facet child count"
|
|
104
|
-
MAX_FACET_CHILDREN
|
|
102
|
+
"Facet child count"
|
|
105
103
|
);
|
|
106
104
|
validateWorkLimit(
|
|
107
105
|
dataset.values.length * values.length,
|
|
@@ -175,8 +173,7 @@ export function resolveFacetGridDefinition(semanticSpec, options = {}) {
|
|
|
175
173
|
}
|
|
176
174
|
validateGeneratedItemLimit(
|
|
177
175
|
selected.length,
|
|
178
|
-
"Facet grid child count"
|
|
179
|
-
MAX_FACET_CHILDREN
|
|
176
|
+
"Facet grid child count"
|
|
180
177
|
);
|
|
181
178
|
validateWorkLimit(
|
|
182
179
|
dataset.values.length * selected.length,
|
package/src/grammar/fold.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { rejectUnknownProperties as rejectUnknownKeys } from "../core/validation.js";
|
|
2
|
+
import { requireStringValue as requireField } from "../core/validation.js";
|
|
1
3
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
4
|
|
|
3
5
|
const TRANSFORM_KEYS = Object.freeze(["type", "fields", "as"]);
|
|
@@ -5,19 +7,7 @@ const AS_KEYS = Object.freeze(["key", "value"]);
|
|
|
5
7
|
const MAX_FIELDS = 64;
|
|
6
8
|
const MAX_OUTPUT_ROWS = 10_000;
|
|
7
9
|
|
|
8
|
-
function rejectUnknownKeys(value, supported, label) {
|
|
9
|
-
const unknown = Object.keys(value).find(key => !supported.includes(key));
|
|
10
|
-
if (unknown !== undefined) {
|
|
11
|
-
throw new Error(`Unknown ${label} property "${unknown}".`);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
10
|
|
|
15
|
-
function requireField(value, label) {
|
|
16
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
17
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
18
|
-
}
|
|
19
|
-
return value;
|
|
20
|
-
}
|
|
21
11
|
|
|
22
12
|
function normalizeAs(value) {
|
|
23
13
|
const requested = value ?? {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requireStringValue as requireField } from "../core/validation.js";
|
|
1
2
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
3
|
import {
|
|
3
4
|
deriveKernelDensity,
|
|
@@ -23,12 +24,6 @@ const TRANSFORM_KEYS = [
|
|
|
23
24
|
];
|
|
24
25
|
const OUTPUT_KEYS = Object.keys(GRADIENT_PROFILE_FIELDS);
|
|
25
26
|
|
|
26
|
-
function requireField(value, label) {
|
|
27
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
28
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
29
|
-
}
|
|
30
|
-
return value;
|
|
31
|
-
}
|
|
32
27
|
|
|
33
28
|
function validatePair(value, label) {
|
|
34
29
|
if (
|
package/src/grammar/impute.js
CHANGED
|
@@ -1,26 +1,16 @@
|
|
|
1
|
+
import { normalizeSortBy, validateSortBy } from "./transformKeys.js";
|
|
2
|
+
import { rejectUnknownProperties as rejectUnknownKeys } from "../core/validation.js";
|
|
3
|
+
import { requireStringValue as requireField } from "../core/validation.js";
|
|
1
4
|
import { IMPUTE_REQUIRED_OPTIONS } from "../core/optionRequirements.js";
|
|
2
5
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
3
|
-
import { interpolateNumber, inverseLerp } from "./numeric.js";
|
|
6
|
+
import { interpolateNumber, inverseLerp, stableFiniteMean } from "./numeric.js";
|
|
4
7
|
import { normalizeTemporalValue } from "./scales/fields.js";
|
|
5
8
|
|
|
6
9
|
const TRANSFORM_KEYS = Object.freeze([
|
|
7
10
|
"type", "fields", "groupBy", "sortBy", "method", "value", "edges", "maxGap"
|
|
8
11
|
]);
|
|
9
|
-
const SORT_KEYS = Object.freeze(["field", "order"]);
|
|
10
12
|
const METHODS = Object.freeze(Object.keys(IMPUTE_REQUIRED_OPTIONS));
|
|
11
13
|
|
|
12
|
-
function rejectUnknownKeys(value, supported, label) {
|
|
13
|
-
const unknown = Object.keys(value).find(key => !supported.includes(key));
|
|
14
|
-
if (unknown !== undefined) throw new Error(`Unknown ${label} property "${unknown}".`);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function requireField(value, label) {
|
|
18
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
19
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
20
|
-
}
|
|
21
|
-
return value;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
14
|
function validateFieldList(value, label) {
|
|
25
15
|
if (!Array.isArray(value) || value.length === 0 || value.some(field =>
|
|
26
16
|
typeof field !== "string" || field.length === 0
|
|
@@ -35,33 +25,6 @@ function normalizeFieldList(value, { allowEmpty = false } = {}) {
|
|
|
35
25
|
return Array.isArray(value) ? [...value] : [value];
|
|
36
26
|
}
|
|
37
27
|
|
|
38
|
-
function normalizeSortBy(value) {
|
|
39
|
-
if (value === undefined) return [];
|
|
40
|
-
if (!Array.isArray(value)) return value;
|
|
41
|
-
return value.map(sort => isPlainObject(sort)
|
|
42
|
-
? { ...sort, order: sort.order ?? "ascending" }
|
|
43
|
-
: sort
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function validateSortBy(sortBy) {
|
|
48
|
-
if (!Array.isArray(sortBy)) throw new TypeError("Impute sortBy must be an array.");
|
|
49
|
-
const fields = [];
|
|
50
|
-
sortBy.forEach((sort, index) => {
|
|
51
|
-
if (!isPlainObject(sort)) {
|
|
52
|
-
throw new TypeError(`Impute sortBy[${index}] must be a plain object.`);
|
|
53
|
-
}
|
|
54
|
-
rejectUnknownKeys(sort, SORT_KEYS, `impute sortBy[${index}]`);
|
|
55
|
-
fields.push(requireField(sort.field, `Impute sortBy[${index}].field`));
|
|
56
|
-
if (!["ascending", "descending"].includes(sort.order)) {
|
|
57
|
-
throw new Error(`Unsupported impute sort order "${sort.order}".`);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
if (new Set(fields).size !== fields.length) {
|
|
61
|
-
throw new Error("Impute sortBy fields must be unique.");
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
28
|
function scalarType(value, label) {
|
|
66
29
|
if (value === null || value === undefined) return undefined;
|
|
67
30
|
if (typeof value === "string" || typeof value === "boolean") return typeof value;
|
|
@@ -94,7 +57,7 @@ export function validateImputeTransform(transform) {
|
|
|
94
57
|
if (new Set(transform.groupBy).size !== transform.groupBy.length) {
|
|
95
58
|
throw new Error("Impute groupBy fields must be unique.");
|
|
96
59
|
}
|
|
97
|
-
validateSortBy(transform.sortBy);
|
|
60
|
+
validateSortBy(transform.sortBy, "Impute");
|
|
98
61
|
if (!METHODS.includes(transform.method)) {
|
|
99
62
|
throw new Error(`Unsupported impute method "${transform.method}".`);
|
|
100
63
|
}
|
|
@@ -107,7 +70,7 @@ export function validateImputeTransform(transform) {
|
|
|
107
70
|
scalarType(transform.value, "Impute constant value");
|
|
108
71
|
} else {
|
|
109
72
|
if (hasValue) throw new Error(`Impute value is not available for ${transform.method}.`);
|
|
110
|
-
if (transform.sortBy.length === 0) {
|
|
73
|
+
if (transform.method !== "mean" && transform.sortBy.length === 0) {
|
|
111
74
|
throw new Error(`Impute ${transform.method} requires non-empty sortBy.`);
|
|
112
75
|
}
|
|
113
76
|
}
|
|
@@ -132,7 +95,7 @@ export function normalizeImputeTransform(args = {}) {
|
|
|
132
95
|
fields: normalizeFieldList(args.fields),
|
|
133
96
|
groupBy: normalizeFieldList(args.groupBy, { allowEmpty: true }),
|
|
134
97
|
sortBy: normalizeSortBy(args.sortBy),
|
|
135
|
-
method: args.method,
|
|
98
|
+
method: args.method ?? "mean",
|
|
136
99
|
edges: args.edges ?? "keep"
|
|
137
100
|
};
|
|
138
101
|
if (Object.hasOwn(args, "value")) transform.value = args.value;
|
|
@@ -212,9 +175,9 @@ function validateFieldTypes(entries, transform) {
|
|
|
212
175
|
if (types.size > 1) {
|
|
213
176
|
throw new TypeError(`Impute field "${field}" must contain one scalar type.`);
|
|
214
177
|
}
|
|
215
|
-
if (transform.method
|
|
178
|
+
if (["linear", "mean"].includes(transform.method) &&
|
|
216
179
|
(types.size > 0 && !types.has("number"))) {
|
|
217
|
-
throw new TypeError(`Impute
|
|
180
|
+
throw new TypeError(`Impute ${transform.method} field "${field}" must contain finite numbers.`);
|
|
218
181
|
}
|
|
219
182
|
}
|
|
220
183
|
}
|
|
@@ -278,10 +241,18 @@ function setField(row, field, value) {
|
|
|
278
241
|
});
|
|
279
242
|
}
|
|
280
243
|
|
|
281
|
-
function fillRun(partition, field, run, transform, positions) {
|
|
244
|
+
function fillRun(partition, field, run, transform, positions, mean) {
|
|
282
245
|
const [start, end] = run;
|
|
283
246
|
const length = end - start + 1;
|
|
284
247
|
if (transform.maxGap !== undefined && length > transform.maxGap) return;
|
|
248
|
+
if (transform.method === "mean") {
|
|
249
|
+
if (mean === undefined) {
|
|
250
|
+
if (transform.edges === "error") throw new RangeError(`Impute mean requires an observed value in field "${field}".`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
for (let index = start; index <= end; index += 1) setField(partition[index].row, field, mean);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
285
256
|
if (transform.method === "constant") {
|
|
286
257
|
for (let index = start; index <= end; index += 1) {
|
|
287
258
|
setField(partition[index].row, field, transform.value);
|
|
@@ -332,8 +303,10 @@ export function deriveImputedRows(rows, transform) {
|
|
|
332
303
|
partition.sort((left, right) => compareEntries(left, right, transform.sortBy));
|
|
333
304
|
const positions = linearPositions(partition, transform);
|
|
334
305
|
for (const field of transform.fields) {
|
|
306
|
+
const observed = transform.method === "mean" ? partition.map(entry => entry.row[field]).filter(value => value !== null && value !== undefined) : [];
|
|
307
|
+
const mean = observed.length ? stableFiniteMean(observed, "Impute group mean") : undefined;
|
|
335
308
|
for (const run of missingRuns(partition, field)) {
|
|
336
|
-
fillRun(partition, field, run, transform, positions);
|
|
309
|
+
fillRun(partition, field, run, transform, positions, mean);
|
|
337
310
|
}
|
|
338
311
|
}
|
|
339
312
|
}
|
package/src/grammar/interval.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { requireStringValue as nonEmptyString } from "../core/validation.js";
|
|
2
|
+
import { isGroupValue } from "./transformKeys.js";
|
|
1
3
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
4
|
import { aggregateScalarValues } from "./aggregate.js";
|
|
3
5
|
import { requireFiniteResult, stableDecimal } from "./numeric.js";
|
|
@@ -21,11 +23,6 @@ const TRANSFORM_KEYS = [
|
|
|
21
23
|
];
|
|
22
24
|
const OUTPUT_KEYS = ["center", "lower", "upper"];
|
|
23
25
|
|
|
24
|
-
function nonEmptyString(value, label) {
|
|
25
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
26
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
26
|
|
|
30
27
|
function validateGrouping(groupBy) {
|
|
31
28
|
if (
|
|
@@ -166,10 +163,6 @@ function isMissing(value) {
|
|
|
166
163
|
(typeof value === "number" && Number.isNaN(value));
|
|
167
164
|
}
|
|
168
165
|
|
|
169
|
-
function isGroupValue(value) {
|
|
170
|
-
return typeof value === "string" || typeof value === "boolean" ||
|
|
171
|
-
(typeof value === "number" && Number.isFinite(value));
|
|
172
|
-
}
|
|
173
166
|
|
|
174
167
|
function deriveGroup(values, transform) {
|
|
175
168
|
if (transform.center === "median") {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { validateOptionObject, validatePositiveFinite, validateUnitInterval } from "../core/validation.js";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_LEGEND_GRADIENT = Object.freeze({ length: 120, thickness: 12 });
|
|
4
|
+
const OPTIONS = Object.freeze(["length", "thickness", "opacity"]);
|
|
5
|
+
|
|
6
|
+
export function validateLegendGradient(value, label = "Legend gradient") {
|
|
7
|
+
validateOptionObject(value, OPTIONS, label);
|
|
8
|
+
validatePositiveFinite(value.length, `${label} length`);
|
|
9
|
+
validatePositiveFinite(value.thickness, `${label} thickness`);
|
|
10
|
+
if (Object.hasOwn(value, "opacity")) validateUnitInterval(value.opacity, `${label} opacity`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function normalizeLegendGradient(patch, previous = DEFAULT_LEGEND_GRADIENT) {
|
|
14
|
+
if (patch !== undefined) validateOptionObject(patch, OPTIONS, "Legend gradient");
|
|
15
|
+
const value = { ...previous, ...patch };
|
|
16
|
+
validateLegendGradient(value);
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{validateOptionObject as r,validateNonNegativeFinite as i}from"../core/validation.js";import{validateUserId as a}from"../core/identifiers.js";import{PLOT_LEGEND_POSITIONS as g,LEGEND_POSITIONS as d,LEGEND_BLOCK_LAYOUTS as f}from"../core/vocabulary.js";function s(n){r(n,void 0,"Legend groups");for(const[o,t]of Object.entries(n)){if(a(o,"Legend group target"),r(t,["placement","blockLayout","inferredOffset"],"Legend group"),!f.includes(t.blockLayout))throw new Error("Legend group blockLayout must be row, column or wrap.");if(t.inferredOffset!==void 0&&typeof t.inferredOffset!="boolean")throw new TypeError("Legend group inferredOffset must be boolean.");const e=t.placement;if(r(e,["at","position","align","offset"],"Legend group placement"),Object.hasOwn(e,"at")){if(Object.keys(e).length!==1)throw new Error("Legend group at cannot coexist with an edge anchor.");if(r(e.at,["x","y"],"Legend group at"),!Number.isFinite(e.at.x)||!Number.isFinite(e.at.y))throw new Error("Legend group at requires finite x and y.")}else{if(Object.keys(e).length!==3||!d.includes(e.position))throw new Error("Legend group requires a complete edge placement.");if(!["left","center","right"].includes(e.align))throw new Error("Unsupported legend group alignment.");if((g.includes(e.position)||["left","right"].includes(e.position))&&e.align!=="center")throw new Error("Side and in-plot legend groups require center alignment.");i(e.offset,"Legend group offset")}}return n}export{s as validateLegendGroupState};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { validateOptionObject } from "../core/validation.js";
|
|
2
|
+
|
|
3
|
+
export const LEGEND_TITLE_STYLE_KEYS = Object.freeze([
|
|
4
|
+
"color", "fontSize", "fontFamily", "fontWeight", "align"
|
|
5
|
+
]);
|
|
6
|
+
|
|
7
|
+
export function validateLegendTitleAlignment(style, titlePosition = "top") {
|
|
8
|
+
if (style?.align === undefined) {
|
|
9
|
+
if (style !== undefined && Object.hasOwn(style, "align")) throw new TypeError("Legend title align requires left, center or right.");
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (!["left", "center", "right"].includes(style.align)) throw new Error("Legend title align requires left, center or right.");
|
|
13
|
+
if (titlePosition === "left") throw new Error("Inline legend titles do not accept title alignment.");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function mergeLegendTitleStyle(previous, patch, { edit = false } = {}) {
|
|
17
|
+
if (patch !== undefined) validateOptionObject(patch, LEGEND_TITLE_STYLE_KEYS, "Legend titleStyle");
|
|
18
|
+
const result = { ...previous, ...patch };
|
|
19
|
+
if (edit && patch?.align === "auto") delete result.align;
|
|
20
|
+
validateLegendTitleAlignment(result);
|
|
21
|
+
return result;
|
|
22
|
+
}
|