ggaction 0.0.12 → 0.0.15
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 +127 -0
- package/README.md +20 -11
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/action-cards.schema.json +1 -1
- package/knowledge/extension-authoring.md +2 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +10 -3
- package/src/actions/basic.js +4 -2
- package/src/actions/boxPlots/components.js +14 -4
- package/src/actions/boxPlots/edit.js +31 -5
- package/src/actions/boxPlots/materialize.js +13 -4
- package/src/actions/boxPlots/options.js +48 -16
- package/src/actions/canvas/actions.js +3 -1
- package/src/actions/canvas/fitting.js +211 -0
- package/src/actions/canvas/index.js +2 -0
- package/src/actions/categoryOrder/index.js +18 -6
- package/src/actions/charts/area.js +51 -0
- package/src/actions/charts/bar.js +13 -6
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +48 -0
- package/src/actions/charts/ecdf.js +186 -0
- package/src/actions/charts/endpoints.js +473 -0
- package/src/actions/charts/heatmap.js +4 -3
- package/src/actions/charts/histogram.js +5 -4
- package/src/actions/charts/horizon.js +73 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +172 -0
- package/src/actions/charts/line.js +8 -6
- package/src/actions/charts/parallel.js +8 -2
- package/src/actions/charts/pie.js +52 -0
- package/src/actions/charts/polar.js +134 -0
- package/src/actions/charts/radar.js +305 -0
- package/src/actions/charts/radial.js +50 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +283 -0
- package/src/actions/charts/scatter.js +11 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +129 -3
- package/src/actions/coordinates/actions.js +1 -1
- package/src/actions/coordinates/edit.js +76 -0
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/bin2d.js +57 -185
- package/src/actions/data/complete.js +26 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/create.js +2 -2
- package/src/actions/data/density.js +12 -20
- package/src/actions/data/derived.js +111 -15
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/edit.js +539 -0
- package/src/actions/data/filter.js +226 -30
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/impute.js +26 -0
- package/src/actions/data/index.js +38 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/normalize.js +27 -0
- package/src/actions/data/regression.js +19 -21
- package/src/actions/data/revisionBindings.js +145 -0
- package/src/actions/data/shared.js +20 -4
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.js +4 -1
- package/src/actions/data/window.js +1 -1
- package/src/actions/encodings/appearance.js +32 -13
- package/src/actions/encodings/barWidth.js +8 -0
- package/src/actions/encodings/basic.js +2 -0
- package/src/actions/encodings/channels.js +415 -0
- package/src/actions/encodings/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +30 -23
- package/src/actions/encodings/color/layout.js +3 -97
- package/src/actions/encodings/color/policy.js +5 -8
- package/src/actions/encodings/density/resolve.js +37 -5
- package/src/actions/encodings/density.js +124 -34
- package/src/actions/encodings/histogram.js +3 -2
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +6 -0
- package/src/actions/encodings/offset.js +30 -29
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +63 -11
- package/src/actions/encodings/position/index.js +52 -6
- package/src/actions/encodings/position/policies/arc.js +14 -0
- package/src/actions/encodings/position/policies/area.js +4 -42
- package/src/actions/encodings/position/policies/bar.js +57 -13
- package/src/actions/encodings/position/resolve.js +88 -26
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +44 -34
- package/src/actions/encodings/ruleAppearance.js +24 -32
- package/src/actions/encodings/seriesLayout.js +105 -0
- package/src/actions/encodings/shared.js +107 -37
- package/src/actions/encodings/stroke.js +226 -0
- package/src/actions/encodings/strokeDash.js +9 -6
- package/src/actions/encodings/temporal.js +7 -0
- package/src/actions/encodings/text.js +30 -15
- package/src/actions/errorBands/create.js +41 -8
- package/src/actions/errorBands/edit.js +360 -25
- package/src/actions/errorBars/create.js +56 -17
- package/src/actions/errorBars/edit.js +372 -21
- package/src/actions/facets/actions.js +619 -41
- package/src/actions/facets/derive.js +139 -25
- package/src/actions/gradientPlots/components.js +43 -13
- package/src/actions/gradientPlots/edit.js +14 -4
- package/src/actions/gradientPlots/materialize.js +8 -8
- package/src/actions/gradientPlots/options.js +10 -2
- package/src/actions/guides/applicability.js +28 -19
- package/src/actions/guides/axes/axes.js +4 -3
- package/src/actions/guides/axes/axis.js +18 -18
- package/src/actions/guides/axes/components.js +43 -0
- package/src/actions/guides/axes/edit.js +16 -51
- package/src/actions/guides/axes/labels.js +184 -28
- package/src/actions/guides/axes/lines.js +13 -7
- package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
- package/src/actions/guides/axes/parallel/policy.js +137 -0
- package/src/actions/guides/axes/parallel/resolve.js +50 -25
- package/src/actions/guides/axes/parallel.js +93 -217
- package/src/actions/guides/axes/policy.js +21 -67
- package/src/actions/guides/axes/tickGroups.js +15 -8
- package/src/actions/guides/axes/ticks.js +11 -6
- package/src/actions/guides/axes/titles.js +32 -11
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facade.js +106 -0
- package/src/actions/guides/facadeAxes.js +200 -0
- package/src/actions/guides/facadeLegend.js +63 -0
- package/src/actions/guides/grids/grid.js +37 -33
- package/src/actions/guides/grids/resolve.js +17 -4
- package/src/actions/guides/guides.js +54 -31
- package/src/actions/guides/legends/blocks.js +426 -0
- package/src/actions/guides/legends/categorical/actions.js +309 -279
- package/src/actions/guides/legends/categorical/components.js +21 -17
- package/src/actions/guides/legends/categorical/index.js +37 -33
- package/src/actions/guides/legends/categorical/layout.js +123 -354
- package/src/actions/guides/legends/categorical/options.js +32 -16
- package/src/actions/guides/legends/categorical/recipes.js +114 -9
- package/src/actions/guides/legends/categorical/resolve.js +30 -15
- package/src/actions/guides/legends/categorical/symbols.js +87 -33
- package/src/actions/guides/legends/continuous/common.js +99 -26
- package/src/actions/guides/legends/continuous/gradient.js +120 -89
- package/src/actions/guides/legends/continuous/index.js +8 -2
- package/src/actions/guides/legends/continuous/interval.js +79 -95
- package/src/actions/guides/legends/continuous/opacity.js +113 -89
- package/src/actions/guides/legends/continuous/stroke.js +525 -0
- package/src/actions/guides/legends/creation.js +40 -0
- package/src/actions/guides/legends/edit.js +321 -90
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/lane.js +149 -102
- package/src/actions/guides/legends/lifecycle.js +143 -0
- package/src/actions/guides/legends/remove.js +43 -78
- package/src/actions/guides/legends/sampling.js +140 -0
- package/src/actions/guides/legends/size.js +213 -116
- package/src/actions/guides/legends/strokeWidth.js +121 -73
- package/src/actions/guides/legends/target.js +123 -0
- package/src/actions/guides/legends/transition.js +192 -0
- package/src/actions/guides/polar/axes/facade.js +81 -66
- package/src/actions/guides/polar/axes/labels.js +28 -8
- package/src/actions/guides/polar/axes/lines.js +12 -5
- package/src/actions/guides/polar/axes/shared.js +22 -0
- package/src/actions/guides/polar/axes/ticks.js +6 -5
- package/src/actions/guides/polar/axes/titles.js +6 -5
- package/src/actions/guides/polar/grids.js +8 -8
- package/src/actions/guides/polar/resolve.js +12 -8
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +15 -2
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +78 -20
- package/src/actions/marks/area/actions.js +51 -12
- package/src/actions/marks/area/materialize.js +22 -3
- package/src/actions/marks/bar/create.js +11 -2
- package/src/actions/marks/bar/edit.js +20 -3
- package/src/actions/marks/bar/materialize.js +33 -9
- package/src/actions/marks/index.js +14 -0
- package/src/actions/marks/line/actions.js +71 -113
- package/src/actions/marks/line/materialize.js +57 -56
- package/src/actions/marks/point/create.js +8 -2
- package/src/actions/marks/point/edit.js +29 -21
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/point/jitter.js +3 -0
- package/src/actions/marks/point/materialize.js +154 -16
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/rect/actions.js +33 -12
- package/src/actions/marks/references.js +658 -0
- package/src/actions/marks/remove.js +87 -4
- package/src/actions/marks/rule/actions.js +77 -9
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +13 -13
- package/src/actions/marks/text/actions.js +296 -13
- package/src/actions/marks/text/index.js +4 -0
- package/src/actions/marks/text/layout.js +156 -8
- package/src/actions/marks/text/removal.js +173 -0
- package/src/actions/marks/text/selection.js +73 -0
- package/src/actions/marks/tick/actions.js +22 -5
- package/src/actions/primitives/createGraphics.js +3 -1
- package/src/actions/primitives/editGraphics.js +26 -0
- package/src/actions/primitives/semantic.js +3 -2
- package/src/actions/primitives/semanticAction.js +120 -10
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/index.js +9 -1
- package/src/actions/primitives/semanticValidation/layer.js +23 -1
- package/src/actions/primitives/semanticValidation/scale.js +38 -8
- package/src/actions/regression/components.js +26 -11
- package/src/actions/regression/create.js +11 -3
- package/src/actions/regression/edit.js +40 -13
- package/src/actions/regression/resolve.js +1 -1
- package/src/actions/resources/index.js +7 -0
- package/src/actions/resources/remove.js +172 -0
- package/src/actions/scales/channels.js +212 -0
- package/src/actions/scales/consumers/common.js +21 -54
- package/src/actions/scales/consumers/families.js +28 -14
- package/src/actions/scales/consumers/index.js +42 -4
- package/src/actions/scales/consumers/seriesLayout.js +57 -12
- package/src/actions/scales/create.js +58 -26
- package/src/actions/scales/definitions.js +109 -39
- package/src/actions/scales/edit.js +101 -34
- package/src/actions/scales/editPolicy.js +95 -35
- package/src/actions/scales/index.js +6 -0
- package/src/actions/scales/materialize.js +14 -49
- package/src/actions/scales/offset.js +48 -0
- package/src/actions/scales/parallel.js +71 -0
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +140 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/selection/actions.js +154 -30
- package/src/actions/selection/index.js +3 -0
- package/src/actions/theme/actions.js +98 -0
- package/src/actions/theme/composition.js +238 -0
- package/src/actions/theme/index.js +16 -0
- package/src/actions/theme/reconcile.js +1130 -0
- package/src/actions/theme/state.js +160 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +90 -43
- package/src/actions/violinPlots/edit.js +192 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +32 -2
- package/src/core/compositionState.js +141 -8
- package/src/core/programState.js +20 -0
- package/src/core/resourceReferences.js +332 -0
- package/src/core/vocabulary.js +16 -3
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +58 -1
- package/src/grammar/areaEndpoints.js +46 -0
- package/src/grammar/areaSeries.js +87 -41
- package/src/grammar/bars/aggregate.js +37 -13
- package/src/grammar/bars/geometry.js +44 -0
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +226 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/complete.js +279 -0
- package/src/grammar/computed.js +351 -0
- package/src/grammar/coordinates.js +37 -0
- package/src/grammar/density.js +239 -37
- package/src/grammar/displayLabels.js +57 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/dependencies.js +133 -1
- package/src/grammar/facets/guides.js +3 -0
- package/src/grammar/facets/index.js +123 -52
- package/src/grammar/facets/scales.js +15 -18
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/impute.js +340 -0
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabelSelection.js +57 -0
- package/src/grammar/markLabels.js +110 -0
- package/src/grammar/normalize.js +366 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +109 -0
- package/src/grammar/pointPacking.js +183 -0
- package/src/grammar/pointShapes.js +13 -4
- package/src/grammar/polar.js +90 -8
- package/src/grammar/positionDatum.js +12 -0
- package/src/grammar/rects.js +9 -1
- package/src/grammar/regression/derive.js +4 -0
- package/src/grammar/regression/index.js +8 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +63 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/roundedRect.js +180 -0
- package/src/grammar/rules.js +8 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +57 -0
- package/src/grammar/scales/continuous.js +2 -2
- package/src/grammar/scales/definition.js +42 -5
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/index.js +1 -0
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/ordinal.js +12 -3
- package/src/grammar/scales/policies.js +2 -2
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/size.js +456 -0
- package/src/grammar/scales/types.js +1 -0
- package/src/grammar/schemas/concreteGraphic.js +14 -2
- package/src/grammar/schemas/graphic.js +15 -0
- package/src/grammar/schemas/graphicBounds.js +179 -30
- package/src/grammar/schemas/semanticPath.js +23 -4
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statisticalReference.js +103 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/strokeStyle.js +77 -0
- package/src/grammar/summary.js +245 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +234 -13
- package/src/grammar/transformTopology.js +10 -0
- package/src/grammar/transforms.js +418 -16
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/weightedStatistics.js +374 -0
- package/src/grammar/window.js +232 -21
- package/src/layout/aspect.js +80 -0
- package/src/layout/facets.js +100 -12
- package/src/layout/guideCollisions.js +12 -0
- package/src/layout/labels.js +343 -0
- package/src/layout/legend.js +2 -10
- package/src/layout/legendItems.js +102 -0
- package/src/layout/legendLane.js +100 -11
- package/src/materialization/bars/aggregate.js +56 -147
- package/src/materialization/bars/histogram.js +104 -20
- package/src/materialization/bars/ranged.js +19 -3
- package/src/materialization/bars/resolve.js +7 -4
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/composition.js +3 -1
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/coordinateBounds.js +204 -0
- package/src/materialization/dependencies.js +93 -16
- package/src/materialization/encodings.js +1 -1
- package/src/materialization/facetGuides/legacyCategorical.js +3 -0
- package/src/materialization/facetGuides/placement.js +8 -2
- package/src/materialization/facetGuides/preparation.js +12 -7
- package/src/materialization/facetHeaders.js +346 -0
- package/src/materialization/facets.js +23 -66
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +28 -2
- package/src/materialization/legends.js +11 -2
- package/src/materialization/marks/capabilities.js +20 -5
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +68 -27
- package/src/materialization/marks/policies.js +4 -1
- package/src/materialization/rect.js +55 -42
- package/src/materialization/rectConfig.js +5 -1
- package/src/materialization/rowEncoding.js +13 -13
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +6 -3
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/offset.js +50 -8
- package/src/materialization/scales/policies/series.js +5 -1
- package/src/materialization/scales/resolve.js +65 -17
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/arc.js +28 -4
- package/src/materialization/selection/items/bar.js +42 -71
- package/src/materialization/selection/items/common.js +27 -6
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +35 -8
- package/src/materialization/selection/path.js +1 -1
- package/src/materialization/selection/point.js +3 -1
- package/src/materialization/text.js +324 -14
- package/src/mcp/adapter.js +2 -2
- package/src/renderers/canvas/circle.js +2 -3
- 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/svg.js +19 -0
- package/src/selectors/datasets.js +44 -0
- package/src/selectors/layers.js +7 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +140 -0
- package/types/basic.d.ts +17 -2
- package/types/index.d.ts +211 -0
- package/types/program.d.ts +2182 -204
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -0,0 +1,332 @@
|
|
|
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
|
+
if ((key === "scale" || key.endsWith("Scale")) && typeof value === "string") {
|
|
152
|
+
append(references, reference("scale", value, "markConfig", ownerId, path));
|
|
153
|
+
} else if (key === "scale" && isPlainObject(value)) {
|
|
154
|
+
append(references, reference(
|
|
155
|
+
"scale", value.id, "markConfig", ownerId, [...path, "id"]
|
|
156
|
+
));
|
|
157
|
+
}
|
|
158
|
+
if (MARK_ID_KEYS.has(key)) {
|
|
159
|
+
append(references, reference("mark", value, "markConfig", ownerId, path));
|
|
160
|
+
}
|
|
161
|
+
if (
|
|
162
|
+
(root === "errorBandBoundary" && key === "owner") ||
|
|
163
|
+
(parentPath.length === 0 && key === "boxSpanOwner") ||
|
|
164
|
+
(root === "raincloudPlot" && (
|
|
165
|
+
key === "ownerId" || parentPath[1] === "childIds" ||
|
|
166
|
+
parentPath[1] === "ownedChildIds"
|
|
167
|
+
))
|
|
168
|
+
) {
|
|
169
|
+
append(references, reference("mark", value, "markConfig", ownerId, path));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
append(references, reference(
|
|
173
|
+
"mark", config.statisticalReference?.source,
|
|
174
|
+
"markConfig", ownerId, ["statisticalReference", "source"]
|
|
175
|
+
));
|
|
176
|
+
for (const [index, id] of (
|
|
177
|
+
config.raincloudPlot?.ownedChildIds ?? []
|
|
178
|
+
).entries()) {
|
|
179
|
+
append(references, reference(
|
|
180
|
+
"mark", id, "markConfig", ownerId,
|
|
181
|
+
["raincloudPlot", "ownedChildIds", index]
|
|
182
|
+
));
|
|
183
|
+
}
|
|
184
|
+
const selection = config.labelAuthoring?.selection;
|
|
185
|
+
if (selection?.kind === "named") {
|
|
186
|
+
append(references, reference(
|
|
187
|
+
"selection", selection.id, "markConfig", ownerId,
|
|
188
|
+
["labelAuthoring", "selection", "id"]
|
|
189
|
+
));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return freezeOwned(references);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function collectGuideConfigReferences(program) {
|
|
196
|
+
const references = [];
|
|
197
|
+
for (const [family, configs] of Object.entries(program.guideConfigs ?? {})) {
|
|
198
|
+
collectKnownKeys(configs, [], (key, value, path, parentPath) => {
|
|
199
|
+
const ownerId = parentPath.length === 0 ? family : parentPath.join(".");
|
|
200
|
+
const relativePath = parentPath.length === 0
|
|
201
|
+
? path
|
|
202
|
+
: path.slice(parentPath.length);
|
|
203
|
+
if (key === "scale") {
|
|
204
|
+
append(references, reference(
|
|
205
|
+
"scale", value, family, ownerId, relativePath
|
|
206
|
+
));
|
|
207
|
+
} else if (key === "scales" && Array.isArray(value)) {
|
|
208
|
+
value.forEach((id, index) => append(references, reference(
|
|
209
|
+
"scale", id, family, ownerId, [...relativePath, index]
|
|
210
|
+
)));
|
|
211
|
+
} else if (key === "coordinate") {
|
|
212
|
+
append(references, reference(
|
|
213
|
+
"coordinate", value, family, ownerId, relativePath
|
|
214
|
+
));
|
|
215
|
+
} else if (key === "target") {
|
|
216
|
+
append(references, reference(
|
|
217
|
+
"mark", value, family, ownerId, relativePath
|
|
218
|
+
));
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return freezeOwned(references);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function collectSelectionReferences(program) {
|
|
226
|
+
const references = [];
|
|
227
|
+
for (const [id, config] of Object.entries(
|
|
228
|
+
program.materializationConfigs.selections ?? {}
|
|
229
|
+
)) {
|
|
230
|
+
append(references, reference(
|
|
231
|
+
"mark", config.target, "selection", id, ["target"]
|
|
232
|
+
));
|
|
233
|
+
}
|
|
234
|
+
for (const [id, config] of Object.entries(
|
|
235
|
+
program.materializationConfigs.highlights ?? {}
|
|
236
|
+
)) {
|
|
237
|
+
append(references, reference(
|
|
238
|
+
"mark", config.target, "highlight", id, ["target"]
|
|
239
|
+
));
|
|
240
|
+
append(references, reference(
|
|
241
|
+
"selection", config.selection, "highlight", id, ["selection"]
|
|
242
|
+
));
|
|
243
|
+
}
|
|
244
|
+
return freezeOwned(references);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function collectDataOwnerReferences(program) {
|
|
248
|
+
const references = [];
|
|
249
|
+
for (const [family, owners] of Object.entries(
|
|
250
|
+
program.materializationConfigs.data ?? {}
|
|
251
|
+
)) {
|
|
252
|
+
for (const [owner, config] of Object.entries(owners ?? {})) {
|
|
253
|
+
for (const key of ["current", "source", "previous"]) {
|
|
254
|
+
append(references, reference(
|
|
255
|
+
"data", config?.[key], "dataOwner", owner,
|
|
256
|
+
["data", family, owner, key]
|
|
257
|
+
));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return freezeOwned(references);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function collectCompositionReferences(program) {
|
|
265
|
+
const references = [];
|
|
266
|
+
const composition = program.compositionSpec;
|
|
267
|
+
if (composition?.type !== "facet") return freezeOwned(references);
|
|
268
|
+
append(references, reference(
|
|
269
|
+
"data", composition.facet.data, "composition", composition.id,
|
|
270
|
+
["facet", "data"]
|
|
271
|
+
));
|
|
272
|
+
append(references, reference(
|
|
273
|
+
"mark", composition.facet.repeat?.target, "composition", composition.id,
|
|
274
|
+
["facet", "repeat", "target"]
|
|
275
|
+
));
|
|
276
|
+
return freezeOwned(references);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function collectContextReferences(program) {
|
|
280
|
+
const references = [];
|
|
281
|
+
for (const [key, kind] of Object.entries({
|
|
282
|
+
currentData: "data",
|
|
283
|
+
currentScale: "scale",
|
|
284
|
+
currentCoordinate: "coordinate",
|
|
285
|
+
currentMark: "mark",
|
|
286
|
+
currentSelection: "selection"
|
|
287
|
+
})) {
|
|
288
|
+
append(references, reference(
|
|
289
|
+
kind, program.context[key], "context", "program", [key], "context"
|
|
290
|
+
));
|
|
291
|
+
}
|
|
292
|
+
return freezeOwned(references);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function referenceKey(value) {
|
|
296
|
+
return [
|
|
297
|
+
value.kind, value.id, value.ownerKind, value.ownerId,
|
|
298
|
+
canonicalResourcePath(value.path), value.strength
|
|
299
|
+
].join("\u0000");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function compareReferences(left, right) {
|
|
303
|
+
const leftKey = [
|
|
304
|
+
left.ownerKind, left.ownerId, canonicalResourcePath(left.path),
|
|
305
|
+
left.kind, left.id, left.strength
|
|
306
|
+
].join("\u0000");
|
|
307
|
+
const rightKey = [
|
|
308
|
+
right.ownerKind, right.ownerId, canonicalResourcePath(right.path),
|
|
309
|
+
right.kind, right.id, right.strength
|
|
310
|
+
].join("\u0000");
|
|
311
|
+
return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function collectResourceReferences(program, { kind, id }) {
|
|
315
|
+
if (!RESOURCE_KINDS.includes(kind)) {
|
|
316
|
+
throw new Error(`Unknown resource kind "${kind}".`);
|
|
317
|
+
}
|
|
318
|
+
const target = validateUserId(id, "Resource id");
|
|
319
|
+
const combined = [
|
|
320
|
+
...collectSemanticDataReferences(program),
|
|
321
|
+
...collectSemanticScaleReferences(program),
|
|
322
|
+
...collectSemanticCoordinateReferences(program),
|
|
323
|
+
...collectMarkConfigReferences(program),
|
|
324
|
+
...collectGuideConfigReferences(program),
|
|
325
|
+
...collectSelectionReferences(program),
|
|
326
|
+
...collectDataOwnerReferences(program),
|
|
327
|
+
...collectCompositionReferences(program),
|
|
328
|
+
...collectContextReferences(program)
|
|
329
|
+
].filter(value => value.kind === kind && value.id === target);
|
|
330
|
+
const unique = new Map(combined.map(value => [referenceKey(value), value]));
|
|
331
|
+
return freezeOwned([...unique.values()].sort(compareReferences));
|
|
332
|
+
}
|
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
|
]);
|
package/src/grammar/aggregate.js
CHANGED
|
@@ -7,6 +7,10 @@ import {
|
|
|
7
7
|
stableFiniteMean,
|
|
8
8
|
stableFiniteSum
|
|
9
9
|
} from "./numeric.js";
|
|
10
|
+
import {
|
|
11
|
+
confidenceCriticalValue,
|
|
12
|
+
normalizeConfidenceInterval
|
|
13
|
+
} from "./statistics/confidenceInterval.js";
|
|
10
14
|
|
|
11
15
|
export const SCALAR_AGGREGATE_OPERATIONS = Object.freeze([
|
|
12
16
|
"count", "sum", "mean", "median", "min", "max",
|
|
@@ -17,7 +21,9 @@ export const SCALAR_AGGREGATE_OPERATIONS = Object.freeze([
|
|
|
17
21
|
|
|
18
22
|
const SCALAR_OPERATIONS = new Set(SCALAR_AGGREGATE_OPERATIONS);
|
|
19
23
|
const NOMINAL_OPERATIONS = ["count", "distinct", "valid", "missing"];
|
|
20
|
-
const PARAMETERIZED_OPERATIONS = [
|
|
24
|
+
const PARAMETERIZED_OPERATIONS = [
|
|
25
|
+
"quantile", "first", "last", "ciLower", "ciUpper"
|
|
26
|
+
];
|
|
21
27
|
|
|
22
28
|
function nonEmptyString(value, label) {
|
|
23
29
|
if (typeof value !== "string" || value.length === 0) {
|
|
@@ -83,6 +89,21 @@ export function validateAggregate(value) {
|
|
|
83
89
|
order: value.order ?? "ascending"
|
|
84
90
|
};
|
|
85
91
|
}
|
|
92
|
+
if (value.op === "ciLower" || value.op === "ciUpper") {
|
|
93
|
+
const unknown = Object.keys(value).find(
|
|
94
|
+
key => !["op", "method", "level"].includes(key)
|
|
95
|
+
);
|
|
96
|
+
if (unknown !== undefined) {
|
|
97
|
+
throw new Error(`Unknown confidence aggregate property "${unknown}".`);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
op: value.op,
|
|
101
|
+
...normalizeConfidenceInterval(value, {
|
|
102
|
+
defaultMethod: "normal",
|
|
103
|
+
label: "Aggregate confidence interval"
|
|
104
|
+
})
|
|
105
|
+
};
|
|
106
|
+
}
|
|
86
107
|
throw new Error(`Unsupported aggregate "${value.op}".`);
|
|
87
108
|
}
|
|
88
109
|
|
|
@@ -194,39 +215,44 @@ export function aggregateScalarValues(values, operation) {
|
|
|
194
215
|
if (!Array.isArray(values)) {
|
|
195
216
|
throw new TypeError("Aggregate values must be an array.");
|
|
196
217
|
}
|
|
197
|
-
validateAggregate(operation);
|
|
198
|
-
|
|
218
|
+
const aggregate = validateAggregate(operation);
|
|
219
|
+
const scalarOperation = isScalarAggregate(aggregate)
|
|
220
|
+
? aggregate
|
|
221
|
+
: ["ciLower", "ciUpper"].includes(aggregate.op)
|
|
222
|
+
? aggregate.op
|
|
223
|
+
: undefined;
|
|
224
|
+
if (scalarOperation === undefined) {
|
|
199
225
|
throw new Error("Scalar aggregate calculation requires a scalar operation.");
|
|
200
226
|
}
|
|
201
227
|
|
|
202
|
-
if (
|
|
228
|
+
if (scalarOperation === "count") return values.length;
|
|
203
229
|
const valid = values.filter(value => !isMissing(value));
|
|
204
|
-
if (
|
|
205
|
-
if (
|
|
206
|
-
if (
|
|
230
|
+
if (scalarOperation === "valid") return valid.length;
|
|
231
|
+
if (scalarOperation === "missing") return values.length - valid.length;
|
|
232
|
+
if (scalarOperation === "distinct") return new Set(valid).size;
|
|
207
233
|
|
|
208
234
|
const finite = finiteValues(values);
|
|
209
235
|
if (finite.length === 0) return undefined;
|
|
210
|
-
if (
|
|
236
|
+
if (scalarOperation === "sum") {
|
|
211
237
|
return stableFiniteSum(finite, "Aggregate sum");
|
|
212
238
|
}
|
|
213
|
-
if (
|
|
214
|
-
if (
|
|
215
|
-
if (
|
|
216
|
-
if (
|
|
217
|
-
if (
|
|
239
|
+
if (scalarOperation === "min") return numericExtent(finite)[0];
|
|
240
|
+
if (scalarOperation === "max") return numericExtent(finite)[1];
|
|
241
|
+
if (scalarOperation === "median") return quantile(finite, 0.5);
|
|
242
|
+
if (scalarOperation === "q1") return quantile(finite, 0.25);
|
|
243
|
+
if (scalarOperation === "q3") return quantile(finite, 0.75);
|
|
218
244
|
|
|
219
245
|
const mean = stableFiniteMean(finite, "Aggregate mean");
|
|
220
|
-
if (
|
|
221
|
-
if (["variance", "varianceP", "stdev", "stdevP"].includes(
|
|
222
|
-
const population =
|
|
246
|
+
if (scalarOperation === "mean") return mean;
|
|
247
|
+
if (["variance", "varianceP", "stdev", "stdevP"].includes(scalarOperation)) {
|
|
248
|
+
const population = scalarOperation.endsWith("P");
|
|
223
249
|
if (!population && finite.length < 2) return undefined;
|
|
224
250
|
const kind = population ? "population" : "sample";
|
|
225
251
|
const deviation = stableFiniteDeviation(finite, {
|
|
226
252
|
sample: !population,
|
|
227
253
|
label: `Aggregate ${kind} deviation`
|
|
228
254
|
});
|
|
229
|
-
if (
|
|
255
|
+
if (scalarOperation.startsWith("stdev")) return deviation.deviation;
|
|
230
256
|
return deviation.squared === undefined
|
|
231
257
|
? requireFiniteResult(
|
|
232
258
|
deviation.deviation ** 2,
|
|
@@ -240,14 +266,25 @@ export function aggregateScalarValues(values, operation) {
|
|
|
240
266
|
divisor: Math.sqrt(finite.length),
|
|
241
267
|
label: "Aggregate standard error"
|
|
242
268
|
}).deviation;
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
|
|
269
|
+
if (scalarOperation === "stderr") return stderr;
|
|
270
|
+
const confidence = isScalarAggregate(aggregate)
|
|
271
|
+
? normalizeConfidenceInterval({}, {
|
|
272
|
+
defaultMethod: "normal",
|
|
273
|
+
label: "Aggregate confidence interval"
|
|
274
|
+
})
|
|
275
|
+
: aggregate;
|
|
276
|
+
const critical = confidenceCriticalValue({
|
|
277
|
+
method: confidence.method,
|
|
278
|
+
level: confidence.level,
|
|
279
|
+
degreesOfFreedom: finite.length - 1
|
|
280
|
+
});
|
|
281
|
+
if (scalarOperation === "ciLower") {
|
|
282
|
+
return requireFiniteResult(mean - critical * stderr, "Aggregate lower CI");
|
|
246
283
|
}
|
|
247
|
-
if (
|
|
248
|
-
return requireFiniteResult(mean +
|
|
284
|
+
if (scalarOperation === "ciUpper") {
|
|
285
|
+
return requireFiniteResult(mean + critical * stderr, "Aggregate upper CI");
|
|
249
286
|
}
|
|
250
|
-
throw new Error(`Unsupported scalar aggregate "${
|
|
287
|
+
throw new Error(`Unsupported scalar aggregate "${scalarOperation}".`);
|
|
251
288
|
}
|
|
252
289
|
|
|
253
290
|
export function aggregateRows(rows, field, operation) {
|
|
@@ -265,6 +302,9 @@ export function aggregateRows(rows, field, operation) {
|
|
|
265
302
|
? undefined
|
|
266
303
|
: quantile(values, aggregate.probability);
|
|
267
304
|
}
|
|
305
|
+
if (aggregate.op === "ciLower" || aggregate.op === "ciUpper") {
|
|
306
|
+
return aggregateScalarValues(rows.map(row => row[field]), aggregate);
|
|
307
|
+
}
|
|
268
308
|
return aggregateOrderedRows(rows, field, aggregate);
|
|
269
309
|
}
|
|
270
310
|
|
|
@@ -275,5 +315,8 @@ export function formatAggregateTitle(operation, field) {
|
|
|
275
315
|
if (aggregate.op === "quantile") {
|
|
276
316
|
return `quantile(${field}, ${aggregate.probability})`;
|
|
277
317
|
}
|
|
318
|
+
if (aggregate.op === "ciLower" || aggregate.op === "ciUpper") {
|
|
319
|
+
return `${aggregate.op}(${field}, ${aggregate.method}, ${aggregate.level})`;
|
|
320
|
+
}
|
|
278
321
|
return `${aggregate.op}(${field}, ${aggregate.orderBy} ${aggregate.order})`;
|
|
279
322
|
}
|
package/src/grammar/arcs.js
CHANGED
|
@@ -241,6 +241,61 @@ function quantitativeSectors(rows, layer, thetaScale, frame, innerRadiusRatio) {
|
|
|
241
241
|
}));
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
export function deriveMeasuredArcValues(rows, layer) {
|
|
245
|
+
const theta = requireArcLayer(layer);
|
|
246
|
+
const radius = layer.encoding?.radius;
|
|
247
|
+
if (!["nominal", "ordinal"].includes(theta.fieldType) || theta.aggregate !== undefined ||
|
|
248
|
+
!["count", "sum"].includes(radius?.aggregate) || radius.fieldType !== "quantitative") {
|
|
249
|
+
throw new Error("Measured Arc requires equal-angle categorical theta and count or sum radius.");
|
|
250
|
+
}
|
|
251
|
+
if (radius.aggregate === "count" && Object.hasOwn(radius, "field")) {
|
|
252
|
+
throw new Error("Measured count radius does not accept a field.");
|
|
253
|
+
}
|
|
254
|
+
const categories = readNominalField(rows, theta.field);
|
|
255
|
+
const measures = radius.aggregate === "count" ? rows.map(() => 1)
|
|
256
|
+
: readQuantitativeField(rows, radius.field);
|
|
257
|
+
if (measures.some(value => value < 0)) {
|
|
258
|
+
throw new RangeError("Measured Arc radius inputs must be non-negative.");
|
|
259
|
+
}
|
|
260
|
+
const colors = colorValues(rows, layer.encoding?.color);
|
|
261
|
+
const groups = new Map();
|
|
262
|
+
for (const [index, category] of categories.entries()) {
|
|
263
|
+
const group = groups.get(category) ?? { key: category, sourceIndices: [], values: [], colors: [] };
|
|
264
|
+
group.sourceIndices.push(index);
|
|
265
|
+
group.values.push(measures[index]);
|
|
266
|
+
group.colors.push(colors[index]);
|
|
267
|
+
groups.set(category, group);
|
|
268
|
+
}
|
|
269
|
+
const result = [...groups.values()].map(group => {
|
|
270
|
+
const distinctColors = [...new Set(group.colors)];
|
|
271
|
+
if (distinctColors.length > 1) {
|
|
272
|
+
throw new Error(`Measured Arc category "${group.key}" must resolve to one color value.`);
|
|
273
|
+
}
|
|
274
|
+
return { key: group.key, theta: group.key, count: group.sourceIndices.length,
|
|
275
|
+
radius: stableFiniteSum(group.values, "Measured Arc radius aggregate"),
|
|
276
|
+
color: distinctColors[0], sourceIndices: group.sourceIndices };
|
|
277
|
+
});
|
|
278
|
+
if (!result.some(group => group.radius > 0)) {
|
|
279
|
+
throw new Error("Measured Arc radius requires at least one positive category aggregate.");
|
|
280
|
+
}
|
|
281
|
+
return cloneAndFreeze(result);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function measuredRadialSectors(rows, layer, thetaScale, radiusScale) {
|
|
285
|
+
const items = deriveMeasuredArcValues(rows, layer);
|
|
286
|
+
const centers = mapOrdinalPositionValues(items.map(item => item.theta), thetaScale);
|
|
287
|
+
const radii = mapContinuousScaleValues(items.map(item => item.radius), radiusScale);
|
|
288
|
+
const halfBand = (Math.sign(thetaScale.step) || 1) * thetaScale.bandwidth / 2;
|
|
289
|
+
const byCategory = new Map(items.map((item, index) => [item.theta, {
|
|
290
|
+
...item, startTheta: centers[index] - halfBand, endTheta: centers[index] + halfBand,
|
|
291
|
+
innerRadius: radiusScale.range[0], outerRadius: radii[index]
|
|
292
|
+
}]));
|
|
293
|
+
return thetaScale.domain.flatMap(category => {
|
|
294
|
+
const item = byCategory.get(category);
|
|
295
|
+
return item === undefined || item.radius === 0 ? [] : [item];
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
244
299
|
function radialSectors(rows, layer, thetaScale, radiusScale) {
|
|
245
300
|
const theta = layer.encoding.theta;
|
|
246
301
|
const radius = layer.encoding?.radius;
|
|
@@ -303,7 +358,9 @@ export function deriveArcSectors(rows, layer, {
|
|
|
303
358
|
) {
|
|
304
359
|
throw new RangeError("Arc innerRadius must be from 0 (inclusive) to 1 (exclusive).");
|
|
305
360
|
}
|
|
306
|
-
const sectors =
|
|
361
|
+
const sectors = radiusScale?.radialMapping !== undefined
|
|
362
|
+
? measuredRadialSectors(rows, layer, requireBandScale(thetaScale, `Arc mark "${layer.id}" theta`), radiusScale)
|
|
363
|
+
: theta.fieldType === "quantitative"
|
|
307
364
|
? quantitativeSectors(
|
|
308
365
|
rows,
|
|
309
366
|
layer,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { validateKeys, validateNonEmptyString } from "../core/validation.js";
|
|
2
|
+
import { isPlainObject } from "../core/immutable.js";
|
|
3
|
+
import { readQuantitativeField } from "./scales/fields.js";
|
|
4
|
+
|
|
5
|
+
export function validateAreaMissing(value = "error") {
|
|
6
|
+
if (!["error", "break"].includes(value)) {
|
|
7
|
+
throw new Error('Area missing must be "error" or "break".');
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function normalizeAreaBound(bound) {
|
|
13
|
+
if (typeof bound === "string") return { field: validateNonEmptyString(bound, "Area endpoint field") };
|
|
14
|
+
if (!isPlainObject(bound)) throw new TypeError("Area bound must be a field or datum object.");
|
|
15
|
+
validateKeys(bound, ["datum"], "Area bound");
|
|
16
|
+
if (!Number.isFinite(bound.datum)) throw new TypeError("Area datum must be finite.");
|
|
17
|
+
return { datum: bound.datum };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function readAreaEndpoint(rows, encoding, missing = "error") {
|
|
21
|
+
if (Object.hasOwn(encoding, "datum")) {
|
|
22
|
+
if (Object.hasOwn(encoding, "field") || !Number.isFinite(encoding.datum) || encoding.fieldType !== "quantitative") {
|
|
23
|
+
throw new TypeError("Area endpoint requires exactly one finite quantitative datum or field.");
|
|
24
|
+
}
|
|
25
|
+
return rows.map(() => encoding.datum);
|
|
26
|
+
}
|
|
27
|
+
if (missing !== "break") return readQuantitativeField(rows, encoding.field);
|
|
28
|
+
validateNonEmptyString(encoding.field, "Area endpoint field");
|
|
29
|
+
if (rows.length > 0 && !rows.some(row => Object.hasOwn(row, encoding.field))) {
|
|
30
|
+
throw new Error(`Area endpoint field "${encoding.field}" does not exist.`);
|
|
31
|
+
}
|
|
32
|
+
return rows.map((row, index) => {
|
|
33
|
+
const value = row[encoding.field];
|
|
34
|
+
if (value == null) return null;
|
|
35
|
+
if (!Number.isFinite(value)) {
|
|
36
|
+
throw new TypeError(`Field "${encoding.field}" must contain a finite number at row ${index}.`);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function validateAreaEndpointPair(primary, secondary) {
|
|
43
|
+
if (primary?.datum !== undefined && secondary?.datum !== undefined) {
|
|
44
|
+
throw new Error("Area range requires at least one field endpoint.");
|
|
45
|
+
}
|
|
46
|
+
}
|