ggaction 0.0.2 → 0.0.3
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 +28 -0
- package/README.md +12 -1
- package/package.json +6 -1
- package/src/actions/boxPlots/components.js +3 -3
- package/src/actions/boxPlots/edit.js +277 -0
- package/src/actions/boxPlots/index.js +2 -0
- package/src/actions/boxPlots/materialize.js +12 -1
- package/src/actions/boxPlots/options.js +25 -14
- package/src/actions/canvas/actions.js +21 -17
- package/src/actions/composition/actions.js +247 -0
- package/src/actions/composition/index.js +1 -0
- package/src/actions/coordinates/actions.js +2 -5
- package/src/actions/data/density.js +7 -1
- package/src/actions/data/filter.js +3 -1
- package/src/actions/encodings/appearance.js +11 -0
- package/src/actions/encodings/barWidth.js +5 -3
- package/src/actions/encodings/color.js +120 -25
- package/src/actions/encodings/index.js +2 -0
- package/src/actions/encodings/offset.js +90 -72
- package/src/actions/encodings/position/apply.js +66 -0
- package/src/actions/encodings/position/policies/arc.js +26 -0
- package/src/actions/encodings/position/policies/bar.js +9 -5
- package/src/actions/encodings/position/policies/index.js +5 -1
- package/src/actions/encodings/position/policies/line.js +20 -3
- package/src/actions/encodings/position/resolve.js +57 -8
- package/src/actions/encodings/position.js +44 -90
- package/src/actions/encodings/ranged.js +28 -6
- package/src/actions/encodings/shared.js +17 -21
- package/src/actions/encodings/text.js +73 -0
- package/src/actions/errorBands/create.js +37 -29
- package/src/actions/errorBands/edit.js +206 -0
- package/src/actions/errorBands/index.js +9 -0
- package/src/actions/errorBands/options.js +39 -0
- package/src/actions/errorBars/create.js +50 -43
- package/src/actions/errorBars/edit.js +130 -0
- package/src/actions/errorBars/index.js +3 -0
- package/src/actions/errorBars/options.js +52 -0
- package/src/actions/facets/actions.js +162 -0
- package/src/actions/facets/derive.js +200 -0
- package/src/actions/facets/guides.js +16 -0
- package/src/actions/facets/index.js +1 -0
- package/src/actions/facets/replay.js +69 -0
- package/src/actions/guides/applicability.js +113 -0
- package/src/actions/guides/axes/axes.js +70 -16
- package/src/actions/guides/axes/edit.js +126 -0
- package/src/actions/guides/axes/index.js +12 -0
- package/src/actions/guides/axes/labels.js +21 -18
- package/src/actions/guides/axes/remove.js +60 -0
- package/src/actions/guides/axes/tickGroups.js +7 -5
- package/src/actions/guides/axes/ticks.js +9 -4
- package/src/actions/guides/axes/titles.js +7 -5
- package/src/actions/guides/grids/grid.js +144 -15
- package/src/actions/guides/grids/resolve.js +5 -13
- package/src/actions/guides/guides.js +17 -75
- package/src/actions/guides/index.js +6 -0
- package/src/actions/guides/legends/categorical/actions.js +48 -9
- package/src/actions/guides/legends/categorical/components.js +16 -16
- package/src/actions/guides/legends/categorical/index.js +14 -14
- package/src/actions/guides/legends/categorical/layout.js +3 -1
- package/src/actions/guides/legends/categorical/options.js +2 -5
- package/src/actions/guides/legends/categorical/resolve.js +2 -2
- package/src/actions/guides/legends/categorical/symbols.js +14 -14
- package/src/actions/guides/legends/continuous/common.js +7 -7
- package/src/actions/guides/legends/continuous/interval.js +5 -5
- package/src/actions/guides/legends/edit.js +2 -6
- package/src/actions/guides/legends/focused.js +63 -0
- package/src/actions/guides/legends/index.js +4 -0
- package/src/actions/guides/legends/remove.js +64 -0
- package/src/actions/guides/legends/size.js +12 -14
- package/src/actions/guides/polar/axes/facade.js +219 -0
- package/src/actions/guides/polar/axes/labels.js +188 -0
- package/src/actions/guides/polar/axes/lines.js +113 -0
- package/src/actions/guides/polar/axes/shared.js +112 -0
- package/src/actions/guides/polar/axes/ticks.js +164 -0
- package/src/actions/guides/polar/axes/titles.js +165 -0
- package/src/actions/guides/polar/axes.js +55 -0
- package/src/actions/guides/polar/grids.js +252 -0
- package/src/actions/guides/polar/index.js +2 -0
- package/src/actions/guides/polar/resolve.js +269 -0
- package/src/actions/index.js +4 -0
- package/src/actions/marks/arc/actions.js +239 -0
- package/src/actions/marks/arc/index.js +1 -0
- package/src/actions/marks/{area.js → area/actions.js} +51 -38
- package/src/actions/marks/area/index.js +4 -0
- package/src/actions/marks/bar/create.js +26 -5
- package/src/actions/marks/bar/materialize.js +25 -31
- package/src/actions/marks/index.js +12 -4
- package/src/actions/marks/lifecycle.js +27 -0
- package/src/actions/marks/line/actions.js +373 -0
- package/src/actions/marks/line/index.js +1 -0
- package/src/actions/marks/{point.js → point/actions.js} +67 -108
- package/src/actions/marks/point/index.js +1 -0
- package/src/actions/marks/rect/actions.js +165 -0
- package/src/actions/marks/rect/index.js +1 -0
- package/src/actions/marks/remove.js +214 -0
- package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
- package/src/actions/marks/rule/index.js +1 -0
- package/src/actions/marks/shared.js +156 -25
- package/src/actions/marks/text/actions.js +192 -0
- package/src/actions/marks/text/index.js +1 -0
- package/src/actions/primitives/createGraphics.js +13 -6
- package/src/actions/primitives/editGraphics.js +10 -3
- package/src/actions/primitives/semantic.js +26 -3
- package/src/actions/primitives/semanticValue.js +25 -3
- package/src/actions/regression/components.js +1 -1
- package/src/actions/regression/create.js +18 -1
- package/src/actions/regression/edit.js +247 -0
- package/src/actions/regression/index.js +3 -0
- package/src/actions/regression/resolve.js +3 -1
- package/src/actions/scales/consumers.js +15 -6
- package/src/actions/scales/definitions.js +13 -4
- package/src/actions/scales/edit.js +42 -19
- package/src/actions/scales/materialize.js +57 -408
- package/src/actions/selection/actions.js +38 -24
- package/src/actions/selection/index.js +2 -0
- package/src/actions/titles/actions.js +63 -2
- package/src/actions/titles/index.js +2 -0
- package/src/actions/titles/resolve.js +12 -17
- package/src/composition.js +10 -0
- package/src/core/ChartProgram.js +200 -2
- package/src/core/action.js +10 -0
- package/src/core/validation.js +16 -0
- package/src/core/vocabulary.js +154 -4
- package/src/grammar/arcs.js +154 -0
- package/src/grammar/bars/aggregate.js +2 -2
- package/src/grammar/bars/geometry.js +3 -3
- package/src/grammar/bars/policy.js +7 -1
- package/src/grammar/coordinates.js +3 -7
- package/src/grammar/density.js +20 -1
- package/src/grammar/facets/dependencies.js +185 -0
- package/src/grammar/facets/guides.js +257 -0
- package/src/grammar/facets/scales.js +254 -0
- package/src/grammar/facets.js +122 -0
- package/src/grammar/lineSeries.js +111 -3
- package/src/grammar/polar.js +102 -0
- package/src/grammar/polarGuides.js +243 -0
- package/src/grammar/polarLineCommands.js +43 -0
- package/src/grammar/polarPaths.js +161 -0
- package/src/grammar/positionCompatibility.js +18 -2
- package/src/grammar/rects.js +26 -0
- package/src/grammar/scales/continuous.js +6 -1
- package/src/grammar/scales/fields.js +7 -0
- package/src/grammar/scales/ordinal.js +5 -2
- package/src/grammar/scales/policies.js +1 -1
- package/src/grammar/schemas/graphic.js +1 -1
- package/src/grammar/schemas/graphicBounds.js +64 -9
- package/src/grammar/schemas/graphicTree.js +44 -10
- package/src/grammar/schemas/semanticPath.js +31 -9
- package/src/grammar/seriesLayout.js +13 -2
- package/src/grammar/text.js +71 -0
- package/src/grammar/ticks.js +50 -13
- package/src/index.js +1 -0
- package/src/layout/composition.js +310 -0
- package/src/layout/facets.js +131 -0
- package/src/layout/title.js +6 -0
- package/src/materialization/bars/aggregate.js +5 -2
- package/src/materialization/bars/grouped.js +75 -37
- package/src/materialization/composition.js +152 -0
- package/src/materialization/compositionSnapshot.js +114 -0
- package/src/materialization/dependencies.js +71 -16
- package/src/materialization/encodings.js +14 -28
- package/src/materialization/facetGuides.js +426 -0
- package/src/materialization/facets.js +214 -0
- package/src/materialization/graphicHierarchy.js +15 -5
- package/src/materialization/guides/resources.js +46 -0
- package/src/materialization/marks.js +220 -14
- package/src/materialization/planner.js +105 -12
- package/src/materialization/rect.js +138 -0
- package/src/materialization/rectConfig.js +38 -0
- package/src/materialization/rowEncoding.js +33 -0
- package/src/materialization/scales/map.js +40 -0
- package/src/materialization/scales/policies/arc.js +37 -0
- package/src/materialization/scales/policies/bar.js +132 -0
- package/src/materialization/scales/policies/series.js +76 -0
- package/src/materialization/scales/resolve.js +326 -0
- package/src/materialization/selection/items/arc.js +29 -0
- package/src/materialization/selection/items/bar.js +255 -0
- package/src/materialization/selection/items/common.js +131 -0
- package/src/materialization/selection/items/index.js +6 -0
- package/src/materialization/selection/items/path.js +59 -0
- package/src/materialization/selection/items/point.js +55 -0
- package/src/materialization/selection/items/rect.js +23 -0
- package/src/materialization/selection/items/rule.js +25 -0
- package/src/materialization/selection/policies/arc.js +10 -0
- package/src/materialization/selection/policies/area.js +1 -1
- package/src/materialization/selection/policies/bar.js +1 -1
- package/src/materialization/selection/policies/index.js +5 -1
- package/src/materialization/selection/policies/line.js +1 -1
- package/src/materialization/selection/policies/point.js +1 -1
- package/src/materialization/selection/policies/rect.js +10 -0
- package/src/materialization/selection/policies/rule.js +1 -1
- package/src/materialization/selection/styles.js +24 -16
- package/src/materialization/text.js +156 -0
- package/src/renderers/canvas/index.js +68 -22
- package/types/extension.d.ts +1 -0
- package/types/index.d.ts +56 -1
- package/types/program.d.ts +664 -27
- package/src/actions/marks/line.js +0 -248
- package/src/materialization/selection/items.js +0 -503
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { ChartProgram as CoreChartProgram } from "../../core/ChartProgram.js";
|
|
3
|
+
import { freezeOwned, isPlainObject } from "../../core/immutable.js";
|
|
4
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
5
|
+
import { validateOptionObject } from "../../core/validation.js";
|
|
6
|
+
import {
|
|
7
|
+
normalizeCompositionPadding,
|
|
8
|
+
resolveCompositionLayout
|
|
9
|
+
} from "../../layout/composition.js";
|
|
10
|
+
import { resolveFacetLayout } from "../../layout/facets.js";
|
|
11
|
+
import { namespaceGraphicSnapshot } from "../../materialization/compositionSnapshot.js";
|
|
12
|
+
import { materializeCompositionGraphics } from
|
|
13
|
+
"../../materialization/composition.js";
|
|
14
|
+
import { materializeFacetGraphics } from "../../materialization/facets.js";
|
|
15
|
+
|
|
16
|
+
const CONCAT_OPTIONS = Object.freeze([
|
|
17
|
+
"id", "programs", "gap", "align", "padding"
|
|
18
|
+
]);
|
|
19
|
+
const LAYOUT_EDIT_OPTIONS = Object.freeze(["gap", "align", "padding"]);
|
|
20
|
+
const REPLACEMENT_OPTIONS = Object.freeze(["target", "program"]);
|
|
21
|
+
|
|
22
|
+
function normalizeChildEntry(entry, index) {
|
|
23
|
+
const fallbackId = `view-${index + 1}`;
|
|
24
|
+
if (entry instanceof CoreChartProgram) {
|
|
25
|
+
return { id: fallbackId, program: entry };
|
|
26
|
+
}
|
|
27
|
+
if (!isPlainObject(entry)) {
|
|
28
|
+
throw new TypeError(`Composition program ${index} must be a ChartProgram or wrapper.`);
|
|
29
|
+
}
|
|
30
|
+
validateOptionObject(entry, ["id", "program"], `Composition program ${index}`);
|
|
31
|
+
if (!(entry.program instanceof CoreChartProgram)) {
|
|
32
|
+
throw new TypeError(`Composition program ${index}.program must be a ChartProgram.`);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
id: entry.id === undefined
|
|
36
|
+
? fallbackId
|
|
37
|
+
: validateUserId(entry.id, "Composition child ID"),
|
|
38
|
+
program: entry.program
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function childDescriptor({ id, program }) {
|
|
43
|
+
if (program.actionStack.length !== 0) {
|
|
44
|
+
throw new Error(`Composition child "${id}" has an unfinished action stack.`);
|
|
45
|
+
}
|
|
46
|
+
namespaceGraphicSnapshot(program.graphicSpec, { namespace: id });
|
|
47
|
+
const canvasId = program.graphicSpec.order.find(
|
|
48
|
+
graphicId => program.graphicSpec.objects[graphicId]?.type === "canvas"
|
|
49
|
+
);
|
|
50
|
+
const canvas = program.graphicSpec.objects[canvasId];
|
|
51
|
+
const size = program.materializationConfigs.canvas?.size ?? {
|
|
52
|
+
width: "explicit",
|
|
53
|
+
height: "explicit"
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
id,
|
|
57
|
+
width: canvas.properties.width,
|
|
58
|
+
height: canvas.properties.height,
|
|
59
|
+
widthMode: size.width,
|
|
60
|
+
heightMode: size.height
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeCompositionInput(args, direction) {
|
|
65
|
+
validateOptionObject(args, CONCAT_OPTIONS, direction === "horizontal" ? "hconcat" : "vconcat");
|
|
66
|
+
if (!Array.isArray(args.programs) || args.programs.length < 2) {
|
|
67
|
+
throw new TypeError("Composition requires at least two programs.");
|
|
68
|
+
}
|
|
69
|
+
const entries = args.programs.map(normalizeChildEntry);
|
|
70
|
+
const ids = entries.map(entry => entry.id);
|
|
71
|
+
if (new Set(ids).size !== ids.length) {
|
|
72
|
+
throw new Error("Composition child IDs must be unique.");
|
|
73
|
+
}
|
|
74
|
+
const id = args.id === undefined
|
|
75
|
+
? "composition"
|
|
76
|
+
: validateUserId(args.id, "Composition ID");
|
|
77
|
+
const layout = resolveCompositionLayout({
|
|
78
|
+
direction,
|
|
79
|
+
children: entries.map(childDescriptor),
|
|
80
|
+
...(Object.hasOwn(args, "gap") ? { gap: args.gap } : {}),
|
|
81
|
+
...(Object.hasOwn(args, "align") ? { align: args.align } : {}),
|
|
82
|
+
...(Object.hasOwn(args, "padding") ? { padding: args.padding } : {})
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
children: freezeOwned(Object.fromEntries(
|
|
86
|
+
entries.map(entry => [entry.id, entry.program])
|
|
87
|
+
)),
|
|
88
|
+
compositionSpec: {
|
|
89
|
+
id,
|
|
90
|
+
direction,
|
|
91
|
+
children: ids,
|
|
92
|
+
gap: layout.gap,
|
|
93
|
+
align: layout.align,
|
|
94
|
+
padding: layout.padding
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const useProgram = action(
|
|
100
|
+
{
|
|
101
|
+
op: "useProgram",
|
|
102
|
+
description: "Retain one named child program in a composition.",
|
|
103
|
+
scope: "composition"
|
|
104
|
+
},
|
|
105
|
+
function ({ id } = {}) {
|
|
106
|
+
validateUserId(id, "Composition child ID");
|
|
107
|
+
if (!Object.hasOwn(this.children, id)) {
|
|
108
|
+
throw new Error(`Unknown composition child "${id}".`);
|
|
109
|
+
}
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const materializeComposition = action(
|
|
115
|
+
{
|
|
116
|
+
op: "materializeComposition",
|
|
117
|
+
description: "Materialize retained child programs into one graphic tree.",
|
|
118
|
+
scope: "composition"
|
|
119
|
+
},
|
|
120
|
+
function () {
|
|
121
|
+
return this.compositionSpec.type === "facet"
|
|
122
|
+
? materializeFacetGraphics(this)
|
|
123
|
+
: materializeCompositionGraphics(this);
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
function concatAction(direction, op) {
|
|
128
|
+
return action(
|
|
129
|
+
{
|
|
130
|
+
op,
|
|
131
|
+
description: direction === "horizontal"
|
|
132
|
+
? "Compose complete programs horizontally."
|
|
133
|
+
: "Compose complete programs vertically."
|
|
134
|
+
},
|
|
135
|
+
function (args = {}) {
|
|
136
|
+
const state = normalizeCompositionInput(args, direction);
|
|
137
|
+
let next = this._withCompositionState(state);
|
|
138
|
+
for (const id of state.compositionSpec.children) {
|
|
139
|
+
next = next.useProgram({ id });
|
|
140
|
+
}
|
|
141
|
+
return next.materializeComposition();
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const hconcatAction = concatAction("horizontal", "hconcat");
|
|
147
|
+
export const vconcatAction = concatAction("vertical", "vconcat");
|
|
148
|
+
|
|
149
|
+
const editCompositionLayout = action(
|
|
150
|
+
{
|
|
151
|
+
op: "editCompositionLayout",
|
|
152
|
+
description: "Edit composition spacing and alignment.",
|
|
153
|
+
scope: "composition"
|
|
154
|
+
},
|
|
155
|
+
function (args = {}) {
|
|
156
|
+
validateOptionObject(args, LAYOUT_EDIT_OPTIONS, "editCompositionLayout");
|
|
157
|
+
if (!LAYOUT_EDIT_OPTIONS.some(option => Object.hasOwn(args, option))) {
|
|
158
|
+
throw new TypeError("editCompositionLayout requires at least one layout option.");
|
|
159
|
+
}
|
|
160
|
+
const current = this.compositionSpec;
|
|
161
|
+
if (current.type === "facet") {
|
|
162
|
+
const layout = resolveFacetLayout({
|
|
163
|
+
children: current.children.map((id, index) => ({
|
|
164
|
+
...childDescriptor({ id, program: this.children[id] }),
|
|
165
|
+
value: current.facet.values[index]
|
|
166
|
+
})),
|
|
167
|
+
columns: current.columns,
|
|
168
|
+
gap: Object.hasOwn(args, "gap") ? args.gap : current.gap,
|
|
169
|
+
align: Object.hasOwn(args, "align") ? args.align : current.align,
|
|
170
|
+
padding: Object.hasOwn(args, "padding")
|
|
171
|
+
? normalizeCompositionPadding(args.padding, current.padding)
|
|
172
|
+
: current.padding,
|
|
173
|
+
sharedLegend: current.facet.guides.legend === "shared"
|
|
174
|
+
});
|
|
175
|
+
return this._withCompositionState({
|
|
176
|
+
children: this.children,
|
|
177
|
+
compositionSpec: {
|
|
178
|
+
...current,
|
|
179
|
+
gap: layout.gap,
|
|
180
|
+
align: layout.align,
|
|
181
|
+
padding: layout.padding
|
|
182
|
+
}
|
|
183
|
+
}).materializeComposition();
|
|
184
|
+
}
|
|
185
|
+
const padding = Object.hasOwn(args, "padding")
|
|
186
|
+
? normalizeCompositionPadding(args.padding, current.padding)
|
|
187
|
+
: current.padding;
|
|
188
|
+
const layout = resolveCompositionLayout({
|
|
189
|
+
direction: current.direction,
|
|
190
|
+
children: current.children.map(id => childDescriptor({
|
|
191
|
+
id,
|
|
192
|
+
program: this.children[id]
|
|
193
|
+
})),
|
|
194
|
+
gap: Object.hasOwn(args, "gap") ? args.gap : current.gap,
|
|
195
|
+
align: Object.hasOwn(args, "align") ? args.align : current.align,
|
|
196
|
+
padding
|
|
197
|
+
});
|
|
198
|
+
return this._withCompositionState({
|
|
199
|
+
children: this.children,
|
|
200
|
+
compositionSpec: {
|
|
201
|
+
...current,
|
|
202
|
+
gap: layout.gap,
|
|
203
|
+
align: layout.align,
|
|
204
|
+
padding: layout.padding
|
|
205
|
+
}
|
|
206
|
+
}).materializeComposition();
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const replaceCompositionChild = action(
|
|
211
|
+
{
|
|
212
|
+
op: "replaceCompositionChild",
|
|
213
|
+
description: "Replace one composition child without changing its slot.",
|
|
214
|
+
scope: "composition"
|
|
215
|
+
},
|
|
216
|
+
function (args = {}) {
|
|
217
|
+
validateOptionObject(args, REPLACEMENT_OPTIONS, "replaceCompositionChild");
|
|
218
|
+
if (this.compositionSpec.type === "facet") {
|
|
219
|
+
throw new Error("replaceCompositionChild is not available on a facet composition.");
|
|
220
|
+
}
|
|
221
|
+
const target = validateUserId(args.target, "Composition child target");
|
|
222
|
+
if (!Object.hasOwn(this.children, target)) {
|
|
223
|
+
throw new Error(`Unknown composition child "${target}".`);
|
|
224
|
+
}
|
|
225
|
+
if (!(args.program instanceof CoreChartProgram)) {
|
|
226
|
+
throw new TypeError("replaceCompositionChild program must be a ChartProgram.");
|
|
227
|
+
}
|
|
228
|
+
childDescriptor({ id: target, program: args.program });
|
|
229
|
+
const children = freezeOwned({
|
|
230
|
+
...this.children,
|
|
231
|
+
[target]: args.program
|
|
232
|
+
});
|
|
233
|
+
return this._withCompositionState({
|
|
234
|
+
children,
|
|
235
|
+
compositionSpec: this.compositionSpec
|
|
236
|
+
})
|
|
237
|
+
.useProgram({ id: target })
|
|
238
|
+
.materializeComposition();
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
export function registerCompositionActions(ProgramClass) {
|
|
243
|
+
ProgramClass.prototype.useProgram = useProgram;
|
|
244
|
+
ProgramClass.prototype.materializeComposition = materializeComposition;
|
|
245
|
+
ProgramClass.prototype.editCompositionLayout = editCompositionLayout;
|
|
246
|
+
ProgramClass.prototype.replaceCompositionChild = replaceCompositionChild;
|
|
247
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerCompositionActions } from "./actions.js";
|
|
@@ -3,15 +3,12 @@ import { validateCoordinateType } from "../../grammar/coordinates.js";
|
|
|
3
3
|
import { validateUserId } from "../../core/identifiers.js";
|
|
4
4
|
import { findCoordinate } from "../../selectors/coordinates.js";
|
|
5
5
|
import { findLayer, hasLayer } from "../../selectors/layers.js";
|
|
6
|
+
import { validateOptionObject } from "../../core/validation.js";
|
|
6
7
|
|
|
7
8
|
const COORDINATE_OPTIONS = Object.freeze(["id", "type", "layers"]);
|
|
8
9
|
|
|
9
10
|
function validateOptions(args) {
|
|
10
|
-
|
|
11
|
-
if (!COORDINATE_OPTIONS.includes(key)) {
|
|
12
|
-
throw new Error(`Unknown createCoordinate option "${key}".`);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
11
|
+
validateOptionObject(args, COORDINATE_OPTIONS, "createCoordinate");
|
|
15
12
|
}
|
|
16
13
|
|
|
17
14
|
function validateLayers(program, layers) {
|
|
@@ -22,7 +22,13 @@ export const materializeDensityData = action(
|
|
|
22
22
|
return this
|
|
23
23
|
.editSemantic({
|
|
24
24
|
property: `dataset[${id}].transform`,
|
|
25
|
-
value: [{
|
|
25
|
+
value: [{
|
|
26
|
+
...transform,
|
|
27
|
+
resolved: {
|
|
28
|
+
bandwidth: result.bandwidth,
|
|
29
|
+
extent: result.extent
|
|
30
|
+
}
|
|
31
|
+
}]
|
|
26
32
|
})
|
|
27
33
|
.editSemantic({
|
|
28
34
|
property: `dataset[${id}].values`,
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
normalizeMarkFilterTransform
|
|
11
11
|
} from "../../grammar/markFilter.js";
|
|
12
12
|
import { resolveMarkFilterSelection } from "../../materialization/selection/filter.js";
|
|
13
|
+
import { findSelectionPolicy } from
|
|
14
|
+
"../../materialization/selection/policies/index.js";
|
|
13
15
|
import {
|
|
14
16
|
applyLayerDataRematerialization
|
|
15
17
|
} from "../../materialization/dependencies.js";
|
|
@@ -121,7 +123,7 @@ export const filterMarks = action(
|
|
|
121
123
|
label: "filter mark",
|
|
122
124
|
predicate: candidate =>
|
|
123
125
|
candidate.data !== undefined &&
|
|
124
|
-
|
|
126
|
+
findSelectionPolicy(candidate.mark?.type) !== undefined
|
|
125
127
|
});
|
|
126
128
|
const derivedId = `${layer.id}FilteredData`;
|
|
127
129
|
if (hasDataset(this, derivedId)) {
|
|
@@ -119,6 +119,16 @@ const encodeRadius = action(
|
|
|
119
119
|
}
|
|
120
120
|
);
|
|
121
121
|
|
|
122
|
+
const encodePointRadius = action(
|
|
123
|
+
{
|
|
124
|
+
op: "encodePointRadius",
|
|
125
|
+
description: "Set a constant graphical radius on a point mark."
|
|
126
|
+
},
|
|
127
|
+
function (args = {}) {
|
|
128
|
+
return this.encodeRadius(args);
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
|
|
122
132
|
const encodeSize = action(
|
|
123
133
|
{
|
|
124
134
|
op: "encodeSize",
|
|
@@ -236,6 +246,7 @@ const encodeOpacity = action(
|
|
|
236
246
|
|
|
237
247
|
export function registerAppearanceEncodingAction(ProgramClass) {
|
|
238
248
|
ProgramClass.prototype.encodeRadius = encodeRadius;
|
|
249
|
+
ProgramClass.prototype.encodePointRadius = encodePointRadius;
|
|
239
250
|
ProgramClass.prototype.encodeSize = encodeSize;
|
|
240
251
|
ProgramClass.prototype.encodeShape = encodeShape;
|
|
241
252
|
ProgramClass.prototype.encodeOpacity = encodeOpacity;
|
|
@@ -3,6 +3,7 @@ import { resolveTarget, validateOptions } from "./shared.js";
|
|
|
3
3
|
import {
|
|
4
4
|
BAR_GRAINS,
|
|
5
5
|
resolveBarColorLayout,
|
|
6
|
+
resolveBarOffsetChannel,
|
|
6
7
|
resolveBarGrain
|
|
7
8
|
} from "../../grammar/bars/policy.js";
|
|
8
9
|
import { normalizeBarWidth } from "../../grammar/bars/geometry.js";
|
|
@@ -12,7 +13,7 @@ const OPTIONS = Object.freeze(["band", "pixels", "target"]);
|
|
|
12
13
|
const encodeBarWidth = action(
|
|
13
14
|
{
|
|
14
15
|
op: "encodeBarWidth",
|
|
15
|
-
description: "
|
|
16
|
+
description: "Override aggregate or ranged bar width within its category slot."
|
|
16
17
|
},
|
|
17
18
|
function (args = {}) {
|
|
18
19
|
validateOptions(args, OPTIONS, "encodeBarWidth");
|
|
@@ -28,6 +29,7 @@ const encodeBarWidth = action(
|
|
|
28
29
|
this.markConfigs[target]?.barWidth
|
|
29
30
|
);
|
|
30
31
|
const grouped = layout === "group";
|
|
32
|
+
const offsetChannel = resolveBarOffsetChannel(layer);
|
|
31
33
|
const grain = resolveBarGrain(layer);
|
|
32
34
|
if (grain === BAR_GRAINS.ranged) {
|
|
33
35
|
return this._withMarkConfig(target, { ...this.markConfigs[target], barWidth: width })
|
|
@@ -37,11 +39,11 @@ const encodeBarWidth = action(
|
|
|
37
39
|
grain !== BAR_GRAINS.aggregate ||
|
|
38
40
|
(grouped &&
|
|
39
41
|
layer.encoding?.color?.field !== undefined &&
|
|
40
|
-
layer.encoding?.
|
|
42
|
+
layer.encoding?.[offsetChannel]?.field !== layer.encoding.color.field)
|
|
41
43
|
) {
|
|
42
44
|
throw new Error(
|
|
43
45
|
grouped
|
|
44
|
-
?
|
|
46
|
+
? `encodeBarWidth requires complete grouped bar x, y, color, and ${offsetChannel} encodings.`
|
|
45
47
|
: "encodeBarWidth requires complete aggregate bar x and y encodings."
|
|
46
48
|
);
|
|
47
49
|
}
|
|
@@ -4,12 +4,13 @@ import {
|
|
|
4
4
|
readQuantitativeField,
|
|
5
5
|
readScaleField,
|
|
6
6
|
readTemporalField,
|
|
7
|
-
|
|
7
|
+
validateCategoricalFieldType
|
|
8
8
|
} from "../../grammar/scales.js";
|
|
9
9
|
import {
|
|
10
10
|
BAR_GRAINS,
|
|
11
11
|
inferBarColorLayout,
|
|
12
12
|
resolveBarChannels,
|
|
13
|
+
resolveBarOffsetChannel,
|
|
13
14
|
resolveBarGrain
|
|
14
15
|
} from "../../grammar/bars/policy.js";
|
|
15
16
|
import { validateColorLayout } from "../../grammar/seriesLayout.js";
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
} from "../../grammar/aggregate.js";
|
|
21
22
|
import {
|
|
22
23
|
resolveColorScaleDefinition,
|
|
24
|
+
resolveOffsetScaleDefinition,
|
|
23
25
|
resolveQuantitativeColorScaleDefinition
|
|
24
26
|
} from "../scales/definitions.js";
|
|
25
27
|
import {
|
|
@@ -33,9 +35,43 @@ import { applyMaterializationPlan } from "../../materialization/dependencies.js"
|
|
|
33
35
|
import { planEncodingRematerialization } from "../../materialization/encodings.js";
|
|
34
36
|
|
|
35
37
|
const COLOR_ENCODING_OPTIONS = Object.freeze([
|
|
36
|
-
"field", "target", "fieldType", "scale", "layout", "aggregate"
|
|
38
|
+
"field", "target", "fieldType", "scale", "palette", "layout", "aggregate"
|
|
37
39
|
]);
|
|
38
40
|
|
|
41
|
+
function resolveColorScaleOptions(args) {
|
|
42
|
+
const options = args.scale ?? {};
|
|
43
|
+
if (args.palette === undefined) return options;
|
|
44
|
+
if (options.palette !== undefined || options.range !== undefined) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
"encodeColor palette cannot be combined with scale.palette or scale.range."
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return { ...options, palette: args.palette };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function assertNoConstantColor(program, layer) {
|
|
53
|
+
const config = program.markConfigs[layer.id];
|
|
54
|
+
const hasConstant = {
|
|
55
|
+
point: config?.fill !== undefined,
|
|
56
|
+
line: config?.stroke !== undefined,
|
|
57
|
+
bar: config?.barAppearance?.fill !== undefined,
|
|
58
|
+
arc: config?.fill !== undefined,
|
|
59
|
+
rect: config?.fillExplicit === true
|
|
60
|
+
}[layer.mark.type] ?? false;
|
|
61
|
+
if (hasConstant) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`encodeColor cannot replace constant appearance on ${layer.mark.type} mark "${layer.id}".`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isRangedArea(layer) {
|
|
69
|
+
return layer.mark.type === "area" && (
|
|
70
|
+
layer.encoding?.x2 !== undefined ||
|
|
71
|
+
layer.encoding?.y2 !== undefined
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
39
75
|
function resolveColorLayout(layer, requested, barGrain) {
|
|
40
76
|
const existing = layer.encoding?.color === undefined
|
|
41
77
|
? undefined
|
|
@@ -60,15 +96,21 @@ function resolveColorLayout(layer, requested, barGrain) {
|
|
|
60
96
|
: undefined
|
|
61
97
|
);
|
|
62
98
|
|
|
63
|
-
if (["point", "line"].includes(layer.mark.type) && layout !== undefined) {
|
|
99
|
+
if (["point", "line", "rect"].includes(layer.mark.type) && layout !== undefined) {
|
|
64
100
|
throw new Error(`Color layout is not supported for ${layer.mark.type} marks.`);
|
|
65
101
|
}
|
|
66
102
|
if (layer.mark.type === "area" && layout === "group") {
|
|
67
103
|
throw new Error('Area color layout does not support "group".');
|
|
68
104
|
}
|
|
69
105
|
if (
|
|
70
|
-
layer.mark.type === "
|
|
71
|
-
|
|
106
|
+
layer.mark.type === "arc" &&
|
|
107
|
+
layout !== undefined &&
|
|
108
|
+
layout !== "overlay"
|
|
109
|
+
) {
|
|
110
|
+
throw new Error('Arc color layout currently supports only "overlay".');
|
|
111
|
+
}
|
|
112
|
+
if (
|
|
113
|
+
isRangedArea(layer) &&
|
|
72
114
|
layout !== "overlay"
|
|
73
115
|
) {
|
|
74
116
|
throw new Error('Ranged area color encoding supports only "overlay" layout.');
|
|
@@ -81,8 +123,9 @@ function applyColorLayoutCompanion(
|
|
|
81
123
|
{ target, layer, layout, scale, field }
|
|
82
124
|
) {
|
|
83
125
|
if (layout === undefined) return program;
|
|
126
|
+
if (layer.mark.type === "arc") return program;
|
|
84
127
|
if (layer.mark.type === "bar" && resolveBarGrain(layer) === BAR_GRAINS.ranged) return program;
|
|
85
|
-
if (layer
|
|
128
|
+
if (isRangedArea(layer)) {
|
|
86
129
|
return program;
|
|
87
130
|
}
|
|
88
131
|
const channels = layer.mark.type === "bar" ? resolveBarChannels(layer) : undefined;
|
|
@@ -98,16 +141,15 @@ function applyColorLayoutCompanion(
|
|
|
98
141
|
: "zero";
|
|
99
142
|
let next = program;
|
|
100
143
|
if (layout === "group") {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
next = next.encodeXOffset({
|
|
144
|
+
const offsetChannel = resolveBarOffsetChannel(layer);
|
|
145
|
+
const method = offsetChannel === "xOffset" ? "encodeXOffset" : "encodeYOffset";
|
|
146
|
+
next = next[method]({
|
|
105
147
|
field,
|
|
106
148
|
target,
|
|
107
149
|
scale: {
|
|
108
|
-
...(layer.encoding?.
|
|
150
|
+
...(layer.encoding?.[offsetChannel]?.scale === undefined
|
|
109
151
|
? {}
|
|
110
|
-
: { id: layer.encoding.
|
|
152
|
+
: { id: layer.encoding[offsetChannel].scale }),
|
|
111
153
|
domain: scale.domain
|
|
112
154
|
}
|
|
113
155
|
});
|
|
@@ -121,6 +163,40 @@ function applyColorLayoutCompanion(
|
|
|
121
163
|
});
|
|
122
164
|
}
|
|
123
165
|
|
|
166
|
+
function preSynchronizeGroupedOffset(
|
|
167
|
+
program,
|
|
168
|
+
{ target, layer, layout, field, fieldType, scale, requestedScale }
|
|
169
|
+
) {
|
|
170
|
+
if (
|
|
171
|
+
layer.mark.type !== "bar" ||
|
|
172
|
+
layout !== "group" ||
|
|
173
|
+
layer.encoding?.color?.scale !== scale.id ||
|
|
174
|
+
!Object.keys(requestedScale).some(key => key !== "id")
|
|
175
|
+
) {
|
|
176
|
+
return program;
|
|
177
|
+
}
|
|
178
|
+
const offsetChannel = resolveBarOffsetChannel(layer);
|
|
179
|
+
const offset = layer.encoding?.[offsetChannel];
|
|
180
|
+
if (offset?.scale === undefined) return program;
|
|
181
|
+
const offsetScale = resolveOffsetScaleDefinition(program, {
|
|
182
|
+
id: offset.scale,
|
|
183
|
+
domain: scale.domain
|
|
184
|
+
}, offsetChannel);
|
|
185
|
+
return program
|
|
186
|
+
.editSemantic({
|
|
187
|
+
property: `layer[${target}].encoding.${offsetChannel}.field`,
|
|
188
|
+
value: field
|
|
189
|
+
})
|
|
190
|
+
.editSemantic({
|
|
191
|
+
property: `layer[${target}].encoding.${offsetChannel}.fieldType`,
|
|
192
|
+
value: fieldType
|
|
193
|
+
})
|
|
194
|
+
.editSemantic({
|
|
195
|
+
property: `scale[${offsetScale.id}].domain`,
|
|
196
|
+
value: offsetScale.domain
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
124
200
|
function encodeContinuousColor(program, args) {
|
|
125
201
|
if (!["quantitative", "temporal"].includes(args.fieldType)) {
|
|
126
202
|
throw new Error(`Unsupported color field type "${args.fieldType}".`);
|
|
@@ -131,12 +207,13 @@ function encodeContinuousColor(program, args) {
|
|
|
131
207
|
const { id: target, dataset, layer } = resolveTarget(
|
|
132
208
|
program,
|
|
133
209
|
args.target,
|
|
134
|
-
["point", "bar"],
|
|
210
|
+
["point", "bar", "rect"],
|
|
135
211
|
"continuous color mark"
|
|
136
212
|
);
|
|
213
|
+
assertNoConstantColor(program, layer);
|
|
137
214
|
const requestedScale = resolveReassignmentScaleOptions(
|
|
138
215
|
layer.encoding?.color,
|
|
139
|
-
args
|
|
216
|
+
resolveColorScaleOptions(args)
|
|
140
217
|
);
|
|
141
218
|
const scale = resolveQuantitativeColorScaleDefinition(
|
|
142
219
|
program,
|
|
@@ -150,16 +227,16 @@ function encodeContinuousColor(program, args) {
|
|
|
150
227
|
}
|
|
151
228
|
if (
|
|
152
229
|
layer.mark.type === "bar" &&
|
|
153
|
-
layer.encoding?.color?.fieldType
|
|
230
|
+
["nominal", "ordinal"].includes(layer.encoding?.color?.fieldType)
|
|
154
231
|
) {
|
|
155
232
|
throw new Error(
|
|
156
233
|
"Continuous bar color cannot replace an existing nominal color layout."
|
|
157
234
|
);
|
|
158
235
|
}
|
|
159
236
|
let aggregate;
|
|
160
|
-
if (layer.mark.type
|
|
237
|
+
if (["point", "rect"].includes(layer.mark.type)) {
|
|
161
238
|
if (args.aggregate !== undefined) {
|
|
162
|
-
throw new Error(
|
|
239
|
+
throw new Error(`${layer.mark.type} continuous color does not support aggregate.`);
|
|
163
240
|
}
|
|
164
241
|
} else {
|
|
165
242
|
if (args.fieldType !== "quantitative") {
|
|
@@ -184,7 +261,11 @@ function encodeContinuousColor(program, args) {
|
|
|
184
261
|
validateAggregateFieldType(aggregate, args.fieldType);
|
|
185
262
|
validateAggregateFieldValues(dataset.values, args.field, args.fieldType);
|
|
186
263
|
}
|
|
187
|
-
if (
|
|
264
|
+
if (layer.mark.type === "rect") {
|
|
265
|
+
readScaleField(dataset.values, args.field, args.fieldType, {
|
|
266
|
+
allowUnknown: true
|
|
267
|
+
});
|
|
268
|
+
} else if (Object.hasOwn(scale, "unknown")) {
|
|
188
269
|
readScaleField(dataset.values, args.field, args.fieldType, {
|
|
189
270
|
allowUnknown: true
|
|
190
271
|
});
|
|
@@ -231,22 +312,23 @@ const encodeColor = action(
|
|
|
231
312
|
function (args = {}) {
|
|
232
313
|
validateOptions(args, COLOR_ENCODING_OPTIONS, "encodeColor");
|
|
233
314
|
const requestedFieldType = args.fieldType ?? "nominal";
|
|
234
|
-
if (
|
|
315
|
+
if (!["nominal", "ordinal"].includes(requestedFieldType)) {
|
|
235
316
|
return encodeContinuousColor(this, {
|
|
236
317
|
...args,
|
|
237
318
|
fieldType: requestedFieldType
|
|
238
319
|
});
|
|
239
320
|
}
|
|
240
321
|
if (args.aggregate !== undefined) {
|
|
241
|
-
throw new Error("
|
|
322
|
+
throw new Error("Categorical color does not support aggregate.");
|
|
242
323
|
}
|
|
243
|
-
const fieldType =
|
|
324
|
+
const fieldType = validateCategoricalFieldType(requestedFieldType);
|
|
244
325
|
const { id: target, dataset, layer } = resolveTarget(
|
|
245
326
|
this,
|
|
246
327
|
args.target,
|
|
247
|
-
["point", "line", "bar", "area"],
|
|
328
|
+
["point", "line", "bar", "area", "arc", "rect"],
|
|
248
329
|
"color mark"
|
|
249
330
|
);
|
|
331
|
+
assertNoConstantColor(this, layer);
|
|
250
332
|
if (
|
|
251
333
|
layer.mark.type === "area" &&
|
|
252
334
|
(layer.encoding?.group?.field === undefined ||
|
|
@@ -267,15 +349,19 @@ const encodeColor = action(
|
|
|
267
349
|
const layout = resolveColorLayout(layer, args.layout, barGrain);
|
|
268
350
|
const requestedScale = resolveReassignmentScaleOptions(
|
|
269
351
|
layer.encoding?.color,
|
|
270
|
-
args
|
|
352
|
+
resolveColorScaleOptions(args)
|
|
271
353
|
);
|
|
272
354
|
const scale = resolveColorScaleDefinition(this, requestedScale);
|
|
273
355
|
if (Object.hasOwn(scale, "unknown") && layer.mark.type !== "point") {
|
|
274
356
|
throw new Error(
|
|
275
|
-
"
|
|
357
|
+
"Categorical color scale unknown currently requires a row-owned point mark."
|
|
276
358
|
);
|
|
277
359
|
}
|
|
278
|
-
if (
|
|
360
|
+
if (layer.mark.type === "rect") {
|
|
361
|
+
readScaleField(dataset.values, args.field, fieldType, {
|
|
362
|
+
allowUnknown: true
|
|
363
|
+
});
|
|
364
|
+
} else if (Object.hasOwn(scale, "unknown")) {
|
|
279
365
|
readScaleField(dataset.values, args.field, fieldType, {
|
|
280
366
|
allowUnknown: true
|
|
281
367
|
});
|
|
@@ -302,6 +388,15 @@ const encodeColor = action(
|
|
|
302
388
|
value: layout
|
|
303
389
|
});
|
|
304
390
|
}
|
|
391
|
+
next = preSynchronizeGroupedOffset(next, {
|
|
392
|
+
target,
|
|
393
|
+
layer,
|
|
394
|
+
layout,
|
|
395
|
+
field: args.field,
|
|
396
|
+
fieldType,
|
|
397
|
+
scale,
|
|
398
|
+
requestedScale
|
|
399
|
+
});
|
|
305
400
|
next = applyEncodingScale(next, scale, requestedScale, {
|
|
306
401
|
reassignment: layer.encoding?.color?.scale === scale.id
|
|
307
402
|
});
|
|
@@ -8,6 +8,7 @@ import { registerPositionEncodingActions } from "./position.js";
|
|
|
8
8
|
import { registerRangedEncodingActions } from "./ranged.js";
|
|
9
9
|
import { registerStrokeDashEncodingActions } from "./strokeDash.js";
|
|
10
10
|
import { registerRuleAppearanceEncodingActions } from "./ruleAppearance.js";
|
|
11
|
+
import { registerTextEncodingAction } from "./text.js";
|
|
11
12
|
|
|
12
13
|
export function registerEncodingActions(ProgramClass) {
|
|
13
14
|
registerPositionEncodingActions(ProgramClass);
|
|
@@ -20,4 +21,5 @@ export function registerEncodingActions(ProgramClass) {
|
|
|
20
21
|
registerStrokeDashEncodingActions(ProgramClass);
|
|
21
22
|
registerAppearanceEncodingAction(ProgramClass);
|
|
22
23
|
registerRuleAppearanceEncodingActions(ProgramClass);
|
|
24
|
+
registerTextEncodingAction(ProgramClass);
|
|
23
25
|
}
|