ggaction 0.0.13 → 0.0.16
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 +146 -0
- package/README.md +41 -12
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-packet.schema.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +39 -7
- package/src/BasicChartProgram.js +3 -1
- package/src/ChartProgram.js +2 -0
- package/src/accessibility.js +157 -0
- package/src/actions/basic.js +2 -2
- package/src/actions/boxPlots/components.js +20 -12
- package/src/actions/boxPlots/create.js +4 -5
- package/src/actions/boxPlots/edit.js +34 -9
- package/src/actions/boxPlots/materialize.js +12 -4
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/canvas/actions.js +3 -3
- package/src/actions/canvas/fitting.js +1 -1
- package/src/actions/categoryOrder/index.js +21 -12
- package/src/actions/charts/area.js +6 -2
- package/src/actions/charts/bar.js +3 -2
- package/src/actions/charts/beeswarm.js +1 -1
- package/src/actions/charts/density.js +7 -3
- package/src/actions/charts/ecdf.js +6 -3
- package/src/actions/charts/endpoints.js +29 -7
- package/src/actions/charts/heatmap.js +3 -2
- package/src/actions/charts/histogram.js +5 -4
- package/src/actions/charts/horizon.js +4 -2
- package/src/actions/charts/interval-regression.js +7 -4
- package/src/actions/charts/line.js +4 -2
- package/src/actions/charts/parallel.js +9 -3
- package/src/actions/charts/pie.js +11 -3
- package/src/actions/charts/polar.js +17 -6
- package/src/actions/charts/radar.js +9 -3
- package/src/actions/charts/radial.js +12 -4
- package/src/actions/charts/raincloud.js +2 -2
- package/src/actions/charts/rug-strip.js +8 -4
- package/src/actions/charts/scatter.js +4 -2
- package/src/actions/composition/actions.js +29 -24
- package/src/actions/coordinates/actions.js +2 -2
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/basicBin2d.js +4 -5
- package/src/actions/data/bin.js +3 -3
- package/src/actions/data/bin2d.js +63 -193
- package/src/actions/data/bin2dMaterialize.js +4 -5
- package/src/actions/data/box.js +4 -4
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +4 -4
- package/src/actions/data/create.js +7 -14
- package/src/actions/data/density.js +15 -23
- package/src/actions/data/derived.js +57 -31
- package/src/actions/data/ecdf.js +2 -2
- package/src/actions/data/edit.js +551 -0
- package/src/actions/data/filter.js +28 -22
- package/src/actions/data/fold.js +2 -2
- package/src/actions/data/gradientProfile.js +2 -2
- package/src/actions/data/horizon.js +2 -2
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +62 -47
- package/src/actions/data/interval.js +2 -2
- package/src/actions/data/intervalEdit.js +26 -18
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +21 -27
- package/src/actions/data/revise.js +48 -0
- package/src/actions/data/revisionBindings.js +125 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/stack.js +2 -2
- package/src/actions/data/summary.js +3 -3
- package/src/actions/data/timeUnit.js +6 -3
- package/src/actions/data/window.js +3 -3
- package/src/actions/encodings/angle.js +1 -1
- package/src/actions/encodings/appearance.js +4 -4
- package/src/actions/encodings/barWidth.js +1 -1
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/index.js +3 -2
- package/src/actions/encodings/density.js +7 -4
- package/src/actions/encodings/histogram.js +4 -3
- package/src/actions/encodings/horizon.js +6 -8
- package/src/actions/encodings/index.js +4 -0
- package/src/actions/encodings/offset.js +22 -15
- package/src/actions/encodings/parallel.js +4 -5
- package/src/actions/encodings/pathOrder.js +6 -11
- package/src/actions/encodings/position/apply.js +53 -7
- package/src/actions/encodings/position/index.js +14 -6
- package/src/actions/encodings/position/policies/bar.js +9 -0
- package/src/actions/encodings/position/resolve.js +24 -7
- package/src/actions/encodings/ranged.js +5 -5
- package/src/actions/encodings/remove.js +13 -9
- package/src/actions/encodings/ruleAppearance.js +2 -24
- package/src/actions/encodings/seriesLayout.js +2 -1
- package/src/actions/encodings/shared.js +82 -14
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/encodings/strokeDash.js +2 -2
- package/src/actions/encodings/text.js +1 -1
- package/src/actions/errorBands/create.js +34 -20
- package/src/actions/errorBands/edit.js +34 -137
- package/src/actions/errorBars/create.js +43 -20
- package/src/actions/errorBars/edit.js +29 -125
- package/src/actions/facets/actions.js +267 -73
- package/src/actions/facets/derive.js +99 -28
- package/src/actions/facets/guides.js +4 -5
- package/src/actions/facets/replay.js +4 -5
- package/src/actions/gradientPlots/components.js +15 -8
- package/src/actions/gradientPlots/create.js +4 -5
- package/src/actions/gradientPlots/edit.js +18 -9
- package/src/actions/gradientPlots/materialize.js +5 -3
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/gradientPlots/rebind.js +4 -5
- package/src/actions/guides/applicability.js +8 -2
- package/src/actions/guides/axes/axes.js +23 -4
- package/src/actions/guides/axes/axis.js +2 -2
- package/src/actions/guides/axes/edit.js +2 -2
- package/src/actions/guides/axes/labels.js +67 -21
- package/src/actions/guides/axes/lines.js +15 -9
- package/src/actions/guides/axes/parallel/lifecycle.js +13 -13
- package/src/actions/guides/axes/parallel/resolve.js +6 -2
- package/src/actions/guides/axes/parallel.js +5 -5
- package/src/actions/guides/axes/remove.js +4 -4
- package/src/actions/guides/axes/tickGroups.js +6 -5
- package/src/actions/guides/axes/ticks.js +19 -6
- package/src/actions/guides/axes/titles.js +21 -13
- package/src/actions/guides/grids/grid.js +18 -29
- package/src/actions/guides/grids/resolve.js +7 -2
- package/src/actions/guides/guides.js +20 -4
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +138 -52
- package/src/actions/guides/legends/categorical/components.js +55 -45
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +98 -16
- package/src/actions/guides/legends/categorical/options.js +2 -2
- package/src/actions/guides/legends/categorical/recipes.js +111 -20
- package/src/actions/guides/legends/categorical/resolve.js +12 -8
- package/src/actions/guides/legends/categorical/symbols.js +104 -52
- package/src/actions/guides/legends/continuous/common.js +76 -20
- package/src/actions/guides/legends/continuous/gradient.js +95 -158
- package/src/actions/guides/legends/continuous/index.js +2 -0
- package/src/actions/guides/legends/continuous/interval.js +21 -16
- package/src/actions/guides/legends/continuous/opacity.js +37 -83
- package/src/actions/guides/legends/continuous/stroke.js +225 -0
- package/src/actions/guides/legends/creation.js +14 -1
- package/src/actions/guides/legends/edit.js +159 -60
- package/src/actions/guides/legends/focused.js +5 -5
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +59 -24
- package/src/actions/guides/legends/lifecycle.js +19 -8
- package/src/actions/guides/legends/remove.js +17 -8
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +100 -108
- package/src/actions/guides/legends/strokeWidth.js +38 -92
- package/src/actions/guides/legends/target.js +112 -1
- package/src/actions/guides/legends/transition.js +165 -16
- package/src/actions/guides/polar/axes/facade.js +8 -8
- package/src/actions/guides/polar/axes/labels.js +29 -10
- package/src/actions/guides/polar/axes/lines.js +19 -19
- package/src/actions/guides/polar/axes/shared.js +16 -0
- package/src/actions/guides/polar/axes/ticks.js +10 -16
- package/src/actions/guides/polar/axes/titles.js +6 -6
- package/src/actions/guides/polar/grids.js +12 -14
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/index.js +16 -3
- package/src/actions/intervals/revision.js +126 -0
- package/src/actions/marks/arc/actions.js +80 -22
- package/src/actions/marks/area/actions.js +38 -13
- package/src/actions/marks/area/materialize.js +17 -1
- package/src/actions/marks/bar/create.js +12 -3
- package/src/actions/marks/bar/edit.js +21 -4
- package/src/actions/marks/bar/materialize.js +34 -40
- package/src/actions/marks/index.js +10 -1
- package/src/actions/marks/line/actions.js +40 -17
- package/src/actions/marks/line/materialize.js +29 -10
- package/src/actions/marks/point/create.js +9 -3
- package/src/actions/marks/point/edit.js +19 -7
- package/src/actions/marks/point/jitter.js +6 -8
- package/src/actions/marks/point/materialize.js +48 -22
- package/src/actions/marks/point/packing.js +6 -8
- package/src/actions/marks/rect/actions.js +36 -15
- package/src/actions/marks/references.js +602 -33
- package/src/actions/marks/remove.js +67 -41
- package/src/actions/marks/rule/actions.js +51 -14
- package/src/actions/marks/shared.js +1 -1
- package/src/actions/marks/text/actions.js +79 -14
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +157 -17
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +72 -0
- package/src/actions/marks/tick/actions.js +25 -8
- package/src/actions/primitives/createGraphics.js +5 -4
- package/src/actions/primitives/editGraphics.js +31 -4
- package/src/actions/primitives/graphicProperties.js +6 -0
- package/src/actions/primitives/semantic.js +11 -3
- package/src/actions/primitives/semanticAction.js +90 -14
- package/src/actions/primitives/semanticValidation/dataset.js +2 -4
- package/src/actions/primitives/semanticValidation/guide.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +5 -0
- package/src/actions/primitives/semanticValidation/scale.js +28 -7
- package/src/actions/regression/components.js +36 -25
- package/src/actions/regression/create.js +13 -6
- package/src/actions/regression/edit.js +24 -11
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +173 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +1 -1
- package/src/actions/scales/consumers/families.js +3 -2
- package/src/actions/scales/consumers/index.js +19 -2
- package/src/actions/scales/consumers/seriesLayout.js +28 -14
- package/src/actions/scales/create.js +55 -23
- package/src/actions/scales/definitions.js +94 -40
- package/src/actions/scales/edit.js +67 -12
- package/src/actions/scales/editPolicy.js +81 -11
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +5 -4
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/preview.js +111 -10
- package/src/actions/scales/quantitativeColor.js +4 -5
- package/src/actions/selection/actions.js +179 -66
- package/src/actions/selection/index.js +3 -0
- package/src/actions/textMetrics/index.js +56 -0
- package/src/actions/theme/actions.js +70 -15
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +9 -2
- package/src/actions/theme/reconcile.js +313 -121
- package/src/actions/theme/state.js +160 -0
- package/src/actions/titles/actions.js +8 -8
- package/src/actions/titles/resolve.js +3 -3
- package/src/actions/violinPlots/create.js +6 -3
- package/src/actions/violinPlots/edit.js +2 -1
- package/src/core/action.js +117 -35
- package/src/core/compositionState.js +6 -1
- package/src/core/diagnostics.js +16 -0
- package/src/core/extensionRegistry.js +27 -0
- package/src/core/font.js +18 -0
- package/src/core/identifiers.js +5 -4
- package/src/core/immutable.js +10 -3
- package/src/core/optionRequirements.js +8 -0
- package/src/core/programState.js +21 -1
- package/src/core/resourceReferences.js +341 -0
- package/src/core/textMetricProfile.js +28 -0
- package/src/core/textMetrics.js +24 -3
- package/src/core/validation.js +22 -10
- package/src/core/vocabulary.js +16 -3
- package/src/diagnostics.js +1 -0
- package/src/grammar/bars/aggregate.js +4 -1
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bin.js +54 -9
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +265 -64
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +7 -47
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/impute.js +341 -0
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +18 -1
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/pathSeries.js +26 -8
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/regression/index.js +1 -0
- package/src/grammar/regression/parameters.js +23 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/colorConsumers.js +20 -1
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +27 -4
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/scales/types.js +8 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +190 -41
- package/src/grammar/schemas/semanticPath.js +7 -2
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +78 -16
- package/src/grammar/timeUnit.js +216 -5
- package/src/grammar/transformTopology.js +4 -0
- package/src/grammar/transforms.js +394 -28
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +221 -16
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +59 -5
- package/src/layout/labels.js +351 -7
- package/src/layout/legend.js +4 -4
- package/src/layout/legendItems.js +6 -6
- package/src/layout/text.js +10 -9
- package/src/layout/title.js +9 -9
- package/src/materialization/bars/aggregate.js +25 -2
- package/src/materialization/bars/histogram.js +84 -24
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/composition.js +3 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +36 -0
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +9 -3
- package/src/materialization/facetGuides/preparation.js +13 -8
- package/src/materialization/facetHeaders.js +347 -0
- package/src/materialization/facets.js +15 -65
- package/src/materialization/guides/layout.js +3 -3
- package/src/materialization/guides/resources.js +26 -0
- package/src/materialization/legends.js +1 -1
- package/src/materialization/marks/index.js +11 -5
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/rect.js +25 -12
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/scaleGuideDependencies.js +10 -0
- package/src/materialization/scales/map.js +5 -3
- package/src/materialization/scales/policies/binnedPosition.js +2 -2
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +4 -1
- package/src/materialization/scales/resolve.js +22 -6
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +30 -3
- package/src/materialization/selection/items/common.js +20 -5
- package/src/materialization/selection/items/rect.js +28 -4
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +265 -14
- package/src/materialization/typography.js +31 -0
- package/src/mcp/cli.js +5 -3
- package/src/persistence/codec.js +65 -0
- package/src/persistence/validation.js +185 -0
- package/src/persistence.js +74 -0
- package/src/renderers/canvas/circle.js +2 -3
- package/src/renderers/canvas/index.js +5 -4
- package/src/renderers/canvas/line.js +2 -3
- package/src/renderers/canvas/native.js +4 -4
- package/src/renderers/canvas/path.js +2 -3
- package/src/renderers/canvas/rect.js +2 -3
- package/src/renderers/canvas/stroke.js +11 -0
- package/src/renderers/nativeBackend.js +10 -0
- package/src/renderers/options.js +13 -0
- package/src/renderers/pdf.js +25 -54
- package/src/renderers/png.js +22 -17
- package/src/renderers/svg.js +29 -14
- package/src/renderers/text.js +1 -11
- package/src/selectors/coordinates.js +3 -1
- package/src/selectors/datasets.js +38 -5
- package/src/selectors/layers.js +10 -4
- package/src/selectors/markOwners.js +46 -0
- package/src/selectors/scales.js +6 -4
- package/src/theme/defaults.js +103 -1
- package/src/version.js +2 -0
- package/types/accessibility.d.ts +37 -0
- package/types/basic.d.ts +10 -2
- package/types/diagnostics.d.ts +15 -0
- package/types/index.d.ts +90 -0
- package/types/pdf.d.ts +9 -0
- package/types/persistence.d.ts +7 -0
- package/types/png.d.ts +9 -0
- package/types/program.d.ts +944 -118
package/src/grammar/computed.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
2
|
|
|
3
3
|
const TRANSFORM_KEYS = Object.freeze(["type", "as", "expression"]);
|
|
4
|
-
const
|
|
5
|
-
const
|
|
4
|
+
const ARITHMETIC_BINARY = new Set(["add", "subtract", "multiply", "divide"]);
|
|
5
|
+
const COMPARISONS = new Set(["eq", "neq", "lt", "lte", "gt", "gte"]);
|
|
6
|
+
const NUMERIC_UNARY = new Set(["negate", "absolute", "log", "sqrt"]);
|
|
7
|
+
const BOOLEAN_UNARY = new Set(["not", "isNull"]);
|
|
8
|
+
const LOGICAL = new Set(["and", "or"]);
|
|
9
|
+
const VARIADIC = new Set(["coalesce", "concat"]);
|
|
6
10
|
const MAX_DEPTH = 16;
|
|
7
11
|
const MAX_NODES = 128;
|
|
8
12
|
const MAX_WORK = 10_000_000;
|
|
@@ -21,10 +25,38 @@ function requireField(value, label) {
|
|
|
21
25
|
return value;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
function
|
|
28
|
+
function validateConstant(value) {
|
|
29
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
30
|
+
if (!Number.isFinite(value)) {
|
|
31
|
+
throw new TypeError(
|
|
32
|
+
"Computed expression constant must be a finite number, string, boolean, or null."
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function requireNode(expression, key, operation) {
|
|
38
|
+
if (!Object.hasOwn(expression, key)) {
|
|
39
|
+
const article = key === "operand" ? "an" : "a";
|
|
40
|
+
throw new TypeError(`Computed ${operation} requires ${article} ${key} expression.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validateOperands(expression, minimum) {
|
|
45
|
+
rejectUnknownKeys(expression, ["op", "operands"], "computed variadic expression");
|
|
46
|
+
if (!Array.isArray(expression.operands) || expression.operands.length < minimum) {
|
|
47
|
+
throw new TypeError(
|
|
48
|
+
`Computed ${expression.op} requires at least ${minimum} operand expressions.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function inspectExpression(expression, depth, state) {
|
|
25
54
|
if (!isPlainObject(expression)) {
|
|
26
55
|
throw new TypeError("Computed expression nodes must be plain objects.");
|
|
27
56
|
}
|
|
57
|
+
if (state.ancestors.has(expression)) {
|
|
58
|
+
throw new RangeError("Computed expression cannot contain cycles.");
|
|
59
|
+
}
|
|
28
60
|
state.nodes += 1;
|
|
29
61
|
if (state.nodes > MAX_NODES) {
|
|
30
62
|
throw new RangeError(`Computed expression cannot exceed ${MAX_NODES} nodes.`);
|
|
@@ -32,35 +64,76 @@ function validateExpression(expression, depth = 1, state = { nodes: 0 }) {
|
|
|
32
64
|
if (depth > MAX_DEPTH) {
|
|
33
65
|
throw new RangeError(`Computed expression cannot exceed depth ${MAX_DEPTH}.`);
|
|
34
66
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
67
|
+
state.ancestors.add(expression);
|
|
68
|
+
try {
|
|
69
|
+
const kinds = ["field", "constant", "op"].filter(key => Object.hasOwn(expression, key));
|
|
70
|
+
if (kinds.length !== 1) {
|
|
71
|
+
throw new Error("Computed expression requires exactly one of field, constant, or op.");
|
|
72
|
+
}
|
|
73
|
+
if (kinds[0] === "field") {
|
|
74
|
+
rejectUnknownKeys(expression, ["field"], "computed field expression");
|
|
75
|
+
state.fields.add(requireField(expression.field, "Computed expression field"));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (kinds[0] === "constant") {
|
|
79
|
+
rejectUnknownKeys(expression, ["constant"], "computed constant expression");
|
|
80
|
+
validateConstant(expression.constant);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (ARITHMETIC_BINARY.has(expression.op) || COMPARISONS.has(expression.op)) {
|
|
84
|
+
rejectUnknownKeys(expression, ["op", "left", "right"], "computed binary expression");
|
|
85
|
+
requireNode(expression, "left", expression.op);
|
|
86
|
+
requireNode(expression, "right", expression.op);
|
|
87
|
+
inspectExpression(expression.left, depth + 1, state);
|
|
88
|
+
inspectExpression(expression.right, depth + 1, state);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (NUMERIC_UNARY.has(expression.op) || BOOLEAN_UNARY.has(expression.op)) {
|
|
92
|
+
rejectUnknownKeys(expression, ["op", "operand"], "computed unary expression");
|
|
93
|
+
requireNode(expression, "operand", expression.op);
|
|
94
|
+
inspectExpression(expression.operand, depth + 1, state);
|
|
95
|
+
return;
|
|
46
96
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
97
|
+
if (LOGICAL.has(expression.op)) {
|
|
98
|
+
validateOperands(expression, 2);
|
|
99
|
+
expression.operands.forEach(operand => inspectExpression(operand, depth + 1, state));
|
|
100
|
+
return;
|
|
51
101
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
102
|
+
if (VARIADIC.has(expression.op)) {
|
|
103
|
+
validateOperands(expression, expression.op === "concat" ? 1 : 2);
|
|
104
|
+
expression.operands.forEach(operand => inspectExpression(operand, depth + 1, state));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (expression.op === "if") {
|
|
108
|
+
rejectUnknownKeys(
|
|
109
|
+
expression,
|
|
110
|
+
["op", "condition", "then", "else"],
|
|
111
|
+
"computed conditional expression"
|
|
112
|
+
);
|
|
113
|
+
for (const key of ["condition", "then", "else"]) {
|
|
114
|
+
requireNode(expression, key, expression.op);
|
|
115
|
+
inspectExpression(expression[key], depth + 1, state);
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
58
118
|
}
|
|
59
|
-
validateExpression(expression.operand, depth + 1, state);
|
|
60
|
-
} else {
|
|
61
119
|
throw new Error(`Unsupported computed operation "${expression.op}".`);
|
|
120
|
+
} finally {
|
|
121
|
+
state.ancestors.delete(expression);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function inspectComputedTransform(transform) {
|
|
126
|
+
if (!isPlainObject(transform)) {
|
|
127
|
+
throw new TypeError("Computed transform must be a plain object.");
|
|
62
128
|
}
|
|
63
|
-
|
|
129
|
+
rejectUnknownKeys(transform, TRANSFORM_KEYS, "computed transform");
|
|
130
|
+
if (transform.type !== "computed") {
|
|
131
|
+
throw new Error(`Unsupported computed transform "${transform.type}".`);
|
|
132
|
+
}
|
|
133
|
+
requireField(transform.as, "Computed output field");
|
|
134
|
+
const state = { nodes: 0, fields: new Set(), ancestors: new WeakSet() };
|
|
135
|
+
inspectExpression(transform.expression, 1, state);
|
|
136
|
+
return state;
|
|
64
137
|
}
|
|
65
138
|
|
|
66
139
|
function normalizeExpression(expression) {
|
|
@@ -75,76 +148,204 @@ function normalizeExpression(expression) {
|
|
|
75
148
|
if (Object.hasOwn(expression, "operand")) {
|
|
76
149
|
return { ...expression, operand: normalizeExpression(expression.operand) };
|
|
77
150
|
}
|
|
151
|
+
if (Array.isArray(expression.operands)) {
|
|
152
|
+
return { ...expression, operands: expression.operands.map(normalizeExpression) };
|
|
153
|
+
}
|
|
154
|
+
if (expression.op === "if") {
|
|
155
|
+
return {
|
|
156
|
+
...expression,
|
|
157
|
+
condition: normalizeExpression(expression.condition),
|
|
158
|
+
then: normalizeExpression(expression.then),
|
|
159
|
+
else: normalizeExpression(expression.else)
|
|
160
|
+
};
|
|
161
|
+
}
|
|
78
162
|
return { ...expression };
|
|
79
163
|
}
|
|
80
164
|
|
|
81
165
|
export function normalizeComputedTransform({ as, expression } = {}) {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
166
|
+
const requested = { type: "computed", as, expression };
|
|
167
|
+
inspectComputedTransform(requested);
|
|
168
|
+
return cloneAndFreeze({
|
|
169
|
+
...requested,
|
|
85
170
|
expression: normalizeExpression(expression)
|
|
86
|
-
};
|
|
87
|
-
validateComputedTransform(transform);
|
|
88
|
-
return cloneAndFreeze(transform);
|
|
171
|
+
});
|
|
89
172
|
}
|
|
90
173
|
|
|
91
174
|
export function validateComputedTransform(transform) {
|
|
92
|
-
|
|
93
|
-
|
|
175
|
+
return inspectComputedTransform(transform).nodes;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function scalar(value, label, rowIndex) {
|
|
179
|
+
if (value === undefined || value === null) return null;
|
|
180
|
+
if (typeof value === "number") {
|
|
181
|
+
if (Number.isFinite(value)) return value;
|
|
182
|
+
} else if (typeof value === "string" || typeof value === "boolean") {
|
|
183
|
+
return value;
|
|
94
184
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
185
|
+
throw new TypeError(`${label} must contain a scalar value at row ${rowIndex}.`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function number(value, operation, rowIndex) {
|
|
189
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
190
|
+
throw new TypeError(`Computed ${operation} requires finite numbers at row ${rowIndex}.`);
|
|
98
191
|
}
|
|
99
|
-
|
|
100
|
-
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function boolean(value, operation, rowIndex) {
|
|
196
|
+
if (typeof value !== "boolean") {
|
|
197
|
+
throw new TypeError(`Computed ${operation} requires booleans at row ${rowIndex}.`);
|
|
198
|
+
}
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function compareStrings(left, right) {
|
|
203
|
+
const a = Array.from(left, character => character.codePointAt(0));
|
|
204
|
+
const b = Array.from(right, character => character.codePointAt(0));
|
|
205
|
+
const count = Math.min(a.length, b.length);
|
|
206
|
+
for (let index = 0; index < count; index += 1) {
|
|
207
|
+
if (a[index] !== b[index]) return a[index] < b[index] ? -1 : 1;
|
|
208
|
+
}
|
|
209
|
+
return a.length === b.length ? 0 : a.length < b.length ? -1 : 1;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function compare(left, right, operation, rowIndex) {
|
|
213
|
+
if (left === null || right === null) {
|
|
214
|
+
if (operation === "eq") return left === right;
|
|
215
|
+
if (operation === "neq") return left !== right;
|
|
216
|
+
throw new TypeError(`Computed ${operation} cannot order null at row ${rowIndex}.`);
|
|
217
|
+
}
|
|
218
|
+
if (typeof left !== typeof right) {
|
|
219
|
+
throw new TypeError(
|
|
220
|
+
`Computed ${operation} requires matching primitive types at row ${rowIndex}.`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
if (operation === "eq") return left === right;
|
|
224
|
+
if (operation === "neq") return left !== right;
|
|
225
|
+
let order;
|
|
226
|
+
if (typeof left === "string") order = compareStrings(left, right);
|
|
227
|
+
else if (typeof left === "number") order = left === right ? 0 : left < right ? -1 : 1;
|
|
228
|
+
else if (typeof left === "boolean") order = left === right ? 0 : left ? 1 : -1;
|
|
229
|
+
else throw new TypeError(`Computed ${operation} cannot order values at row ${rowIndex}.`);
|
|
230
|
+
return operation === "lt" ? order < 0
|
|
231
|
+
: operation === "lte" ? order <= 0
|
|
232
|
+
: operation === "gt" ? order > 0
|
|
233
|
+
: order >= 0;
|
|
101
234
|
}
|
|
102
235
|
|
|
103
236
|
function evaluate(expression, row, rowIndex) {
|
|
104
237
|
if (Object.hasOwn(expression, "field")) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
238
|
+
return scalar(
|
|
239
|
+
row[expression.field],
|
|
240
|
+
`Computed field "${expression.field}"`,
|
|
241
|
+
rowIndex
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
if (Object.hasOwn(expression, "constant")) return expression.constant;
|
|
245
|
+
const operation = expression.op;
|
|
246
|
+
if (operation === "if") {
|
|
247
|
+
return boolean(evaluate(expression.condition, row, rowIndex), operation, rowIndex)
|
|
248
|
+
? evaluate(expression.then, row, rowIndex)
|
|
249
|
+
: evaluate(expression.else, row, rowIndex);
|
|
250
|
+
}
|
|
251
|
+
if (operation === "and") {
|
|
252
|
+
for (const operand of expression.operands) {
|
|
253
|
+
if (!boolean(evaluate(operand, row, rowIndex), operation, rowIndex)) return false;
|
|
109
254
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
);
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
if (operation === "or") {
|
|
258
|
+
for (const operand of expression.operands) {
|
|
259
|
+
if (boolean(evaluate(operand, row, rowIndex), operation, rowIndex)) return true;
|
|
115
260
|
}
|
|
116
|
-
return
|
|
261
|
+
return false;
|
|
117
262
|
}
|
|
118
|
-
if (
|
|
119
|
-
|
|
263
|
+
if (operation === "coalesce") {
|
|
264
|
+
for (const operand of expression.operands) {
|
|
265
|
+
const value = evaluate(operand, row, rowIndex);
|
|
266
|
+
if (value !== null) return value;
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
if (operation === "concat") {
|
|
271
|
+
return expression.operands.map(operand => {
|
|
272
|
+
const value = evaluate(operand, row, rowIndex);
|
|
273
|
+
if (typeof value !== "string") {
|
|
274
|
+
throw new TypeError(`Computed concat requires strings at row ${rowIndex}.`);
|
|
275
|
+
}
|
|
276
|
+
return value;
|
|
277
|
+
}).join("");
|
|
278
|
+
}
|
|
279
|
+
if (BOOLEAN_UNARY.has(operation)) {
|
|
120
280
|
const value = evaluate(expression.operand, row, rowIndex);
|
|
121
|
-
return
|
|
281
|
+
return operation === "isNull"
|
|
282
|
+
? value === null
|
|
283
|
+
: !boolean(value, operation, rowIndex);
|
|
284
|
+
}
|
|
285
|
+
if (NUMERIC_UNARY.has(operation)) {
|
|
286
|
+
const value = number(evaluate(expression.operand, row, rowIndex), operation, rowIndex);
|
|
287
|
+
if (operation === "log" && value <= 0) {
|
|
288
|
+
throw new RangeError(`Computed log requires a positive value at row ${rowIndex}.`);
|
|
289
|
+
}
|
|
290
|
+
if (operation === "sqrt" && value < 0) {
|
|
291
|
+
throw new RangeError(`Computed sqrt requires a non-negative value at row ${rowIndex}.`);
|
|
292
|
+
}
|
|
293
|
+
const result = operation === "negate" ? -value
|
|
294
|
+
: operation === "absolute" ? Math.abs(value)
|
|
295
|
+
: operation === "log" ? Math.log(value)
|
|
296
|
+
: Math.sqrt(value);
|
|
297
|
+
if (!Number.isFinite(result)) {
|
|
298
|
+
throw new RangeError(`Computed ${operation} result is not finite at row ${rowIndex}.`);
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
122
301
|
}
|
|
123
302
|
const left = evaluate(expression.left, row, rowIndex);
|
|
124
303
|
const right = evaluate(expression.right, row, rowIndex);
|
|
125
|
-
if (
|
|
304
|
+
if (COMPARISONS.has(operation)) return compare(left, right, operation, rowIndex);
|
|
305
|
+
const leftNumber = number(left, operation, rowIndex);
|
|
306
|
+
const rightNumber = number(right, operation, rowIndex);
|
|
307
|
+
if (operation === "divide" && rightNumber === 0) {
|
|
126
308
|
throw new RangeError(`Computed divide by zero at row ${rowIndex}.`);
|
|
127
309
|
}
|
|
128
|
-
const result =
|
|
129
|
-
:
|
|
130
|
-
:
|
|
131
|
-
:
|
|
310
|
+
const result = operation === "add" ? leftNumber + rightNumber
|
|
311
|
+
: operation === "subtract" ? leftNumber - rightNumber
|
|
312
|
+
: operation === "multiply" ? leftNumber * rightNumber
|
|
313
|
+
: leftNumber / rightNumber;
|
|
132
314
|
if (!Number.isFinite(result)) {
|
|
133
|
-
throw new RangeError(`Computed ${
|
|
315
|
+
throw new RangeError(`Computed ${operation} result is not finite at row ${rowIndex}.`);
|
|
134
316
|
}
|
|
135
317
|
return result;
|
|
136
318
|
}
|
|
137
319
|
|
|
138
320
|
export function deriveComputedRows(rows, transform) {
|
|
139
|
-
const nodes =
|
|
321
|
+
const { nodes, fields } = inspectComputedTransform(transform);
|
|
140
322
|
if (rows.length * nodes > MAX_WORK) {
|
|
141
323
|
throw new RangeError(`Computed work cannot exceed ${MAX_WORK} row-nodes.`);
|
|
142
324
|
}
|
|
143
325
|
if (rows.some(row => Object.hasOwn(row, transform.as))) {
|
|
144
326
|
throw new Error(`Computed output field "${transform.as}" already exists.`);
|
|
145
327
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
328
|
+
if (rows.length > 0) {
|
|
329
|
+
rows.forEach((row, index) => {
|
|
330
|
+
for (const field of fields) {
|
|
331
|
+
if (!Object.hasOwn(row, field)) {
|
|
332
|
+
throw new Error(
|
|
333
|
+
`Computed source does not contain field "${field}" at row ${index}.`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
let outputType;
|
|
340
|
+
return rows.map((row, index) => {
|
|
341
|
+
const value = evaluate(transform.expression, row, index);
|
|
342
|
+
if (value !== null) {
|
|
343
|
+
const type = typeof value;
|
|
344
|
+
if (outputType === undefined) outputType = type;
|
|
345
|
+
else if (outputType !== type) {
|
|
346
|
+
throw new TypeError("Computed output must have one non-null primitive type.");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return { ...row, [transform.as]: value };
|
|
350
|
+
});
|
|
150
351
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { getPositionChannelDefinition } from "../core/vocabulary.js";
|
|
2
|
+
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
3
|
+
import { validateOptionObject } from "../core/validation.js";
|
|
2
4
|
|
|
3
5
|
const COORDINATE_TYPES = new Set(["cartesian", "polar", "parallel"]);
|
|
6
|
+
const ASPECT_MODES = new Set(["frame", "data"]);
|
|
7
|
+
const ASPECT_ALIGNMENTS = new Set(["start", "center", "end"]);
|
|
8
|
+
const ASPECT_OPTIONS = Object.freeze(["mode", "ratio", "alignX", "alignY"]);
|
|
4
9
|
|
|
5
10
|
export function validateCoordinateType(value) {
|
|
6
11
|
if (!COORDINATE_TYPES.has(value)) {
|
|
@@ -19,3 +24,35 @@ export function getPositionCoordinateDefaults(channel) {
|
|
|
19
24
|
|
|
20
25
|
return coordinate;
|
|
21
26
|
}
|
|
27
|
+
|
|
28
|
+
function validateAspectAlignment(value, property) {
|
|
29
|
+
if (!ASPECT_ALIGNMENTS.has(value)) {
|
|
30
|
+
throw new Error(`Unknown coordinate aspect ${property} "${value}".`);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function normalizeCoordinateAspect(value) {
|
|
36
|
+
if (value === "auto") return value;
|
|
37
|
+
if (!isPlainObject(value)) {
|
|
38
|
+
throw new TypeError(
|
|
39
|
+
'Coordinate aspect must be "auto" or a plain object.'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
validateOptionObject(value, ASPECT_OPTIONS, "coordinate aspect", {
|
|
43
|
+
allowEmpty: false,
|
|
44
|
+
emptyError: TypeError
|
|
45
|
+
});
|
|
46
|
+
if (!ASPECT_MODES.has(value.mode)) {
|
|
47
|
+
throw new Error(`Unknown coordinate aspect mode "${value.mode}".`);
|
|
48
|
+
}
|
|
49
|
+
if (!Number.isFinite(value.ratio) || value.ratio <= 0) {
|
|
50
|
+
throw new RangeError("Coordinate aspect ratio must be a positive finite number.");
|
|
51
|
+
}
|
|
52
|
+
return cloneAndFreeze({
|
|
53
|
+
mode: value.mode,
|
|
54
|
+
ratio: value.ratio,
|
|
55
|
+
alignX: validateAspectAlignment(value.alignX ?? "center", "alignX"),
|
|
56
|
+
alignY: validateAspectAlignment(value.alignY ?? "center", "alignY")
|
|
57
|
+
});
|
|
58
|
+
}
|