ggaction 0.0.24 → 0.0.26
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 +47 -0
- package/README.md +2 -2
- package/knowledge/action-cards.json +1 -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 +1 -1
- package/src/accessibility.js +13 -0
- package/src/actions/canvas/actions.js +2 -1
- package/src/actions/canvas/fitting.js +68 -13
- package/src/actions/charts/beeswarm.js +2 -1
- package/src/actions/charts/ecdf.js +2 -1
- package/src/actions/charts/endpoints.js +25 -16
- package/src/actions/charts/heatmap.js +5 -3
- package/src/actions/charts/missing.js +17 -0
- package/src/actions/charts/radar.js +70 -33
- package/src/actions/charts/raincloud.js +18 -4
- package/src/actions/charts/rug-strip.js +2 -2
- package/src/actions/charts/shared.js +17 -0
- package/src/actions/composition/actions.js +24 -7
- package/src/actions/composition/adjacent.js +90 -0
- package/src/actions/composition/index.js +11 -1
- package/src/actions/composition/layer.js +42 -0
- package/src/actions/composition/layerPlan.js +176 -0
- package/src/actions/composition/targets.js +105 -0
- package/src/actions/data/derived.js +3 -0
- package/src/actions/data/filter.js +1 -1
- package/src/actions/data/raincloudFilter.js +3 -1
- package/src/actions/encodings/appearance.js +26 -7
- package/src/actions/encodings/channels.js +1 -0
- package/src/actions/encodings/color/continuous.js +37 -34
- package/src/actions/encodings/color/index.js +57 -43
- package/src/actions/encodings/color/policy.js +15 -0
- package/src/actions/encodings/density/resolve.js +3 -2
- package/src/actions/encodings/density.js +2 -2
- package/src/actions/encodings/offset.js +2 -0
- package/src/actions/encodings/pathOrder.js +2 -0
- package/src/actions/encodings/position/areaAlignment.js +42 -0
- package/src/actions/encodings/position/index.js +37 -3
- package/src/actions/encodings/position/policies/line.js +1 -1
- package/src/actions/encodings/ranged.js +2 -0
- package/src/actions/encodings/remove.js +13 -10
- package/src/actions/encodings/ruleAppearance.js +14 -2
- package/src/actions/facets/actions.js +1 -1
- package/src/actions/guides/axes/axes.js +2 -1
- package/src/actions/guides/axes/edit.js +1 -1
- package/src/actions/guides/axes/labels.js +2 -1
- package/src/actions/guides/axes/parallel/lifecycle.js +11 -4
- package/src/actions/guides/axes/parallel/policy.js +5 -2
- package/src/actions/guides/axes/parallel/resolve.js +3 -1
- package/src/actions/guides/axes/parallel.js +9 -4
- package/src/actions/guides/axes/titles.js +1 -1
- package/src/actions/guides/facadeLegend.js +3 -2
- package/src/actions/guides/grids/grid.js +3 -2
- package/src/actions/guides/grids/resolve.js +2 -1
- package/src/actions/guides/guides.js +3 -2
- package/src/actions/guides/legends/automatic.js +28 -0
- package/src/actions/guides/legends/blocks.js +15 -21
- package/src/actions/guides/legends/categorical/actions.js +1 -1
- package/src/actions/guides/legends/categorical/components.js +4 -3
- package/src/actions/guides/legends/categorical/layout.js +5 -2
- package/src/actions/guides/legends/categorical/options.js +9 -14
- package/src/actions/guides/legends/categorical/recipes.js +3 -0
- package/src/actions/guides/legends/categorical/resolve.js +7 -5
- package/src/actions/guides/legends/colorSize.js +51 -14
- package/src/actions/guides/legends/continuous/common.js +1 -1
- package/src/actions/guides/legends/continuous/gradient.js +1 -1
- package/src/actions/guides/legends/continuous/opacity.js +2 -0
- package/src/actions/guides/legends/edit.js +1 -1
- package/src/actions/guides/legends/focused.js +2 -2
- package/src/actions/guides/legends/group.js +1 -1
- package/src/actions/guides/legends/lane.js +1 -1
- package/src/actions/guides/legends/lifecycle.js +2 -2
- package/src/actions/guides/legends/remove.js +18 -4
- package/src/actions/guides/legends/size.js +1 -1
- package/src/actions/guides/legends/transition.js +15 -6
- package/src/actions/guides/polar/axes/facade.js +4 -2
- package/src/actions/guides/polar/axes/labels.js +6 -1
- package/src/actions/guides/polar/axes/shared.js +4 -4
- package/src/actions/guides/polar/axes/titles.js +13 -2
- package/src/actions/guides/polar/grids.js +6 -3
- package/src/actions/guides/tickValues.js +7 -1
- package/src/actions/index.js +2 -0
- package/src/actions/marks/area/actions.js +9 -2
- package/src/actions/marks/area/materialize.js +1 -1
- package/src/actions/marks/bar/materialize.js +4 -4
- package/src/actions/marks/lifecycle.js +14 -0
- package/src/actions/marks/line/actions.js +3 -0
- package/src/actions/marks/line/materialize.js +4 -2
- package/src/actions/marks/point/edit.js +2 -0
- package/src/actions/marks/point/jitter.js +2 -0
- package/src/actions/marks/point/materialize.js +5 -4
- package/src/actions/marks/point/packing.js +2 -0
- package/src/actions/marks/references.js +5 -3
- package/src/actions/marks/remove.js +16 -0
- package/src/actions/marks/shared.js +2 -1
- package/src/actions/marks/text/actions.js +5 -2
- package/src/actions/overlays/index.js +168 -0
- package/src/actions/overlays/snapshot.js +45 -0
- package/src/actions/primitives/editGraphics.js +21 -27
- package/src/actions/primitives/semanticAction.js +4 -5
- package/src/actions/primitives/semanticValidation/guide.js +1 -0
- package/src/actions/primitives/semanticValidation/layer.js +7 -0
- package/src/actions/scales/consumers/common.js +10 -0
- package/src/actions/scales/consumers/families.js +1 -1
- package/src/actions/scales/preview.js +1 -1
- package/src/actions/selection/actions.js +63 -11
- package/src/actions/titles/actions.js +3 -14
- package/src/actions/titles/resolve.js +12 -9
- package/src/core/action.js +41 -25
- package/src/core/compositionScales.js +12 -0
- package/src/core/compositionState.js +16 -2
- package/src/core/immutable.js +11 -6
- package/src/core/programState.js +16 -0
- package/src/core/resourceReferences.js +19 -3
- package/src/core/validation.js +7 -0
- package/src/core/vocabulary.js +8 -2
- package/src/grammar/actionTargets.js +24 -0
- package/src/grammar/bin2d.js +7 -0
- package/src/grammar/colorSizeLegend.js +56 -5
- package/src/grammar/displayLabels.js +10 -0
- package/src/grammar/facetHeaders.js +1 -1
- package/src/grammar/facets/dependencies.js +10 -3
- package/src/grammar/facets/guides.js +2 -1
- package/src/grammar/facets/legendIntent.js +7 -5
- package/src/grammar/legendBlocks.js +1 -1
- package/src/grammar/legendCombinations.js +1 -1
- package/src/grammar/legendGroups.js +1 -1
- package/src/grammar/legendItemsOffset.js +12 -0
- package/src/grammar/legendOverflow.js +25 -0
- package/src/grammar/lineSeries.js +1 -1
- package/src/grammar/markSelection.js +23 -0
- package/src/grammar/overlay.js +51 -0
- package/src/grammar/parallelCoordinates.js +1 -208
- package/src/grammar/pathCommands.js +8 -2
- package/src/grammar/pathSeries.js +1 -1
- package/src/grammar/plotComposition.js +152 -0
- package/src/grammar/pointShapes.js +18 -0
- package/src/grammar/scales/transformed.js +1 -1
- package/src/grammar/schemas/concreteGraphic.js +8 -1
- package/src/grammar/schemas/graphicBounds.js +8 -1
- package/src/grammar/schemas/graphicTree.js +18 -2
- package/src/grammar/schemas/semanticPath.js +3 -0
- package/src/grammar/strokeStyle.js +14 -8
- package/src/grammar/text.js +72 -0
- package/src/inspection.js +1 -1
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/composition.js +30 -4
- package/src/layout/compositionLegend.js +91 -0
- package/src/layout/facetHeaderTitles.js +1 -1
- package/src/layout/labels.js +55 -27
- package/src/layout/legend.js +3 -2
- package/src/layout/legendGroup.js +1 -1
- package/src/layout/legendItems.js +7 -6
- package/src/layout/legendLane.js +1 -1
- package/src/layout/legendText.js +1 -1
- package/src/materialization/adjacentSource.js +27 -0
- package/src/materialization/composition.js +165 -52
- package/src/materialization/compositionLegends.js +120 -0
- package/src/materialization/compositionScales.js +125 -0
- package/src/materialization/compositionTitle.js +96 -0
- package/src/materialization/facetGuides/edit.js +22 -2
- package/src/materialization/facetGuides/layout.js +1 -1
- package/src/materialization/facetGuides/owner.js +4 -3
- package/src/materialization/facetGuides/ownerLayout.js +12 -6
- package/src/materialization/facetGuides/placement.js +19 -22
- package/src/materialization/facetGuides/preparation.js +17 -5
- package/src/materialization/facetHeaders.js +5 -1
- package/src/materialization/facets.js +1 -1
- package/src/materialization/guides/layout.js +1 -1
- package/src/materialization/guides/resources.js +1 -1
- package/src/materialization/guides/visibility.js +105 -0
- package/src/materialization/legendClip.js +1 -1
- package/src/materialization/legendGroups.js +1 -1
- package/src/materialization/legendItemsOffset.js +68 -0
- package/src/materialization/legendSuppression.js +22 -0
- package/src/materialization/legends.js +3 -2
- package/src/materialization/marks/capabilities.js +4 -0
- package/src/materialization/marks/index.js +5 -1
- package/src/materialization/marks/pathOrder.js +1 -1
- package/src/materialization/marks/policies.js +2 -1
- package/src/materialization/overlayItems.js +154 -0
- package/src/materialization/scales/policies/binnedPosition.js +43 -5
- package/src/materialization/scales/policies/series.js +12 -4
- package/src/materialization/scales/resolve.js +2 -0
- package/src/materialization/selection/items/bar.js +21 -3
- package/src/materialization/selection/items/common.js +18 -5
- package/src/materialization/selection/items/overlay.js +15 -0
- package/src/materialization/selection/items/path.js +1 -0
- package/src/materialization/selection/items/point.js +2 -3
- package/src/materialization/selection/items/tick.js +3 -12
- package/src/materialization/selection/path.js +2 -2
- package/src/materialization/selection/policies/bar.js +2 -0
- package/src/materialization/selection/policies/index.js +35 -1
- package/src/materialization/selection/policies/point.js +2 -0
- package/src/materialization/selection/policies/text.js +17 -0
- package/src/materialization/selection/rows.js +103 -0
- package/src/materialization/selection/state.js +19 -3
- package/src/materialization/selection/styles.js +8 -0
- package/src/materialization/text.js +6 -489
- package/src/materialization/textGeometry.js +503 -0
- package/src/materialization/textLines.js +20 -0
- package/src/persistence/validation.js +97 -24
- package/src/renderers/canvas/index.js +12 -7
- package/src/renderers/canvas/text.js +5 -5
- package/src/renderers/nativeBackend.js +25 -0
- package/src/renderers/pdf.js +3 -3
- package/src/renderers/png.js +4 -3
- package/src/renderers/svgSerialization.js +1 -643
- package/src/scene.js +5 -3
- package/src/selectors/plotComposition.js +115 -0
- package/src/selectors/selectionTargets.js +19 -0
- package/src/version.js +1 -1
- package/types/basic.d.ts +2 -2
- package/types/index.d.ts +11 -0
- package/types/inspection.d.ts +34 -0
- package/types/program.d.ts +553 -365
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { prepareFacadeMissingData } from './missing.js';
|
|
1
2
|
import { validateUserId } from "../../core/identifiers.js";
|
|
2
3
|
import { action } from "../../core/action.js";
|
|
3
4
|
import { STROKE_STYLE_PROPERTIES } from "../../grammar/strokeStyle.js";
|
|
@@ -14,6 +15,7 @@ import { findDataset } from "../../selectors/datasets.js";
|
|
|
14
15
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
15
16
|
import {
|
|
16
17
|
applyFacadeGuides,
|
|
18
|
+
retainFacadeAxisPresence,
|
|
17
19
|
normalizeAppearance,
|
|
18
20
|
normalizeFieldEncoding,
|
|
19
21
|
normalizeGuides,
|
|
@@ -24,7 +26,7 @@ import {
|
|
|
24
26
|
} from "./shared.js";
|
|
25
27
|
|
|
26
28
|
const COMMON_OPTIONS = Object.freeze([
|
|
27
|
-
"id", "data", "coordinate", "category", "orientation", "summary", "guides", "labels"
|
|
29
|
+
"id", "data", "coordinate", "category", "orientation", "summary", "guides", "labels", "missing"
|
|
28
30
|
]);
|
|
29
31
|
const DOT_OPTIONS = Object.freeze([...COMMON_OPTIONS, "value", "point"]);
|
|
30
32
|
const LOLLIPOP_OPTIONS = Object.freeze([...DOT_OPTIONS, "baseline", "stem"]);
|
|
@@ -48,7 +50,7 @@ const LABEL_OPTIONS = Object.freeze([
|
|
|
48
50
|
const SUMMARY_OPERATIONS = Object.freeze(["mean", "median", "sum", "min", "max"]);
|
|
49
51
|
const EDIT_OPTIONS = Object.freeze([
|
|
50
52
|
"target", "data", "coordinate", "category", "value", "start", "end",
|
|
51
|
-
"orientation", "summary", "baseline"
|
|
53
|
+
"orientation", "summary", "baseline", "missing"
|
|
52
54
|
]);
|
|
53
55
|
|
|
54
56
|
function normalizeCategory(value, operation) {
|
|
@@ -124,9 +126,12 @@ function outputField(id, role) {
|
|
|
124
126
|
return `__${id}_${role}`;
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
function prepareData(program, { id, data, category, measures, summary, operation }) {
|
|
129
|
+
function prepareData(program, { id, data, category, measures, summary, operation, missing }) {
|
|
130
|
+
const prepared = prepareFacadeMissingData(program, { id, source: data,
|
|
131
|
+
fields: [category.field, ...measures.map(item => item.field)], missing });
|
|
132
|
+
program = prepared.program; data = prepared.data;
|
|
128
133
|
validateRows(program, data, category.field, measures.map(item => item.field), operation);
|
|
129
|
-
if (summary === false) return { program, data, measures };
|
|
134
|
+
if (summary === false) return { program, data, measures, ownedData: prepared.ownedData };
|
|
130
135
|
const derived = `${id}SummaryData`;
|
|
131
136
|
const outputs = measures.map((item, index) => ({
|
|
132
137
|
...item,
|
|
@@ -144,6 +149,7 @@ function prepareData(program, { id, data, category, measures, summary, operation
|
|
|
144
149
|
}))
|
|
145
150
|
}),
|
|
146
151
|
data: derived,
|
|
152
|
+
ownedData: prepared.ownedData,
|
|
147
153
|
measures: outputs
|
|
148
154
|
};
|
|
149
155
|
}
|
|
@@ -249,10 +255,10 @@ function attachLabels(program, id, labels, defaultField) {
|
|
|
249
255
|
});
|
|
250
256
|
}
|
|
251
257
|
|
|
252
|
-
function finish(program, { id, source, data, kind, roles, summary, orientation, options }) {
|
|
258
|
+
function finish(program, { id, source, data, kind, roles, summary, orientation, options, ownedData }) {
|
|
253
259
|
return program._withMarkConfig(id, {
|
|
254
260
|
...program.markConfigs[id],
|
|
255
|
-
endpointPlot: { kind, source, data, roles, summary, orientation, options }
|
|
261
|
+
endpointPlot: { kind, source, data, roles, summary, orientation, options, ownedData }
|
|
256
262
|
});
|
|
257
263
|
}
|
|
258
264
|
|
|
@@ -271,7 +277,7 @@ export const createDotPlot = /* @__PURE__ */ action(
|
|
|
271
277
|
const guides = normalizeGuides(args.guides, operation);
|
|
272
278
|
const labels = normalizeLabels(args.labels, operation, false);
|
|
273
279
|
const prepared = prepareData(this, {
|
|
274
|
-
id, data: source, category, measures: [{ ...value, role: "value" }], summary, operation
|
|
280
|
+
id, data: source, category, measures: [{ ...value, role: "value" }], summary, operation, missing: args.missing
|
|
275
281
|
});
|
|
276
282
|
const finalValue = prepared.measures[0];
|
|
277
283
|
const bindings = positionBindings(id, category, finalValue, orientation, args.coordinate);
|
|
@@ -280,7 +286,8 @@ export const createDotPlot = /* @__PURE__ */ action(
|
|
|
280
286
|
next = applyFacadeGuides(next, guides, id, guides);
|
|
281
287
|
return finish(next, {
|
|
282
288
|
id, source, data: prepared.data, kind: "dot", roles: { category, value }, summary, orientation,
|
|
283
|
-
|
|
289
|
+
ownedData: prepared.ownedData,
|
|
290
|
+
options: { id, data: source, missing: args.missing, coordinate: args.coordinate, category, value, orientation,
|
|
284
291
|
summary, point, guides, labels }
|
|
285
292
|
});
|
|
286
293
|
}
|
|
@@ -304,7 +311,7 @@ export const createLollipopPlot = /* @__PURE__ */ action(
|
|
|
304
311
|
const guides = normalizeGuides(args.guides, operation);
|
|
305
312
|
const labels = normalizeLabels(args.labels, operation, false);
|
|
306
313
|
const prepared = prepareData(this, {
|
|
307
|
-
id, data: source, category, measures: [{ ...value, role: "value" }], summary, operation
|
|
314
|
+
id, data: source, category, measures: [{ ...value, role: "value" }], summary, operation, missing: args.missing
|
|
308
315
|
});
|
|
309
316
|
const finalValue = prepared.measures[0];
|
|
310
317
|
const bindings = positionBindings(id, category, finalValue, orientation, args.coordinate);
|
|
@@ -323,7 +330,8 @@ export const createLollipopPlot = /* @__PURE__ */ action(
|
|
|
323
330
|
return finish(next, {
|
|
324
331
|
id, source, data: prepared.data, kind: "lollipop",
|
|
325
332
|
roles: { category, value, baseline, stemId }, summary, orientation,
|
|
326
|
-
|
|
333
|
+
ownedData: prepared.ownedData,
|
|
334
|
+
options: { id, data: source, missing: args.missing, coordinate: args.coordinate, category, value, orientation,
|
|
327
335
|
summary, baseline, point, stem, guides, labels }
|
|
328
336
|
});
|
|
329
337
|
}
|
|
@@ -358,7 +366,7 @@ export const createDumbbellPlot = /* @__PURE__ */ action(
|
|
|
358
366
|
{ ...start, scale: sharedScale, role: "start" },
|
|
359
367
|
{ ...end, scale: sharedScale, role: "end" }
|
|
360
368
|
],
|
|
361
|
-
summary, operation
|
|
369
|
+
summary, operation, missing: args.missing
|
|
362
370
|
});
|
|
363
371
|
const [finalStart, finalEnd] = prepared.measures;
|
|
364
372
|
const startBindings = positionBindings(id, category, finalStart, orientation, args.coordinate);
|
|
@@ -396,7 +404,8 @@ export const createDumbbellPlot = /* @__PURE__ */ action(
|
|
|
396
404
|
return finish(next, {
|
|
397
405
|
id, source, data: prepared.data, kind: "dumbbell",
|
|
398
406
|
roles: { category, start, end, startId, connectorId }, summary, orientation,
|
|
399
|
-
|
|
407
|
+
ownedData: prepared.ownedData,
|
|
408
|
+
options: { id, data: source, missing: args.missing, coordinate: args.coordinate, category, start, end, orientation,
|
|
400
409
|
summary, startPoint, endPoint, connector, guides, labels }
|
|
401
410
|
});
|
|
402
411
|
}
|
|
@@ -410,12 +419,12 @@ const KIND_METHOD = Object.freeze({
|
|
|
410
419
|
dumbbell: "createDumbbellPlot"
|
|
411
420
|
});
|
|
412
421
|
const KIND_ROLES = Object.freeze({
|
|
413
|
-
dot: new Set(["data", "coordinate", "category", "value", "orientation", "summary"]),
|
|
422
|
+
dot: new Set(["data", "coordinate", "category", "value", "orientation", "summary", "missing"]),
|
|
414
423
|
lollipop: new Set([
|
|
415
|
-
"data", "coordinate", "category", "value", "orientation", "summary", "baseline"
|
|
424
|
+
"data", "coordinate", "category", "value", "orientation", "summary", "baseline", "missing"
|
|
416
425
|
]),
|
|
417
426
|
dumbbell: new Set([
|
|
418
|
-
"data", "coordinate", "category", "start", "end", "orientation", "summary"
|
|
427
|
+
"data", "coordinate", "category", "start", "end", "orientation", "summary", "missing"
|
|
419
428
|
])
|
|
420
429
|
});
|
|
421
430
|
|
|
@@ -494,7 +503,7 @@ export const editEndpointPlot = /* @__PURE__ */ action(
|
|
|
494
503
|
if (invalid !== undefined) {
|
|
495
504
|
throw new Error(`editEndpointPlot ${invalid} is not available for ${current.kind} plots.`);
|
|
496
505
|
}
|
|
497
|
-
const revised = { ...current.options };
|
|
506
|
+
const revised = { ...current.options, guides: retainFacadeAxisPresence(this, owner, current.options.guides) };
|
|
498
507
|
for (const key of changes) revised[key] = args[key];
|
|
499
508
|
const axes = { fromAxis: current.orientation === "vertical" ? "y" : "x",
|
|
500
509
|
toAxis: revised.orientation === "vertical" ? "y" : "x" };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findSemanticScale } from "../../selectors/scales.js";
|
|
1
2
|
import { action } from "../../core/action.js";
|
|
2
3
|
import { isPlainObject } from "../../core/immutable.js";
|
|
3
4
|
import {
|
|
@@ -71,7 +72,8 @@ function normalizeBinnedColor(value) {
|
|
|
71
72
|
return { ...value };
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
function resolvedPosition(encoding, field, extent) {
|
|
75
|
+
function resolvedPosition(encoding, field, extent, program) {
|
|
76
|
+
if (encoding.scale?.id && findSemanticScale(program, encoding.scale.id)) return {field,fieldType:"quantitative",scale:encoding.scale};
|
|
75
77
|
const scale = encoding.scale ?? {};
|
|
76
78
|
return {
|
|
77
79
|
field,
|
|
@@ -178,8 +180,8 @@ export const createHeatmap = /* @__PURE__ */ action(
|
|
|
178
180
|
});
|
|
179
181
|
const transform = requireDataset(binned, generatedData).transform[0];
|
|
180
182
|
const resolved = transform.resolved;
|
|
181
|
-
const xEncoding = resolvedPosition(x, transform.as.x0, resolved.extent.x);
|
|
182
|
-
const yEncoding = resolvedPosition(y, transform.as.y0, resolved.extent.y);
|
|
183
|
+
const xEncoding = resolvedPosition(x, transform.as.x0, resolved.extent.x, this);
|
|
184
|
+
const yEncoding = resolvedPosition(y, transform.as.y0, resolved.extent.y, this);
|
|
183
185
|
const next = binned
|
|
184
186
|
.createRectMark({ id, data: generatedData, ...rect })
|
|
185
187
|
.encodeX(positionArgs(xEncoding, { target: id, coordinate: args.coordinate }))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { validateItemMissing } from '../../grammar/itemMissing.js';
|
|
2
|
+
|
|
3
|
+
// Complete-case source ownership for composite statistical charts. Keep the
|
|
4
|
+
// original source in the facade config; every child consumes this same view.
|
|
5
|
+
// Native filters preserve schemas, dependencies, trace and rematerialization.
|
|
6
|
+
export function prepareFacadeMissingData(program, { id, source, fields, missing }) {
|
|
7
|
+
const policy = validateItemMissing(missing ?? 'error', 'Chart missing');
|
|
8
|
+
if (policy === 'error') return { program, data: source, ownedData: [] };
|
|
9
|
+
let next = program, data = source;
|
|
10
|
+
const ownedData = [];
|
|
11
|
+
for (const [index, field] of [...new Set(fields.filter(Boolean))].entries()) {
|
|
12
|
+
const target = `${id}CompleteCases${index + 1}`;
|
|
13
|
+
next = next.filterData({ id: target, source: data, field, noneOf: [null], nulls: 'exclude' });
|
|
14
|
+
data = target; ownedData.unshift(target);
|
|
15
|
+
}
|
|
16
|
+
return { program: next, data, ownedData };
|
|
17
|
+
}
|
|
@@ -10,7 +10,6 @@ import { findDataset } from "../../selectors/datasets.js";
|
|
|
10
10
|
import { resolveEligibleLayer } from "../../selectors/layers.js";
|
|
11
11
|
import { planDerivedDataRevision } from "../../materialization/dataProvenance.js";
|
|
12
12
|
import { applyLayerDataRematerialization } from "../../materialization/dependencies.js";
|
|
13
|
-
import { deriveFoldRows, normalizeFoldTransform } from "../../grammar/fold.js";
|
|
14
13
|
import {
|
|
15
14
|
normalizeAppearance,
|
|
16
15
|
normalizeEncoding,
|
|
@@ -33,7 +32,7 @@ const CATEGORY_SCALE_OPTIONS = Object.freeze([
|
|
|
33
32
|
"id", "type", "domain", "range", "reverse", "paddingInner", "paddingOuter",
|
|
34
33
|
"padding", "align"
|
|
35
34
|
]);
|
|
36
|
-
const WIDE_OPTIONS = Object.freeze(["fields", "as"]);
|
|
35
|
+
const WIDE_OPTIONS = Object.freeze(["fields", "as", "summary", "normalize"]);
|
|
37
36
|
const WIDE_AS_OPTIONS = Object.freeze(["key", "value"]);
|
|
38
37
|
const LINE_OPTIONS = Object.freeze([
|
|
39
38
|
"strokeWidth", "curve", "stroke", "opacity", "closed",
|
|
@@ -105,7 +104,15 @@ function normalizeWide(value, id, operation, minimum = 3) {
|
|
|
105
104
|
if (key === foldedValue || fields.includes(key) || fields.includes(foldedValue)) {
|
|
106
105
|
throw new Error(`${operation} wide aliases must be distinct from selected fields.`);
|
|
107
106
|
}
|
|
108
|
-
|
|
107
|
+
const summary = value.summary ?? false;
|
|
108
|
+
const normalize = value.normalize ?? false;
|
|
109
|
+
if (![false, "mean", "median"].includes(summary)) {
|
|
110
|
+
throw new TypeError(`${operation} wide.summary must be false, mean, or median.`);
|
|
111
|
+
}
|
|
112
|
+
if (![false, "minmax"].includes(normalize)) {
|
|
113
|
+
throw new TypeError(`${operation} wide.normalize must be false or minmax.`);
|
|
114
|
+
}
|
|
115
|
+
return { fields, as: { key, value: foldedValue }, summary, normalize };
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
function orderedCategories(rows, field, requested, operation) {
|
|
@@ -189,6 +196,33 @@ function scaleWithOrder(category, order, operation) {
|
|
|
189
196
|
return { ...category, scale: { ...scale, domain: order } };
|
|
190
197
|
}
|
|
191
198
|
|
|
199
|
+
// Keep summaries and normalization in the public data grammar. Original values
|
|
200
|
+
// remain available, and each derived dataset has ordinary trace/provenance.
|
|
201
|
+
function prepareWideData(program, { source, wide, groupBy, color, strokeDash, data, operation }) {
|
|
202
|
+
let next = program, input = source;
|
|
203
|
+
const ownedData = [];
|
|
204
|
+
if (wide.summary) {
|
|
205
|
+
input = `${data}Summary`;
|
|
206
|
+
const groups = [...new Set([...groupBy, color?.field, strokeDash?.field].filter(Boolean))];
|
|
207
|
+
next = next.createSummaryData({ id: input, source,
|
|
208
|
+
...(groups.length ? { groupBy: groups } : {}),
|
|
209
|
+
aggregates: wide.fields.filter(field => !groups.includes(field)).map(field => ({ op: wide.summary, field, as: field })), missing: "error" });
|
|
210
|
+
ownedData.push(input);
|
|
211
|
+
}
|
|
212
|
+
const folded = wide.normalize ? `${data}Fold` : data;
|
|
213
|
+
next = next.createFoldData({ id: folded, source: input, fields: wide.fields, as: wide.as });
|
|
214
|
+
ownedData.push(folded);
|
|
215
|
+
const value = wide.normalize ? `${wide.as.value}Normalized` : wide.as.value;
|
|
216
|
+
if (wide.normalize) {
|
|
217
|
+
next = next.createNormalizedData({ id: data, source: folded, field: wide.as.value,
|
|
218
|
+
as: value, groupBy: wide.as.key, method: "minmax", zeroDenominator: "zero" });
|
|
219
|
+
ownedData.push(data);
|
|
220
|
+
}
|
|
221
|
+
validateRadarRows(findDataset(next, data).values, { category: wide.as.key,
|
|
222
|
+
value, groupBy, order: wide.fields }, operation);
|
|
223
|
+
return { next, data, value, ownedData };
|
|
224
|
+
}
|
|
225
|
+
|
|
192
226
|
export const createRadarPlot = /* @__PURE__ */ action(
|
|
193
227
|
{
|
|
194
228
|
op: "createRadarPlot",
|
|
@@ -240,6 +274,7 @@ export const createRadarPlot = /* @__PURE__ */ action(
|
|
|
240
274
|
let category;
|
|
241
275
|
let radarValue;
|
|
242
276
|
let order;
|
|
277
|
+
let radarPlot;
|
|
243
278
|
if (longForm) {
|
|
244
279
|
category = normalizeCategory(args.category, operation);
|
|
245
280
|
radarValue = normalizeValue(args.value, operation);
|
|
@@ -252,7 +287,7 @@ export const createRadarPlot = /* @__PURE__ */ action(
|
|
|
252
287
|
}, operation);
|
|
253
288
|
} else {
|
|
254
289
|
const wide = normalizeWide(args.wide, id, operation);
|
|
255
|
-
if (sourceRows.length > 1 && groupBy.length === 0) {
|
|
290
|
+
if (sourceRows.length > 1 && groupBy.length === 0 && !wide.summary) {
|
|
256
291
|
throw new Error(`${operation} wide rows require groupBy as series identity.`);
|
|
257
292
|
}
|
|
258
293
|
for (const field of wide.fields) readQuantitativeField(sourceRows, field);
|
|
@@ -271,21 +306,14 @@ export const createRadarPlot = /* @__PURE__ */ action(
|
|
|
271
306
|
);
|
|
272
307
|
}
|
|
273
308
|
data = `${id}FoldData`;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
fields: wide.fields,
|
|
278
|
-
as: wide.as
|
|
279
|
-
});
|
|
280
|
-
const foldedRows = findDataset(next, data).values;
|
|
309
|
+
const prepared = prepareWideData(next, { source, wide, groupBy: seriesFields,
|
|
310
|
+
color, strokeDash, data, operation });
|
|
311
|
+
next = prepared.next;
|
|
281
312
|
category = { field: wide.as.key, fieldType: "nominal" };
|
|
282
|
-
radarValue = { field:
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
groupBy: seriesFields,
|
|
287
|
-
order
|
|
288
|
-
}, operation);
|
|
313
|
+
radarValue = { field: prepared.value, fieldType: "quantitative",
|
|
314
|
+
...(wide.normalize ? { scale: { domain: [0, 1], nice: false } } : {}) };
|
|
315
|
+
radarPlot = { source, wide, ownedData: prepared.ownedData };
|
|
316
|
+
|
|
289
317
|
}
|
|
290
318
|
|
|
291
319
|
category = scaleWithOrder(category, order, operation);
|
|
@@ -303,7 +331,8 @@ export const createRadarPlot = /* @__PURE__ */ action(
|
|
|
303
331
|
});
|
|
304
332
|
return next._withMarkConfig(id, {
|
|
305
333
|
...next.markConfigs[id],
|
|
306
|
-
compositionRole: "radar"
|
|
334
|
+
compositionRole: "radar",
|
|
335
|
+
...(radarPlot ? { radarPlot } : {})
|
|
307
336
|
});
|
|
308
337
|
}
|
|
309
338
|
);
|
|
@@ -316,30 +345,34 @@ export const editRadarPlot = /* @__PURE__ */ action(
|
|
|
316
345
|
function (args = {}) {
|
|
317
346
|
const operation = "editRadarPlot";
|
|
318
347
|
validateFacadeOptions(args, ["target", "wide"], operation);
|
|
319
|
-
validateOptionObject(args.wide, ["fields"], `${operation} wide`);
|
|
348
|
+
validateOptionObject(args.wide, ["fields", "summary", "normalize"], `${operation} wide`);
|
|
320
349
|
const owner = resolveEligibleLayer(this, {
|
|
321
350
|
target: args.target,
|
|
322
351
|
label: "wide Radar plot",
|
|
323
352
|
predicate: layer => this.markConfigs[layer.id]?.compositionRole === "radar" &&
|
|
324
|
-
|
|
353
|
+
(this.markConfigs[layer.id]?.radarPlot !== undefined ||
|
|
354
|
+
findDataset(this, layer.data)?.transform?.[0]?.type === "fold")
|
|
325
355
|
});
|
|
326
356
|
const previous = findDataset(this, owner.data);
|
|
327
|
-
const
|
|
328
|
-
const
|
|
329
|
-
const
|
|
357
|
+
const stored = this.markConfigs[owner.id].radarPlot;
|
|
358
|
+
const prior = stored?.wide ?? previous.transform[0];
|
|
359
|
+
const source = findDataset(this, stored?.source ?? previous.source);
|
|
360
|
+
const wide = normalizeWide({ summary: prior.summary, normalize: prior.normalize, ...args.wide, as: prior.as,
|
|
361
|
+
fields: args.wide.fields }, owner.id, operation, 2);
|
|
330
362
|
for (const field of wide.fields) readQuantitativeField(source.values, field);
|
|
331
|
-
const transform = normalizeFoldTransform(wide, previous.id);
|
|
332
|
-
const rows = deriveFoldRows(source.values, transform);
|
|
333
363
|
const group = owner.encoding.group;
|
|
334
364
|
const groupBy = group?.fields ?? (group?.field ? [group.field] : []);
|
|
335
|
-
validateRadarRows(rows, { category: prior.as.key, value: prior.as.value,
|
|
336
|
-
groupBy, order: wide.fields }, operation);
|
|
337
365
|
const revision = planDerivedDataRevision(this, {
|
|
338
366
|
owner: owner.id, role: "RadarData", previous: previous.id, consumers: [owner.id]
|
|
339
367
|
});
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
368
|
+
const prepared = prepareWideData(this, { source: source.id, wide, groupBy,
|
|
369
|
+
color: owner.encoding.color, strokeDash: owner.encoding.strokeDash,
|
|
370
|
+
data: revision.id, operation });
|
|
371
|
+
let next = prepared.next.rebindLayerData(revision.rebinds[0]);
|
|
372
|
+
if (Boolean(prior.normalize) !== Boolean(wide.normalize)) {
|
|
373
|
+
next = next.encodeR({ target: owner.id, field: prepared.value,
|
|
374
|
+
fieldType: "quantitative", scale: { domain: wide.normalize ? [0, 1] : "auto", nice: false } });
|
|
375
|
+
}
|
|
343
376
|
const theta = owner.encoding.theta.scale;
|
|
344
377
|
const revisedValues = config => {
|
|
345
378
|
const full = config.inferredValues || !Array.isArray(config.values) ||
|
|
@@ -359,7 +392,11 @@ export const editRadarPlot = /* @__PURE__ */ action(
|
|
|
359
392
|
next = next.editThetaScale({ target: owner.id, domain: wide.fields });
|
|
360
393
|
next = applyLayerDataRematerialization(next, owner.id);
|
|
361
394
|
if (next.markConfigs[owner.id]?.closed !== true) next = next.editLineMark({ target: owner.id, closed: true });
|
|
362
|
-
|
|
363
|
-
|
|
395
|
+
next = next._withMarkConfig(owner.id, { ...next.markConfigs[owner.id],
|
|
396
|
+
radarPlot: { source: source.id, wide, ownedData: prepared.ownedData } });
|
|
397
|
+
for (const id of [...(stored?.ownedData ?? [previous.id])].reverse()) {
|
|
398
|
+
next = next.releaseDerivedData({ id });
|
|
399
|
+
}
|
|
400
|
+
return next._withContext({ currentMark: owner.id, currentData: source.id });
|
|
364
401
|
}
|
|
365
402
|
);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { prepareFacadeMissingData } from './missing.js';
|
|
1
2
|
import { action } from "../../core/action.js";
|
|
2
3
|
import { isPlainObject } from "../../core/immutable.js";
|
|
3
4
|
import { validateUserId } from "../../core/identifiers.js";
|
|
@@ -5,6 +6,7 @@ import { validateOptionObject } from "../../core/validation.js";
|
|
|
5
6
|
import { findDataset } from "../../selectors/datasets.js";
|
|
6
7
|
import { findLayer } from "../../selectors/layers.js";
|
|
7
8
|
import { findSemanticScale } from "../../selectors/scales.js";
|
|
9
|
+
import { resolveColorScaleDefinition } from "../scales/definitions.js";
|
|
8
10
|
import { retainStatisticalDependents, restoreStatisticalDependents } from "../marks/dependentReferences.js";
|
|
9
11
|
import { resolveViolinRoles } from "../violinPlots/create.js";
|
|
10
12
|
import {
|
|
@@ -18,11 +20,11 @@ import {
|
|
|
18
20
|
|
|
19
21
|
const CREATE_OPTIONS = Object.freeze([
|
|
20
22
|
"id", "data", "coordinate", "category", "value", "orientation", "side",
|
|
21
|
-
"density", "summary", "points", "color", "guides"
|
|
23
|
+
"density", "summary", "points", "color", "guides", "missing"
|
|
22
24
|
]);
|
|
23
25
|
const EDIT_OPTIONS = Object.freeze([
|
|
24
26
|
"target", "data", "category", "value", "orientation", "side", "density",
|
|
25
|
-
"summary", "points", "color"
|
|
27
|
+
"summary", "points", "color", "missing"
|
|
26
28
|
]);
|
|
27
29
|
const DENSITY_OPTIONS = Object.freeze([
|
|
28
30
|
"bandwidth", "extent", "steps", "kernel", "normalization", "width", "area"
|
|
@@ -275,6 +277,12 @@ function addPoints(program, config, positions) {
|
|
|
275
277
|
}
|
|
276
278
|
|
|
277
279
|
function buildRaincloud(program, config) {
|
|
280
|
+
const original = config;
|
|
281
|
+
const prepared = prepareFacadeMissingData(program, { id: config.id, source: config.source,
|
|
282
|
+
fields: [config.category.field, config.value.field, config.color?.field,
|
|
283
|
+
config.points?.size?.field, config.points?.shape?.field], missing: config.missing });
|
|
284
|
+
program = prepared.program;
|
|
285
|
+
config = { ...config, source: prepared.data };
|
|
278
286
|
const positions = rolePositions(config.category, config.value, config.orientation);
|
|
279
287
|
let next = program;
|
|
280
288
|
if (config.density !== false) {
|
|
@@ -308,7 +316,7 @@ function buildRaincloud(program, config) {
|
|
|
308
316
|
.filter(id => findLayer(next, id) !== undefined);
|
|
309
317
|
return next._withMarkConfig(ownerId, {
|
|
310
318
|
...next.markConfigs[ownerId],
|
|
311
|
-
raincloudPlot: { ...
|
|
319
|
+
raincloudPlot: { ...original, ownedData: prepared.ownedData, ownerId, ownedChildIds, materialized: true }
|
|
312
320
|
})._withContext({ currentMark: ownerId, currentData: config.source });
|
|
313
321
|
}
|
|
314
322
|
|
|
@@ -317,7 +325,11 @@ function alignExistingRoleScales(program, config) {
|
|
|
317
325
|
for (const encoding of [config.category, config.value, config.color]) {
|
|
318
326
|
const scale = encoding?.scale;
|
|
319
327
|
if (!isPlainObject(scale) || findSemanticScale(next, scale.id) === undefined) continue;
|
|
320
|
-
|
|
328
|
+
// Rebuilding removes the owned consumers first. Normalize the known color
|
|
329
|
+
// role before editing its retained resource, so palette does not depend on
|
|
330
|
+
// a temporarily absent consumer to infer that role.
|
|
331
|
+
const definition = encoding === config.color ? resolveColorScaleDefinition(next, scale) : scale;
|
|
332
|
+
const patch = Object.fromEntries(Object.entries(definition).filter(
|
|
321
333
|
([property, value]) => property !== "id" && value !== undefined
|
|
322
334
|
));
|
|
323
335
|
if (Object.keys(patch).length > 0) next = next.editScale({ id: scale.id, ...patch });
|
|
@@ -358,6 +370,7 @@ function normalizeCreate(program, args, operation) {
|
|
|
358
370
|
return {
|
|
359
371
|
id,
|
|
360
372
|
source,
|
|
373
|
+
missing: args.missing ?? "error",
|
|
361
374
|
...(args.coordinate === undefined ? {} : { coordinate: args.coordinate }),
|
|
362
375
|
category,
|
|
363
376
|
value,
|
|
@@ -419,6 +432,7 @@ export const editRaincloudPlot = /* @__PURE__ */ action(
|
|
|
419
432
|
const candidateArgs = {
|
|
420
433
|
id: current.id,
|
|
421
434
|
data: Object.hasOwn(args, "data") ? args.data : current.source,
|
|
435
|
+
missing: args.missing ?? current.missing,
|
|
422
436
|
...(current.coordinate === undefined ? {} : { coordinate: current.coordinate }),
|
|
423
437
|
category,
|
|
424
438
|
value: Object.hasOwn(args, "value") ? args.value : current.value,
|
|
@@ -24,7 +24,7 @@ const TICK_OPTIONS = Object.freeze([
|
|
|
24
24
|
"clip", "length", "stroke", "strokeWidth", "opacity", ...STROKE_STYLE_PROPERTIES
|
|
25
25
|
]);
|
|
26
26
|
const STRIP_OPTIONS = Object.freeze([
|
|
27
|
-
"id", "data", "coordinate", "x", "y", "color", "size", "shape", "point", "jitter", "guides"
|
|
27
|
+
"id", "data", "coordinate", "x", "y", "color", "size", "shape", "point", "jitter", "guides", "missing"
|
|
28
28
|
]);
|
|
29
29
|
const POINT_OPTIONS = Object.freeze([
|
|
30
30
|
"clip", "radius", "shape", "fill", "opacity", "stroke", "strokeWidth",
|
|
@@ -260,7 +260,7 @@ export const createStripPlot = /* @__PURE__ */ action(
|
|
|
260
260
|
operation
|
|
261
261
|
);
|
|
262
262
|
|
|
263
|
-
let next = this.createPointMark({ id, data, ...point });
|
|
263
|
+
let next = this.createPointMark({ id, data, ...point, ...(args.missing === undefined ? {} : { missing: args.missing }) });
|
|
264
264
|
if (args.coordinate !== undefined) {
|
|
265
265
|
next = next.createCoordinate({ id: args.coordinate, type: "cartesian", layers: [id] });
|
|
266
266
|
}
|
|
@@ -173,6 +173,23 @@ export function applyFacadeGuides(program, guides, target, explicitGuides = guid
|
|
|
173
173
|
return fulfillFacadeGuides(program, guides, target, explicitGuides);
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
// Rebuilding a facade must not resurrect an axis removed after construction.
|
|
177
|
+
// This also lets callers fit a changed variable by removing its old axis and
|
|
178
|
+
// recreating it against the new domain through ordinary public guide actions.
|
|
179
|
+
export function retainFacadeAxisPresence(program, owner, guides) {
|
|
180
|
+
if (guides === false) return false;
|
|
181
|
+
const axes = { ...guides?.axes };
|
|
182
|
+
for (const channel of ['x', 'y']) {
|
|
183
|
+
if (!owner.encoding[channel]?.scale) continue;
|
|
184
|
+
const present = Object.entries(program.semanticSpec.guides.axis ?? {}).some(([id, axis]) =>
|
|
185
|
+
(axis.channel ?? id) === channel && axis.scale === owner.encoding[channel].scale &&
|
|
186
|
+
(axis.coordinate ?? owner.coordinate) === owner.coordinate);
|
|
187
|
+
if (!present) axes[channel] = false;
|
|
188
|
+
else axes[channel] ??= {};
|
|
189
|
+
}
|
|
190
|
+
return { ...guides, axes };
|
|
191
|
+
}
|
|
192
|
+
|
|
176
193
|
export function normalizeArcCategory(value, operation) {
|
|
177
194
|
const label = `${operation} category`;
|
|
178
195
|
const category = normalizeFieldEncoding(value, label);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeCompositionLegends } from "../../core/compositionState.js";
|
|
2
|
+
import { normalizeCompositionShares } from "../../core/compositionScales.js";
|
|
1
3
|
import { resolveGraphicBounds } from "../../layout/canvas.js";
|
|
2
4
|
import { inheritTextMetrics } from "../textMetrics/index.js";
|
|
3
5
|
import { action, closedAction } from "../../core/action.js";
|
|
@@ -17,10 +19,10 @@ import { materializeFacetGraphics } from "../../materialization/facets.js";
|
|
|
17
19
|
import { replayInheritedThemeFrames } from "../theme/composition.js";
|
|
18
20
|
|
|
19
21
|
const CONCAT_OPTIONS = Object.freeze([
|
|
20
|
-
"id", "programs", "gap", "align", "padding"
|
|
22
|
+
"id", "programs", "gap", "align", "padding", "share", "legends"
|
|
21
23
|
]);
|
|
22
24
|
const LAYOUT_EDIT_OPTIONS = Object.freeze([
|
|
23
|
-
"columns", "gap", "spacing", "topLane", "align", "padding"
|
|
25
|
+
"columns", "gap", "spacing", "topLane", "align", "padding", "legends"
|
|
24
26
|
]);
|
|
25
27
|
const REPLACEMENT_OPTIONS = Object.freeze(["target", "program"]);
|
|
26
28
|
const INSERTION_OPTIONS = Object.freeze(["id", "program", "before", "after"]);
|
|
@@ -94,7 +96,7 @@ function normalizeCompositionInput(args, direction) {
|
|
|
94
96
|
direction,
|
|
95
97
|
children: entries.map(childDescriptor),
|
|
96
98
|
...(Object.hasOwn(args, "gap") ? { gap: args.gap } : {}),
|
|
97
|
-
...(Object.hasOwn(args, "align") ? { align: args.align } : {}),
|
|
99
|
+
...(Object.hasOwn(args, "align") ? { align: args.align === "plot" ? "start" : args.align } : {}),
|
|
98
100
|
...(Object.hasOwn(args, "padding") ? { padding: args.padding } : {})
|
|
99
101
|
});
|
|
100
102
|
return {
|
|
@@ -106,8 +108,10 @@ function normalizeCompositionInput(args, direction) {
|
|
|
106
108
|
direction,
|
|
107
109
|
children: ids,
|
|
108
110
|
gap: layout.gap,
|
|
109
|
-
align: layout.align,
|
|
110
|
-
padding: layout.padding
|
|
111
|
+
align: args.align === "plot" ? "plot" : layout.align,
|
|
112
|
+
padding: layout.padding,
|
|
113
|
+
...(Object.hasOwn(args, "legends") ? { legends: normalizeCompositionLegends(args.legends) } : {}),
|
|
114
|
+
...(args.share === undefined ? {} : { share: normalizeCompositionShares(args.share) })
|
|
111
115
|
}
|
|
112
116
|
};
|
|
113
117
|
}
|
|
@@ -175,6 +179,7 @@ const editCompositionLayout = /* @__PURE__ */ closedAction(
|
|
|
175
179
|
throw new TypeError("editCompositionLayout requires at least one layout option.");
|
|
176
180
|
}
|
|
177
181
|
const current = this.compositionSpec;
|
|
182
|
+
if (Object.hasOwn(args, "legends") && current.type === "facet") throw new Error("Facet uses editFacetGuides for legend placement.");
|
|
178
183
|
if (current.type !== "facet" && Object.hasOwn(args, "columns")) {
|
|
179
184
|
throw new Error(
|
|
180
185
|
"editCompositionLayout columns is available only on a facet composition."
|
|
@@ -218,7 +223,7 @@ const editCompositionLayout = /* @__PURE__ */ closedAction(
|
|
|
218
223
|
program: this.children[id]
|
|
219
224
|
})),
|
|
220
225
|
gap: option(args, "gap", current.gap),
|
|
221
|
-
align: option(args, "align", current.align),
|
|
226
|
+
align: option(args, "align", current.align) === "plot" ? "start" : option(args, "align", current.align),
|
|
222
227
|
padding
|
|
223
228
|
});
|
|
224
229
|
}
|
|
@@ -227,11 +232,12 @@ const editCompositionLayout = /* @__PURE__ */ closedAction(
|
|
|
227
232
|
children: this.children,
|
|
228
233
|
compositionSpec: {
|
|
229
234
|
...currentLayoutSpec,
|
|
235
|
+
...(Object.hasOwn(args, "legends") ? { legends: normalizeCompositionLegends(args.legends) } : {}),
|
|
230
236
|
...(current.type === "facet" ? { columns: layout.columns,
|
|
231
237
|
...(layout.spacing !== undefined ? { spacing: layout.spacing } : current.spacing !== undefined ? { spacing: "canvas" } : {}),
|
|
232
238
|
...(layout.topLane === undefined ? {} : { topLane: layout.topLane }) } : {}),
|
|
233
239
|
gap: layout.gap,
|
|
234
|
-
align: layout.align,
|
|
240
|
+
align: current.type !== "facet" && option(args, "align", current.align) === "plot" ? "plot" : layout.align,
|
|
235
241
|
padding: layout.padding
|
|
236
242
|
}
|
|
237
243
|
});
|
|
@@ -376,7 +382,18 @@ const reorderCompositionChildren = /* @__PURE__ */ closedAction(
|
|
|
376
382
|
}
|
|
377
383
|
);
|
|
378
384
|
|
|
385
|
+
const editCompositionScales = /* @__PURE__ */ closedAction({
|
|
386
|
+
op: "editCompositionScales", description: "Edit shared scale policies across adjacent chart views.", scope: "composition"
|
|
387
|
+
}, ["share"], function ({ share } = {}) {
|
|
388
|
+
requireConcat(this, "editCompositionScales");
|
|
389
|
+
if (share === undefined || !Object.keys(share).length) throw new Error("editCompositionScales requires a nonempty share edit.");
|
|
390
|
+
const patch = normalizeCompositionShares(share);
|
|
391
|
+
return applyCompositionState(this, { children: this.children,
|
|
392
|
+
compositionSpec: { ...this.compositionSpec, share: { ...this.compositionSpec.share, ...patch } } });
|
|
393
|
+
});
|
|
394
|
+
|
|
379
395
|
export function registerCompositionActions(ProgramClass) {
|
|
396
|
+
ProgramClass.prototype.editCompositionScales = editCompositionScales;
|
|
380
397
|
ProgramClass.prototype.useProgram = useProgram;
|
|
381
398
|
ProgramClass.prototype.materializeComposition = materializeComposition;
|
|
382
399
|
ProgramClass.prototype.editCompositionLayout = editCompositionLayout;
|