ggaction 0.0.11 → 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 +80 -0
- package/README.md +3 -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 +33 -27
- package/src/actions/encodings/parallel.js +2 -8
- package/src/actions/encodings/position/apply.js +40 -4
- package/src/actions/encodings/position/index.js +43 -5
- package/src/actions/encodings/position/policies/arc.js +37 -2
- 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 +79 -24
- 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 +153 -11
- package/src/actions/errorBars/edit.js +365 -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 +138 -14
- 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 +60 -4
- 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/core/vocabulary.js +2 -2
- package/src/grammar/aggregate.js +66 -23
- package/src/grammar/arcs.js +213 -65
- 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/positionCompatibility.js +1 -1
- 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 +9 -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/ordinal.js +23 -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 +86 -23
- 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/facetGuides/legacyCategorical.js +7 -1
- package/src/materialization/facetGuides/placement.js +16 -1
- package/src/materialization/facetGuides/preparation.js +26 -1
- package/src/materialization/facets.js +11 -2
- 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 +30 -7
- package/src/materialization/marks/grouping.js +52 -0
- package/src/materialization/marks/index.js +63 -25
- package/src/materialization/marks/policies.js +2 -0
- package/src/materialization/rect.js +33 -33
- package/src/materialization/rowEncoding.js +70 -14
- 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/bar.js +0 -43
- package/src/materialization/scales/policies/binnedPosition.js +7 -4
- package/src/materialization/scales/policies/offset.js +54 -0
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +48 -18
- 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 +1327 -124
- package/src/materialization/bars/grouped.js +0 -144
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { action } from "../../core/action.js";
|
|
2
2
|
import { freezeOwned, isPlainObject } from "../../core/immutable.js";
|
|
3
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
4
|
import {
|
|
4
5
|
validateNonEmptyString,
|
|
5
6
|
validateNonNegativeFinite,
|
|
6
7
|
validateOptionObject,
|
|
7
8
|
validatePositiveFinite
|
|
8
9
|
} from "../../core/validation.js";
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
resolveFacetDefinition,
|
|
12
|
+
resolveFacetGridDefinition
|
|
13
|
+
} from "../../grammar/facets/index.js";
|
|
10
14
|
import {
|
|
11
15
|
FACET_SCALE_CHANNELS,
|
|
12
16
|
normalizeFacetScalePolicies
|
|
@@ -16,14 +20,26 @@ import { compositionChildDescriptor } from
|
|
|
16
20
|
"../../materialization/composition.js";
|
|
17
21
|
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from "../../theme/defaults.js";
|
|
18
22
|
import { deriveFacetChildren } from "./derive.js";
|
|
23
|
+
import { resolveFacetChildrenScales } from "./derive.js";
|
|
19
24
|
import { replayDerivedData } from "./replay.js";
|
|
20
25
|
import { composeFacetGuides } from "./guides.js";
|
|
21
26
|
import { applyCompositionState } from "../composition/actions.js";
|
|
27
|
+
import { findDataset } from "../../selectors/datasets.js";
|
|
28
|
+
import { findLayer } from "../../selectors/layers.js";
|
|
22
29
|
|
|
23
30
|
const FACET_OPTIONS = Object.freeze([
|
|
24
|
-
"id", "field", "data", "columns", "gap", "align", "padding", "scales",
|
|
31
|
+
"id", "field", "data", "values", "columns", "gap", "align", "padding", "scales",
|
|
25
32
|
"guides"
|
|
26
33
|
]);
|
|
34
|
+
const FACET_GRID_OPTIONS = Object.freeze([
|
|
35
|
+
"id", "data", "rows", "columns", "combinations", "gap", "align",
|
|
36
|
+
"padding", "scales", "guides"
|
|
37
|
+
]);
|
|
38
|
+
const REPEAT_OPTIONS = Object.freeze([
|
|
39
|
+
"id", "target", "channel", "fields", "columns", "gap", "align",
|
|
40
|
+
"padding", "scales", "guides"
|
|
41
|
+
]);
|
|
42
|
+
const SOURCE_EDIT_OPTIONS = Object.freeze(["program"]);
|
|
27
43
|
const GUIDE_OPTIONS = Object.freeze(["axes", "legend"]);
|
|
28
44
|
const HEADER_OPTIONS = Object.freeze([
|
|
29
45
|
"fontSize", "fontFamily", "fontWeight", "color", "offset"
|
|
@@ -57,18 +73,32 @@ function requireFacetProgram(program, operation) {
|
|
|
57
73
|
}
|
|
58
74
|
}
|
|
59
75
|
|
|
76
|
+
function usedFacetScalePolicies(program, policies) {
|
|
77
|
+
return Object.fromEntries(FACET_SCALE_CHANNELS.flatMap(channel =>
|
|
78
|
+
program.semanticSpec.layers.some(
|
|
79
|
+
layer => layer.encoding?.[channel]?.scale !== undefined
|
|
80
|
+
) ? [[channel, policies[channel]]] : []
|
|
81
|
+
));
|
|
82
|
+
}
|
|
83
|
+
|
|
60
84
|
function facetUnitTemplate(program) {
|
|
61
|
-
const
|
|
85
|
+
const seedId = program.compositionSpec.children.find(id =>
|
|
86
|
+
program.children[id]?.semanticSpec.layers.length > 0
|
|
87
|
+
) ?? program.compositionSpec.children[0];
|
|
88
|
+
const seed = program.children[seedId];
|
|
62
89
|
if (seed === undefined) {
|
|
63
90
|
throw new Error(`Facet "${program.compositionSpec.id}" requires a retained child.`);
|
|
64
91
|
}
|
|
65
|
-
const { facets: _facets, ...unitConfigs } = program.materializationConfigs;
|
|
92
|
+
const { facets: _facets, guides: _guides, ...unitConfigs } = program.materializationConfigs;
|
|
66
93
|
return new program.constructor({
|
|
67
94
|
semanticSpec: program.semanticSpec,
|
|
68
95
|
graphicSpec: seed.graphicSpec,
|
|
69
96
|
resolvedScales: program.resolvedScales,
|
|
70
97
|
materializationConfigs: freezeOwned({
|
|
71
98
|
...unitConfigs,
|
|
99
|
+
...(seed.materializationConfigs.guides === undefined
|
|
100
|
+
? {}
|
|
101
|
+
: { guides: seed.materializationConfigs.guides }),
|
|
72
102
|
canvas: seed.materializationConfigs.canvas
|
|
73
103
|
}),
|
|
74
104
|
children: {},
|
|
@@ -81,19 +111,48 @@ function facetUnitTemplate(program) {
|
|
|
81
111
|
|
|
82
112
|
function rederiveFacet(program, { scales, guides }) {
|
|
83
113
|
const current = program.compositionSpec;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
114
|
+
if (current.facet.repeat !== undefined) {
|
|
115
|
+
const template = facetUnitTemplate(program);
|
|
116
|
+
const definition = resolveRepeatDefinition(template, {
|
|
117
|
+
id: current.id,
|
|
118
|
+
...current.facet.repeat
|
|
119
|
+
});
|
|
120
|
+
const derived = deriveRepeatChildren(
|
|
121
|
+
template,
|
|
122
|
+
definition,
|
|
123
|
+
usedFacetScalePolicies(template, scales),
|
|
124
|
+
true
|
|
125
|
+
);
|
|
126
|
+
return applyCompositionState(program, {
|
|
127
|
+
children: derived.children,
|
|
128
|
+
compositionSpec: {
|
|
129
|
+
...current,
|
|
130
|
+
facet: { ...current.facet, scales, guides }
|
|
131
|
+
}
|
|
132
|
+
}, current.children);
|
|
133
|
+
}
|
|
134
|
+
const definition = current.facet.grid === undefined
|
|
135
|
+
? resolveFacetDefinition(program.semanticSpec, {
|
|
136
|
+
id: current.id,
|
|
137
|
+
data: current.facet.data,
|
|
138
|
+
field: current.facet.field,
|
|
139
|
+
values: current.facet.values
|
|
140
|
+
})
|
|
141
|
+
: resolveFacetGridDefinition(program.semanticSpec, {
|
|
142
|
+
id: current.id,
|
|
143
|
+
data: current.facet.data,
|
|
144
|
+
rows: current.facet.grid.rows,
|
|
145
|
+
columns: current.facet.grid.columns,
|
|
146
|
+
combinations: current.facet.grid.combinations
|
|
147
|
+
});
|
|
90
148
|
const request = Object.fromEntries(FACET_SCALE_CHANNELS.flatMap(channel =>
|
|
91
149
|
program.semanticSpec.layers.some(
|
|
92
150
|
layer => layer.encoding?.[channel]?.scale !== undefined
|
|
93
151
|
) ? [[channel, scales[channel]]] : []
|
|
94
152
|
));
|
|
95
153
|
const normalized = normalizeFacetScalePolicies(program.semanticSpec, request);
|
|
96
|
-
const
|
|
154
|
+
const template = facetUnitTemplate(program);
|
|
155
|
+
const derived = deriveFacetChildren(template, definition, {
|
|
97
156
|
closeInheritedAction: true,
|
|
98
157
|
stripTitle: true,
|
|
99
158
|
scales: request
|
|
@@ -106,7 +165,11 @@ function rederiveFacet(program, { scales, guides }) {
|
|
|
106
165
|
guides
|
|
107
166
|
}
|
|
108
167
|
};
|
|
109
|
-
|
|
168
|
+
let parent = program._withoutMaterializationConfig(["guides", "legend"]);
|
|
169
|
+
for (const [kind, config] of Object.entries(template.guideConfigs.legend ?? {})) {
|
|
170
|
+
parent = parent._withLegendConfig(kind, config);
|
|
171
|
+
}
|
|
172
|
+
return applyCompositionState(parent, {
|
|
110
173
|
children: derived.children,
|
|
111
174
|
compositionSpec
|
|
112
175
|
}, compositionSpec.children);
|
|
@@ -170,6 +233,242 @@ export const facet = action(
|
|
|
170
233
|
}
|
|
171
234
|
);
|
|
172
235
|
|
|
236
|
+
function resolveRepeatDefinition(program, args) {
|
|
237
|
+
if (!isPlainObject(args)) {
|
|
238
|
+
throw new TypeError("repeatCharts options must be a plain object.");
|
|
239
|
+
}
|
|
240
|
+
const id = validateUserId(args.id ?? "repeat", "Repeat id");
|
|
241
|
+
if (!["x", "y"].includes(args.channel)) {
|
|
242
|
+
throw new Error('repeatCharts channel must be "x" or "y".');
|
|
243
|
+
}
|
|
244
|
+
if (!Array.isArray(args.fields) || args.fields.length === 0 ||
|
|
245
|
+
args.fields.some(field => typeof field !== "string" || field.length === 0)) {
|
|
246
|
+
throw new TypeError("repeatCharts fields must be a non-empty array of field names.");
|
|
247
|
+
}
|
|
248
|
+
if (new Set(args.fields).size !== args.fields.length) {
|
|
249
|
+
throw new Error("repeatCharts fields must be unique.");
|
|
250
|
+
}
|
|
251
|
+
const eligible = program.semanticSpec.layers.filter(layer =>
|
|
252
|
+
layer.encoding?.x?.scale !== undefined &&
|
|
253
|
+
layer.encoding?.y?.scale !== undefined &&
|
|
254
|
+
layer.encoding?.[args.channel]?.field !== undefined &&
|
|
255
|
+
["point", "line", "area", "bar", "rule", "tick", "rect"].includes(layer.mark?.type)
|
|
256
|
+
);
|
|
257
|
+
let target;
|
|
258
|
+
if (args.target !== undefined) {
|
|
259
|
+
target = validateUserId(args.target, "Repeat target");
|
|
260
|
+
if (!eligible.some(layer => layer.id === target)) {
|
|
261
|
+
throw new Error(`repeatCharts target "${target}" is not an eligible Cartesian mark.`);
|
|
262
|
+
}
|
|
263
|
+
} else if (eligible.length === 1) {
|
|
264
|
+
target = eligible[0].id;
|
|
265
|
+
} else {
|
|
266
|
+
throw new Error(
|
|
267
|
+
eligible.length === 0
|
|
268
|
+
? "repeatCharts requires one complete Cartesian mark."
|
|
269
|
+
: "repeatCharts target is ambiguous; provide target."
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
if (program.semanticSpec.layers.length !== 1) {
|
|
273
|
+
throw new Error("repeatCharts currently supports one direct Cartesian mark only.");
|
|
274
|
+
}
|
|
275
|
+
const config = program.markConfigs[target] ?? {};
|
|
276
|
+
const composite = [
|
|
277
|
+
"boxPlot", "gradientPlot", "violinPlot", "regressionPlot", "endpointPlot",
|
|
278
|
+
"ecdfPlot", "raincloudPlot", "intervalPlot"
|
|
279
|
+
].find(key => config[key] !== undefined);
|
|
280
|
+
if (composite !== undefined) {
|
|
281
|
+
throw new Error(`repeatCharts does not replace the ${composite} composite role.`);
|
|
282
|
+
}
|
|
283
|
+
const layer = findLayer(program, target);
|
|
284
|
+
const dataset = findDataset(program, layer.data);
|
|
285
|
+
if (dataset?.transform?.length > 0) {
|
|
286
|
+
throw new Error("repeatCharts does not rewrite a derived dataset dependency.");
|
|
287
|
+
}
|
|
288
|
+
const encoding = layer.encoding[args.channel];
|
|
289
|
+
return {
|
|
290
|
+
id,
|
|
291
|
+
target,
|
|
292
|
+
channel: args.channel,
|
|
293
|
+
fields: [...args.fields],
|
|
294
|
+
data: layer.data,
|
|
295
|
+
encoding,
|
|
296
|
+
cells: args.fields.map((field, index) => ({
|
|
297
|
+
id: `${id}-field-${index + 1}`,
|
|
298
|
+
field,
|
|
299
|
+
value: field
|
|
300
|
+
}))
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function repeatEncodingArgs(definition, field) {
|
|
305
|
+
const encoding = definition.encoding;
|
|
306
|
+
return {
|
|
307
|
+
target: definition.target,
|
|
308
|
+
field,
|
|
309
|
+
fieldType: encoding.fieldType,
|
|
310
|
+
...(encoding.temporalUnit === undefined ? {} : { temporalUnit: encoding.temporalUnit }),
|
|
311
|
+
...(encoding.aggregate === undefined ? {} : { aggregate: encoding.aggregate }),
|
|
312
|
+
...(encoding.bin === undefined ? {} : { bin: encoding.bin }),
|
|
313
|
+
...(encoding.stack === undefined ? {} : { stack: encoding.stack }),
|
|
314
|
+
...(encoding.weight === undefined ? {} : { weight: encoding.weight }),
|
|
315
|
+
scale: { id: encoding.scale }
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function deriveRepeatChildren(base, definition, scales, closeInheritedAction) {
|
|
320
|
+
const template = base.semanticSpec.title.text === undefined
|
|
321
|
+
? base
|
|
322
|
+
: base.removeTitle();
|
|
323
|
+
const independentlyResolved = Object.fromEntries(definition.cells.map(cell => [
|
|
324
|
+
cell.id,
|
|
325
|
+
template[definition.channel === "x" ? "encodeX" : "encodeY"](
|
|
326
|
+
repeatEncodingArgs(definition, cell.field)
|
|
327
|
+
)
|
|
328
|
+
]));
|
|
329
|
+
return resolveFacetChildrenScales(
|
|
330
|
+
template,
|
|
331
|
+
definition.cells.map(cell => cell.id),
|
|
332
|
+
independentlyResolved,
|
|
333
|
+
scales,
|
|
334
|
+
closeInheritedAction
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export const repeatCharts = action(
|
|
339
|
+
{
|
|
340
|
+
op: "repeatCharts",
|
|
341
|
+
description: "Repeat one direct Cartesian chart across an ordered field list."
|
|
342
|
+
},
|
|
343
|
+
function (args = {}) {
|
|
344
|
+
validateOptionObject(args, REPEAT_OPTIONS, "repeatCharts");
|
|
345
|
+
const guides = normalizeGuides(args.guides);
|
|
346
|
+
if (guides.axes === "outer") {
|
|
347
|
+
throw new Error("repeatCharts does not promote axes across different repeated fields.");
|
|
348
|
+
}
|
|
349
|
+
const definition = resolveRepeatDefinition(this, args);
|
|
350
|
+
const requestedScales = {
|
|
351
|
+
...(args.scales ?? {}),
|
|
352
|
+
[definition.channel]: args.scales?.[definition.channel] ?? "independent"
|
|
353
|
+
};
|
|
354
|
+
const scalePolicies = normalizeFacetScalePolicies(
|
|
355
|
+
this.semanticSpec,
|
|
356
|
+
requestedScales
|
|
357
|
+
);
|
|
358
|
+
const derived = deriveRepeatChildren(
|
|
359
|
+
this,
|
|
360
|
+
definition,
|
|
361
|
+
requestedScales,
|
|
362
|
+
true
|
|
363
|
+
);
|
|
364
|
+
const preflight = resolveFacetLayout({
|
|
365
|
+
children: definition.cells.map(cell => ({
|
|
366
|
+
...compositionChildDescriptor(cell.id, derived.children[cell.id]),
|
|
367
|
+
value: cell.value
|
|
368
|
+
})),
|
|
369
|
+
...(Object.hasOwn(args, "columns") ? { columns: args.columns } : {}),
|
|
370
|
+
...(Object.hasOwn(args, "gap") ? { gap: args.gap } : {}),
|
|
371
|
+
...(Object.hasOwn(args, "align") ? { align: args.align } : {}),
|
|
372
|
+
...(Object.hasOwn(args, "padding") ? { padding: args.padding } : {}),
|
|
373
|
+
sharedLegend: guides.legend === "shared"
|
|
374
|
+
});
|
|
375
|
+
const compositionSpec = {
|
|
376
|
+
id: definition.id,
|
|
377
|
+
type: "facet",
|
|
378
|
+
children: definition.cells.map(cell => cell.id),
|
|
379
|
+
columns: preflight.columns,
|
|
380
|
+
gap: preflight.gap,
|
|
381
|
+
align: preflight.align,
|
|
382
|
+
padding: preflight.padding,
|
|
383
|
+
facet: {
|
|
384
|
+
data: definition.data,
|
|
385
|
+
values: definition.fields,
|
|
386
|
+
repeat: {
|
|
387
|
+
target: definition.target,
|
|
388
|
+
channel: definition.channel,
|
|
389
|
+
fields: definition.fields
|
|
390
|
+
},
|
|
391
|
+
scales: scalePolicies.channels,
|
|
392
|
+
guides
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
return applyCompositionState(
|
|
396
|
+
this._withMaterializationConfig(["facets", definition.id], {
|
|
397
|
+
headers: DEFAULT_HEADERS
|
|
398
|
+
}),
|
|
399
|
+
{ children: derived.children, compositionSpec },
|
|
400
|
+
compositionSpec.children
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
export const facetGrid = action(
|
|
406
|
+
{
|
|
407
|
+
op: "facetGrid",
|
|
408
|
+
description: "Repeat one direct-source Cartesian chart across a row and column field grid."
|
|
409
|
+
},
|
|
410
|
+
function (args = {}) {
|
|
411
|
+
validateOptionObject(args, FACET_GRID_OPTIONS, "facetGrid");
|
|
412
|
+
const guides = normalizeGuides(args.guides);
|
|
413
|
+
const definition = resolveFacetGridDefinition(this.semanticSpec, args);
|
|
414
|
+
const scalePolicies = normalizeFacetScalePolicies(
|
|
415
|
+
this.semanticSpec,
|
|
416
|
+
args.scales ?? {}
|
|
417
|
+
);
|
|
418
|
+
const derived = deriveFacetChildren(this, definition, {
|
|
419
|
+
closeInheritedAction: true,
|
|
420
|
+
stripTitle: true,
|
|
421
|
+
scales: args.scales ?? {}
|
|
422
|
+
});
|
|
423
|
+
const preflight = resolveFacetLayout({
|
|
424
|
+
children: definition.cells.map(cell => ({
|
|
425
|
+
...compositionChildDescriptor(cell.id, derived.children[cell.id]),
|
|
426
|
+
value: cell.value,
|
|
427
|
+
row: cell.row,
|
|
428
|
+
column: cell.column
|
|
429
|
+
})),
|
|
430
|
+
columns: definition.grid.columns.values.length,
|
|
431
|
+
...(Object.hasOwn(args, "gap") ? { gap: args.gap } : {}),
|
|
432
|
+
...(Object.hasOwn(args, "align") ? { align: args.align } : {}),
|
|
433
|
+
...(Object.hasOwn(args, "padding") ? { padding: args.padding } : {}),
|
|
434
|
+
sharedLegend: guides.legend === "shared"
|
|
435
|
+
});
|
|
436
|
+
const compositionSpec = {
|
|
437
|
+
id: definition.id,
|
|
438
|
+
type: "facet",
|
|
439
|
+
children: definition.cells.map(cell => cell.id),
|
|
440
|
+
columns: preflight.columns,
|
|
441
|
+
gap: preflight.gap,
|
|
442
|
+
align: preflight.align,
|
|
443
|
+
padding: preflight.padding,
|
|
444
|
+
facet: {
|
|
445
|
+
data: definition.data,
|
|
446
|
+
values: definition.cells.map(cell => cell.value),
|
|
447
|
+
grid: {
|
|
448
|
+
...definition.grid,
|
|
449
|
+
cells: definition.cells.map(cell => ({
|
|
450
|
+
id: cell.id,
|
|
451
|
+
row: cell.row,
|
|
452
|
+
column: cell.column,
|
|
453
|
+
rowValue: cell.rowValue,
|
|
454
|
+
columnValue: cell.columnValue,
|
|
455
|
+
empty: cell.empty
|
|
456
|
+
}))
|
|
457
|
+
},
|
|
458
|
+
scales: scalePolicies.channels,
|
|
459
|
+
guides
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
return applyCompositionState(
|
|
463
|
+
this._withMaterializationConfig(["facets", definition.id], {
|
|
464
|
+
headers: DEFAULT_HEADERS
|
|
465
|
+
}),
|
|
466
|
+
{ children: derived.children, compositionSpec },
|
|
467
|
+
compositionSpec.children
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
);
|
|
471
|
+
|
|
173
472
|
export const editFacetHeaders = action(
|
|
174
473
|
{
|
|
175
474
|
op: "editFacetHeaders",
|
|
@@ -255,6 +554,9 @@ export const editFacetGuides = action(
|
|
|
255
554
|
});
|
|
256
555
|
const current = this.compositionSpec.facet;
|
|
257
556
|
const guides = normalizeGuides({ ...current.guides, ...args });
|
|
557
|
+
if (current.repeat !== undefined && guides.axes === "outer") {
|
|
558
|
+
throw new Error("repeatCharts does not promote axes across different repeated fields.");
|
|
559
|
+
}
|
|
258
560
|
return rederiveFacet(this, {
|
|
259
561
|
scales: current.scales,
|
|
260
562
|
guides
|
|
@@ -262,11 +564,107 @@ export const editFacetGuides = action(
|
|
|
262
564
|
}
|
|
263
565
|
);
|
|
264
566
|
|
|
567
|
+
function adoptUnitState(program, actionOwner) {
|
|
568
|
+
if (!(program instanceof actionOwner.constructor)) {
|
|
569
|
+
throw new TypeError("editFacetSource program must be a ChartProgram.");
|
|
570
|
+
}
|
|
571
|
+
if (program.compositionSpec !== undefined) {
|
|
572
|
+
throw new Error("editFacetSource program must be a complete unit ChartProgram.");
|
|
573
|
+
}
|
|
574
|
+
if (program.actionStack.length !== 0) {
|
|
575
|
+
throw new Error("editFacetSource program has an unfinished action stack.");
|
|
576
|
+
}
|
|
577
|
+
return new actionOwner.constructor({
|
|
578
|
+
semanticSpec: program.semanticSpec,
|
|
579
|
+
graphicSpec: program.graphicSpec,
|
|
580
|
+
resolvedScales: program.resolvedScales,
|
|
581
|
+
materializationConfigs: program.materializationConfigs,
|
|
582
|
+
children: {},
|
|
583
|
+
context: program.context,
|
|
584
|
+
trace: actionOwner.trace,
|
|
585
|
+
actionStack: actionOwner.actionStack,
|
|
586
|
+
actionSequence: actionOwner._actionSequence
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export const editFacetSource = action(
|
|
591
|
+
{
|
|
592
|
+
op: "editFacetSource",
|
|
593
|
+
description: "Reapply one facet, grid, or repeat recipe to a revised complete unit program.",
|
|
594
|
+
scope: "composition"
|
|
595
|
+
},
|
|
596
|
+
function (args = {}) {
|
|
597
|
+
validateOptionObject(args, SOURCE_EDIT_OPTIONS, "editFacetSource");
|
|
598
|
+
requireFacetProgram(this, "editFacetSource");
|
|
599
|
+
const current = this.compositionSpec;
|
|
600
|
+
const facetConfig = this.materializationConfigs.facets?.[current.id];
|
|
601
|
+
const base = adoptUnitState(args.program, this);
|
|
602
|
+
let revised;
|
|
603
|
+
const scales = usedFacetScalePolicies(base, current.facet.scales);
|
|
604
|
+
if (current.facet.grid !== undefined) {
|
|
605
|
+
revised = base.facetGrid({
|
|
606
|
+
id: current.id,
|
|
607
|
+
data: current.facet.data,
|
|
608
|
+
rows: current.facet.grid.rows,
|
|
609
|
+
columns: current.facet.grid.columns,
|
|
610
|
+
combinations: current.facet.grid.combinations,
|
|
611
|
+
gap: current.gap,
|
|
612
|
+
align: current.align,
|
|
613
|
+
padding: current.padding,
|
|
614
|
+
scales,
|
|
615
|
+
guides: current.facet.guides
|
|
616
|
+
});
|
|
617
|
+
} else if (current.facet.repeat !== undefined) {
|
|
618
|
+
revised = base.repeatCharts({
|
|
619
|
+
id: current.id,
|
|
620
|
+
...current.facet.repeat,
|
|
621
|
+
columns: current.columns,
|
|
622
|
+
gap: current.gap,
|
|
623
|
+
align: current.align,
|
|
624
|
+
padding: current.padding,
|
|
625
|
+
scales,
|
|
626
|
+
guides: current.facet.guides
|
|
627
|
+
});
|
|
628
|
+
} else {
|
|
629
|
+
revised = base.facet({
|
|
630
|
+
id: current.id,
|
|
631
|
+
data: current.facet.data,
|
|
632
|
+
field: current.facet.field,
|
|
633
|
+
values: current.facet.values,
|
|
634
|
+
columns: current.columns,
|
|
635
|
+
gap: current.gap,
|
|
636
|
+
align: current.align,
|
|
637
|
+
padding: current.padding,
|
|
638
|
+
scales,
|
|
639
|
+
guides: current.facet.guides
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
if (facetConfig !== undefined) {
|
|
643
|
+
revised = revised
|
|
644
|
+
._withMaterializationConfig(["facets", current.id], facetConfig)
|
|
645
|
+
.materializeComposition();
|
|
646
|
+
}
|
|
647
|
+
if (revised.semanticSpec.title.text !== undefined) {
|
|
648
|
+
revised = revised.removeTitle();
|
|
649
|
+
}
|
|
650
|
+
if (this.semanticSpec.title.text !== undefined) {
|
|
651
|
+
revised = revised.createTitle({
|
|
652
|
+
...this.titleConfig,
|
|
653
|
+
...this.semanticSpec.title
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
return revised;
|
|
657
|
+
}
|
|
658
|
+
);
|
|
659
|
+
|
|
265
660
|
export function registerFacetActions(ProgramClass) {
|
|
266
661
|
ProgramClass.prototype.replayDerivedData = replayDerivedData;
|
|
267
662
|
ProgramClass.prototype.composeFacetGuides = composeFacetGuides;
|
|
268
663
|
ProgramClass.prototype.facet = facet;
|
|
664
|
+
ProgramClass.prototype.facetGrid = facetGrid;
|
|
665
|
+
ProgramClass.prototype.repeatCharts = repeatCharts;
|
|
269
666
|
ProgramClass.prototype.editFacetHeaders = editFacetHeaders;
|
|
270
667
|
ProgramClass.prototype.editFacetScales = editFacetScales;
|
|
271
668
|
ProgramClass.prototype.editFacetGuides = editFacetGuides;
|
|
669
|
+
ProgramClass.prototype.editFacetSource = editFacetSource;
|
|
272
670
|
}
|
|
@@ -63,12 +63,30 @@ function deriveCellProgram(
|
|
|
63
63
|
histogramBoundaries,
|
|
64
64
|
scales
|
|
65
65
|
) {
|
|
66
|
-
|
|
67
|
-
id: cell.data,
|
|
68
|
-
source: definition.data,
|
|
66
|
+
const filters = cell.filters ?? [{
|
|
69
67
|
field: definition.field,
|
|
70
|
-
|
|
71
|
-
}
|
|
68
|
+
value: cell.value
|
|
69
|
+
}];
|
|
70
|
+
let child = base;
|
|
71
|
+
let source = definition.data;
|
|
72
|
+
for (const [index, filter] of filters.entries()) {
|
|
73
|
+
const id = index === filters.length - 1
|
|
74
|
+
? cell.data
|
|
75
|
+
: `${cell.id}-partition-${index + 1}-data`;
|
|
76
|
+
child = child.filterData({
|
|
77
|
+
id,
|
|
78
|
+
source,
|
|
79
|
+
field: filter.field,
|
|
80
|
+
oneOf: [filter.value]
|
|
81
|
+
});
|
|
82
|
+
source = id;
|
|
83
|
+
}
|
|
84
|
+
if (requireDataset(child, cell.data).values.length === 0) {
|
|
85
|
+
while (child.semanticSpec.layers.length > 0) {
|
|
86
|
+
child = child.removeMark({ target: child.semanticSpec.layers[0].id });
|
|
87
|
+
}
|
|
88
|
+
return child;
|
|
89
|
+
}
|
|
72
90
|
const datasets = new Map([[definition.data, cell.data]]);
|
|
73
91
|
for (const replay of definition.dependencies.replay) {
|
|
74
92
|
const source = datasets.get(replay.source);
|
|
@@ -182,9 +200,35 @@ export function deriveFacetChildren(
|
|
|
182
200
|
cell.id,
|
|
183
201
|
deriveCellProgram(template, definition, cell, bins, scales)
|
|
184
202
|
]));
|
|
203
|
+
return resolveFacetChildrenScales(
|
|
204
|
+
template,
|
|
205
|
+
definition.cells.map(cell => cell.id),
|
|
206
|
+
independentlyResolved,
|
|
207
|
+
scales,
|
|
208
|
+
closeInheritedAction
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function resolveFacetChildrenScales(
|
|
213
|
+
template,
|
|
214
|
+
cellIds,
|
|
215
|
+
independentlyResolved,
|
|
216
|
+
scales = {},
|
|
217
|
+
closeInheritedAction = false
|
|
218
|
+
) {
|
|
219
|
+
const populated = Object.fromEntries(cellIds.flatMap(id => {
|
|
220
|
+
const child = independentlyResolved[id];
|
|
221
|
+
if (child === undefined) {
|
|
222
|
+
throw new Error(`Facet derivation is missing child "${id}".`);
|
|
223
|
+
}
|
|
224
|
+
return child.semanticSpec.layers.length > 0 ? [[id, child]] : [];
|
|
225
|
+
}));
|
|
226
|
+
if (Object.keys(populated).length === 0) {
|
|
227
|
+
throw new Error("Facet derivation requires at least one populated cell.");
|
|
228
|
+
}
|
|
185
229
|
const resolution = resolveFacetScaleDomains(
|
|
186
230
|
template.semanticSpec,
|
|
187
|
-
Object.fromEntries(Object.entries(
|
|
231
|
+
Object.fromEntries(Object.entries(populated).map(([id, child]) => [
|
|
188
232
|
id,
|
|
189
233
|
child.resolvedScales
|
|
190
234
|
])),
|
|
@@ -192,7 +236,11 @@ export function deriveFacetChildren(
|
|
|
192
236
|
template.resolvedScales
|
|
193
237
|
);
|
|
194
238
|
const resolvedChildren = Object.fromEntries(
|
|
195
|
-
|
|
239
|
+
cellIds.map(id => {
|
|
240
|
+
const child = independentlyResolved[id];
|
|
241
|
+
if (child.semanticSpec.layers.length === 0) {
|
|
242
|
+
return [id, closeInheritedAction ? child._exitAction() : child];
|
|
243
|
+
}
|
|
196
244
|
const resolved = applyResolvedDomains(
|
|
197
245
|
child,
|
|
198
246
|
id,
|
|
@@ -202,10 +250,11 @@ export function deriveFacetChildren(
|
|
|
202
250
|
return [id, closeInheritedAction ? resolved._exitAction() : resolved];
|
|
203
251
|
})
|
|
204
252
|
);
|
|
253
|
+
const firstPopulatedId = Object.keys(populated)[0];
|
|
205
254
|
const sharedScales = Object.fromEntries(
|
|
206
255
|
Object.entries(resolution.scales)
|
|
207
256
|
.filter(([, value]) => value.policy === "shared")
|
|
208
|
-
.map(([id]) => [id, resolvedChildren[
|
|
257
|
+
.map(([id]) => [id, resolvedChildren[firstPopulatedId].resolvedScales[id]])
|
|
209
258
|
);
|
|
210
259
|
return freezeOwned({
|
|
211
260
|
children: freezeOwned(resolvedChildren),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertGuideOptions, guideConflict } from "../guides/reuse.js";
|
|
1
2
|
import { action } from "../../core/action.js";
|
|
2
3
|
import { validateKeys } from "../../core/validation.js";
|
|
3
4
|
import { GRADIENT_PROFILE_FIELDS } from "../../grammar/gradientProfile.js";
|
|
@@ -120,23 +121,44 @@ export const rematerializeGradientPlotLegend = action(
|
|
|
120
121
|
}
|
|
121
122
|
);
|
|
122
123
|
|
|
124
|
+
function resolveDensityLegend(program, args) {
|
|
125
|
+
validateKeys(args, LEGEND_OPTIONS, "createGradientPlotLegend");
|
|
126
|
+
requireLegendOwner(program, args.owner);
|
|
127
|
+
if (args.position !== undefined && args.position !== "right") {
|
|
128
|
+
throw new Error('Gradient plot density legend currently requires position "right".');
|
|
129
|
+
}
|
|
130
|
+
if (args.title !== undefined && (typeof args.title !== "string" || args.title.length === 0)) {
|
|
131
|
+
throw new TypeError("Gradient plot density legend title must be a non-empty string.");
|
|
132
|
+
}
|
|
133
|
+
const ids = legendIds(args.owner);
|
|
134
|
+
const densityScale = `${args.owner}Density`;
|
|
135
|
+
const title = args.title ?? "Relative density";
|
|
136
|
+
return { ids, densityScale, title };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function fulfillGradientPlotLegend(program, args) {
|
|
140
|
+
const { ids, densityScale } = resolveDensityLegend(program, args);
|
|
141
|
+
const occupied = program.semanticSpec.guides.legend?.color;
|
|
142
|
+
if (occupied !== undefined && occupied.scale !== densityScale) {
|
|
143
|
+
guideConflict("Gradient plot density legend uses a different scale or family");
|
|
144
|
+
}
|
|
145
|
+
const existing = Object.values(ids).filter(id => program.graphicSpec.objects[id] !== undefined);
|
|
146
|
+
if (existing.length === 0) return program.createGradientPlotLegend(args);
|
|
147
|
+
if (existing.length !== Object.keys(ids).length || occupied?.scale !== densityScale) {
|
|
148
|
+
guideConflict("Gradient plot density legend has incomplete ownership");
|
|
149
|
+
}
|
|
150
|
+
const { owner, ...explicit } = args;
|
|
151
|
+
assertGuideOptions(explicit, program.markConfigs[owner].gradientPlot.guides.legend, "Gradient plot density legend");
|
|
152
|
+
return program;
|
|
153
|
+
}
|
|
154
|
+
|
|
123
155
|
export const createGradientPlotLegend = action(
|
|
124
156
|
{
|
|
125
157
|
op: "createGradientPlotLegend",
|
|
126
158
|
description: "Create the neutral density legend owned by one gradient plot."
|
|
127
159
|
},
|
|
128
160
|
function (args = {}) {
|
|
129
|
-
|
|
130
|
-
requireLegendOwner(this, args.owner);
|
|
131
|
-
if (args.position !== undefined && args.position !== "right") {
|
|
132
|
-
throw new Error('Gradient plot density legend currently requires position "right".');
|
|
133
|
-
}
|
|
134
|
-
if (args.title !== undefined && (typeof args.title !== "string" || args.title.length === 0)) {
|
|
135
|
-
throw new TypeError("Gradient plot density legend title must be a non-empty string.");
|
|
136
|
-
}
|
|
137
|
-
const ids = legendIds(args.owner);
|
|
138
|
-
const densityScale = `${args.owner}Density`;
|
|
139
|
-
const title = args.title ?? "Relative density";
|
|
161
|
+
const { ids, densityScale, title } = resolveDensityLegend(this, args);
|
|
140
162
|
let next = this;
|
|
141
163
|
for (const [property, value] of [
|
|
142
164
|
[`scale[${densityScale}].type`, "sequential"],
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { applyFacadeGuides } from "../charts/shared.js";
|
|
2
|
+
import { fulfillGradientPlotLegend } from "./components.js";
|
|
1
3
|
import { action } from "../../core/action.js";
|
|
2
4
|
import { validateUserId } from "../../core/identifiers.js";
|
|
3
5
|
import { isPlainObject } from "../../core/immutable.js";
|
|
@@ -253,19 +255,15 @@ export const materializeGradientPlot = action(
|
|
|
253
255
|
if (guides !== false) {
|
|
254
256
|
const wantsStandard = guides.axes !== false || guides.grid !== false;
|
|
255
257
|
if (wantsStandard) {
|
|
256
|
-
next = next
|
|
258
|
+
next = applyFacadeGuides(next, {
|
|
257
259
|
axes: gradientAxesOptions(next.markConfigs[owner].gradientPlot),
|
|
258
260
|
grid: guides.grid,
|
|
259
261
|
legend: false
|
|
260
|
-
});
|
|
262
|
+
}, owner, guides);
|
|
261
263
|
}
|
|
262
264
|
if (guides.legend !== false) {
|
|
263
265
|
const legend = isPlainObject(guides.legend) ? guides.legend : {};
|
|
264
|
-
next = next
|
|
265
|
-
owner,
|
|
266
|
-
title: legend.title ?? "Relative density",
|
|
267
|
-
position: legend.position ?? "right"
|
|
268
|
-
});
|
|
266
|
+
next = fulfillGradientPlotLegend(next, { owner, ...legend });
|
|
269
267
|
}
|
|
270
268
|
}
|
|
271
269
|
return next._withContext({ currentMark: owner, currentData: layer.data });
|