ggaction 0.0.21 → 0.0.22
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 +73 -0
- package/README.md +3 -2
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/package.json +6 -2
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +1 -0
- package/src/actions/boxPlots/components.js +1 -96
- package/src/actions/boxPlots/create.js +6 -1
- package/src/actions/boxPlots/edit.js +8 -0
- package/src/actions/boxPlots/materialize.js +3 -0
- package/src/actions/boxPlots/options.js +1 -1
- package/src/actions/canvas/actions.js +2 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +2 -1
- package/src/actions/canvas/plotOrder.js +14 -0
- package/src/actions/charts/area.js +1 -1
- package/src/actions/charts/bar.js +4 -4
- package/src/actions/charts/density.js +1 -1
- package/src/actions/charts/ecdf.js +1 -1
- package/src/actions/charts/endpoints.js +54 -5
- package/src/actions/charts/heatmap.js +1 -1
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +1 -1
- package/src/actions/charts/index.js +2 -1
- package/src/actions/charts/interval-regression.js +1 -1
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/radar.js +63 -3
- package/src/actions/charts/raincloud.js +37 -6
- package/src/actions/charts/rug-strip.js +2 -2
- package/src/actions/charts/scatter.js +3 -3
- package/src/actions/charts/text.js +1 -1
- package/src/actions/composition/actions.js +10 -3
- package/src/actions/coordinates/actions.js +2 -0
- package/src/actions/data/basic.js +6 -0
- package/src/actions/data/derived.js +6 -2
- package/src/actions/data/edit.js +21 -35
- package/src/actions/data/filter.js +1 -376
- package/src/actions/data/flatten.js +19 -0
- package/src/actions/data/index.js +6 -0
- package/src/actions/data/lookup.js +50 -0
- package/src/actions/data/raincloudFilter.js +56 -0
- package/src/actions/data/shared.js +10 -0
- package/src/actions/distributions/revision.js +3 -1
- package/src/actions/encodings/angle.js +2 -1
- package/src/actions/encodings/appearance.js +38 -13
- package/src/actions/encodings/channels.js +14 -3
- package/src/actions/encodings/color/continuous.js +26 -10
- package/src/actions/encodings/color/index.js +50 -20
- package/src/actions/encodings/color/policy.js +57 -4
- package/src/actions/encodings/parallel.js +4 -0
- package/src/actions/encodings/position/policies/bar.js +10 -6
- package/src/actions/encodings/position/policies/line.js +1 -152
- package/src/actions/encodings/position/resolve.js +4 -1
- package/src/actions/encodings/ranged.js +46 -0
- package/src/actions/encodings/remove.js +1 -1
- package/src/actions/encodings/ruleAppearance.js +7 -6
- package/src/actions/encodings/shared.js +14 -2
- package/src/actions/encodings/stroke.js +20 -14
- package/src/actions/encodings/strokeDash.js +9 -11
- package/src/actions/encodings/text.js +30 -2
- package/src/actions/errorBars/edit.js +24 -4
- package/src/actions/facets/actions.js +1 -793
- package/src/actions/facets/derive.js +34 -5
- package/src/actions/facets/replay.js +1 -38
- package/src/actions/gradientPlots/components.js +21 -9
- package/src/actions/gradientPlots/create.js +4 -2
- package/src/actions/gradientPlots/edit.js +18 -1
- package/src/actions/gradientPlots/materialize.js +35 -36
- package/src/actions/gradientPlots/options.js +1 -1
- package/src/actions/gradientPlots/paint.js +13 -7
- package/src/actions/guides/applicability.js +2 -0
- package/src/actions/guides/axes/axes.js +39 -0
- package/src/actions/guides/axes/axis.js +9 -5
- package/src/actions/guides/axes/edit.js +11 -8
- package/src/actions/guides/axes/labels.js +24 -8
- package/src/actions/guides/axes/parallel/policy.js +3 -5
- package/src/actions/guides/axes/parallel/resolve.js +4 -3
- package/src/actions/guides/axes/policy.js +22 -0
- package/src/actions/guides/axes/tickGroups.js +4 -2
- package/src/actions/guides/axes/ticks.js +22 -4
- package/src/actions/guides/axes/titles.js +19 -6
- package/src/actions/guides/facadeAxes.js +3 -0
- package/src/actions/guides/grids/grid.js +3 -1
- package/src/actions/guides/grids/resolve.js +7 -11
- package/src/actions/guides/guides.js +21 -1
- package/src/actions/guides/legends/categorical/actions.js +1 -515
- package/src/actions/guides/legends/categorical/layout.js +5 -2
- package/src/actions/guides/legends/categorical/options.js +4 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -3
- package/src/actions/guides/legends/categorical/symbols.js +3 -3
- package/src/actions/guides/legends/colorSize.js +2 -0
- package/src/actions/guides/legends/continuous/common.js +1 -1
- package/src/actions/guides/legends/continuous/gradient.js +1 -360
- package/src/actions/guides/legends/continuous/interval.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +15 -1
- package/src/actions/guides/legends/edit.js +1 -1
- package/src/actions/guides/legends/focused.js +1 -1
- package/src/actions/guides/legends/lane.js +1 -1
- package/src/actions/guides/legends/sampling.js +1 -140
- package/src/actions/guides/legends/strokeWidth.js +3 -2
- package/src/actions/guides/legends/transition.js +19 -2
- package/src/actions/guides/polar/axes/facade.js +5 -4
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/resolve.js +2 -1
- package/src/actions/guides/tickValues.js +7 -7
- package/src/actions/marks/arc/actions.js +5 -7
- package/src/actions/marks/area/actions.js +9 -3
- package/src/actions/marks/bar/create.js +4 -2
- package/src/actions/marks/bar/edit.js +9 -6
- package/src/actions/marks/bar/materialize.js +5 -3
- package/src/actions/marks/dependentReferences.js +87 -0
- package/src/actions/marks/index.js +2 -0
- package/src/actions/marks/lifecycle.js +2 -1
- package/src/actions/marks/line/actions.js +24 -6
- package/src/actions/marks/line/materialize.js +7 -3
- package/src/actions/marks/point/create.js +5 -4
- package/src/actions/marks/point/edit.js +6 -3
- package/src/actions/marks/point/jitter.js +6 -6
- package/src/actions/marks/point/materialize.js +36 -39
- package/src/actions/marks/rect/actions.js +16 -4
- package/src/actions/marks/references.js +183 -35
- package/src/actions/marks/rule/actions.js +20 -9
- package/src/actions/marks/shared.js +2 -1
- package/src/actions/marks/text/actions.js +10 -3
- package/src/actions/marks/tick/actions.js +11 -2
- package/src/actions/primitives/editGraphics.js +28 -3
- package/src/actions/primitives/index.js +9 -0
- package/src/actions/primitives/semanticAction.js +13 -7
- package/src/actions/primitives/semanticValidation/layer.js +20 -0
- package/src/actions/primitives/semanticValidation/scale.js +8 -1
- package/src/actions/scales/channels.js +1 -1
- package/src/actions/scales/consumers/common.js +19 -1
- package/src/actions/scales/consumers/families.js +2 -2
- package/src/actions/scales/consumers/seriesLayout.js +4 -3
- package/src/actions/scales/edit.js +11 -0
- package/src/actions/scales/editPolicy.js +5 -3
- package/src/actions/scales/preview.js +2 -1
- package/src/actions/scales/quantitativeColor.js +2 -4
- package/src/actions/selection/actions.js +10 -1
- package/src/actions/theme/reconcile.js +37 -9
- package/src/core/compositionState.js +21 -4
- package/src/core/datasetReferences.js +17 -0
- package/src/core/immutable.js +1 -0
- package/src/core/resourceReferences.js +5 -3
- package/src/core/validation.js +4 -0
- package/src/core/vocabulary.js +24 -2
- package/src/grammar/aggregate.js +1 -0
- package/src/grammar/arcs.js +1 -1
- package/src/grammar/areaSeries.js +1 -1
- package/src/grammar/axisTextPlacement.js +1 -1
- package/src/grammar/bars/policy.js +2 -2
- package/src/grammar/datasetDependencies.js +97 -0
- package/src/grammar/datasetSchema.js +31 -1
- package/src/grammar/facetHeaders.js +1 -0
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/legendIntent.js +6 -2
- package/src/grammar/facets/scales.js +27 -7
- package/src/grammar/flatten.js +85 -0
- package/src/grammar/histogram.js +13 -1
- package/src/grammar/horizon.js +1 -494
- package/src/grammar/jitter.js +2 -2
- package/src/grammar/legendSpacing.js +45 -0
- package/src/grammar/lineMissing.js +90 -0
- package/src/grammar/lineSeries.js +1 -376
- package/src/grammar/lookup.js +97 -0
- package/src/grammar/markFilter.js +4 -2
- package/src/grammar/markLabels.js +8 -3
- package/src/grammar/numeric.js +1 -1
- package/src/grammar/pathCommands.js +1 -1
- package/src/grammar/pathSeries.js +1 -109
- package/src/grammar/pointPolygon.js +81 -0
- package/src/grammar/pointShapes.js +15 -15
- package/src/grammar/rects.js +1 -1
- package/src/grammar/regression/derive.js +1 -195
- package/src/grammar/scales/appearance.js +1 -1
- package/src/grammar/scales/color.js +1 -1
- package/src/grammar/scales/colorConsumers.js +1 -63
- package/src/grammar/scales/continuous.js +58 -1
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +3 -3
- package/src/grammar/schemas/graphicBounds.js +40 -12
- package/src/grammar/schemas/graphicClip.js +23 -0
- package/src/grammar/schemas/graphicTree.js +16 -0
- package/src/grammar/statisticalReference.js +22 -0
- package/src/grammar/text.js +65 -1
- package/src/grammar/ticks.js +92 -1
- package/src/grammar/transformTopology.js +3 -1
- package/src/grammar/transforms.js +24 -0
- package/src/inspection.js +1 -495
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -1
- package/src/layout/facetContentFlow.js +101 -0
- package/src/layout/facetHeaderTitles.js +1 -0
- package/src/layout/facetTopLane.js +29 -0
- package/src/layout/facets.js +1 -1
- package/src/layout/legendGroup.js +1 -1
- package/src/layout/legendItems.js +26 -10
- package/src/layout/legendSpacing.js +66 -0
- package/src/layout/plotDrawOrder.js +17 -0
- package/src/materialization/bars/histogram.js +8 -2
- package/src/materialization/bars/resolve.js +1 -1
- package/src/materialization/composition.js +1 -0
- package/src/materialization/compositionSnapshot.js +22 -2
- package/src/materialization/dataProvenance.js +1 -1
- package/src/materialization/facetGuides/layout.js +1 -1
- package/src/materialization/facetGuides/placement.js +23 -11
- package/src/materialization/facetGuides/preparation.js +43 -24
- package/src/materialization/facetHeaders.js +1 -1
- package/src/materialization/facetTopLane.js +25 -0
- package/src/materialization/facets.js +1 -1
- package/src/materialization/graphicHierarchy.js +5 -0
- package/src/materialization/graphicTranslation.js +2 -0
- package/src/materialization/guides/layout.js +1 -1
- package/src/materialization/guides/resources.js +1 -1
- package/src/materialization/legends.js +1 -1
- package/src/materialization/markClip.js +26 -0
- package/src/materialization/marks/capabilities.js +3 -2
- package/src/materialization/marks/grouping.js +7 -5
- package/src/materialization/marks/index.js +4 -2
- package/src/materialization/marks/policies.js +4 -0
- package/src/materialization/plotDrawOrder.js +44 -0
- package/src/materialization/rect.js +11 -3
- package/src/materialization/scaleGuideDependencies.js +13 -5
- package/src/materialization/selection/items/common.js +1 -1
- package/src/materialization/selection/items/path.js +1 -0
- package/src/materialization/selection/policies/index.js +2 -1
- package/src/materialization/text.js +10 -5
- package/src/mcp/cli.js +0 -0
- package/src/persistence/validation.js +108 -18
- package/src/persistence.js +4 -2
- package/src/renderers/canvas/index.js +16 -0
- package/src/renderers/canvas/isolated.js +11 -1
- package/src/renderers/canvas/native.js +7 -0
- package/src/renderers/canvas/scene.js +66 -0
- package/src/renderers/canvas/text.js +3 -2
- package/src/renderers/svg.js +1 -617
- package/src/renderers/svgSerialization.js +643 -0
- package/src/renderers/transitionScene.js +67 -0
- package/src/scene.js +89 -0
- package/src/selectors/axes.js +1 -1
- package/src/selectors/datasets.js +1 -0
- package/src/selectors/grids.js +9 -0
- package/src/selectors/markClip.js +28 -0
- package/src/selectors/raincloud.js +7 -0
- package/src/version.js +1 -1
- package/types/basic.d.ts +7 -0
- package/types/index.d.ts +8 -0
- package/types/program.d.ts +270 -60
- package/types/scene.d.ts +35 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { materializeMarkClip } from "../../../materialization/markClip.js";
|
|
2
|
+
import { validateGraphicItemLimit, validateWorkLimit } from "../../../core/validation.js";
|
|
2
3
|
import { editGraphicProperties } from "../../primitives/graphicProperties.js";
|
|
3
4
|
import { action } from "../../../core/action.js";
|
|
4
5
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
@@ -187,47 +188,40 @@ function applyPointJitter(program, {
|
|
|
187
188
|
const stored = program.materializationConfigs.jitters?.[id];
|
|
188
189
|
if (stored === undefined) return { program, x, y };
|
|
189
190
|
const policy = normalizePointJitterPolicy(stored);
|
|
190
|
-
const encoding = layer.encoding?.[policy.channel];
|
|
191
|
-
const scale = program.resolvedScales[encoding?.scale];
|
|
192
|
-
if (scale === undefined) {
|
|
193
|
-
throw new Error(
|
|
194
|
-
`Point jitter on "${id}" requires a resolved ${policy.channel} scale.`
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
191
|
const bounds = resolveCoordinateBounds(program, layer.coordinate);
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const values = [...(policy.channel === "x" ? x : y)];
|
|
221
|
-
for (const item of resolution.items) {
|
|
222
|
-
values[item.index] += item.finalOffset;
|
|
192
|
+
const channels = policy.channel === "both" ? ["x", "y"] : [policy.channel];
|
|
193
|
+
const resolutions = {}, positions = { x, y };
|
|
194
|
+
for (const channel of channels) {
|
|
195
|
+
const scale = program.resolvedScales[layer.encoding?.[channel]?.scale];
|
|
196
|
+
if (scale === undefined) {
|
|
197
|
+
throw new Error(`Point jitter on "${id}" requires a resolved ${channel} scale.`);
|
|
198
|
+
}
|
|
199
|
+
// Resolve from the same unjittered positions, using each axis in the stable
|
|
200
|
+
// hash. Both is exactly the independent X and Y assignments together.
|
|
201
|
+
const axisPolicy = { ...policy, channel };
|
|
202
|
+
const entries = resolveJitterEntries({
|
|
203
|
+
policy: axisPolicy, dataset, x, y, area, shapes, angles,
|
|
204
|
+
config, graphic, existingChildren
|
|
205
|
+
});
|
|
206
|
+
const resolution = resolvePointJitter({
|
|
207
|
+
target: id, policy: axisPolicy, scale, entries,
|
|
208
|
+
plotMinimum: channel === "x" ? bounds.x : bounds.y,
|
|
209
|
+
plotMaximum: channel === "x" ? bounds.x + bounds.width : bounds.y + bounds.height
|
|
210
|
+
});
|
|
211
|
+
resolutions[channel] = resolution;
|
|
212
|
+
positions[channel] = [...positions[channel]];
|
|
213
|
+
for (const item of resolution.items) positions[channel][item.index] += item.finalOffset;
|
|
223
214
|
}
|
|
215
|
+
const resolved = policy.channel === "both"
|
|
216
|
+
? { algorithm: policy.algorithm, itemCount: resolutions.x.itemCount, axes: resolutions }
|
|
217
|
+
: resolutions[policy.channel];
|
|
224
218
|
return {
|
|
225
219
|
program: program._withMaterializationConfig(["jitters", id], {
|
|
226
220
|
...policy,
|
|
227
|
-
resolved
|
|
221
|
+
resolved
|
|
228
222
|
}),
|
|
229
|
-
x:
|
|
230
|
-
y:
|
|
223
|
+
x: positions.x,
|
|
224
|
+
y: positions.y
|
|
231
225
|
};
|
|
232
226
|
}
|
|
233
227
|
|
|
@@ -368,6 +362,9 @@ export const rematerializePointMark = /* @__PURE__ */ action(
|
|
|
368
362
|
const strokeDetails = requestedStrokeDetails(config, "Point mark");
|
|
369
363
|
const fill = mappedFill ?? config.fill ?? DEFAULT_POINT_FILL;
|
|
370
364
|
const shapes = encodedShape ?? dataset.values.map(() => config.shape ?? "circle");
|
|
365
|
+
validateWorkLimit(shapes.reduce((sum, shape) => sum +
|
|
366
|
+
(typeof shape === "object" ? validatePointShape(shape).polygon.length + 1 : 0), 0),
|
|
367
|
+
"Point polygon materialization");
|
|
371
368
|
const existingChildren = graphic.items ?? [];
|
|
372
369
|
const constantShape = validatePointShape(config.shape ?? "circle");
|
|
373
370
|
const requiresMixedCollection =
|
|
@@ -456,11 +453,11 @@ export const rematerializePointMark = /* @__PURE__ */ action(
|
|
|
456
453
|
...strokeDetails
|
|
457
454
|
});
|
|
458
455
|
});
|
|
459
|
-
return jittered.program.editGraphics({
|
|
456
|
+
return materializeMarkClip(jittered.program.editGraphics({
|
|
460
457
|
target: id,
|
|
461
458
|
property: "items",
|
|
462
459
|
value: items
|
|
463
|
-
});
|
|
460
|
+
}), id);
|
|
464
461
|
}
|
|
465
462
|
|
|
466
463
|
let next = graphic.items.length === dataset.values.length
|
|
@@ -555,6 +552,6 @@ export const rematerializePointMark = /* @__PURE__ */ action(
|
|
|
555
552
|
for (const [property, value] of Object.entries(strokeDetails)) {
|
|
556
553
|
next = next.editGraphics({ target: id, property, value });
|
|
557
554
|
}
|
|
558
|
-
return next;
|
|
555
|
+
return materializeMarkClip(next, id);
|
|
559
556
|
}
|
|
560
557
|
);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { materializeMarkClip } from "../../../materialization/markClip.js";
|
|
2
|
+
import { requestedMarkClip, validateMarkClipCoordinate } from "../../../selectors/markClip.js";
|
|
1
3
|
import { action } from "../../../core/action.js";
|
|
2
4
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
5
|
import {
|
|
@@ -33,7 +35,7 @@ import { applyItemMissingPolicy, validateItemMissing } from
|
|
|
33
35
|
"../../../grammar/itemMissing.js";
|
|
34
36
|
|
|
35
37
|
const STYLE_OPTIONS = Object.freeze([
|
|
36
|
-
"fill", "opacity", "stroke", "strokeWidth", ...RECT_RADIUS_PROPERTIES,
|
|
38
|
+
"clip", "fill", "opacity", "stroke", "strokeWidth", ...RECT_RADIUS_PROPERTIES,
|
|
37
39
|
...STROKE_STYLE_PROPERTIES
|
|
38
40
|
]);
|
|
39
41
|
const CREATE_OPTIONS = Object.freeze(["id", "data", "missing", ...STYLE_OPTIONS]);
|
|
@@ -55,6 +57,7 @@ const createRectMark = /* @__PURE__ */ action(
|
|
|
55
57
|
{ op: "createRectMark", description: "Create a semantic rectangular cell layer." },
|
|
56
58
|
function (args = {}) {
|
|
57
59
|
validateMarkOptions(args, CREATE_OPTIONS, "createRectMark");
|
|
60
|
+
requestedMarkClip(args);
|
|
58
61
|
requestedRectStyleDetails(args, "createRectMark");
|
|
59
62
|
const id = resolveMarkId(this, args.id, {
|
|
60
63
|
defaultId: "rect",
|
|
@@ -109,6 +112,7 @@ const rematerializeRectMark = /* @__PURE__ */ action(
|
|
|
109
112
|
const highlighted = rematerializeHighlightBaseline(this, {
|
|
110
113
|
target: id,
|
|
111
114
|
operation: "rematerializeRectMark",
|
|
115
|
+
scales: args.scales,
|
|
112
116
|
resetProperty: "items",
|
|
113
117
|
resetValue: []
|
|
114
118
|
});
|
|
@@ -126,7 +130,7 @@ const rematerializeRectMark = /* @__PURE__ */ action(
|
|
|
126
130
|
throw new Error(`Rect mark "${id}" requires rect collection graphics.`);
|
|
127
131
|
}
|
|
128
132
|
if (this.markConfigs[id]?.gradientPlot?.materialized === true) {
|
|
129
|
-
return this.materializeGradientPlotFill({ id });
|
|
133
|
+
return materializeMarkClip(this.materializeGradientPlotFill({ id, scales: args.scales }), id);
|
|
130
134
|
}
|
|
131
135
|
const sourceDataset = findDataset(this, layer.data);
|
|
132
136
|
if (sourceDataset === undefined) {
|
|
@@ -146,12 +150,12 @@ const rematerializeRectMark = /* @__PURE__ */ action(
|
|
|
146
150
|
next = next.rematerializeScale({ id: scale, marks: false });
|
|
147
151
|
}
|
|
148
152
|
}
|
|
149
|
-
return replaceGraphicItems(
|
|
153
|
+
return materializeMarkClip(replaceGraphicItems(
|
|
150
154
|
next,
|
|
151
155
|
id,
|
|
152
156
|
"rect",
|
|
153
157
|
resolveRectGraphicItems(next, layer, dataset)
|
|
154
|
-
);
|
|
158
|
+
), id);
|
|
155
159
|
}
|
|
156
160
|
);
|
|
157
161
|
|
|
@@ -159,6 +163,7 @@ const editRectMark = /* @__PURE__ */ action(
|
|
|
159
163
|
{ op: "editRectMark", description: "Edit rectangular cell appearance." },
|
|
160
164
|
function (args = {}) {
|
|
161
165
|
validateMarkOptions(args, EDIT_OPTIONS, "editRectMark");
|
|
166
|
+
requestedMarkClip(args);
|
|
162
167
|
requestedRectStyleDetails(args, "editRectMark");
|
|
163
168
|
if (!["missing", ...STYLE_OPTIONS].some(option => Object.hasOwn(args, option))) {
|
|
164
169
|
throw new Error("editRectMark requires at least one editable property.");
|
|
@@ -172,6 +177,12 @@ const editRectMark = /* @__PURE__ */ action(
|
|
|
172
177
|
"editRectMark stroke conflicts with a field encoding; use encodeStroke with value to replace it."
|
|
173
178
|
);
|
|
174
179
|
}
|
|
180
|
+
validateMarkClipCoordinate(this, layer, args.clip ?? this.markConfigs[layer.id]?.clip);
|
|
181
|
+
for (const channel of ["opacity", "strokeWidth"]) {
|
|
182
|
+
if (Object.hasOwn(args, channel) && layer.encoding?.[channel] !== undefined) {
|
|
183
|
+
throw new Error(`editRectMark ${channel} conflicts with a field encoding; use ${channel === "opacity" ? "encodeOpacity" : "encodeStrokeWidth"} with value to replace it.`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
175
186
|
const semantic = Object.hasOwn(args, "missing")
|
|
176
187
|
? this.editSemantic({
|
|
177
188
|
property: `layer[${layer.id}].mark.missing`,
|
|
@@ -185,6 +196,7 @@ const editRectMark = /* @__PURE__ */ action(
|
|
|
185
196
|
args,
|
|
186
197
|
semantic.markConfigs[layer.id] ?? DEFAULT_RECT_MARK
|
|
187
198
|
),
|
|
199
|
+
...requestedMarkClip(args),
|
|
188
200
|
fillExplicit: Object.hasOwn(args, "fill")
|
|
189
201
|
? true
|
|
190
202
|
: semantic.markConfigs[layer.id]?.fillExplicit ?? false
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isSourceOwnedText } from "../../grammar/text.js";
|
|
2
|
+
import { GRADIENT_PROFILE_FIELDS } from "../../grammar/gradientProfile.js";
|
|
2
3
|
import { action, closedAction } from "../../core/action.js";
|
|
3
4
|
import { isPlainObject } from "../../core/immutable.js";
|
|
4
5
|
import { validateUserId } from "../../core/identifiers.js";
|
|
@@ -12,7 +13,7 @@ import {
|
|
|
12
13
|
import { findLayer, resolveEligibleLayer } from "../../selectors/layers.js";
|
|
13
14
|
import { resolveConsumerValuesForDataset } from
|
|
14
15
|
"../scales/consumers/index.js";
|
|
15
|
-
import { resolveMarkItems } from
|
|
16
|
+
import { findSelectionPolicy, resolveMarkItems } from
|
|
16
17
|
"../../materialization/selection/policies/index.js";
|
|
17
18
|
import { getMarkMaterializationPolicy } from
|
|
18
19
|
"../../materialization/marks/policies.js";
|
|
@@ -22,11 +23,17 @@ import { withRematerializedDerivedDataset } from
|
|
|
22
23
|
import {
|
|
23
24
|
deriveStatisticalReferenceValues,
|
|
24
25
|
normalizeReferenceField,
|
|
26
|
+
normalizeReferenceGroupBy,
|
|
25
27
|
normalizeReferencePopulation,
|
|
26
28
|
normalizeReferenceStatistics,
|
|
27
29
|
normalizeStatisticalReferenceTransform,
|
|
28
|
-
validateStatisticalReferenceTransform
|
|
30
|
+
validateStatisticalReferenceTransform,
|
|
31
|
+
partitionReferenceRows
|
|
29
32
|
} from "../../grammar/statisticalReference.js";
|
|
33
|
+
import { scalarKey } from "../../grammar/transformKeys.js";
|
|
34
|
+
import { deriveLineSeries, resolveLineBins } from "../../grammar/lineSeries.js";
|
|
35
|
+
import { isAggregate } from "../../grammar/aggregate.js";
|
|
36
|
+
import { findSemanticScale } from "../../selectors/scales.js";
|
|
30
37
|
import {
|
|
31
38
|
resolveFacadeData,
|
|
32
39
|
resolveFacadeId,
|
|
@@ -39,7 +46,7 @@ const COMMON_OPTIONS = Object.freeze([
|
|
|
39
46
|
"id", "x", "y", "space", "source", "data", "coordinate", "temporalUnit"
|
|
40
47
|
]);
|
|
41
48
|
const DYNAMIC_OPTIONS = Object.freeze([
|
|
42
|
-
"id", "source", "axis", "population", "field", "statistic", "statistics"
|
|
49
|
+
"id", "source", "axis", "population", "field", "groupBy", "statistic", "statistics"
|
|
43
50
|
]);
|
|
44
51
|
const LINE_STYLE = Object.freeze([
|
|
45
52
|
"stroke", "strokeWidth", "strokeDash", "opacity", ...STROKE_STYLE_PROPERTIES
|
|
@@ -211,6 +218,12 @@ function requireStatisticalSource(program, config, referenceId) {
|
|
|
211
218
|
);
|
|
212
219
|
}
|
|
213
220
|
const encoding = source.encoding?.[config.axis];
|
|
221
|
+
const sourceTransform = findDataset(program, source.data)?.transform?.[0];
|
|
222
|
+
if (sourceTransform?.type === "horizon" && config.axis === "y") {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Statistical reference "${referenceId}" cannot summarize Horizon folded Y coordinates. Use a quantitative X axis.`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
214
227
|
if (
|
|
215
228
|
encoding?.fieldType !== "quantitative" ||
|
|
216
229
|
typeof encoding.scale !== "string" ||
|
|
@@ -233,14 +246,14 @@ function requireStatisticalSource(program, config, referenceId) {
|
|
|
233
246
|
return { source, encoding };
|
|
234
247
|
}
|
|
235
248
|
|
|
236
|
-
function
|
|
249
|
+
function resolveBoundPopulation(program, source, config, referenceId) {
|
|
237
250
|
let dataset = findDataset(program, source.data);
|
|
238
251
|
if (dataset === undefined || !Array.isArray(dataset.values)) {
|
|
239
252
|
throw new Error(
|
|
240
253
|
`Statistical reference "${referenceId}" source requires materialized data.`
|
|
241
254
|
);
|
|
242
255
|
}
|
|
243
|
-
if (population === "visibleItems") return dataset;
|
|
256
|
+
if (config.population === "visibleItems") return { dataset };
|
|
244
257
|
while (
|
|
245
258
|
dataset.transform?.length === 1 &&
|
|
246
259
|
dataset.transform[0].type === "markFilter" &&
|
|
@@ -254,7 +267,58 @@ function resolvePopulationDataset(program, source, population, referenceId) {
|
|
|
254
267
|
}
|
|
255
268
|
dataset = parent;
|
|
256
269
|
}
|
|
257
|
-
|
|
270
|
+
const transform = dataset.transform?.length === 1 ? dataset.transform[0] : undefined;
|
|
271
|
+
const axisField = source.encoding[config.axis].field;
|
|
272
|
+
let field;
|
|
273
|
+
if (transform?.type === "density" && axisField === transform.as[0]) {
|
|
274
|
+
field = transform.field;
|
|
275
|
+
} else if (transform?.type === "ecdf" && axisField === transform.as.value) {
|
|
276
|
+
field = transform.field;
|
|
277
|
+
} else if (transform?.type === "boxSummary" && transform.method !== "precomputed" &&
|
|
278
|
+
Object.values(transform.as).includes(axisField)) {
|
|
279
|
+
field = transform.field;
|
|
280
|
+
} else if (transform?.type === "gradientProfile" &&
|
|
281
|
+
[GRADIENT_PROFILE_FIELDS.lower, GRADIENT_PROFILE_FIELDS.upper,
|
|
282
|
+
GRADIENT_PROFILE_FIELDS.center].includes(axisField)) {
|
|
283
|
+
field = transform.field;
|
|
284
|
+
} else if (transform?.type === "summary") {
|
|
285
|
+
const endpointOwner = Object.entries(program.markConfigs).some(([id, config]) =>
|
|
286
|
+
config.endpointPlot && (id === source.id ||
|
|
287
|
+
Object.values(config.endpointPlot.roles).includes(source.id)));
|
|
288
|
+
if (endpointOwner) field = transform.aggregates.find(aggregate => aggregate.as === axisField)?.field;
|
|
289
|
+
} else if (transform?.type === "horizon" && axisField === transform.as.x) {
|
|
290
|
+
field = transform.x.field;
|
|
291
|
+
}
|
|
292
|
+
if (field !== undefined) {
|
|
293
|
+
const observationField = field;
|
|
294
|
+
const parent = findDataset(program, dataset.source);
|
|
295
|
+
if (parent === undefined || !Array.isArray(parent.values)) {
|
|
296
|
+
throw new Error(`Statistical reference "${referenceId}" bound data is unavailable.`);
|
|
297
|
+
}
|
|
298
|
+
if (config.field.kind === "explicit") {
|
|
299
|
+
// Explicit output fields remain available; original observation fields
|
|
300
|
+
// resolve against the statistical owner's input rather than sampled rows.
|
|
301
|
+
if (!parent.values.every(row => Object.hasOwn(row, config.field.field))) return { dataset };
|
|
302
|
+
field = config.field.field;
|
|
303
|
+
}
|
|
304
|
+
const groupFields = transform.category === undefined
|
|
305
|
+
? [...(Array.isArray(transform.groupBy) ? transform.groupBy
|
|
306
|
+
: transform.groupBy === undefined ? [] : [transform.groupBy]),
|
|
307
|
+
...(transform.placement?.split?.field === undefined ? [] : [transform.placement.split.field])]
|
|
308
|
+
: [transform.category];
|
|
309
|
+
const groupKey = row => JSON.stringify(groupFields.map(key => row?.[key]));
|
|
310
|
+
const observedGroups = new Set(dataset.values.map(row =>
|
|
311
|
+
transform.type === "horizon" && groupFields.length
|
|
312
|
+
? JSON.stringify([row[transform.as.group]]) : groupKey(row)));
|
|
313
|
+
return {
|
|
314
|
+
dataset: { ...parent, values: parent.values.filter(row =>
|
|
315
|
+
Number.isFinite(row?.[transform.field ?? observationField]) &&
|
|
316
|
+
(transform.type !== "horizon" || Number.isFinite(row?.[transform.y.field])) &&
|
|
317
|
+
observedGroups.has(groupKey(row))) },
|
|
318
|
+
field
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
return { dataset };
|
|
258
322
|
}
|
|
259
323
|
|
|
260
324
|
function strictFieldValues(rows, field, label) {
|
|
@@ -341,6 +405,29 @@ function resolvePopulationValues(
|
|
|
341
405
|
`Statistical reference "${referenceId}" boundData`
|
|
342
406
|
);
|
|
343
407
|
}
|
|
408
|
+
const gradient = program.markConfigs[source.id]?.gradientPlot;
|
|
409
|
+
if (config.population === "boundData") {
|
|
410
|
+
const { field } = resolveBoundPopulation(program, source, config, referenceId);
|
|
411
|
+
if (field !== undefined) return strictFieldValues(dataset.values, field,
|
|
412
|
+
`Statistical reference "${referenceId}" boundData`);
|
|
413
|
+
if (source.mark.type === "line" && isAggregate(source.encoding?.y?.aggregate)) {
|
|
414
|
+
const x = source.encoding.x;
|
|
415
|
+
const bins = x.bin === undefined ? undefined : resolveLineBins(
|
|
416
|
+
dataset.values, source, findSemanticScale(program, x.scale)
|
|
417
|
+
);
|
|
418
|
+
const derived = deriveLineSeries(dataset.values, source, {
|
|
419
|
+
...(bins === undefined ? {} : { xBinBoundaries: bins.boundaries }),
|
|
420
|
+
allowSingleton: true
|
|
421
|
+
});
|
|
422
|
+
return config.axis === "x" ? derived.xValues : derived.yValues;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (gradient?.materialized &&
|
|
426
|
+
config.axis === (gradient.orientation === "vertical" ? "y" : "x")) {
|
|
427
|
+
return strictFieldValues(dataset.values, config.population === "boundData"
|
|
428
|
+
? gradient.measure : GRADIENT_PROFILE_FIELDS.center,
|
|
429
|
+
`Statistical reference "${referenceId}" ${config.population}`);
|
|
430
|
+
}
|
|
344
431
|
return resolveConsumerValuesForDataset(program, {
|
|
345
432
|
layer: source,
|
|
346
433
|
channel: config.axis,
|
|
@@ -348,7 +435,7 @@ function resolvePopulationValues(
|
|
|
348
435
|
}, dataset);
|
|
349
436
|
}
|
|
350
437
|
|
|
351
|
-
function statisticalRows(program, referenceId
|
|
438
|
+
function statisticalRows(program, referenceId) {
|
|
352
439
|
const config = program.markConfigs[referenceId]?.statisticalReference;
|
|
353
440
|
const { source, encoding } = requireStatisticalSource(
|
|
354
441
|
program,
|
|
@@ -369,27 +456,43 @@ function statisticalRows(program, referenceId, dataset) {
|
|
|
369
456
|
config.population,
|
|
370
457
|
`Statistical reference "${referenceId}" population`
|
|
371
458
|
);
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
encoding,
|
|
376
|
-
{ ...config, population, statistics },
|
|
377
|
-
dataset,
|
|
378
|
-
referenceId
|
|
379
|
-
);
|
|
380
|
-
const resolved = deriveStatisticalReferenceValues(
|
|
381
|
-
values,
|
|
382
|
-
statistics,
|
|
383
|
-
`Statistical reference "${referenceId}"`
|
|
384
|
-
);
|
|
385
|
-
if (resolved.length === 2 && resolved[0] > resolved[1]) {
|
|
459
|
+
const { dataset } = resolveBoundPopulation(program, source, { ...config, population }, referenceId);
|
|
460
|
+
const groupBy = normalizeReferenceGroupBy(config.groupBy);
|
|
461
|
+
if (population === "visibleItems" && findSelectionPolicy(source.mark?.type) === undefined) {
|
|
386
462
|
throw new Error(
|
|
387
|
-
`Statistical reference
|
|
463
|
+
`Statistical reference "${referenceId}" visibleItems population is unavailable for ${source.mark?.type} marks. Use boundData.`
|
|
388
464
|
);
|
|
389
465
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
466
|
+
let visibleGroups;
|
|
467
|
+
if (groupBy !== undefined && population === "visibleItems") {
|
|
468
|
+
if (SERIES_MARKS.has(source.mark?.type) || source.encoding?.parallel !== undefined) {
|
|
469
|
+
throw new Error(`Statistical reference "${referenceId}" visibleItems population is ambiguous for series marks.`);
|
|
470
|
+
}
|
|
471
|
+
visibleGroups = new Map();
|
|
472
|
+
for (const item of resolveMarkItems(program, source.id)) {
|
|
473
|
+
if (!Object.hasOwn(item.fields, groupBy)) {
|
|
474
|
+
throw new Error(`Statistical reference "${referenceId}" groupBy field "${groupBy}" is not scalar at a visible item. Use boundData.`);
|
|
475
|
+
}
|
|
476
|
+
const key = scalarKey(item.fields[groupBy], "Statistical reference groupBy");
|
|
477
|
+
if (!visibleGroups.has(key)) visibleGroups.set(key, []);
|
|
478
|
+
visibleGroups.get(key).push(item.fields);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const groups = partitionReferenceRows(dataset.values, groupBy)
|
|
482
|
+
.filter(group => !visibleGroups || visibleGroups.has(group.key));
|
|
483
|
+
if (!groups.length) throw new Error(`Statistical reference "${referenceId}" population must contain at least one value.`);
|
|
484
|
+
return groups.map(group => {
|
|
485
|
+
const values = visibleGroups && config.field.kind === "explicit"
|
|
486
|
+
? strictFieldValues(visibleGroups.get(group.key), config.field.field, `Statistical reference "${referenceId}" visibleItems`)
|
|
487
|
+
: resolvePopulationValues(program, source, encoding,
|
|
488
|
+
{ ...config, population, statistics }, { ...dataset, values: group.rows }, referenceId);
|
|
489
|
+
const resolved = deriveStatisticalReferenceValues(values, statistics, `Statistical reference "${referenceId}"`);
|
|
490
|
+
if (resolved.length === 2 && resolved[0] > resolved[1]) {
|
|
491
|
+
throw new Error(`Statistical reference band "${referenceId}" lower statistic exceeds its upper statistic.`);
|
|
492
|
+
}
|
|
493
|
+
return { ...(groupBy === undefined ? {} : { group: group.group }),
|
|
494
|
+
...(resolved.length === 1 ? { value: resolved[0] } : { lower: resolved[0], upper: resolved[1] }) };
|
|
495
|
+
});
|
|
393
496
|
}
|
|
394
497
|
|
|
395
498
|
function replaceStatisticalDataset(program, id, source, transform, values) {
|
|
@@ -471,12 +574,12 @@ export const materializeStatisticalReferenceData = /* @__PURE__ */ closedAction(
|
|
|
471
574
|
description: "Materialize one owned statistical reference value dataset."
|
|
472
575
|
}, MATERIALIZE_OPTIONS,
|
|
473
576
|
function (args = {}) {
|
|
474
|
-
const { id,
|
|
577
|
+
const { id, transform } = requireDerivedDataset(
|
|
475
578
|
this,
|
|
476
579
|
args.id,
|
|
477
580
|
"statisticalReference"
|
|
478
581
|
);
|
|
479
|
-
const values = statisticalRows(this, transform.target
|
|
582
|
+
const values = statisticalRows(this, transform.target);
|
|
480
583
|
return this.editSemantic({
|
|
481
584
|
property: `dataset[${id}].values`,
|
|
482
585
|
value: values
|
|
@@ -494,8 +597,8 @@ export const rematerializeStatisticalReference = /* @__PURE__ */ closedAction(
|
|
|
494
597
|
const config = this.markConfigs[id]?.statisticalReference;
|
|
495
598
|
const { source } = requireStatisticalSource(this, config, id);
|
|
496
599
|
const population = normalizeReferencePopulation(config.population);
|
|
497
|
-
const dataset =
|
|
498
|
-
const values = statisticalRows(this, id
|
|
600
|
+
const { dataset } = resolveBoundPopulation(this, source, { ...config, population }, id);
|
|
601
|
+
const values = statisticalRows(this, id);
|
|
499
602
|
const transform = normalizeStatisticalReferenceTransform({ target: id });
|
|
500
603
|
let next = replaceStatisticalDataset(
|
|
501
604
|
this,
|
|
@@ -511,15 +614,23 @@ export const rematerializeStatisticalReference = /* @__PURE__ */ closedAction(
|
|
|
511
614
|
});
|
|
512
615
|
}
|
|
513
616
|
next = syncReferenceEncoding(next, id, source, config);
|
|
617
|
+
const appearanceScales = new Set(["stroke", "strokeWidth", "strokeDash", "opacity", "color"]
|
|
618
|
+
.map(channel => findLayer(next, id).encoding?.[channel]?.scale).filter(Boolean));
|
|
619
|
+
for (const scale of appearanceScales) {
|
|
620
|
+
next = next.rematerializeScale({ id: scale, marks: false, guides: false });
|
|
621
|
+
}
|
|
622
|
+
// References do not contribute to their source's positional scale domain.
|
|
623
|
+
// Re-entering that scale's mark plan here would rematerialize the source
|
|
624
|
+
// and its dependent reference recursively (notably gradient strips).
|
|
514
625
|
next = config.statistics.length === 1
|
|
515
|
-
? next.rematerializeRuleMark({ id })
|
|
516
|
-
: next.rematerializeRectMark({ id });
|
|
626
|
+
? next.rematerializeRuleMark({ id, scales: false })
|
|
627
|
+
: next.rematerializeRectMark({ id, scales: false });
|
|
517
628
|
return next._withContext(this.context);
|
|
518
629
|
}
|
|
519
630
|
);
|
|
520
631
|
|
|
521
632
|
function isDynamicReference(args) {
|
|
522
|
-
return ["axis", "population", "field", "statistic", "statistics"]
|
|
633
|
+
return ["axis", "population", "field", "groupBy", "statistic", "statistics"]
|
|
523
634
|
.some(key => Object.hasOwn(args, key));
|
|
524
635
|
}
|
|
525
636
|
|
|
@@ -558,18 +669,19 @@ function normalizeDynamicReference(program, args, band) {
|
|
|
558
669
|
axis: args.axis,
|
|
559
670
|
population: normalizeReferencePopulation(args.population, `${operation} population`),
|
|
560
671
|
field: normalizeReferenceField(args.field, `${operation} field`),
|
|
672
|
+
...(args.groupBy === undefined ? {} : { groupBy: normalizeReferenceGroupBy(args.groupBy) }),
|
|
561
673
|
statistics,
|
|
562
674
|
dataId: `${id}-statistical-reference-data`
|
|
563
675
|
};
|
|
564
676
|
const preview = program._withMarkConfig(id, { statisticalReference: config });
|
|
565
677
|
const { source } = requireStatisticalSource(preview, config, id);
|
|
566
|
-
const dataset =
|
|
678
|
+
const { dataset } = resolveBoundPopulation(
|
|
567
679
|
program,
|
|
568
680
|
source,
|
|
569
|
-
config
|
|
681
|
+
config,
|
|
570
682
|
id
|
|
571
683
|
);
|
|
572
|
-
statisticalRows(preview, id
|
|
684
|
+
statisticalRows(preview, id);
|
|
573
685
|
if (hasDataset(program, config.dataId) || hasDatasetOwner(program, config.dataId)) {
|
|
574
686
|
throw new Error(`Dataset "${config.dataId}" already exists.`);
|
|
575
687
|
}
|
|
@@ -654,3 +766,39 @@ export const createReferenceBand = /* @__PURE__ */ action(
|
|
|
654
766
|
return createReference(this, args, true);
|
|
655
767
|
}
|
|
656
768
|
);
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
export const createStatisticalReference = /* @__PURE__ */ action(
|
|
772
|
+
{
|
|
773
|
+
op: "createStatisticalReference",
|
|
774
|
+
description: "Create a statistical reference line or central interval through one public action."
|
|
775
|
+
},
|
|
776
|
+
function (args = {}) {
|
|
777
|
+
const band = Object.hasOwn(args, "statistics");
|
|
778
|
+
validateFacadeOptions(args, ["id", "source", "axis", "population", "field", "groupBy",
|
|
779
|
+
...(band ? ["statistics", ...BAND_STYLE] : ["statistic", ...LINE_STYLE])], "createStatisticalReference");
|
|
780
|
+
const id = resolveFacadeId(this, args.id, {
|
|
781
|
+
defaultId: "statisticalReference", operation: "createStatisticalReference"
|
|
782
|
+
});
|
|
783
|
+
// The existing owners retain statistical population, scale exclusion,
|
|
784
|
+
// source replay, persistence and removal semantics, with wrapped trace steps.
|
|
785
|
+
if (band) {
|
|
786
|
+
return this.createReferenceBand({ fill: "#6b7280", opacity: 0.18, ...args, id });
|
|
787
|
+
}
|
|
788
|
+
const next = this.createReferenceLine({ statistic: { op: "mean" }, stroke: "#222222",
|
|
789
|
+
strokeWidth: 2.5, strokeDash: "solid", ...args, id });
|
|
790
|
+
const color = findLayer(this, args.source)?.encoding?.color;
|
|
791
|
+
// Reuse the source scale: a fresh palette would assign different colors
|
|
792
|
+
// after filtering, domain sorting, or a custom range.
|
|
793
|
+
if (args.stroke === undefined && args.groupBy !== undefined &&
|
|
794
|
+
color?.field === args.groupBy && color.scale !== undefined &&
|
|
795
|
+
color.aggregate === undefined) {
|
|
796
|
+
return next.encodeStroke({
|
|
797
|
+
target: id, field: "group", fieldType: color.fieldType,
|
|
798
|
+
...(color.temporalUnit === undefined ? {} : { temporalUnit: color.temporalUnit }),
|
|
799
|
+
scale: { id: color.scale }
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
return next;
|
|
803
|
+
}
|
|
804
|
+
);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requestedMarkClip, validateMarkClipCoordinate } from "../../../selectors/markClip.js";
|
|
1
2
|
import { action } from "../../../core/action.js";
|
|
2
3
|
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
4
|
import { validatePositiveFinite } from "../../../core/validation.js";
|
|
@@ -5,7 +6,6 @@ import {
|
|
|
5
6
|
mapContinuousScaleValues,
|
|
6
7
|
mapOrdinalOffsetValues,
|
|
7
8
|
mapOrdinalPositionValues,
|
|
8
|
-
mapOrdinalValues,
|
|
9
9
|
normalizeStrokeDashPattern
|
|
10
10
|
} from "../../../grammar/scales/index.js";
|
|
11
11
|
import { deriveRuleValues, resolveRuleMode } from "../../../grammar/rules.js";
|
|
@@ -43,12 +43,12 @@ import { applyItemMissingPolicy, validateItemMissing } from
|
|
|
43
43
|
"../../../grammar/itemMissing.js";
|
|
44
44
|
|
|
45
45
|
const APPEARANCE_OPTIONS = Object.freeze([
|
|
46
|
-
"stroke", "strokeWidth", "strokeDash", "opacity",
|
|
46
|
+
"clip", "stroke", "strokeWidth", "strokeDash", "opacity",
|
|
47
47
|
...STROKE_STYLE_PROPERTIES
|
|
48
48
|
]);
|
|
49
49
|
const CREATE_OPTIONS = Object.freeze(["id", "data", "missing", ...APPEARANCE_OPTIONS]);
|
|
50
50
|
const EDIT_OPTIONS = Object.freeze(["target", "missing", ...APPEARANCE_OPTIONS]);
|
|
51
|
-
const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
|
|
51
|
+
const REMATERIALIZE_OPTIONS = Object.freeze(["id", "scales"]);
|
|
52
52
|
const SPAN_OPTIONS = Object.freeze(["id", "orientation", "size"]);
|
|
53
53
|
const DEFAULT_RULE_CONFIG = Object.freeze({
|
|
54
54
|
stroke: DEFAULT_COLORS.mark,
|
|
@@ -102,6 +102,7 @@ const createRuleMark = /* @__PURE__ */ action(
|
|
|
102
102
|
},
|
|
103
103
|
function (args = {}) {
|
|
104
104
|
validateMarkOptions(args, CREATE_OPTIONS, "createRuleMark");
|
|
105
|
+
requestedMarkClip(args);
|
|
105
106
|
const strokeDetails = requestedStrokeDetails(args, "createRuleMark");
|
|
106
107
|
const id = resolveMarkId(this, args.id, {
|
|
107
108
|
defaultId: "rule",
|
|
@@ -137,6 +138,7 @@ const createRuleMark = /* @__PURE__ */ action(
|
|
|
137
138
|
})
|
|
138
139
|
._withMarkConfig(id, {
|
|
139
140
|
...DEFAULT_RULE_CONFIG,
|
|
141
|
+
...requestedMarkClip(args),
|
|
140
142
|
...strokeDetails,
|
|
141
143
|
...(inherited === undefined
|
|
142
144
|
? {}
|
|
@@ -155,6 +157,7 @@ const editRuleMark = /* @__PURE__ */ action(
|
|
|
155
157
|
{ op: "editRuleMark", description: "Edit constant rule appearance through its encoding owners." },
|
|
156
158
|
function (args = {}) {
|
|
157
159
|
validateMarkOptions(args, EDIT_OPTIONS, "editRuleMark");
|
|
160
|
+
requestedMarkClip(args);
|
|
158
161
|
const strokeDetails = requestedStrokeDetails(args, "editRuleMark");
|
|
159
162
|
if (args.target !== undefined) validateUserId(args.target, "Rule mark id");
|
|
160
163
|
const layer = resolveEligibleLayer(this, {
|
|
@@ -162,9 +165,10 @@ const editRuleMark = /* @__PURE__ */ action(
|
|
|
162
165
|
predicate: candidate => candidate.mark?.type === "rule",
|
|
163
166
|
label: "rule mark"
|
|
164
167
|
});
|
|
168
|
+
validateMarkClipCoordinate(this, layer, args.clip ?? this.markConfigs[layer.id]?.clip);
|
|
165
169
|
const appearance = planRuleAppearance(args, layer);
|
|
166
170
|
if (appearance.length === 0 && Object.keys(strokeDetails).length === 0 &&
|
|
167
|
-
!Object.hasOwn(args, "missing")) {
|
|
171
|
+
!Object.hasOwn(args, "missing") && !Object.hasOwn(args, "clip")) {
|
|
168
172
|
throw new Error("editRuleMark requires missing or an appearance change.");
|
|
169
173
|
}
|
|
170
174
|
const semantic = Object.hasOwn(args, "missing")
|
|
@@ -175,6 +179,7 @@ const editRuleMark = /* @__PURE__ */ action(
|
|
|
175
179
|
: this;
|
|
176
180
|
const configured = semantic._withMarkConfig(layer.id, {
|
|
177
181
|
...semantic.markConfigs[layer.id],
|
|
182
|
+
...requestedMarkClip(args),
|
|
178
183
|
...strokeDetails
|
|
179
184
|
});
|
|
180
185
|
const materialized = appearance.length === 0
|
|
@@ -191,10 +196,14 @@ const rematerializeRuleMark = /* @__PURE__ */ action(
|
|
|
191
196
|
},
|
|
192
197
|
function (args = {}) {
|
|
193
198
|
validateMarkOptions(args, REMATERIALIZE_OPTIONS, "rematerializeRuleMark");
|
|
199
|
+
if (args.scales !== undefined && typeof args.scales !== "boolean") {
|
|
200
|
+
throw new TypeError("rematerializeRuleMark scales must be a boolean.");
|
|
201
|
+
}
|
|
194
202
|
const id = validateUserId(args.id, "Rule mark id");
|
|
195
203
|
const highlighted = rematerializeHighlightBaseline(this, {
|
|
196
204
|
target: id,
|
|
197
205
|
operation: "rematerializeRuleMark",
|
|
206
|
+
scales: args.scales,
|
|
198
207
|
resetProperty: "length",
|
|
199
208
|
resetValue: 0
|
|
200
209
|
});
|
|
@@ -226,8 +235,10 @@ const rematerializeRuleMark = /* @__PURE__ */ action(
|
|
|
226
235
|
.filter(scale => scale !== undefined)
|
|
227
236
|
)];
|
|
228
237
|
let resolved = this;
|
|
229
|
-
|
|
230
|
-
|
|
238
|
+
if (args.scales !== false) {
|
|
239
|
+
for (const scaleId of scaleIds) {
|
|
240
|
+
resolved = resolved.rematerializeScale({ id: scaleId });
|
|
241
|
+
}
|
|
231
242
|
}
|
|
232
243
|
|
|
233
244
|
const derived = deriveRuleValues(dataset.values, layer);
|
|
@@ -356,10 +367,10 @@ const rematerializeRuleMark = /* @__PURE__ */ action(
|
|
|
356
367
|
dashEncoding?.datum ?? resolvedConfig.strokeDash
|
|
357
368
|
)
|
|
358
369
|
)
|
|
359
|
-
:
|
|
370
|
+
: mapScaleConsumerValues(
|
|
360
371
|
derived.values.strokeDash,
|
|
361
|
-
resolved.resolvedScales[dashEncoding.scale]
|
|
362
|
-
|
|
372
|
+
resolved.resolvedScales[dashEncoding.scale],
|
|
373
|
+
"strokeDash"
|
|
363
374
|
);
|
|
364
375
|
const opacity = opacityEncoding?.scale === undefined
|
|
365
376
|
? resolvedConfig.opacity
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { materializeMarkClip } from "../../materialization/markClip.js";
|
|
1
2
|
import { isSourceOwnedText } from "../../grammar/text.js";
|
|
2
3
|
import {
|
|
3
4
|
resolveOptionalUserId,
|
|
@@ -243,5 +244,5 @@ export function editMarkGraphic(program, target, properties) {
|
|
|
243
244
|
for (const [property, value] of Object.entries(properties)) {
|
|
244
245
|
program = program.editGraphics({ target, property, value });
|
|
245
246
|
}
|
|
246
|
-
return program;
|
|
247
|
+
return materializeMarkClip(program, target);
|
|
247
248
|
}
|