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/core/programState.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
isOwned,
|
|
5
5
|
isPlainObject
|
|
6
6
|
} from "./immutable.js";
|
|
7
|
+
import { removeMaterializationConfig } from "./materializationState.js";
|
|
7
8
|
|
|
8
9
|
export function ownProgramState(value) {
|
|
9
10
|
return isOwned(value) ? value : cloneAndFreeze(value);
|
|
@@ -22,7 +23,26 @@ export function ownChildPrograms(children, ProgramClass) {
|
|
|
22
23
|
if (!(program instanceof ProgramClass)) {
|
|
23
24
|
throw new TypeError(`ChartProgram child "${id}" must be a ChartProgram.`);
|
|
24
25
|
}
|
|
25
|
-
owned
|
|
26
|
+
Object.defineProperty(owned, id, { value: program, enumerable: true });
|
|
26
27
|
}
|
|
27
28
|
return freezeOwned(owned);
|
|
28
29
|
}
|
|
30
|
+
|
|
31
|
+
// Semantic entries, resolved scale caches, and physical current-resource IDs
|
|
32
|
+
// are removed by editSemantic. A logical data owner also retains its own ID
|
|
33
|
+
// and replay configuration; release those non-semantic references after the
|
|
34
|
+
// domain's dependency validation and before the primitive deletion.
|
|
35
|
+
export function releaseNamedResourceOwnership(program, { kind, id, dataOwner }) {
|
|
36
|
+
if (kind !== "data") return program;
|
|
37
|
+
const materializationConfigs = dataOwner === undefined
|
|
38
|
+
? program.materializationConfigs
|
|
39
|
+
: removeMaterializationConfig(
|
|
40
|
+
program.materializationConfigs,
|
|
41
|
+
["data", dataOwner.family, dataOwner.owner]
|
|
42
|
+
).value;
|
|
43
|
+
const context = program.context.currentData === id
|
|
44
|
+
? freezeOwned({ ...program.context, currentData: undefined })
|
|
45
|
+
: program.context;
|
|
46
|
+
if (context === program.context && materializationConfigs === program.materializationConfigs) return program;
|
|
47
|
+
return program._clone({ context, materializationConfigs });
|
|
48
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { freezeOwned, isPlainObject } from "./immutable.js";
|
|
2
|
+
import { validateUserId } from "./identifiers.js";
|
|
3
|
+
|
|
4
|
+
export const RESOURCE_KINDS = Object.freeze([
|
|
5
|
+
"data", "scale", "coordinate", "mark", "selection"
|
|
6
|
+
]);
|
|
7
|
+
|
|
8
|
+
const DATA_VALUE_KEYS = new Set([
|
|
9
|
+
"data", "dataset", "dataId", "filteredData", "originalData",
|
|
10
|
+
"outlierDataId", "profileId", "summaryId"
|
|
11
|
+
]);
|
|
12
|
+
const DATA_SOURCE_CONFIGS = new Set([
|
|
13
|
+
"boxPlot", "density", "ecdfPlot", "endpointPlot", "errorBand", "errorBar",
|
|
14
|
+
"gradientPlot", "horizon", "intervalPlot", "markFilter", "raincloudPlot",
|
|
15
|
+
"regression", "violinPlot"
|
|
16
|
+
]);
|
|
17
|
+
const MARK_ID_KEYS = new Set([
|
|
18
|
+
"bandId", "bodyId", "centerId", "connectorId", "intervalId", "lineId",
|
|
19
|
+
"lowerBoundaryId", "lowerCapId", "medianId", "outlierId", "startId",
|
|
20
|
+
"stemId", "upperBoundaryId", "upperCapId", "whiskerId"
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
function ownedPath(path) {
|
|
24
|
+
return freezeOwned([...path]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function reference(kind, id, ownerKind, ownerId, path, strength = "live") {
|
|
28
|
+
if (typeof id !== "string" || id.length === 0) return undefined;
|
|
29
|
+
return freezeOwned({
|
|
30
|
+
kind,
|
|
31
|
+
id,
|
|
32
|
+
ownerKind,
|
|
33
|
+
ownerId,
|
|
34
|
+
path: ownedPath(path),
|
|
35
|
+
strength
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function append(references, value) {
|
|
40
|
+
if (value !== undefined) references.push(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function collectKnownKeys(value, path, visit) {
|
|
44
|
+
if (Array.isArray(value)) {
|
|
45
|
+
value.forEach((item, index) => collectKnownKeys(item, [...path, index], visit));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!isPlainObject(value)) return;
|
|
49
|
+
for (const [key, child] of Object.entries(value)) {
|
|
50
|
+
visit(key, child, [...path, key], path);
|
|
51
|
+
collectKnownKeys(child, [...path, key], visit);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function canonicalResourcePath(path) {
|
|
56
|
+
if (!Array.isArray(path)) {
|
|
57
|
+
throw new TypeError("Resource reference path must be an array.");
|
|
58
|
+
}
|
|
59
|
+
return path.map(part => {
|
|
60
|
+
if (Number.isInteger(part) && part >= 0) return `[${part}]`;
|
|
61
|
+
if (typeof part !== "string" || part.length === 0) {
|
|
62
|
+
throw new TypeError("Resource reference path parts must be names or indexes.");
|
|
63
|
+
}
|
|
64
|
+
return `.${part}`;
|
|
65
|
+
}).join("");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function collectSemanticDataReferences(program) {
|
|
69
|
+
const references = [];
|
|
70
|
+
for (const layer of program.semanticSpec.layers) {
|
|
71
|
+
append(references, reference("data", layer.data, "layer", layer.id, ["data"]));
|
|
72
|
+
}
|
|
73
|
+
for (const dataset of program.semanticSpec.datasets) {
|
|
74
|
+
append(references, reference(
|
|
75
|
+
"data", dataset.source, "dataset", dataset.id, ["source"]
|
|
76
|
+
));
|
|
77
|
+
}
|
|
78
|
+
return freezeOwned(references);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function collectSemanticScaleReferences(program) {
|
|
82
|
+
const references = [];
|
|
83
|
+
for (const layer of program.semanticSpec.layers) {
|
|
84
|
+
for (const [channel, encoding] of Object.entries(layer.encoding ?? {})) {
|
|
85
|
+
append(references, reference(
|
|
86
|
+
"scale", encoding?.scale, "layer", layer.id,
|
|
87
|
+
["encoding", channel, "scale"]
|
|
88
|
+
));
|
|
89
|
+
if (channel === "parallel") {
|
|
90
|
+
for (const [index, dimension] of (encoding?.dimensions ?? []).entries()) {
|
|
91
|
+
append(references, reference(
|
|
92
|
+
"scale", dimension.scale, "layer", layer.id,
|
|
93
|
+
["encoding", "parallel", "dimensions", index, "scale"]
|
|
94
|
+
));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
collectKnownKeys(program.semanticSpec.guides, [], (key, value, path) => {
|
|
100
|
+
if (key === "scale") {
|
|
101
|
+
append(references, reference(
|
|
102
|
+
"scale", value, "semanticGuide", "guides", path
|
|
103
|
+
));
|
|
104
|
+
} else if (key === "scales" && Array.isArray(value)) {
|
|
105
|
+
value.forEach((id, index) => append(references, reference(
|
|
106
|
+
"scale", id, "semanticGuide", "guides", [...path, index]
|
|
107
|
+
)));
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return freezeOwned(references);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function collectSemanticCoordinateReferences(program) {
|
|
114
|
+
const references = [];
|
|
115
|
+
for (const layer of program.semanticSpec.layers) {
|
|
116
|
+
append(references, reference(
|
|
117
|
+
"coordinate", layer.coordinate, "layer", layer.id, ["coordinate"]
|
|
118
|
+
));
|
|
119
|
+
}
|
|
120
|
+
collectKnownKeys(program.semanticSpec.guides, [], (key, value, path) => {
|
|
121
|
+
if (key === "coordinate") {
|
|
122
|
+
append(references, reference(
|
|
123
|
+
"coordinate", value, "semanticGuide", "guides", path
|
|
124
|
+
));
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return freezeOwned(references);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function collectMarkConfigReferences(program) {
|
|
131
|
+
const references = [];
|
|
132
|
+
for (const layer of program.semanticSpec.layers) {
|
|
133
|
+
append(references, reference(
|
|
134
|
+
"mark", layer.source, "layer", layer.id, ["source"]
|
|
135
|
+
));
|
|
136
|
+
}
|
|
137
|
+
for (const [ownerId, config] of Object.entries(program.markConfigs)) {
|
|
138
|
+
collectKnownKeys(config, [], (key, value, path, parentPath) => {
|
|
139
|
+
if (DATA_VALUE_KEYS.has(key)) {
|
|
140
|
+
append(references, reference("data", value, "markConfig", ownerId, path));
|
|
141
|
+
}
|
|
142
|
+
const root = parentPath[0];
|
|
143
|
+
if (key === "source" && DATA_SOURCE_CONFIGS.has(root)) {
|
|
144
|
+
append(references, reference("data", value, "markConfig", ownerId, path));
|
|
145
|
+
}
|
|
146
|
+
if (key === "coordinate") {
|
|
147
|
+
append(references, reference(
|
|
148
|
+
"coordinate", value, "markConfig", ownerId, path
|
|
149
|
+
));
|
|
150
|
+
}
|
|
151
|
+
const reservedScale = root === "regression" && key === "colorScale" && config.regression.groupBy === undefined;
|
|
152
|
+
if (!reservedScale && (key === "scale" || key.endsWith("Scale")) && typeof value === "string") {
|
|
153
|
+
append(references, reference("scale", value, "markConfig", ownerId, path));
|
|
154
|
+
} else if (key === "scale" && isPlainObject(value)) {
|
|
155
|
+
append(references, reference(
|
|
156
|
+
"scale", value.id, "markConfig", ownerId, [...path, "id"]
|
|
157
|
+
));
|
|
158
|
+
}
|
|
159
|
+
const inactiveMark = (root === "boxPlot" && key === "outlierId" && !Object.hasOwn(program.markConfigs, value)) ||
|
|
160
|
+
(root === "errorBar" && ["lowerCapId", "upperCapId"].includes(key) && config.errorBar.caps === false) ||
|
|
161
|
+
(root === "errorBand" && ["lowerBoundaryId", "upperBoundaryId"].includes(key) &&
|
|
162
|
+
!Object.hasOwn(program.markConfigs, value));
|
|
163
|
+
if (MARK_ID_KEYS.has(key) && !inactiveMark) {
|
|
164
|
+
append(references, reference("mark", value, "markConfig", ownerId, path));
|
|
165
|
+
}
|
|
166
|
+
if (
|
|
167
|
+
(root === "errorBandBoundary" && key === "owner") ||
|
|
168
|
+
(parentPath.length === 0 && key === "boxSpanOwner") ||
|
|
169
|
+
(root === "raincloudPlot" && (
|
|
170
|
+
key === "ownerId" || parentPath[1] === "childIds" ||
|
|
171
|
+
parentPath[1] === "ownedChildIds"
|
|
172
|
+
))
|
|
173
|
+
) {
|
|
174
|
+
append(references, reference("mark", value, "markConfig", ownerId, path));
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
append(references, reference(
|
|
178
|
+
"mark", config.statisticalReference?.source,
|
|
179
|
+
"markConfig", ownerId, ["statisticalReference", "source"]
|
|
180
|
+
));
|
|
181
|
+
for (const [index, id] of (
|
|
182
|
+
config.raincloudPlot?.ownedChildIds ?? []
|
|
183
|
+
).entries()) {
|
|
184
|
+
append(references, reference(
|
|
185
|
+
"mark", id, "markConfig", ownerId,
|
|
186
|
+
["raincloudPlot", "ownedChildIds", index]
|
|
187
|
+
));
|
|
188
|
+
}
|
|
189
|
+
const selection = config.labelAuthoring?.selection;
|
|
190
|
+
if (selection?.kind === "named") {
|
|
191
|
+
append(references, reference(
|
|
192
|
+
"selection", selection.id, "markConfig", ownerId,
|
|
193
|
+
["labelAuthoring", "selection", "id"]
|
|
194
|
+
));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return freezeOwned(references);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function collectGuideConfigReferences(program) {
|
|
201
|
+
const references = [];
|
|
202
|
+
for (const [family, configs] of Object.entries(program.guideConfigs ?? {})) {
|
|
203
|
+
collectKnownKeys(configs, [], (key, value, path, parentPath) => {
|
|
204
|
+
const ownerId = parentPath.length === 0 ? family : parentPath.join(".");
|
|
205
|
+
const relativePath = parentPath.length === 0
|
|
206
|
+
? path
|
|
207
|
+
: path.slice(parentPath.length);
|
|
208
|
+
if (key === "scale") {
|
|
209
|
+
append(references, reference(
|
|
210
|
+
"scale", value, family, ownerId, relativePath
|
|
211
|
+
));
|
|
212
|
+
} else if (key === "scales" && Array.isArray(value)) {
|
|
213
|
+
value.forEach((id, index) => append(references, reference(
|
|
214
|
+
"scale", id, family, ownerId, [...relativePath, index]
|
|
215
|
+
)));
|
|
216
|
+
} else if (key === "coordinate") {
|
|
217
|
+
append(references, reference(
|
|
218
|
+
"coordinate", value, family, ownerId, relativePath
|
|
219
|
+
));
|
|
220
|
+
} else if (key === "target") {
|
|
221
|
+
append(references, reference(
|
|
222
|
+
"mark", value, family, ownerId, relativePath
|
|
223
|
+
));
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return freezeOwned(references);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function collectSelectionReferences(program) {
|
|
231
|
+
const references = [];
|
|
232
|
+
for (const [id, config] of Object.entries(
|
|
233
|
+
program.materializationConfigs.selections ?? {}
|
|
234
|
+
)) {
|
|
235
|
+
append(references, reference(
|
|
236
|
+
"mark", config.target, "selection", id, ["target"]
|
|
237
|
+
));
|
|
238
|
+
}
|
|
239
|
+
for (const [id, config] of Object.entries(
|
|
240
|
+
program.materializationConfigs.highlights ?? {}
|
|
241
|
+
)) {
|
|
242
|
+
append(references, reference(
|
|
243
|
+
"mark", config.target, "highlight", id, ["target"]
|
|
244
|
+
));
|
|
245
|
+
append(references, reference(
|
|
246
|
+
"selection", config.selection, "highlight", id, ["selection"]
|
|
247
|
+
));
|
|
248
|
+
}
|
|
249
|
+
return freezeOwned(references);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function collectDataOwnerReferences(program) {
|
|
253
|
+
const references = [];
|
|
254
|
+
for (const [family, owners] of Object.entries(
|
|
255
|
+
program.materializationConfigs.data ?? {}
|
|
256
|
+
)) {
|
|
257
|
+
for (const [owner, config] of Object.entries(owners ?? {})) {
|
|
258
|
+
for (const key of ["current", "source", "previous"]) {
|
|
259
|
+
append(references, reference(
|
|
260
|
+
"data", config?.[key], "dataOwner", owner,
|
|
261
|
+
["data", family, owner, key]
|
|
262
|
+
));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return freezeOwned(references);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function collectCompositionReferences(program) {
|
|
270
|
+
const references = [];
|
|
271
|
+
const composition = program.compositionSpec;
|
|
272
|
+
if (composition?.type !== "facet") return freezeOwned(references);
|
|
273
|
+
append(references, reference(
|
|
274
|
+
"data", composition.facet.data, "composition", composition.id,
|
|
275
|
+
["facet", "data"]
|
|
276
|
+
));
|
|
277
|
+
append(references, reference(
|
|
278
|
+
"mark", composition.facet.repeat?.target, "composition", composition.id,
|
|
279
|
+
["facet", "repeat", "target"]
|
|
280
|
+
));
|
|
281
|
+
return freezeOwned(references);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function collectContextReferences(program) {
|
|
285
|
+
const references = [];
|
|
286
|
+
for (const [key, kind] of Object.entries({
|
|
287
|
+
currentData: "data",
|
|
288
|
+
currentScale: "scale",
|
|
289
|
+
currentCoordinate: "coordinate",
|
|
290
|
+
currentMark: "mark",
|
|
291
|
+
currentSelection: "selection"
|
|
292
|
+
})) {
|
|
293
|
+
append(references, reference(
|
|
294
|
+
kind, program.context[key], "context", "program", [key], "context"
|
|
295
|
+
));
|
|
296
|
+
}
|
|
297
|
+
return freezeOwned(references);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function referenceKey(value) {
|
|
301
|
+
return [
|
|
302
|
+
value.kind, value.id, value.ownerKind, value.ownerId,
|
|
303
|
+
canonicalResourcePath(value.path), value.strength
|
|
304
|
+
].join("\u0000");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function compareReferences(left, right) {
|
|
308
|
+
const leftKey = [
|
|
309
|
+
left.ownerKind, left.ownerId, canonicalResourcePath(left.path),
|
|
310
|
+
left.kind, left.id, left.strength
|
|
311
|
+
].join("\u0000");
|
|
312
|
+
const rightKey = [
|
|
313
|
+
right.ownerKind, right.ownerId, canonicalResourcePath(right.path),
|
|
314
|
+
right.kind, right.id, right.strength
|
|
315
|
+
].join("\u0000");
|
|
316
|
+
return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function collectResourceReferences(program, { kind, id }) {
|
|
320
|
+
if (!RESOURCE_KINDS.includes(kind)) {
|
|
321
|
+
throw new Error(`Unknown resource kind "${kind}".`);
|
|
322
|
+
}
|
|
323
|
+
const target = validateUserId(id, "Resource id");
|
|
324
|
+
const combined = collectAllResourceReferences(program).filter(value => value.kind === kind && value.id === target);
|
|
325
|
+
const unique = new Map(combined.map(value => [referenceKey(value), value]));
|
|
326
|
+
return freezeOwned([...unique.values()].sort(compareReferences));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function collectAllResourceReferences(program) {
|
|
330
|
+
return [
|
|
331
|
+
...collectSemanticDataReferences(program),
|
|
332
|
+
...collectSemanticScaleReferences(program),
|
|
333
|
+
...collectSemanticCoordinateReferences(program),
|
|
334
|
+
...collectMarkConfigReferences(program),
|
|
335
|
+
...collectGuideConfigReferences(program),
|
|
336
|
+
...collectSelectionReferences(program),
|
|
337
|
+
...collectDataOwnerReferences(program),
|
|
338
|
+
...collectCompositionReferences(program),
|
|
339
|
+
...collectContextReferences(program)
|
|
340
|
+
];
|
|
341
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { cloneAndFreeze } from "./immutable.js";
|
|
2
|
+
import { validateUserId } from "./identifiers.js";
|
|
3
|
+
import { validateOptionObject, validatePositiveFinite, validateNonNegativeFinite, validateNonEmptyString } from "./validation.js";
|
|
4
|
+
|
|
5
|
+
import { textMetricKey } from "./font.js";
|
|
6
|
+
|
|
7
|
+
export function normalizeTextMetricProfile(profile) {
|
|
8
|
+
validateOptionObject(profile, ["schemaVersion", "id", "measurements"], "Text metrics profile");
|
|
9
|
+
if (profile.schemaVersion !== 1) throw new Error("Text metrics require schemaVersion 1.");
|
|
10
|
+
validateUserId(profile.id, "Text metrics profile id");
|
|
11
|
+
if (!Array.isArray(profile.measurements)) throw new TypeError("Text metrics measurements must be an array.");
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
for (const measurement of profile.measurements) {
|
|
14
|
+
validateOptionObject(measurement, ["text", "fontFamily", "fontSize", "fontWeight", "width"], "Text measurement");
|
|
15
|
+
if (typeof measurement.text !== "string") throw new TypeError("Text measurement text must be a string.");
|
|
16
|
+
validateNonEmptyString(measurement.fontFamily, "Text measurement fontFamily");
|
|
17
|
+
validatePositiveFinite(measurement.fontSize, "Text measurement fontSize");
|
|
18
|
+
validateNonNegativeFinite(measurement.width, "Text measurement width");
|
|
19
|
+
if (!Number.isInteger(measurement.fontWeight) || measurement.fontWeight < 100 ||
|
|
20
|
+
measurement.fontWeight > 900 || measurement.fontWeight % 100 !== 0) {
|
|
21
|
+
throw new RangeError("Text measurement fontWeight must be 100–900 in steps of 100.");
|
|
22
|
+
}
|
|
23
|
+
const key = textMetricKey(measurement);
|
|
24
|
+
if (seen.has(key)) throw new Error("Duplicate text measurement combination.");
|
|
25
|
+
seen.add(key);
|
|
26
|
+
}
|
|
27
|
+
return cloneAndFreeze(profile);
|
|
28
|
+
}
|
package/src/core/textMetrics.js
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
import { DEFAULT_FONT_FAMILY, textMetricFontWeight, textMetricKey } from "./font.js";
|
|
2
|
+
|
|
3
|
+
import { isOwned } from "./immutable.js";
|
|
4
|
+
|
|
5
|
+
const measurementIndexes = new WeakMap();
|
|
6
|
+
|
|
7
|
+
function measuredWidth(profile, text, style) {
|
|
8
|
+
if (profile === undefined) return undefined;
|
|
9
|
+
let index = measurementIndexes.get(profile);
|
|
10
|
+
if (index === undefined) {
|
|
11
|
+
index = new Map(profile.measurements.map(measurement =>
|
|
12
|
+
[textMetricKey(measurement), measurement.width]));
|
|
13
|
+
if (isOwned(profile)) measurementIndexes.set(profile, index);
|
|
14
|
+
}
|
|
15
|
+
return index.get(textMetricKey({ text, fontFamily: style.fontFamily ?? DEFAULT_FONT_FAMILY,
|
|
16
|
+
fontSize: style.fontSize, fontWeight: textMetricFontWeight(style.fontWeight) }));
|
|
17
|
+
}
|
|
18
|
+
|
|
1
19
|
function codePointWidth(codePoint) {
|
|
2
20
|
if (/\s/u.test(codePoint)) return 0.28;
|
|
3
21
|
if (/[iIl.,:;!'|]/u.test(codePoint)) return 0.27;
|
|
@@ -25,7 +43,8 @@ export function textBoundsFitCanvas(bounds, canvas) {
|
|
|
25
43
|
|
|
26
44
|
export function measureTextWidth(
|
|
27
45
|
text,
|
|
28
|
-
{ fontSize, fontFamily, fontWeight } = {}
|
|
46
|
+
{ fontSize, fontFamily, fontWeight } = {},
|
|
47
|
+
profile
|
|
29
48
|
) {
|
|
30
49
|
if (typeof text !== "string") {
|
|
31
50
|
throw new TypeError("Text measurement requires a string.");
|
|
@@ -33,6 +52,8 @@ export function measureTextWidth(
|
|
|
33
52
|
if (!Number.isFinite(fontSize) || fontSize <= 0) {
|
|
34
53
|
throw new RangeError("Text measurement requires a positive fontSize.");
|
|
35
54
|
}
|
|
55
|
+
const measured = measuredWidth(profile, text, { fontSize, fontFamily, fontWeight });
|
|
56
|
+
if (measured !== undefined) return measured;
|
|
36
57
|
let joined = false;
|
|
37
58
|
let width = 0;
|
|
38
59
|
for (const codePoint of text) {
|
|
@@ -76,11 +97,11 @@ export function resolveTextBounds({
|
|
|
76
97
|
textAlign = "left",
|
|
77
98
|
textBaseline = "alphabetic",
|
|
78
99
|
rotation = 0
|
|
79
|
-
} = {}) {
|
|
100
|
+
} = {}, profile) {
|
|
80
101
|
if (![x, y, rotation].every(Number.isFinite)) {
|
|
81
102
|
throw new TypeError("Text bounds require finite x, y, and rotation values.");
|
|
82
103
|
}
|
|
83
|
-
const width = measureTextWidth(text, { fontSize, fontFamily, fontWeight });
|
|
104
|
+
const width = measureTextWidth(text, { fontSize, fontFamily, fontWeight }, profile);
|
|
84
105
|
const [left, right] = textAlign === "center"
|
|
85
106
|
? [-width / 2, width / 2]
|
|
86
107
|
: ["right", "end"].includes(textAlign) ? [-width, 0] : [0, width];
|
package/src/core/validation.js
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
|
+
import { annotateError } from "./diagnostics.js";
|
|
1
2
|
import { isPlainObject } from "./immutable.js";
|
|
2
3
|
|
|
3
4
|
export const MAX_GENERATED_ITEMS = 10_000;
|
|
4
5
|
export const MAX_WORK_ITEMS = 10_000_000;
|
|
5
6
|
|
|
7
|
+
export function validateDataRows(values, label = "Dataset") {
|
|
8
|
+
if (!Array.isArray(values)) {
|
|
9
|
+
throw annotateError(new TypeError(`${label} requires values to be an array.`), { code: "invalid-value", optionPath: "values" });
|
|
10
|
+
}
|
|
11
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
12
|
+
if (!Object.hasOwn(values, index) || !isPlainObject(values[index])) {
|
|
13
|
+
throw annotateError(new TypeError(`${label} requires every row to be a plain object; invalid row at index ${index}.`), { code: "invalid-value", optionPath: `values[${index}]` });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
export function validateGeneratedItemLimit(
|
|
7
19
|
value,
|
|
8
20
|
label,
|
|
9
21
|
maximum = MAX_GENERATED_ITEMS
|
|
10
22
|
) {
|
|
11
23
|
if (value > maximum) {
|
|
12
|
-
throw new RangeError(`${label} must not exceed ${maximum}.`);
|
|
24
|
+
throw annotateError(new RangeError(`${label} must not exceed ${maximum}.`), { code: "resource-limit", optionPath: label, limit: maximum, actual: value });
|
|
13
25
|
}
|
|
14
26
|
return value;
|
|
15
27
|
}
|
|
@@ -21,7 +33,7 @@ export function validateWorkLimit(value, label) {
|
|
|
21
33
|
export function validateKeys(value, supported, label) {
|
|
22
34
|
for (const key of Object.keys(value)) {
|
|
23
35
|
if (!supported.includes(key)) {
|
|
24
|
-
throw new Error(`Unknown ${label} option "${key}".`);
|
|
36
|
+
throw annotateError(new Error(`Unknown ${label} option "${key}".`), { code: "invalid-option", optionPath: key });
|
|
25
37
|
}
|
|
26
38
|
}
|
|
27
39
|
}
|
|
@@ -33,18 +45,18 @@ export function validateOptionObject(value, supported, label, {
|
|
|
33
45
|
emptyError = TypeError
|
|
34
46
|
} = {}) {
|
|
35
47
|
if (!isPlainObject(value)) {
|
|
36
|
-
throw new TypeError(plainObjectMessage);
|
|
48
|
+
throw annotateError(new TypeError(plainObjectMessage), { code: "invalid-option" });
|
|
37
49
|
}
|
|
38
50
|
if (supported !== undefined) validateKeys(value, supported, label);
|
|
39
51
|
if (!allowEmpty && Object.keys(value).length === 0) {
|
|
40
|
-
throw new emptyError(emptyMessage);
|
|
52
|
+
throw annotateError(new emptyError(emptyMessage), { code: "invalid-option" });
|
|
41
53
|
}
|
|
42
54
|
return value;
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
export function noOptions(args, operation) {
|
|
46
58
|
if (!isPlainObject(args) || Object.keys(args).length > 0) {
|
|
47
|
-
throw new Error(`${operation} does not accept options.`);
|
|
59
|
+
throw annotateError(new Error(`${operation} does not accept options.`), { code: "invalid-option", operation });
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
|
|
@@ -59,30 +71,30 @@ export function sameOrderedValues(left, right) {
|
|
|
59
71
|
|
|
60
72
|
export function validateNonEmptyString(value, label) {
|
|
61
73
|
if (typeof value !== "string" || value.length === 0) {
|
|
62
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
74
|
+
throw annotateError(new TypeError(`${label} must be a non-empty string.`), { code: "invalid-value", optionPath: label });
|
|
63
75
|
}
|
|
64
76
|
return value;
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
export function validateUnitInterval(value, label) {
|
|
68
80
|
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
69
|
-
throw new RangeError(
|
|
81
|
+
throw annotateError(new RangeError(
|
|
70
82
|
`${label} must be between 0 and 1 (values from 0 to 1).`
|
|
71
|
-
);
|
|
83
|
+
), { code: "invalid-value", optionPath: label });
|
|
72
84
|
}
|
|
73
85
|
return value;
|
|
74
86
|
}
|
|
75
87
|
|
|
76
88
|
export function validatePositiveFinite(value, label) {
|
|
77
89
|
if (!Number.isFinite(value) || value <= 0) {
|
|
78
|
-
throw new RangeError(`${label} must be a positive finite number.`);
|
|
90
|
+
throw annotateError(new RangeError(`${label} must be a positive finite number.`), { code: "invalid-value", optionPath: label });
|
|
79
91
|
}
|
|
80
92
|
return value;
|
|
81
93
|
}
|
|
82
94
|
|
|
83
95
|
export function validateNonNegativeFinite(value, label) {
|
|
84
96
|
if (!Number.isFinite(value) || value < 0) {
|
|
85
|
-
throw new RangeError(`${label} must be a non-negative finite number.`);
|
|
97
|
+
throw annotateError(new RangeError(`${label} must be a non-negative finite number.`), { code: "invalid-value", optionPath: label });
|
|
86
98
|
}
|
|
87
99
|
return value;
|
|
88
100
|
}
|
package/src/core/vocabulary.js
CHANGED
|
@@ -13,13 +13,13 @@ export const MARK_TYPES = Object.freeze([
|
|
|
13
13
|
export const MARK_GRAPHIC_TYPES = Object.freeze({
|
|
14
14
|
point: Object.freeze(["circle", "rect", "path", "collection"]),
|
|
15
15
|
line: Object.freeze(["path"]),
|
|
16
|
-
bar: Object.freeze(["rect"]),
|
|
16
|
+
bar: Object.freeze(["rect", "collection"]),
|
|
17
17
|
area: Object.freeze(["path"]),
|
|
18
18
|
arc: Object.freeze(["path"]),
|
|
19
19
|
rule: Object.freeze(["line"]),
|
|
20
20
|
tick: Object.freeze(["line"]),
|
|
21
21
|
text: Object.freeze(["text"]),
|
|
22
|
-
rect: Object.freeze(["rect"])
|
|
22
|
+
rect: Object.freeze(["rect", "collection"])
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
export const ENCODING_CHANNELS = Object.freeze([
|
|
@@ -32,6 +32,7 @@ export const ENCODING_CHANNELS = Object.freeze([
|
|
|
32
32
|
"theta",
|
|
33
33
|
"radius",
|
|
34
34
|
"color",
|
|
35
|
+
"stroke",
|
|
35
36
|
"strokeDash",
|
|
36
37
|
"strokeWidth",
|
|
37
38
|
"size",
|
|
@@ -155,13 +156,21 @@ export const FACET_SCALE_CHANNELS = Object.freeze([
|
|
|
155
156
|
"y",
|
|
156
157
|
"xOffset",
|
|
157
158
|
"yOffset",
|
|
159
|
+
"theta",
|
|
160
|
+
"r",
|
|
158
161
|
"color",
|
|
162
|
+
"stroke",
|
|
159
163
|
"size",
|
|
160
164
|
"shape",
|
|
161
165
|
"opacity",
|
|
162
|
-
"strokeDash"
|
|
166
|
+
"strokeDash",
|
|
167
|
+
"parallelDimensions"
|
|
163
168
|
]);
|
|
164
169
|
|
|
170
|
+
export function facetSemanticChannel(channel) {
|
|
171
|
+
return channel === "r" ? "radius" : channel;
|
|
172
|
+
}
|
|
173
|
+
|
|
165
174
|
export const FACET_SCALE_RESOLUTIONS = Object.freeze([
|
|
166
175
|
"shared",
|
|
167
176
|
"independent"
|
|
@@ -198,6 +207,7 @@ export const STACK_MODES = Object.freeze(["zero", "normalize", "center"]);
|
|
|
198
207
|
|
|
199
208
|
export const CATEGORICAL_LEGEND_CHANNELS = Object.freeze([
|
|
200
209
|
"color",
|
|
210
|
+
"stroke",
|
|
201
211
|
"strokeDash",
|
|
202
212
|
"shape"
|
|
203
213
|
]);
|
|
@@ -212,9 +222,12 @@ export const LEGEND_CHANNELS = Object.freeze([
|
|
|
212
222
|
export const LEGEND_CONFIG_KINDS = Object.freeze([
|
|
213
223
|
"series",
|
|
214
224
|
"color",
|
|
225
|
+
"stroke",
|
|
215
226
|
"size",
|
|
216
227
|
"gradient",
|
|
217
228
|
"interval",
|
|
229
|
+
"strokeGradient",
|
|
230
|
+
"strokeInterval",
|
|
218
231
|
"opacity",
|
|
219
232
|
"strokeWidth"
|
|
220
233
|
]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getErrorDetails } from "./core/diagnostics.js";
|
|
@@ -58,12 +58,14 @@ export function deriveBarAggregates(rows, layer) {
|
|
|
58
58
|
category: categoryValue,
|
|
59
59
|
...(identity === undefined ? {} : { series: seriesValue }),
|
|
60
60
|
...(colorValues === undefined ? {} : { color: colorValue }),
|
|
61
|
-
rows: []
|
|
61
|
+
rows: [],
|
|
62
|
+
sourceIndices: []
|
|
62
63
|
};
|
|
63
64
|
if (identity !== undefined && colorValues !== undefined && group.color !== colorValue) {
|
|
64
65
|
throw new Error("Bar color requires one categorical value within each aggregate cell.");
|
|
65
66
|
}
|
|
66
67
|
group.rows.push(rows[index]);
|
|
68
|
+
group.sourceIndices.push(index);
|
|
67
69
|
groups.set(key, group);
|
|
68
70
|
}
|
|
69
71
|
|
|
@@ -83,6 +85,7 @@ export function deriveBarAggregates(rows, layer) {
|
|
|
83
85
|
y: channels.category === "y" ? group.category : value,
|
|
84
86
|
...(color === undefined ? {} : { color: aggregateColor }),
|
|
85
87
|
...(identity === undefined ? {} : { series: group.series }),
|
|
88
|
+
sourceIndices: group.sourceIndices,
|
|
86
89
|
count: group.rows.length
|
|
87
90
|
}];
|
|
88
91
|
});
|