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,6 +1,4 @@
|
|
|
1
1
|
import { action } from "../../../core/action.js";
|
|
2
|
-
import { deriveLineSeries } from "../../../grammar/lineSeries.js";
|
|
3
|
-
import { mapContinuousScaleValues, mapOrdinalValues } from "../../../grammar/scales/index.js";
|
|
4
2
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
5
3
|
import {
|
|
6
4
|
validateNonEmptyString,
|
|
@@ -19,18 +17,17 @@ import {
|
|
|
19
17
|
import { DEFAULT_COLORS } from "../../../theme/defaults.js";
|
|
20
18
|
import { findDataset } from "../../../selectors/datasets.js";
|
|
21
19
|
import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
22
|
-
import {
|
|
23
|
-
buildCurvePathCommands,
|
|
24
|
-
validateCurveInterpolation
|
|
25
|
-
} from "../../../grammar/curveCommands.js";
|
|
26
|
-
import { buildPolarLinePathCommands } from "../../../grammar/polarLineCommands.js";
|
|
27
|
-
import { resolvePolarFrame } from "../../../grammar/polar.js";
|
|
20
|
+
import { validateCurveInterpolation } from "../../../grammar/curveCommands.js";
|
|
28
21
|
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
29
|
-
import { normalizeStrokeDashPattern } from "../../../grammar/scales/index.js";
|
|
30
22
|
import { canMaterializeLine } from "../../../materialization/marks/index.js";
|
|
31
23
|
import { resolveMarkGraphicPlacement } from
|
|
32
24
|
"../../../materialization/graphicHierarchy.js";
|
|
33
25
|
import { rematerializeHighlightBaseline } from "../lifecycle.js";
|
|
26
|
+
import { validateParallelRows } from "../../../grammar/parallelCoordinates.js";
|
|
27
|
+
import {
|
|
28
|
+
resolveParallelLineMaterialization,
|
|
29
|
+
resolvePositionedLineMaterialization
|
|
30
|
+
} from "./materialize.js";
|
|
34
31
|
|
|
35
32
|
const DEFAULT_LINE_STROKE = DEFAULT_COLORS.mark;
|
|
36
33
|
const DEFAULT_LINE_WIDTH = 2;
|
|
@@ -54,7 +51,17 @@ function isPolarLine(layer) {
|
|
|
54
51
|
layer.encoding?.radius !== undefined;
|
|
55
52
|
}
|
|
56
53
|
|
|
54
|
+
function isParallelLine(layer) {
|
|
55
|
+
return layer.encoding?.parallel !== undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
57
58
|
function validatePolarLineConfig(layer, config) {
|
|
59
|
+
if (isParallelLine(layer)) {
|
|
60
|
+
if ((config.curve ?? "linear") !== "linear" || config.closed === true) {
|
|
61
|
+
throw new Error("Parallel lines require curve \"linear\" and closed false.");
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
58
65
|
if (!isPolarLine(layer)) return;
|
|
59
66
|
if ((config.curve ?? "linear") !== "linear") {
|
|
60
67
|
throw new Error("Polar line position currently requires curve \"linear\".");
|
|
@@ -84,9 +91,6 @@ const createLineMark = action(
|
|
|
84
91
|
args.strokeWidth ?? DEFAULT_LINE_WIDTH,
|
|
85
92
|
"Line strokeWidth"
|
|
86
93
|
);
|
|
87
|
-
if (args.scales !== undefined && typeof args.scales !== "boolean") {
|
|
88
|
-
throw new TypeError("rematerializeLineMark scales must be a boolean.");
|
|
89
|
-
}
|
|
90
94
|
const curve = validateCurveInterpolation(args.curve ?? "linear");
|
|
91
95
|
const closed = Object.hasOwn(args, "closed")
|
|
92
96
|
? validateClosed(args.closed)
|
|
@@ -176,6 +180,7 @@ const rematerializeLineMark = action(
|
|
|
176
180
|
if (highlighted !== undefined) return highlighted;
|
|
177
181
|
const { dataset, layer } = requireLine(this, id);
|
|
178
182
|
const existingChildren = this.graphicSpec.objects[id].items;
|
|
183
|
+
const parallel = layer.encoding?.parallel;
|
|
179
184
|
const xScaleId = layer.encoding?.x?.scale;
|
|
180
185
|
const yScaleId = layer.encoding?.y?.scale;
|
|
181
186
|
const thetaScaleId = layer.encoding?.theta?.scale;
|
|
@@ -184,6 +189,64 @@ const rematerializeLineMark = action(
|
|
|
184
189
|
const config = this.markConfigs[id] ?? {};
|
|
185
190
|
validatePolarLineConfig(layer, config);
|
|
186
191
|
|
|
192
|
+
if (parallel !== undefined) {
|
|
193
|
+
validateParallelRows(dataset.values, parallel.dimensions, parallel);
|
|
194
|
+
let resolved = this;
|
|
195
|
+
if (args.scales !== false) {
|
|
196
|
+
for (const dimension of parallel.dimensions) {
|
|
197
|
+
resolved = resolved.rematerializeScale({ id: dimension.scale });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
for (const channel of args.scales === false
|
|
201
|
+
? []
|
|
202
|
+
: ["color", "strokeDash", "strokeWidth"]) {
|
|
203
|
+
const scaleId = layer.encoding?.[channel]?.scale;
|
|
204
|
+
if (scaleId !== undefined) {
|
|
205
|
+
resolved = resolved.rematerializeScale({ id: scaleId });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const materialization = resolveParallelLineMaterialization({
|
|
209
|
+
rows: dataset.values,
|
|
210
|
+
parallel,
|
|
211
|
+
layer,
|
|
212
|
+
resolvedScales: resolved.resolvedScales,
|
|
213
|
+
bounds: resolveGraphicBounds(resolved),
|
|
214
|
+
config,
|
|
215
|
+
existingChildren,
|
|
216
|
+
defaults: { stroke: DEFAULT_LINE_STROKE, strokeWidth: DEFAULT_LINE_WIDTH }
|
|
217
|
+
});
|
|
218
|
+
let next = resolved
|
|
219
|
+
.editGraphics({
|
|
220
|
+
target: id,
|
|
221
|
+
property: "length",
|
|
222
|
+
value: materialization.commands.length
|
|
223
|
+
})
|
|
224
|
+
.editGraphics({
|
|
225
|
+
target: id,
|
|
226
|
+
property: "commands",
|
|
227
|
+
value: materialization.commands
|
|
228
|
+
})
|
|
229
|
+
.editGraphics({ target: id, property: "stroke", value: materialization.strokes })
|
|
230
|
+
.editGraphics({
|
|
231
|
+
target: id,
|
|
232
|
+
property: "strokeWidth",
|
|
233
|
+
value: materialization.strokeWidths
|
|
234
|
+
})
|
|
235
|
+
.editGraphics({
|
|
236
|
+
target: id,
|
|
237
|
+
property: "strokeDash",
|
|
238
|
+
value: materialization.strokeDashes
|
|
239
|
+
});
|
|
240
|
+
if (config.opacity !== undefined) {
|
|
241
|
+
next = next.editGraphics({
|
|
242
|
+
target: id,
|
|
243
|
+
property: "opacity",
|
|
244
|
+
value: config.opacity
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return next;
|
|
248
|
+
}
|
|
249
|
+
|
|
187
250
|
if (
|
|
188
251
|
polar
|
|
189
252
|
? thetaScaleId === undefined || radiusScaleId === undefined
|
|
@@ -206,85 +269,37 @@ const rematerializeLineMark = action(
|
|
|
206
269
|
.rematerializeScale({ id: xScaleId })
|
|
207
270
|
.rematerializeScale({ id: yScaleId });
|
|
208
271
|
|
|
209
|
-
for (const channel of args.scales === false ? [] : ["color", "strokeDash"]) {
|
|
272
|
+
for (const channel of args.scales === false ? [] : ["color", "strokeDash", "strokeWidth"]) {
|
|
210
273
|
const scaleId = layer.encoding?.[channel]?.scale;
|
|
211
274
|
if (scaleId !== undefined) {
|
|
212
275
|
resolved = resolved.rematerializeScale({ id: scaleId });
|
|
213
276
|
}
|
|
214
277
|
}
|
|
215
278
|
|
|
216
|
-
const
|
|
217
|
-
dataset.values,
|
|
279
|
+
const materialization = resolvePositionedLineMaterialization({
|
|
280
|
+
rows: dataset.values,
|
|
218
281
|
layer,
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
thetaFieldType: derived.thetaFieldType,
|
|
227
|
-
thetaScale: resolved.resolvedScales[thetaScaleId],
|
|
228
|
-
radiusScale: resolved.resolvedScales[radiusScaleId],
|
|
229
|
-
frame: resolvePolarFrame(resolveGraphicBounds(resolved)),
|
|
230
|
-
closed: config.closed ?? false
|
|
231
|
-
}))
|
|
232
|
-
: derived.series.map(series => {
|
|
233
|
-
const x = mapContinuousScaleValues(
|
|
234
|
-
series.values.map(value => value.x),
|
|
235
|
-
resolved.resolvedScales[xScaleId]
|
|
236
|
-
);
|
|
237
|
-
const y = mapContinuousScaleValues(
|
|
238
|
-
series.values.map(value => value.y),
|
|
239
|
-
resolved.resolvedScales[yScaleId]
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
return buildCurvePathCommands(
|
|
243
|
-
series.values.map((_, index) => ({ x: x[index], y: y[index] })),
|
|
244
|
-
config.curve ?? "linear"
|
|
245
|
-
);
|
|
246
|
-
});
|
|
247
|
-
const colorEncoding = layer.encoding?.color;
|
|
248
|
-
const dashEncoding = layer.encoding?.strokeDash;
|
|
249
|
-
const strokes = colorEncoding?.scale === undefined
|
|
250
|
-
? commands.map(
|
|
251
|
-
(_, index) =>
|
|
252
|
-
config.stroke ??
|
|
253
|
-
existingChildren[index]?.properties.stroke ??
|
|
254
|
-
DEFAULT_LINE_STROKE
|
|
255
|
-
)
|
|
256
|
-
: mapOrdinalValues(
|
|
257
|
-
derived.series.map(series => series.key[colorEncoding.field]),
|
|
258
|
-
resolved.resolvedScales[colorEncoding.scale].domain,
|
|
259
|
-
resolved.resolvedScales[colorEncoding.scale].range
|
|
260
|
-
);
|
|
261
|
-
const strokeWidths = commands.map(
|
|
262
|
-
(_, index) =>
|
|
263
|
-
config.strokeWidth ??
|
|
264
|
-
existingChildren[index]?.properties.strokeWidth ??
|
|
265
|
-
DEFAULT_LINE_WIDTH
|
|
266
|
-
);
|
|
267
|
-
const strokeDashes = dashEncoding?.datum !== undefined
|
|
268
|
-
? commands.map(() => normalizeStrokeDashPattern(dashEncoding.datum))
|
|
269
|
-
: dashEncoding?.scale === undefined
|
|
270
|
-
? commands.map(
|
|
271
|
-
(_, index) => existingChildren[index]?.properties.strokeDash ?? []
|
|
272
|
-
)
|
|
273
|
-
: mapOrdinalValues(
|
|
274
|
-
derived.series.map(series => series.key[dashEncoding.field]),
|
|
275
|
-
resolved.resolvedScales[dashEncoding.scale].domain,
|
|
276
|
-
resolved.resolvedScales[dashEncoding.scale].range
|
|
277
|
-
);
|
|
282
|
+
resolvedScales: resolved.resolvedScales,
|
|
283
|
+
bounds: resolveGraphicBounds(resolved),
|
|
284
|
+
config,
|
|
285
|
+
existingChildren,
|
|
286
|
+
polar,
|
|
287
|
+
defaults: { stroke: DEFAULT_LINE_STROKE, strokeWidth: DEFAULT_LINE_WIDTH }
|
|
288
|
+
});
|
|
278
289
|
|
|
279
290
|
let next = resolved
|
|
280
|
-
.editGraphics({
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
291
|
+
.editGraphics({
|
|
292
|
+
target: id,
|
|
293
|
+
property: "length",
|
|
294
|
+
value: materialization.commands.length
|
|
295
|
+
})
|
|
296
|
+
.editGraphics({ target: id, property: "commands", value: materialization.commands })
|
|
297
|
+
.editGraphics({ target: id, property: "stroke", value: materialization.strokes })
|
|
298
|
+
.editGraphics({ target: id, property: "strokeWidth", value: materialization.strokeWidths })
|
|
284
299
|
.editGraphics({
|
|
285
300
|
target: id,
|
|
286
301
|
property: "strokeDash",
|
|
287
|
-
value: strokeDashes
|
|
302
|
+
value: materialization.strokeDashes
|
|
288
303
|
});
|
|
289
304
|
if (config.opacity !== undefined) {
|
|
290
305
|
next = next.editGraphics({
|
|
@@ -329,13 +344,17 @@ const editLineMark = action(
|
|
|
329
344
|
);
|
|
330
345
|
}
|
|
331
346
|
if (Object.hasOwn(args, "closed") && args.closed === true &&
|
|
332
|
-
layer.encoding?.x !== undefined) {
|
|
347
|
+
(layer.encoding?.x !== undefined || isParallelLine(layer))) {
|
|
333
348
|
throw new Error("Line closed requires theta/radius Polar position encodings.");
|
|
334
349
|
}
|
|
335
350
|
if (Object.hasOwn(args, "curve") && isPolarLine(layer) &&
|
|
336
351
|
args.curve !== "linear") {
|
|
337
352
|
throw new Error("Polar line position currently requires curve \"linear\".");
|
|
338
353
|
}
|
|
354
|
+
if (Object.hasOwn(args, "curve") && isParallelLine(layer) &&
|
|
355
|
+
args.curve !== "linear") {
|
|
356
|
+
throw new Error("Parallel lines require curve \"linear\".");
|
|
357
|
+
}
|
|
339
358
|
const config = {
|
|
340
359
|
...this.markConfigs[layer.id],
|
|
341
360
|
...(Object.hasOwn(args, "stroke")
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deriveLineSeries,
|
|
3
|
+
deriveLineSeriesFieldValues
|
|
4
|
+
} from "../../../grammar/lineSeries.js";
|
|
5
|
+
import {
|
|
6
|
+
mapContinuousScaleValues,
|
|
7
|
+
mapOrdinalValues,
|
|
8
|
+
normalizeStrokeDashPattern
|
|
9
|
+
} from "../../../grammar/scales/index.js";
|
|
10
|
+
import { buildCurvePathCommands } from
|
|
11
|
+
"../../../grammar/curveCommands.js";
|
|
12
|
+
import { buildPolarLinePathCommands } from
|
|
13
|
+
"../../../grammar/polarLineCommands.js";
|
|
14
|
+
import { resolvePolarFrame } from "../../../grammar/polar.js";
|
|
15
|
+
import { materializeParallelRows } from
|
|
16
|
+
"../../../grammar/parallelCoordinates.js";
|
|
17
|
+
import { mapScaleConsumerValues } from
|
|
18
|
+
"../../../materialization/scales/map.js";
|
|
19
|
+
|
|
20
|
+
function existingValue(children, index, property, fallback) {
|
|
21
|
+
return children[index]?.properties[property] ?? fallback;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveParallelLineMaterialization({
|
|
25
|
+
rows,
|
|
26
|
+
parallel,
|
|
27
|
+
layer,
|
|
28
|
+
resolvedScales,
|
|
29
|
+
bounds,
|
|
30
|
+
config,
|
|
31
|
+
existingChildren,
|
|
32
|
+
defaults
|
|
33
|
+
}) {
|
|
34
|
+
const items = materializeParallelRows(
|
|
35
|
+
rows,
|
|
36
|
+
parallel.dimensions,
|
|
37
|
+
resolvedScales,
|
|
38
|
+
bounds,
|
|
39
|
+
parallel
|
|
40
|
+
);
|
|
41
|
+
const sourceRows = items.map(item => rows[item.sourceRowIndex]);
|
|
42
|
+
const mapAppearance = (channel, fallback) => {
|
|
43
|
+
const encoding = layer.encoding?.[channel];
|
|
44
|
+
if (encoding?.datum !== undefined) {
|
|
45
|
+
return items.map(() => channel === "strokeDash"
|
|
46
|
+
? normalizeStrokeDashPattern(encoding.datum)
|
|
47
|
+
: encoding.datum);
|
|
48
|
+
}
|
|
49
|
+
if (encoding?.scale === undefined) return items.map(fallback);
|
|
50
|
+
return mapScaleConsumerValues(
|
|
51
|
+
sourceRows.map(row => row[encoding.field]),
|
|
52
|
+
resolvedScales[encoding.scale],
|
|
53
|
+
channel
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
commands: items.map(item => item.commands),
|
|
58
|
+
strokes: mapAppearance(
|
|
59
|
+
"color",
|
|
60
|
+
(_, index) => config.stroke ??
|
|
61
|
+
existingValue(existingChildren, index, "stroke", defaults.stroke)
|
|
62
|
+
),
|
|
63
|
+
strokeWidths: mapAppearance(
|
|
64
|
+
"strokeWidth",
|
|
65
|
+
(_, index) => config.strokeWidth ??
|
|
66
|
+
existingValue(existingChildren, index, "strokeWidth", defaults.strokeWidth)
|
|
67
|
+
),
|
|
68
|
+
strokeDashes: mapAppearance(
|
|
69
|
+
"strokeDash",
|
|
70
|
+
(_, index) => existingValue(existingChildren, index, "strokeDash", [])
|
|
71
|
+
)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function resolvePositionedLineMaterialization({
|
|
76
|
+
rows,
|
|
77
|
+
layer,
|
|
78
|
+
resolvedScales,
|
|
79
|
+
bounds,
|
|
80
|
+
config,
|
|
81
|
+
existingChildren,
|
|
82
|
+
polar,
|
|
83
|
+
defaults
|
|
84
|
+
}) {
|
|
85
|
+
const xScaleId = layer.encoding?.x?.scale;
|
|
86
|
+
const yScaleId = layer.encoding?.y?.scale;
|
|
87
|
+
const thetaScaleId = layer.encoding?.theta?.scale;
|
|
88
|
+
const radiusScaleId = layer.encoding?.radius?.scale;
|
|
89
|
+
const derived = deriveLineSeries(
|
|
90
|
+
rows,
|
|
91
|
+
layer,
|
|
92
|
+
polar ? { thetaDomain: resolvedScales[thetaScaleId].domain } : undefined
|
|
93
|
+
);
|
|
94
|
+
const commands = polar
|
|
95
|
+
? derived.series.map(series => buildPolarLinePathCommands({
|
|
96
|
+
series: series.values,
|
|
97
|
+
thetaFieldType: derived.thetaFieldType,
|
|
98
|
+
thetaScale: resolvedScales[thetaScaleId],
|
|
99
|
+
radiusScale: resolvedScales[radiusScaleId],
|
|
100
|
+
frame: resolvePolarFrame(bounds),
|
|
101
|
+
closed: config.closed ?? false
|
|
102
|
+
}))
|
|
103
|
+
: derived.series.map(series => {
|
|
104
|
+
const x = mapContinuousScaleValues(
|
|
105
|
+
series.values.map(value => value.x),
|
|
106
|
+
resolvedScales[xScaleId]
|
|
107
|
+
);
|
|
108
|
+
const y = mapContinuousScaleValues(
|
|
109
|
+
series.values.map(value => value.y),
|
|
110
|
+
resolvedScales[yScaleId]
|
|
111
|
+
);
|
|
112
|
+
return buildCurvePathCommands(
|
|
113
|
+
series.values.map((_, index) => ({ x: x[index], y: y[index] })),
|
|
114
|
+
config.curve ?? "linear"
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
const colorEncoding = layer.encoding?.color;
|
|
118
|
+
const dashEncoding = layer.encoding?.strokeDash;
|
|
119
|
+
const widthEncoding = layer.encoding?.strokeWidth;
|
|
120
|
+
const strokes = colorEncoding?.scale === undefined
|
|
121
|
+
? commands.map((_, index) => config.stroke ??
|
|
122
|
+
existingValue(existingChildren, index, "stroke", defaults.stroke))
|
|
123
|
+
: mapOrdinalValues(
|
|
124
|
+
derived.series.map(series => series.key[colorEncoding.field]),
|
|
125
|
+
resolvedScales[colorEncoding.scale].domain,
|
|
126
|
+
resolvedScales[colorEncoding.scale].range
|
|
127
|
+
);
|
|
128
|
+
const strokeWidths = widthEncoding?.scale === undefined
|
|
129
|
+
? commands.map((_, index) => config.strokeWidth ??
|
|
130
|
+
existingValue(existingChildren, index, "strokeWidth", defaults.strokeWidth))
|
|
131
|
+
: mapContinuousScaleValues(
|
|
132
|
+
deriveLineSeriesFieldValues(rows, layer, derived, widthEncoding.field),
|
|
133
|
+
resolvedScales[widthEncoding.scale]
|
|
134
|
+
);
|
|
135
|
+
const strokeDashes = dashEncoding?.datum !== undefined
|
|
136
|
+
? commands.map(() => normalizeStrokeDashPattern(dashEncoding.datum))
|
|
137
|
+
: dashEncoding?.scale === undefined
|
|
138
|
+
? commands.map((_, index) =>
|
|
139
|
+
existingValue(existingChildren, index, "strokeDash", []))
|
|
140
|
+
: mapOrdinalValues(
|
|
141
|
+
derived.series.map(series => series.key[dashEncoding.field]),
|
|
142
|
+
resolvedScales[dashEncoding.scale].domain,
|
|
143
|
+
resolvedScales[dashEncoding.scale].range
|
|
144
|
+
);
|
|
145
|
+
return { commands, strokes, strokeWidths, strokeDashes };
|
|
146
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validatePointShape } from "../../../grammar/pointShapes.js";
|
|
3
|
+
import { getPointGraphicType } from "../../../grammar/schemas/mark.js";
|
|
4
|
+
import { resolveMarkGraphicPlacement } from
|
|
5
|
+
"../../../materialization/graphicHierarchy.js";
|
|
6
|
+
import {
|
|
7
|
+
applyLayeredMarkInheritance,
|
|
8
|
+
assertMarkAvailable,
|
|
9
|
+
materializeInheritedMark,
|
|
10
|
+
resolveLayeredMarkInheritance,
|
|
11
|
+
resolveMarkData,
|
|
12
|
+
resolveMarkId,
|
|
13
|
+
validateMarkOptions
|
|
14
|
+
} from "../shared.js";
|
|
15
|
+
|
|
16
|
+
const OPTIONS = Object.freeze([
|
|
17
|
+
"id", "data", "shape", "fill", "opacity", "stroke", "strokeWidth"
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
export const createPointMark = action(
|
|
21
|
+
{
|
|
22
|
+
op: "createPointMark",
|
|
23
|
+
description: "Create a semantic point mark and concrete point graphics."
|
|
24
|
+
},
|
|
25
|
+
function (args = {}) {
|
|
26
|
+
validateMarkOptions(args, OPTIONS, "createPointMark");
|
|
27
|
+
const id = resolveMarkId(this, args.id, {
|
|
28
|
+
defaultId: "point",
|
|
29
|
+
label: "Point mark id",
|
|
30
|
+
markType: "point",
|
|
31
|
+
operation: "createPointMark"
|
|
32
|
+
});
|
|
33
|
+
const inherited = resolveLayeredMarkInheritance(this, args, "point");
|
|
34
|
+
const { data, dataset } = resolveMarkData(this, {
|
|
35
|
+
...args,
|
|
36
|
+
...(args.data === undefined &&
|
|
37
|
+
this.context.currentData === undefined &&
|
|
38
|
+
inherited?.data !== undefined
|
|
39
|
+
? { data: inherited.data }
|
|
40
|
+
: {})
|
|
41
|
+
});
|
|
42
|
+
const shape = Object.hasOwn(args, "shape") ? args.shape : "circle";
|
|
43
|
+
validatePointShape(shape);
|
|
44
|
+
const resolvedType = getPointGraphicType(shape);
|
|
45
|
+
const graphicType = resolvedType === "path" ? "circle" : resolvedType;
|
|
46
|
+
|
|
47
|
+
assertMarkAvailable(this, id);
|
|
48
|
+
|
|
49
|
+
let next = this
|
|
50
|
+
.editSemantic({
|
|
51
|
+
property: `layer[${id}].mark.type`,
|
|
52
|
+
value: "point"
|
|
53
|
+
})
|
|
54
|
+
.editSemantic({
|
|
55
|
+
property: `layer[${id}].data`,
|
|
56
|
+
value: data
|
|
57
|
+
});
|
|
58
|
+
next = applyLayeredMarkInheritance(next, id, inherited)
|
|
59
|
+
.createGraphics({
|
|
60
|
+
id,
|
|
61
|
+
type: graphicType,
|
|
62
|
+
length: dataset.values.length,
|
|
63
|
+
...resolveMarkGraphicPlacement(next, { data, markType: "point" })
|
|
64
|
+
})
|
|
65
|
+
._withMarkConfig(id, { shape });
|
|
66
|
+
const created = materializeInheritedMark(next, id);
|
|
67
|
+
const appearance = Object.fromEntries(
|
|
68
|
+
["fill", "opacity", "stroke", "strokeWidth"]
|
|
69
|
+
.filter(property => Object.hasOwn(args, property))
|
|
70
|
+
.map(property => [property, args[property]])
|
|
71
|
+
);
|
|
72
|
+
return Object.keys(appearance).length === 0
|
|
73
|
+
? created
|
|
74
|
+
: created.editPointMark({ target: id, ...appearance });
|
|
75
|
+
}
|
|
76
|
+
);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
+
import {
|
|
4
|
+
validateNonEmptyString,
|
|
5
|
+
validateNonNegativeFinite,
|
|
6
|
+
validateUnitInterval
|
|
7
|
+
} from "../../../core/validation.js";
|
|
8
|
+
import { validatePointShape } from "../../../grammar/pointShapes.js";
|
|
9
|
+
import { findLayer } from "../../../selectors/layers.js";
|
|
10
|
+
import { rematerializeExistingLegend } from "../../encodings/shared.js";
|
|
11
|
+
import { validateMarkOptions } from "../shared.js";
|
|
12
|
+
|
|
13
|
+
const OPTIONS = Object.freeze([
|
|
14
|
+
"target", "shape", "fill", "opacity", "stroke", "strokeWidth"
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
export const editPointMark = action(
|
|
18
|
+
{
|
|
19
|
+
op: "editPointMark",
|
|
20
|
+
description: "Edit constant point-mark appearance."
|
|
21
|
+
},
|
|
22
|
+
function (args = {}) {
|
|
23
|
+
validateMarkOptions(args, OPTIONS, "editPointMark");
|
|
24
|
+
const editable = ["shape", "fill", "opacity", "stroke", "strokeWidth"];
|
|
25
|
+
if (!editable.some(property => Object.hasOwn(args, property))) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
"editPointMark requires shape, fill, opacity, stroke, or strokeWidth."
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
const candidates = this.semanticSpec.layers.filter(
|
|
31
|
+
layer => layer.mark?.type === "point"
|
|
32
|
+
);
|
|
33
|
+
const current = findLayer(this, this.context.currentMark);
|
|
34
|
+
const requested = args.target ??
|
|
35
|
+
(current?.mark?.type === "point" ? current.id : undefined);
|
|
36
|
+
const inferred = requested ?? (candidates.length === 1
|
|
37
|
+
? candidates[0].id
|
|
38
|
+
: undefined);
|
|
39
|
+
const id = validateUserId(inferred, "Point mark id");
|
|
40
|
+
const layer = findLayer(this, id);
|
|
41
|
+
if (layer?.mark?.type !== "point") {
|
|
42
|
+
throw new Error(`Unknown point mark "${id}".`);
|
|
43
|
+
}
|
|
44
|
+
if (Object.hasOwn(args, "shape") && layer.encoding?.shape !== undefined) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
"editPointMark shape cannot be combined with a shape encoding."
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
"editPointMark fill cannot be combined with a color encoding."
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const config = { ...this.markConfigs[id] };
|
|
55
|
+
if (Object.hasOwn(args, "shape")) {
|
|
56
|
+
config.shape = validatePointShape(args.shape);
|
|
57
|
+
}
|
|
58
|
+
if (Object.hasOwn(args, "fill")) {
|
|
59
|
+
config.fill = validateNonEmptyString(args.fill, "Point fill");
|
|
60
|
+
}
|
|
61
|
+
if (Object.hasOwn(args, "opacity")) {
|
|
62
|
+
config.opacity = validateUnitInterval(args.opacity, "Point opacity");
|
|
63
|
+
}
|
|
64
|
+
if (args.stroke === false && Object.hasOwn(args, "strokeWidth")) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
"editPointMark cannot set strokeWidth while removing stroke."
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (Object.hasOwn(args, "stroke")) {
|
|
70
|
+
if (args.stroke === false) {
|
|
71
|
+
config.stroke = false;
|
|
72
|
+
delete config.strokeWidth;
|
|
73
|
+
} else {
|
|
74
|
+
const restoresStroke = config.stroke === false;
|
|
75
|
+
config.stroke = validateNonEmptyString(args.stroke, "Point stroke");
|
|
76
|
+
if (restoresStroke) config.strokeWidth = 1;
|
|
77
|
+
else config.strokeWidth ??= 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (Object.hasOwn(args, "strokeWidth")) {
|
|
81
|
+
if (typeof config.stroke !== "string") {
|
|
82
|
+
throw new Error("Point strokeWidth requires an active stroke.");
|
|
83
|
+
}
|
|
84
|
+
config.strokeWidth = validateNonNegativeFinite(
|
|
85
|
+
args.strokeWidth,
|
|
86
|
+
"Point strokeWidth"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const next = this
|
|
90
|
+
._withMarkConfig(id, config)
|
|
91
|
+
.rematerializePointMark({ id });
|
|
92
|
+
const legend = next.guideConfigs.legend?.series;
|
|
93
|
+
return legend?.target === id && legend.channels.includes("shape")
|
|
94
|
+
? rematerializeExistingLegend(next)
|
|
95
|
+
: next;
|
|
96
|
+
}
|
|
97
|
+
);
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { createPointMark } from "./create.js";
|
|
2
|
+
import { editPointMark } from "./edit.js";
|
|
3
|
+
import { rematerializePointMark } from "./materialize.js";
|
|
4
|
+
|
|
5
|
+
export { registerPointJitterActions } from "./jitter.js";
|
|
6
|
+
|
|
7
|
+
export function registerPointMarkActions(ProgramClass) {
|
|
8
|
+
ProgramClass.prototype.createPointMark = createPointMark;
|
|
9
|
+
ProgramClass.prototype.editPointMark = editPointMark;
|
|
10
|
+
ProgramClass.prototype.rematerializePointMark = rematerializePointMark;
|
|
11
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
+
import { validateKeys } from "../../../core/validation.js";
|
|
4
|
+
import { normalizePointJitterPolicy } from "../../../grammar/jitter.js";
|
|
5
|
+
import { findCoordinate } from "../../../selectors/coordinates.js";
|
|
6
|
+
import { resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
7
|
+
|
|
8
|
+
const JITTER_OPTIONS = Object.freeze([
|
|
9
|
+
"target", "channel", "maxOffset", "seed", "key"
|
|
10
|
+
]);
|
|
11
|
+
const REMOVE_OPTIONS = Object.freeze(["target"]);
|
|
12
|
+
|
|
13
|
+
function isCartesianPoint(program, layer, channel) {
|
|
14
|
+
return layer.mark?.type === "point" &&
|
|
15
|
+
layer.encoding?.[channel]?.scale !== undefined &&
|
|
16
|
+
layer.encoding?.[channel === "x" ? "y" : "x"]?.scale !== undefined &&
|
|
17
|
+
findCoordinate(program, layer.coordinate)?.type === "cartesian";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveJitterTarget(program, target, channel) {
|
|
21
|
+
const requested = target === undefined
|
|
22
|
+
? undefined
|
|
23
|
+
: validateUserId(target, "Point jitter target");
|
|
24
|
+
return resolveEligibleLayer(program, {
|
|
25
|
+
target: requested,
|
|
26
|
+
predicate: layer => isCartesianPoint(program, layer, channel),
|
|
27
|
+
label: "point jitter"
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const jitterPoints = action(
|
|
32
|
+
{
|
|
33
|
+
op: "jitterPoints",
|
|
34
|
+
description: "Apply deterministic bounded graphical jitter to a point mark."
|
|
35
|
+
},
|
|
36
|
+
function (args = {}) {
|
|
37
|
+
validateKeys(args, JITTER_OPTIONS, "jitterPoints");
|
|
38
|
+
const policy = normalizePointJitterPolicy(args);
|
|
39
|
+
const layer = resolveJitterTarget(this, args.target, policy.channel);
|
|
40
|
+
return this
|
|
41
|
+
._withMaterializationConfig(["jitters", layer.id], policy)
|
|
42
|
+
.rematerializePointMark({ id: layer.id });
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export const removeJitter = action(
|
|
47
|
+
{
|
|
48
|
+
op: "removeJitter",
|
|
49
|
+
description: "Remove point jitter and restore semantic scale positions."
|
|
50
|
+
},
|
|
51
|
+
function (args = {}) {
|
|
52
|
+
validateKeys(args, REMOVE_OPTIONS, "removeJitter");
|
|
53
|
+
const requested = args.target === undefined
|
|
54
|
+
? undefined
|
|
55
|
+
: validateUserId(args.target, "Point jitter target");
|
|
56
|
+
const layer = resolveEligibleLayer(this, {
|
|
57
|
+
target: requested,
|
|
58
|
+
predicate: candidate =>
|
|
59
|
+
candidate.mark?.type === "point" &&
|
|
60
|
+
this.materializationConfigs.jitters?.[candidate.id] !== undefined,
|
|
61
|
+
label: "point jitter"
|
|
62
|
+
});
|
|
63
|
+
return this
|
|
64
|
+
._withoutMaterializationConfig(["jitters", layer.id])
|
|
65
|
+
.rematerializePointMark({ id: layer.id });
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
export function registerPointJitterActions(ProgramClass) {
|
|
70
|
+
ProgramClass.prototype.jitterPoints = jitterPoints;
|
|
71
|
+
ProgramClass.prototype.removeJitter = removeJitter;
|
|
72
|
+
}
|