ggaction 0.0.2 → 0.0.4
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 +55 -2
- package/README.md +95 -72
- package/package.json +15 -5
- package/src/actions/boxPlots/components.js +3 -3
- package/src/actions/boxPlots/edit.js +277 -0
- package/src/actions/boxPlots/index.js +2 -0
- package/src/actions/boxPlots/materialize.js +12 -1
- package/src/actions/boxPlots/options.js +25 -14
- package/src/actions/canvas/actions.js +21 -17
- package/src/actions/composition/actions.js +247 -0
- package/src/actions/composition/index.js +1 -0
- package/src/actions/coordinates/actions.js +2 -5
- package/src/actions/data/density.js +7 -1
- package/src/actions/data/filter.js +3 -1
- package/src/actions/data/regression.js +1 -1
- package/src/actions/encodings/appearance.js +12 -1
- package/src/actions/encodings/barWidth.js +5 -3
- package/src/actions/encodings/color/continuous.js +137 -0
- package/src/actions/encodings/color/index.js +151 -0
- package/src/actions/encodings/color/layout.js +91 -0
- package/src/actions/encodings/color/policy.js +88 -0
- package/src/actions/encodings/index.js +4 -2
- package/src/actions/encodings/offset.js +91 -73
- package/src/actions/encodings/position/apply.js +66 -0
- package/src/actions/encodings/position/index.js +130 -0
- package/src/actions/encodings/position/policies/arc.js +26 -0
- package/src/actions/encodings/position/policies/bar.js +9 -5
- package/src/actions/encodings/position/policies/index.js +5 -1
- package/src/actions/encodings/position/policies/line.js +20 -3
- package/src/actions/encodings/position/resolve.js +58 -9
- package/src/actions/encodings/ranged.js +30 -8
- package/src/actions/encodings/shared.js +17 -21
- package/src/actions/encodings/strokeDash.js +1 -1
- package/src/actions/encodings/text.js +73 -0
- package/src/actions/errorBands/create.js +37 -29
- package/src/actions/errorBands/edit.js +206 -0
- package/src/actions/errorBands/index.js +9 -0
- package/src/actions/errorBands/options.js +39 -0
- package/src/actions/errorBars/create.js +50 -43
- package/src/actions/errorBars/edit.js +130 -0
- package/src/actions/errorBars/index.js +3 -0
- package/src/actions/errorBars/options.js +52 -0
- package/src/actions/facets/actions.js +162 -0
- package/src/actions/facets/derive.js +200 -0
- package/src/actions/facets/guides.js +16 -0
- package/src/actions/facets/index.js +1 -0
- package/src/actions/facets/replay.js +69 -0
- package/src/actions/guides/applicability.js +113 -0
- package/src/actions/guides/axes/axes.js +70 -16
- package/src/actions/guides/axes/edit.js +126 -0
- package/src/actions/guides/axes/index.js +12 -0
- package/src/actions/guides/axes/labels.js +22 -19
- package/src/actions/guides/axes/remove.js +60 -0
- package/src/actions/guides/axes/tickGroups.js +7 -5
- package/src/actions/guides/axes/ticks.js +10 -5
- package/src/actions/guides/axes/titles.js +8 -6
- package/src/actions/guides/grids/grid.js +144 -15
- package/src/actions/guides/grids/resolve.js +6 -14
- package/src/actions/guides/guides.js +17 -75
- package/src/actions/guides/index.js +6 -0
- package/src/actions/guides/legends/categorical/actions.js +48 -9
- package/src/actions/guides/legends/categorical/components.js +16 -16
- package/src/actions/guides/legends/categorical/index.js +14 -14
- package/src/actions/guides/legends/categorical/layout.js +7 -3
- package/src/actions/guides/legends/categorical/options.js +2 -5
- package/src/actions/guides/legends/categorical/resolve.js +2 -2
- package/src/actions/guides/legends/categorical/symbols.js +14 -14
- package/src/actions/guides/legends/continuous/common.js +7 -7
- package/src/actions/guides/legends/continuous/interval.js +6 -6
- package/src/actions/guides/legends/continuous/opacity.js +1 -1
- package/src/actions/guides/legends/edit.js +4 -26
- package/src/actions/guides/legends/focused.js +63 -0
- package/src/actions/guides/legends/index.js +4 -0
- package/src/actions/guides/legends/remove.js +46 -0
- package/src/actions/guides/legends/size.js +13 -15
- package/src/actions/guides/legends/target.js +25 -0
- package/src/actions/guides/polar/axes/facade.js +219 -0
- package/src/actions/guides/polar/axes/index.js +55 -0
- package/src/actions/guides/polar/axes/labels.js +188 -0
- package/src/actions/guides/polar/axes/lines.js +113 -0
- package/src/actions/guides/polar/axes/shared.js +112 -0
- package/src/actions/guides/polar/axes/ticks.js +164 -0
- package/src/actions/guides/polar/axes/titles.js +165 -0
- package/src/actions/guides/polar/grids.js +252 -0
- package/src/actions/guides/polar/index.js +2 -0
- package/src/actions/guides/polar/resolve.js +269 -0
- package/src/actions/guides/tickValues.js +1 -1
- package/src/actions/index.js +4 -0
- package/src/actions/intervals/resolve.js +1 -1
- package/src/actions/marks/arc/actions.js +239 -0
- package/src/actions/marks/arc/index.js +1 -0
- package/src/actions/marks/{area.js → area/actions.js} +51 -38
- package/src/actions/marks/area/index.js +4 -0
- package/src/actions/marks/bar/create.js +26 -5
- package/src/actions/marks/bar/edit.js +1 -1
- package/src/actions/marks/bar/materialize.js +25 -31
- package/src/actions/marks/index.js +12 -4
- package/src/actions/marks/lifecycle.js +27 -0
- package/src/actions/marks/line/actions.js +373 -0
- package/src/actions/marks/line/index.js +1 -0
- package/src/actions/marks/{point.js → point/actions.js} +67 -108
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/rect/actions.js +165 -0
- package/src/actions/marks/rect/index.js +1 -0
- package/src/actions/marks/remove.js +214 -0
- package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
- package/src/actions/marks/rule/index.js +1 -0
- package/src/actions/marks/shared.js +156 -25
- package/src/actions/marks/text/actions.js +192 -0
- package/src/actions/marks/text/index.js +1 -0
- package/src/actions/primitives/createGraphics.js +13 -6
- package/src/actions/primitives/editGraphics.js +10 -3
- package/src/actions/primitives/semantic.js +26 -3
- package/src/actions/primitives/semanticValue.js +26 -4
- package/src/actions/regression/components.js +2 -2
- package/src/actions/regression/create.js +19 -2
- package/src/actions/regression/edit.js +247 -0
- package/src/actions/regression/index.js +3 -0
- package/src/actions/regression/resolve.js +3 -1
- package/src/actions/scales/consumers/common.js +91 -0
- package/src/actions/scales/consumers/families.js +47 -0
- package/src/actions/scales/consumers/index.js +41 -0
- package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +14 -127
- package/src/actions/scales/create.js +1 -1
- package/src/actions/scales/definitions.js +15 -6
- package/src/actions/scales/edit.js +45 -22
- package/src/actions/scales/materialize.js +58 -409
- package/src/actions/selection/actions.js +38 -24
- package/src/actions/selection/index.js +2 -0
- package/src/actions/titles/actions.js +63 -2
- package/src/actions/titles/index.js +2 -0
- package/src/actions/titles/resolve.js +12 -17
- package/src/composition.js +10 -0
- package/src/core/ChartProgram.js +49 -65
- package/src/core/action.js +10 -0
- package/src/core/compositionState.js +169 -0
- package/src/core/materializationState.js +65 -0
- package/src/core/programState.js +28 -0
- package/src/core/validation.js +16 -0
- package/src/core/vocabulary.js +154 -4
- package/src/grammar/arcs.js +154 -0
- package/src/grammar/areaSeries.js +1 -1
- package/src/grammar/bars/aggregate.js +3 -3
- package/src/grammar/bars/geometry.js +3 -3
- package/src/grammar/bars/policy.js +7 -1
- package/src/grammar/coordinates.js +3 -7
- package/src/grammar/density.js +21 -2
- package/src/grammar/facets/dependencies.js +185 -0
- package/src/grammar/facets/guides.js +257 -0
- package/src/grammar/facets/index.js +122 -0
- package/src/grammar/facets/scales.js +254 -0
- package/src/grammar/interval.js +2 -96
- package/src/grammar/lineSeries.js +112 -4
- package/src/grammar/palettes.js +5 -7
- package/src/grammar/polar.js +102 -0
- package/src/grammar/polarGuides.js +243 -0
- package/src/grammar/polarLineCommands.js +43 -0
- package/src/grammar/polarPaths.js +161 -0
- package/src/grammar/positionCompatibility.js +18 -2
- package/src/grammar/rects.js +26 -0
- package/src/grammar/regression/derive.js +97 -0
- package/src/grammar/regression/index.js +22 -0
- package/src/grammar/regression/models.js +282 -0
- package/src/grammar/regression/parameters.js +106 -0
- package/src/grammar/rules.js +1 -1
- package/src/grammar/scales/color.js +4 -2
- package/src/grammar/scales/continuous.js +6 -1
- package/src/grammar/scales/fields.js +7 -0
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +1 -1
- package/src/grammar/schemas/graphic.js +1 -1
- package/src/grammar/schemas/graphicBounds.js +64 -9
- package/src/grammar/schemas/graphicTree.js +44 -10
- package/src/grammar/schemas/semanticPath.js +31 -9
- package/src/grammar/seriesLayout.js +13 -2
- package/src/grammar/statistics/studentT.js +98 -0
- package/src/grammar/text.js +71 -0
- package/src/grammar/ticks.js +50 -13
- package/src/grammar/transforms.js +1 -1
- package/src/index.js +1 -0
- package/src/layout/composition.js +310 -0
- package/src/layout/facets.js +131 -0
- package/src/layout/title.js +6 -0
- package/src/materialization/bars/aggregate.js +6 -3
- package/src/materialization/bars/grouped.js +75 -37
- package/src/materialization/bars/histogram.js +1 -1
- package/src/materialization/bars/ranged.js +1 -1
- package/src/materialization/composition.js +152 -0
- package/src/materialization/compositionSnapshot.js +114 -0
- package/src/materialization/density.js +1 -1
- package/src/materialization/dependencies.js +47 -73
- package/src/materialization/encodings.js +14 -28
- package/src/materialization/facetGuides/index.js +2 -0
- package/src/materialization/facetGuides/legacyCategorical.js +125 -0
- package/src/materialization/facetGuides/placement.js +126 -0
- package/src/materialization/facetGuides/preparation.js +188 -0
- package/src/materialization/facets.js +214 -0
- package/src/materialization/graphicHierarchy.js +15 -5
- package/src/materialization/guides/resources.js +46 -0
- package/src/materialization/marks/capabilities.js +141 -0
- package/src/materialization/marks/index.js +105 -0
- package/src/materialization/marks/policies.js +87 -0
- package/src/materialization/planner.js +105 -12
- package/src/materialization/rect.js +138 -0
- package/src/materialization/rectConfig.js +38 -0
- package/src/materialization/rowEncoding.js +33 -0
- package/src/materialization/scaleGuideDependencies.js +94 -0
- package/src/materialization/scales/map.js +40 -0
- package/src/materialization/scales/policies/arc.js +37 -0
- package/src/materialization/scales/policies/bar.js +132 -0
- package/src/materialization/scales/policies/series.js +76 -0
- package/src/materialization/scales/resolve.js +326 -0
- package/src/materialization/selection/items/arc.js +29 -0
- package/src/materialization/selection/items/bar.js +255 -0
- package/src/materialization/selection/items/common.js +131 -0
- package/src/materialization/selection/items/index.js +6 -0
- package/src/materialization/selection/items/path.js +59 -0
- package/src/materialization/selection/items/point.js +55 -0
- package/src/materialization/selection/items/rect.js +23 -0
- package/src/materialization/selection/items/rule.js +25 -0
- package/src/materialization/selection/policies/arc.js +10 -0
- package/src/materialization/selection/policies/area.js +1 -1
- package/src/materialization/selection/policies/bar.js +1 -1
- package/src/materialization/selection/policies/index.js +5 -1
- package/src/materialization/selection/policies/line.js +1 -1
- package/src/materialization/selection/policies/point.js +1 -1
- package/src/materialization/selection/policies/rect.js +10 -0
- package/src/materialization/selection/policies/rule.js +1 -1
- package/src/materialization/selection/styles.js +25 -17
- package/src/materialization/text.js +156 -0
- package/src/renderers/canvas/index.js +68 -22
- package/src/renderers/canvas/text.js +14 -1
- package/types/extension.d.ts +5 -1
- package/types/index.d.ts +56 -1
- package/types/program.d.ts +664 -27
- package/src/actions/encodings/color.js +0 -329
- package/src/actions/encodings/position.js +0 -176
- package/src/actions/marks/line.js +0 -248
- package/src/grammar/regression.js +0 -578
- package/src/grammar/scales.js +0 -1
- package/src/materialization/marks.js +0 -110
- package/src/materialization/selection/items.js +0 -503
|
@@ -27,12 +27,12 @@ const DEFAULT_OUTLIER = Object.freeze({
|
|
|
27
27
|
opacity: 0.75
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
function plainOptions(value, keys, label) {
|
|
30
|
+
function plainOptions(value, keys, label, operation = "createBoxPlot") {
|
|
31
31
|
if (value === undefined) return {};
|
|
32
32
|
if (!isPlainObject(value)) {
|
|
33
|
-
throw new TypeError(
|
|
33
|
+
throw new TypeError(`${operation} ${label} must be a plain object.`);
|
|
34
34
|
}
|
|
35
|
-
validateKeys(value, keys,
|
|
35
|
+
validateKeys(value, keys, `${operation} ${label}`);
|
|
36
36
|
return value;
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -52,12 +52,12 @@ export function boxEncodingArgs(value) {
|
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export function resolveBoxWhisker(value) {
|
|
55
|
+
export function resolveBoxWhisker(value, operation = "createBoxPlot") {
|
|
56
56
|
if (value === undefined) return Object.freeze({ type: "tukey", factor: 1.5 });
|
|
57
57
|
if (!isPlainObject(value)) {
|
|
58
|
-
throw new TypeError(
|
|
58
|
+
throw new TypeError(`${operation} whisker must be a plain object.`);
|
|
59
59
|
}
|
|
60
|
-
validateKeys(value, ["type", "factor"],
|
|
60
|
+
validateKeys(value, ["type", "factor"], `${operation} whisker`);
|
|
61
61
|
const type = value.type ?? "tukey";
|
|
62
62
|
if (!["tukey", "minmax"].includes(type)) {
|
|
63
63
|
throw new Error(`Unsupported createBoxPlot whisker type "${type}".`);
|
|
@@ -77,8 +77,8 @@ export function resolveBoxWhisker(value) {
|
|
|
77
77
|
return Object.freeze({ type, factor });
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
export function resolveBoxWidth(value) {
|
|
81
|
-
const options = plainOptions(value, ["band"], "width");
|
|
80
|
+
export function resolveBoxWidth(value, operation = "createBoxPlot") {
|
|
81
|
+
const options = plainOptions(value, ["band"], "width", operation);
|
|
82
82
|
const band = options.band ?? 0.7;
|
|
83
83
|
if (!Number.isFinite(band) || band <= 0 || band >= 1) {
|
|
84
84
|
throw new RangeError(
|
|
@@ -88,11 +88,12 @@ export function resolveBoxWidth(value) {
|
|
|
88
88
|
return band;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
export function resolveBoxAppearance(value) {
|
|
91
|
+
export function resolveBoxAppearance(value, operation = "createBoxPlot") {
|
|
92
92
|
const options = plainOptions(
|
|
93
93
|
value,
|
|
94
94
|
["fill", "opacity", "stroke", "strokeWidth"],
|
|
95
|
-
"box"
|
|
95
|
+
"box",
|
|
96
|
+
operation
|
|
96
97
|
);
|
|
97
98
|
return Object.freeze({
|
|
98
99
|
fill: options.fill === undefined
|
|
@@ -113,8 +114,13 @@ export function resolveBoxAppearance(value) {
|
|
|
113
114
|
});
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
export function resolveBoxMedianAppearance(value) {
|
|
117
|
-
const options = plainOptions(
|
|
117
|
+
export function resolveBoxMedianAppearance(value, operation = "createBoxPlot") {
|
|
118
|
+
const options = plainOptions(
|
|
119
|
+
value,
|
|
120
|
+
["stroke", "strokeWidth"],
|
|
121
|
+
"median",
|
|
122
|
+
operation
|
|
123
|
+
);
|
|
118
124
|
return Object.freeze({
|
|
119
125
|
stroke: options.stroke === undefined
|
|
120
126
|
? DEFAULT_MEDIAN.stroke
|
|
@@ -128,8 +134,13 @@ export function resolveBoxMedianAppearance(value) {
|
|
|
128
134
|
});
|
|
129
135
|
}
|
|
130
136
|
|
|
131
|
-
export function resolveBoxOutlierAppearance(value) {
|
|
132
|
-
const options = plainOptions(
|
|
137
|
+
export function resolveBoxOutlierAppearance(value, operation = "createBoxPlot") {
|
|
138
|
+
const options = plainOptions(
|
|
139
|
+
value,
|
|
140
|
+
["shape", "radius", "opacity"],
|
|
141
|
+
"outlier",
|
|
142
|
+
operation
|
|
143
|
+
);
|
|
133
144
|
return Object.freeze({
|
|
134
145
|
shape: options.shape === undefined
|
|
135
146
|
? DEFAULT_OUTLIER.shape
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
validateCanvasState
|
|
7
7
|
} from "../../layout/canvas.js";
|
|
8
8
|
import { cloneAndFreeze } from "../../core/immutable.js";
|
|
9
|
+
import { validateOptionObject } from "../../core/validation.js";
|
|
9
10
|
import {
|
|
10
11
|
applyMaterializationPlan,
|
|
11
12
|
planCanvasRematerialization
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
import {
|
|
14
15
|
assertCanvasHierarchyAvailable,
|
|
15
16
|
CANVAS_GRAPHIC_ID,
|
|
17
|
+
findCanvasGraphic,
|
|
16
18
|
PLOT_GRAPHIC_ID
|
|
17
19
|
} from "../../materialization/graphicHierarchy.js";
|
|
18
20
|
|
|
@@ -24,21 +26,11 @@ const CANVAS_OPTIONS = Object.freeze([
|
|
|
24
26
|
]);
|
|
25
27
|
|
|
26
28
|
function validateOptions(args, operation, { allowEmpty = false } = {}) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (!allowEmpty && keys.length === 0) {
|
|
30
|
-
throw new TypeError(`${operation} requires at least one option.`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
for (const key of keys) {
|
|
34
|
-
if (!CANVAS_OPTIONS.includes(key)) {
|
|
35
|
-
throw new Error(`Unknown ${operation} option "${key}".`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
29
|
+
validateOptionObject(args, CANVAS_OPTIONS, operation, { allowEmpty });
|
|
38
30
|
}
|
|
39
31
|
|
|
40
32
|
function requireCanvas(program) {
|
|
41
|
-
const canvas = program
|
|
33
|
+
const canvas = findCanvasGraphic(program);
|
|
42
34
|
|
|
43
35
|
if (canvas?.type !== "canvas") {
|
|
44
36
|
throw new Error("editCanvas requires an existing canvas.");
|
|
@@ -49,8 +41,9 @@ function requireCanvas(program) {
|
|
|
49
41
|
|
|
50
42
|
function resolveCanvasState(program, args) {
|
|
51
43
|
const canvas = requireCanvas(program);
|
|
52
|
-
const
|
|
53
|
-
|
|
44
|
+
const canvasConfig = program.materializationConfigs.canvas;
|
|
45
|
+
const baseMargin = canvasConfig?.margin ?? DEFAULT_MARGIN;
|
|
46
|
+
const baseSize = canvasConfig?.size ?? { width: "explicit", height: "explicit" };
|
|
54
47
|
const state = {
|
|
55
48
|
width: Object.hasOwn(args, "width")
|
|
56
49
|
? args.width
|
|
@@ -63,7 +56,11 @@ function resolveCanvasState(program, args) {
|
|
|
63
56
|
: canvas.properties.background,
|
|
64
57
|
margin: Object.hasOwn(args, "margin")
|
|
65
58
|
? normalizeMargin(args.margin, baseMargin)
|
|
66
|
-
: baseMargin
|
|
59
|
+
: baseMargin,
|
|
60
|
+
size: {
|
|
61
|
+
width: Object.hasOwn(args, "width") ? "explicit" : baseSize.width,
|
|
62
|
+
height: Object.hasOwn(args, "height") ? "explicit" : baseSize.height
|
|
63
|
+
}
|
|
67
64
|
};
|
|
68
65
|
|
|
69
66
|
validateCanvasState(state);
|
|
@@ -90,7 +87,7 @@ export const editCanvas = action(
|
|
|
90
87
|
}
|
|
91
88
|
}
|
|
92
89
|
|
|
93
|
-
next = next._withCanvasConfig({ margin: state.margin });
|
|
90
|
+
next = next._withCanvasConfig({ margin: state.margin, size: state.size });
|
|
94
91
|
|
|
95
92
|
if (
|
|
96
93
|
Object.hasOwn(args, "width") ||
|
|
@@ -132,7 +129,7 @@ export const createCanvas = action(
|
|
|
132
129
|
: DEFAULT_CANVAS.margin
|
|
133
130
|
};
|
|
134
131
|
|
|
135
|
-
|
|
132
|
+
const next = this
|
|
136
133
|
.createGraphics({ id: CANVAS_GRAPHIC_ID, type: "canvas" })
|
|
137
134
|
.createGraphics({
|
|
138
135
|
id: PLOT_GRAPHIC_ID,
|
|
@@ -140,5 +137,12 @@ export const createCanvas = action(
|
|
|
140
137
|
parent: CANVAS_GRAPHIC_ID
|
|
141
138
|
})
|
|
142
139
|
.editCanvas(options);
|
|
140
|
+
return next._withCanvasConfig({
|
|
141
|
+
...next.materializationConfigs.canvas,
|
|
142
|
+
size: {
|
|
143
|
+
width: Object.hasOwn(args, "width") ? "explicit" : "auto",
|
|
144
|
+
height: Object.hasOwn(args, "height") ? "explicit" : "auto"
|
|
145
|
+
}
|
|
146
|
+
});
|
|
143
147
|
}
|
|
144
148
|
);
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { ChartProgram as CoreChartProgram } from "../../core/ChartProgram.js";
|
|
3
|
+
import { freezeOwned, isPlainObject } from "../../core/immutable.js";
|
|
4
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
5
|
+
import { validateOptionObject } from "../../core/validation.js";
|
|
6
|
+
import {
|
|
7
|
+
normalizeCompositionPadding,
|
|
8
|
+
resolveCompositionLayout
|
|
9
|
+
} from "../../layout/composition.js";
|
|
10
|
+
import { resolveFacetLayout } from "../../layout/facets.js";
|
|
11
|
+
import { namespaceGraphicSnapshot } from "../../materialization/compositionSnapshot.js";
|
|
12
|
+
import { materializeCompositionGraphics } from
|
|
13
|
+
"../../materialization/composition.js";
|
|
14
|
+
import { materializeFacetGraphics } from "../../materialization/facets.js";
|
|
15
|
+
|
|
16
|
+
const CONCAT_OPTIONS = Object.freeze([
|
|
17
|
+
"id", "programs", "gap", "align", "padding"
|
|
18
|
+
]);
|
|
19
|
+
const LAYOUT_EDIT_OPTIONS = Object.freeze(["gap", "align", "padding"]);
|
|
20
|
+
const REPLACEMENT_OPTIONS = Object.freeze(["target", "program"]);
|
|
21
|
+
|
|
22
|
+
function normalizeChildEntry(entry, index) {
|
|
23
|
+
const fallbackId = `view-${index + 1}`;
|
|
24
|
+
if (entry instanceof CoreChartProgram) {
|
|
25
|
+
return { id: fallbackId, program: entry };
|
|
26
|
+
}
|
|
27
|
+
if (!isPlainObject(entry)) {
|
|
28
|
+
throw new TypeError(`Composition program ${index} must be a ChartProgram or wrapper.`);
|
|
29
|
+
}
|
|
30
|
+
validateOptionObject(entry, ["id", "program"], `Composition program ${index}`);
|
|
31
|
+
if (!(entry.program instanceof CoreChartProgram)) {
|
|
32
|
+
throw new TypeError(`Composition program ${index}.program must be a ChartProgram.`);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
id: entry.id === undefined
|
|
36
|
+
? fallbackId
|
|
37
|
+
: validateUserId(entry.id, "Composition child ID"),
|
|
38
|
+
program: entry.program
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function childDescriptor({ id, program }) {
|
|
43
|
+
if (program.actionStack.length !== 0) {
|
|
44
|
+
throw new Error(`Composition child "${id}" has an unfinished action stack.`);
|
|
45
|
+
}
|
|
46
|
+
namespaceGraphicSnapshot(program.graphicSpec, { namespace: id });
|
|
47
|
+
const canvasId = program.graphicSpec.order.find(
|
|
48
|
+
graphicId => program.graphicSpec.objects[graphicId]?.type === "canvas"
|
|
49
|
+
);
|
|
50
|
+
const canvas = program.graphicSpec.objects[canvasId];
|
|
51
|
+
const size = program.materializationConfigs.canvas?.size ?? {
|
|
52
|
+
width: "explicit",
|
|
53
|
+
height: "explicit"
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
id,
|
|
57
|
+
width: canvas.properties.width,
|
|
58
|
+
height: canvas.properties.height,
|
|
59
|
+
widthMode: size.width,
|
|
60
|
+
heightMode: size.height
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeCompositionInput(args, direction) {
|
|
65
|
+
validateOptionObject(args, CONCAT_OPTIONS, direction === "horizontal" ? "hconcat" : "vconcat");
|
|
66
|
+
if (!Array.isArray(args.programs) || args.programs.length < 2) {
|
|
67
|
+
throw new TypeError("Composition requires at least two programs.");
|
|
68
|
+
}
|
|
69
|
+
const entries = args.programs.map(normalizeChildEntry);
|
|
70
|
+
const ids = entries.map(entry => entry.id);
|
|
71
|
+
if (new Set(ids).size !== ids.length) {
|
|
72
|
+
throw new Error("Composition child IDs must be unique.");
|
|
73
|
+
}
|
|
74
|
+
const id = args.id === undefined
|
|
75
|
+
? "composition"
|
|
76
|
+
: validateUserId(args.id, "Composition ID");
|
|
77
|
+
const layout = resolveCompositionLayout({
|
|
78
|
+
direction,
|
|
79
|
+
children: entries.map(childDescriptor),
|
|
80
|
+
...(Object.hasOwn(args, "gap") ? { gap: args.gap } : {}),
|
|
81
|
+
...(Object.hasOwn(args, "align") ? { align: args.align } : {}),
|
|
82
|
+
...(Object.hasOwn(args, "padding") ? { padding: args.padding } : {})
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
children: freezeOwned(Object.fromEntries(
|
|
86
|
+
entries.map(entry => [entry.id, entry.program])
|
|
87
|
+
)),
|
|
88
|
+
compositionSpec: {
|
|
89
|
+
id,
|
|
90
|
+
direction,
|
|
91
|
+
children: ids,
|
|
92
|
+
gap: layout.gap,
|
|
93
|
+
align: layout.align,
|
|
94
|
+
padding: layout.padding
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const useProgram = action(
|
|
100
|
+
{
|
|
101
|
+
op: "useProgram",
|
|
102
|
+
description: "Retain one named child program in a composition.",
|
|
103
|
+
scope: "composition"
|
|
104
|
+
},
|
|
105
|
+
function ({ id } = {}) {
|
|
106
|
+
validateUserId(id, "Composition child ID");
|
|
107
|
+
if (!Object.hasOwn(this.children, id)) {
|
|
108
|
+
throw new Error(`Unknown composition child "${id}".`);
|
|
109
|
+
}
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const materializeComposition = action(
|
|
115
|
+
{
|
|
116
|
+
op: "materializeComposition",
|
|
117
|
+
description: "Materialize retained child programs into one graphic tree.",
|
|
118
|
+
scope: "composition"
|
|
119
|
+
},
|
|
120
|
+
function () {
|
|
121
|
+
return this.compositionSpec.type === "facet"
|
|
122
|
+
? materializeFacetGraphics(this)
|
|
123
|
+
: materializeCompositionGraphics(this);
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
function concatAction(direction, op) {
|
|
128
|
+
return action(
|
|
129
|
+
{
|
|
130
|
+
op,
|
|
131
|
+
description: direction === "horizontal"
|
|
132
|
+
? "Compose complete programs horizontally."
|
|
133
|
+
: "Compose complete programs vertically."
|
|
134
|
+
},
|
|
135
|
+
function (args = {}) {
|
|
136
|
+
const state = normalizeCompositionInput(args, direction);
|
|
137
|
+
let next = this._withCompositionState(state);
|
|
138
|
+
for (const id of state.compositionSpec.children) {
|
|
139
|
+
next = next.useProgram({ id });
|
|
140
|
+
}
|
|
141
|
+
return next.materializeComposition();
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const hconcatAction = concatAction("horizontal", "hconcat");
|
|
147
|
+
export const vconcatAction = concatAction("vertical", "vconcat");
|
|
148
|
+
|
|
149
|
+
const editCompositionLayout = action(
|
|
150
|
+
{
|
|
151
|
+
op: "editCompositionLayout",
|
|
152
|
+
description: "Edit composition spacing and alignment.",
|
|
153
|
+
scope: "composition"
|
|
154
|
+
},
|
|
155
|
+
function (args = {}) {
|
|
156
|
+
validateOptionObject(args, LAYOUT_EDIT_OPTIONS, "editCompositionLayout");
|
|
157
|
+
if (!LAYOUT_EDIT_OPTIONS.some(option => Object.hasOwn(args, option))) {
|
|
158
|
+
throw new TypeError("editCompositionLayout requires at least one layout option.");
|
|
159
|
+
}
|
|
160
|
+
const current = this.compositionSpec;
|
|
161
|
+
if (current.type === "facet") {
|
|
162
|
+
const layout = resolveFacetLayout({
|
|
163
|
+
children: current.children.map((id, index) => ({
|
|
164
|
+
...childDescriptor({ id, program: this.children[id] }),
|
|
165
|
+
value: current.facet.values[index]
|
|
166
|
+
})),
|
|
167
|
+
columns: current.columns,
|
|
168
|
+
gap: Object.hasOwn(args, "gap") ? args.gap : current.gap,
|
|
169
|
+
align: Object.hasOwn(args, "align") ? args.align : current.align,
|
|
170
|
+
padding: Object.hasOwn(args, "padding")
|
|
171
|
+
? normalizeCompositionPadding(args.padding, current.padding)
|
|
172
|
+
: current.padding,
|
|
173
|
+
sharedLegend: current.facet.guides.legend === "shared"
|
|
174
|
+
});
|
|
175
|
+
return this._withCompositionState({
|
|
176
|
+
children: this.children,
|
|
177
|
+
compositionSpec: {
|
|
178
|
+
...current,
|
|
179
|
+
gap: layout.gap,
|
|
180
|
+
align: layout.align,
|
|
181
|
+
padding: layout.padding
|
|
182
|
+
}
|
|
183
|
+
}).materializeComposition();
|
|
184
|
+
}
|
|
185
|
+
const padding = Object.hasOwn(args, "padding")
|
|
186
|
+
? normalizeCompositionPadding(args.padding, current.padding)
|
|
187
|
+
: current.padding;
|
|
188
|
+
const layout = resolveCompositionLayout({
|
|
189
|
+
direction: current.direction,
|
|
190
|
+
children: current.children.map(id => childDescriptor({
|
|
191
|
+
id,
|
|
192
|
+
program: this.children[id]
|
|
193
|
+
})),
|
|
194
|
+
gap: Object.hasOwn(args, "gap") ? args.gap : current.gap,
|
|
195
|
+
align: Object.hasOwn(args, "align") ? args.align : current.align,
|
|
196
|
+
padding
|
|
197
|
+
});
|
|
198
|
+
return this._withCompositionState({
|
|
199
|
+
children: this.children,
|
|
200
|
+
compositionSpec: {
|
|
201
|
+
...current,
|
|
202
|
+
gap: layout.gap,
|
|
203
|
+
align: layout.align,
|
|
204
|
+
padding: layout.padding
|
|
205
|
+
}
|
|
206
|
+
}).materializeComposition();
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const replaceCompositionChild = action(
|
|
211
|
+
{
|
|
212
|
+
op: "replaceCompositionChild",
|
|
213
|
+
description: "Replace one composition child without changing its slot.",
|
|
214
|
+
scope: "composition"
|
|
215
|
+
},
|
|
216
|
+
function (args = {}) {
|
|
217
|
+
validateOptionObject(args, REPLACEMENT_OPTIONS, "replaceCompositionChild");
|
|
218
|
+
if (this.compositionSpec.type === "facet") {
|
|
219
|
+
throw new Error("replaceCompositionChild is not available on a facet composition.");
|
|
220
|
+
}
|
|
221
|
+
const target = validateUserId(args.target, "Composition child target");
|
|
222
|
+
if (!Object.hasOwn(this.children, target)) {
|
|
223
|
+
throw new Error(`Unknown composition child "${target}".`);
|
|
224
|
+
}
|
|
225
|
+
if (!(args.program instanceof CoreChartProgram)) {
|
|
226
|
+
throw new TypeError("replaceCompositionChild program must be a ChartProgram.");
|
|
227
|
+
}
|
|
228
|
+
childDescriptor({ id: target, program: args.program });
|
|
229
|
+
const children = freezeOwned({
|
|
230
|
+
...this.children,
|
|
231
|
+
[target]: args.program
|
|
232
|
+
});
|
|
233
|
+
return this._withCompositionState({
|
|
234
|
+
children,
|
|
235
|
+
compositionSpec: this.compositionSpec
|
|
236
|
+
})
|
|
237
|
+
.useProgram({ id: target })
|
|
238
|
+
.materializeComposition();
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
export function registerCompositionActions(ProgramClass) {
|
|
243
|
+
ProgramClass.prototype.useProgram = useProgram;
|
|
244
|
+
ProgramClass.prototype.materializeComposition = materializeComposition;
|
|
245
|
+
ProgramClass.prototype.editCompositionLayout = editCompositionLayout;
|
|
246
|
+
ProgramClass.prototype.replaceCompositionChild = replaceCompositionChild;
|
|
247
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerCompositionActions } from "./actions.js";
|
|
@@ -3,15 +3,12 @@ import { validateCoordinateType } from "../../grammar/coordinates.js";
|
|
|
3
3
|
import { validateUserId } from "../../core/identifiers.js";
|
|
4
4
|
import { findCoordinate } from "../../selectors/coordinates.js";
|
|
5
5
|
import { findLayer, hasLayer } from "../../selectors/layers.js";
|
|
6
|
+
import { validateOptionObject } from "../../core/validation.js";
|
|
6
7
|
|
|
7
8
|
const COORDINATE_OPTIONS = Object.freeze(["id", "type", "layers"]);
|
|
8
9
|
|
|
9
10
|
function validateOptions(args) {
|
|
10
|
-
|
|
11
|
-
if (!COORDINATE_OPTIONS.includes(key)) {
|
|
12
|
-
throw new Error(`Unknown createCoordinate option "${key}".`);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
11
|
+
validateOptionObject(args, COORDINATE_OPTIONS, "createCoordinate");
|
|
15
12
|
}
|
|
16
13
|
|
|
17
14
|
function validateLayers(program, layers) {
|
|
@@ -22,7 +22,13 @@ export const materializeDensityData = action(
|
|
|
22
22
|
return this
|
|
23
23
|
.editSemantic({
|
|
24
24
|
property: `dataset[${id}].transform`,
|
|
25
|
-
value: [{
|
|
25
|
+
value: [{
|
|
26
|
+
...transform,
|
|
27
|
+
resolved: {
|
|
28
|
+
bandwidth: result.bandwidth,
|
|
29
|
+
extent: result.extent
|
|
30
|
+
}
|
|
31
|
+
}]
|
|
26
32
|
})
|
|
27
33
|
.editSemantic({
|
|
28
34
|
property: `dataset[${id}].values`,
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
normalizeMarkFilterTransform
|
|
11
11
|
} from "../../grammar/markFilter.js";
|
|
12
12
|
import { resolveMarkFilterSelection } from "../../materialization/selection/filter.js";
|
|
13
|
+
import { findSelectionPolicy } from
|
|
14
|
+
"../../materialization/selection/policies/index.js";
|
|
13
15
|
import {
|
|
14
16
|
applyLayerDataRematerialization
|
|
15
17
|
} from "../../materialization/dependencies.js";
|
|
@@ -121,7 +123,7 @@ export const filterMarks = action(
|
|
|
121
123
|
label: "filter mark",
|
|
122
124
|
predicate: candidate =>
|
|
123
125
|
candidate.data !== undefined &&
|
|
124
|
-
|
|
126
|
+
findSelectionPolicy(candidate.mark?.type) !== undefined
|
|
125
127
|
});
|
|
126
128
|
const derivedId = `${layer.id}FilteredData`;
|
|
127
129
|
if (hasDataset(this, derivedId)) {
|
|
@@ -4,7 +4,7 @@ import { validateKeys } from "../../core/validation.js";
|
|
|
4
4
|
import {
|
|
5
5
|
deriveRegression,
|
|
6
6
|
normalizeRegressionParameters
|
|
7
|
-
} from "../../grammar/regression.js";
|
|
7
|
+
} from "../../grammar/regression/index.js";
|
|
8
8
|
import { MATERIALIZE_OPTIONS, requireDerivedDataset } from "./shared.js";
|
|
9
9
|
|
|
10
10
|
const OPTIONS = Object.freeze([
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
readQuantitativeField,
|
|
5
5
|
readScaleField,
|
|
6
6
|
validateOpacityValue
|
|
7
|
-
} from "../../grammar/scales.js";
|
|
7
|
+
} from "../../grammar/scales/index.js";
|
|
8
8
|
import {
|
|
9
9
|
resolveAppearanceScaleDefinition,
|
|
10
10
|
resolveOpacityScaleDefinition
|
|
@@ -119,6 +119,16 @@ const encodeRadius = action(
|
|
|
119
119
|
}
|
|
120
120
|
);
|
|
121
121
|
|
|
122
|
+
const encodePointRadius = action(
|
|
123
|
+
{
|
|
124
|
+
op: "encodePointRadius",
|
|
125
|
+
description: "Set a constant graphical radius on a point mark."
|
|
126
|
+
},
|
|
127
|
+
function (args = {}) {
|
|
128
|
+
return this.encodeRadius(args);
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
|
|
122
132
|
const encodeSize = action(
|
|
123
133
|
{
|
|
124
134
|
op: "encodeSize",
|
|
@@ -236,6 +246,7 @@ const encodeOpacity = action(
|
|
|
236
246
|
|
|
237
247
|
export function registerAppearanceEncodingAction(ProgramClass) {
|
|
238
248
|
ProgramClass.prototype.encodeRadius = encodeRadius;
|
|
249
|
+
ProgramClass.prototype.encodePointRadius = encodePointRadius;
|
|
239
250
|
ProgramClass.prototype.encodeSize = encodeSize;
|
|
240
251
|
ProgramClass.prototype.encodeShape = encodeShape;
|
|
241
252
|
ProgramClass.prototype.encodeOpacity = encodeOpacity;
|
|
@@ -3,6 +3,7 @@ import { resolveTarget, validateOptions } from "./shared.js";
|
|
|
3
3
|
import {
|
|
4
4
|
BAR_GRAINS,
|
|
5
5
|
resolveBarColorLayout,
|
|
6
|
+
resolveBarOffsetChannel,
|
|
6
7
|
resolveBarGrain
|
|
7
8
|
} from "../../grammar/bars/policy.js";
|
|
8
9
|
import { normalizeBarWidth } from "../../grammar/bars/geometry.js";
|
|
@@ -12,7 +13,7 @@ const OPTIONS = Object.freeze(["band", "pixels", "target"]);
|
|
|
12
13
|
const encodeBarWidth = action(
|
|
13
14
|
{
|
|
14
15
|
op: "encodeBarWidth",
|
|
15
|
-
description: "
|
|
16
|
+
description: "Override aggregate or ranged bar width within its category slot."
|
|
16
17
|
},
|
|
17
18
|
function (args = {}) {
|
|
18
19
|
validateOptions(args, OPTIONS, "encodeBarWidth");
|
|
@@ -28,6 +29,7 @@ const encodeBarWidth = action(
|
|
|
28
29
|
this.markConfigs[target]?.barWidth
|
|
29
30
|
);
|
|
30
31
|
const grouped = layout === "group";
|
|
32
|
+
const offsetChannel = resolveBarOffsetChannel(layer);
|
|
31
33
|
const grain = resolveBarGrain(layer);
|
|
32
34
|
if (grain === BAR_GRAINS.ranged) {
|
|
33
35
|
return this._withMarkConfig(target, { ...this.markConfigs[target], barWidth: width })
|
|
@@ -37,11 +39,11 @@ const encodeBarWidth = action(
|
|
|
37
39
|
grain !== BAR_GRAINS.aggregate ||
|
|
38
40
|
(grouped &&
|
|
39
41
|
layer.encoding?.color?.field !== undefined &&
|
|
40
|
-
layer.encoding?.
|
|
42
|
+
layer.encoding?.[offsetChannel]?.field !== layer.encoding.color.field)
|
|
41
43
|
) {
|
|
42
44
|
throw new Error(
|
|
43
45
|
grouped
|
|
44
|
-
?
|
|
46
|
+
? `encodeBarWidth requires complete grouped bar x, y, color, and ${offsetChannel} encodings.`
|
|
45
47
|
: "encodeBarWidth requires complete aggregate bar x and y encodings."
|
|
46
48
|
);
|
|
47
49
|
}
|