ggaction 0.0.9 → 0.0.11
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 +84 -0
- package/README.md +5 -4
- package/knowledge/action-card.schema.json +1 -0
- package/knowledge/action-cards.json +1 -10941
- package/knowledge/action-cards.schema.json +1 -0
- package/knowledge/extension-authoring.md +248 -0
- package/knowledge/intent-taxonomy.json +1 -183
- package/knowledge/intent-taxonomy.schema.json +1 -0
- package/knowledge/mcp-resources.json +1 -95
- package/knowledge/mcp-resources.schema.json +1 -0
- package/knowledge/task-packet.schema.json +1 -159
- package/knowledge/task-resolver.js +1 -1230
- package/package.json +19 -2
- package/src/actions/boxPlots/edit.js +45 -298
- package/src/actions/boxPlots/options.js +46 -77
- package/src/actions/boxPlots/resolve.js +1 -1
- package/src/actions/charts/bar.js +3 -0
- package/src/actions/charts/heatmap.js +73 -83
- package/src/actions/charts/index.js +1 -1
- package/src/actions/charts/parallel.js +35 -1
- package/src/actions/charts/shared.js +20 -0
- package/src/actions/composition/actions.js +46 -49
- package/src/actions/data/bin2d.js +105 -2
- package/src/actions/data/box.js +17 -17
- package/src/actions/data/density.js +42 -78
- package/src/actions/data/gradientProfile.js +22 -52
- package/src/actions/data/horizon.js +16 -41
- package/src/actions/data/index.js +10 -10
- package/src/actions/data/interval.js +16 -38
- package/src/actions/data/intervalEdit.js +55 -0
- package/src/actions/data/regression.js +7 -17
- package/src/actions/data/shared.js +47 -0
- package/src/actions/data/timeUnit.js +14 -38
- package/src/actions/data/window.js +14 -38
- package/src/actions/distributions/revision.js +154 -34
- package/src/actions/encodings/angle.js +9 -10
- package/src/actions/encodings/appearance.js +17 -51
- package/src/actions/encodings/density/resolve.js +3 -1
- package/src/actions/encodings/density.js +1 -15
- package/src/actions/encodings/horizon/resolve.js +13 -3
- package/src/actions/encodings/horizon.js +8 -7
- package/src/actions/encodings/index.js +9 -9
- package/src/actions/encodings/offset.js +8 -24
- package/src/actions/encodings/parallel.js +9 -10
- package/src/actions/encodings/pathOrder.js +6 -13
- package/src/actions/encodings/position/policies/index.js +4 -4
- package/src/actions/encodings/position/policies/line.js +47 -17
- package/src/actions/encodings/position/resolve.js +24 -5
- package/src/actions/encodings/ranged.js +18 -23
- package/src/actions/encodings/remove.js +15 -25
- package/src/actions/encodings/ruleAppearance.js +9 -24
- package/src/actions/encodings/shared.js +37 -0
- package/src/actions/encodings/strokeDash.js +8 -23
- package/src/actions/encodings/text.js +9 -10
- package/src/actions/errorBands/create.js +105 -39
- package/src/actions/errorBands/edit.js +89 -93
- package/src/actions/errorBands/index.js +1 -2
- package/src/actions/errorBars/create.js +78 -27
- package/src/actions/errorBars/edit.js +81 -48
- package/src/actions/errorBars/index.js +1 -2
- package/src/actions/facets/actions.js +45 -69
- package/src/actions/facets/derive.js +38 -53
- package/src/actions/facets/replay.js +0 -1
- package/src/actions/gradientPlots/components.js +59 -48
- package/src/actions/gradientPlots/edit.js +35 -216
- package/src/actions/gradientPlots/index.js +11 -10
- package/src/actions/gradientPlots/materialize.js +21 -35
- package/src/actions/gradientPlots/options.js +5 -7
- package/src/actions/gradientPlots/paint.js +9 -6
- package/src/actions/gradientPlots/resolve.js +1 -1
- package/src/actions/guides/axes/index.js +3 -3
- package/src/actions/guides/axes/labels.js +68 -26
- package/src/actions/guides/axes/lines.js +22 -2
- package/src/actions/guides/axes/policy.js +28 -8
- package/src/actions/guides/axes/ticks.js +27 -9
- package/src/actions/guides/axes/titles.js +113 -65
- package/src/actions/guides/grids/resolve.js +14 -0
- package/src/actions/guides/guides.js +2 -4
- package/src/actions/guides/index.js +2 -2
- package/src/actions/guides/legends/categorical/actions.js +44 -68
- package/src/actions/guides/legends/categorical/components.js +6 -1
- package/src/actions/guides/legends/categorical/index.js +2 -2
- package/src/actions/guides/legends/categorical/layout.js +148 -175
- package/src/actions/guides/legends/categorical/recipes.js +5 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -1
- package/src/actions/guides/legends/continuous/common.js +117 -66
- package/src/actions/guides/legends/continuous/gradient.js +38 -37
- package/src/actions/guides/legends/continuous/interval.js +97 -76
- package/src/actions/guides/legends/continuous/opacity.js +28 -38
- package/src/actions/guides/legends/edit.js +14 -1
- package/src/actions/guides/legends/index.js +1 -1
- package/src/actions/guides/legends/lane.js +2 -1
- package/src/actions/guides/legends/remove.js +8 -0
- package/src/actions/guides/legends/size.js +36 -43
- package/src/actions/guides/legends/strokeWidth.js +20 -26
- package/src/actions/guides/polar/axes/labels.js +17 -1
- package/src/actions/guides/polar/axes/shared.js +7 -1
- package/src/actions/guides/polar/axes/titles.js +19 -2
- package/src/actions/guides/polar/grids.js +4 -0
- package/src/actions/guides/polar/resolve.js +6 -0
- package/src/actions/index.js +13 -13
- package/src/actions/intervals/resolve.js +87 -117
- package/src/actions/marks/arc/actions.js +31 -36
- package/src/actions/marks/area/actions.js +23 -31
- package/src/actions/marks/index.js +6 -6
- package/src/actions/marks/line/actions.js +33 -52
- package/src/actions/marks/line/materialize.js +7 -5
- package/src/actions/marks/point/materialize.js +5 -3
- package/src/actions/marks/remove.js +21 -0
- package/src/actions/marks/rule/actions.js +53 -53
- package/src/actions/marks/shared.js +8 -4
- package/src/actions/marks/text/actions.js +14 -6
- package/src/actions/marks/text/layout.js +21 -45
- package/src/actions/marks/tick/actions.js +22 -21
- package/src/actions/primitives/createGraphics.js +4 -0
- package/src/actions/primitives/editGraphics.js +4 -0
- package/src/actions/primitives/index.js +3 -3
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +2 -2
- package/src/actions/primitives/semanticValidation/layer.js +1 -1
- package/src/actions/regression/edit.js +3 -43
- package/src/actions/scales/consumers/families.js +24 -4
- package/src/actions/scales/consumers/index.js +1 -1
- package/src/actions/scales/consumers/seriesLayout.js +17 -0
- package/src/actions/scales/definitions.js +100 -103
- package/src/actions/scales/materialize.js +5 -0
- package/src/actions/selection/actions.js +79 -121
- package/src/actions/titles/actions.js +48 -94
- package/src/actions/titles/resolve.js +14 -27
- package/src/actions/violinPlots/create.js +37 -44
- package/src/core/action.js +20 -5
- package/src/core/extensionRegistry.js +104 -0
- package/src/core/textMetrics.js +74 -37
- package/src/core/validation.js +18 -0
- package/src/extension.js +8 -1
- package/src/grammar/aggregate.js +48 -28
- package/src/grammar/arcs.js +58 -11
- package/src/grammar/areaSeries.js +14 -16
- package/src/grammar/bin2d.js +37 -23
- package/src/grammar/boxPlot.js +14 -13
- package/src/grammar/categoricalDensity.js +14 -14
- package/src/grammar/categoryOrder.js +8 -14
- package/src/grammar/curveCommands.js +87 -20
- package/src/grammar/density.js +162 -141
- package/src/grammar/facets/index.js +14 -0
- package/src/grammar/facets/scales.js +7 -4
- package/src/grammar/gradientProfile.js +12 -15
- package/src/grammar/histogram.js +145 -83
- package/src/grammar/horizon.js +60 -23
- package/src/grammar/interval.js +14 -13
- package/src/grammar/lineSeries.js +91 -61
- package/src/grammar/numeric.js +290 -0
- package/src/grammar/paint.js +3 -4
- package/src/grammar/palettes.js +21 -32
- package/src/grammar/pathCommands.js +6 -0
- package/src/grammar/polarPaths.js +5 -5
- package/src/grammar/regression/derive.js +48 -9
- package/src/grammar/regression/models.js +248 -69
- package/src/grammar/regression/parameters.js +8 -0
- package/src/grammar/scales/color.js +35 -26
- package/src/grammar/scales/continuous.js +71 -24
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/discretized.js +42 -12
- package/src/grammar/scales/fields.js +21 -4
- package/src/grammar/scales/ordinal.js +117 -61
- package/src/grammar/scales/temporal.js +40 -11
- package/src/grammar/scales/transformed.js +199 -52
- package/src/grammar/schemas/graphicBounds.js +270 -79
- package/src/grammar/seriesLayout.js +50 -18
- package/src/grammar/ticks.js +123 -53
- package/src/grammar/timeUnit.js +18 -8
- package/src/grammar/transforms.js +4 -1
- package/src/grammar/window.js +70 -57
- package/src/index.js +1 -1
- package/src/layout/canvas.js +4 -8
- package/src/layout/composition.js +39 -40
- package/src/layout/facets.js +10 -14
- package/src/layout/labels.js +91 -15
- package/src/layout/legend.js +22 -20
- package/src/layout/legendLane.js +54 -40
- package/src/layout/title.js +7 -7
- package/src/materialization/bars/aggregate.js +14 -40
- package/src/materialization/bars/grouped.js +7 -18
- package/src/materialization/bars/histogram.js +2 -19
- package/src/materialization/bars/ranged.js +20 -15
- package/src/materialization/bars/resolve.js +21 -0
- package/src/materialization/facetGuides/legacyCategorical.js +2 -1
- package/src/materialization/facetGuides/placement.js +34 -19
- package/src/materialization/facetGuides/preparation.js +9 -5
- package/src/materialization/facets.js +39 -27
- package/src/materialization/guides/resources.js +2 -1
- package/src/materialization/marks/capabilities.js +5 -2
- package/src/materialization/marks/index.js +24 -4
- package/src/materialization/scales/policies/bar.js +61 -59
- package/src/materialization/scales/policies/binnedPosition.js +34 -0
- package/src/materialization/scales/policies/series.js +13 -7
- package/src/materialization/scales/resolve.js +4 -5
- package/src/materialization/selection/items/arc.js +16 -2
- package/src/materialization/selection/items/bar.js +4 -2
- package/src/materialization/selection/items/common.js +21 -1
- package/src/materialization/selection/items/path.js +19 -2
- package/src/materialization/selection/items/point.js +4 -16
- package/src/materialization/selection/items/tick.js +13 -21
- package/src/materialization/selection/point.js +11 -4
- package/src/materialization/selection/styles.js +19 -27
- package/src/materialization/text.js +4 -1
- package/src/mcp/adapter.js +8 -3
- package/src/renderers/canvas/fill.js +2 -2
- package/src/renderers/canvas/index.js +35 -2
- package/src/renderers/canvas/native.js +160 -0
- package/src/renderers/pdf.js +14 -0
- package/src/renderers/svg.js +37 -4
- package/types/extension.d.ts +19 -0
- package/types/index.d.ts +3 -0
- package/types/program.d.ts +509 -131
- package/types/svg.d.ts +1 -0
- package/src/actions/errorBands/components.js +0 -81
- package/src/actions/errorBands/options.js +0 -39
- package/src/actions/errorBands/resolve.js +0 -28
- package/src/actions/errorBars/components.js +0 -62
- package/src/actions/errorBars/options.js +0 -58
- package/src/actions/errorBars/resolve.js +0 -25
|
@@ -15,18 +15,26 @@ export const BOX_PLOT_OPTIONS = Object.freeze([
|
|
|
15
15
|
"width", "outliers", "box", "median", "outlier", "guides"
|
|
16
16
|
]);
|
|
17
17
|
|
|
18
|
-
const DEFAULT_BOX =
|
|
18
|
+
const DEFAULT_BOX = {
|
|
19
19
|
fill: DEFAULT_COLORS.mark,
|
|
20
20
|
opacity: 1,
|
|
21
21
|
stroke: DEFAULT_COLORS.mark,
|
|
22
22
|
strokeWidth: 1.5
|
|
23
|
-
}
|
|
24
|
-
const DEFAULT_MEDIAN =
|
|
25
|
-
const DEFAULT_OUTLIER =
|
|
23
|
+
};
|
|
24
|
+
const DEFAULT_MEDIAN = { stroke: "#1f2937", strokeWidth: 1.5 };
|
|
25
|
+
const DEFAULT_OUTLIER = {
|
|
26
26
|
shape: "diamond",
|
|
27
27
|
radius: 3,
|
|
28
28
|
opacity: 0.75
|
|
29
|
-
}
|
|
29
|
+
};
|
|
30
|
+
const APPEARANCE_VALIDATORS = {
|
|
31
|
+
fill: validateNonEmptyString,
|
|
32
|
+
opacity: validateUnitInterval,
|
|
33
|
+
stroke: validateNonEmptyString,
|
|
34
|
+
strokeWidth: validateNonNegativeFinite,
|
|
35
|
+
shape: validatePointShape,
|
|
36
|
+
radius: validatePositiveFinite
|
|
37
|
+
};
|
|
30
38
|
|
|
31
39
|
function plainOptions(value, keys, label, operation = "createBoxPlot") {
|
|
32
40
|
if (value === undefined) return {};
|
|
@@ -37,13 +45,29 @@ function plainOptions(value, keys, label, operation = "createBoxPlot") {
|
|
|
37
45
|
return value;
|
|
38
46
|
}
|
|
39
47
|
|
|
48
|
+
function resolveAppearance(value, defaults, label, operation) {
|
|
49
|
+
const options = plainOptions(
|
|
50
|
+
value,
|
|
51
|
+
Object.keys(defaults),
|
|
52
|
+
label,
|
|
53
|
+
operation
|
|
54
|
+
);
|
|
55
|
+
return Object.freeze(Object.fromEntries(
|
|
56
|
+
Object.entries(defaults).map(([key, fallback]) => [
|
|
57
|
+
key,
|
|
58
|
+
options[key] === undefined
|
|
59
|
+
? fallback
|
|
60
|
+
: APPEARANCE_VALIDATORS[key](
|
|
61
|
+
options[key],
|
|
62
|
+
`createBoxPlot ${label}.${key}`
|
|
63
|
+
)
|
|
64
|
+
])
|
|
65
|
+
));
|
|
66
|
+
}
|
|
67
|
+
|
|
40
68
|
export function resolveBoxPosition(value, label, operation = "createBoxPlot") {
|
|
41
69
|
if (value === undefined) return undefined;
|
|
42
|
-
|
|
43
|
-
throw new TypeError(`${operation} ${label} must be a plain object.`);
|
|
44
|
-
}
|
|
45
|
-
validateKeys(value, ["field", "fieldType", "scale"], `${operation} ${label}`);
|
|
46
|
-
return value;
|
|
70
|
+
return plainOptions(value, ["field", "fieldType", "scale"], label, operation);
|
|
47
71
|
}
|
|
48
72
|
|
|
49
73
|
export function boxEncodingArgs(value) {
|
|
@@ -61,21 +85,23 @@ export function resolveBoxGuides(value) {
|
|
|
61
85
|
|
|
62
86
|
export function resolveBoxWhisker(value, operation = "createBoxPlot") {
|
|
63
87
|
if (value === undefined) return Object.freeze({ type: "tukey", factor: 1.5 });
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
const options = plainOptions(
|
|
89
|
+
value,
|
|
90
|
+
["type", "factor"],
|
|
91
|
+
"whisker",
|
|
92
|
+
operation
|
|
93
|
+
);
|
|
94
|
+
const type = options.type ?? "tukey";
|
|
69
95
|
if (!["tukey", "minmax"].includes(type)) {
|
|
70
96
|
throw new Error(`Unsupported createBoxPlot whisker type "${type}".`);
|
|
71
97
|
}
|
|
72
98
|
if (type === "minmax") {
|
|
73
|
-
if (
|
|
99
|
+
if (options.factor !== undefined) {
|
|
74
100
|
throw new Error("createBoxPlot minmax whiskers do not accept factor.");
|
|
75
101
|
}
|
|
76
102
|
return Object.freeze({ type });
|
|
77
103
|
}
|
|
78
|
-
const factor =
|
|
104
|
+
const factor = options.factor ?? 1.5;
|
|
79
105
|
if (!Number.isFinite(factor) || factor <= 0) {
|
|
80
106
|
throw new RangeError(
|
|
81
107
|
"createBoxPlot whisker factor must be positive and finite."
|
|
@@ -96,70 +122,13 @@ export function resolveBoxWidth(value, operation = "createBoxPlot") {
|
|
|
96
122
|
}
|
|
97
123
|
|
|
98
124
|
export function resolveBoxAppearance(value, operation = "createBoxPlot") {
|
|
99
|
-
|
|
100
|
-
value,
|
|
101
|
-
["fill", "opacity", "stroke", "strokeWidth"],
|
|
102
|
-
"box",
|
|
103
|
-
operation
|
|
104
|
-
);
|
|
105
|
-
return Object.freeze({
|
|
106
|
-
fill: options.fill === undefined
|
|
107
|
-
? DEFAULT_BOX.fill
|
|
108
|
-
: validateNonEmptyString(options.fill, "createBoxPlot box.fill"),
|
|
109
|
-
opacity: options.opacity === undefined
|
|
110
|
-
? DEFAULT_BOX.opacity
|
|
111
|
-
: validateUnitInterval(options.opacity, "createBoxPlot box.opacity"),
|
|
112
|
-
stroke: options.stroke === undefined
|
|
113
|
-
? DEFAULT_BOX.stroke
|
|
114
|
-
: validateNonEmptyString(options.stroke, "createBoxPlot box.stroke"),
|
|
115
|
-
strokeWidth: options.strokeWidth === undefined
|
|
116
|
-
? DEFAULT_BOX.strokeWidth
|
|
117
|
-
: validateNonNegativeFinite(
|
|
118
|
-
options.strokeWidth,
|
|
119
|
-
"createBoxPlot box.strokeWidth"
|
|
120
|
-
)
|
|
121
|
-
});
|
|
125
|
+
return resolveAppearance(value, DEFAULT_BOX, "box", operation);
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
export function resolveBoxMedianAppearance(value, operation = "createBoxPlot") {
|
|
125
|
-
|
|
126
|
-
value,
|
|
127
|
-
["stroke", "strokeWidth"],
|
|
128
|
-
"median",
|
|
129
|
-
operation
|
|
130
|
-
);
|
|
131
|
-
return Object.freeze({
|
|
132
|
-
stroke: options.stroke === undefined
|
|
133
|
-
? DEFAULT_MEDIAN.stroke
|
|
134
|
-
: validateNonEmptyString(options.stroke, "createBoxPlot median.stroke"),
|
|
135
|
-
strokeWidth: options.strokeWidth === undefined
|
|
136
|
-
? DEFAULT_MEDIAN.strokeWidth
|
|
137
|
-
: validateNonNegativeFinite(
|
|
138
|
-
options.strokeWidth,
|
|
139
|
-
"createBoxPlot median.strokeWidth"
|
|
140
|
-
)
|
|
141
|
-
});
|
|
129
|
+
return resolveAppearance(value, DEFAULT_MEDIAN, "median", operation);
|
|
142
130
|
}
|
|
143
131
|
|
|
144
132
|
export function resolveBoxOutlierAppearance(value, operation = "createBoxPlot") {
|
|
145
|
-
|
|
146
|
-
value,
|
|
147
|
-
["shape", "radius", "opacity"],
|
|
148
|
-
"outlier",
|
|
149
|
-
operation
|
|
150
|
-
);
|
|
151
|
-
return Object.freeze({
|
|
152
|
-
shape: options.shape === undefined
|
|
153
|
-
? DEFAULT_OUTLIER.shape
|
|
154
|
-
: validatePointShape(options.shape),
|
|
155
|
-
radius: options.radius === undefined
|
|
156
|
-
? DEFAULT_OUTLIER.radius
|
|
157
|
-
: validatePositiveFinite(options.radius, "createBoxPlot outlier.radius"),
|
|
158
|
-
opacity: options.opacity === undefined
|
|
159
|
-
? DEFAULT_OUTLIER.opacity
|
|
160
|
-
: validateUnitInterval(
|
|
161
|
-
options.opacity,
|
|
162
|
-
"createBoxPlot outlier.opacity"
|
|
163
|
-
)
|
|
164
|
-
});
|
|
133
|
+
return resolveAppearance(value, DEFAULT_OUTLIER, "outlier", operation);
|
|
165
134
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolveOptionalUserId, validateUserId } from "../../core/identifiers.js";
|
|
2
2
|
import { findLayer, hasLayer } from "../../selectors/layers.js";
|
|
3
3
|
|
|
4
|
-
const CATEGORY_TYPES = Object.freeze(["nominal", "ordinal"
|
|
4
|
+
const CATEGORY_TYPES = Object.freeze(["nominal", "ordinal"]);
|
|
5
5
|
|
|
6
6
|
export function resolveBoxOrientation(x, y) {
|
|
7
7
|
const xType = x?.fieldType ?? "quantitative";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { action } from "../../core/action.js";
|
|
2
2
|
import {
|
|
3
3
|
applyFacadeGuides,
|
|
4
|
+
inferFacadeFieldType,
|
|
4
5
|
normalizeAppearance,
|
|
5
6
|
normalizeEncoding,
|
|
6
7
|
normalizeFieldEncoding,
|
|
@@ -35,6 +36,8 @@ export const createBarPlot = action(
|
|
|
35
36
|
const bar = normalizeAppearance(args.bar, BAR_OPTIONS, "createBarPlot bar");
|
|
36
37
|
const x = normalizeFieldEncoding(args.x, "createBarPlot x");
|
|
37
38
|
const y = normalizeFieldEncoding(args.y, "createBarPlot y");
|
|
39
|
+
x.fieldType = inferFacadeFieldType(this, data, x, "createBarPlot x");
|
|
40
|
+
y.fieldType = inferFacadeFieldType(this, data, y, "createBarPlot y");
|
|
38
41
|
const color = normalizeEncoding(args.color, "createBarPlot color");
|
|
39
42
|
const width = normalizeTargetOptions(args.width, "createBarPlot width");
|
|
40
43
|
const guides = normalizeGuides(args.guides, "createBarPlot");
|
|
@@ -17,41 +17,43 @@ import {
|
|
|
17
17
|
validateFacadeOptions
|
|
18
18
|
} from "./shared.js";
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const OPERATION = "createHeatmap";
|
|
21
|
+
const OPTIONS = [
|
|
21
22
|
"id", "data", "coordinate", "x", "y", "bin", "color", "rect", "guides"
|
|
22
|
-
]
|
|
23
|
-
const RECT_OPTIONS =
|
|
23
|
+
];
|
|
24
|
+
const RECT_OPTIONS = [
|
|
24
25
|
"opacity", "stroke", "strokeWidth"
|
|
25
|
-
]
|
|
26
|
-
const BIN_OPTIONS =
|
|
26
|
+
];
|
|
27
|
+
const BIN_OPTIONS = [
|
|
27
28
|
"bins", "extent", "includeEmpty"
|
|
28
|
-
]
|
|
29
|
-
const BINNED_POSITION_OPTIONS =
|
|
29
|
+
];
|
|
30
|
+
const BINNED_POSITION_OPTIONS = [
|
|
30
31
|
"field", "fieldType", "scale"
|
|
31
|
-
]
|
|
32
|
-
const BINNED_COLOR_OPTIONS =
|
|
32
|
+
];
|
|
33
|
+
const BINNED_COLOR_OPTIONS = [
|
|
33
34
|
"scale", "palette"
|
|
34
|
-
]
|
|
35
|
+
];
|
|
35
36
|
|
|
36
37
|
function normalizeBin(value) {
|
|
37
38
|
if (!isPlainObject(value)) {
|
|
38
|
-
throw new TypeError(
|
|
39
|
+
throw new TypeError(`${OPERATION} bin must be a plain object.`);
|
|
39
40
|
}
|
|
40
|
-
validateOptionObject(value, BIN_OPTIONS,
|
|
41
|
+
validateOptionObject(value, BIN_OPTIONS, `${OPERATION} bin`);
|
|
41
42
|
return {
|
|
42
43
|
...value,
|
|
43
44
|
includeEmpty: value.includeEmpty ?? true
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
function
|
|
48
|
-
const label =
|
|
48
|
+
function normalizePosition(value, channel, categorical = false) {
|
|
49
|
+
const label = `${OPERATION} ${channel}`;
|
|
49
50
|
const encoding = normalizeFieldEncoding(value, label);
|
|
50
51
|
validateOptionObject(encoding, BINNED_POSITION_OPTIONS, label);
|
|
51
52
|
const field = validateNonEmptyString(encoding.field, `${label} field`);
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const types = categorical ? ["nominal", "ordinal"] : ["quantitative"];
|
|
54
|
+
const fieldType = encoding.fieldType ?? types[0];
|
|
55
|
+
if (!types.includes(fieldType)) {
|
|
56
|
+
throw new Error(`${label} requires a ${categorical ? "categorical" : "quantitative"} field.`);
|
|
55
57
|
}
|
|
56
58
|
if (encoding.scale !== undefined && !isPlainObject(encoding.scale)) {
|
|
57
59
|
throw new TypeError(`${label} scale must be a plain object.`);
|
|
@@ -62,9 +64,9 @@ function normalizeBinnedPosition(value, channel) {
|
|
|
62
64
|
function normalizeBinnedColor(value) {
|
|
63
65
|
if (value === undefined) return {};
|
|
64
66
|
if (!isPlainObject(value)) {
|
|
65
|
-
throw new TypeError(
|
|
67
|
+
throw new TypeError(`${OPERATION} binned color must be a plain object.`);
|
|
66
68
|
}
|
|
67
|
-
validateOptionObject(value, BINNED_COLOR_OPTIONS,
|
|
69
|
+
validateOptionObject(value, BINNED_COLOR_OPTIONS, `${OPERATION} binned color`);
|
|
68
70
|
return { ...value };
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -76,7 +78,7 @@ function resolvedPosition(encoding, field, extent) {
|
|
|
76
78
|
scale: {
|
|
77
79
|
type: "linear",
|
|
78
80
|
nice: false,
|
|
79
|
-
zero: false,
|
|
81
|
+
...(scale.type !== "log" && { zero: false }),
|
|
80
82
|
...scale,
|
|
81
83
|
domain: scale.domain === undefined || scale.domain === "auto"
|
|
82
84
|
? extent
|
|
@@ -88,10 +90,10 @@ function resolvedPosition(encoding, field, extent) {
|
|
|
88
90
|
function axisWithTitle(value, text) {
|
|
89
91
|
if (value === false) return false;
|
|
90
92
|
if (value !== undefined && !isPlainObject(value)) {
|
|
91
|
-
throw new TypeError(
|
|
93
|
+
throw new TypeError(`${OPERATION} binned axis must be false or a plain object.`);
|
|
92
94
|
}
|
|
93
95
|
if (value?.title !== undefined && !isPlainObject(value.title)) {
|
|
94
|
-
throw new TypeError(
|
|
96
|
+
throw new TypeError(`${OPERATION} binned axis title must be a plain object.`);
|
|
95
97
|
}
|
|
96
98
|
return {
|
|
97
99
|
...value,
|
|
@@ -111,7 +113,7 @@ function binnedGuides(guides, xTitle, yTitle) {
|
|
|
111
113
|
!isPlainObject(guides[key])
|
|
112
114
|
) {
|
|
113
115
|
throw new TypeError(
|
|
114
|
-
|
|
116
|
+
`${OPERATION} guides ${key} must be false or a plain object.`
|
|
115
117
|
);
|
|
116
118
|
}
|
|
117
119
|
}
|
|
@@ -133,73 +135,61 @@ function binnedGuides(guides, xTitle, yTitle) {
|
|
|
133
135
|
};
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
function createPreGriddedHeatmap(program, args, { id, data, rect, guides }) {
|
|
137
|
-
const x = normalizeFieldEncoding(args.x, "createHeatmap x");
|
|
138
|
-
const y = normalizeFieldEncoding(args.y, "createHeatmap y");
|
|
139
|
-
const color = normalizeFieldEncoding(args.color, "createHeatmap color");
|
|
140
|
-
const next = program
|
|
141
|
-
.createRectMark({ id, data, ...rect })
|
|
142
|
-
.encodeX(positionArgs(x, { target: id, coordinate: args.coordinate }))
|
|
143
|
-
.encodeY(positionArgs(y, { target: id, coordinate: args.coordinate }))
|
|
144
|
-
.encodeColor(targetArgs(color, id));
|
|
145
|
-
return applyFacadeGuides(next, guides);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function createBinnedHeatmap(program, args, { id, data, rect, guides }) {
|
|
149
|
-
const x = normalizeBinnedPosition(args.x, "x");
|
|
150
|
-
const y = normalizeBinnedPosition(args.y, "y");
|
|
151
|
-
const bin = normalizeBin(args.bin);
|
|
152
|
-
const color = normalizeBinnedColor(args.color);
|
|
153
|
-
const resolvedGuides = binnedGuides(guides, x.field, y.field);
|
|
154
|
-
const generatedData = `${id}Bin2DData`;
|
|
155
|
-
const binned = program.createBin2DData({
|
|
156
|
-
id: generatedData,
|
|
157
|
-
source: data,
|
|
158
|
-
x: x.field,
|
|
159
|
-
y: y.field,
|
|
160
|
-
...bin
|
|
161
|
-
});
|
|
162
|
-
const dataset = requireDataset(binned, generatedData);
|
|
163
|
-
const transform = dataset.transform[0];
|
|
164
|
-
const resolved = transform.resolved;
|
|
165
|
-
const xEncoding = resolvedPosition(x, transform.as.x0, resolved.extent.x);
|
|
166
|
-
const yEncoding = resolvedPosition(y, transform.as.y0, resolved.extent.y);
|
|
167
|
-
|
|
168
|
-
const next = binned
|
|
169
|
-
.createRectMark({ id, data: generatedData, ...rect })
|
|
170
|
-
.encodeX(positionArgs(xEncoding, { target: id, coordinate: args.coordinate }))
|
|
171
|
-
.encodeX2({ target: id, field: transform.as.x1, fieldType: "quantitative" })
|
|
172
|
-
.encodeY(positionArgs(yEncoding, { target: id, coordinate: args.coordinate }))
|
|
173
|
-
.encodeY2({ target: id, field: transform.as.y1, fieldType: "quantitative" })
|
|
174
|
-
.encodeColor({
|
|
175
|
-
target: id,
|
|
176
|
-
field: transform.as.count,
|
|
177
|
-
fieldType: "quantitative",
|
|
178
|
-
...color
|
|
179
|
-
});
|
|
180
|
-
return applyFacadeGuides(
|
|
181
|
-
next,
|
|
182
|
-
resolvedGuides
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
138
|
export const createHeatmap = action(
|
|
187
139
|
{
|
|
188
|
-
op:
|
|
140
|
+
op: OPERATION,
|
|
189
141
|
description: "Create a pre-gridded or rectangularly binned heatmap."
|
|
190
142
|
},
|
|
191
143
|
function (args = {}) {
|
|
192
|
-
validateFacadeOptions(args, OPTIONS,
|
|
144
|
+
validateFacadeOptions(args, OPTIONS, OPERATION);
|
|
193
145
|
const id = resolveFacadeId(this, args.id, {
|
|
194
146
|
defaultId: "heatmap",
|
|
195
|
-
operation:
|
|
147
|
+
operation: OPERATION
|
|
148
|
+
});
|
|
149
|
+
const data = resolveFacadeData(this, args.data, OPERATION);
|
|
150
|
+
const rect = normalizeAppearance(args.rect, RECT_OPTIONS, `${OPERATION} rect`);
|
|
151
|
+
const guides = normalizeGuides(args.guides, OPERATION);
|
|
152
|
+
if (args.bin === undefined) {
|
|
153
|
+
const x = normalizePosition(args.x, "x", true);
|
|
154
|
+
const y = normalizePosition(args.y, "y", true);
|
|
155
|
+
const color = normalizeFieldEncoding(args.color, `${OPERATION} color`);
|
|
156
|
+
const next = this
|
|
157
|
+
.createRectMark({ id, data, ...rect })
|
|
158
|
+
.encodeX(positionArgs(x, { target: id, coordinate: args.coordinate }))
|
|
159
|
+
.encodeY(positionArgs(y, { target: id, coordinate: args.coordinate }))
|
|
160
|
+
.encodeColor(targetArgs(color, id));
|
|
161
|
+
return applyFacadeGuides(next, guides);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const x = normalizePosition(args.x, "x");
|
|
165
|
+
const y = normalizePosition(args.y, "y");
|
|
166
|
+
const bin = normalizeBin(args.bin);
|
|
167
|
+
const color = normalizeBinnedColor(args.color);
|
|
168
|
+
const resolvedGuides = binnedGuides(guides, x.field, y.field);
|
|
169
|
+
const generatedData = `${id}Bin2DData`;
|
|
170
|
+
const binned = this.createBin2DData({
|
|
171
|
+
id: generatedData,
|
|
172
|
+
source: data,
|
|
173
|
+
x: x.field,
|
|
174
|
+
y: y.field,
|
|
175
|
+
...bin
|
|
196
176
|
});
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
:
|
|
177
|
+
const transform = requireDataset(binned, generatedData).transform[0];
|
|
178
|
+
const resolved = transform.resolved;
|
|
179
|
+
const xEncoding = resolvedPosition(x, transform.as.x0, resolved.extent.x);
|
|
180
|
+
const yEncoding = resolvedPosition(y, transform.as.y0, resolved.extent.y);
|
|
181
|
+
const next = binned
|
|
182
|
+
.createRectMark({ id, data: generatedData, ...rect })
|
|
183
|
+
.encodeX(positionArgs(xEncoding, { target: id, coordinate: args.coordinate }))
|
|
184
|
+
.encodeX2({ target: id, field: transform.as.x1, fieldType: "quantitative" })
|
|
185
|
+
.encodeY(positionArgs(yEncoding, { target: id, coordinate: args.coordinate }))
|
|
186
|
+
.encodeY2({ target: id, field: transform.as.y1, fieldType: "quantitative" })
|
|
187
|
+
.encodeColor({
|
|
188
|
+
target: id,
|
|
189
|
+
field: transform.as.count,
|
|
190
|
+
fieldType: "quantitative",
|
|
191
|
+
...color
|
|
192
|
+
});
|
|
193
|
+
return applyFacadeGuides(next, resolvedGuides);
|
|
204
194
|
}
|
|
205
195
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createBarPlot } from "./bar.js";
|
|
2
1
|
import { createHeatmap } from "./heatmap.js";
|
|
2
|
+
import { createBarPlot } from "./bar.js";
|
|
3
3
|
import { createHistogram } from "./histogram.js";
|
|
4
4
|
import { createLinePlot } from "./line.js";
|
|
5
5
|
import { createScatterPlot } from "./scatter.js";
|
|
@@ -20,6 +20,37 @@ const LINE_OPTIONS = Object.freeze([
|
|
|
20
20
|
"strokeWidth", "stroke", "opacity", "curve", "closed"
|
|
21
21
|
]);
|
|
22
22
|
|
|
23
|
+
function scopeParallelGuides(guides, coordinate) {
|
|
24
|
+
if (guides === false) return guides;
|
|
25
|
+
if (guides.grid !== undefined && guides.grid !== false) {
|
|
26
|
+
throw new Error("createParallelCoordinates guides do not accept grid options.");
|
|
27
|
+
}
|
|
28
|
+
if (guides.axes === false) return guides;
|
|
29
|
+
const axes = guides.axes ?? {};
|
|
30
|
+
const descriptor = axes.coordinate ?? {};
|
|
31
|
+
if (
|
|
32
|
+
descriptor.id !== undefined && descriptor.id !== coordinate ||
|
|
33
|
+
descriptor.type !== undefined &&
|
|
34
|
+
descriptor.type !== "auto" && descriptor.type !== "parallel"
|
|
35
|
+
) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"createParallelCoordinates guides must use the chart's Parallel coordinate."
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (["x", "y", "theta", "radius"].some(channel => axes[channel] !== undefined)) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"createParallelCoordinates guides do not accept Cartesian or Polar axis channels."
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
...guides,
|
|
47
|
+
axes: {
|
|
48
|
+
...axes,
|
|
49
|
+
coordinate: { ...descriptor, id: coordinate, type: "parallel" }
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
23
54
|
export const createParallelCoordinates = action(
|
|
24
55
|
{
|
|
25
56
|
op: "createParallelCoordinates",
|
|
@@ -55,7 +86,10 @@ export const createParallelCoordinates = action(
|
|
|
55
86
|
args.strokeDash,
|
|
56
87
|
"createParallelCoordinates strokeDash"
|
|
57
88
|
);
|
|
58
|
-
const guides =
|
|
89
|
+
const guides = scopeParallelGuides(
|
|
90
|
+
normalizeGuides(args.guides, "createParallelCoordinates"),
|
|
91
|
+
coordinate
|
|
92
|
+
);
|
|
59
93
|
|
|
60
94
|
let next = this
|
|
61
95
|
.createCoordinate({ id: coordinate, type: "parallel" })
|
|
@@ -2,6 +2,7 @@ import { resolveOptionalUserId, validateUserId } from "../../core/identifiers.js
|
|
|
2
2
|
import { isPlainObject } from "../../core/immutable.js";
|
|
3
3
|
import { validateOptionObject } from "../../core/validation.js";
|
|
4
4
|
import { findDataset } from "../../selectors/datasets.js";
|
|
5
|
+
import { isNominalValue } from "../../grammar/scales/fields.js";
|
|
5
6
|
import { hasLayer } from "../../selectors/layers.js";
|
|
6
7
|
|
|
7
8
|
export function validateFacadeOptions(args, supported, operation) {
|
|
@@ -62,6 +63,25 @@ export function normalizeEncoding(value, label) {
|
|
|
62
63
|
return normalizeFieldEncoding(value, label);
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
export function inferFacadeFieldType(program, data, encoding, label) {
|
|
67
|
+
if (encoding.fieldType !== undefined) return encoding.fieldType;
|
|
68
|
+
if (typeof encoding.field !== "string" || encoding.field.length === 0) {
|
|
69
|
+
throw new TypeError(`${label} field must be a non-empty string.`);
|
|
70
|
+
}
|
|
71
|
+
const dataset = findDataset(program, data);
|
|
72
|
+
const values = dataset.values
|
|
73
|
+
.map(row => row?.[encoding.field])
|
|
74
|
+
.filter(value => value !== undefined && value !== null && value !== "");
|
|
75
|
+
if (values.length === 0) {
|
|
76
|
+
throw new Error(`${label} field "${encoding.field}" has no values.`);
|
|
77
|
+
}
|
|
78
|
+
if (values.every(Number.isFinite)) return "quantitative";
|
|
79
|
+
if (values.every(isNominalValue)) return "nominal";
|
|
80
|
+
throw new Error(
|
|
81
|
+
`${label} field "${encoding.field}" requires an explicit fieldType.`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
65
85
|
export function normalizeStrokeDashEncoding(
|
|
66
86
|
value,
|
|
67
87
|
label = "createLinePlot strokeDash"
|
|
@@ -21,6 +21,10 @@ const LAYOUT_EDIT_OPTIONS = Object.freeze([
|
|
|
21
21
|
]);
|
|
22
22
|
const REPLACEMENT_OPTIONS = Object.freeze(["target", "program"]);
|
|
23
23
|
|
|
24
|
+
function option(args, name, fallback) {
|
|
25
|
+
return Object.hasOwn(args, name) ? args[name] : fallback;
|
|
26
|
+
}
|
|
27
|
+
|
|
24
28
|
function normalizeChildEntry(entry, index) {
|
|
25
29
|
const fallbackId = `view-${index + 1}`;
|
|
26
30
|
if (entry instanceof CoreChartProgram) {
|
|
@@ -64,7 +68,11 @@ function childDescriptor({ id, program }) {
|
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
function normalizeCompositionInput(args, direction) {
|
|
67
|
-
validateOptionObject(
|
|
71
|
+
validateOptionObject(
|
|
72
|
+
args,
|
|
73
|
+
CONCAT_OPTIONS,
|
|
74
|
+
direction === "horizontal" ? "hconcat" : "vconcat"
|
|
75
|
+
);
|
|
68
76
|
if (!Array.isArray(args.programs) || args.programs.length < 2) {
|
|
69
77
|
throw new TypeError("Composition requires at least two programs.");
|
|
70
78
|
}
|
|
@@ -98,6 +106,12 @@ function normalizeCompositionInput(args, direction) {
|
|
|
98
106
|
};
|
|
99
107
|
}
|
|
100
108
|
|
|
109
|
+
export function applyCompositionState(program, state, tracedChildren = []) {
|
|
110
|
+
let next = program._withCompositionState(state);
|
|
111
|
+
for (const id of tracedChildren) next = next.useProgram({ id });
|
|
112
|
+
return next.materializeComposition();
|
|
113
|
+
}
|
|
114
|
+
|
|
101
115
|
const useProgram = action(
|
|
102
116
|
{
|
|
103
117
|
op: "useProgram",
|
|
@@ -136,11 +150,7 @@ function concatAction(direction, op) {
|
|
|
136
150
|
},
|
|
137
151
|
function (args = {}) {
|
|
138
152
|
const state = normalizeCompositionInput(args, direction);
|
|
139
|
-
|
|
140
|
-
for (const id of state.compositionSpec.children) {
|
|
141
|
-
next = next.useProgram({ id });
|
|
142
|
-
}
|
|
143
|
-
return next.materializeComposition();
|
|
153
|
+
return applyCompositionState(this, state, state.compositionSpec.children);
|
|
144
154
|
}
|
|
145
155
|
);
|
|
146
156
|
}
|
|
@@ -160,60 +170,49 @@ const editCompositionLayout = action(
|
|
|
160
170
|
throw new TypeError("editCompositionLayout requires at least one layout option.");
|
|
161
171
|
}
|
|
162
172
|
const current = this.compositionSpec;
|
|
173
|
+
if (current.type !== "facet" && Object.hasOwn(args, "columns")) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
"editCompositionLayout columns is available only on a facet composition."
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
const padding = Object.hasOwn(args, "padding")
|
|
179
|
+
? normalizeCompositionPadding(args.padding, current.padding)
|
|
180
|
+
: current.padding;
|
|
181
|
+
let layout;
|
|
163
182
|
if (current.type === "facet") {
|
|
164
|
-
|
|
183
|
+
layout = resolveFacetLayout({
|
|
165
184
|
children: current.children.map((id, index) => ({
|
|
166
185
|
...childDescriptor({ id, program: this.children[id] }),
|
|
167
186
|
value: current.facet.values[index]
|
|
168
187
|
})),
|
|
169
|
-
columns:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
align: Object.hasOwn(args, "align") ? args.align : current.align,
|
|
174
|
-
padding: Object.hasOwn(args, "padding")
|
|
175
|
-
? normalizeCompositionPadding(args.padding, current.padding)
|
|
176
|
-
: current.padding,
|
|
188
|
+
columns: option(args, "columns", current.columns),
|
|
189
|
+
gap: option(args, "gap", current.gap),
|
|
190
|
+
align: option(args, "align", current.align),
|
|
191
|
+
padding,
|
|
177
192
|
sharedLegend: current.facet.guides.legend === "shared"
|
|
178
193
|
});
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (Object.hasOwn(args, "columns")) {
|
|
191
|
-
throw new Error(
|
|
192
|
-
"editCompositionLayout columns is available only on a facet composition."
|
|
193
|
-
);
|
|
194
|
+
} else {
|
|
195
|
+
layout = resolveCompositionLayout({
|
|
196
|
+
direction: current.direction,
|
|
197
|
+
children: current.children.map(id => childDescriptor({
|
|
198
|
+
id,
|
|
199
|
+
program: this.children[id]
|
|
200
|
+
})),
|
|
201
|
+
gap: option(args, "gap", current.gap),
|
|
202
|
+
align: option(args, "align", current.align),
|
|
203
|
+
padding
|
|
204
|
+
});
|
|
194
205
|
}
|
|
195
|
-
|
|
196
|
-
? normalizeCompositionPadding(args.padding, current.padding)
|
|
197
|
-
: current.padding;
|
|
198
|
-
const layout = resolveCompositionLayout({
|
|
199
|
-
direction: current.direction,
|
|
200
|
-
children: current.children.map(id => childDescriptor({
|
|
201
|
-
id,
|
|
202
|
-
program: this.children[id]
|
|
203
|
-
})),
|
|
204
|
-
gap: Object.hasOwn(args, "gap") ? args.gap : current.gap,
|
|
205
|
-
align: Object.hasOwn(args, "align") ? args.align : current.align,
|
|
206
|
-
padding
|
|
207
|
-
});
|
|
208
|
-
return this._withCompositionState({
|
|
206
|
+
return applyCompositionState(this, {
|
|
209
207
|
children: this.children,
|
|
210
208
|
compositionSpec: {
|
|
211
209
|
...current,
|
|
210
|
+
...(current.type === "facet" ? { columns: layout.columns } : {}),
|
|
212
211
|
gap: layout.gap,
|
|
213
212
|
align: layout.align,
|
|
214
213
|
padding: layout.padding
|
|
215
214
|
}
|
|
216
|
-
})
|
|
215
|
+
});
|
|
217
216
|
}
|
|
218
217
|
);
|
|
219
218
|
|
|
@@ -240,12 +239,10 @@ const replaceCompositionChild = action(
|
|
|
240
239
|
...this.children,
|
|
241
240
|
[target]: args.program
|
|
242
241
|
});
|
|
243
|
-
return this
|
|
242
|
+
return applyCompositionState(this, {
|
|
244
243
|
children,
|
|
245
244
|
compositionSpec: this.compositionSpec
|
|
246
|
-
})
|
|
247
|
-
.useProgram({ id: target })
|
|
248
|
-
.materializeComposition();
|
|
245
|
+
}, [target]);
|
|
249
246
|
}
|
|
250
247
|
);
|
|
251
248
|
|