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
package/src/grammar/interval.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
2
|
import { aggregateScalarValues } from "./aggregate.js";
|
|
3
|
+
import { studentTCriticalValue } from "./statistics/studentT.js";
|
|
3
4
|
|
|
4
5
|
const CENTER_VALUES = Object.freeze(["mean", "median"]);
|
|
5
6
|
const EXTENT_VALUES = Object.freeze(["stderr", "stdev", "ci", "iqr"]);
|
|
@@ -133,92 +134,6 @@ export function normalizeIntervalParameters({
|
|
|
133
134
|
});
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
const LANCZOS = Object.freeze([
|
|
137
|
-
676.5203681218851,
|
|
138
|
-
-1259.1392167224028,
|
|
139
|
-
771.3234287776531,
|
|
140
|
-
-176.6150291621406,
|
|
141
|
-
12.507343278686905,
|
|
142
|
-
-0.13857109526572012,
|
|
143
|
-
9.984369578019572e-6,
|
|
144
|
-
1.5056327351493116e-7
|
|
145
|
-
]);
|
|
146
|
-
|
|
147
|
-
function logGamma(value) {
|
|
148
|
-
if (value < 0.5) {
|
|
149
|
-
return Math.log(Math.PI) - Math.log(Math.sin(Math.PI * value)) -
|
|
150
|
-
logGamma(1 - value);
|
|
151
|
-
}
|
|
152
|
-
const shifted = value - 1;
|
|
153
|
-
let sum = 0.9999999999998099;
|
|
154
|
-
for (const [index, coefficient] of LANCZOS.entries()) {
|
|
155
|
-
sum += coefficient / (shifted + index + 1);
|
|
156
|
-
}
|
|
157
|
-
const t = shifted + LANCZOS.length - 0.5;
|
|
158
|
-
return 0.5 * Math.log(2 * Math.PI) +
|
|
159
|
-
(shifted + 0.5) * Math.log(t) - t + Math.log(sum);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function betaFraction(a, b, x) {
|
|
163
|
-
const maximumIterations = 200;
|
|
164
|
-
const epsilon = 3e-14;
|
|
165
|
-
const floor = 1e-300;
|
|
166
|
-
const sum = a + b;
|
|
167
|
-
let c = 1;
|
|
168
|
-
let d = 1 - sum * x / (a + 1);
|
|
169
|
-
if (Math.abs(d) < floor) d = floor;
|
|
170
|
-
d = 1 / d;
|
|
171
|
-
let result = d;
|
|
172
|
-
|
|
173
|
-
for (let iteration = 1; iteration <= maximumIterations; iteration += 1) {
|
|
174
|
-
const even = 2 * iteration;
|
|
175
|
-
let coefficient = iteration * (b - iteration) * x /
|
|
176
|
-
((a + even - 1) * (a + even));
|
|
177
|
-
d = 1 + coefficient * d;
|
|
178
|
-
if (Math.abs(d) < floor) d = floor;
|
|
179
|
-
c = 1 + coefficient / c;
|
|
180
|
-
if (Math.abs(c) < floor) c = floor;
|
|
181
|
-
d = 1 / d;
|
|
182
|
-
result *= d * c;
|
|
183
|
-
|
|
184
|
-
coefficient = -(a + iteration) * (sum + iteration) * x /
|
|
185
|
-
((a + even) * (a + even + 1));
|
|
186
|
-
d = 1 + coefficient * d;
|
|
187
|
-
if (Math.abs(d) < floor) d = floor;
|
|
188
|
-
c = 1 + coefficient / c;
|
|
189
|
-
if (Math.abs(c) < floor) c = floor;
|
|
190
|
-
d = 1 / d;
|
|
191
|
-
const delta = d * c;
|
|
192
|
-
result *= delta;
|
|
193
|
-
if (Math.abs(delta - 1) <= epsilon) return result;
|
|
194
|
-
}
|
|
195
|
-
throw new Error("Student-t calculation did not converge.");
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function regularizedBeta(x, a, b) {
|
|
199
|
-
if (x <= 0) return 0;
|
|
200
|
-
if (x >= 1) return 1;
|
|
201
|
-
const front = Math.exp(
|
|
202
|
-
logGamma(a + b) - logGamma(a) - logGamma(b) +
|
|
203
|
-
a * Math.log(x) + b * Math.log1p(-x)
|
|
204
|
-
);
|
|
205
|
-
return x < (a + 1) / (a + b + 2)
|
|
206
|
-
? front * betaFraction(a, b, x) / a
|
|
207
|
-
: 1 - front * betaFraction(b, a, 1 - x) / b;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function studentTCdf(value, degreesOfFreedom) {
|
|
211
|
-
if (value === 0) return 0.5;
|
|
212
|
-
const x = degreesOfFreedom /
|
|
213
|
-
(degreesOfFreedom + value * value);
|
|
214
|
-
const tail = 0.5 * regularizedBeta(
|
|
215
|
-
x,
|
|
216
|
-
degreesOfFreedom / 2,
|
|
217
|
-
0.5
|
|
218
|
-
);
|
|
219
|
-
return value > 0 ? 1 - tail : tail;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
137
|
export function studentTCritical(degreesOfFreedom, level) {
|
|
223
138
|
if (!Number.isInteger(degreesOfFreedom) || degreesOfFreedom < 1) {
|
|
224
139
|
throw new RangeError("Student-t degreesOfFreedom must be a positive integer.");
|
|
@@ -226,16 +141,7 @@ export function studentTCritical(degreesOfFreedom, level) {
|
|
|
226
141
|
if (!Number.isFinite(level) || level <= 0 || level >= 1) {
|
|
227
142
|
throw new RangeError("Student-t level must be between 0 and 1.");
|
|
228
143
|
}
|
|
229
|
-
|
|
230
|
-
let low = 0;
|
|
231
|
-
let high = 1;
|
|
232
|
-
while (studentTCdf(high, degreesOfFreedom) < target) high *= 2;
|
|
233
|
-
for (let iteration = 0; iteration < 100; iteration += 1) {
|
|
234
|
-
const midpoint = (low + high) / 2;
|
|
235
|
-
if (studentTCdf(midpoint, degreesOfFreedom) < target) low = midpoint;
|
|
236
|
-
else high = midpoint;
|
|
237
|
-
}
|
|
238
|
-
return (low + high) / 2;
|
|
144
|
+
return studentTCriticalValue(level, degreesOfFreedom);
|
|
239
145
|
}
|
|
240
146
|
|
|
241
147
|
function isMissing(value) {
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
readNominalField,
|
|
4
4
|
readQuantitativeField,
|
|
5
5
|
readTemporalField
|
|
6
|
-
} from "./scales.js";
|
|
6
|
+
} from "./scales/index.js";
|
|
7
7
|
import {
|
|
8
8
|
aggregateRows,
|
|
9
9
|
isAggregate,
|
|
@@ -64,8 +64,13 @@ function readSeriesFields(rows, layer) {
|
|
|
64
64
|
const encoding = layer.encoding?.[channel];
|
|
65
65
|
|
|
66
66
|
if (encoding?.field === undefined) continue;
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
const categorical = channel === "color"
|
|
68
|
+
? ["nominal", "ordinal"].includes(encoding.fieldType)
|
|
69
|
+
: encoding.fieldType === "nominal";
|
|
70
|
+
if (!categorical) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Line ${channel} encoding on mark "${layer.id}" must be categorical.`
|
|
73
|
+
);
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
if (!values.has(encoding.field)) {
|
|
@@ -81,7 +86,7 @@ function groupKey(values) {
|
|
|
81
86
|
return JSON.stringify(values);
|
|
82
87
|
}
|
|
83
88
|
|
|
84
|
-
|
|
89
|
+
function deriveCartesianLineSeries(rows, layer) {
|
|
85
90
|
const { x, y, isAggregate, directTemporal } = requireLineEncoding(layer);
|
|
86
91
|
if (isAggregate) {
|
|
87
92
|
validateAggregateFieldValues(rows, y.field, y.fieldType);
|
|
@@ -195,3 +200,106 @@ export function deriveLineSeries(rows, layer) {
|
|
|
195
200
|
series
|
|
196
201
|
});
|
|
197
202
|
}
|
|
203
|
+
|
|
204
|
+
function requirePolarLineEncoding(layer) {
|
|
205
|
+
if (layer?.mark?.type !== "line") {
|
|
206
|
+
throw new Error("Polar line series derivation requires a semantic line mark.");
|
|
207
|
+
}
|
|
208
|
+
const theta = layer.encoding?.theta;
|
|
209
|
+
const radius = layer.encoding?.radius;
|
|
210
|
+
if (theta === undefined || radius === undefined) {
|
|
211
|
+
throw new Error(
|
|
212
|
+
`Polar line mark "${layer.id}" requires theta and radius encodings.`
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
if (!["quantitative", "temporal", "ordinal", "nominal"].includes(
|
|
216
|
+
theta.fieldType
|
|
217
|
+
)) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
`Polar line mark "${layer.id}" has unsupported theta field type.`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (radius.fieldType !== "quantitative") {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`Polar line mark "${layer.id}" requires a quantitative radius encoding.`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return { theta, radius };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function readThetaValues(rows, encoding) {
|
|
231
|
+
if (["nominal", "ordinal"].includes(encoding.fieldType)) {
|
|
232
|
+
return readNominalField(rows, encoding.field);
|
|
233
|
+
}
|
|
234
|
+
return encoding.fieldType === "temporal"
|
|
235
|
+
? readTemporalField(rows, encoding.field)
|
|
236
|
+
: readQuantitativeField(rows, encoding.field);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function thetaOrder(values, fieldType, domain) {
|
|
240
|
+
if (!["nominal", "ordinal"].includes(fieldType)) return values;
|
|
241
|
+
const order = domain ?? [...new Set(values)];
|
|
242
|
+
const indices = new Map(order.map((value, index) => [value, index]));
|
|
243
|
+
for (const value of values) {
|
|
244
|
+
if (!indices.has(value)) {
|
|
245
|
+
throw new Error(`Polar line theta domain does not contain "${value}".`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return values.map(value => indices.get(value));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function derivePolarLineSeries(rows, layer, { thetaDomain } = {}) {
|
|
252
|
+
const { theta, radius } = requirePolarLineEncoding(layer);
|
|
253
|
+
const thetaValues = readThetaValues(rows, theta);
|
|
254
|
+
const radiusValues = readQuantitativeField(rows, radius.field);
|
|
255
|
+
const sortValues = thetaOrder(thetaValues, theta.fieldType, thetaDomain);
|
|
256
|
+
const seriesFields = readSeriesFields(rows, layer);
|
|
257
|
+
const groups = new Map();
|
|
258
|
+
|
|
259
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
260
|
+
const dimensions = seriesFields.fields.map(
|
|
261
|
+
field => seriesFields.values.get(field)[index]
|
|
262
|
+
);
|
|
263
|
+
const key = groupKey(dimensions);
|
|
264
|
+
const series = groups.get(key) ?? {
|
|
265
|
+
key: Object.fromEntries(
|
|
266
|
+
seriesFields.fields.map((field, item) => [field, dimensions[item]])
|
|
267
|
+
),
|
|
268
|
+
values: []
|
|
269
|
+
};
|
|
270
|
+
series.values.push({
|
|
271
|
+
theta: thetaValues[index],
|
|
272
|
+
radius: radiusValues[index],
|
|
273
|
+
order: sortValues[index],
|
|
274
|
+
sourceIndex: index
|
|
275
|
+
});
|
|
276
|
+
groups.set(key, series);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const series = [...groups.values()].flatMap(item => {
|
|
280
|
+
const values = item.values.sort(
|
|
281
|
+
(left, right) => left.order - right.order ||
|
|
282
|
+
left.sourceIndex - right.sourceIndex
|
|
283
|
+
);
|
|
284
|
+
return values.length < 2 ? [] : [{ key: item.key, values }];
|
|
285
|
+
});
|
|
286
|
+
if (series.length === 0) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`Polar line series on mark "${layer.id}" requires at least two points.`
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
return cloneAndFreeze({
|
|
292
|
+
thetaFieldType: theta.fieldType,
|
|
293
|
+
thetaValues: series.flatMap(item => item.values.map(value => value.theta)),
|
|
294
|
+
radiusValues: series.flatMap(item => item.values.map(value => value.radius)),
|
|
295
|
+
series
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function deriveLineSeries(rows, layer, options) {
|
|
300
|
+
const polar = layer?.encoding?.theta !== undefined ||
|
|
301
|
+
layer?.encoding?.radius !== undefined;
|
|
302
|
+
return polar
|
|
303
|
+
? derivePolarLineSeries(rows, layer, options)
|
|
304
|
+
: deriveCartesianLineSeries(rows, layer);
|
|
305
|
+
}
|
package/src/grammar/palettes.js
CHANGED
|
@@ -190,16 +190,14 @@ export function resolvePalette(value, domainCount) {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
export function resolveContinuousPalette(value, count = 31) {
|
|
193
|
-
if (!Number.isInteger(count) || count < 2) {
|
|
194
|
-
throw new RangeError("Continuous palette count must be an integer of at least 2.");
|
|
195
|
-
}
|
|
196
193
|
const palette = normalizePalette(value);
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
const resolvedCount = palette.count ?? count;
|
|
195
|
+
if (!Number.isInteger(resolvedCount) || resolvedCount < 2) {
|
|
196
|
+
throw new RangeError("Continuous palette count must be an integer of at least 2.");
|
|
199
197
|
}
|
|
200
198
|
const discrete = DISCRETE[palette.name];
|
|
201
|
-
if (discrete !== undefined) return discrete;
|
|
202
|
-
return resolvePalette({ ...palette, count },
|
|
199
|
+
if (discrete !== undefined && palette.count === undefined) return discrete;
|
|
200
|
+
return resolvePalette({ ...palette, count: resolvedCount }, resolvedCount);
|
|
203
201
|
}
|
|
204
202
|
|
|
205
203
|
export function paletteFamily(name) {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
|
|
2
|
+
import { validatePair } from "./scales/validation.js";
|
|
3
|
+
|
|
4
|
+
function validateBounds(bounds) {
|
|
5
|
+
if (
|
|
6
|
+
!isPlainObject(bounds) ||
|
|
7
|
+
![bounds.x, bounds.y, bounds.width, bounds.height].every(Number.isFinite) ||
|
|
8
|
+
bounds.width < 0 ||
|
|
9
|
+
bounds.height < 0
|
|
10
|
+
) {
|
|
11
|
+
throw new TypeError(
|
|
12
|
+
"Polar geometry requires finite, non-negative graphical bounds."
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
return bounds;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolvePolarFrame(bounds) {
|
|
19
|
+
validateBounds(bounds);
|
|
20
|
+
return cloneAndFreeze({
|
|
21
|
+
centerX: bounds.x + bounds.width / 2,
|
|
22
|
+
centerY: bounds.y + bounds.height / 2,
|
|
23
|
+
availableRadius: Math.min(bounds.width, bounds.height) / 2
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function validateThetaRange(range) {
|
|
28
|
+
if (range === "auto") return range;
|
|
29
|
+
const validated = validatePair(range, "Theta scale range");
|
|
30
|
+
if (Math.abs(validated[1] - validated[0]) > 360) {
|
|
31
|
+
throw new RangeError("Theta scale range span must not exceed 360 degrees.");
|
|
32
|
+
}
|
|
33
|
+
return validated;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function validateRadialRange(range, availableRadius) {
|
|
37
|
+
if (range === "auto") return range;
|
|
38
|
+
const validated = validatePair(range, "Radius scale range");
|
|
39
|
+
if (validated.some(value => value < 0)) {
|
|
40
|
+
throw new RangeError("Radius scale range values must be non-negative.");
|
|
41
|
+
}
|
|
42
|
+
if (
|
|
43
|
+
availableRadius !== undefined &&
|
|
44
|
+
validated.some(value => value > availableRadius)
|
|
45
|
+
) {
|
|
46
|
+
throw new RangeError(
|
|
47
|
+
`Radius scale range must fit within the available radius ${availableRadius}.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return validated;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function resolvePolarScaleRange(range, channel, bounds) {
|
|
54
|
+
if (channel === "theta") {
|
|
55
|
+
return range === "auto"
|
|
56
|
+
? cloneAndFreeze([0, 360])
|
|
57
|
+
: validateThetaRange(range);
|
|
58
|
+
}
|
|
59
|
+
if (channel !== "radius") {
|
|
60
|
+
throw new Error(`Unknown Polar position channel "${channel}".`);
|
|
61
|
+
}
|
|
62
|
+
const frame = resolvePolarFrame(bounds);
|
|
63
|
+
return range === "auto"
|
|
64
|
+
? cloneAndFreeze([0, frame.availableRadius])
|
|
65
|
+
: validateRadialRange(range, frame.availableRadius);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function polarDirection(theta) {
|
|
69
|
+
if (!Number.isFinite(theta)) {
|
|
70
|
+
throw new TypeError("Polar theta must be a finite number of degrees.");
|
|
71
|
+
}
|
|
72
|
+
const angle = theta * Math.PI / 180;
|
|
73
|
+
return cloneAndFreeze({
|
|
74
|
+
x: Math.sin(angle),
|
|
75
|
+
y: -Math.cos(angle)
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function polarToCartesian({ theta, radius, frame }) {
|
|
80
|
+
const direction = polarDirection(theta);
|
|
81
|
+
if (!Number.isFinite(radius) || radius < 0) {
|
|
82
|
+
throw new TypeError("Polar radius must be a non-negative finite number.");
|
|
83
|
+
}
|
|
84
|
+
const resolvedFrame = frame?.availableRadius === undefined
|
|
85
|
+
? resolvePolarFrame(frame)
|
|
86
|
+
: frame;
|
|
87
|
+
if (
|
|
88
|
+
![resolvedFrame.centerX, resolvedFrame.centerY,
|
|
89
|
+
resolvedFrame.availableRadius].every(Number.isFinite)
|
|
90
|
+
) {
|
|
91
|
+
throw new TypeError("Polar frame must contain finite geometry.");
|
|
92
|
+
}
|
|
93
|
+
if (radius > resolvedFrame.availableRadius) {
|
|
94
|
+
throw new RangeError(
|
|
95
|
+
`Polar radius ${radius} exceeds the available radius ${resolvedFrame.availableRadius}.`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return cloneAndFreeze({
|
|
99
|
+
x: resolvedFrame.centerX + radius * direction.x,
|
|
100
|
+
y: resolvedFrame.centerY + radius * direction.y
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { freezeOwned } from "../core/immutable.js";
|
|
2
|
+
import { polarDirection } from "./polar.js";
|
|
3
|
+
import { buildPolarCircleCommands } from "./polarPaths.js";
|
|
4
|
+
|
|
5
|
+
export { buildPolarCircleCommands } from "./polarPaths.js";
|
|
6
|
+
|
|
7
|
+
function validateFrame(frame) {
|
|
8
|
+
if (
|
|
9
|
+
frame === null ||
|
|
10
|
+
typeof frame !== "object" ||
|
|
11
|
+
![frame.centerX, frame.centerY, frame.availableRadius].every(Number.isFinite) ||
|
|
12
|
+
frame.availableRadius < 0
|
|
13
|
+
) {
|
|
14
|
+
throw new TypeError(
|
|
15
|
+
"Polar guide frame requires finite centerX, centerY, and non-negative availableRadius."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return frame;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function validateFiniteValues(values, label) {
|
|
22
|
+
if (!Array.isArray(values) || !values.every(Number.isFinite)) {
|
|
23
|
+
throw new TypeError(`${label} must be an array of finite numbers.`);
|
|
24
|
+
}
|
|
25
|
+
return values;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validateNonNegative(value, label) {
|
|
29
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
30
|
+
throw new RangeError(`${label} must be a non-negative finite number.`);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function own(value) {
|
|
36
|
+
return freezeOwned(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function pointAt(frame, theta, radius) {
|
|
40
|
+
validateNonNegative(radius, "Polar guide radius");
|
|
41
|
+
const direction = polarDirection(theta);
|
|
42
|
+
return {
|
|
43
|
+
x: frame.centerX + direction.x * radius,
|
|
44
|
+
y: frame.centerY + direction.y * radius
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveThetaSpokes({ frame, angles }) {
|
|
49
|
+
validateFrame(frame);
|
|
50
|
+
validateFiniteValues(angles, "Polar spoke angles");
|
|
51
|
+
const end = angles.map(theta => pointAt(frame, theta, frame.availableRadius));
|
|
52
|
+
return own({
|
|
53
|
+
x1: angles.map(() => frame.centerX),
|
|
54
|
+
y1: angles.map(() => frame.centerY),
|
|
55
|
+
x2: end.map(point => point.x),
|
|
56
|
+
y2: end.map(point => point.y)
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function resolveRadialCircles({ frame, radii }) {
|
|
61
|
+
validateFrame(frame);
|
|
62
|
+
validateFiniteValues(radii, "Polar grid radii");
|
|
63
|
+
if (radii.some(radius => radius < 0 || radius > frame.availableRadius)) {
|
|
64
|
+
throw new RangeError("Polar grid radii must be inside the Polar frame.");
|
|
65
|
+
}
|
|
66
|
+
const visibleRadii = radii.filter(radius => radius > 0);
|
|
67
|
+
return own({
|
|
68
|
+
radii: visibleRadii,
|
|
69
|
+
commands: visibleRadii.map(radius => buildPolarCircleCommands(frame, radius))
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function resolveThetaAxisTicks({ frame, angles, length }) {
|
|
74
|
+
validateFrame(frame);
|
|
75
|
+
validateFiniteValues(angles, "Theta-axis angles");
|
|
76
|
+
validateNonNegative(length, "Theta-axis tick length");
|
|
77
|
+
const start = angles.map(theta => pointAt(frame, theta, frame.availableRadius));
|
|
78
|
+
const end = angles.map(theta => pointAt(
|
|
79
|
+
frame,
|
|
80
|
+
theta,
|
|
81
|
+
frame.availableRadius + length
|
|
82
|
+
));
|
|
83
|
+
return own({
|
|
84
|
+
x1: start.map(point => point.x),
|
|
85
|
+
y1: start.map(point => point.y),
|
|
86
|
+
x2: end.map(point => point.x),
|
|
87
|
+
y2: end.map(point => point.y)
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function perimeterAlignment(theta) {
|
|
92
|
+
const radians = theta * Math.PI / 180;
|
|
93
|
+
const horizontal = Math.sin(radians);
|
|
94
|
+
const vertical = Math.cos(radians);
|
|
95
|
+
return {
|
|
96
|
+
textAlign: horizontal > 0.25
|
|
97
|
+
? "left"
|
|
98
|
+
: horizontal < -0.25
|
|
99
|
+
? "right"
|
|
100
|
+
: "center",
|
|
101
|
+
textBaseline: vertical > 0.25
|
|
102
|
+
? "bottom"
|
|
103
|
+
: vertical < -0.25
|
|
104
|
+
? "top"
|
|
105
|
+
: "middle"
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function resolveThetaAxisLabels({ frame, angles, offset }) {
|
|
110
|
+
validateFrame(frame);
|
|
111
|
+
validateFiniteValues(angles, "Theta-axis label angles");
|
|
112
|
+
validateNonNegative(offset, "Theta-axis label offset");
|
|
113
|
+
const points = angles.map(theta => pointAt(
|
|
114
|
+
frame,
|
|
115
|
+
theta,
|
|
116
|
+
frame.availableRadius + offset
|
|
117
|
+
));
|
|
118
|
+
const alignment = angles.map(perimeterAlignment);
|
|
119
|
+
return own({
|
|
120
|
+
x: points.map(point => point.x),
|
|
121
|
+
y: points.map(point => point.y),
|
|
122
|
+
textAlign: alignment.map(value => value.textAlign),
|
|
123
|
+
textBaseline: alignment.map(value => value.textBaseline)
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function radialVectors(angle) {
|
|
128
|
+
let direction;
|
|
129
|
+
try {
|
|
130
|
+
direction = polarDirection(angle);
|
|
131
|
+
} catch {
|
|
132
|
+
throw new TypeError("Radial-axis angle must be finite degrees.");
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
direction,
|
|
136
|
+
normal: { x: -direction.y, y: direction.x }
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function resolveRadialAxisLine({ frame, angle, startRadius = 0 }) {
|
|
141
|
+
validateFrame(frame);
|
|
142
|
+
validateNonNegative(startRadius, "Radial-axis start radius");
|
|
143
|
+
if (startRadius > frame.availableRadius) {
|
|
144
|
+
throw new RangeError("Radial-axis start radius must be inside the Polar frame.");
|
|
145
|
+
}
|
|
146
|
+
const { direction } = radialVectors(angle);
|
|
147
|
+
return own({
|
|
148
|
+
x1: frame.centerX + direction.x * startRadius,
|
|
149
|
+
y1: frame.centerY + direction.y * startRadius,
|
|
150
|
+
x2: frame.centerX + direction.x * frame.availableRadius,
|
|
151
|
+
y2: frame.centerY + direction.y * frame.availableRadius
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function resolveRadialAxisTicks({ frame, angle, radii, length }) {
|
|
156
|
+
validateFrame(frame);
|
|
157
|
+
validateFiniteValues(radii, "Radial-axis tick radii");
|
|
158
|
+
validateNonNegative(length, "Radial-axis tick length");
|
|
159
|
+
if (radii.some(radius => radius < 0 || radius > frame.availableRadius)) {
|
|
160
|
+
throw new RangeError("Radial-axis tick radii must be inside the Polar frame.");
|
|
161
|
+
}
|
|
162
|
+
const { direction, normal } = radialVectors(angle);
|
|
163
|
+
const half = length / 2;
|
|
164
|
+
const centers = radii.map(radius => ({
|
|
165
|
+
x: frame.centerX + direction.x * radius,
|
|
166
|
+
y: frame.centerY + direction.y * radius
|
|
167
|
+
}));
|
|
168
|
+
return own({
|
|
169
|
+
x1: centers.map(point => point.x - normal.x * half),
|
|
170
|
+
y1: centers.map(point => point.y - normal.y * half),
|
|
171
|
+
x2: centers.map(point => point.x + normal.x * half),
|
|
172
|
+
y2: centers.map(point => point.y + normal.y * half)
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function resolveRadialAxisLabels({ frame, angle, radii, offset }) {
|
|
177
|
+
validateFrame(frame);
|
|
178
|
+
validateFiniteValues(radii, "Radial-axis label radii");
|
|
179
|
+
validateNonNegative(offset, "Radial-axis label offset");
|
|
180
|
+
if (radii.some(radius => radius < 0 || radius > frame.availableRadius)) {
|
|
181
|
+
throw new RangeError("Radial-axis label radii must be inside the Polar frame.");
|
|
182
|
+
}
|
|
183
|
+
const { direction, normal } = radialVectors(angle);
|
|
184
|
+
return own({
|
|
185
|
+
x: radii.map(radius =>
|
|
186
|
+
frame.centerX + direction.x * radius - normal.x * offset
|
|
187
|
+
),
|
|
188
|
+
y: radii.map(radius =>
|
|
189
|
+
frame.centerY + direction.y * radius - normal.y * offset
|
|
190
|
+
),
|
|
191
|
+
textAlign: radii.map(() => "center"),
|
|
192
|
+
textBaseline: radii.map(() => "bottom")
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function resolveRadialAxisTitle({
|
|
197
|
+
frame,
|
|
198
|
+
angle,
|
|
199
|
+
radius = frame.availableRadius / 2,
|
|
200
|
+
offset,
|
|
201
|
+
position = "inside"
|
|
202
|
+
}) {
|
|
203
|
+
validateFrame(frame);
|
|
204
|
+
validateNonNegative(offset, "Radial-axis title offset");
|
|
205
|
+
if (!["inside", "outside"].includes(position)) {
|
|
206
|
+
throw new Error(`Unknown radial-axis title position "${position}".`);
|
|
207
|
+
}
|
|
208
|
+
if (position === "outside") {
|
|
209
|
+
const { direction } = radialVectors(angle);
|
|
210
|
+
return own({
|
|
211
|
+
x: frame.centerX + direction.x * (frame.availableRadius + offset),
|
|
212
|
+
y: frame.centerY + direction.y * (frame.availableRadius + offset),
|
|
213
|
+
textAlign: direction.x > 0.25
|
|
214
|
+
? "left"
|
|
215
|
+
: direction.x < -0.25 ? "right" : "center",
|
|
216
|
+
textBaseline: direction.y > 0.25
|
|
217
|
+
? "top"
|
|
218
|
+
: direction.y < -0.25 ? "bottom" : "middle"
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
validateNonNegative(radius, "Radial-axis title radius");
|
|
222
|
+
if (radius > frame.availableRadius) {
|
|
223
|
+
throw new RangeError("Radial-axis title radius must be inside the Polar frame.");
|
|
224
|
+
}
|
|
225
|
+
const { direction, normal } = radialVectors(angle);
|
|
226
|
+
return own({
|
|
227
|
+
x: frame.centerX + direction.x * radius + normal.x * offset,
|
|
228
|
+
y: frame.centerY + direction.y * radius + normal.y * offset,
|
|
229
|
+
textAlign: "center",
|
|
230
|
+
textBaseline: "top"
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function resolveThetaAxisTitle({ frame, offset }) {
|
|
235
|
+
validateFrame(frame);
|
|
236
|
+
validateNonNegative(offset, "Theta-axis title offset");
|
|
237
|
+
return own({
|
|
238
|
+
x: frame.centerX,
|
|
239
|
+
y: frame.centerY + frame.availableRadius + offset,
|
|
240
|
+
textAlign: "center",
|
|
241
|
+
textBaseline: "middle"
|
|
242
|
+
});
|
|
243
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { buildLinearPathCommands } from "./pathCommands.js";
|
|
2
|
+
import { polarToCartesian } from "./polar.js";
|
|
3
|
+
import {
|
|
4
|
+
mapContinuousScaleValues,
|
|
5
|
+
mapOrdinalPositionValues
|
|
6
|
+
} from "./scales/index.js";
|
|
7
|
+
|
|
8
|
+
function mappedTheta(values, fieldType, scale) {
|
|
9
|
+
return ["nominal", "ordinal"].includes(fieldType)
|
|
10
|
+
? mapOrdinalPositionValues(values, scale)
|
|
11
|
+
: mapContinuousScaleValues(values, scale);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function buildPolarLinePathCommands({
|
|
15
|
+
series,
|
|
16
|
+
thetaFieldType,
|
|
17
|
+
thetaScale,
|
|
18
|
+
radiusScale,
|
|
19
|
+
frame,
|
|
20
|
+
closed = false
|
|
21
|
+
}) {
|
|
22
|
+
if (!Array.isArray(series) || series.length < 2) {
|
|
23
|
+
throw new TypeError("Polar line commands require at least two series values.");
|
|
24
|
+
}
|
|
25
|
+
if (typeof closed !== "boolean") {
|
|
26
|
+
throw new TypeError("Polar line closed must be a boolean.");
|
|
27
|
+
}
|
|
28
|
+
const theta = mappedTheta(
|
|
29
|
+
series.map(value => value.theta),
|
|
30
|
+
thetaFieldType,
|
|
31
|
+
thetaScale
|
|
32
|
+
);
|
|
33
|
+
const radius = mapContinuousScaleValues(
|
|
34
|
+
series.map(value => value.radius),
|
|
35
|
+
radiusScale
|
|
36
|
+
);
|
|
37
|
+
const points = series.map((_, index) => polarToCartesian({
|
|
38
|
+
theta: theta[index],
|
|
39
|
+
radius: radius[index],
|
|
40
|
+
frame
|
|
41
|
+
}));
|
|
42
|
+
return buildLinearPathCommands(points, { close: closed });
|
|
43
|
+
}
|