ggaction 0.0.21 → 0.0.23
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 +90 -0
- package/README.md +83 -90
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/package.json +11 -4
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +1 -0
- package/src/actions/boxPlots/components.js +1 -96
- package/src/actions/boxPlots/create.js +6 -1
- package/src/actions/boxPlots/edit.js +8 -0
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +1 -1
- package/src/actions/canvas/actions.js +2 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +2 -1
- package/src/actions/canvas/plotOrder.js +14 -0
- package/src/actions/charts/area.js +1 -1
- package/src/actions/charts/bar.js +4 -4
- package/src/actions/charts/density.js +1 -1
- package/src/actions/charts/ecdf.js +1 -1
- package/src/actions/charts/endpoints.js +54 -5
- package/src/actions/charts/heatmap.js +1 -1
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +1 -1
- package/src/actions/charts/index.js +2 -1
- package/src/actions/charts/interval-regression.js +3 -3
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/radar.js +63 -3
- package/src/actions/charts/raincloud.js +37 -6
- package/src/actions/charts/rug-strip.js +2 -2
- package/src/actions/charts/scatter.js +3 -3
- package/src/actions/charts/text.js +1 -1
- package/src/actions/composition/actions.js +10 -3
- package/src/actions/coordinates/actions.js +2 -0
- package/src/actions/data/basic.js +6 -0
- package/src/actions/data/derived.js +6 -2
- package/src/actions/data/edit.js +22 -36
- package/src/actions/data/filter.js +1 -376
- package/src/actions/data/flatten.js +19 -0
- package/src/actions/data/index.js +6 -0
- package/src/actions/data/lookup.js +50 -0
- package/src/actions/data/raincloudFilter.js +56 -0
- package/src/actions/data/shared.js +10 -0
- package/src/actions/data/timeUnit.js +1 -1
- package/src/actions/distributions/revision.js +3 -1
- package/src/actions/encodings/angle.js +2 -1
- package/src/actions/encodings/appearance.js +38 -13
- package/src/actions/encodings/channels.js +14 -3
- package/src/actions/encodings/color/continuous.js +26 -10
- package/src/actions/encodings/color/index.js +50 -20
- package/src/actions/encodings/color/policy.js +57 -4
- package/src/actions/encodings/parallel.js +4 -0
- package/src/actions/encodings/position/policies/bar.js +10 -6
- package/src/actions/encodings/position/policies/line.js +1 -152
- package/src/actions/encodings/position/resolve.js +4 -1
- package/src/actions/encodings/ranged.js +46 -0
- package/src/actions/encodings/remove.js +1 -1
- package/src/actions/encodings/ruleAppearance.js +7 -6
- package/src/actions/encodings/shared.js +14 -2
- package/src/actions/encodings/stroke.js +20 -14
- package/src/actions/encodings/strokeDash.js +9 -11
- package/src/actions/encodings/text.js +30 -2
- package/src/actions/errorBars/edit.js +24 -4
- package/src/actions/facets/actions.js +1 -793
- package/src/actions/facets/derive.js +34 -5
- package/src/actions/facets/replay.js +1 -38
- package/src/actions/gradientPlots/components.js +21 -9
- package/src/actions/gradientPlots/create.js +4 -2
- package/src/actions/gradientPlots/edit.js +18 -1
- package/src/actions/gradientPlots/materialize.js +35 -36
- package/src/actions/gradientPlots/options.js +1 -1
- package/src/actions/gradientPlots/paint.js +13 -7
- package/src/actions/guides/applicability.js +2 -0
- package/src/actions/guides/axes/axes.js +39 -0
- package/src/actions/guides/axes/axis.js +9 -5
- package/src/actions/guides/axes/edit.js +11 -8
- package/src/actions/guides/axes/labels.js +24 -8
- package/src/actions/guides/axes/parallel/policy.js +3 -5
- package/src/actions/guides/axes/parallel/resolve.js +4 -3
- package/src/actions/guides/axes/policy.js +22 -0
- package/src/actions/guides/axes/tickGroups.js +4 -2
- package/src/actions/guides/axes/ticks.js +22 -4
- package/src/actions/guides/axes/titles.js +19 -6
- package/src/actions/guides/facadeAxes.js +3 -0
- package/src/actions/guides/grids/grid.js +3 -1
- package/src/actions/guides/grids/resolve.js +7 -11
- package/src/actions/guides/guides.js +21 -1
- package/src/actions/guides/legends/categorical/actions.js +1 -515
- package/src/actions/guides/legends/categorical/components.js +1 -4
- package/src/actions/guides/legends/categorical/layout.js +5 -2
- package/src/actions/guides/legends/categorical/options.js +4 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -3
- package/src/actions/guides/legends/categorical/symbols.js +3 -3
- package/src/actions/guides/legends/colorSize.js +2 -0
- package/src/actions/guides/legends/continuous/common.js +1 -1
- package/src/actions/guides/legends/continuous/gradient.js +1 -360
- package/src/actions/guides/legends/continuous/interval.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +15 -1
- package/src/actions/guides/legends/edit.js +1 -1
- package/src/actions/guides/legends/focused.js +1 -1
- package/src/actions/guides/legends/lane.js +1 -1
- package/src/actions/guides/legends/sampling.js +1 -140
- package/src/actions/guides/legends/strokeWidth.js +3 -2
- package/src/actions/guides/legends/transition.js +19 -2
- package/src/actions/guides/polar/axes/facade.js +5 -4
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/resolve.js +2 -1
- package/src/actions/guides/tickValues.js +7 -7
- package/src/actions/marks/arc/actions.js +5 -7
- package/src/actions/marks/area/actions.js +9 -3
- package/src/actions/marks/bar/create.js +4 -2
- package/src/actions/marks/bar/edit.js +9 -6
- package/src/actions/marks/bar/materialize.js +5 -3
- package/src/actions/marks/dependentReferences.js +87 -0
- package/src/actions/marks/index.js +2 -0
- package/src/actions/marks/lifecycle.js +2 -1
- package/src/actions/marks/line/actions.js +24 -6
- package/src/actions/marks/line/materialize.js +7 -3
- package/src/actions/marks/point/create.js +5 -4
- package/src/actions/marks/point/edit.js +6 -3
- package/src/actions/marks/point/jitter.js +6 -6
- package/src/actions/marks/point/materialize.js +36 -39
- package/src/actions/marks/rect/actions.js +16 -4
- package/src/actions/marks/references.js +183 -35
- package/src/actions/marks/rule/actions.js +20 -9
- package/src/actions/marks/shared.js +2 -1
- package/src/actions/marks/text/actions.js +10 -3
- package/src/actions/marks/tick/actions.js +11 -2
- package/src/actions/primitives/editGraphics.js +28 -3
- package/src/actions/primitives/index.js +9 -0
- package/src/actions/primitives/semanticAction.js +13 -7
- package/src/actions/primitives/semanticValidation/layer.js +20 -0
- package/src/actions/primitives/semanticValidation/scale.js +8 -1
- package/src/actions/regression/create.js +6 -1
- package/src/actions/regression/edit.js +10 -1
- package/src/actions/scales/channels.js +1 -1
- package/src/actions/scales/consumers/common.js +19 -1
- package/src/actions/scales/consumers/families.js +2 -2
- package/src/actions/scales/consumers/seriesLayout.js +4 -3
- package/src/actions/scales/edit.js +11 -0
- package/src/actions/scales/editPolicy.js +5 -3
- package/src/actions/scales/preview.js +2 -1
- package/src/actions/scales/quantitativeColor.js +2 -4
- package/src/actions/selection/actions.js +10 -1
- package/src/actions/theme/reconcile.js +37 -9
- package/src/core/compositionState.js +26 -7
- package/src/core/datasetReferences.js +17 -0
- package/src/core/facetAxisPolicy.js +30 -0
- package/src/core/immutable.js +1 -0
- package/src/core/resourceReferences.js +5 -3
- package/src/core/validation.js +4 -0
- package/src/core/vocabulary.js +24 -2
- package/src/grammar/aggregate.js +1 -0
- package/src/grammar/arcs.js +1 -1
- package/src/grammar/areaSeries.js +1 -1
- package/src/grammar/axisTextPlacement.js +1 -1
- package/src/grammar/bars/policy.js +2 -2
- package/src/grammar/curveCommands.js +35 -2
- package/src/grammar/datasetDependencies.js +97 -0
- package/src/grammar/datasetSchema.js +32 -2
- package/src/grammar/facetHeaders.js +1 -0
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +7 -5
- package/src/grammar/facets/legendIntent.js +6 -2
- package/src/grammar/facets/scales.js +27 -7
- package/src/grammar/flatten.js +85 -0
- package/src/grammar/histogram.js +13 -1
- package/src/grammar/horizon.js +1 -494
- package/src/grammar/jitter.js +2 -2
- package/src/grammar/legendSpacing.js +45 -0
- package/src/grammar/lineMissing.js +90 -0
- package/src/grammar/lineSeries.js +1 -376
- package/src/grammar/lookup.js +97 -0
- package/src/grammar/markFilter.js +4 -2
- package/src/grammar/markLabels.js +8 -3
- package/src/grammar/numeric.js +1 -1
- package/src/grammar/pathCommands.js +1 -1
- package/src/grammar/pathSeries.js +1 -109
- package/src/grammar/pointPolygon.js +81 -0
- package/src/grammar/pointShapes.js +15 -15
- package/src/grammar/rects.js +1 -1
- package/src/grammar/regression/derive.js +1 -195
- package/src/grammar/regression/parameters.js +7 -0
- package/src/grammar/scales/appearance.js +1 -1
- package/src/grammar/scales/color.js +1 -1
- package/src/grammar/scales/colorConsumers.js +1 -63
- package/src/grammar/scales/continuous.js +58 -1
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +3 -3
- package/src/grammar/schemas/graphicBounds.js +40 -12
- package/src/grammar/schemas/graphicClip.js +23 -0
- package/src/grammar/schemas/graphicTree.js +16 -0
- package/src/grammar/statisticalReference.js +22 -0
- package/src/grammar/text.js +65 -1
- package/src/grammar/ticks.js +92 -1
- package/src/grammar/timeUnit.js +9 -3
- package/src/grammar/transformTopology.js +3 -1
- package/src/grammar/transforms.js +24 -0
- package/src/inspection.js +1 -495
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -1
- package/src/layout/facetContentFlow.js +101 -0
- package/src/layout/facetHeaderTitles.js +1 -0
- package/src/layout/facetTopLane.js +29 -0
- package/src/layout/facets.js +1 -1
- package/src/layout/guideCollisions.js +9 -3
- package/src/layout/legendGroup.js +1 -1
- package/src/layout/legendItems.js +26 -10
- package/src/layout/legendSpacing.js +66 -0
- package/src/layout/plotDrawOrder.js +17 -0
- package/src/materialization/bars/histogram.js +8 -2
- package/src/materialization/bars/resolve.js +1 -1
- package/src/materialization/composition.js +1 -0
- package/src/materialization/compositionSnapshot.js +22 -2
- package/src/materialization/dataProvenance.js +1 -1
- package/src/materialization/facetContentBounds.js +96 -0
- package/src/materialization/facetGuides/layout.js +1 -1
- package/src/materialization/facetGuides/placement.js +39 -11
- package/src/materialization/facetGuides/preparation.js +43 -24
- package/src/materialization/facetHeaders.js +1 -1
- package/src/materialization/facetTopLane.js +25 -0
- package/src/materialization/facets.js +1 -1
- package/src/materialization/graphicHierarchy.js +30 -1
- package/src/materialization/graphicTranslation.js +2 -0
- package/src/materialization/guides/layout.js +1 -1
- package/src/materialization/guides/resources.js +1 -1
- package/src/materialization/legends.js +1 -1
- package/src/materialization/markClip.js +26 -0
- package/src/materialization/marks/capabilities.js +3 -2
- package/src/materialization/marks/grouping.js +7 -5
- package/src/materialization/marks/index.js +4 -2
- package/src/materialization/marks/policies.js +4 -0
- package/src/materialization/plotDrawOrder.js +44 -0
- package/src/materialization/rect.js +11 -3
- package/src/materialization/scaleGuideDependencies.js +13 -5
- package/src/materialization/selection/items/common.js +1 -1
- package/src/materialization/selection/items/path.js +1 -0
- package/src/materialization/selection/policies/index.js +2 -1
- package/src/materialization/text.js +10 -5
- package/src/mcp/cli.js +0 -0
- package/src/persistence/validation.js +114 -18
- package/src/persistence.js +4 -2
- package/src/renderers/canvas/index.js +16 -0
- package/src/renderers/canvas/isolated.js +11 -1
- package/src/renderers/canvas/native.js +7 -0
- package/src/renderers/canvas/scene.js +66 -0
- package/src/renderers/canvas/text.js +3 -2
- package/src/renderers/svg.js +1 -617
- package/src/renderers/svgSerialization.js +643 -0
- package/src/renderers/transitionScene.js +67 -0
- package/src/scene.js +89 -0
- package/src/selectors/axes.js +1 -1
- package/src/selectors/datasets.js +1 -0
- package/src/selectors/grids.js +9 -0
- package/src/selectors/markClip.js +28 -0
- package/src/selectors/raincloud.js +7 -0
- package/src/version.js +1 -1
- package/types/basic.d.ts +7 -0
- package/types/index.d.ts +8 -0
- package/types/program.d.ts +286 -70
- package/types/scene.d.ts +35 -0
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
resolveEffectiveThemeTokens,
|
|
7
7
|
setThemeStateOverrides
|
|
8
8
|
} from "./state.js";
|
|
9
|
+
import { isOwned } from "../../core/immutable.js";
|
|
9
10
|
import { findLayer } from "../../selectors/layers.js";
|
|
10
11
|
import { reconcileFollowingRegressions } from "../regression/follow.js";
|
|
11
12
|
|
|
@@ -285,6 +286,14 @@ function recolorGraphic(
|
|
|
285
286
|
targetTokens,
|
|
286
287
|
overrides
|
|
287
288
|
) {
|
|
289
|
+
if (id === `${program.compositionSpec?.id}-headers`) {
|
|
290
|
+
const headers = program.materializationConfigs.facets?.[program.compositionSpec.id]?.headers;
|
|
291
|
+
if (headers?.column?.title !== undefined || headers?.row?.title !== undefined) {
|
|
292
|
+
// Role titles and category labels have independently owned paint. Their
|
|
293
|
+
// reconciled header configs already materialized the complete collection.
|
|
294
|
+
return program;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
288
297
|
if (graphic.items === undefined) {
|
|
289
298
|
const mapped = mapGraphicProperties(
|
|
290
299
|
program,
|
|
@@ -663,18 +672,27 @@ function visitTrace(node, visitor) {
|
|
|
663
672
|
for (const child of node.children ?? []) visitTrace(child, visitor);
|
|
664
673
|
}
|
|
665
674
|
|
|
666
|
-
|
|
675
|
+
// Completed immutable action branches never acquire new removals. Retain only
|
|
676
|
+
// their distinct reset prefixes so later themed edits do not rescan old facet
|
|
677
|
+
// materialization traces. Prefix deletion commutes; authored overrides are still
|
|
678
|
+
// applied in their original top-level action order.
|
|
679
|
+
const removedResources = new WeakMap();
|
|
680
|
+
|
|
681
|
+
function removedResourcePrefixes(node) {
|
|
682
|
+
const cached = removedResources.get(node);
|
|
683
|
+
if (cached !== undefined) return cached;
|
|
684
|
+
const prefixes = new Set();
|
|
667
685
|
visitTrace(node, current => {
|
|
668
686
|
if (current.op === "editGraphics" && current.args.remove === true &&
|
|
669
687
|
typeof current.args.target === "string") {
|
|
670
|
-
|
|
688
|
+
prefixes.add(`g:${current.args.target}.`);
|
|
671
689
|
}
|
|
672
690
|
if (current.op !== "editSemantic" || current.args.remove !== true ||
|
|
673
691
|
typeof current.args.property !== "string") return;
|
|
674
692
|
const mark = /^layer\[([^\]]+)\]$/u.exec(current.args.property);
|
|
675
693
|
if (mark !== null) {
|
|
676
|
-
|
|
677
|
-
|
|
694
|
+
prefixes.add(`c:marks.${mark[1]}.`);
|
|
695
|
+
prefixes.add(`g:${mark[1]}.`);
|
|
678
696
|
return;
|
|
679
697
|
}
|
|
680
698
|
const axis = /^guide\.axis\.([A-Za-z0-9_-]+)$/u
|
|
@@ -682,21 +700,28 @@ function clearRemovedResources(overrides, node) {
|
|
|
682
700
|
if (axis !== null) {
|
|
683
701
|
const prefix = axis[1] === "radius" ? "radial" : axis[1];
|
|
684
702
|
const graphicPrefix = ["x", "y", "theta", "radius"].includes(axis[1]) ? `${prefix}Axis` : `axis_${axis[1]}__`;
|
|
685
|
-
|
|
686
|
-
|
|
703
|
+
prefixes.add(`c:guides.axis.${axis[1]}.`);
|
|
704
|
+
prefixes.add(`g:${graphicPrefix}`);
|
|
687
705
|
return;
|
|
688
706
|
}
|
|
689
707
|
const grid = /^guide\.grid\.([^\.]+)$/u.exec(current.args.property);
|
|
690
708
|
if (grid !== null) {
|
|
691
|
-
|
|
692
|
-
|
|
709
|
+
prefixes.add(`c:guides.grid.${grid[1]}.`);
|
|
710
|
+
prefixes.add(`g:${grid[1]}Grid`);
|
|
693
711
|
return;
|
|
694
712
|
}
|
|
695
713
|
const legend = /^guide\.legend\.([^\.]+)$/u.exec(current.args.property);
|
|
696
714
|
if (legend !== null) {
|
|
697
|
-
|
|
715
|
+
prefixes.add(`c:guides.legend.${legend[1]}.`);
|
|
698
716
|
}
|
|
699
717
|
});
|
|
718
|
+
const result = [...prefixes];
|
|
719
|
+
if (isOwned(node)) removedResources.set(node, result);
|
|
720
|
+
return result;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function clearRemovedResources(overrides, node) {
|
|
724
|
+
for (const prefix of removedResourcePrefixes(node)) clearPrefix(overrides, prefix);
|
|
700
725
|
}
|
|
701
726
|
|
|
702
727
|
function clearLocalResets(overrides, program, node) {
|
|
@@ -970,6 +995,9 @@ function addFacetOverrides(overrides, program, args) {
|
|
|
970
995
|
["color", "fill"],
|
|
971
996
|
["fontFamily", "fontFamily"]
|
|
972
997
|
]) {
|
|
998
|
+
if (Object.hasOwn(args.title ?? {}, property)) {
|
|
999
|
+
overrides.add(`c:facets.${id}.headers.${owner}.title.${property}`);
|
|
1000
|
+
}
|
|
973
1001
|
if (!Object.hasOwn(args, property)) continue;
|
|
974
1002
|
overrides.add(`c:facets.${id}.headers.${owner}.${property}`);
|
|
975
1003
|
overrides.add(`g:${id}-headers.${graphicProperty}`);
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { normalizeFacetAxes } from "./facetAxisPolicy.js";
|
|
2
|
+
import { isOwned, isPlainObject } from "./immutable.js";
|
|
2
3
|
import { ownProgramState } from "./programState.js";
|
|
3
4
|
import {
|
|
4
5
|
FACET_SCALE_CHANNELS,
|
|
5
|
-
FACET_SCALE_RESOLUTIONS
|
|
6
|
+
FACET_SCALE_RESOLUTIONS,
|
|
7
|
+
FACET_TOP_LANE_MODES,
|
|
8
|
+
DEFAULT_FACET_TOP_LANE
|
|
6
9
|
} from "./vocabulary.js";
|
|
7
10
|
|
|
11
|
+
// Primitive graphics/trace edits retain both immutable owners. Validate their
|
|
12
|
+
// relationship once, rather than scanning every facet cell on every primitive.
|
|
13
|
+
const validatedCompositions = new WeakMap();
|
|
14
|
+
|
|
8
15
|
function validateFacetSpec(compositionSpec) {
|
|
9
16
|
if (compositionSpec.direction !== undefined) {
|
|
10
17
|
throw new Error("Facet compositionSpec does not use direction.");
|
|
@@ -37,6 +44,7 @@ function validateFacetSpec(compositionSpec) {
|
|
|
37
44
|
}
|
|
38
45
|
const grid = compositionSpec.facet.grid;
|
|
39
46
|
const repeat = compositionSpec.facet.repeat;
|
|
47
|
+
if (grid !== undefined && compositionSpec.spacing === "content") throw new Error("Content flow requires a wrapped facet.");
|
|
40
48
|
if (grid !== undefined && repeat !== undefined) {
|
|
41
49
|
throw new Error("Facet compositionSpec cannot combine grid and repeat recipes.");
|
|
42
50
|
}
|
|
@@ -198,10 +206,10 @@ function validateFacetSpec(compositionSpec) {
|
|
|
198
206
|
);
|
|
199
207
|
}
|
|
200
208
|
const guides = compositionSpec.facet.guides;
|
|
209
|
+
normalizeFacetAxes(guides?.axes, "each", { stored: true });
|
|
201
210
|
if (
|
|
202
211
|
!isPlainObject(guides) ||
|
|
203
212
|
Object.keys(guides).some(key => !["axes", "legend"].includes(key)) ||
|
|
204
|
-
!["each", "outer"].includes(guides.axes) ||
|
|
205
213
|
(![false, "shared"].includes(guides.legend) && !isPlainObject(guides.legend))
|
|
206
214
|
) {
|
|
207
215
|
throw new Error(
|
|
@@ -260,6 +268,7 @@ function validateLayout(compositionSpec) {
|
|
|
260
268
|
}
|
|
261
269
|
|
|
262
270
|
export function ownCompositionSpec(compositionSpec, children) {
|
|
271
|
+
if (validatedCompositions.get(compositionSpec)?.has(children)) return compositionSpec;
|
|
263
272
|
const childIds = Object.keys(children);
|
|
264
273
|
if (compositionSpec === undefined) {
|
|
265
274
|
if (childIds.length > 0) {
|
|
@@ -272,7 +281,7 @@ export function ownCompositionSpec(compositionSpec, children) {
|
|
|
272
281
|
}
|
|
273
282
|
const allowed = [
|
|
274
283
|
"id", "type", "direction", "children", "columns", "gap", "align",
|
|
275
|
-
"padding", "facet", "spacing"
|
|
284
|
+
"padding", "facet", "spacing", "topLane"
|
|
276
285
|
];
|
|
277
286
|
const unknown = Object.keys(compositionSpec).find(key => !allowed.includes(key));
|
|
278
287
|
if (unknown !== undefined) {
|
|
@@ -282,8 +291,13 @@ export function ownCompositionSpec(compositionSpec, children) {
|
|
|
282
291
|
throw new TypeError("compositionSpec.id must be a non-empty string.");
|
|
283
292
|
}
|
|
284
293
|
const facet = compositionSpec.type === "facet";
|
|
285
|
-
if (compositionSpec.spacing !== undefined && (!facet || !["plot", "canvas"].includes(compositionSpec.spacing))) {
|
|
286
|
-
throw new Error("compositionSpec.spacing requires a facet and canvas or
|
|
294
|
+
if (compositionSpec.spacing !== undefined && (!facet || !["plot", "canvas", "content"].includes(compositionSpec.spacing))) {
|
|
295
|
+
throw new Error("compositionSpec.spacing requires a facet and canvas, plot, or content spacing.");
|
|
296
|
+
}
|
|
297
|
+
if (compositionSpec.topLane !== undefined && (!facet ||
|
|
298
|
+
!FACET_TOP_LANE_MODES.includes(compositionSpec.topLane) ||
|
|
299
|
+
compositionSpec.topLane === DEFAULT_FACET_TOP_LANE || compositionSpec.spacing !== "plot")) {
|
|
300
|
+
throw new Error('compositionSpec.topLane requires a facet with plot spacing and canonical shared intent.');
|
|
287
301
|
}
|
|
288
302
|
if (compositionSpec.type !== undefined && !facet) {
|
|
289
303
|
throw new Error(`Unknown compositionSpec type "${compositionSpec.type}".`);
|
|
@@ -301,5 +315,10 @@ export function ownCompositionSpec(compositionSpec, children) {
|
|
|
301
315
|
throw new Error("Concat compositionSpec does not accept facet properties.");
|
|
302
316
|
}
|
|
303
317
|
validateLayout(compositionSpec);
|
|
304
|
-
|
|
318
|
+
const owned = ownProgramState(compositionSpec);
|
|
319
|
+
if (isOwned(children)) {
|
|
320
|
+
if (!validatedCompositions.has(owned)) validatedCompositions.set(owned, new WeakSet());
|
|
321
|
+
validatedCompositions.get(owned).add(children);
|
|
322
|
+
}
|
|
323
|
+
return owned;
|
|
305
324
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Stored dataset reference paths have one owner across resource and replay policies.
|
|
2
|
+
export function datasetInputReferences(dataset) {
|
|
3
|
+
const refs = dataset.source === undefined ? [] : [{ id: dataset.source, path: ["source"] }];
|
|
4
|
+
if (dataset.transform?.[0]?.type === "lookup") {
|
|
5
|
+
refs.push({ id: dataset.transform[0].lookup.data, path: ["transform", 0, "lookup", "data"] });
|
|
6
|
+
}
|
|
7
|
+
return refs;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function datasetInputIds(dataset) {
|
|
11
|
+
return [...new Set(datasetInputReferences(dataset).map(ref => ref.id))];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function rebindTransformDatasetInputs(transform, replacements) {
|
|
15
|
+
if (transform.type !== "lookup" || !replacements.has(transform.lookup.data)) return transform;
|
|
16
|
+
return { ...transform, lookup: { ...transform.lookup, data: replacements.get(transform.lookup.data) } };
|
|
17
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { cloneAndFreeze, isPlainObject } from "./immutable.js";
|
|
2
|
+
|
|
3
|
+
export function normalizeFacetAxes(value, previous = "each", { stored = false } = {}) {
|
|
4
|
+
if (value === undefined && !stored) value = "each";
|
|
5
|
+
if (["each", "outer"].includes(value)) return value;
|
|
6
|
+
if (!isPlainObject(value) || Object.keys(value).length === 0 ||
|
|
7
|
+
Object.keys(value).some(key => !["x", "y"].includes(key)) ||
|
|
8
|
+
Object.values(value).some(mode => !["each", "outer"].includes(mode)) ||
|
|
9
|
+
stored && (!Object.hasOwn(value, "x") || !Object.hasOwn(value, "y"))) {
|
|
10
|
+
throw new Error('Facet axes must be each, outer, or a non-empty x/y policy object.');
|
|
11
|
+
}
|
|
12
|
+
return cloneAndFreeze({ x: value.x ?? facetAxisMode(previous, "x"), y: value.y ?? facetAxisMode(previous, "y") });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function facetAxisMode(policy, channel) {
|
|
16
|
+
return typeof policy === "string" ? policy : policy[channel];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function hasOuterFacetAxes(policy) {
|
|
20
|
+
return ["x", "y"].some(channel => facetAxisMode(policy, channel) === "outer");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function validateFacetAxisPolicy(policy, family, repeat = false) {
|
|
24
|
+
if (family !== "cartesian" && (hasOuterFacetAxes(policy) || isPlainObject(policy))) {
|
|
25
|
+
throw new Error("Polar and Parallel facets do not support outer axes or per-channel axis policies.");
|
|
26
|
+
}
|
|
27
|
+
if (repeat && hasOuterFacetAxes(policy)) {
|
|
28
|
+
throw new Error("repeatCharts does not promote axes across different repeated fields.");
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/core/immutable.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { freezeOwned, isPlainObject } from "./immutable.js";
|
|
2
2
|
import { validateUserId } from "./identifiers.js";
|
|
3
|
+
import {datasetInputReferences} from "./datasetReferences.js";
|
|
3
4
|
|
|
4
5
|
export const RESOURCE_KINDS = Object.freeze([
|
|
5
6
|
"data", "scale", "coordinate", "mark", "selection"
|
|
@@ -71,8 +72,8 @@ export function collectSemanticDataReferences(program) {
|
|
|
71
72
|
append(references, reference("data", layer.data, "layer", layer.id, ["data"]));
|
|
72
73
|
}
|
|
73
74
|
for (const dataset of program.semanticSpec.datasets) {
|
|
74
|
-
append(references,
|
|
75
|
-
"data",
|
|
75
|
+
for(const input of datasetInputReferences(dataset))append(references,reference(
|
|
76
|
+
"data",input.id,"dataset",dataset.id,input.path
|
|
76
77
|
));
|
|
77
78
|
}
|
|
78
79
|
return freezeOwned(references);
|
|
@@ -167,7 +168,8 @@ export function collectMarkConfigReferences(program) {
|
|
|
167
168
|
(root === "errorBandBoundary" && key === "owner") ||
|
|
168
169
|
(parentPath.length === 0 && key === "boxSpanOwner") ||
|
|
169
170
|
(root === "raincloudPlot" && (
|
|
170
|
-
key === "ownerId" ||
|
|
171
|
+
key === "ownerId" ||
|
|
172
|
+
(parentPath[1] === "childIds" && config.raincloudPlot.ownedChildIds?.includes(value)) ||
|
|
171
173
|
parentPath[1] === "ownedChildIds"
|
|
172
174
|
))
|
|
173
175
|
) {
|
package/src/core/validation.js
CHANGED
|
@@ -133,3 +133,7 @@ export function validateTextStyle(config, label) {
|
|
|
133
133
|
validateNonEmptyString(config.fontFamily, `${label} fontFamily`);
|
|
134
134
|
validateFontWeight(config.fontWeight, `${label} fontWeight`);
|
|
135
135
|
}
|
|
136
|
+
export function validateBoolean(value, label) {
|
|
137
|
+
if (typeof value !== "boolean") throw new TypeError(`${label} must be a boolean.`);
|
|
138
|
+
return value;
|
|
139
|
+
}
|
package/src/core/vocabulary.js
CHANGED
|
@@ -41,7 +41,9 @@ export const ENCODING_CHANNELS = Object.freeze([
|
|
|
41
41
|
"group",
|
|
42
42
|
"pathOrder",
|
|
43
43
|
"opacity",
|
|
44
|
-
"text"
|
|
44
|
+
"text",
|
|
45
|
+
"dx",
|
|
46
|
+
"dy"
|
|
45
47
|
]);
|
|
46
48
|
|
|
47
49
|
const CARTESIAN_MARK_TYPES = Object.freeze([
|
|
@@ -118,7 +120,7 @@ export const POSITION_CHANNEL_DEFINITIONS = Object.freeze({
|
|
|
118
120
|
|
|
119
121
|
export const SCALED_ENCODING_CHANNELS = Object.freeze(
|
|
120
122
|
ENCODING_CHANNELS.filter(channel =>
|
|
121
|
-
!["angle", "group", "pathOrder", "text"].includes(channel)
|
|
123
|
+
!["angle", "group", "pathOrder", "text", "dx", "dy"].includes(channel)
|
|
122
124
|
)
|
|
123
125
|
);
|
|
124
126
|
|
|
@@ -176,6 +178,9 @@ export const FACET_SCALE_RESOLUTIONS = Object.freeze([
|
|
|
176
178
|
"independent"
|
|
177
179
|
]);
|
|
178
180
|
|
|
181
|
+
export const FACET_TOP_LANE_MODES = Object.freeze(["stacked", "shared"]);
|
|
182
|
+
export const DEFAULT_FACET_TOP_LANE = "stacked";
|
|
183
|
+
|
|
179
184
|
export function getPositionChannelDefinition(channel) {
|
|
180
185
|
return POSITION_CHANNEL_DEFINITIONS[channel];
|
|
181
186
|
}
|
|
@@ -242,3 +247,20 @@ export const LEGEND_POSITIONS = Object.freeze(["left", "right", "top", "bottom",
|
|
|
242
247
|
export const LEGEND_BLOCK_LAYOUTS = Object.freeze(["row", "column", "wrap"]);
|
|
243
248
|
|
|
244
249
|
export const AXIS_RESOURCE_CHANNELS = Object.freeze(["x", "y", "theta", "radius", "parallel"]);
|
|
250
|
+
|
|
251
|
+
// Shared target policy for authoring and read-only action inspection.
|
|
252
|
+
const marks = (...types) => Object.freeze(types);
|
|
253
|
+
export const APPEARANCE_MARK_TYPES = Object.freeze({
|
|
254
|
+
encodeColor: marks('point', 'line', 'bar', 'area', 'arc', 'rect', 'text'),
|
|
255
|
+
encodeStroke: marks('point', 'line', 'area', 'bar', 'rect', 'arc', 'rule', 'tick'),
|
|
256
|
+
encodeOpacity: marks('point', 'rule', 'line', 'bar', 'rect'),
|
|
257
|
+
encodeStrokeWidth: marks('rule', 'line', 'rect'),
|
|
258
|
+
encodeStrokeDash: marks('line', 'rule'),
|
|
259
|
+
encodeSize: marks('point'),
|
|
260
|
+
encodeShape: marks('point'),
|
|
261
|
+
encodeAngle: marks('point', 'tick')
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Generated default action methods: start
|
|
265
|
+
export const DEFAULT_ACTION_METHODS = /* @__PURE__ */ Object.freeze(["createCanvas","editCanvas","fitCanvas","applyTextMetrics","removeTextMetrics","applyTheme","removeTheme","createData","reviseData","removeData","removeScale","removeCoordinate","bindMarkData","filterData","createSummaryData","createBinData","createLookupData","createFlattenData","createFoldData","createComputedData","createNormalizedData","createSortedData","createCompleteData","createImputedData","createStackData","createDensityData","createRegressionData","createRegressionStatisticsData","createWindowData","createTimeUnitData","createBin2DData","editBin2DData","editDerivedData","editComputedData","editFilteredData","editLookupData","editFlattenData","editFoldData","editSummaryData","editBinData","editTimeUnitData","editWindowData","editDensityData","editStackData","editRegressionData","editRegressionStatisticsData","editIntervalData","editECDFData","editNormalizedData","editSortedData","editCompleteData","editImputedData","createIntervalData","createECDFData","createPointMark","createTickMark","createStatisticalReference","createReferenceLine","createReferenceBand","createMarkLabels","editMarkLabelSelection","editMarkLabelPlacement","removeMarkLabels","createAnnotation","createTextMark","editTextMark","layoutLabels","removeLabelLayout","editPointMark","editTickMark","jitterPoints","removeJitter","packPoints","removePointPacking","removeMark","createLineMark","editLineMark","createBarMark","editBarMark","createAreaMark","createRuleMark","editRuleMark","editAreaMark","encodeX","encodeY","encodeChannels","encodeX2","encodeColor","encodeStrokeDash","encodeStroke","encodeStrokeWidth","encodeSize","encodeShape","encodeAngle","encodeOpacity","encodeRadius","encodeTheta","encodeR","encodePointRadius","removePointRadius","encodeXOffset","encodeYOffset","encodeY2","encodeYRange","encodeXRange","encodeGroup","encodePathOrder","encodeParallelCoordinates","removePathOrder","orderCategories","removeCategoryOrder","removeEncoding","encodeText","encodeDx","encodeDy","encodeHistogram","encodeDensity","editDensity","encodeHorizon","editHorizon","encodeBarWidth","createRegression","editRegression","createErrorBar","editErrorBar","createErrorBand","editErrorBand","editErrorBandBoundary","createBoxPlot","editBoxPlot","createGradientPlot","editGradientPlot","createViolinPlot","editViolinPlot","createParallelAxes","createParallelAxis","editParallelAxis","removeParallelAxis","removeParallelAxes","createAxes","createXAxis","createYAxis","createXAxisLine","createYAxisLine","editXAxisLine","editYAxisLine","createXAxisTicks","createYAxisTicks","editXAxisTicks","editYAxisTicks","createXAxisLabels","createYAxisLabels","editXAxisLabels","editYAxisLabels","createXAxisTicksAndLabels","createYAxisTicksAndLabels","editXAxisTicksAndLabels","editYAxisTicksAndLabels","createXAxisTitle","createYAxisTitle","editXAxisTitle","editYAxisTitle","editAxes","editXAxis","editYAxis","removeXAxis","removeYAxis","createGrid","createHorizontalGrid","createVerticalGrid","editHorizontalGrid","editVerticalGrid","editGrid","removeGrid","createLegend","editLegend","editLegendBlock","editLegendLayout","editLegendLabels","editLegendTitle","editLegendSymbols","editLegendBorder","createGuides","removeLegend","createTitle","editTitle","removeTitle","createCoordinate","editCoordinate","createScale","editScale","editXScale","editYScale","editXOffsetScale","editYOffsetScale","editParallelScale","editThetaScale","editRScale","editColorScale","editStrokeScale","editSizeScale","editOpacityScale","editShapeScale","editStrokeWidthScale","editStrokeDashScale","createDerivedData","createRegressionBand","editRegressionBand","createRegressionLine","editRegressionLine","filterMarks","removeMarkFilter","selectMarks","editMarkSelection","removeMarkHighlight","removeMarkSelection","highlightMarks","editSemantic","createGraphics","editGraphics","createArcMark","editArcMark","createThetaAxis","createRadialAxis","editThetaAxis","editRadialAxis","removeThetaAxis","removeRadialAxis","createThetaAxisLine","editThetaAxisLine","createRadialAxisLine","editRadialAxisLine","createThetaAxisTicks","editThetaAxisTicks","createRadialAxisTicks","editRadialAxisTicks","createThetaAxisLabels","editThetaAxisLabels","createRadialAxisLabels","editRadialAxisLabels","createThetaAxisTitle","editThetaAxisTitle","createRadialAxisTitle","editRadialAxisTitle","createThetaGrid","createRadialGrid","editThetaGrid","editRadialGrid","editCompositionLayout","replaceCompositionChild","insertCompositionChild","removeCompositionChild","reorderCompositionChildren","facet","facetGrid","repeatCharts","editFacetSource","editFacetHeaders","editFacetScales","editFacetGuides","createScatterPlot","createTextPlot","createDotPlot","createLollipopPlot","createDumbbellPlot","editEndpointPlot","createECDFPlot","editECDFPlot","createIntervalPlot","createRegressionPlot","createLinePlot","createPolarScatterPlot","createPolarLinePlot","createRadarPlot","editRadarPlot","createRugPlot","createStripPlot","createBeeswarmPlot","createRaincloudPlot","editRaincloudPlot","createBarPlot","createHistogram","createHeatmap","createParallelCoordinates","createRectMark","editRectMark","createPiePlot","createRosePlot","createRadialBarPlot","createDensityPlot","createHorizonPlot","createAreaPlot","layoutSeries"]);
|
|
266
|
+
// Generated default action methods: end
|
package/src/grammar/aggregate.js
CHANGED
|
@@ -297,6 +297,7 @@ export function aggregateRows(rows, field, operation) {
|
|
|
297
297
|
|
|
298
298
|
export function formatAggregateTitle(operation, field) {
|
|
299
299
|
const aggregate = validateAggregate(operation);
|
|
300
|
+
if (aggregate === "count" && field === undefined) return "count";
|
|
300
301
|
nonEmptyString(field, "Aggregate field");
|
|
301
302
|
if (isScalarAggregate(aggregate)) return `${aggregate}(${field})`;
|
|
302
303
|
if (aggregate.op === "quantile") {
|
package/src/grammar/arcs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{validateOptionObject as P,validateNonNegativeFinite as j}from"../core/validation.js";import{cloneAndFreeze as R,isPlainObject as
|
|
1
|
+
import{validateOptionObject as P,validateNonNegativeFinite as j}from"../core/validation.js";import{cloneAndFreeze as R,isPlainObject as S}from"../core/immutable.js";import{mapContinuousScaleValues as q,mapOrdinalPositionValues as M,readNominalField as b,readQuantitativeField as I,readScaleField as B}from"./scales/index.js";import{interpolateNumber as U,normalizedFiniteSum as _,stableFiniteSum as y}from"./numeric.js";function k(e){if(e?.mark?.type!=="arc")throw new Error("Arc derivation requires a semantic arc mark.");const t=e.encoding?.theta;if(t===void 0||!["quantitative","nominal","ordinal"].includes(t.fieldType))throw new Error(`Arc mark "${e.id}" requires quantitative or categorical theta.`);return t}function G(e,t){if(e?.type!=="linear"||!Array.isArray(e.range)||e.range.length!==2||!e.range.every(Number.isFinite)||e.range[0]===e.range[1])throw new Error(`${t} requires a resolved linear scale.`);return e}function L(e,t){if(e?.type!=="band"||!Array.isArray(e.domain)||!Array.isArray(e.range)||e.range.length!==2||!e.range.every(Number.isFinite)||e.range[0]===e.range[1]||!Number.isFinite(e.bandwidth)||e.bandwidth<=0)throw new Error(`${t} requires a resolved band scale.`);return e}function E(e,t){return t===void 0?e.map(()=>{}):B(e,t.field,t.fieldType??"nominal",{temporalUnit:t.temporalUnit})}function ne(e,t){const r=E(e,t.encoding?.color),n=t.encoding?.theta;if(!(n?.aggregate!==void 0||t.encoding?.radius?.aggregate!==void 0)||n?.field===void 0)return r;const s=b(e,n.field),o=new Map;return s.forEach((i,a)=>{const u=r[a];if(o.has(i)&&!Object.is(o.get(i),u))throw new Error(`Arc category "${i}" must resolve to one color value.`);o.set(i,u)}),[...o.values()]}function F(e,t,r,n){if(!Array.isArray(e))throw new TypeError(`Arc theta ${n}s require rows.`);if(typeof t!="string"||t.length===0)throw new TypeError(`Arc theta ${n} must be a non-empty string.`);const c=e.map((s,o)=>{const i=s?.[t];if(!Number.isFinite(i)||i<0)throw new TypeError(`Arc theta ${n} field "${t}" must contain non-negative finite numbers at row ${o}.`);return i});if(c.every(s=>s===0))throw new Error(`Arc mark "${r}" theta ${n}s must have a positive total.`);return R(c)}function N(e,t,r="arc"){return F(e,t,r,"weight")}function V(e,t,r="arc"){return F(e,t,r,"value")}function Q(e,t,r){const n=e.reduce((u,d)=>u+d,0),c=Number.isFinite(n)?void 0:_(e,"Arc theta values");if(n===0||c?.total<=0)throw new Error("Arc theta values must have a positive total.");const s=t[1]-t[0];let o=t[0],i=0,a=0;return e.map((u,d)=>{const h=c===void 0?u:u/c.scale,v=i+h;c!==void 0&&(a+=Math.abs(i)>=Math.abs(h)?i-v+h:h-v+i),i=v;const m=o+u/n*s,f=d===e.length-1?t[1]:c===void 0&&Number.isFinite(m)?m:U(t[0],t[1],(i+a)/(c?.total??n));if(!Number.isFinite(f)||f===o)throw new RangeError(`Arc theta ${r(d)} cannot resolve a distinct finite sector.`);const p={startTheta:o,endTheta:f};return o=f,p})}function W(e,t,r){if(!Number.isFinite(t?.availableRadius)||t.availableRadius<0)throw new RangeError(`Arc mark "${e.id}" requires a finite available radius.`);return{innerRadius:X(r,t.availableRadius),outerRadius:t.availableRadius}}function D(e,t,r,n,c){const s=t.encoding.theta,o=s.fieldType==="quantitative";if(!o&&!["count","sum"].includes(s.aggregate))throw new Error(`Arc mark "${t.id}" requires count, sum, or radial layout.`);const i=o?V(e,s.field,t.id):s.aggregate==="count"?e.map(()=>1):N(e,s.weight,t.id),a=o?e.map((g,w)=>w):b(e,s.field),u=E(e,t.encoding?.color),d=new Map(z(a,i).map(g=>[g.key,g])),h=o?a:r.domain,v=new Set(h);for(const g of d.keys())if(!v.has(g))throw new Error(`Arc theta value "${g}" is outside the scale domain.`);const m=h.filter(g=>d.get(g)?.values.some(w=>w>0)),f=g=>o?`row ${m[g]}`:`group "${String(m[g])}"`,p=m.map((g,w)=>o?i[g]:y(d.get(g).values,`Arc theta ${f(w)} aggregate`)),l=Q(p,r.range,f),A=W(t,n,c);return m.map((g,w)=>{const $=d.get(g),T=[...new Set($.sourceIndices.map(O=>u[O]))];if(T.length>1)throw new Error(`Aggregated arc theta group "${g}" must resolve to one color value.`);return{key:o?`${String(i[g])}:${g}`:g,theta:o?i[g]:g,count:$.sourceIndices.length,aggregateValue:p[w],color:T[0],...l[w],...A,sourceIndices:$.sourceIndices}})}function x(e,t,r){const n=t.aggregate==="count"?e.map(()=>1):I(e,t.field);return z(r,n)}function z(e,t){const r=new Map;for(const[n,c]of e.entries()){const s=r.get(c)??{key:c,sourceIndices:[],values:[]};s.sourceIndices.push(n),s.values.push(t[n]),r.set(c,s)}return[...r.values()]}function H(e,t){const r=k(t),n=t.encoding?.radius;if(!["nominal","ordinal"].includes(r.fieldType)||r.aggregate!==void 0||!["count","sum"].includes(n?.aggregate)||n.fieldType!=="quantitative")throw new Error("Measured Arc requires equal-angle categorical theta and count or sum radius.");if(n.aggregate==="count"&&Object.hasOwn(n,"field"))throw new Error("Measured count radius does not accept a field.");const c=b(e,r.field),s=E(e,t.encoding?.color),o=x(e,n,c);if(o.some(a=>a.values.some(u=>u<0)))throw new RangeError("Measured Arc radius inputs must be non-negative.");const i=o.map(a=>{const u=[...new Set(a.sourceIndices.map(d=>s[d]))];if(u.length>1)throw new Error(`Measured Arc category "${a.key}" must resolve to one color value.`);return{key:a.key,theta:a.key,count:a.sourceIndices.length,radius:y(a.values,"Measured Arc radius aggregate"),color:u[0],sourceIndices:a.sourceIndices}});if(!i.some(a=>a.radius>0))throw new Error("Measured Arc radius requires at least one positive category aggregate.");return R(i)}function J(e,t,r,n){const c=H(e,t),s=M(c.map(u=>u.theta),r),o=q(c.map(u=>u.radius),n),i=(Math.sign(r.step)||1)*r.bandwidth/2,a=new Map(c.map((u,d)=>[u.theta,{...u,startTheta:s[d]-i,endTheta:s[d]+i,innerRadius:n.range[0],outerRadius:o[d]}]));return r.domain.flatMap(u=>{const d=a.get(u);return d===void 0||d.radius===0?[]:[d]})}function K(e,t,r,n){const c=t.encoding.theta,s=t.encoding?.radius;if(s?.fieldType!=="quantitative")throw new Error(`Arc mark "${t.id}" requires quantitative radius.`);const o=b(e,c.field),i=I(e,s.field),a=E(e,t.encoding?.color),u=M(o,r),d=q(i,n),h=Math.min(...n.range),m=(Math.sign(r.step)||1)*r.bandwidth/2,f=new Map(r.domain.map(l=>[l,[]]));for(let l=0;l<e.length;l+=1){if(d[l]<=h)continue;const A=f.get(o[l]);if(A===void 0)throw new Error(`Arc theta value "${o[l]}" is outside the scale domain.`);A.push({key:`${String(o[l])}:${l}`,theta:o[l],radius:i[l],color:a[l],startTheta:u[l]-m,endTheta:u[l]+m,innerRadius:h,outerRadius:d[l],sourceIndices:[l]})}const p=[];for(const l of r.domain){const A=f.get(l);A.sort((g,w)=>w.outerRadius-g.outerRadius||g.sourceIndices[0]-w.sourceIndices[0]),p.push(...A)}return p}function ie(e,t,{thetaScale:r,radiusScale:n,frame:c,innerRadius:s=0}={}){if(!Array.isArray(e))throw new TypeError("Arc derivation requires rows.");const o=k(t);C(s);const i=n?.radialMapping!==void 0,a=o.fieldType==="quantitative",u=(a&&!i?G:L)(r,`Arc mark "${t.id}" theta`);let d=i?J(e,t,u,n):a||["count","sum"].includes(o.aggregate)?D(e,t,u,c,s):K(e,t,u,n);if(n!==void 0&&!i&&(a||o.aggregate!==void 0)){const h=Y(e,t),v=q(h.map(f=>f.radius),n),m=new Map(h.map((f,p)=>[f.sourceIndices[0],{radius:f.radius,outerRadius:v[p]}]));d=d.map(f=>{const p=m.get(f.sourceIndices[0]);if(p===void 0||!Number.isFinite(p.outerRadius)||p.outerRadius<=f.innerRadius)throw new RangeError("Arc outer radius must exceed inner radius.");return{...f,...p}})}return R({sectors:d})}function C(e){if(S(e)){if(P(e,["unit","value"],"Arc innerRadius"),e.unit!=="px")throw new RangeError("Arc innerRadius unit must be px.");return R({unit:"px",value:j(e.value,"Arc innerRadius")})}if(!Number.isFinite(e)||e<0||e>=1)throw new RangeError("Arc innerRadius must be from 0 (inclusive) to 1 (exclusive).");return e}function X(e,t){const r=C(e);if(typeof r=="number")return r*t;if(r.value>=t)throw new RangeError("Arc pixel innerRadius must be smaller than its outer radius.");return r.value}function Y(e,t){const r=t.encoding?.theta,n=t.encoding?.radius;if(r===void 0)return[];const c=r.fieldType==="quantitative";if(c?n.aggregate!==void 0:!["count","sum"].includes(r.aggregate))throw new Error("Grouped arc radius requires aggregated categorical theta.");const s=c?V(e,r.field,t.id):r.aggregate==="count"?e.map(()=>1):N(e,r.weight,t.id),o=c?e.map((i,a)=>a):b(e,r.field);return x(e,n,o).filter(i=>i.sourceIndices.some(a=>s[a]>0)).map(({sourceIndices:i,values:a})=>{if(n.aggregate===void 0&&new Set(a).size!==1)throw new Error("Arc radius requires one value per theta group or explicit aggregation.");return{sourceIndices:i,radius:n.aggregate===void 0?a[0]:y(a,"Arc radius aggregate")}})}export{ie as deriveArcSectors,H as deriveMeasuredArcValues,Y as deriveProportionalArcRadiusValues,C as normalizeArcInnerRadius,ne as readArcColorValues,V as readArcThetaValues,N as readArcThetaWeights,X as resolveArcInnerRadius};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{cloneAndFreeze as
|
|
1
|
+
import{cloneAndFreeze as z}from"../core/immutable.js";import{readNominalField as B,readQuantitativeField as V,readTemporalField as P}from"./scales/index.js";import{layoutSeriesPartition as F,validateColorLayout as j}from"./seriesLayout.js";import{stableOrderPathValues as J}from"./pathOrder.js";import{deriveCategoricalDensitySeries as K}from"./categoricalDensity.js";import{validatePathSeriesAppearance as G}from"./pathSeries.js";import{readAreaEndpoint as N,validateAreaEndpointPair as Q}from"./areaEndpoints.js";import{validateGeneratedItemLimit as R}from"../core/validation.js";function U(n){const e=[];for(const a of n)e[a.index]=a;return e}function re(n,e,a={}){if(e?.mark?.type!=="area")throw new Error("Area series derivation requires a semantic area mark.");const{x:p,y:u,x2:l,y2:k}=e.encoding??{},o=["quantitative","temporal","nominal","ordinal"].includes(p?.fieldType)&&u?.fieldType==="quantitative"&&k?.fieldType==="quantitative"&&l===void 0,y=["quantitative","temporal","nominal","ordinal"].includes(u?.fieldType)&&p?.fieldType==="quantitative"&&l?.fieldType==="quantitative"&&k===void 0;if(o===y)throw new Error(`Area mark "${e.id}" requires exactly one quantitative x/x2 or y/y2 range and one quantitative, temporal, or categorical independent position.`);const v=G(n,e),c=o?"vertical":"horizontal",f=o?p:u,x=["nominal","ordinal"].includes(f.fieldType),m=x?B(n,f.field):f.fieldType==="temporal"?P(n,f.field,f.temporalUnit):V(n,f.field);if(Object.hasOwn(f,"datum"))throw new Error("Area independent position requires a field.");const w=o?u:p,i=o?k:l;Q(w,i);const r=e.mark.missing??"error",g=N(n,w,r),O=N(n,i,r),s=x?a[o?"xDomain":"yDomain"]??[...new Set(m)]:void 0,t=new Map(s?.map((d,h)=>[d,h]));if(x&&m.some(d=>!t.has(d)))throw new Error("Area categorical domain must contain every observed position.");const E=e.encoding?.pathOrder,$=E===void 0?void 0:V(n,E.field),D=new Map;for(let d=0;d<n.length;d+=1){const h=v.map(q=>n[d][q]),M=JSON.stringify(h),T=D.get(M)??{key:Object.fromEntries(v.map((q,C)=>[q,h[C]])),values:[]};T.values.push(o?{x:m[d],y:g[d],y2:O[d],...r==="break"?{sourceIndex:d}:{},...$===void 0?{}:{pathOrder:$[d]}}:{x:g[d],x2:O[d],...r==="break"?{sourceIndex:d}:{},y:m[d],...$===void 0?{}:{pathOrder:$[d]}}),D.set(M,T)}if(D.size===0)throw new Error(`Area mark "${e.id}" has no values.`);_(D,{layer:e,vertical:o,primary:w,secondary:i});const A=[...D.values()].flatMap(d=>{const h=o?"x":"y",M=E===void 0?d.values.sort((T,q)=>x?t.get(T[h])-t.get(q[h]):T[h]-q[h]):J(d.values.map(({pathOrder:T,...q})=>q),d.values.map(T=>T.pathOrder),E.order);if(r==="break"){const T=[];let q=[];const C=()=>{q.length>=2&&T.push({key:d.key,sourceIndices:q.map(S=>S.sourceIndex),values:q.map(({sourceIndex:S,...L})=>L)}),q=[]};for(const S of M)(o?S.y==null||S.y2==null:S.x==null||S.x2==null)?C():q.push(S);return C(),T}if(M.length<2)throw new Error(`Area series on mark "${e.id}" requires at least two points.`);return{key:d.key,values:M}}),b=A.length===0&&r==="break",I=[...g,...O].filter(Number.isFinite);return z({orientation:c,xValues:b?o?m:I:A.flatMap(d=>d.values.flatMap(h=>o?[h.x]:[h.x,h.x2])),yValues:b?o?I:m:A.flatMap(d=>d.values.flatMap(h=>o?[h.y,h.y2]:[h.y])),series:A})}function ie(n,e,a={}){if(e?.mark?.type!=="area")throw new Error("Centered area series derivation requires an area mark.");const{x:p,y:u,x2:l,y2:k,group:o,color:y}=e.encoding??{};if(!["quantitative","temporal","nominal","ordinal"].includes(p?.fieldType)||u?.fieldType!=="quantitative"||u?.stack!=="center"||l!==void 0||k!==void 0)throw new Error(`Centered area mark "${e.id}" requires one x field and one quantitative y field without ranged endpoints.`);if(o?.fieldType!=="nominal")throw new Error(`Centered area mark "${e.id}" requires a nominal group encoding.`);if(y!==void 0&&y.fieldType!=="quantitative"&&y.field!==o.field)throw new Error(`Centered area color on mark "${e.id}" must match its group field.`);G(n,e);const v=["nominal","ordinal"].includes(p.fieldType),c=v?B(n,p.field):p.fieldType==="temporal"?P(n,p.field,p.temporalUnit):V(n,p.field),f=V(n,u.field),x=B(n,o.field),m=[],w=new Set,i=new Map;for(let s=0;s<n.length;s+=1){const t=x[s];i.has(t)||(m.push(t),i.set(t,new Map));const E=i.get(t);if(E.has(c[s]))throw new Error(`Centered area mark "${e.id}" has duplicate ${o.field}/${p.field} rows.`);E.set(c[s],f[s]),w.add(c[s])}const r=v?(a.xDomain??[...w]).filter(s=>w.has(s)):[...w].sort((s,t)=>s-t);if(r.length!==w.size)throw new Error("Area categorical domain must contain every observed position.");if(m.length===0||r.length<2)throw new Error(`Centered area mark "${e.id}" requires at least two aligned positions.`);for(const[s,t]of i)if(t.size!==r.length||r.some(E=>!t.has(E)))throw new Error(`Centered area series "${String(s)}" requires one aligned value at every x position.`);const g=m.map(()=>[]);for(const s of r){const t=m.map(A=>i.get(A).get(s)),E=F(t,"center"),$=U(E);let D=E[0]?.start??0;for(let A=0;A<m.length;A+=1){const b=$[A];g[A].push({x:s,y:t[A],lower:b?.start??D,upper:b?.end??D}),b!==void 0&&(D=b.end)}}const O=m.map((s,t)=>({key:{[o.field]:s,...y===void 0||y.fieldType==="quantitative"?{}:{[y.field]:s}},values:g[t]}));return z({mode:"y-center",orientation:"vertical",xValues:r,yValues:O.flatMap(s=>s.values.flatMap(t=>[t.lower,t.upper])),series:O})}function oe(n,e,a){if(e?.mark?.type!=="area")throw new Error("Density area derivation requires a semantic area mark.");if(e.encoding?.pathOrder!==void 0)throw new Error(`Density area mark "${e.id}" does not support path order.`);if(a?.type!=="density"||!Array.isArray(a.as))throw new Error(`Area mark "${e.id}" requires density provenance.`);if(a.placement?.type==="category")return K(n,e,a);const{x:p,y:u,group:l}=e.encoding??{};if(p?.fieldType!=="quantitative"||u?.fieldType!=="quantitative")throw new Error(`Density area mark "${e.id}" requires quantitative x and y encodings.`);const[k,o]=a.as,y=p.field===k&&u.field===o?"y-density":p.field===o&&u.field===k?"x-density":void 0;if(y===void 0)throw new Error(`Density area mark "${e.id}" must encode its value and density fields.`);if(a.groupBy===void 0){if(l!==void 0)throw new Error(`Ungrouped density area mark "${e.id}" cannot encode group.`)}else if(l?.field!==a.groupBy||l.fieldType!=="nominal")throw new Error(`Density area mark "${e.id}" must group by "${a.groupBy}".`);const v=V(n,p.field),c=V(n,u.field),f=a.groupBy===void 0?n.map(()=>{}):B(n,a.groupBy),x=new Map;for(let i=0;i<n.length;i+=1){const r=f[i],g=x.get(r)??{key:a.groupBy===void 0?{}:{[a.groupBy]:r},values:[]};g.values.push({x:v[i],y:c[i]}),x.set(r,g)}if(x.size===0)throw new Error(`Density area mark "${e.id}" has no values.`);const m=y==="y-density"?"x":"y",w=[...x.values()].map(i=>{const r=i.values.sort((g,O)=>g[m]-O[m]);if(r.length<2)throw new Error(`Density area series on mark "${e.id}" requires at least two points.`);return{key:i.key,values:r}});return z({mode:y,series:w})}function ae(n,e="overlay"){if(j(e),e==="group")throw new Error('Density area series do not support "group" layout.');if(n?.mode!=="y-density"){if(e==="overlay")return n;throw new Error(`Area layout "${e}" currently requires vertical density series.`)}const a=n.series[0]?.values.length??0;if(a===0||n.series.some(u=>u.values.length!==a))throw new Error("Density area layout requires aligned non-empty samples.");const p=n.series.map(()=>[]);for(let u=0;u<a;u+=1){const l=n.series[0].values[u].x,k=n.series.map(c=>{if(c.values[u].x!==l)throw new Error("Density area layout requires one shared sample grid.");return c.values[u].y}),o=F(k,e),y=U(o);let v=e==="center"?o[0]?.start??0:0;for(let c=0;c<n.series.length;c+=1){const f=y[c];p[c].push({x:l,lower:f?.start??v,upper:f?.end??v}),f!==void 0&&["stack","fill","center"].includes(e)&&(v=f.end)}}return z({mode:n.mode,series:n.series.map((u,l)=>({key:u.key,values:p[l]}))})}function _(n,{layer:e,vertical:a,primary:p,secondary:u}){const l=e.layout?.mode??"overlay";if(j(l),l==="overlay")return;if(l==="group"||l==="center"&&!a)throw new Error(`Area layout "${l}" is incompatible with this orientation.`);const k=Object.hasOwn(p,"datum"),o=Object.hasOwn(u,"datum");if(k===o||(k?p.datum:u.datum)!==0)throw new Error("Stacked area requires one value field and one zero datum endpoint.");const y=a?"x":"y",v=a?"y":"x",c=`${v}2`,f=k?c:v,x=[...n.values()],m=x.map(i=>{const r=new Map;for(const g of i.values){if(r.has(g[y]))throw new Error("Area layout requires unique group/position rows.");r.set(g[y],g)}return r}),w=[...new Set(x.flatMap(i=>i.values.map(r=>r[y])))].sort((i,r)=>i-r);if(R(w.length*x.length,"Area layout cell count"),m.some(i=>i.size!==w.length||w.some(r=>!i.has(r))))throw new Error("Area layout requires an aligned row for every group and position.");for(const i of w){const r=m.map(t=>t.get(i));if(r.some(t=>t[f]==null)){for(const t of r)t[v]=null,t[c]=null;continue}const g=F(r.map(t=>t[f]),l),O=new Map(g.map(t=>[t.index,t]));let s=l==="center"?g[0]?.start??0:0;r.forEach((t,E)=>{const $=O.get(E);t[v]=$?.start??(l==="diverging"?0:s),t[c]=$?.end??(l==="diverging"?0:s),$!==void 0&&l!=="diverging"&&(s=$.end)})}}export{re as deriveAreaSeries,ie as deriveCenteredAreaSeries,oe as deriveDensityAreaSeries,ae as layoutDensityAreaSeries,_ as layoutRawAreaGroups};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolveTextLines as
|
|
1
|
+
import{resolveTextLines as l}from"../core/textMetrics.js";function a(e,{title:i=!1}={}){if(Object.hasOwn(e,"align")&&!["left","center","right"].includes(e.align))throw new TypeError("Axis text align must be left, center, or right.");if(i){if(Object.hasOwn(e,"overlap")&&!["error","allow"].includes(e.overlap))throw new TypeError("Axis title overlap must be error or allow.");if(Object.hasOwn(e,"baseline")&&!["top","hanging","middle","alphabetic","ideographic","bottom"].includes(e.baseline))throw new TypeError("Axis title baseline must be a supported text baseline.");for(const t of["offset","alongOffset"])if(Object.hasOwn(e,t)&&!Number.isFinite(e[t]))throw new TypeError(`Axis title ${t} must be a finite number.`)}}function f(e,i){if(!/[\r\n]/u.test(e))return;const t=i.baseline??"middle",n=l(e,{fontSize:i.fontSize,align:i.align??"center",baseline:t,...t==="middle"?{blockAlign:"middle"}:{}});if(["bottom","ideographic"].includes(t)){const o=(n.length-1)*i.fontSize*1.2;return n.map(r=>({...r,y:r.y-o}))}return n.length>1?n:void 0}function b(e){return e?.overlap==="allow"}export{b as axisTitleAllowsLabelOverlap,f as axisTitleTextLines,a as validateAxisTextPlacement};
|
|
@@ -32,7 +32,7 @@ export function resolveBarOrientation(layer) {
|
|
|
32
32
|
const x = layer.encoding?.x;
|
|
33
33
|
const y = layer.encoding?.y;
|
|
34
34
|
if (hasNumericCenters(layer)) return layer.mark.orientation;
|
|
35
|
-
if (x?.bin !== undefined && y?.aggregate
|
|
35
|
+
if (x?.bin !== undefined && isAggregate(y?.aggregate)) {
|
|
36
36
|
return BAR_ORIENTATIONS.vertical;
|
|
37
37
|
}
|
|
38
38
|
if (isBarCategoryEncoding(x) && y?.fieldType === "quantitative" && layer.encoding?.y2?.fieldType === "quantitative") {
|
|
@@ -68,7 +68,7 @@ export function resolveBarGrain(layer) {
|
|
|
68
68
|
|
|
69
69
|
if (
|
|
70
70
|
x?.bin !== undefined &&
|
|
71
|
-
y?.aggregate
|
|
71
|
+
isAggregate(y?.aggregate)
|
|
72
72
|
) {
|
|
73
73
|
return BAR_GRAINS.histogram;
|
|
74
74
|
}
|
|
@@ -9,6 +9,7 @@ export const CURVE_INTERPOLATIONS = Object.freeze([
|
|
|
9
9
|
"basis",
|
|
10
10
|
"cardinal",
|
|
11
11
|
"monotone",
|
|
12
|
+
"monotone-steffen",
|
|
12
13
|
"natural"
|
|
13
14
|
]);
|
|
14
15
|
|
|
@@ -16,6 +17,7 @@ const SMOOTH_CURVES = new Set([
|
|
|
16
17
|
"basis",
|
|
17
18
|
"cardinal",
|
|
18
19
|
"monotone",
|
|
20
|
+
"monotone-steffen",
|
|
19
21
|
"natural"
|
|
20
22
|
]);
|
|
21
23
|
|
|
@@ -196,8 +198,38 @@ function monotoneTangents(points) {
|
|
|
196
198
|
return tangents;
|
|
197
199
|
}
|
|
198
200
|
|
|
199
|
-
function
|
|
200
|
-
const
|
|
201
|
+
function steffenTangents(points) {
|
|
202
|
+
const widths = points.slice(1).map((point, index) => point.x - points[index].x);
|
|
203
|
+
const direction = Math.sign(widths.find(width => width !== 0) ?? 0);
|
|
204
|
+
if (widths.some(width => width !== 0 && Math.sign(width) !== direction)) {
|
|
205
|
+
throw new Error("Steffen curve requires monotonic x values.");
|
|
206
|
+
}
|
|
207
|
+
const slopes = widths.map((width, i) =>
|
|
208
|
+
(points[i + 1].y - points[i].y) / (width || (direction < 0 ? -0 : 0)));
|
|
209
|
+
const tangents = Array(points.length);
|
|
210
|
+
for (let i = 1; i < points.length - 1; i += 1) {
|
|
211
|
+
const a = slopes[i - 1], b = slopes[i];
|
|
212
|
+
const mean = (a * widths[i] + b * widths[i - 1]) / (widths[i - 1] + widths[i]);
|
|
213
|
+
// A zero-width turn has no finite secant estimate; its Hermite limit is flat.
|
|
214
|
+
const sign = value => value < 0 ? -1 : 1;
|
|
215
|
+
const slope = (sign(a) + sign(b)) * Math.min(Math.abs(a), Math.abs(b), Math.abs(mean) / 2);
|
|
216
|
+
tangents[i] = Number.isNaN(slope) && (widths[i - 1] === 0 || widths[i] === 0) ? 0 : slope;
|
|
217
|
+
}
|
|
218
|
+
tangents[0] = widths[0] === 0 ? tangents[1] : (3 * slopes[0] - tangents[1]) / 2;
|
|
219
|
+
tangents[points.length - 1] = widths.at(-1) === 0 ? tangents.at(-2) :
|
|
220
|
+
(3 * slopes.at(-1) - tangents.at(-2)) / 2;
|
|
221
|
+
return tangents;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function buildSteffenCommands(points) {
|
|
225
|
+
const distinct = points.filter((point, i) => i === 0 ||
|
|
226
|
+
point.x !== points[i - 1].x || point.y !== points[i - 1].y);
|
|
227
|
+
if (distinct.length === 1) return ownCommands([{ op: "M", ...distinct[0] }]);
|
|
228
|
+
if (distinct.length === 2) return buildLinearPathCommands(distinct);
|
|
229
|
+
return buildMonotoneCommands(distinct, steffenTangents(distinct));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function buildMonotoneCommands(points, tangents = monotoneTangents(points)) {
|
|
201
233
|
const commands = [{ op: "M", x: points[0].x, y: points[0].y }];
|
|
202
234
|
for (let index = 1; index < points.length; index += 1) {
|
|
203
235
|
const previous = points[index - 1];
|
|
@@ -274,6 +306,7 @@ function buildNaturalCommands(points) {
|
|
|
274
306
|
}
|
|
275
307
|
|
|
276
308
|
function buildValidatedCurvePathCommands(points, curve, tension) {
|
|
309
|
+
if (curve === "monotone-steffen") return buildSteffenCommands(points);
|
|
277
310
|
if (curve === "linear" ||
|
|
278
311
|
(SMOOTH_CURVES.has(curve) &&
|
|
279
312
|
curve !== "monotone" &&
|