ggaction 0.0.4 → 0.0.6
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 -0
- package/README.md +35 -13
- package/package.json +18 -7
- package/src/actions/boxPlots/create.js +13 -7
- package/src/actions/boxPlots/edit.js +484 -36
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +11 -4
- package/src/actions/boxPlots/resolve.js +10 -2
- package/src/actions/charts/bar.js +50 -0
- package/src/actions/charts/heatmap.js +205 -0
- package/src/actions/charts/histogram.js +58 -0
- package/src/actions/charts/index.js +15 -0
- package/src/actions/charts/line.js +69 -0
- package/src/actions/charts/parallel.js +76 -0
- package/src/actions/charts/scatter.js +56 -0
- package/src/actions/charts/shared.js +121 -0
- package/src/actions/composition/actions.js +13 -3
- package/src/actions/coordinates/parallel.js +50 -0
- package/src/actions/data/bin2d.js +260 -0
- package/src/actions/data/density.js +54 -13
- package/src/actions/data/derived.js +22 -0
- package/src/actions/data/filter.js +5 -0
- package/src/actions/data/gradientProfile.js +65 -0
- package/src/actions/data/horizon.js +71 -0
- package/src/actions/data/index.js +34 -2
- package/src/actions/data/intervalEdit.js +76 -0
- package/src/actions/data/window.js +50 -0
- package/src/actions/distributions/revision.js +326 -0
- package/src/actions/encodings/appearance.js +35 -1
- package/src/actions/encodings/color/index.js +17 -1
- package/src/actions/encodings/color/layout.js +8 -0
- package/src/actions/encodings/density/resolve.js +109 -0
- package/src/actions/encodings/density.js +330 -90
- package/src/actions/encodings/horizon/resolve.js +283 -0
- package/src/actions/encodings/horizon.js +364 -0
- package/src/actions/encodings/index.js +8 -0
- package/src/actions/encodings/parallel.js +115 -0
- package/src/actions/encodings/pathOrder.js +131 -0
- package/src/actions/encodings/position/apply.js +73 -4
- package/src/actions/encodings/position/index.js +15 -2
- package/src/actions/encodings/position/policies/arc.js +27 -4
- package/src/actions/encodings/position/policies/area.js +33 -2
- package/src/actions/encodings/position/policies/index.js +7 -2
- package/src/actions/encodings/position/policies/line.js +18 -2
- package/src/actions/encodings/position/resolve.js +24 -2
- package/src/actions/encodings/ranged.js +3 -1
- package/src/actions/encodings/remove.js +268 -0
- package/src/actions/encodings/ruleAppearance.js +88 -10
- package/src/actions/errorBands/edit.js +121 -13
- package/src/actions/errorBars/edit.js +45 -8
- package/src/actions/errorBars/options.js +7 -1
- package/src/actions/facets/actions.js +137 -3
- package/src/actions/facets/derive.js +33 -3
- package/src/actions/facets/replay.js +7 -36
- package/src/actions/gradientPlots/components.js +177 -0
- package/src/actions/gradientPlots/create.js +96 -0
- package/src/actions/gradientPlots/edit.js +501 -0
- package/src/actions/gradientPlots/index.js +24 -0
- package/src/actions/gradientPlots/materialize.js +287 -0
- package/src/actions/gradientPlots/options.js +120 -0
- package/src/actions/gradientPlots/paint.js +107 -0
- package/src/actions/gradientPlots/rebind.js +51 -0
- package/src/actions/gradientPlots/resolve.js +81 -0
- package/src/actions/guides/applicability.js +44 -17
- package/src/actions/guides/axes/axes.js +37 -3
- package/src/actions/guides/axes/edit.js +129 -47
- package/src/actions/guides/axes/index.js +2 -0
- package/src/actions/guides/axes/labels.js +11 -6
- package/src/actions/guides/axes/parallel/resolve.js +86 -0
- package/src/actions/guides/axes/parallel.js +228 -0
- package/src/actions/guides/axes/titles.js +11 -8
- package/src/actions/guides/guides.js +60 -5
- package/src/actions/guides/legends/categorical/actions.js +67 -19
- package/src/actions/guides/legends/continuous/common.js +3 -3
- package/src/actions/guides/legends/edit.js +63 -0
- package/src/actions/guides/legends/index.js +2 -0
- package/src/actions/guides/legends/remove.js +124 -30
- package/src/actions/guides/legends/strokeWidth.js +198 -0
- package/src/actions/index.js +6 -0
- package/src/actions/marks/arc/actions.js +46 -14
- package/src/actions/marks/area/actions.js +60 -136
- package/src/actions/marks/area/materialize.js +163 -0
- package/src/actions/marks/index.js +5 -1
- package/src/actions/marks/line/actions.js +99 -80
- package/src/actions/marks/line/materialize.js +146 -0
- package/src/actions/marks/point/create.js +76 -0
- package/src/actions/marks/point/edit.js +97 -0
- package/src/actions/marks/point/index.js +11 -1
- package/src/actions/marks/point/jitter.js +72 -0
- package/src/actions/marks/point/{actions.js → materialize.js} +183 -172
- package/src/actions/marks/rect/actions.js +3 -0
- package/src/actions/marks/remove.js +13 -2
- package/src/actions/marks/rule/actions.js +29 -4
- package/src/actions/marks/text/actions.js +19 -4
- package/src/actions/marks/text/index.js +7 -1
- package/src/actions/marks/text/layout.js +321 -0
- package/src/actions/primitives/semanticValidation/dataset.js +22 -0
- package/src/actions/primitives/semanticValidation/guide.js +43 -0
- package/src/actions/primitives/semanticValidation/index.js +23 -0
- package/src/actions/primitives/semanticValidation/layer.js +74 -0
- package/src/actions/primitives/semanticValidation/scale.js +74 -0
- package/src/actions/primitives/semanticValidation/shared.js +5 -0
- package/src/actions/primitives/semanticValue.js +1 -204
- package/src/actions/regression/edit.js +247 -88
- package/src/actions/scales/consumers/common.js +18 -0
- package/src/actions/scales/consumers/families.js +14 -3
- package/src/actions/scales/definitions.js +30 -0
- package/src/actions/scales/edit.js +6 -204
- package/src/actions/scales/editPolicy.js +214 -0
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/patch.js +26 -0
- package/src/actions/selection/actions.js +150 -5
- package/src/actions/selection/index.js +6 -0
- package/src/actions/violinPlots/create.js +198 -0
- package/src/actions/violinPlots/index.js +5 -0
- package/src/core/textMetrics.js +47 -0
- package/src/core/vocabulary.js +14 -2
- package/src/grammar/arcs.js +49 -12
- package/src/grammar/areaSeries.js +41 -5
- package/src/grammar/bin2d.js +333 -0
- package/src/grammar/categoricalDensity.js +192 -0
- package/src/grammar/coordinates.js +1 -1
- package/src/grammar/curveCommands.js +3 -1
- package/src/grammar/density.js +252 -26
- package/src/grammar/facets/dependencies.js +3 -11
- package/src/grammar/facets/index.js +3 -1
- package/src/grammar/gradientProfile.js +213 -0
- package/src/grammar/horizon.js +455 -0
- package/src/grammar/jitter.js +197 -0
- package/src/grammar/lineSeries.js +69 -0
- package/src/grammar/paint.js +88 -0
- package/src/grammar/parallelCoordinates.js +213 -0
- package/src/grammar/pathOrder.js +35 -0
- package/src/grammar/pointShapes.js +24 -0
- package/src/grammar/rules.js +1 -0
- package/src/grammar/scales/appearance.js +21 -0
- package/src/grammar/schemas/concreteGraphic.js +11 -1
- package/src/grammar/schemas/graphicBounds.js +19 -37
- package/src/grammar/schemas/semanticPath.js +17 -2
- package/src/grammar/transforms.js +100 -11
- package/src/grammar/window.js +339 -0
- package/src/layout/labels.js +270 -0
- package/src/layout/text.js +4 -1
- package/src/layout/title.js +10 -20
- package/src/materialization/dataProvenance.js +56 -2
- package/src/materialization/dependencies.js +9 -14
- package/src/materialization/guides/resources.js +74 -24
- package/src/materialization/horizon.js +29 -0
- package/src/materialization/layout.js +15 -0
- package/src/materialization/marks/capabilities.js +21 -1
- package/src/materialization/marks/index.js +3 -0
- package/src/materialization/marks/pathOrder.js +71 -0
- package/src/materialization/marks/policies.js +6 -2
- package/src/materialization/scaleGuideDependencies.js +8 -0
- package/src/materialization/scales/resolve.js +2 -0
- package/src/materialization/selection/items/path.js +27 -1
- package/src/materialization/selection/items/point.js +1 -5
- package/src/materialization/selection/items/rect.js +21 -0
- package/src/materialization/selection/styles.js +20 -9
- package/src/materialization/text.js +12 -4
- package/src/renderers/canvas/fill.js +33 -0
- package/src/renderers/canvas/index.js +7 -0
- package/src/renderers/canvas/path.js +7 -4
- package/src/renderers/canvas/rect.js +9 -3
- package/src/selectors/datasets.js +4 -0
- package/src/theme/defaults.js +4 -0
- package/types/extension.d.ts +6 -0
- package/types/index.d.ts +77 -0
- package/types/program.d.ts +703 -17
|
@@ -1,103 +1,32 @@
|
|
|
1
1
|
import { action } from "../../../core/action.js";
|
|
2
2
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
-
import {
|
|
4
|
-
validateNonEmptyString,
|
|
5
|
-
validateNonNegativeFinite,
|
|
6
|
-
validateUnitInterval
|
|
7
|
-
} from "../../../core/validation.js";
|
|
8
3
|
import { getPointGraphicType } from "../../../grammar/schemas/mark.js";
|
|
9
4
|
import {
|
|
10
5
|
createPointShapeGraphic,
|
|
6
|
+
resolvePointShapeExtent,
|
|
11
7
|
validatePointShape
|
|
12
8
|
} from "../../../grammar/pointShapes.js";
|
|
9
|
+
import {
|
|
10
|
+
canonicalJitterScalar,
|
|
11
|
+
normalizePointJitterPolicy,
|
|
12
|
+
resolvePointJitter
|
|
13
|
+
} from "../../../grammar/jitter.js";
|
|
13
14
|
import { polarToCartesian, resolvePolarFrame } from "../../../grammar/polar.js";
|
|
14
15
|
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
16
|
+
import { validateMarkOptions } from "../shared.js";
|
|
15
17
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
resolveLayeredMarkInheritance,
|
|
20
|
-
resolveMarkId,
|
|
21
|
-
resolveMarkData,
|
|
22
|
-
validateMarkOptions
|
|
23
|
-
} from "../shared.js";
|
|
24
|
-
import { DEFAULT_COLORS } from "../../../theme/defaults.js";
|
|
18
|
+
DEFAULT_COLORS,
|
|
19
|
+
DEFAULT_POINT_RADIUS
|
|
20
|
+
} from "../../../theme/defaults.js";
|
|
25
21
|
import { findDataset } from "../../../selectors/datasets.js";
|
|
26
22
|
import { findLayer } from "../../../selectors/layers.js";
|
|
27
|
-
import { rematerializeExistingLegend } from "../../encodings/shared.js";
|
|
28
|
-
import { resolveMarkGraphicPlacement } from
|
|
29
|
-
"../../../materialization/graphicHierarchy.js";
|
|
30
23
|
import { rematerializeHighlightBaseline } from "../lifecycle.js";
|
|
31
24
|
import { resolveRowEncodingValues } from
|
|
32
25
|
"../../../materialization/rowEncoding.js";
|
|
33
26
|
|
|
34
|
-
const POINT_MARK_OPTIONS = Object.freeze([
|
|
35
|
-
"id", "data", "shape", "fill", "opacity", "stroke", "strokeWidth"
|
|
36
|
-
]);
|
|
37
|
-
const EDIT_POINT_OPTIONS = Object.freeze([
|
|
38
|
-
"target", "shape", "fill", "opacity", "stroke", "strokeWidth"
|
|
39
|
-
]);
|
|
40
27
|
const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
|
|
41
28
|
const DEFAULT_POINT_FILL = DEFAULT_COLORS.mark;
|
|
42
29
|
|
|
43
|
-
const createPointMark = action(
|
|
44
|
-
{
|
|
45
|
-
op: "createPointMark",
|
|
46
|
-
description: "Create a semantic point mark and concrete point graphics."
|
|
47
|
-
},
|
|
48
|
-
function (args = {}) {
|
|
49
|
-
validateMarkOptions(args, POINT_MARK_OPTIONS, "createPointMark");
|
|
50
|
-
const id = resolveMarkId(this, args.id, {
|
|
51
|
-
defaultId: "point",
|
|
52
|
-
label: "Point mark id",
|
|
53
|
-
markType: "point",
|
|
54
|
-
operation: "createPointMark"
|
|
55
|
-
});
|
|
56
|
-
const inherited = resolveLayeredMarkInheritance(this, args, "point");
|
|
57
|
-
const { data, dataset } = resolveMarkData(this, {
|
|
58
|
-
...args,
|
|
59
|
-
...(args.data === undefined &&
|
|
60
|
-
this.context.currentData === undefined &&
|
|
61
|
-
inherited?.data !== undefined
|
|
62
|
-
? { data: inherited.data }
|
|
63
|
-
: {})
|
|
64
|
-
});
|
|
65
|
-
const shape = Object.hasOwn(args, "shape") ? args.shape : "circle";
|
|
66
|
-
validatePointShape(shape);
|
|
67
|
-
const resolvedType = getPointGraphicType(shape);
|
|
68
|
-
const graphicType = resolvedType === "path" ? "circle" : resolvedType;
|
|
69
|
-
|
|
70
|
-
assertMarkAvailable(this, id);
|
|
71
|
-
|
|
72
|
-
let next = this
|
|
73
|
-
.editSemantic({
|
|
74
|
-
property: `layer[${id}].mark.type`,
|
|
75
|
-
value: "point"
|
|
76
|
-
})
|
|
77
|
-
.editSemantic({
|
|
78
|
-
property: `layer[${id}].data`,
|
|
79
|
-
value: data
|
|
80
|
-
});
|
|
81
|
-
next = applyLayeredMarkInheritance(next, id, inherited)
|
|
82
|
-
.createGraphics({
|
|
83
|
-
id,
|
|
84
|
-
type: graphicType,
|
|
85
|
-
length: dataset.values.length,
|
|
86
|
-
...resolveMarkGraphicPlacement(next, { data, markType: "point" })
|
|
87
|
-
})
|
|
88
|
-
._withMarkConfig(id, { shape });
|
|
89
|
-
const created = materializeInheritedMark(next, id);
|
|
90
|
-
const appearance = Object.fromEntries(
|
|
91
|
-
["fill", "opacity", "stroke", "strokeWidth"]
|
|
92
|
-
.filter(property => Object.hasOwn(args, property))
|
|
93
|
-
.map(property => [property, args[property]])
|
|
94
|
-
);
|
|
95
|
-
return Object.keys(appearance).length === 0
|
|
96
|
-
? created
|
|
97
|
-
: created.editPointMark({ target: id, ...appearance });
|
|
98
|
-
}
|
|
99
|
-
);
|
|
100
|
-
|
|
101
30
|
function resolvePointPositions(program, layer, dataset) {
|
|
102
31
|
const hasPolar = layer.encoding?.theta !== undefined ||
|
|
103
32
|
layer.encoding?.radius !== undefined;
|
|
@@ -154,7 +83,124 @@ function existingArea(child, parentType) {
|
|
|
154
83
|
return undefined;
|
|
155
84
|
}
|
|
156
85
|
|
|
157
|
-
|
|
86
|
+
function resolvedPointArea(area, index, config, existingChildren, graphicType) {
|
|
87
|
+
return area?.[index] ?? (
|
|
88
|
+
config.radius === undefined
|
|
89
|
+
? existingArea(existingChildren[index], graphicType) ??
|
|
90
|
+
Math.PI * DEFAULT_POINT_RADIUS ** 2
|
|
91
|
+
: Math.PI * config.radius ** 2
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolveJitterEntries({
|
|
96
|
+
policy,
|
|
97
|
+
dataset,
|
|
98
|
+
x,
|
|
99
|
+
y,
|
|
100
|
+
area,
|
|
101
|
+
shapes,
|
|
102
|
+
config,
|
|
103
|
+
graphic,
|
|
104
|
+
existingChildren
|
|
105
|
+
}) {
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
return dataset.values.flatMap((row, index) => {
|
|
108
|
+
if (!Number.isFinite(x?.[index]) || !Number.isFinite(y?.[index])) return [];
|
|
109
|
+
const identity = policy.key === undefined ? index : row[policy.key];
|
|
110
|
+
const canonical = canonicalJitterScalar(
|
|
111
|
+
identity,
|
|
112
|
+
policy.key === undefined
|
|
113
|
+
? "Point jitter source index"
|
|
114
|
+
: `Point jitter key field "${policy.key}"`
|
|
115
|
+
);
|
|
116
|
+
if (seen.has(canonical)) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Point jitter key field "${policy.key}" must be unique for materialized items.`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
seen.add(canonical);
|
|
122
|
+
const resolvedArea = resolvedPointArea(
|
|
123
|
+
area,
|
|
124
|
+
index,
|
|
125
|
+
config,
|
|
126
|
+
existingChildren,
|
|
127
|
+
graphic.type
|
|
128
|
+
);
|
|
129
|
+
const extent = resolvePointShapeExtent({
|
|
130
|
+
shape: shapes[index],
|
|
131
|
+
area: resolvedArea,
|
|
132
|
+
strokeWidth: typeof config.stroke === "string"
|
|
133
|
+
? config.strokeWidth ?? 1
|
|
134
|
+
: 0
|
|
135
|
+
});
|
|
136
|
+
return [{
|
|
137
|
+
index,
|
|
138
|
+
identity,
|
|
139
|
+
base: policy.channel === "x" ? x[index] : y[index],
|
|
140
|
+
halfExtent: extent[policy.channel]
|
|
141
|
+
}];
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function applyPointJitter(program, {
|
|
146
|
+
id,
|
|
147
|
+
layer,
|
|
148
|
+
dataset,
|
|
149
|
+
x,
|
|
150
|
+
y,
|
|
151
|
+
area,
|
|
152
|
+
shapes,
|
|
153
|
+
config,
|
|
154
|
+
graphic,
|
|
155
|
+
existingChildren
|
|
156
|
+
}) {
|
|
157
|
+
const stored = program.materializationConfigs.jitters?.[id];
|
|
158
|
+
if (stored === undefined) return { program, x, y };
|
|
159
|
+
const policy = normalizePointJitterPolicy(stored);
|
|
160
|
+
const encoding = layer.encoding?.[policy.channel];
|
|
161
|
+
const scale = program.resolvedScales[encoding?.scale];
|
|
162
|
+
if (scale === undefined) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Point jitter on "${id}" requires a resolved ${policy.channel} scale.`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
const bounds = resolveGraphicBounds(program);
|
|
168
|
+
const entries = resolveJitterEntries({
|
|
169
|
+
policy,
|
|
170
|
+
dataset,
|
|
171
|
+
x,
|
|
172
|
+
y,
|
|
173
|
+
area,
|
|
174
|
+
shapes,
|
|
175
|
+
config,
|
|
176
|
+
graphic,
|
|
177
|
+
existingChildren
|
|
178
|
+
});
|
|
179
|
+
const resolution = resolvePointJitter({
|
|
180
|
+
target: id,
|
|
181
|
+
policy,
|
|
182
|
+
scale,
|
|
183
|
+
entries,
|
|
184
|
+
plotMinimum: policy.channel === "x" ? bounds.x : bounds.y,
|
|
185
|
+
plotMaximum: policy.channel === "x"
|
|
186
|
+
? bounds.x + bounds.width
|
|
187
|
+
: bounds.y + bounds.height
|
|
188
|
+
});
|
|
189
|
+
const values = [...(policy.channel === "x" ? x : y)];
|
|
190
|
+
for (const item of resolution.items) {
|
|
191
|
+
values[item.index] += item.finalOffset;
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
program: program._withMaterializationConfig(["jitters", id], {
|
|
195
|
+
...policy,
|
|
196
|
+
resolved: resolution
|
|
197
|
+
}),
|
|
198
|
+
x: policy.channel === "x" ? values : x,
|
|
199
|
+
y: policy.channel === "y" ? values : y
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export const rematerializePointMark = action(
|
|
158
204
|
{
|
|
159
205
|
op: "rematerializePointMark",
|
|
160
206
|
description: "Recompute concrete point geometry and appearance."
|
|
@@ -183,7 +229,7 @@ const rematerializePointMark = action(
|
|
|
183
229
|
throw new Error(`Point mark "${id}" requires an existing dataset.`);
|
|
184
230
|
}
|
|
185
231
|
|
|
186
|
-
const
|
|
232
|
+
const positions = resolvePointPositions(this, layer, dataset);
|
|
187
233
|
const mappedFill = resolveRowEncodingValues(this, layer, dataset, "color");
|
|
188
234
|
const area = resolveRowEncodingValues(this, layer, dataset, "size");
|
|
189
235
|
const encodedShape = resolveRowEncodingValues(this, layer, dataset, "shape");
|
|
@@ -197,6 +243,19 @@ const rematerializePointMark = action(
|
|
|
197
243
|
encodedShape !== undefined ||
|
|
198
244
|
graphic.type === "collection" ||
|
|
199
245
|
getPointGraphicType(constantShape) !== graphic.type;
|
|
246
|
+
const jittered = applyPointJitter(this, {
|
|
247
|
+
id,
|
|
248
|
+
layer,
|
|
249
|
+
dataset,
|
|
250
|
+
x: positions.x,
|
|
251
|
+
y: positions.y,
|
|
252
|
+
area,
|
|
253
|
+
shapes,
|
|
254
|
+
config,
|
|
255
|
+
graphic,
|
|
256
|
+
existingChildren
|
|
257
|
+
});
|
|
258
|
+
const { x, y } = jittered;
|
|
200
259
|
|
|
201
260
|
if (requiresMixedCollection) {
|
|
202
261
|
const items = dataset.values.map((_, index) => {
|
|
@@ -205,11 +264,13 @@ const rematerializePointMark = action(
|
|
|
205
264
|
const color = mappedFill?.[index] ?? config.fill ??
|
|
206
265
|
existing.fill ?? DEFAULT_POINT_FILL;
|
|
207
266
|
const opacity = encodedOpacity?.[index] ?? config.opacity ?? existing.opacity;
|
|
208
|
-
const resolvedArea =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
267
|
+
const resolvedArea = resolvedPointArea(
|
|
268
|
+
area,
|
|
269
|
+
index,
|
|
270
|
+
config,
|
|
271
|
+
existingChildren,
|
|
272
|
+
graphic.type
|
|
273
|
+
);
|
|
213
274
|
const centerX = x?.[index] ?? existing.x;
|
|
214
275
|
const centerY = y?.[index] ?? existing.y;
|
|
215
276
|
if (
|
|
@@ -230,19 +291,23 @@ const rematerializePointMark = action(
|
|
|
230
291
|
y: centerY,
|
|
231
292
|
area: resolvedArea,
|
|
232
293
|
fill: color,
|
|
233
|
-
...(config.stroke ===
|
|
294
|
+
...(typeof config.stroke === "string" ? { stroke: config.stroke } : {}),
|
|
234
295
|
...(config.strokeWidth === undefined
|
|
235
296
|
? {}
|
|
236
297
|
: { strokeWidth: config.strokeWidth }),
|
|
237
298
|
opacity
|
|
238
299
|
});
|
|
239
300
|
});
|
|
240
|
-
return
|
|
301
|
+
return jittered.program.editGraphics({
|
|
302
|
+
target: id,
|
|
303
|
+
property: "items",
|
|
304
|
+
value: items
|
|
305
|
+
});
|
|
241
306
|
}
|
|
242
307
|
|
|
243
308
|
let next = graphic.items.length === dataset.values.length
|
|
244
|
-
?
|
|
245
|
-
:
|
|
309
|
+
? jittered.program
|
|
310
|
+
: jittered.program.editGraphics({
|
|
246
311
|
target: id,
|
|
247
312
|
property: "length",
|
|
248
313
|
value: dataset.values.length
|
|
@@ -254,14 +319,32 @@ const rematerializePointMark = action(
|
|
|
254
319
|
}
|
|
255
320
|
if (graphic.type === "circle") {
|
|
256
321
|
const radii = area === undefined
|
|
257
|
-
? config.radius
|
|
322
|
+
? config.radius ?? (
|
|
323
|
+
x !== undefined && y !== undefined
|
|
324
|
+
? dataset.values.map((_, index) => {
|
|
325
|
+
const existing = existingChildren[index]?.properties?.radius;
|
|
326
|
+
return Number.isFinite(existing)
|
|
327
|
+
? existing
|
|
328
|
+
: DEFAULT_POINT_RADIUS;
|
|
329
|
+
})
|
|
330
|
+
: undefined
|
|
331
|
+
)
|
|
258
332
|
: area.map(value => Math.sqrt(value / Math.PI));
|
|
259
333
|
if (radii !== undefined) {
|
|
260
334
|
next = next.editGraphics({ target: id, property: "radius", value: radii });
|
|
261
335
|
}
|
|
262
|
-
} else if (
|
|
336
|
+
} else if (
|
|
337
|
+
area !== undefined ||
|
|
338
|
+
config.radius !== undefined ||
|
|
339
|
+
(x !== undefined && y !== undefined)
|
|
340
|
+
) {
|
|
263
341
|
const sides = area === undefined
|
|
264
|
-
?
|
|
342
|
+
? dataset.values.map((_, index) => {
|
|
343
|
+
const existing = existingArea(existingChildren[index], graphic.type);
|
|
344
|
+
return Math.sqrt(
|
|
345
|
+
existing ?? Math.PI * (config.radius ?? DEFAULT_POINT_RADIUS) ** 2
|
|
346
|
+
);
|
|
347
|
+
})
|
|
265
348
|
: area.map(value => Math.sqrt(value));
|
|
266
349
|
const centersX = x ?? existingChildren.map(child => child.properties.x);
|
|
267
350
|
const centersY = y ?? existingChildren.map(child => child.properties.y);
|
|
@@ -290,7 +373,11 @@ const rematerializePointMark = action(
|
|
|
290
373
|
} else if (encodedOpacity !== undefined) {
|
|
291
374
|
next = next.editGraphics({ target: id, property: "opacity", value: encodedOpacity });
|
|
292
375
|
}
|
|
293
|
-
if (config.stroke
|
|
376
|
+
if (config.stroke === false) {
|
|
377
|
+
next = next
|
|
378
|
+
.editGraphics({ target: id, property: "stroke", value: "transparent" })
|
|
379
|
+
.editGraphics({ target: id, property: "strokeWidth", value: 0 });
|
|
380
|
+
} else if (config.stroke !== undefined) {
|
|
294
381
|
next = next.editGraphics({ target: id, property: "stroke", value: config.stroke });
|
|
295
382
|
next = next.editGraphics({
|
|
296
383
|
target: id,
|
|
@@ -301,79 +388,3 @@ const rematerializePointMark = action(
|
|
|
301
388
|
return next;
|
|
302
389
|
}
|
|
303
390
|
);
|
|
304
|
-
|
|
305
|
-
const editPointMark = action(
|
|
306
|
-
{
|
|
307
|
-
op: "editPointMark",
|
|
308
|
-
description: "Edit constant point-mark appearance."
|
|
309
|
-
},
|
|
310
|
-
function (args = {}) {
|
|
311
|
-
validateMarkOptions(args, EDIT_POINT_OPTIONS, "editPointMark");
|
|
312
|
-
const editable = ["shape", "fill", "opacity", "stroke", "strokeWidth"];
|
|
313
|
-
if (!editable.some(property => Object.hasOwn(args, property))) {
|
|
314
|
-
throw new Error(
|
|
315
|
-
"editPointMark requires shape, fill, opacity, stroke, or strokeWidth."
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
const candidates = this.semanticSpec.layers.filter(
|
|
319
|
-
layer => layer.mark?.type === "point"
|
|
320
|
-
);
|
|
321
|
-
const current = findLayer(this, this.context.currentMark);
|
|
322
|
-
const requested = args.target ??
|
|
323
|
-
(current?.mark?.type === "point" ? current.id : undefined);
|
|
324
|
-
const inferred = requested ?? (candidates.length === 1
|
|
325
|
-
? candidates[0].id
|
|
326
|
-
: undefined);
|
|
327
|
-
const id = validateUserId(inferred, "Point mark id");
|
|
328
|
-
const layer = findLayer(this, id);
|
|
329
|
-
if (layer?.mark?.type !== "point") {
|
|
330
|
-
throw new Error(`Unknown point mark "${id}".`);
|
|
331
|
-
}
|
|
332
|
-
if (Object.hasOwn(args, "shape") && layer.encoding?.shape !== undefined) {
|
|
333
|
-
throw new Error(
|
|
334
|
-
"editPointMark shape cannot be combined with a shape encoding."
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
|
|
338
|
-
throw new Error(
|
|
339
|
-
"editPointMark fill cannot be combined with a color encoding."
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
const config = { ...this.markConfigs[id] };
|
|
343
|
-
if (Object.hasOwn(args, "shape")) {
|
|
344
|
-
config.shape = validatePointShape(args.shape);
|
|
345
|
-
}
|
|
346
|
-
if (Object.hasOwn(args, "fill")) {
|
|
347
|
-
config.fill = validateNonEmptyString(args.fill, "Point fill");
|
|
348
|
-
}
|
|
349
|
-
if (Object.hasOwn(args, "opacity")) {
|
|
350
|
-
config.opacity = validateUnitInterval(args.opacity, "Point opacity");
|
|
351
|
-
}
|
|
352
|
-
if (Object.hasOwn(args, "stroke")) {
|
|
353
|
-
config.stroke = validateNonEmptyString(args.stroke, "Point stroke");
|
|
354
|
-
config.strokeWidth ??= 1;
|
|
355
|
-
}
|
|
356
|
-
if (Object.hasOwn(args, "strokeWidth")) {
|
|
357
|
-
if (config.stroke === undefined) {
|
|
358
|
-
throw new Error("Point strokeWidth requires an active stroke.");
|
|
359
|
-
}
|
|
360
|
-
config.strokeWidth = validateNonNegativeFinite(
|
|
361
|
-
args.strokeWidth,
|
|
362
|
-
"Point strokeWidth"
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
const next = this
|
|
366
|
-
._withMarkConfig(id, config)
|
|
367
|
-
.rematerializePointMark({ id });
|
|
368
|
-
const legend = next.guideConfigs.legend?.series;
|
|
369
|
-
return legend?.target === id && legend.channels.includes("shape")
|
|
370
|
-
? rematerializeExistingLegend(next)
|
|
371
|
-
: next;
|
|
372
|
-
}
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
export function registerPointMarkActions(ProgramClass) {
|
|
376
|
-
ProgramClass.prototype.createPointMark = createPointMark;
|
|
377
|
-
ProgramClass.prototype.editPointMark = editPointMark;
|
|
378
|
-
ProgramClass.prototype.rematerializePointMark = rematerializePointMark;
|
|
379
|
-
}
|
|
@@ -102,6 +102,9 @@ const rematerializeRectMark = action(
|
|
|
102
102
|
if (graphic?.type !== "rect" || !Array.isArray(graphic.items)) {
|
|
103
103
|
throw new Error(`Rect mark "${id}" requires rect collection graphics.`);
|
|
104
104
|
}
|
|
105
|
+
if (this.markConfigs[id]?.gradientPlot?.materialized === true) {
|
|
106
|
+
return this.materializeGradientPlotFill({ id });
|
|
107
|
+
}
|
|
105
108
|
const dataset = findDataset(this, layer.data);
|
|
106
109
|
if (dataset === undefined) {
|
|
107
110
|
throw new Error(`Rect mark "${id}" requires an existing dataset.`);
|
|
@@ -27,7 +27,8 @@ function ownedChildren(program, id) {
|
|
|
27
27
|
config.regression?.lineId,
|
|
28
28
|
config.boxPlot?.whiskerId,
|
|
29
29
|
config.boxPlot?.medianId,
|
|
30
|
-
config.boxPlot?.outlierId
|
|
30
|
+
config.boxPlot?.outlierId,
|
|
31
|
+
config.gradientPlot?.centerId
|
|
31
32
|
].concat(
|
|
32
33
|
program.semanticSpec.layers
|
|
33
34
|
.filter(layer => layer.source === id)
|
|
@@ -88,7 +89,8 @@ function ownedDerivedData(program, ids) {
|
|
|
88
89
|
config.errorBand?.data,
|
|
89
90
|
config.regression?.dataId,
|
|
90
91
|
config.boxPlot?.summaryId,
|
|
91
|
-
config.boxPlot?.outlierDataId
|
|
92
|
+
config.boxPlot?.outlierDataId,
|
|
93
|
+
config.gradientPlot?.profileId
|
|
92
94
|
]) {
|
|
93
95
|
if (candidate !== undefined) candidates.add(candidate);
|
|
94
96
|
}
|
|
@@ -189,6 +191,13 @@ export const removeMark = action(
|
|
|
189
191
|
const selectionCleanup = cleanupSelectionState(next, ids);
|
|
190
192
|
next = selectionCleanup.program;
|
|
191
193
|
for (const id of ids) {
|
|
194
|
+
const labelLayout = next.materializationConfigs.labelLayouts?.[id];
|
|
195
|
+
if (
|
|
196
|
+
labelLayout?.leaderId !== undefined &&
|
|
197
|
+
next.graphicSpec.objects[labelLayout.leaderId] !== undefined
|
|
198
|
+
) {
|
|
199
|
+
next = next.editGraphics({ target: labelLayout.leaderId, remove: true });
|
|
200
|
+
}
|
|
192
201
|
if (findLayer(next, id) !== undefined) {
|
|
193
202
|
next = next.editSemantic({ property: `layer[${id}]`, remove: true });
|
|
194
203
|
}
|
|
@@ -196,6 +205,8 @@ export const removeMark = action(
|
|
|
196
205
|
next = next.editGraphics({ target: id, remove: true });
|
|
197
206
|
}
|
|
198
207
|
next = next._withoutMaterializationConfig(["marks", id]);
|
|
208
|
+
next = next._withoutMaterializationConfig(["jitters", id]);
|
|
209
|
+
next = next._withoutMaterializationConfig(["labelLayouts", id]);
|
|
199
210
|
}
|
|
200
211
|
|
|
201
212
|
next = cleanupPositionGuides(next, positionScales);
|
|
@@ -104,7 +104,17 @@ const createRuleMark = action(
|
|
|
104
104
|
length: 0,
|
|
105
105
|
...resolveMarkGraphicPlacement(created, { data, markType: "rule" })
|
|
106
106
|
})
|
|
107
|
-
._withMarkConfig(id,
|
|
107
|
+
._withMarkConfig(id, {
|
|
108
|
+
...DEFAULT_RULE_CONFIG,
|
|
109
|
+
...(inherited === undefined
|
|
110
|
+
? {}
|
|
111
|
+
: {
|
|
112
|
+
inheritedPosition: {
|
|
113
|
+
source: inherited.source,
|
|
114
|
+
channels: Object.keys(inherited.encoding)
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
});
|
|
108
118
|
return materializeInheritedMark(created, id);
|
|
109
119
|
}
|
|
110
120
|
);
|
|
@@ -143,7 +153,7 @@ const rematerializeRuleMark = action(
|
|
|
143
153
|
}
|
|
144
154
|
|
|
145
155
|
const scaleIds = [...new Set(
|
|
146
|
-
["x", "y", "x2", "y2", "strokeDash", "opacity"]
|
|
156
|
+
["x", "y", "x2", "y2", "strokeDash", "strokeWidth", "opacity"]
|
|
147
157
|
.map(channel => layer.encoding?.[channel]?.scale)
|
|
148
158
|
.filter(scale => scale !== undefined)
|
|
149
159
|
)];
|
|
@@ -153,6 +163,11 @@ const rematerializeRuleMark = action(
|
|
|
153
163
|
}
|
|
154
164
|
|
|
155
165
|
const derived = deriveRuleValues(dataset.values, layer);
|
|
166
|
+
if (derived.values.strokeWidth?.some(value => value < 0)) {
|
|
167
|
+
throw new RangeError(
|
|
168
|
+
`Rule strokeWidth field "${layer.encoding.strokeWidth.field}" cannot contain negative values.`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
156
171
|
const mapped = {};
|
|
157
172
|
for (const channel of ["x", "y", "x2", "y2"]) {
|
|
158
173
|
const encoding = layer.encoding?.[channel];
|
|
@@ -223,9 +238,13 @@ const rematerializeRuleMark = action(
|
|
|
223
238
|
}
|
|
224
239
|
}
|
|
225
240
|
|
|
226
|
-
const resolvedConfig =
|
|
241
|
+
const resolvedConfig = {
|
|
242
|
+
...DEFAULT_RULE_CONFIG,
|
|
243
|
+
...resolved.markConfigs[id]
|
|
244
|
+
};
|
|
227
245
|
const dashEncoding = layer.encoding?.strokeDash;
|
|
228
246
|
const opacityEncoding = layer.encoding?.opacity;
|
|
247
|
+
const strokeWidthEncoding = layer.encoding?.strokeWidth;
|
|
229
248
|
const strokeDash = dashEncoding?.scale === undefined
|
|
230
249
|
? Array.from(
|
|
231
250
|
{ length: derived.length },
|
|
@@ -244,6 +263,12 @@ const rematerializeRuleMark = action(
|
|
|
244
263
|
derived.values.opacity,
|
|
245
264
|
resolved.resolvedScales[opacityEncoding.scale]
|
|
246
265
|
);
|
|
266
|
+
const strokeWidth = strokeWidthEncoding?.scale === undefined
|
|
267
|
+
? resolvedConfig.strokeWidth
|
|
268
|
+
: mapContinuousScaleValues(
|
|
269
|
+
derived.values.strokeWidth,
|
|
270
|
+
resolved.resolvedScales[strokeWidthEncoding.scale]
|
|
271
|
+
);
|
|
247
272
|
|
|
248
273
|
return resolved
|
|
249
274
|
.editGraphics({ target: id, property: "length", value: derived.length })
|
|
@@ -255,7 +280,7 @@ const rematerializeRuleMark = action(
|
|
|
255
280
|
.editGraphics({
|
|
256
281
|
target: id,
|
|
257
282
|
property: "strokeWidth",
|
|
258
|
-
value:
|
|
283
|
+
value: strokeWidth
|
|
259
284
|
})
|
|
260
285
|
.editGraphics({ target: id, property: "strokeDash", value: strokeDash })
|
|
261
286
|
.editGraphics({ target: id, property: "opacity", value: opacity });
|
|
@@ -24,14 +24,19 @@ const STYLE_OPTIONS = Object.freeze([
|
|
|
24
24
|
]);
|
|
25
25
|
const CREATE_OPTIONS = Object.freeze(["id", "data", "text", ...STYLE_OPTIONS]);
|
|
26
26
|
const EDIT_OPTIONS = Object.freeze(["target", ...STYLE_OPTIONS]);
|
|
27
|
-
const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
|
|
27
|
+
const REMATERIALIZE_OPTIONS = Object.freeze(["id", "replayLayout"]);
|
|
28
28
|
const SOURCE_TYPES = new Set(["point", "bar", "rule", "rect"]);
|
|
29
29
|
|
|
30
|
+
function sourceMatchesData(program, layer, requestedData) {
|
|
31
|
+
if (requestedData === undefined || layer?.data === requestedData) return true;
|
|
32
|
+
return program.markConfigs[layer.id]?.gradientPlot?.source === requestedData;
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
function eligibleSource(program, layer, requestedData) {
|
|
31
36
|
if (
|
|
32
37
|
!SOURCE_TYPES.has(layer?.mark?.type) ||
|
|
33
38
|
layer.data === undefined ||
|
|
34
|
-
(
|
|
39
|
+
!sourceMatchesData(program, layer, requestedData)
|
|
35
40
|
) return false;
|
|
36
41
|
const encodings = resolveCompatibleEncodings(program, layer, "text");
|
|
37
42
|
return encodings.x !== undefined && encodings.y !== undefined;
|
|
@@ -133,6 +138,10 @@ const rematerializeTextMark = action(
|
|
|
133
138
|
function (args = {}) {
|
|
134
139
|
validateMarkOptions(args, REMATERIALIZE_OPTIONS, "rematerializeTextMark");
|
|
135
140
|
const id = validateUserId(args.id, "Text mark id");
|
|
141
|
+
const replayLayout = args.replayLayout ?? true;
|
|
142
|
+
if (typeof replayLayout !== "boolean") {
|
|
143
|
+
throw new TypeError("rematerializeTextMark replayLayout must be a boolean.");
|
|
144
|
+
}
|
|
136
145
|
const layer = findLayer(this, id);
|
|
137
146
|
const graphic = this.graphicSpec.objects[id];
|
|
138
147
|
if (layer?.mark?.type !== "text") throw new Error(`Unknown text mark "${id}".`);
|
|
@@ -140,11 +149,14 @@ const rematerializeTextMark = action(
|
|
|
140
149
|
throw new Error(`Text mark "${id}" requires text collection graphics.`);
|
|
141
150
|
}
|
|
142
151
|
if (!canMaterializeText(this, layer)) {
|
|
143
|
-
|
|
152
|
+
const next = graphic.items.length === 0
|
|
144
153
|
? this
|
|
145
154
|
: this.editGraphics({ target: id, property: "length", value: 0 });
|
|
155
|
+
return replayLayout && next.materializationConfigs.labelLayouts?.[id] !== undefined
|
|
156
|
+
? next.materializeLabelLayout({ id, rematerializeBase: false })
|
|
157
|
+
: next;
|
|
146
158
|
}
|
|
147
|
-
|
|
159
|
+
const next = this.editGraphics({
|
|
148
160
|
target: id,
|
|
149
161
|
property: "items",
|
|
150
162
|
value: resolveTextGraphicItems(
|
|
@@ -153,6 +165,9 @@ const rematerializeTextMark = action(
|
|
|
153
165
|
this.markConfigs[id] ?? DEFAULT_TEXT_MARK
|
|
154
166
|
)
|
|
155
167
|
});
|
|
168
|
+
return replayLayout && next.materializationConfigs.labelLayouts?.[id] !== undefined
|
|
169
|
+
? next.materializeLabelLayout({ id, rematerializeBase: false })
|
|
170
|
+
: next;
|
|
156
171
|
}
|
|
157
172
|
);
|
|
158
173
|
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import { registerTextMarkActions as registerTextCoreActions } from "./actions.js";
|
|
2
|
+
import { registerTextLabelLayoutActions } from "./layout.js";
|
|
3
|
+
|
|
4
|
+
export function registerTextMarkActions(ProgramClass) {
|
|
5
|
+
registerTextCoreActions(ProgramClass);
|
|
6
|
+
registerTextLabelLayoutActions(ProgramClass);
|
|
7
|
+
}
|