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,269 @@
|
|
|
1
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
2
|
+
import {
|
|
3
|
+
validateNonEmptyString,
|
|
4
|
+
validateNonNegativeFinite,
|
|
5
|
+
validatePositiveFinite
|
|
6
|
+
} from "../../../core/validation.js";
|
|
7
|
+
import { resolvePolarFrame } from "../../../grammar/polar.js";
|
|
8
|
+
import {
|
|
9
|
+
formatTransformedTick,
|
|
10
|
+
isTransformedScaleType,
|
|
11
|
+
mapContinuousScaleValues,
|
|
12
|
+
mapOrdinalPositionValues
|
|
13
|
+
} from "../../../grammar/scales.js";
|
|
14
|
+
import { formatTimeTick, formatTimeTicks } from "../../../grammar/ticks.js";
|
|
15
|
+
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
16
|
+
import { findCoordinate } from "../../../selectors/coordinates.js";
|
|
17
|
+
import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
|
|
18
|
+
"../../../theme/defaults.js";
|
|
19
|
+
import {
|
|
20
|
+
findGraphicParent,
|
|
21
|
+
walkGraphicDrawOrder
|
|
22
|
+
} from "../../../grammar/schemas/graphicTree.js";
|
|
23
|
+
import { valuesFromTickConfig } from "../tickValues.js";
|
|
24
|
+
import { formatAxisValue, validateAxisFormat } from "../axes/policy.js";
|
|
25
|
+
|
|
26
|
+
export const POLAR_AXIS_DEFAULTS = Object.freeze({
|
|
27
|
+
angle: 90,
|
|
28
|
+
line: Object.freeze({ color: "#475569", lineWidth: 1.25 }),
|
|
29
|
+
ticks: Object.freeze({
|
|
30
|
+
thetaCount: 6,
|
|
31
|
+
radiusCount: 5,
|
|
32
|
+
length: 6,
|
|
33
|
+
radialLength: 8,
|
|
34
|
+
color: "#475569",
|
|
35
|
+
lineWidth: 1
|
|
36
|
+
}),
|
|
37
|
+
labels: Object.freeze({
|
|
38
|
+
thetaOffset: 18,
|
|
39
|
+
radiusOffset: 10,
|
|
40
|
+
format: "auto",
|
|
41
|
+
color: DEFAULT_COLORS.text,
|
|
42
|
+
fontSize: 11,
|
|
43
|
+
fontFamily: DEFAULT_FONT_FAMILY,
|
|
44
|
+
fontWeight: "normal"
|
|
45
|
+
}),
|
|
46
|
+
title: Object.freeze({
|
|
47
|
+
thetaOffset: 42,
|
|
48
|
+
radiusOffset: 8,
|
|
49
|
+
color: DEFAULT_COLORS.strongText,
|
|
50
|
+
fontSize: 13,
|
|
51
|
+
fontFamily: DEFAULT_FONT_FAMILY,
|
|
52
|
+
fontWeight: 600
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export const POLAR_GRID_DEFAULTS = Object.freeze({
|
|
57
|
+
color: "#d7e0ea",
|
|
58
|
+
lineWidth: 1,
|
|
59
|
+
strokeDash: Object.freeze([]),
|
|
60
|
+
thetaCount: 6,
|
|
61
|
+
radiusCount: 5
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export function polarGuideNames(kind) {
|
|
65
|
+
if (kind === "theta") {
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
channel: "theta",
|
|
68
|
+
axis: "thetaAxis",
|
|
69
|
+
line: "thetaAxisLine",
|
|
70
|
+
ticks: "thetaAxisTicks",
|
|
71
|
+
labels: "thetaAxisLabels",
|
|
72
|
+
title: "thetaAxisTitle",
|
|
73
|
+
grid: "thetaGridLines"
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (kind === "radial" || kind === "radius") {
|
|
77
|
+
return Object.freeze({
|
|
78
|
+
channel: "radius",
|
|
79
|
+
axis: "radiusAxis",
|
|
80
|
+
line: "radialAxisLine",
|
|
81
|
+
ticks: "radialAxisTicks",
|
|
82
|
+
labels: "radialAxisLabels",
|
|
83
|
+
title: "radialAxisTitle",
|
|
84
|
+
grid: "radialGridCircles"
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
throw new Error(`Unknown Polar guide kind "${kind}".`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function resolvePolarGuideResources(program, kind, args, operation) {
|
|
91
|
+
const { channel } = polarGuideNames(kind);
|
|
92
|
+
const requestedCoordinate = args.coordinate === undefined
|
|
93
|
+
? undefined
|
|
94
|
+
: validateUserId(args.coordinate, `${operation} coordinate id`);
|
|
95
|
+
const requestedScale = args.scale === undefined
|
|
96
|
+
? undefined
|
|
97
|
+
: validateUserId(args.scale, `${operation} scale id`);
|
|
98
|
+
let layers = program.semanticSpec.layers.filter(layer =>
|
|
99
|
+
layer.encoding?.[channel]?.scale !== undefined
|
|
100
|
+
);
|
|
101
|
+
if (requestedCoordinate !== undefined) {
|
|
102
|
+
layers = layers.filter(layer => layer.coordinate === requestedCoordinate);
|
|
103
|
+
}
|
|
104
|
+
if (requestedScale !== undefined) {
|
|
105
|
+
layers = layers.filter(
|
|
106
|
+
layer => layer.encoding[channel].scale === requestedScale
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (layers.length === 0) {
|
|
110
|
+
throw new Error(`${operation} requires a stored ${channel} encoding.`);
|
|
111
|
+
}
|
|
112
|
+
if (layers.some(layer => layer.coordinate === undefined)) {
|
|
113
|
+
throw new Error(`${operation} requires a stored Polar coordinate.`);
|
|
114
|
+
}
|
|
115
|
+
const coordinates = [...new Set(layers.map(layer => layer.coordinate))];
|
|
116
|
+
if (coordinates.length > 1) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`${operation} found multiple Polar coordinates; provide coordinate explicitly.`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
const coordinate = requestedCoordinate ?? coordinates[0];
|
|
122
|
+
if (findCoordinate(program, coordinate)?.type !== "polar") {
|
|
123
|
+
throw new Error(`${operation} requires a Polar coordinate.`);
|
|
124
|
+
}
|
|
125
|
+
const scales = [...new Set(
|
|
126
|
+
layers.map(layer => layer.encoding[channel].scale)
|
|
127
|
+
)];
|
|
128
|
+
if (scales.length > 1) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`${operation} found multiple ${channel} scales; provide scale explicitly.`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const scale = requestedScale ?? scales[0];
|
|
134
|
+
const resolved = program.resolvedScales[scale];
|
|
135
|
+
const discrete = ["ordinal", "band", "point"].includes(resolved?.type);
|
|
136
|
+
const continuous = ["linear", "time"].includes(resolved?.type) ||
|
|
137
|
+
isTransformedScaleType(resolved?.type);
|
|
138
|
+
if ((channel === "theta" && !continuous && !discrete) ||
|
|
139
|
+
(channel === "radius" && !continuous)) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`${operation} requires a supported resolved ${channel} scale "${scale}".`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const related = new Set(layers.map(layer => layer.id));
|
|
145
|
+
let before;
|
|
146
|
+
walkGraphicDrawOrder(program.graphicSpec, ({ id }) => {
|
|
147
|
+
if (before === undefined && related.has(id)) before = id;
|
|
148
|
+
});
|
|
149
|
+
if (before === undefined) {
|
|
150
|
+
throw new Error(`${operation} requires related materialized marks.`);
|
|
151
|
+
}
|
|
152
|
+
const owner = findGraphicParent(program.graphicSpec, before);
|
|
153
|
+
return Object.freeze({
|
|
154
|
+
channel,
|
|
155
|
+
coordinate,
|
|
156
|
+
scale,
|
|
157
|
+
before,
|
|
158
|
+
...(owner?.kind === "parent" ? { parent: owner.id } : {})
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function resolvePolarFrameForProgram(program) {
|
|
163
|
+
const bounds = resolveGraphicBounds(program);
|
|
164
|
+
if (bounds === undefined) {
|
|
165
|
+
throw new Error("Polar guides require graphical Canvas bounds.");
|
|
166
|
+
}
|
|
167
|
+
return resolvePolarFrame(bounds);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function resolvePolarTickValues(program, config) {
|
|
171
|
+
const scale = program.resolvedScales[config.scale];
|
|
172
|
+
const discrete = ["ordinal", "band", "point"].includes(scale?.type);
|
|
173
|
+
if (discrete) {
|
|
174
|
+
if (config.mode !== "values") {
|
|
175
|
+
throw new Error("Discrete theta guides require explicit or inferred values.");
|
|
176
|
+
}
|
|
177
|
+
const domain = new Set(scale.domain);
|
|
178
|
+
if (!config.values.every(value => domain.has(value))) {
|
|
179
|
+
throw new RangeError("Polar guide values must be inside the scale domain.");
|
|
180
|
+
}
|
|
181
|
+
return config.values;
|
|
182
|
+
}
|
|
183
|
+
const values = valuesFromTickConfig(program, config);
|
|
184
|
+
const low = Math.min(...scale.domain);
|
|
185
|
+
const high = Math.max(...scale.domain);
|
|
186
|
+
if (!values.every(value => value >= low && value <= high)) {
|
|
187
|
+
throw new RangeError("Polar guide values must be inside the scale domain.");
|
|
188
|
+
}
|
|
189
|
+
return values;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function mapPolarGuideValues(program, config) {
|
|
193
|
+
const scale = program.resolvedScales[config.scale];
|
|
194
|
+
const values = resolvePolarTickValues(program, config);
|
|
195
|
+
const positions = ["ordinal", "band", "point"].includes(scale.type)
|
|
196
|
+
? mapOrdinalPositionValues(values, scale)
|
|
197
|
+
: mapContinuousScaleValues(values, scale);
|
|
198
|
+
return Object.freeze({ values, positions });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function formatPolarGuideValues(program, config, values) {
|
|
202
|
+
const scale = program.resolvedScales[config.scale];
|
|
203
|
+
if (scale.type === "time" && config.format === "auto") {
|
|
204
|
+
return formatTimeTicks(values, scale.domain);
|
|
205
|
+
}
|
|
206
|
+
return values.map(value => formatAxisValue(
|
|
207
|
+
value,
|
|
208
|
+
scale.type,
|
|
209
|
+
config.format,
|
|
210
|
+
item => scale.type === "time"
|
|
211
|
+
? formatTimeTick(item, scale.domain)
|
|
212
|
+
: isTransformedScaleType(scale.type)
|
|
213
|
+
? formatTransformedTick(scale.type, item)
|
|
214
|
+
: String(item)
|
|
215
|
+
));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function normalizePolarTickMode(program, scale, args, defaultCount) {
|
|
219
|
+
const resolved = program.resolvedScales[scale];
|
|
220
|
+
if (Object.hasOwn(args, "values")) {
|
|
221
|
+
return { mode: "values", values: args.values, inferredValues: false };
|
|
222
|
+
}
|
|
223
|
+
if (Object.hasOwn(args, "count")) {
|
|
224
|
+
return { mode: "count", count: args.count, inferredValues: false };
|
|
225
|
+
}
|
|
226
|
+
if (["ordinal", "band", "point"].includes(resolved?.type)) {
|
|
227
|
+
return {
|
|
228
|
+
mode: "values",
|
|
229
|
+
values: resolved.domain,
|
|
230
|
+
inferredValues: true
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return { mode: "count", count: defaultCount, inferredValues: true };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function validatePolarTickConfig(config, label) {
|
|
237
|
+
if (config.mode === "count" &&
|
|
238
|
+
(!Number.isInteger(config.count) || config.count <= 0)) {
|
|
239
|
+
throw new RangeError(`${label} count must be a positive integer.`);
|
|
240
|
+
}
|
|
241
|
+
if (config.mode === "values" && (
|
|
242
|
+
!Array.isArray(config.values) || config.values.length === 0 ||
|
|
243
|
+
!config.values.every(value =>
|
|
244
|
+
typeof value === "string" || typeof value === "boolean" ||
|
|
245
|
+
(typeof value === "number" && Number.isFinite(value))
|
|
246
|
+
)
|
|
247
|
+
)) {
|
|
248
|
+
throw new TypeError(`${label} values must be a non-empty domain-value array.`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function validatePolarLineStyle(config, label) {
|
|
253
|
+
validateNonEmptyString(config.color, `${label} color`);
|
|
254
|
+
validateNonNegativeFinite(config.lineWidth, `${label} lineWidth`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function validatePolarTextStyle(config, label) {
|
|
258
|
+
validateNonEmptyString(config.color, `${label} color`);
|
|
259
|
+
validatePositiveFinite(config.fontSize, `${label} fontSize`);
|
|
260
|
+
validateNonEmptyString(config.fontFamily, `${label} fontFamily`);
|
|
261
|
+
if (typeof config.fontWeight !== "string" &&
|
|
262
|
+
!Number.isFinite(config.fontWeight)) {
|
|
263
|
+
throw new TypeError(`${label} fontWeight must be a string or number.`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function validatePolarLabelFormat(format) {
|
|
268
|
+
return validateAxisFormat(format);
|
|
269
|
+
}
|
package/src/actions/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { registerCanvasActions } from "./canvas/index.js";
|
|
2
2
|
import { registerCoordinateActions } from "./coordinates/index.js";
|
|
3
|
+
import { registerCompositionActions } from "./composition/index.js";
|
|
3
4
|
import { registerDataActions } from "./data/index.js";
|
|
4
5
|
import { registerEncodingActions } from "./encodings/index.js";
|
|
5
6
|
import { registerErrorBandActions } from "./errorBands/index.js";
|
|
6
7
|
import { registerErrorBarActions } from "./errorBars/index.js";
|
|
8
|
+
import { registerFacetActions } from "./facets/index.js";
|
|
7
9
|
import { registerBoxPlotActions } from "./boxPlots/index.js";
|
|
8
10
|
import { registerGuideActions } from "./guides/index.js";
|
|
9
11
|
import { registerMarkActions } from "./marks/index.js";
|
|
@@ -15,6 +17,8 @@ import { registerSelectionActions } from "./selection/index.js";
|
|
|
15
17
|
|
|
16
18
|
export function registerActions(ProgramClass) {
|
|
17
19
|
registerPrimitiveActions(ProgramClass);
|
|
20
|
+
registerCompositionActions(ProgramClass);
|
|
21
|
+
registerFacetActions(ProgramClass);
|
|
18
22
|
registerCanvasActions(ProgramClass);
|
|
19
23
|
registerDataActions(ProgramClass);
|
|
20
24
|
registerMarkActions(ProgramClass);
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { action } from "../../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../../core/identifiers.js";
|
|
3
|
+
import {
|
|
4
|
+
validateNonEmptyString,
|
|
5
|
+
validateNonNegativeFinite,
|
|
6
|
+
validateUnitInterval
|
|
7
|
+
} from "../../../core/validation.js";
|
|
8
|
+
import { deriveArcSectors } from "../../../grammar/arcs.js";
|
|
9
|
+
import { buildAnnularSectorCommands } from "../../../grammar/polarPaths.js";
|
|
10
|
+
import { resolvePolarFrame } from "../../../grammar/polar.js";
|
|
11
|
+
import { mapOrdinalValues } from "../../../grammar/scales.js";
|
|
12
|
+
import { resolveGraphicBounds } from "../../../layout/canvas.js";
|
|
13
|
+
import { canMaterializeArc } from "../../../materialization/marks.js";
|
|
14
|
+
import { findDataset } from "../../../selectors/datasets.js";
|
|
15
|
+
import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
|
|
16
|
+
import { DEFAULT_COLORS } from "../../../theme/defaults.js";
|
|
17
|
+
import { resolveMarkGraphicPlacement } from
|
|
18
|
+
"../../../materialization/graphicHierarchy.js";
|
|
19
|
+
import { rematerializeHighlightBaseline } from "../lifecycle.js";
|
|
20
|
+
import {
|
|
21
|
+
assertMarkAvailable,
|
|
22
|
+
applyLayeredMarkInheritance,
|
|
23
|
+
materializeInheritedMark,
|
|
24
|
+
resolveLayeredMarkInheritance,
|
|
25
|
+
resolveMarkData,
|
|
26
|
+
resolveMarkId,
|
|
27
|
+
validateMarkOptions
|
|
28
|
+
} from "../shared.js";
|
|
29
|
+
|
|
30
|
+
const CREATE_OPTIONS = Object.freeze([
|
|
31
|
+
"id", "data", "innerRadius", "padAngle", "fill", "opacity", "stroke",
|
|
32
|
+
"strokeWidth"
|
|
33
|
+
]);
|
|
34
|
+
const EDIT_OPTIONS = Object.freeze([
|
|
35
|
+
"target", "innerRadius", "padAngle", "fill", "opacity", "stroke",
|
|
36
|
+
"strokeWidth"
|
|
37
|
+
]);
|
|
38
|
+
const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
|
|
39
|
+
|
|
40
|
+
function validateInnerRadius(value) {
|
|
41
|
+
if (!Number.isFinite(value) || value < 0 || value >= 1) {
|
|
42
|
+
throw new RangeError(
|
|
43
|
+
"Arc innerRadius must be from 0 (inclusive) to 1 (exclusive)."
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function normalizeConfig(args, previous = {}) {
|
|
50
|
+
return {
|
|
51
|
+
...previous,
|
|
52
|
+
...(Object.hasOwn(args, "innerRadius")
|
|
53
|
+
? { innerRadius: validateInnerRadius(args.innerRadius) }
|
|
54
|
+
: {}),
|
|
55
|
+
...(Object.hasOwn(args, "padAngle")
|
|
56
|
+
? { padAngle: validateNonNegativeFinite(args.padAngle, "Arc padAngle") }
|
|
57
|
+
: {}),
|
|
58
|
+
...(Object.hasOwn(args, "fill")
|
|
59
|
+
? { fill: validateNonEmptyString(args.fill, "Arc fill") }
|
|
60
|
+
: {}),
|
|
61
|
+
...(Object.hasOwn(args, "opacity")
|
|
62
|
+
? { opacity: validateUnitInterval(args.opacity, "Arc opacity") }
|
|
63
|
+
: {}),
|
|
64
|
+
...(Object.hasOwn(args, "stroke")
|
|
65
|
+
? { stroke: validateNonEmptyString(args.stroke, "Arc stroke") }
|
|
66
|
+
: {}),
|
|
67
|
+
...(Object.hasOwn(args, "strokeWidth")
|
|
68
|
+
? {
|
|
69
|
+
strokeWidth: validateNonNegativeFinite(
|
|
70
|
+
args.strokeWidth,
|
|
71
|
+
"Arc strokeWidth"
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
: {})
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const createArcMark = action(
|
|
79
|
+
{
|
|
80
|
+
op: "createArcMark",
|
|
81
|
+
description: "Create a semantic arc mark and empty path collection."
|
|
82
|
+
},
|
|
83
|
+
function (args = {}) {
|
|
84
|
+
validateMarkOptions(args, CREATE_OPTIONS, "createArcMark");
|
|
85
|
+
const id = resolveMarkId(this, args.id, {
|
|
86
|
+
defaultId: "arc",
|
|
87
|
+
label: "Arc mark id",
|
|
88
|
+
markType: "arc",
|
|
89
|
+
operation: "createArcMark"
|
|
90
|
+
});
|
|
91
|
+
const inherited = resolveLayeredMarkInheritance(this, args, "arc");
|
|
92
|
+
const { data } = resolveMarkData(this, {
|
|
93
|
+
...args,
|
|
94
|
+
...(args.data === undefined && this.context.currentData === undefined &&
|
|
95
|
+
inherited?.data !== undefined ? { data: inherited.data } : {})
|
|
96
|
+
});
|
|
97
|
+
assertMarkAvailable(this, id);
|
|
98
|
+
const config = normalizeConfig(args, {
|
|
99
|
+
innerRadius: 0,
|
|
100
|
+
padAngle: 0,
|
|
101
|
+
opacity: 1,
|
|
102
|
+
stroke: "#ffffff",
|
|
103
|
+
strokeWidth: 1
|
|
104
|
+
});
|
|
105
|
+
let created = this
|
|
106
|
+
.editSemantic({ property: `layer[${id}].mark.type`, value: "arc" })
|
|
107
|
+
.editSemantic({ property: `layer[${id}].data`, value: data });
|
|
108
|
+
created = applyLayeredMarkInheritance(created, id, inherited);
|
|
109
|
+
created = created
|
|
110
|
+
.createGraphics({
|
|
111
|
+
id,
|
|
112
|
+
type: "path",
|
|
113
|
+
length: 0,
|
|
114
|
+
...resolveMarkGraphicPlacement(created, { data, markType: "arc" })
|
|
115
|
+
})
|
|
116
|
+
._withMarkConfig(id, config);
|
|
117
|
+
return materializeInheritedMark(created, id);
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
function requireArc(program, id) {
|
|
122
|
+
const layer = findLayer(program, id);
|
|
123
|
+
const dataset = findDataset(program, layer?.data);
|
|
124
|
+
if (layer?.mark?.type !== "arc" || program.graphicSpec.objects[id]?.type !== "path") {
|
|
125
|
+
throw new Error(`Unknown arc mark "${id}".`);
|
|
126
|
+
}
|
|
127
|
+
if (dataset === undefined) {
|
|
128
|
+
throw new Error(`Arc mark "${id}" requires an existing dataset.`);
|
|
129
|
+
}
|
|
130
|
+
return { layer, dataset };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const rematerializeArcMark = action(
|
|
134
|
+
{
|
|
135
|
+
op: "rematerializeArcMark",
|
|
136
|
+
description: "Recompute concrete annular-sector paths."
|
|
137
|
+
},
|
|
138
|
+
function (args = {}) {
|
|
139
|
+
validateMarkOptions(args, REMATERIALIZE_OPTIONS, "rematerializeArcMark");
|
|
140
|
+
const id = validateUserId(args.id, "Arc mark id");
|
|
141
|
+
const highlighted = rematerializeHighlightBaseline(this, {
|
|
142
|
+
target: id,
|
|
143
|
+
operation: "rematerializeArcMark",
|
|
144
|
+
resetProperty: "length",
|
|
145
|
+
resetValue: 0
|
|
146
|
+
});
|
|
147
|
+
if (highlighted !== undefined) return highlighted;
|
|
148
|
+
const { layer, dataset } = requireArc(this, id);
|
|
149
|
+
if (!canMaterializeArc(this, layer)) {
|
|
150
|
+
throw new Error(`Arc mark "${id}" does not have a complete encoding.`);
|
|
151
|
+
}
|
|
152
|
+
const thetaScaleId = layer.encoding.theta.scale;
|
|
153
|
+
const radiusScaleId = layer.encoding?.radius?.scale;
|
|
154
|
+
const colorScaleId = layer.encoding?.color?.scale;
|
|
155
|
+
let resolved = this.rematerializeScale({ id: thetaScaleId });
|
|
156
|
+
if (radiusScaleId !== undefined) {
|
|
157
|
+
resolved = resolved.rematerializeScale({ id: radiusScaleId });
|
|
158
|
+
}
|
|
159
|
+
if (colorScaleId !== undefined) {
|
|
160
|
+
resolved = resolved.rematerializeScale({ id: colorScaleId });
|
|
161
|
+
}
|
|
162
|
+
const config = resolved.markConfigs[id] ?? {};
|
|
163
|
+
const derived = deriveArcSectors(dataset.values, layer, {
|
|
164
|
+
thetaScale: resolved.resolvedScales[thetaScaleId],
|
|
165
|
+
...(radiusScaleId === undefined
|
|
166
|
+
? {}
|
|
167
|
+
: { radiusScale: resolved.resolvedScales[radiusScaleId] }),
|
|
168
|
+
frame: resolvePolarFrame(resolveGraphicBounds(resolved)),
|
|
169
|
+
innerRadiusRatio: config.innerRadius ?? 0
|
|
170
|
+
});
|
|
171
|
+
const frame = resolvePolarFrame(resolveGraphicBounds(resolved));
|
|
172
|
+
const commands = derived.sectors.map(sector => buildAnnularSectorCommands({
|
|
173
|
+
frame,
|
|
174
|
+
startTheta: sector.startTheta,
|
|
175
|
+
endTheta: sector.endTheta,
|
|
176
|
+
innerRadius: sector.innerRadius,
|
|
177
|
+
outerRadius: sector.outerRadius,
|
|
178
|
+
padAngle: config.padAngle ?? 0
|
|
179
|
+
}));
|
|
180
|
+
const fills = colorScaleId === undefined
|
|
181
|
+
? commands.map(() => config.fill ?? DEFAULT_COLORS.mark)
|
|
182
|
+
: mapOrdinalValues(
|
|
183
|
+
derived.sectors.map(sector => sector.color),
|
|
184
|
+
resolved.resolvedScales[colorScaleId].domain,
|
|
185
|
+
resolved.resolvedScales[colorScaleId].range
|
|
186
|
+
);
|
|
187
|
+
return resolved
|
|
188
|
+
.editGraphics({ target: id, property: "length", value: commands.length })
|
|
189
|
+
.editGraphics({ target: id, property: "commands", value: commands })
|
|
190
|
+
.editGraphics({ target: id, property: "fill", value: fills })
|
|
191
|
+
.editGraphics({ target: id, property: "opacity", value: config.opacity ?? 1 })
|
|
192
|
+
.editGraphics({ target: id, property: "stroke", value: config.stroke ?? "#ffffff" })
|
|
193
|
+
.editGraphics({ target: id, property: "strokeWidth", value: config.strokeWidth ?? 1 })
|
|
194
|
+
.editGraphics({
|
|
195
|
+
target: id,
|
|
196
|
+
property: "strokeDash",
|
|
197
|
+
value: commands.map(() => [])
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const editArcMark = action(
|
|
203
|
+
{
|
|
204
|
+
op: "editArcMark",
|
|
205
|
+
description: "Edit arc geometry and appearance."
|
|
206
|
+
},
|
|
207
|
+
function (args = {}) {
|
|
208
|
+
validateMarkOptions(args, EDIT_OPTIONS, "editArcMark");
|
|
209
|
+
if (Object.keys(args).every(key => key === "target")) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
"editArcMark requires innerRadius, padAngle, fill, opacity, stroke, or strokeWidth."
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
const target = Object.hasOwn(args, "target")
|
|
215
|
+
? validateUserId(args.target, "Arc mark id")
|
|
216
|
+
: undefined;
|
|
217
|
+
const layer = resolveEligibleLayer(this, {
|
|
218
|
+
target,
|
|
219
|
+
predicate: candidate => candidate.mark?.type === "arc",
|
|
220
|
+
label: "arc mark"
|
|
221
|
+
});
|
|
222
|
+
if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
|
|
223
|
+
throw new Error("editArcMark fill cannot be combined with a color encoding.");
|
|
224
|
+
}
|
|
225
|
+
const next = this._withMarkConfig(
|
|
226
|
+
layer.id,
|
|
227
|
+
normalizeConfig(args, this.markConfigs[layer.id])
|
|
228
|
+
);
|
|
229
|
+
return canMaterializeArc(next, layer)
|
|
230
|
+
? next.rematerializeArcMark({ id: layer.id })
|
|
231
|
+
: next;
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
export function registerArcMarkActions(ProgramClass) {
|
|
236
|
+
ProgramClass.prototype.createArcMark = createArcMark;
|
|
237
|
+
ProgramClass.prototype.editArcMark = editArcMark;
|
|
238
|
+
ProgramClass.prototype.rematerializeArcMark = rematerializeArcMark;
|
|
239
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerArcMarkActions } from "./actions.js";
|