ggaction 0.0.2 → 0.0.4
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 +55 -2
- package/README.md +95 -72
- package/package.json +15 -5
- 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/data/regression.js +1 -1
- package/src/actions/encodings/appearance.js +12 -1
- package/src/actions/encodings/barWidth.js +5 -3
- package/src/actions/encodings/color/continuous.js +137 -0
- package/src/actions/encodings/color/index.js +151 -0
- package/src/actions/encodings/color/layout.js +91 -0
- package/src/actions/encodings/color/policy.js +88 -0
- package/src/actions/encodings/index.js +4 -2
- package/src/actions/encodings/offset.js +91 -73
- package/src/actions/encodings/position/apply.js +66 -0
- package/src/actions/encodings/position/index.js +130 -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 +58 -9
- package/src/actions/encodings/ranged.js +30 -8
- package/src/actions/encodings/shared.js +17 -21
- package/src/actions/encodings/strokeDash.js +1 -1
- 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 +22 -19
- 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 +10 -5
- package/src/actions/guides/axes/titles.js +8 -6
- package/src/actions/guides/grids/grid.js +144 -15
- package/src/actions/guides/grids/resolve.js +6 -14
- 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 +7 -3
- 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 +6 -6
- package/src/actions/guides/legends/continuous/opacity.js +1 -1
- package/src/actions/guides/legends/edit.js +4 -26
- 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 +46 -0
- package/src/actions/guides/legends/size.js +13 -15
- package/src/actions/guides/legends/target.js +25 -0
- package/src/actions/guides/polar/axes/facade.js +219 -0
- package/src/actions/guides/polar/axes/index.js +55 -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/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/guides/tickValues.js +1 -1
- package/src/actions/index.js +4 -0
- package/src/actions/intervals/resolve.js +1 -1
- 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/edit.js +1 -1
- 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 +26 -4
- package/src/actions/regression/components.js +2 -2
- package/src/actions/regression/create.js +19 -2
- 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/common.js +91 -0
- package/src/actions/scales/consumers/families.js +47 -0
- package/src/actions/scales/consumers/index.js +41 -0
- package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +14 -127
- package/src/actions/scales/create.js +1 -1
- package/src/actions/scales/definitions.js +15 -6
- package/src/actions/scales/edit.js +45 -22
- package/src/actions/scales/materialize.js +58 -409
- 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 +49 -65
- package/src/core/action.js +10 -0
- package/src/core/compositionState.js +169 -0
- package/src/core/materializationState.js +65 -0
- package/src/core/programState.js +28 -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/areaSeries.js +1 -1
- package/src/grammar/bars/aggregate.js +3 -3
- 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 +21 -2
- package/src/grammar/facets/dependencies.js +185 -0
- package/src/grammar/facets/guides.js +257 -0
- package/src/grammar/facets/index.js +122 -0
- package/src/grammar/facets/scales.js +254 -0
- package/src/grammar/interval.js +2 -96
- package/src/grammar/lineSeries.js +112 -4
- package/src/grammar/palettes.js +5 -7
- 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/regression/derive.js +97 -0
- package/src/grammar/regression/index.js +22 -0
- package/src/grammar/regression/models.js +282 -0
- package/src/grammar/regression/parameters.js +106 -0
- package/src/grammar/rules.js +1 -1
- package/src/grammar/scales/color.js +4 -2
- 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/statistics/studentT.js +98 -0
- package/src/grammar/text.js +71 -0
- package/src/grammar/ticks.js +50 -13
- package/src/grammar/transforms.js +1 -1
- 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 +6 -3
- package/src/materialization/bars/grouped.js +75 -37
- package/src/materialization/bars/histogram.js +1 -1
- package/src/materialization/bars/ranged.js +1 -1
- package/src/materialization/composition.js +152 -0
- package/src/materialization/compositionSnapshot.js +114 -0
- package/src/materialization/density.js +1 -1
- package/src/materialization/dependencies.js +47 -73
- package/src/materialization/encodings.js +14 -28
- package/src/materialization/facetGuides/index.js +2 -0
- package/src/materialization/facetGuides/legacyCategorical.js +125 -0
- package/src/materialization/facetGuides/placement.js +126 -0
- package/src/materialization/facetGuides/preparation.js +188 -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/capabilities.js +141 -0
- package/src/materialization/marks/index.js +105 -0
- package/src/materialization/marks/policies.js +87 -0
- 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/scaleGuideDependencies.js +94 -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 +25 -17
- package/src/materialization/text.js +156 -0
- package/src/renderers/canvas/index.js +68 -22
- package/src/renderers/canvas/text.js +14 -1
- package/types/extension.d.ts +5 -1
- package/types/index.d.ts +56 -1
- package/types/program.d.ts +664 -27
- package/src/actions/encodings/color.js +0 -329
- package/src/actions/encodings/position.js +0 -176
- package/src/actions/marks/line.js +0 -248
- package/src/grammar/regression.js +0 -578
- package/src/grammar/scales.js +0 -1
- package/src/materialization/marks.js +0 -110
- package/src/materialization/selection/items.js +0 -503
|
@@ -11,6 +11,15 @@ import {
|
|
|
11
11
|
resolveCanvasGraphicPlacement
|
|
12
12
|
} from "../../materialization/graphicHierarchy.js";
|
|
13
13
|
|
|
14
|
+
function assertTitleScope(program, operation) {
|
|
15
|
+
if (
|
|
16
|
+
program.compositionSpec !== undefined &&
|
|
17
|
+
program.compositionSpec.type !== "facet"
|
|
18
|
+
) {
|
|
19
|
+
throw new Error(`${operation} is not available on this composition ChartProgram.`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
function hasRotation(graphic) {
|
|
15
24
|
const properties = graphic.items?.map(child => child.properties) ??
|
|
16
25
|
[graphic.properties];
|
|
@@ -179,8 +188,13 @@ export const rematerializeTitle = action(
|
|
|
179
188
|
);
|
|
180
189
|
|
|
181
190
|
export const createTitle = action(
|
|
182
|
-
{
|
|
191
|
+
{
|
|
192
|
+
op: "createTitle",
|
|
193
|
+
description: "Create a chart title and optional subtitle.",
|
|
194
|
+
scope: "any"
|
|
195
|
+
},
|
|
183
196
|
function (args = {}) {
|
|
197
|
+
assertTitleScope(this, "createTitle");
|
|
184
198
|
const options = normalizeTitleOptions(args);
|
|
185
199
|
if (Object.keys(this.semanticSpec.title).length > 0) {
|
|
186
200
|
throw new Error("createTitle requires missing semantic title state.");
|
|
@@ -197,6 +211,9 @@ export const createTitle = action(
|
|
|
197
211
|
next = next.editSemantic({ property: "title.subtitle", value: subtitle });
|
|
198
212
|
}
|
|
199
213
|
next = next._withTitleConfig(config);
|
|
214
|
+
if (next.compositionSpec?.type === "facet") {
|
|
215
|
+
return next.materializeComposition();
|
|
216
|
+
}
|
|
200
217
|
resolveTitleLayout(next, config);
|
|
201
218
|
next = next.createTitleText();
|
|
202
219
|
if (subtitle !== undefined) next = next.createSubtitleText();
|
|
@@ -205,8 +222,13 @@ export const createTitle = action(
|
|
|
205
222
|
);
|
|
206
223
|
|
|
207
224
|
export const editTitle = action(
|
|
208
|
-
{
|
|
225
|
+
{
|
|
226
|
+
op: "editTitle",
|
|
227
|
+
description: "Edit one stable chart title resource.",
|
|
228
|
+
scope: "any"
|
|
229
|
+
},
|
|
209
230
|
function (args = {}) {
|
|
231
|
+
assertTitleScope(this, "editTitle");
|
|
210
232
|
if (this.semanticSpec.title.text === undefined) {
|
|
211
233
|
throw new Error("editTitle requires an existing chart title.");
|
|
212
234
|
}
|
|
@@ -229,7 +251,46 @@ export const editTitle = action(
|
|
|
229
251
|
});
|
|
230
252
|
}
|
|
231
253
|
next = next._withTitleConfig(normalized.config);
|
|
254
|
+
if (next.compositionSpec?.type === "facet") {
|
|
255
|
+
return next.materializeComposition();
|
|
256
|
+
}
|
|
232
257
|
resolveTitleLayout(next, normalized.config);
|
|
233
258
|
return next.rematerializeTitle();
|
|
234
259
|
}
|
|
235
260
|
);
|
|
261
|
+
|
|
262
|
+
export const removeTitle = action(
|
|
263
|
+
{
|
|
264
|
+
op: "removeTitle",
|
|
265
|
+
description: "Remove the complete chart title resource.",
|
|
266
|
+
scope: "any"
|
|
267
|
+
},
|
|
268
|
+
function (args = {}) {
|
|
269
|
+
assertTitleScope(this, "removeTitle");
|
|
270
|
+
noOptions(args, "removeTitle");
|
|
271
|
+
if (
|
|
272
|
+
this.semanticSpec.title.text === undefined &&
|
|
273
|
+
this.titleConfig === undefined &&
|
|
274
|
+
this.graphicSpec.objects.chartTitle === undefined &&
|
|
275
|
+
this.graphicSpec.objects.chartSubtitle === undefined
|
|
276
|
+
) {
|
|
277
|
+
throw new Error("removeTitle requires an existing chart title.");
|
|
278
|
+
}
|
|
279
|
+
let next = this;
|
|
280
|
+
if (next.semanticSpec.title.subtitle !== undefined) {
|
|
281
|
+
next = next.editSemantic({ property: "title.subtitle", remove: true });
|
|
282
|
+
}
|
|
283
|
+
if (next.semanticSpec.title.text !== undefined) {
|
|
284
|
+
next = next.editSemantic({ property: "title.text", remove: true });
|
|
285
|
+
}
|
|
286
|
+
for (const id of ["chartTitle", "chartSubtitle"]) {
|
|
287
|
+
if (next.graphicSpec.objects[id] !== undefined) {
|
|
288
|
+
next = next.editGraphics({ target: id, remove: true });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
next = next._withoutMaterializationConfig(["title"]);
|
|
292
|
+
return next.compositionSpec?.type === "facet"
|
|
293
|
+
? next.materializeComposition()
|
|
294
|
+
: next;
|
|
295
|
+
}
|
|
296
|
+
);
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
createTitle,
|
|
4
4
|
createTitleText,
|
|
5
5
|
editTitle,
|
|
6
|
+
removeTitle,
|
|
6
7
|
editSubtitleText,
|
|
7
8
|
editTitleText,
|
|
8
9
|
rematerializeTitle
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
export function registerTitleActions(ProgramClass) {
|
|
12
13
|
ProgramClass.prototype.createTitle = createTitle;
|
|
13
14
|
ProgramClass.prototype.editTitle = editTitle;
|
|
15
|
+
ProgramClass.prototype.removeTitle = removeTitle;
|
|
14
16
|
ProgramClass.prototype.createTitleText = createTitleText;
|
|
15
17
|
ProgramClass.prototype.editTitleText = editTitleText;
|
|
16
18
|
ProgramClass.prototype.createSubtitleText = createSubtitleText;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isPlainObject } from "../../core/immutable.js";
|
|
2
2
|
import {
|
|
3
3
|
validateKeys,
|
|
4
|
+
validateOptionObject,
|
|
4
5
|
validateNonEmptyString,
|
|
5
6
|
validateNonNegativeFinite,
|
|
6
7
|
validatePositiveFinite
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
import { resolveGraphicBounds } from "../../layout/canvas.js";
|
|
9
10
|
import { resolveConcreteGraphicBounds } from "../../grammar/schemas/graphicBounds.js";
|
|
10
11
|
import {
|
|
12
|
+
alignedTextAnchor,
|
|
11
13
|
alignedTitleAnchor,
|
|
12
14
|
buildTitleReadingBlock,
|
|
13
15
|
layoutBoundsIntersect,
|
|
@@ -15,6 +17,8 @@ import {
|
|
|
15
17
|
unionTitleBounds
|
|
16
18
|
} from "../../layout/title.js";
|
|
17
19
|
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from "../../theme/defaults.js";
|
|
20
|
+
import { findCanvasGraphic } from
|
|
21
|
+
"../../materialization/graphicHierarchy.js";
|
|
18
22
|
|
|
19
23
|
const OPTIONS = Object.freeze([
|
|
20
24
|
"text", "subtitle", "position", "align", "offset", "gap", "titleStyle",
|
|
@@ -129,10 +133,7 @@ function normalizeConfig(args, defaults, { hasSubtitle }) {
|
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
export function normalizeTitleOptions(args) {
|
|
132
|
-
|
|
133
|
-
throw new TypeError("createTitle options must be a plain object.");
|
|
134
|
-
}
|
|
135
|
-
validateKeys(args, OPTIONS, "createTitle");
|
|
136
|
+
validateOptionObject(args, OPTIONS, "createTitle");
|
|
136
137
|
const text = validateTitleString(args.text, "Chart title text");
|
|
137
138
|
const subtitle = args.subtitle === undefined
|
|
138
139
|
? undefined
|
|
@@ -145,13 +146,11 @@ export function normalizeTitleOptions(args) {
|
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
export function normalizeTitleEditOptions(args, previous, semanticTitle) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
throw new Error("editTitle requires at least one change.");
|
|
154
|
-
}
|
|
149
|
+
validateOptionObject(args, OPTIONS, "editTitle", {
|
|
150
|
+
allowEmpty: false,
|
|
151
|
+
emptyMessage: "editTitle requires at least one change.",
|
|
152
|
+
emptyError: Error
|
|
153
|
+
});
|
|
155
154
|
const text = args.text === undefined
|
|
156
155
|
? semanticTitle.text
|
|
157
156
|
: validateTitleString(args.text, "Chart title text");
|
|
@@ -233,7 +232,7 @@ function validateLayout(program, position, titleBounds, plot, canvas) {
|
|
|
233
232
|
|
|
234
233
|
export function resolveTitleLayout(program, config) {
|
|
235
234
|
const plot = resolveGraphicBounds(program);
|
|
236
|
-
const canvas = program
|
|
235
|
+
const canvas = findCanvasGraphic(program);
|
|
237
236
|
if (
|
|
238
237
|
plot === undefined ||
|
|
239
238
|
![plot.x, plot.y, plot.width, plot.height].every(Number.isFinite) ||
|
|
@@ -256,11 +255,7 @@ export function resolveTitleLayout(program, config) {
|
|
|
256
255
|
let title;
|
|
257
256
|
let subtitle;
|
|
258
257
|
if (horizontal) {
|
|
259
|
-
const x = config.align
|
|
260
|
-
? plot.x
|
|
261
|
-
: config.align === "center"
|
|
262
|
-
? plot.x + plot.width / 2
|
|
263
|
-
: plot.x + plot.width;
|
|
258
|
+
const x = alignedTextAnchor(plot.x, plot.width, config.align);
|
|
264
259
|
const blockTop = config.position === "top"
|
|
265
260
|
? 16 + config.offset
|
|
266
261
|
: plot.y + plot.height + config.offset;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { chart } from "./ChartProgram.js";
|
|
2
|
+
import { hconcatAction, vconcatAction } from "./actions/composition/actions.js";
|
|
3
|
+
|
|
4
|
+
export function hconcat(options) {
|
|
5
|
+
return hconcatAction.call(chart(), options);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function vconcat(options) {
|
|
9
|
+
return vconcatAction.call(chart(), options);
|
|
10
|
+
}
|
package/src/core/ChartProgram.js
CHANGED
|
@@ -6,43 +6,23 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
cloneAndFreeze,
|
|
8
8
|
freezeOwned,
|
|
9
|
-
|
|
10
|
-
isPlainObject,
|
|
11
|
-
removeOwnedPath
|
|
9
|
+
isPlainObject
|
|
12
10
|
} from "./immutable.js";
|
|
13
11
|
import {
|
|
14
12
|
createEmptyGraphicSpec,
|
|
15
13
|
createEmptySemanticSpec,
|
|
16
14
|
createTraceRoot
|
|
17
15
|
} from "./specs.js";
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
canvasConfig
|
|
29
|
-
) {
|
|
30
|
-
return cloneAndFreeze({
|
|
31
|
-
marks: markConfigs,
|
|
32
|
-
guides: guideConfigs,
|
|
33
|
-
...(canvasConfig === undefined ? {} : { canvas: canvasConfig }),
|
|
34
|
-
...(titleConfig === undefined ? {} : { title: titleConfig })
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function updateConfigPath(value, path, config) {
|
|
39
|
-
if (path.length === 0) return cloneAndFreeze(config);
|
|
40
|
-
const [key, ...rest] = path;
|
|
41
|
-
return freezeOwned({
|
|
42
|
-
...value,
|
|
43
|
-
[key]: updateConfigPath(value?.[key] ?? {}, rest, config)
|
|
44
|
-
});
|
|
45
|
-
}
|
|
16
|
+
import {
|
|
17
|
+
LEGEND_CONFIG_KINDS
|
|
18
|
+
} from "./vocabulary.js";
|
|
19
|
+
import { ownCompositionSpec } from "./compositionState.js";
|
|
20
|
+
import {
|
|
21
|
+
createMaterializationConfigs,
|
|
22
|
+
removeMaterializationConfig,
|
|
23
|
+
setMaterializationConfig
|
|
24
|
+
} from "./materializationState.js";
|
|
25
|
+
import { ownChildPrograms, ownProgramState } from "./programState.js";
|
|
46
26
|
|
|
47
27
|
export class ChartProgram {
|
|
48
28
|
constructor({
|
|
@@ -54,14 +34,16 @@ export class ChartProgram {
|
|
|
54
34
|
guideConfigs = {},
|
|
55
35
|
titleConfig,
|
|
56
36
|
canvasConfig,
|
|
37
|
+
children = {},
|
|
38
|
+
compositionSpec,
|
|
57
39
|
context = {},
|
|
58
40
|
trace = createTraceRoot(),
|
|
59
41
|
actionStack = [],
|
|
60
42
|
actionSequence
|
|
61
43
|
} = {}) {
|
|
62
|
-
this.semanticSpec =
|
|
63
|
-
this.graphicSpec =
|
|
64
|
-
this.resolvedScales =
|
|
44
|
+
this.semanticSpec = ownProgramState(semanticSpec);
|
|
45
|
+
this.graphicSpec = ownProgramState(graphicSpec);
|
|
46
|
+
this.resolvedScales = ownProgramState(resolvedScales);
|
|
65
47
|
this.materializationConfigs = materializationConfigs === undefined
|
|
66
48
|
? createMaterializationConfigs(
|
|
67
49
|
markConfigs,
|
|
@@ -69,10 +51,12 @@ export class ChartProgram {
|
|
|
69
51
|
titleConfig,
|
|
70
52
|
canvasConfig
|
|
71
53
|
)
|
|
72
|
-
:
|
|
73
|
-
this.
|
|
74
|
-
this.
|
|
75
|
-
this.
|
|
54
|
+
: ownProgramState(materializationConfigs);
|
|
55
|
+
this.children = ownChildPrograms(children, ChartProgram);
|
|
56
|
+
this.compositionSpec = ownCompositionSpec(compositionSpec, this.children);
|
|
57
|
+
this.context = ownProgramState(context);
|
|
58
|
+
this.trace = ownProgramState(trace);
|
|
59
|
+
this.actionStack = ownProgramState(actionStack);
|
|
76
60
|
Object.defineProperty(this, "_actionSequence", {
|
|
77
61
|
value: actionSequence ?? countActionNodes(this.trace),
|
|
78
62
|
enumerable: false
|
|
@@ -118,6 +102,8 @@ export class ChartProgram {
|
|
|
118
102
|
graphicSpec = this.graphicSpec,
|
|
119
103
|
resolvedScales = this.resolvedScales,
|
|
120
104
|
materializationConfigs = this.materializationConfigs,
|
|
105
|
+
children = this.children,
|
|
106
|
+
compositionSpec = this.compositionSpec,
|
|
121
107
|
context = this.context,
|
|
122
108
|
trace = this.trace,
|
|
123
109
|
actionStack = this.actionStack,
|
|
@@ -128,6 +114,8 @@ export class ChartProgram {
|
|
|
128
114
|
graphicSpec,
|
|
129
115
|
resolvedScales,
|
|
130
116
|
materializationConfigs,
|
|
117
|
+
children,
|
|
118
|
+
compositionSpec,
|
|
131
119
|
context,
|
|
132
120
|
trace,
|
|
133
121
|
actionStack,
|
|
@@ -135,6 +123,22 @@ export class ChartProgram {
|
|
|
135
123
|
});
|
|
136
124
|
}
|
|
137
125
|
|
|
126
|
+
_assertUnitProgram(operation) {
|
|
127
|
+
if (this.compositionSpec !== undefined) {
|
|
128
|
+
throw new Error(`${operation} is not available on a composition ChartProgram.`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
_assertCompositionProgram(operation) {
|
|
133
|
+
if (this.compositionSpec === undefined) {
|
|
134
|
+
throw new Error(`${operation} requires a composition ChartProgram.`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
_withCompositionState({ children, compositionSpec, graphicSpec = this.graphicSpec }) {
|
|
139
|
+
return this._clone({ children, compositionSpec, graphicSpec });
|
|
140
|
+
}
|
|
141
|
+
|
|
138
142
|
_withContext(patch = {}) {
|
|
139
143
|
if (!isPlainObject(patch)) {
|
|
140
144
|
throw new TypeError("Context patch must be a plain object.");
|
|
@@ -188,18 +192,8 @@ export class ChartProgram {
|
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
_withMaterializationConfig(path, config) {
|
|
191
|
-
if (
|
|
192
|
-
!Array.isArray(path) ||
|
|
193
|
-
path.length === 0 ||
|
|
194
|
-
!path.every(key => typeof key === "string" && key.length > 0)
|
|
195
|
-
) {
|
|
196
|
-
throw new TypeError("Materialization config path must contain names.");
|
|
197
|
-
}
|
|
198
|
-
if (!isPlainObject(config)) {
|
|
199
|
-
throw new TypeError("Materialization config must be a plain object.");
|
|
200
|
-
}
|
|
201
195
|
return this._clone({
|
|
202
|
-
materializationConfigs:
|
|
196
|
+
materializationConfigs: setMaterializationConfig(
|
|
203
197
|
this.materializationConfigs,
|
|
204
198
|
path,
|
|
205
199
|
config
|
|
@@ -208,22 +202,12 @@ export class ChartProgram {
|
|
|
208
202
|
}
|
|
209
203
|
|
|
210
204
|
_withoutMaterializationConfig(path) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
path
|
|
214
|
-
|
|
215
|
-
) {
|
|
216
|
-
throw new TypeError("Materialization config path must contain names.");
|
|
217
|
-
}
|
|
218
|
-
const removed = removeOwnedPath(this.materializationConfigs, path);
|
|
205
|
+
const removed = removeMaterializationConfig(
|
|
206
|
+
this.materializationConfigs,
|
|
207
|
+
path
|
|
208
|
+
);
|
|
219
209
|
if (!removed.removed) return this;
|
|
220
|
-
return this._clone({
|
|
221
|
-
materializationConfigs: freezeOwned({
|
|
222
|
-
...removed.value,
|
|
223
|
-
marks: removed.value.marks ?? freezeOwned({}),
|
|
224
|
-
guides: removed.value.guides ?? freezeOwned({})
|
|
225
|
-
})
|
|
226
|
-
});
|
|
210
|
+
return this._clone({ materializationConfigs: removed.value });
|
|
227
211
|
}
|
|
228
212
|
|
|
229
213
|
_withGuideConfig(channel, component, config) {
|
|
@@ -257,7 +241,7 @@ export class ChartProgram {
|
|
|
257
241
|
}
|
|
258
242
|
|
|
259
243
|
_withGridConfig(direction, config) {
|
|
260
|
-
if (!["horizontal", "vertical"].includes(direction)) {
|
|
244
|
+
if (!["horizontal", "vertical", "theta", "radial"].includes(direction)) {
|
|
261
245
|
throw new Error(`Unknown grid direction "${direction}".`);
|
|
262
246
|
}
|
|
263
247
|
if (!isPlainObject(config)) {
|
package/src/core/action.js
CHANGED
|
@@ -12,6 +12,8 @@ function summarizeObject(value, ancestors = new WeakSet()) {
|
|
|
12
12
|
summary[`${key}Count`] = item.length;
|
|
13
13
|
} else if (isPlainObject(item)) {
|
|
14
14
|
summary[key] = summarizeObject(item, ancestors);
|
|
15
|
+
} else if (item !== null && typeof item === "object") {
|
|
16
|
+
summary[`${key}Type`] = item.constructor?.name ?? "object";
|
|
15
17
|
} else {
|
|
16
18
|
summary[key] = item;
|
|
17
19
|
}
|
|
@@ -100,11 +102,19 @@ export function action(metadata, implementation) {
|
|
|
100
102
|
throw new TypeError("Action implementation must be a function.");
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
const scope = metadata.scope ?? "unit";
|
|
106
|
+
if (!["unit", "composition", "any"].includes(scope)) {
|
|
107
|
+
throw new Error(`Unknown action scope "${scope}".`);
|
|
108
|
+
}
|
|
109
|
+
|
|
103
110
|
return function wrappedAction(args = {}) {
|
|
104
111
|
if (!isPlainObject(args)) {
|
|
105
112
|
throw new TypeError("Action arguments must be a plain object.");
|
|
106
113
|
}
|
|
107
114
|
|
|
115
|
+
if (scope === "unit") this._assertUnitProgram(metadata.op);
|
|
116
|
+
if (scope === "composition") this._assertCompositionProgram(metadata.op);
|
|
117
|
+
|
|
108
118
|
const entered = this._enterAction({
|
|
109
119
|
...metadata,
|
|
110
120
|
args: summarizeArgs(args)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { isPlainObject } from "./immutable.js";
|
|
2
|
+
import { ownProgramState } from "./programState.js";
|
|
3
|
+
import {
|
|
4
|
+
FACET_SCALE_CHANNELS,
|
|
5
|
+
FACET_SCALE_RESOLUTIONS
|
|
6
|
+
} from "./vocabulary.js";
|
|
7
|
+
|
|
8
|
+
function validateFacetSpec(compositionSpec) {
|
|
9
|
+
if (compositionSpec.direction !== undefined) {
|
|
10
|
+
throw new Error("Facet compositionSpec does not use direction.");
|
|
11
|
+
}
|
|
12
|
+
if (
|
|
13
|
+
!Number.isInteger(compositionSpec.columns) ||
|
|
14
|
+
compositionSpec.columns <= 0 ||
|
|
15
|
+
compositionSpec.columns > compositionSpec.children.length
|
|
16
|
+
) {
|
|
17
|
+
throw new RangeError(
|
|
18
|
+
"Facet compositionSpec.columns must be a positive integer no larger than its children."
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
if (!isPlainObject(compositionSpec.facet)) {
|
|
22
|
+
throw new TypeError("Facet compositionSpec.facet must be a plain object.");
|
|
23
|
+
}
|
|
24
|
+
const facetKeys = ["data", "field", "values", "scales", "guides"];
|
|
25
|
+
const unknownFacet = Object.keys(compositionSpec.facet).find(
|
|
26
|
+
key => !facetKeys.includes(key)
|
|
27
|
+
);
|
|
28
|
+
if (unknownFacet !== undefined) {
|
|
29
|
+
throw new Error(`Unknown compositionSpec.facet property "${unknownFacet}".`);
|
|
30
|
+
}
|
|
31
|
+
for (const property of ["data", "field"]) {
|
|
32
|
+
if (
|
|
33
|
+
typeof compositionSpec.facet[property] !== "string" ||
|
|
34
|
+
compositionSpec.facet[property].length === 0
|
|
35
|
+
) {
|
|
36
|
+
throw new TypeError(`compositionSpec.facet.${property} must be a non-empty string.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const values = compositionSpec.facet.values;
|
|
40
|
+
if (
|
|
41
|
+
!Array.isArray(values) ||
|
|
42
|
+
values.length !== compositionSpec.children.length ||
|
|
43
|
+
values.some(value => !(
|
|
44
|
+
typeof value === "string" ||
|
|
45
|
+
typeof value === "boolean" ||
|
|
46
|
+
(typeof value === "number" && Number.isFinite(value))
|
|
47
|
+
)) ||
|
|
48
|
+
new Set(values).size !== values.length
|
|
49
|
+
) {
|
|
50
|
+
throw new TypeError(
|
|
51
|
+
"compositionSpec.facet.values must contain one unique scalar per child."
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const scales = compositionSpec.facet.scales;
|
|
55
|
+
if (
|
|
56
|
+
!isPlainObject(scales) ||
|
|
57
|
+
Object.keys(scales).length !== FACET_SCALE_CHANNELS.length ||
|
|
58
|
+
FACET_SCALE_CHANNELS.some(channel =>
|
|
59
|
+
!FACET_SCALE_RESOLUTIONS.includes(scales[channel])
|
|
60
|
+
) ||
|
|
61
|
+
Object.keys(scales).some(channel => !FACET_SCALE_CHANNELS.includes(channel))
|
|
62
|
+
) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
"compositionSpec.facet.scales requires one shared or independent policy per supported channel."
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const guides = compositionSpec.facet.guides;
|
|
68
|
+
if (
|
|
69
|
+
!isPlainObject(guides) ||
|
|
70
|
+
Object.keys(guides).some(key => !["axes", "legend"].includes(key)) ||
|
|
71
|
+
!["each", "outer"].includes(guides.axes) ||
|
|
72
|
+
![false, "shared"].includes(guides.legend)
|
|
73
|
+
) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
'compositionSpec.facet.guides requires axes "each" or "outer" and legend false or "shared".'
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function validateCompositionChildren(compositionSpec, childIds, facet) {
|
|
81
|
+
if (
|
|
82
|
+
!Array.isArray(compositionSpec.children) ||
|
|
83
|
+
compositionSpec.children.length < (facet ? 1 : 2) ||
|
|
84
|
+
!compositionSpec.children.every(id => typeof id === "string" && id.length > 0)
|
|
85
|
+
) {
|
|
86
|
+
throw new TypeError(
|
|
87
|
+
facet
|
|
88
|
+
? "Facet compositionSpec.children requires at least one child ID."
|
|
89
|
+
: "compositionSpec.children requires at least two child IDs."
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (new Set(compositionSpec.children).size !== compositionSpec.children.length) {
|
|
93
|
+
throw new Error("compositionSpec.children must not contain duplicate IDs.");
|
|
94
|
+
}
|
|
95
|
+
if (
|
|
96
|
+
compositionSpec.children.length !== childIds.length ||
|
|
97
|
+
compositionSpec.children.some(id => !childIds.includes(id))
|
|
98
|
+
) {
|
|
99
|
+
throw new Error("compositionSpec.children must match ChartProgram children exactly.");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function validateLayout(compositionSpec) {
|
|
104
|
+
if (!Number.isFinite(compositionSpec.gap) || compositionSpec.gap < 0) {
|
|
105
|
+
throw new RangeError("compositionSpec.gap must be a non-negative finite number.");
|
|
106
|
+
}
|
|
107
|
+
if (!["start", "center", "end"].includes(compositionSpec.align)) {
|
|
108
|
+
throw new Error("compositionSpec.align must be start, center, or end.");
|
|
109
|
+
}
|
|
110
|
+
if (!isPlainObject(compositionSpec.padding)) {
|
|
111
|
+
throw new TypeError("compositionSpec.padding must be a plain object.");
|
|
112
|
+
}
|
|
113
|
+
for (const side of ["top", "right", "bottom", "left"]) {
|
|
114
|
+
if (!Number.isFinite(compositionSpec.padding[side]) || compositionSpec.padding[side] < 0) {
|
|
115
|
+
throw new RangeError(
|
|
116
|
+
`compositionSpec.padding.${side} must be a non-negative finite number.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const paddingKeys = Object.keys(compositionSpec.padding);
|
|
121
|
+
if (
|
|
122
|
+
paddingKeys.length !== 4 ||
|
|
123
|
+
paddingKeys.some(key => !["top", "right", "bottom", "left"].includes(key))
|
|
124
|
+
) {
|
|
125
|
+
throw new Error("compositionSpec.padding must contain exactly four sides.");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function ownCompositionSpec(compositionSpec, children) {
|
|
130
|
+
const childIds = Object.keys(children);
|
|
131
|
+
if (compositionSpec === undefined) {
|
|
132
|
+
if (childIds.length > 0) {
|
|
133
|
+
throw new Error("ChartProgram children require a compositionSpec.");
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
if (!isPlainObject(compositionSpec)) {
|
|
138
|
+
throw new TypeError("ChartProgram compositionSpec must be a plain object.");
|
|
139
|
+
}
|
|
140
|
+
const allowed = [
|
|
141
|
+
"id", "type", "direction", "children", "columns", "gap", "align",
|
|
142
|
+
"padding", "facet"
|
|
143
|
+
];
|
|
144
|
+
const unknown = Object.keys(compositionSpec).find(key => !allowed.includes(key));
|
|
145
|
+
if (unknown !== undefined) {
|
|
146
|
+
throw new Error(`Unknown compositionSpec property "${unknown}".`);
|
|
147
|
+
}
|
|
148
|
+
if (typeof compositionSpec.id !== "string" || compositionSpec.id.length === 0) {
|
|
149
|
+
throw new TypeError("compositionSpec.id must be a non-empty string.");
|
|
150
|
+
}
|
|
151
|
+
const facet = compositionSpec.type === "facet";
|
|
152
|
+
if (compositionSpec.type !== undefined && !facet) {
|
|
153
|
+
throw new Error(`Unknown compositionSpec type "${compositionSpec.type}".`);
|
|
154
|
+
}
|
|
155
|
+
if (!facet && !["horizontal", "vertical"].includes(compositionSpec.direction)) {
|
|
156
|
+
throw new Error("compositionSpec.direction must be horizontal or vertical.");
|
|
157
|
+
}
|
|
158
|
+
validateCompositionChildren(compositionSpec, childIds, facet);
|
|
159
|
+
if (facet) {
|
|
160
|
+
validateFacetSpec(compositionSpec);
|
|
161
|
+
} else if (
|
|
162
|
+
compositionSpec.columns !== undefined ||
|
|
163
|
+
compositionSpec.facet !== undefined
|
|
164
|
+
) {
|
|
165
|
+
throw new Error("Concat compositionSpec does not accept facet properties.");
|
|
166
|
+
}
|
|
167
|
+
validateLayout(compositionSpec);
|
|
168
|
+
return ownProgramState(compositionSpec);
|
|
169
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cloneAndFreeze,
|
|
3
|
+
freezeOwned,
|
|
4
|
+
isPlainObject,
|
|
5
|
+
removeOwnedPath
|
|
6
|
+
} from "./immutable.js";
|
|
7
|
+
|
|
8
|
+
function validatePath(path) {
|
|
9
|
+
if (
|
|
10
|
+
!Array.isArray(path) ||
|
|
11
|
+
path.length === 0 ||
|
|
12
|
+
!path.every(key => typeof key === "string" && key.length > 0)
|
|
13
|
+
) {
|
|
14
|
+
throw new TypeError("Materialization config path must contain names.");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function validateConfig(config) {
|
|
19
|
+
if (!isPlainObject(config)) {
|
|
20
|
+
throw new TypeError("Materialization config must be a plain object.");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function updateConfigPath(value, path, config) {
|
|
25
|
+
if (path.length === 0) return cloneAndFreeze(config);
|
|
26
|
+
const [key, ...rest] = path;
|
|
27
|
+
return freezeOwned({
|
|
28
|
+
...value,
|
|
29
|
+
[key]: updateConfigPath(value?.[key] ?? {}, rest, config)
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createMaterializationConfigs(
|
|
34
|
+
markConfigs,
|
|
35
|
+
guideConfigs,
|
|
36
|
+
titleConfig,
|
|
37
|
+
canvasConfig
|
|
38
|
+
) {
|
|
39
|
+
return cloneAndFreeze({
|
|
40
|
+
marks: markConfigs,
|
|
41
|
+
guides: guideConfigs,
|
|
42
|
+
...(canvasConfig === undefined ? {} : { canvas: canvasConfig }),
|
|
43
|
+
...(titleConfig === undefined ? {} : { title: titleConfig })
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function setMaterializationConfig(configs, path, config) {
|
|
48
|
+
validatePath(path);
|
|
49
|
+
validateConfig(config);
|
|
50
|
+
return updateConfigPath(configs, path, config);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function removeMaterializationConfig(configs, path) {
|
|
54
|
+
validatePath(path);
|
|
55
|
+
const removed = removeOwnedPath(configs, path);
|
|
56
|
+
if (!removed.removed) return { removed: false, value: configs };
|
|
57
|
+
return {
|
|
58
|
+
removed: true,
|
|
59
|
+
value: freezeOwned({
|
|
60
|
+
...removed.value,
|
|
61
|
+
marks: removed.value.marks ?? freezeOwned({}),
|
|
62
|
+
guides: removed.value.guides ?? freezeOwned({})
|
|
63
|
+
})
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cloneAndFreeze,
|
|
3
|
+
freezeOwned,
|
|
4
|
+
isOwned,
|
|
5
|
+
isPlainObject
|
|
6
|
+
} from "./immutable.js";
|
|
7
|
+
|
|
8
|
+
export function ownProgramState(value) {
|
|
9
|
+
return isOwned(value) ? value : cloneAndFreeze(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ownChildPrograms(children, ProgramClass) {
|
|
13
|
+
if (!isPlainObject(children)) {
|
|
14
|
+
throw new TypeError("ChartProgram children must be a plain object.");
|
|
15
|
+
}
|
|
16
|
+
if (isOwned(children)) return children;
|
|
17
|
+
const owned = {};
|
|
18
|
+
for (const [id, program] of Object.entries(children)) {
|
|
19
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
20
|
+
throw new TypeError("ChartProgram child IDs must be non-empty strings.");
|
|
21
|
+
}
|
|
22
|
+
if (!(program instanceof ProgramClass)) {
|
|
23
|
+
throw new TypeError(`ChartProgram child "${id}" must be a ChartProgram.`);
|
|
24
|
+
}
|
|
25
|
+
owned[id] = program;
|
|
26
|
+
}
|
|
27
|
+
return freezeOwned(owned);
|
|
28
|
+
}
|