ggaction 0.0.12 → 0.0.13
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 +49 -0
- package/README.md +2 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/action-cards.schema.json +1 -1
- package/knowledge/extension-authoring.md +2 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +4 -2
- package/src/actions/basic.js +2 -0
- package/src/actions/boxPlots/materialize.js +2 -1
- package/src/actions/canvas/actions.js +3 -1
- package/src/actions/canvas/fitting.js +211 -0
- package/src/actions/canvas/index.js +2 -0
- package/src/actions/categoryOrder/index.js +3 -2
- package/src/actions/charts/area.js +47 -0
- package/src/actions/charts/bar.js +11 -5
- package/src/actions/charts/beeswarm.js +70 -0
- package/src/actions/charts/density.js +44 -0
- package/src/actions/charts/ecdf.js +183 -0
- package/src/actions/charts/endpoints.js +451 -0
- package/src/actions/charts/heatmap.js +2 -2
- package/src/actions/charts/histogram.js +1 -1
- package/src/actions/charts/horizon.js +71 -0
- package/src/actions/charts/index.js +37 -0
- package/src/actions/charts/interval-regression.js +169 -0
- package/src/actions/charts/line.js +5 -5
- package/src/actions/charts/parallel.js +1 -1
- package/src/actions/charts/pie.js +44 -0
- package/src/actions/charts/polar.js +123 -0
- package/src/actions/charts/radar.js +299 -0
- package/src/actions/charts/radial.js +42 -0
- package/src/actions/charts/raincloud.js +422 -0
- package/src/actions/charts/rug-strip.js +279 -0
- package/src/actions/charts/scatter.js +9 -3
- package/src/actions/charts/shared.js +69 -10
- package/src/actions/composition/actions.js +118 -1
- package/src/actions/data/bin.js +29 -0
- package/src/actions/data/computed.js +24 -0
- package/src/actions/data/derived.js +66 -0
- package/src/actions/data/ecdf.js +38 -0
- package/src/actions/data/filter.js +212 -28
- package/src/actions/data/fold.js +24 -0
- package/src/actions/data/index.js +24 -0
- package/src/actions/data/interval.js +2 -0
- package/src/actions/data/intervalEdit.js +86 -4
- package/src/actions/data/regression.js +5 -2
- package/src/actions/data/stack.js +26 -0
- package/src/actions/data/summary.js +26 -0
- package/src/actions/data/timeUnit.js +1 -1
- package/src/actions/encodings/appearance.js +32 -13
- package/src/actions/encodings/barWidth.js +8 -0
- package/src/actions/encodings/basic.js +2 -0
- package/src/actions/encodings/color/continuous.js +19 -59
- package/src/actions/encodings/color/index.js +28 -22
- package/src/actions/encodings/color/layout.js +3 -97
- package/src/actions/encodings/color/policy.js +5 -8
- package/src/actions/encodings/density/resolve.js +37 -5
- package/src/actions/encodings/density.js +120 -33
- package/src/actions/encodings/horizon/resolve.js +8 -4
- package/src/actions/encodings/horizon.js +18 -8
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/offset.js +10 -16
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +10 -4
- package/src/actions/encodings/position/index.js +43 -5
- package/src/actions/encodings/position/policies/arc.js +14 -0
- package/src/actions/encodings/position/policies/area.js +4 -42
- package/src/actions/encodings/position/policies/bar.js +48 -13
- package/src/actions/encodings/position/resolve.js +67 -22
- package/src/actions/encodings/ranged.js +123 -97
- package/src/actions/encodings/remove.js +37 -32
- package/src/actions/encodings/ruleAppearance.js +23 -9
- package/src/actions/encodings/seriesLayout.js +104 -0
- package/src/actions/encodings/shared.js +25 -23
- package/src/actions/encodings/strokeDash.js +9 -6
- package/src/actions/encodings/temporal.js +7 -0
- package/src/actions/encodings/text.js +30 -15
- package/src/actions/errorBands/create.js +18 -5
- package/src/actions/errorBands/edit.js +341 -21
- package/src/actions/errorBars/create.js +21 -10
- package/src/actions/errorBars/edit.js +355 -19
- package/src/actions/facets/actions.js +410 -12
- package/src/actions/facets/derive.js +57 -8
- package/src/actions/gradientPlots/components.js +33 -11
- package/src/actions/gradientPlots/materialize.js +5 -7
- package/src/actions/guides/applicability.js +20 -17
- package/src/actions/guides/axes/axes.js +4 -3
- package/src/actions/guides/axes/axis.js +18 -18
- package/src/actions/guides/axes/components.js +43 -0
- package/src/actions/guides/axes/edit.js +16 -51
- package/src/actions/guides/axes/labels.js +129 -19
- package/src/actions/guides/axes/lines.js +7 -5
- package/src/actions/guides/axes/parallel/lifecycle.js +117 -0
- package/src/actions/guides/axes/parallel/policy.js +137 -0
- package/src/actions/guides/axes/parallel/resolve.js +44 -23
- package/src/actions/guides/axes/parallel.js +93 -217
- package/src/actions/guides/axes/policy.js +21 -67
- package/src/actions/guides/axes/tickGroups.js +14 -8
- package/src/actions/guides/axes/ticks.js +5 -4
- package/src/actions/guides/axes/titles.js +21 -8
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facade.js +106 -0
- package/src/actions/guides/facadeAxes.js +200 -0
- package/src/actions/guides/facadeLegend.js +63 -0
- package/src/actions/guides/grids/grid.js +37 -33
- package/src/actions/guides/grids/resolve.js +10 -2
- package/src/actions/guides/guides.js +35 -28
- package/src/actions/guides/legends/categorical/actions.js +193 -249
- package/src/actions/guides/legends/categorical/components.js +4 -5
- package/src/actions/guides/legends/categorical/layout.js +36 -349
- package/src/actions/guides/legends/categorical/options.js +30 -14
- package/src/actions/guides/legends/categorical/recipes.js +14 -0
- package/src/actions/guides/legends/categorical/resolve.js +21 -10
- package/src/actions/guides/legends/categorical/symbols.js +2 -3
- package/src/actions/guides/legends/continuous/common.js +69 -14
- package/src/actions/guides/legends/continuous/gradient.js +111 -83
- package/src/actions/guides/legends/continuous/index.js +6 -2
- package/src/actions/guides/legends/continuous/interval.js +71 -92
- package/src/actions/guides/legends/continuous/opacity.js +105 -84
- package/src/actions/guides/legends/creation.js +27 -0
- package/src/actions/guides/legends/edit.js +198 -66
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/lane.js +98 -86
- package/src/actions/guides/legends/lifecycle.js +132 -0
- package/src/actions/guides/legends/remove.js +31 -76
- package/src/actions/guides/legends/size.js +149 -105
- package/src/actions/guides/legends/strokeWidth.js +106 -64
- package/src/actions/guides/legends/target.js +12 -0
- package/src/actions/guides/legends/transition.js +43 -0
- package/src/actions/guides/polar/axes/facade.js +77 -62
- package/src/actions/guides/polar/axes/labels.js +5 -4
- package/src/actions/guides/polar/axes/lines.js +2 -1
- package/src/actions/guides/polar/axes/shared.js +6 -0
- package/src/actions/guides/polar/axes/ticks.js +5 -4
- package/src/actions/guides/polar/axes/titles.js +5 -4
- package/src/actions/guides/polar/grids.js +7 -7
- package/src/actions/guides/reuse.js +44 -0
- package/src/actions/guides/tickValues.js +19 -21
- package/src/actions/index.js +2 -0
- package/src/actions/intervals/resolve.js +35 -15
- package/src/actions/marks/arc/actions.js +1 -1
- package/src/actions/marks/area/actions.js +18 -4
- package/src/actions/marks/area/materialize.js +5 -2
- package/src/actions/marks/bar/materialize.js +11 -7
- package/src/actions/marks/index.js +5 -0
- package/src/actions/marks/line/actions.js +43 -108
- package/src/actions/marks/line/materialize.js +34 -52
- package/src/actions/marks/point/edit.js +11 -15
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/point/jitter.js +3 -0
- package/src/actions/marks/point/materialize.js +106 -2
- package/src/actions/marks/point/packing.js +68 -0
- package/src/actions/marks/references.js +87 -0
- package/src/actions/marks/remove.js +17 -4
- package/src/actions/marks/rule/actions.js +34 -3
- package/src/actions/marks/rule/appearance.js +26 -0
- package/src/actions/marks/shared.js +12 -12
- package/src/actions/marks/text/actions.js +224 -6
- package/src/actions/marks/text/layout.js +2 -2
- package/src/actions/primitives/createGraphics.js +3 -1
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticAction.js +39 -2
- package/src/actions/primitives/semanticValidation/guide.js +10 -0
- package/src/actions/primitives/semanticValidation/layer.js +18 -1
- package/src/actions/primitives/semanticValidation/scale.js +10 -1
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +19 -6
- package/src/actions/regression/resolve.js +1 -1
- package/src/actions/scales/consumers/common.js +20 -53
- package/src/actions/scales/consumers/families.js +27 -14
- package/src/actions/scales/consumers/index.js +23 -2
- package/src/actions/scales/consumers/seriesLayout.js +33 -2
- package/src/actions/scales/create.js +4 -4
- package/src/actions/scales/definitions.js +19 -3
- package/src/actions/scales/edit.js +46 -34
- package/src/actions/scales/editPolicy.js +21 -31
- package/src/actions/scales/materialize.js +10 -46
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +39 -0
- package/src/actions/scales/quantitativeColor.js +9 -5
- package/src/actions/theme/actions.js +43 -0
- package/src/actions/theme/index.js +9 -0
- package/src/actions/theme/reconcile.js +882 -0
- package/src/actions/titles/actions.js +11 -10
- package/src/actions/titles/resolve.js +1 -40
- package/src/actions/violinPlots/create.js +86 -42
- package/src/actions/violinPlots/edit.js +191 -0
- package/src/actions/violinPlots/index.js +2 -0
- package/src/core/ChartProgram.js +7 -0
- package/src/core/action.js +22 -2
- package/src/core/compositionState.js +136 -8
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +58 -1
- package/src/grammar/areaEndpoints.js +46 -0
- package/src/grammar/areaSeries.js +87 -41
- package/src/grammar/bars/aggregate.js +33 -12
- package/src/grammar/bars/policy.js +9 -7
- package/src/grammar/bin.js +181 -0
- package/src/grammar/categoryOrder.js +34 -0
- package/src/grammar/computed.js +150 -0
- package/src/grammar/ecdf.js +198 -0
- package/src/grammar/facets/index.js +117 -6
- package/src/grammar/fold.js +123 -0
- package/src/grammar/horizon.js +6 -4
- package/src/grammar/interval.js +37 -20
- package/src/grammar/lineSeries.js +20 -63
- package/src/grammar/markFilter.js +22 -4
- package/src/grammar/markLabels.js +93 -0
- package/src/grammar/parallelAxisTitles.js +16 -0
- package/src/grammar/pathSeries.js +91 -0
- package/src/grammar/pointPacking.js +183 -0
- package/src/grammar/positionDatum.js +12 -0
- package/src/grammar/rects.js +9 -1
- package/src/grammar/regression/derive.js +4 -0
- package/src/grammar/regression/index.js +7 -2
- package/src/grammar/regression/models.js +22 -7
- package/src/grammar/regression/parameters.js +40 -13
- package/src/grammar/rotation.js +29 -0
- package/src/grammar/rules.js +7 -21
- package/src/grammar/scales/color.js +23 -1
- package/src/grammar/scales/colorConsumers.js +38 -0
- package/src/grammar/scales/definition.js +15 -1
- package/src/grammar/scales/discretized.js +11 -5
- package/src/grammar/scales/fields.js +38 -8
- package/src/grammar/scales/mapping.js +2 -0
- package/src/grammar/scales/radial.js +55 -0
- package/src/grammar/scales/types.js +1 -0
- package/src/grammar/schemas/semanticPath.js +17 -3
- package/src/grammar/stack.js +170 -0
- package/src/grammar/statistics/confidenceInterval.js +101 -0
- package/src/grammar/summary.js +183 -0
- package/src/grammar/text.js +22 -16
- package/src/grammar/timeUnit.js +21 -11
- package/src/grammar/transformTopology.js +6 -0
- package/src/grammar/transforms.js +36 -0
- package/src/grammar/valueFormat.js +112 -0
- package/src/grammar/window.js +13 -7
- package/src/layout/facets.js +41 -7
- package/src/layout/guideCollisions.js +12 -0
- package/src/layout/legend.js +2 -10
- package/src/layout/legendItems.js +102 -0
- package/src/layout/legendLane.js +100 -11
- package/src/materialization/bars/aggregate.js +38 -152
- package/src/materialization/bars/histogram.js +24 -0
- package/src/materialization/categorySlotOffset.js +40 -0
- package/src/materialization/compositionSnapshot.js +1 -1
- package/src/materialization/dependencies.js +57 -16
- package/src/materialization/encodings.js +1 -1
- package/src/materialization/facets.js +8 -1
- package/src/materialization/guides/layout.js +57 -0
- package/src/materialization/guides/resources.js +2 -2
- package/src/materialization/legends.js +10 -1
- package/src/materialization/marks/capabilities.js +20 -5
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +58 -23
- package/src/materialization/marks/policies.js +2 -0
- package/src/materialization/rect.js +33 -33
- package/src/materialization/rowEncoding.js +13 -13
- package/src/materialization/scaleGuideDependencies.js +8 -5
- package/src/materialization/scales/map.js +1 -0
- package/src/materialization/scales/policies/arc.js +42 -0
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +46 -14
- package/src/materialization/selection/compatibility.js +11 -0
- package/src/materialization/selection/items/bar.js +12 -68
- package/src/materialization/selection/items/common.js +8 -2
- package/src/materialization/selection/items/path.js +3 -13
- package/src/materialization/selection/items/rect.js +7 -4
- package/src/materialization/text.js +64 -5
- package/src/mcp/adapter.js +2 -2
- package/src/selectors/datasets.js +13 -0
- package/src/selectors/layers.js +3 -2
- package/src/selectors/scales.js +6 -0
- package/src/theme/defaults.js +39 -0
- package/types/basic.d.ts +11 -2
- package/types/index.d.ts +126 -0
- package/types/program.d.ts +1320 -135
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
normalizePointJitterPolicy,
|
|
12
12
|
resolvePointJitter
|
|
13
13
|
} from "../../../grammar/jitter.js";
|
|
14
|
+
import {
|
|
15
|
+
normalizePointPackingPolicy,
|
|
16
|
+
resolvePointPacking
|
|
17
|
+
} from "../../../grammar/pointPacking.js";
|
|
14
18
|
import { polarToCartesian, resolvePolarFrame } from "../../../grammar/polar.js";
|
|
15
19
|
import { resolveDirectionValues } from "../../../grammar/direction.js";
|
|
16
20
|
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
@@ -21,12 +25,15 @@ import {
|
|
|
21
25
|
} from "../../../theme/defaults.js";
|
|
22
26
|
import { findDataset } from "../../../selectors/datasets.js";
|
|
23
27
|
import { findLayer } from "../../../selectors/layers.js";
|
|
28
|
+
import { canMaterializePoint } from "../../../materialization/marks/capabilities.js";
|
|
24
29
|
import { rematerializeHighlightBaseline } from "../lifecycle.js";
|
|
25
30
|
import {
|
|
26
31
|
resolveRowEncodingValues,
|
|
27
32
|
resolveRowPositionValues
|
|
28
33
|
} from
|
|
29
34
|
"../../../materialization/rowEncoding.js";
|
|
35
|
+
import { offsetCategoryPositions } from
|
|
36
|
+
"../../../materialization/categorySlotOffset.js";
|
|
30
37
|
|
|
31
38
|
const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
|
|
32
39
|
const DEFAULT_POINT_FILL = DEFAULT_COLORS.mark;
|
|
@@ -210,6 +217,80 @@ function applyPointJitter(program, {
|
|
|
210
217
|
};
|
|
211
218
|
}
|
|
212
219
|
|
|
220
|
+
function applyPointPacking(program, {
|
|
221
|
+
id,
|
|
222
|
+
layer,
|
|
223
|
+
dataset,
|
|
224
|
+
x,
|
|
225
|
+
y,
|
|
226
|
+
area,
|
|
227
|
+
shapes,
|
|
228
|
+
angles,
|
|
229
|
+
config,
|
|
230
|
+
graphic,
|
|
231
|
+
existingChildren
|
|
232
|
+
}) {
|
|
233
|
+
const stored = program.materializationConfigs.pointPacking?.[id];
|
|
234
|
+
if (stored === undefined) return { program, x, y };
|
|
235
|
+
const policy = normalizePointPackingPolicy(stored);
|
|
236
|
+
const encoding = layer.encoding?.[policy.channel];
|
|
237
|
+
const scale = program.resolvedScales[encoding?.scale];
|
|
238
|
+
if (scale === undefined) {
|
|
239
|
+
throw new Error(`Point packing on "${id}" requires a resolved ${policy.channel} scale.`);
|
|
240
|
+
}
|
|
241
|
+
const seen = new Set();
|
|
242
|
+
const entries = dataset.values.flatMap((row, index) => {
|
|
243
|
+
if (!Number.isFinite(x?.[index]) || !Number.isFinite(y?.[index])) return [];
|
|
244
|
+
const rawIdentity = policy.key === undefined ? index : row[policy.key];
|
|
245
|
+
const identity = canonicalJitterScalar(
|
|
246
|
+
rawIdentity,
|
|
247
|
+
policy.key === undefined
|
|
248
|
+
? "Point packing source index"
|
|
249
|
+
: `Point packing key field "${policy.key}"`
|
|
250
|
+
);
|
|
251
|
+
if (seen.has(identity)) {
|
|
252
|
+
throw new Error(`Point packing key field "${policy.key}" must be unique for materialized items.`);
|
|
253
|
+
}
|
|
254
|
+
seen.add(identity);
|
|
255
|
+
const resolvedArea = resolvedPointArea(
|
|
256
|
+
area, index, config, existingChildren, graphic.type
|
|
257
|
+
);
|
|
258
|
+
const extent = resolvePointShapeExtent({
|
|
259
|
+
shape: shapes[index],
|
|
260
|
+
area: resolvedArea,
|
|
261
|
+
strokeWidth: typeof config.stroke === "string" ? config.strokeWidth ?? 1 : 0,
|
|
262
|
+
angle: angles?.[index]
|
|
263
|
+
});
|
|
264
|
+
return [{
|
|
265
|
+
index,
|
|
266
|
+
identity,
|
|
267
|
+
base: policy.channel === "x" ? x[index] : y[index],
|
|
268
|
+
fixed: policy.channel === "x" ? y[index] : x[index],
|
|
269
|
+
halfExtent: extent[policy.channel],
|
|
270
|
+
fixedHalfExtent: extent[policy.channel === "x" ? "y" : "x"]
|
|
271
|
+
}];
|
|
272
|
+
});
|
|
273
|
+
const bounds = resolveGraphicBounds(program);
|
|
274
|
+
const resolution = resolvePointPacking({
|
|
275
|
+
target: id,
|
|
276
|
+
policy,
|
|
277
|
+
scale,
|
|
278
|
+
entries,
|
|
279
|
+
plotMinimum: policy.channel === "x" ? bounds.x : bounds.y,
|
|
280
|
+
plotMaximum: policy.channel === "x" ? bounds.x + bounds.width : bounds.y + bounds.height
|
|
281
|
+
});
|
|
282
|
+
const values = [...(policy.channel === "x" ? x : y)];
|
|
283
|
+
for (const item of resolution.items) values[item.index] = item.final;
|
|
284
|
+
return {
|
|
285
|
+
program: program._withMaterializationConfig(["pointPacking", id], {
|
|
286
|
+
...policy,
|
|
287
|
+
resolved: resolution
|
|
288
|
+
}),
|
|
289
|
+
x: policy.channel === "x" ? values : x,
|
|
290
|
+
y: policy.channel === "y" ? values : y
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
213
294
|
export const rematerializePointMark = action(
|
|
214
295
|
{
|
|
215
296
|
op: "rematerializePointMark",
|
|
@@ -251,6 +332,10 @@ export const rematerializePointMark = action(
|
|
|
251
332
|
throw new Error(`Point mark "${id}" requires an existing dataset.`);
|
|
252
333
|
}
|
|
253
334
|
|
|
335
|
+
if (graphic.items.length === 0 && !canMaterializePoint(resolved, layer)) {
|
|
336
|
+
return resolved;
|
|
337
|
+
}
|
|
338
|
+
|
|
254
339
|
const positions = resolvePointPositions(resolved, layer, dataset);
|
|
255
340
|
const mappedFill = resolveRowEncodingValues(resolved, layer, dataset, "color");
|
|
256
341
|
const area = resolveRowEncodingValues(resolved, layer, dataset, "size");
|
|
@@ -272,7 +357,13 @@ export const rematerializePointMark = action(
|
|
|
272
357
|
angles !== undefined ||
|
|
273
358
|
graphic.type === "collection" ||
|
|
274
359
|
getPointGraphicType(constantShape) !== graphic.type;
|
|
275
|
-
|
|
360
|
+
if (
|
|
361
|
+
resolved.materializationConfigs.jitters?.[id] !== undefined &&
|
|
362
|
+
resolved.materializationConfigs.pointPacking?.[id] !== undefined
|
|
363
|
+
) {
|
|
364
|
+
throw new Error(`Point mark "${id}" cannot combine jitter and packing.`);
|
|
365
|
+
}
|
|
366
|
+
const packed = applyPointPacking(resolved, {
|
|
276
367
|
id,
|
|
277
368
|
layer,
|
|
278
369
|
dataset,
|
|
@@ -285,7 +376,20 @@ export const rematerializePointMark = action(
|
|
|
285
376
|
graphic,
|
|
286
377
|
existingChildren
|
|
287
378
|
});
|
|
288
|
-
const
|
|
379
|
+
const jittered = applyPointJitter(packed.program, {
|
|
380
|
+
id,
|
|
381
|
+
layer,
|
|
382
|
+
dataset,
|
|
383
|
+
x: packed.x,
|
|
384
|
+
y: packed.y,
|
|
385
|
+
area,
|
|
386
|
+
shapes,
|
|
387
|
+
angles,
|
|
388
|
+
config,
|
|
389
|
+
graphic,
|
|
390
|
+
existingChildren
|
|
391
|
+
});
|
|
392
|
+
const { x, y } = offsetCategoryPositions(jittered.program, layer, jittered);
|
|
289
393
|
|
|
290
394
|
if (requiresMixedCollection) {
|
|
291
395
|
const items = dataset.values.map((_, index) => {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
+
import { validateKeys } from "../../../core/validation.js";
|
|
4
|
+
import { normalizePointPackingPolicy } from "../../../grammar/pointPacking.js";
|
|
5
|
+
import { findCoordinate } from "../../../selectors/coordinates.js";
|
|
6
|
+
import { resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
7
|
+
|
|
8
|
+
const OPTIONS = Object.freeze([
|
|
9
|
+
"target", "channel", "maxOffset", "padding", "key", "overflow"
|
|
10
|
+
]);
|
|
11
|
+
const REMOVE_OPTIONS = Object.freeze(["target"]);
|
|
12
|
+
|
|
13
|
+
function isPackablePoint(program, layer, channel) {
|
|
14
|
+
const packed = layer.encoding?.[channel];
|
|
15
|
+
const fixed = layer.encoding?.[channel === "x" ? "y" : "x"];
|
|
16
|
+
return layer.mark?.type === "point" &&
|
|
17
|
+
["nominal", "ordinal"].includes(packed?.fieldType) &&
|
|
18
|
+
["quantitative", "temporal"].includes(fixed?.fieldType) &&
|
|
19
|
+
packed.scale !== undefined && fixed.scale !== undefined &&
|
|
20
|
+
findCoordinate(program, layer.coordinate)?.type === "cartesian";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function target(program, requested, channel) {
|
|
24
|
+
return resolveEligibleLayer(program, {
|
|
25
|
+
target: requested === undefined ? undefined : validateUserId(requested, "Point packing target"),
|
|
26
|
+
predicate: layer => isPackablePoint(program, layer, channel),
|
|
27
|
+
label: "point packing"
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const packPoints = action(
|
|
32
|
+
{ op: "packPoints", description: "Pack point glyphs deterministically within categorical slots." },
|
|
33
|
+
function (args = {}) {
|
|
34
|
+
validateKeys(args, OPTIONS, "packPoints");
|
|
35
|
+
const policy = normalizePointPackingPolicy(args);
|
|
36
|
+
const layer = target(this, args.target, policy.channel);
|
|
37
|
+
if (this.materializationConfigs.jitters?.[layer.id] !== undefined) {
|
|
38
|
+
throw new Error(`Point packing on "${layer.id}" conflicts with point jitter.`);
|
|
39
|
+
}
|
|
40
|
+
return this
|
|
41
|
+
._withMaterializationConfig(["pointPacking", layer.id], policy)
|
|
42
|
+
.rematerializePointMark({ id: layer.id });
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export const removePointPacking = action(
|
|
47
|
+
{ op: "removePointPacking", description: "Remove point packing and restore semantic positions." },
|
|
48
|
+
function (args = {}) {
|
|
49
|
+
validateKeys(args, REMOVE_OPTIONS, "removePointPacking");
|
|
50
|
+
const requested = args.target === undefined
|
|
51
|
+
? undefined
|
|
52
|
+
: validateUserId(args.target, "Point packing target");
|
|
53
|
+
const layer = resolveEligibleLayer(this, {
|
|
54
|
+
target: requested,
|
|
55
|
+
predicate: candidate => candidate.mark?.type === "point" &&
|
|
56
|
+
this.materializationConfigs.pointPacking?.[candidate.id] !== undefined,
|
|
57
|
+
label: "point packing"
|
|
58
|
+
});
|
|
59
|
+
return this
|
|
60
|
+
._withoutMaterializationConfig(["pointPacking", layer.id])
|
|
61
|
+
.rematerializePointMark({ id: layer.id });
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
export function registerPointPackingActions(ProgramClass) {
|
|
66
|
+
ProgramClass.prototype.packPoints = packPoints;
|
|
67
|
+
ProgramClass.prototype.removePointPacking = removePointPacking;
|
|
68
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../../grammar/text.js";
|
|
2
|
+
import { action } from "../../core/action.js";
|
|
3
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
4
|
+
import { findCoordinate } from "../../selectors/coordinates.js";
|
|
5
|
+
import { resolveEligibleLayer } from "../../selectors/layers.js";
|
|
6
|
+
import { resolveFacadeData, resolveFacadeId, validateFacadeOptions } from "../charts/shared.js";
|
|
7
|
+
|
|
8
|
+
const COMMON_OPTIONS = ["id", "x", "y", "space", "source", "data", "coordinate", "temporalUnit"];
|
|
9
|
+
const LINE_STYLE = ["stroke", "strokeWidth", "strokeDash", "opacity"];
|
|
10
|
+
const BAND_STYLE = ["fill", "opacity", "stroke", "strokeWidth"];
|
|
11
|
+
|
|
12
|
+
function resolveBinding(program, args, axis, operation, band) {
|
|
13
|
+
const space = args.space === undefined ? "data" : args.space;
|
|
14
|
+
if (space !== "data" && space !== "plot") {
|
|
15
|
+
throw new Error(`${operation} space must be data or plot.`);
|
|
16
|
+
}
|
|
17
|
+
if (space === "plot") {
|
|
18
|
+
if (Object.hasOwn(args, "source") || Object.hasOwn(args, "temporalUnit")) {
|
|
19
|
+
throw new Error(`${operation} plot space does not accept source or temporalUnit.`);
|
|
20
|
+
}
|
|
21
|
+
return { data: resolveFacadeData(program, args.data, operation),
|
|
22
|
+
fieldType: "quantitative", coordinate: args.coordinate, plot: true };
|
|
23
|
+
}
|
|
24
|
+
if (Object.hasOwn(args, "data") || Object.hasOwn(args, "coordinate")) {
|
|
25
|
+
throw new Error(`${operation} data space uses source data and coordinate.`);
|
|
26
|
+
}
|
|
27
|
+
const source = resolveEligibleLayer(program, {
|
|
28
|
+
target: args.source === undefined ? undefined : validateUserId(args.source, `${operation} source`),
|
|
29
|
+
predicate: layer => !isSourceOwnedText(layer) && layer.data !== undefined && layer.encoding?.[axis]?.scale !== undefined &&
|
|
30
|
+
findCoordinate(program, layer.coordinate)?.type === "cartesian" &&
|
|
31
|
+
(!band || ["quantitative", "temporal"].includes(layer.encoding[axis].fieldType)),
|
|
32
|
+
label: `${operation} Cartesian layer`,
|
|
33
|
+
targetOption: "source"
|
|
34
|
+
});
|
|
35
|
+
const encoding = source.encoding[axis];
|
|
36
|
+
return { data: source.data, coordinate: source.coordinate, scale: encoding.scale,
|
|
37
|
+
fieldType: encoding.fieldType, temporalUnit: args.temporalUnit === undefined ? encoding.temporalUnit : args.temporalUnit };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createReference(program, args, band) {
|
|
41
|
+
const operation = band ? "createReferenceBand" : "createReferenceLine";
|
|
42
|
+
const styleOptions = band ? BAND_STYLE : LINE_STYLE;
|
|
43
|
+
validateFacadeOptions(args, [...COMMON_OPTIONS, ...styleOptions], operation);
|
|
44
|
+
const axes = ["x", "y"].filter(axis => Object.hasOwn(args, axis));
|
|
45
|
+
if (axes.length !== 1) throw new Error(`${operation} requires exactly one of x or y.`);
|
|
46
|
+
const axis = axes[0];
|
|
47
|
+
const values = band ? args[axis] : [args[axis]];
|
|
48
|
+
if (!Array.isArray(values) || values.length !== (band ? 2 : 1)) {
|
|
49
|
+
throw new Error(`${operation} ${axis} requires a two-value interval.`);
|
|
50
|
+
}
|
|
51
|
+
const id = resolveFacadeId(program, args.id, {
|
|
52
|
+
defaultId: band ? "referenceBand" : "referenceLine", operation
|
|
53
|
+
});
|
|
54
|
+
const binding = resolveBinding(program, args, axis, operation, band);
|
|
55
|
+
if (binding.plot && !values.every(value => Number.isFinite(value) && value >= 0 && value <= 1)) {
|
|
56
|
+
throw new Error(`${operation} plot coordinates must be finite numbers in [0, 1].`);
|
|
57
|
+
}
|
|
58
|
+
const scale = binding.plot ? `${id}-${axis}` : binding.scale;
|
|
59
|
+
const style = Object.fromEntries(styleOptions.filter(key => args[key] !== undefined).map(key => [key, args[key]]));
|
|
60
|
+
const encode = axis === "x" ? "encodeX" : "encodeY";
|
|
61
|
+
const options = { target: id, fieldType: binding.fieldType, scale: { id: scale },
|
|
62
|
+
...(binding.coordinate === undefined ? {} : { coordinate: binding.coordinate }),
|
|
63
|
+
...(binding.temporalUnit === undefined ? {} : { temporalUnit: binding.temporalUnit }) };
|
|
64
|
+
const apply = initial => {
|
|
65
|
+
let next = binding.plot
|
|
66
|
+
? initial.createScale({ id: scale, type: "linear", domain: [0, 1] })
|
|
67
|
+
: initial;
|
|
68
|
+
next = band
|
|
69
|
+
? next.createRectMark({ id, data: binding.data, fill: "#94a3b8", opacity: 0.15, stroke: false, ...style })
|
|
70
|
+
: next.createRuleMark({ id, data: binding.data, stroke: "#64748b", strokeWidth: 1, strokeDash: "dashed", ...style });
|
|
71
|
+
next = next[encode]({ ...options, datum: values[0] });
|
|
72
|
+
return band ? next[`${encode}2`]({ ...options, datum: values[1] }) : next;
|
|
73
|
+
};
|
|
74
|
+
// Validate the complete lower-action chain on a discarded immutable branch.
|
|
75
|
+
apply(program);
|
|
76
|
+
return apply(program);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const createReferenceLine = action(
|
|
80
|
+
{ op: "createReferenceLine", description: "Create a constant reference line in data or plot coordinates." },
|
|
81
|
+
function (args = {}) { return createReference(this, args, false); }
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
export const createReferenceBand = action(
|
|
85
|
+
{ op: "createReferenceBand", description: "Create a constant reference interval in data or plot coordinates." },
|
|
86
|
+
function (args = {}) { return createReference(this, args, true); }
|
|
87
|
+
);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../../grammar/text.js";
|
|
1
2
|
import { action } from "../../core/action.js";
|
|
2
3
|
import { validateUserId } from "../../core/identifiers.js";
|
|
3
4
|
import { validateKeys } from "../../core/validation.js";
|
|
4
5
|
import { findDataset } from "../../selectors/datasets.js";
|
|
5
6
|
import { findLayer } from "../../selectors/layers.js";
|
|
7
|
+
import { applyDetachedScaleRematerialization } from "../../materialization/dependencies.js";
|
|
6
8
|
import {
|
|
7
9
|
getPositionChannelDefinition,
|
|
8
10
|
POSITION_CHANNELS
|
|
@@ -28,12 +30,17 @@ function ownedChildren(program, id) {
|
|
|
28
30
|
config.boxPlot?.whiskerId,
|
|
29
31
|
config.boxPlot?.medianId,
|
|
30
32
|
config.boxPlot?.outlierId,
|
|
31
|
-
config.gradientPlot?.centerId
|
|
33
|
+
config.gradientPlot?.centerId,
|
|
34
|
+
config.intervalPlot?.intervalId,
|
|
35
|
+
config.endpointPlot?.roles?.stemId,
|
|
36
|
+
config.endpointPlot?.roles?.startId,
|
|
37
|
+
config.endpointPlot?.roles?.connectorId,
|
|
38
|
+
...(config.raincloudPlot?.ownedChildIds ?? [])
|
|
32
39
|
].concat(
|
|
33
40
|
program.semanticSpec.layers
|
|
34
41
|
.filter(layer => layer.source === id)
|
|
35
42
|
.map(layer => layer.id)
|
|
36
|
-
).filter(child => child !== undefined && findLayer(program, child) !== undefined);
|
|
43
|
+
).filter(child => child !== undefined && child !== id && findLayer(program, child) !== undefined);
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
function ownership(program) {
|
|
@@ -90,7 +97,10 @@ function ownedDerivedData(program, ids) {
|
|
|
90
97
|
config.regression?.dataId,
|
|
91
98
|
config.boxPlot?.summaryId,
|
|
92
99
|
config.boxPlot?.outlierDataId,
|
|
93
|
-
config.gradientPlot?.profileId
|
|
100
|
+
config.gradientPlot?.profileId,
|
|
101
|
+
config.violinPlot?.materialized === true ? layer?.data : undefined,
|
|
102
|
+
config.ecdfPlot?.data,
|
|
103
|
+
config.endpointPlot?.data
|
|
94
104
|
]) {
|
|
95
105
|
if (candidate !== undefined) candidates.add(candidate);
|
|
96
106
|
}
|
|
@@ -161,7 +171,7 @@ export function removeOwnedMark(program, id, partial = false) {
|
|
|
161
171
|
|
|
162
172
|
function hasScaleConsumer(program, channel, scale) {
|
|
163
173
|
return program.semanticSpec.layers.some(
|
|
164
|
-
layer => layer.encoding?.[channel]?.scale === scale
|
|
174
|
+
layer => !isSourceOwnedText(layer) && layer.encoding?.[channel]?.scale === scale
|
|
165
175
|
);
|
|
166
176
|
}
|
|
167
177
|
|
|
@@ -198,6 +208,7 @@ export const removeMark = action(
|
|
|
198
208
|
validateKeys(args, OPTIONS, "removeMark");
|
|
199
209
|
const owner = resolveOwner(this, args.target);
|
|
200
210
|
const ids = collectClosure(this, owner.id);
|
|
211
|
+
const previousLayers = ids.map(id => findLayer(this, id));
|
|
201
212
|
const derived = ownedDerivedData(this, ids);
|
|
202
213
|
const positionScales = usedPositionScales(this, ids);
|
|
203
214
|
let next = this;
|
|
@@ -227,6 +238,7 @@ export const removeMark = action(
|
|
|
227
238
|
}
|
|
228
239
|
next = next._withoutMaterializationConfig(["marks", id]);
|
|
229
240
|
next = next._withoutMaterializationConfig(["jitters", id]);
|
|
241
|
+
next = next._withoutMaterializationConfig(["pointPacking", id]);
|
|
230
242
|
next = next._withoutMaterializationConfig(["labelLayouts", id]);
|
|
231
243
|
}
|
|
232
244
|
|
|
@@ -236,6 +248,7 @@ export const removeMark = action(
|
|
|
236
248
|
next = next.releaseDerivedData({ id });
|
|
237
249
|
}
|
|
238
250
|
}
|
|
251
|
+
next = applyDetachedScaleRematerialization(next, previousLayers);
|
|
239
252
|
return next._withContext({
|
|
240
253
|
...(ids.includes(this.context.currentMark) ? { currentMark: undefined } : {}),
|
|
241
254
|
...(selectionCleanup.selectionIds.includes(this.context.currentSelection)
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
import { deriveRuleValues, resolveRuleMode } from "../../../grammar/rules.js";
|
|
12
12
|
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
13
13
|
import { findDataset } from "../../../selectors/datasets.js";
|
|
14
|
-
import { findLayer } from "../../../selectors/layers.js";
|
|
14
|
+
import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
15
|
+
import { applyRuleAppearance, planRuleAppearance } from "./appearance.js";
|
|
15
16
|
import { DEFAULT_COLORS } from "../../../theme/defaults.js";
|
|
16
17
|
import {
|
|
17
18
|
assertMarkAvailable,
|
|
@@ -28,8 +29,11 @@ import { resolveMarkGraphicPlacement } from
|
|
|
28
29
|
import { rematerializeHighlightBaseline } from "../lifecycle.js";
|
|
29
30
|
import { applyOffsetPositionValues } from
|
|
30
31
|
"../../../materialization/rowEncoding.js";
|
|
32
|
+
import { resolveCategorySlotOffset } from
|
|
33
|
+
"../../../materialization/categorySlotOffset.js";
|
|
31
34
|
|
|
32
|
-
const CREATE_OPTIONS = Object.freeze(["id", "data"]);
|
|
35
|
+
const CREATE_OPTIONS = Object.freeze(["id", "data", "stroke", "strokeWidth", "strokeDash", "opacity"]);
|
|
36
|
+
const EDIT_OPTIONS = Object.freeze(["target", "stroke", "strokeWidth", "strokeDash", "opacity"]);
|
|
33
37
|
const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
|
|
34
38
|
const SPAN_OPTIONS = Object.freeze(["id", "orientation", "size"]);
|
|
35
39
|
const DEFAULT_RULE_CONFIG = Object.freeze({
|
|
@@ -96,6 +100,7 @@ const createRuleMark = action(
|
|
|
96
100
|
inherited?.data !== undefined ? { data: inherited.data } : {})
|
|
97
101
|
});
|
|
98
102
|
assertMarkAvailable(this, id);
|
|
103
|
+
const appearance = planRuleAppearance(args, inherited);
|
|
99
104
|
|
|
100
105
|
let created = this
|
|
101
106
|
.editSemantic({ property: `layer[${id}].mark.type`, value: "rule" })
|
|
@@ -119,7 +124,23 @@ const createRuleMark = action(
|
|
|
119
124
|
}
|
|
120
125
|
})
|
|
121
126
|
});
|
|
122
|
-
return materializeInheritedMark(created, id);
|
|
127
|
+
return applyRuleAppearance(materializeInheritedMark(created, id), id, appearance);
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const editRuleMark = action(
|
|
132
|
+
{ op: "editRuleMark", description: "Edit constant rule appearance through its encoding owners." },
|
|
133
|
+
function (args = {}) {
|
|
134
|
+
validateMarkOptions(args, EDIT_OPTIONS, "editRuleMark");
|
|
135
|
+
if (args.target !== undefined) validateUserId(args.target, "Rule mark id");
|
|
136
|
+
const layer = resolveEligibleLayer(this, {
|
|
137
|
+
target: args.target,
|
|
138
|
+
predicate: candidate => candidate.mark?.type === "rule",
|
|
139
|
+
label: "rule mark"
|
|
140
|
+
});
|
|
141
|
+
const appearance = planRuleAppearance(args, layer);
|
|
142
|
+
if (appearance.length === 0) throw new Error("editRuleMark requires an appearance change.");
|
|
143
|
+
return applyRuleAppearance(this, layer.id, appearance);
|
|
123
144
|
}
|
|
124
145
|
);
|
|
125
146
|
|
|
@@ -263,6 +284,15 @@ const rematerializeRuleMark = action(
|
|
|
263
284
|
x2.push(endX);
|
|
264
285
|
y2.push(endY);
|
|
265
286
|
}
|
|
287
|
+
const categoryOffset = resolveCategorySlotOffset(resolved, layer);
|
|
288
|
+
if (categoryOffset?.pixels) {
|
|
289
|
+
const targets = categoryOffset.channel === "x" ? [x1, x2] : [y1, y2];
|
|
290
|
+
for (const values of targets) {
|
|
291
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
292
|
+
if (Number.isFinite(values[index])) values[index] += categoryOffset.pixels;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
266
296
|
|
|
267
297
|
const resolvedConfig = {
|
|
268
298
|
...DEFAULT_RULE_CONFIG,
|
|
@@ -337,6 +367,7 @@ export const materializeRuleSpan = action(
|
|
|
337
367
|
export function registerRuleMarkActions(ProgramClass) {
|
|
338
368
|
Object.assign(ProgramClass.prototype, {
|
|
339
369
|
createRuleMark,
|
|
370
|
+
editRuleMark,
|
|
340
371
|
rematerializeRuleMark,
|
|
341
372
|
materializeRuleSpan
|
|
342
373
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { validateRuleStroke, validateRuleStrokeWidth } from "../../../grammar/ruleAppearance.js";
|
|
2
|
+
import { normalizeStrokeDashPattern, validateOpacityValue } from "../../../grammar/scales/index.js";
|
|
3
|
+
|
|
4
|
+
const STYLE_ACTIONS = Object.freeze({
|
|
5
|
+
stroke: ["encodeStroke", validateRuleStroke],
|
|
6
|
+
strokeWidth: ["encodeStrokeWidth", validateRuleStrokeWidth],
|
|
7
|
+
strokeDash: ["encodeStrokeDash", normalizeStrokeDashPattern],
|
|
8
|
+
opacity: ["encodeOpacity", validateOpacityValue]
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export function planRuleAppearance(args, layer) {
|
|
12
|
+
return Object.entries(STYLE_ACTIONS).flatMap(([property, [op, validate]]) => {
|
|
13
|
+
if (!Object.hasOwn(args, property)) return [];
|
|
14
|
+
if (layer?.encoding?.[property]?.field !== undefined) {
|
|
15
|
+
throw new Error(`Rule ${property} conflicts with a field encoding; use ${op} with value to replace it.`);
|
|
16
|
+
}
|
|
17
|
+
validate(args[property], `Rule ${property}`);
|
|
18
|
+
return [{ op, value: args[property] }];
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function applyRuleAppearance(program, target, plan) {
|
|
23
|
+
let next = program;
|
|
24
|
+
for (const { op, value } of plan) next = next[op]({ target, value });
|
|
25
|
+
return next;
|
|
26
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { isSourceOwnedText } from "../../grammar/text.js";
|
|
1
2
|
import {
|
|
2
3
|
resolveOptionalUserId,
|
|
3
4
|
validateUserId
|
|
4
5
|
} from "../../core/identifiers.js";
|
|
5
|
-
import { findDataset } from "../../selectors/datasets.js";
|
|
6
|
+
import { findDataset, requireMaterializedDataset } from "../../selectors/datasets.js";
|
|
6
7
|
import { findLayer, hasLayer } from "../../selectors/layers.js";
|
|
7
8
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
8
9
|
import { resolveMarkPositionPolicy } from "../encodings/position/policies/index.js";
|
|
@@ -27,12 +28,7 @@ export function resolveMarkData(program, requested) {
|
|
|
27
28
|
throw new Error("Mark creation requires data or a current dataset.");
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
const dataset =
|
|
31
|
-
|
|
32
|
-
if (dataset === undefined) {
|
|
33
|
-
throw new Error(`Unknown dataset "${data}".`);
|
|
34
|
-
}
|
|
35
|
-
|
|
31
|
+
const dataset = requireMaterializedDataset(program, data);
|
|
36
32
|
return { data, dataset };
|
|
37
33
|
}
|
|
38
34
|
|
|
@@ -60,7 +56,7 @@ export function assertMarkAvailable(program, id) {
|
|
|
60
56
|
throw new Error(`Mark "${id}" already exists.`);
|
|
61
57
|
}
|
|
62
58
|
|
|
63
|
-
if (program.graphicSpec.objects
|
|
59
|
+
if (Object.hasOwn(program.graphicSpec.objects, id)) {
|
|
64
60
|
throw new Error(`Graphic "${id}" already exists.`);
|
|
65
61
|
}
|
|
66
62
|
}
|
|
@@ -78,9 +74,9 @@ const POSITION_POLICY_SUBSETS = Object.freeze([
|
|
|
78
74
|
]);
|
|
79
75
|
|
|
80
76
|
function inheritedPositionEncodings(encoding) {
|
|
81
|
-
if (encoding?.field === undefined) return undefined;
|
|
77
|
+
if (encoding?.field === undefined && encoding?.aggregate !== "count") return undefined;
|
|
82
78
|
const base = Object.fromEntries(
|
|
83
|
-
["field", "fieldType", "scale", "title"]
|
|
79
|
+
["field", "fieldType", "scale", "title", "temporalUnit"]
|
|
84
80
|
.filter(property => Object.hasOwn(encoding, property))
|
|
85
81
|
.map(property => [property, encoding[property]])
|
|
86
82
|
);
|
|
@@ -104,6 +100,9 @@ function inheritedPositionEncodings(encoding) {
|
|
|
104
100
|
function scaleSupportsEncoding(program, markType, channel, encoding) {
|
|
105
101
|
const scale = findSemanticScale(program, encoding.scale);
|
|
106
102
|
if (scale === undefined) return false;
|
|
103
|
+
if (scale.radialMapping !== undefined) {
|
|
104
|
+
return markType === "arc" && channel === "radius" && ["count", "sum"].includes(encoding.aggregate);
|
|
105
|
+
}
|
|
107
106
|
const categorical = ["nominal", "ordinal"].includes(encoding.fieldType);
|
|
108
107
|
if (categorical) {
|
|
109
108
|
if (["bar", "rect"].includes(markType)) return scale.type === "band";
|
|
@@ -143,7 +142,8 @@ export function resolveCompatibleEncodings(program, source, markType) {
|
|
|
143
142
|
layer: candidate,
|
|
144
143
|
dataset,
|
|
145
144
|
channel,
|
|
146
|
-
args: encoding,
|
|
145
|
+
args: { ...encoding, ...(channel === "radius" && findSemanticScale(program, encoding.scale)?.radialMapping !== undefined
|
|
146
|
+
? { mapping: findSemanticScale(program, encoding.scale).radialMapping } : {}) },
|
|
147
147
|
field: encoding.field,
|
|
148
148
|
fieldType: encoding.fieldType
|
|
149
149
|
});
|
|
@@ -163,7 +163,7 @@ export function resolveCompatibleEncodings(program, source, markType) {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
function eligibleLayeredSource(program, layer, requestedData, markType) {
|
|
166
|
-
if (layer?.data === undefined) return false;
|
|
166
|
+
if (layer?.data === undefined || isSourceOwnedText(layer)) return false;
|
|
167
167
|
if (requestedData !== undefined && layer.data !== requestedData) return false;
|
|
168
168
|
return Object.keys(resolveCompatibleEncodings(program, layer, markType)).length > 0;
|
|
169
169
|
}
|