ggaction 0.0.9 → 0.0.11
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 +84 -0
- package/README.md +5 -4
- package/knowledge/action-card.schema.json +1 -0
- package/knowledge/action-cards.json +1 -10941
- package/knowledge/action-cards.schema.json +1 -0
- package/knowledge/extension-authoring.md +248 -0
- package/knowledge/intent-taxonomy.json +1 -183
- package/knowledge/intent-taxonomy.schema.json +1 -0
- package/knowledge/mcp-resources.json +1 -95
- package/knowledge/mcp-resources.schema.json +1 -0
- package/knowledge/task-packet.schema.json +1 -159
- package/knowledge/task-resolver.js +1 -1230
- package/package.json +19 -2
- package/src/actions/boxPlots/edit.js +45 -298
- package/src/actions/boxPlots/options.js +46 -77
- package/src/actions/boxPlots/resolve.js +1 -1
- package/src/actions/charts/bar.js +3 -0
- package/src/actions/charts/heatmap.js +73 -83
- package/src/actions/charts/index.js +1 -1
- package/src/actions/charts/parallel.js +35 -1
- package/src/actions/charts/shared.js +20 -0
- package/src/actions/composition/actions.js +46 -49
- package/src/actions/data/bin2d.js +105 -2
- package/src/actions/data/box.js +17 -17
- package/src/actions/data/density.js +42 -78
- package/src/actions/data/gradientProfile.js +22 -52
- package/src/actions/data/horizon.js +16 -41
- package/src/actions/data/index.js +10 -10
- package/src/actions/data/interval.js +16 -38
- package/src/actions/data/intervalEdit.js +55 -0
- package/src/actions/data/regression.js +7 -17
- package/src/actions/data/shared.js +47 -0
- package/src/actions/data/timeUnit.js +14 -38
- package/src/actions/data/window.js +14 -38
- package/src/actions/distributions/revision.js +154 -34
- package/src/actions/encodings/angle.js +9 -10
- package/src/actions/encodings/appearance.js +17 -51
- package/src/actions/encodings/density/resolve.js +3 -1
- package/src/actions/encodings/density.js +1 -15
- package/src/actions/encodings/horizon/resolve.js +13 -3
- package/src/actions/encodings/horizon.js +8 -7
- package/src/actions/encodings/index.js +9 -9
- package/src/actions/encodings/offset.js +8 -24
- package/src/actions/encodings/parallel.js +9 -10
- package/src/actions/encodings/pathOrder.js +6 -13
- package/src/actions/encodings/position/policies/index.js +4 -4
- package/src/actions/encodings/position/policies/line.js +47 -17
- package/src/actions/encodings/position/resolve.js +24 -5
- package/src/actions/encodings/ranged.js +18 -23
- package/src/actions/encodings/remove.js +15 -25
- package/src/actions/encodings/ruleAppearance.js +9 -24
- package/src/actions/encodings/shared.js +37 -0
- package/src/actions/encodings/strokeDash.js +8 -23
- package/src/actions/encodings/text.js +9 -10
- package/src/actions/errorBands/create.js +105 -39
- package/src/actions/errorBands/edit.js +89 -93
- package/src/actions/errorBands/index.js +1 -2
- package/src/actions/errorBars/create.js +78 -27
- package/src/actions/errorBars/edit.js +81 -48
- package/src/actions/errorBars/index.js +1 -2
- package/src/actions/facets/actions.js +45 -69
- package/src/actions/facets/derive.js +38 -53
- package/src/actions/facets/replay.js +0 -1
- package/src/actions/gradientPlots/components.js +59 -48
- package/src/actions/gradientPlots/edit.js +35 -216
- package/src/actions/gradientPlots/index.js +11 -10
- package/src/actions/gradientPlots/materialize.js +21 -35
- package/src/actions/gradientPlots/options.js +5 -7
- package/src/actions/gradientPlots/paint.js +9 -6
- package/src/actions/gradientPlots/resolve.js +1 -1
- package/src/actions/guides/axes/index.js +3 -3
- package/src/actions/guides/axes/labels.js +68 -26
- package/src/actions/guides/axes/lines.js +22 -2
- package/src/actions/guides/axes/policy.js +28 -8
- package/src/actions/guides/axes/ticks.js +27 -9
- package/src/actions/guides/axes/titles.js +113 -65
- package/src/actions/guides/grids/resolve.js +14 -0
- package/src/actions/guides/guides.js +2 -4
- package/src/actions/guides/index.js +2 -2
- package/src/actions/guides/legends/categorical/actions.js +44 -68
- package/src/actions/guides/legends/categorical/components.js +6 -1
- package/src/actions/guides/legends/categorical/index.js +2 -2
- package/src/actions/guides/legends/categorical/layout.js +148 -175
- package/src/actions/guides/legends/categorical/recipes.js +5 -0
- package/src/actions/guides/legends/categorical/resolve.js +8 -1
- package/src/actions/guides/legends/continuous/common.js +117 -66
- package/src/actions/guides/legends/continuous/gradient.js +38 -37
- package/src/actions/guides/legends/continuous/interval.js +97 -76
- package/src/actions/guides/legends/continuous/opacity.js +28 -38
- package/src/actions/guides/legends/edit.js +14 -1
- package/src/actions/guides/legends/index.js +1 -1
- package/src/actions/guides/legends/lane.js +2 -1
- package/src/actions/guides/legends/remove.js +8 -0
- package/src/actions/guides/legends/size.js +36 -43
- package/src/actions/guides/legends/strokeWidth.js +20 -26
- package/src/actions/guides/polar/axes/labels.js +17 -1
- package/src/actions/guides/polar/axes/shared.js +7 -1
- package/src/actions/guides/polar/axes/titles.js +19 -2
- package/src/actions/guides/polar/grids.js +4 -0
- package/src/actions/guides/polar/resolve.js +6 -0
- package/src/actions/index.js +13 -13
- package/src/actions/intervals/resolve.js +87 -117
- package/src/actions/marks/arc/actions.js +31 -36
- package/src/actions/marks/area/actions.js +23 -31
- package/src/actions/marks/index.js +6 -6
- package/src/actions/marks/line/actions.js +33 -52
- package/src/actions/marks/line/materialize.js +7 -5
- package/src/actions/marks/point/materialize.js +5 -3
- package/src/actions/marks/remove.js +21 -0
- package/src/actions/marks/rule/actions.js +53 -53
- package/src/actions/marks/shared.js +8 -4
- package/src/actions/marks/text/actions.js +14 -6
- package/src/actions/marks/text/layout.js +21 -45
- package/src/actions/marks/tick/actions.js +22 -21
- package/src/actions/primitives/createGraphics.js +4 -0
- package/src/actions/primitives/editGraphics.js +4 -0
- package/src/actions/primitives/index.js +3 -3
- package/src/actions/primitives/semantic.js +1 -1
- package/src/actions/primitives/semanticValidation/index.js +2 -2
- package/src/actions/primitives/semanticValidation/layer.js +1 -1
- package/src/actions/regression/edit.js +3 -43
- package/src/actions/scales/consumers/families.js +24 -4
- package/src/actions/scales/consumers/index.js +1 -1
- package/src/actions/scales/consumers/seriesLayout.js +17 -0
- package/src/actions/scales/definitions.js +100 -103
- package/src/actions/scales/materialize.js +5 -0
- package/src/actions/selection/actions.js +79 -121
- package/src/actions/titles/actions.js +48 -94
- package/src/actions/titles/resolve.js +14 -27
- package/src/actions/violinPlots/create.js +37 -44
- package/src/core/action.js +20 -5
- package/src/core/extensionRegistry.js +104 -0
- package/src/core/textMetrics.js +74 -37
- package/src/core/validation.js +18 -0
- package/src/extension.js +8 -1
- package/src/grammar/aggregate.js +48 -28
- package/src/grammar/arcs.js +58 -11
- package/src/grammar/areaSeries.js +14 -16
- package/src/grammar/bin2d.js +37 -23
- package/src/grammar/boxPlot.js +14 -13
- package/src/grammar/categoricalDensity.js +14 -14
- package/src/grammar/categoryOrder.js +8 -14
- package/src/grammar/curveCommands.js +87 -20
- package/src/grammar/density.js +162 -141
- package/src/grammar/facets/index.js +14 -0
- package/src/grammar/facets/scales.js +7 -4
- package/src/grammar/gradientProfile.js +12 -15
- package/src/grammar/histogram.js +145 -83
- package/src/grammar/horizon.js +60 -23
- package/src/grammar/interval.js +14 -13
- package/src/grammar/lineSeries.js +91 -61
- package/src/grammar/numeric.js +290 -0
- package/src/grammar/paint.js +3 -4
- package/src/grammar/palettes.js +21 -32
- package/src/grammar/pathCommands.js +6 -0
- package/src/grammar/polarPaths.js +5 -5
- package/src/grammar/regression/derive.js +48 -9
- package/src/grammar/regression/models.js +248 -69
- package/src/grammar/regression/parameters.js +8 -0
- package/src/grammar/scales/color.js +35 -26
- package/src/grammar/scales/continuous.js +71 -24
- package/src/grammar/scales/definition.js +1 -1
- package/src/grammar/scales/discretized.js +42 -12
- package/src/grammar/scales/fields.js +21 -4
- package/src/grammar/scales/ordinal.js +117 -61
- package/src/grammar/scales/temporal.js +40 -11
- package/src/grammar/scales/transformed.js +199 -52
- package/src/grammar/schemas/graphicBounds.js +270 -79
- package/src/grammar/seriesLayout.js +50 -18
- package/src/grammar/ticks.js +123 -53
- package/src/grammar/timeUnit.js +18 -8
- package/src/grammar/transforms.js +4 -1
- package/src/grammar/window.js +70 -57
- package/src/index.js +1 -1
- package/src/layout/canvas.js +4 -8
- package/src/layout/composition.js +39 -40
- package/src/layout/facets.js +10 -14
- package/src/layout/labels.js +91 -15
- package/src/layout/legend.js +22 -20
- package/src/layout/legendLane.js +54 -40
- package/src/layout/title.js +7 -7
- package/src/materialization/bars/aggregate.js +14 -40
- package/src/materialization/bars/grouped.js +7 -18
- package/src/materialization/bars/histogram.js +2 -19
- package/src/materialization/bars/ranged.js +20 -15
- package/src/materialization/bars/resolve.js +21 -0
- package/src/materialization/facetGuides/legacyCategorical.js +2 -1
- package/src/materialization/facetGuides/placement.js +34 -19
- package/src/materialization/facetGuides/preparation.js +9 -5
- package/src/materialization/facets.js +39 -27
- package/src/materialization/guides/resources.js +2 -1
- package/src/materialization/marks/capabilities.js +5 -2
- package/src/materialization/marks/index.js +24 -4
- package/src/materialization/scales/policies/bar.js +61 -59
- package/src/materialization/scales/policies/binnedPosition.js +34 -0
- package/src/materialization/scales/policies/series.js +13 -7
- package/src/materialization/scales/resolve.js +4 -5
- package/src/materialization/selection/items/arc.js +16 -2
- package/src/materialization/selection/items/bar.js +4 -2
- package/src/materialization/selection/items/common.js +21 -1
- package/src/materialization/selection/items/path.js +19 -2
- package/src/materialization/selection/items/point.js +4 -16
- package/src/materialization/selection/items/tick.js +13 -21
- package/src/materialization/selection/point.js +11 -4
- package/src/materialization/selection/styles.js +19 -27
- package/src/materialization/text.js +4 -1
- package/src/mcp/adapter.js +8 -3
- package/src/renderers/canvas/fill.js +2 -2
- package/src/renderers/canvas/index.js +35 -2
- package/src/renderers/canvas/native.js +160 -0
- package/src/renderers/pdf.js +14 -0
- package/src/renderers/svg.js +37 -4
- package/types/extension.d.ts +19 -0
- package/types/index.d.ts +3 -0
- package/types/program.d.ts +509 -131
- package/types/svg.d.ts +1 -0
- package/src/actions/errorBands/components.js +0 -81
- package/src/actions/errorBands/options.js +0 -39
- package/src/actions/errorBands/resolve.js +0 -28
- package/src/actions/errorBars/components.js +0 -62
- package/src/actions/errorBars/options.js +0 -58
- package/src/actions/errorBars/resolve.js +0 -25
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { cloneAndFreeze, isPlainObject } from "../../core/immutable.js";
|
|
2
|
+
import { validateGeneratedItemLimit } from "../../core/validation.js";
|
|
2
3
|
import {
|
|
3
4
|
normalizePalette,
|
|
4
5
|
resolveContinuousPalette
|
|
5
6
|
} from "../palettes.js";
|
|
7
|
+
import { inverseLerp } from "../numeric.js";
|
|
6
8
|
|
|
7
9
|
export const CONTINUOUS_COLOR_INTERPOLATIONS = cloneAndFreeze([
|
|
8
10
|
"rgb",
|
|
@@ -105,6 +107,10 @@ function rgbToHex({ r, g, b }) {
|
|
|
105
107
|
).join("")}`;
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
function byteChannels({ r, g, b }) {
|
|
111
|
+
return [r, g, b].map(channel => Math.round(channel * 255));
|
|
112
|
+
}
|
|
113
|
+
|
|
108
114
|
function rgbToHsl({ r, g, b }) {
|
|
109
115
|
const maximum = Math.max(r, g, b);
|
|
110
116
|
const minimum = Math.min(r, g, b);
|
|
@@ -213,10 +219,6 @@ function interpolateHue(left, right, amount, long) {
|
|
|
213
219
|
}
|
|
214
220
|
|
|
215
221
|
function mix(left, right, amount, method) {
|
|
216
|
-
const linear = key => left[key] + (right[key] - left[key]) * amount;
|
|
217
|
-
if (method === "rgb") {
|
|
218
|
-
return { r: linear("r"), g: linear("g"), b: linear("b") };
|
|
219
|
-
}
|
|
220
222
|
if (method.startsWith("hsl")) {
|
|
221
223
|
const a = rgbToHsl(left);
|
|
222
224
|
const b = rgbToHsl(right);
|
|
@@ -263,6 +265,7 @@ export function validateContinuousColorInterpolation(value) {
|
|
|
263
265
|
export function validateSequentialColorRange(value) {
|
|
264
266
|
if (value === "auto") return value;
|
|
265
267
|
if (Array.isArray(value)) {
|
|
268
|
+
validateGeneratedItemLimit(value.length, "Sequential color range length");
|
|
266
269
|
if (value.length < 2) {
|
|
267
270
|
throw new TypeError("Sequential color range requires at least two colors.");
|
|
268
271
|
}
|
|
@@ -293,29 +296,33 @@ export function resolveSequentialColorStops(value) {
|
|
|
293
296
|
: resolveContinuousPalette(range.palette);
|
|
294
297
|
}
|
|
295
298
|
|
|
299
|
+
function interpolateColors(colors, position, method, shouldClamp) {
|
|
300
|
+
const bounded = shouldClamp ? clamp(position) : position;
|
|
301
|
+
const scaled = bounded * (colors.length - 1);
|
|
302
|
+
const left = Math.max(0, Math.min(colors.length - 2, Math.floor(scaled)));
|
|
303
|
+
const right = left + 1;
|
|
304
|
+
const amount = scaled - left;
|
|
305
|
+
if (method === "rgb") {
|
|
306
|
+
const leftChannels = byteChannels(colors[left]);
|
|
307
|
+
const rightChannels = byteChannels(colors[right]);
|
|
308
|
+
return `#${leftChannels.map((channel, index) =>
|
|
309
|
+
Math.round(clamp(
|
|
310
|
+
channel + (rightChannels[index] - channel) * amount,
|
|
311
|
+
0,
|
|
312
|
+
255
|
|
313
|
+
)).toString(16).padStart(2, "0")
|
|
314
|
+
).join("")}`;
|
|
315
|
+
}
|
|
316
|
+
return rgbToHex(mix(colors[left], colors[right], amount, method));
|
|
317
|
+
}
|
|
318
|
+
|
|
296
319
|
export function interpolateColorStops(stops, position, interpolation = "rgb") {
|
|
297
320
|
const method = validateContinuousColorInterpolation(interpolation);
|
|
298
321
|
if (!Array.isArray(stops) || stops.length < 2) {
|
|
299
322
|
throw new TypeError("Color interpolation requires at least two stops.");
|
|
300
323
|
}
|
|
301
324
|
const colors = stops.map(parseColor);
|
|
302
|
-
|
|
303
|
-
const scaled = bounded * (colors.length - 1);
|
|
304
|
-
const left = Math.floor(scaled);
|
|
305
|
-
const right = Math.min(colors.length - 1, left + 1);
|
|
306
|
-
if (method === "rgb") {
|
|
307
|
-
const leftHex = rgbToHex(colors[left]);
|
|
308
|
-
const rightHex = rgbToHex(colors[right]);
|
|
309
|
-
const amount = scaled - left;
|
|
310
|
-
return `#${[1, 3, 5].map(offset =>
|
|
311
|
-
Math.round(
|
|
312
|
-
Number.parseInt(leftHex.slice(offset, offset + 2), 16) +
|
|
313
|
-
(Number.parseInt(rightHex.slice(offset, offset + 2), 16) -
|
|
314
|
-
Number.parseInt(leftHex.slice(offset, offset + 2), 16)) * amount
|
|
315
|
-
).toString(16).padStart(2, "0")
|
|
316
|
-
).join("")}`;
|
|
317
|
-
}
|
|
318
|
-
return rgbToHex(mix(colors[left], colors[right], scaled - left, method));
|
|
325
|
+
return interpolateColors(colors, position, method, true);
|
|
319
326
|
}
|
|
320
327
|
|
|
321
328
|
export function mapSequentialColors(
|
|
@@ -337,15 +344,17 @@ export function mapSequentialColors(
|
|
|
337
344
|
}
|
|
338
345
|
const [start, end] = domain;
|
|
339
346
|
const constant = start === end;
|
|
347
|
+
const method = validateContinuousColorInterpolation(interpolation);
|
|
348
|
+
if (!Array.isArray(stops) || stops.length < 2) {
|
|
349
|
+
throw new TypeError("Color interpolation requires at least two stops.");
|
|
350
|
+
}
|
|
351
|
+
const colors = stops.map(parseColor);
|
|
340
352
|
return cloneAndFreeze(values.map(value => {
|
|
341
353
|
if (!Number.isFinite(value)) {
|
|
342
354
|
if (hasUnknown) return options.unknown;
|
|
343
355
|
throw new TypeError("Sequential color values must be finite numbers.");
|
|
344
356
|
}
|
|
345
|
-
const raw = constant ? 0.5 : (value
|
|
346
|
-
|
|
347
|
-
return interpolateColorStops(stops, raw < 0 ? 0 : 1, interpolation);
|
|
348
|
-
}
|
|
349
|
-
return interpolateColorStops(stops, clamp(raw), interpolation);
|
|
357
|
+
const raw = constant ? 0.5 : inverseLerp(value, start, end);
|
|
358
|
+
return interpolateColors(colors, raw, method, shouldClamp);
|
|
350
359
|
}));
|
|
351
360
|
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { cloneAndFreeze, isPlainObject } from "../../core/immutable.js";
|
|
2
2
|
import { POLAR_POSITION_CHANNELS } from "../../core/vocabulary.js";
|
|
3
|
+
import {
|
|
4
|
+
alignNumericStep,
|
|
5
|
+
cleanNumericValue,
|
|
6
|
+
interpolateNumber,
|
|
7
|
+
inverseLerp,
|
|
8
|
+
niceNumericStep,
|
|
9
|
+
normalizeNumericRange
|
|
10
|
+
} from "../numeric.js";
|
|
3
11
|
import { resolvePolarScaleRange } from "../polar.js";
|
|
4
12
|
import { niceTimeDomain } from "./temporal.js";
|
|
5
13
|
import {
|
|
@@ -24,29 +32,48 @@ export function resolveScaleDomain(domain, values) {
|
|
|
24
32
|
return cloneAndFreeze([minimum, maximum]);
|
|
25
33
|
}
|
|
26
34
|
|
|
27
|
-
function niceLinearStep(span, count = 5) {
|
|
28
|
-
const rough = span / Math.max(1, count);
|
|
29
|
-
const power = 10 ** Math.floor(Math.log10(rough));
|
|
30
|
-
const fraction = rough / power;
|
|
31
|
-
const factor = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 3 ? 3
|
|
32
|
-
: fraction <= 5 ? 5 : 10;
|
|
33
|
-
return factor * power;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
35
|
export function niceLinearDomain(domain) {
|
|
37
36
|
const [minimum, maximum] = domain;
|
|
37
|
+
if (minimum === maximum) return cloneAndFreeze([minimum, maximum]);
|
|
38
|
+
|
|
39
|
+
const directSpan = maximum - minimum;
|
|
38
40
|
const tolerance = Number.EPSILON * 16 * Math.max(
|
|
39
|
-
1,
|
|
41
|
+
1,
|
|
42
|
+
Math.abs(minimum),
|
|
43
|
+
Math.abs(maximum)
|
|
40
44
|
);
|
|
41
|
-
if (
|
|
42
|
-
const
|
|
43
|
-
|
|
45
|
+
if (Number.isFinite(directSpan) && directSpan > tolerance) {
|
|
46
|
+
const directStep = niceNumericStep(directSpan);
|
|
47
|
+
const direct = [
|
|
48
|
+
Number((Math.floor(minimum / directStep) * directStep).toPrecision(12)),
|
|
49
|
+
Number((Math.ceil(maximum / directStep) * directStep).toPrecision(12))
|
|
50
|
+
];
|
|
51
|
+
if (
|
|
52
|
+
direct.every(Number.isFinite) &&
|
|
53
|
+
direct[0] < direct[1] &&
|
|
54
|
+
direct[0] <= minimum &&
|
|
55
|
+
direct[1] >= maximum
|
|
56
|
+
) {
|
|
57
|
+
return cloneAndFreeze(direct);
|
|
58
|
+
}
|
|
44
59
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
|
|
61
|
+
const normalized = normalizeNumericRange(minimum, maximum);
|
|
62
|
+
const normalizedStep = niceNumericStep(normalized.span);
|
|
63
|
+
const resolution = normalizedStep * normalized.scale;
|
|
64
|
+
let lower = cleanNumericValue(
|
|
65
|
+
alignNumericStep(normalized.start, normalizedStep, "floor") * normalized.scale,
|
|
66
|
+
resolution
|
|
67
|
+
);
|
|
68
|
+
let upper = cleanNumericValue(
|
|
69
|
+
alignNumericStep(normalized.end, normalizedStep, "ceil") * normalized.scale,
|
|
70
|
+
resolution
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (!Number.isFinite(lower) || lower > minimum) lower = minimum;
|
|
74
|
+
if (!Number.isFinite(upper) || upper < maximum) upper = maximum;
|
|
75
|
+
if (lower === upper) return cloneAndFreeze([minimum, maximum]);
|
|
76
|
+
return cloneAndFreeze([lower, upper]);
|
|
50
77
|
}
|
|
51
78
|
|
|
52
79
|
export function resolveContinuousDomain({ domain, values, type, nice, zero }) {
|
|
@@ -80,9 +107,13 @@ export function resolveScaleRange(range, channel, bounds) {
|
|
|
80
107
|
) {
|
|
81
108
|
throw new Error("Automatic position range requires graphical bounds.");
|
|
82
109
|
}
|
|
83
|
-
|
|
84
|
-
?
|
|
85
|
-
:
|
|
110
|
+
const resolved = channel === "x"
|
|
111
|
+
? [bounds.x, bounds.x + bounds.width]
|
|
112
|
+
: [bounds.y + bounds.height, bounds.y];
|
|
113
|
+
if (!resolved.every(Number.isFinite)) {
|
|
114
|
+
throw new RangeError("Automatic position range exceeds the finite numeric range.");
|
|
115
|
+
}
|
|
116
|
+
return cloneAndFreeze(resolved);
|
|
86
117
|
}
|
|
87
118
|
|
|
88
119
|
export function mapLinearValues(values, domain, range, options = {}) {
|
|
@@ -97,7 +128,7 @@ export function mapLinearValues(values, domain, range, options = {}) {
|
|
|
97
128
|
throw new TypeError("Linear scale values must be finite numbers.");
|
|
98
129
|
}
|
|
99
130
|
if (domainStart === domainEnd) {
|
|
100
|
-
const midpoint = (rangeStart
|
|
131
|
+
const midpoint = interpolateNumber(rangeStart, rangeEnd, 0.5);
|
|
101
132
|
return cloneAndFreeze(values.map(value =>
|
|
102
133
|
Number.isFinite(value) ? midpoint : options.unknown
|
|
103
134
|
));
|
|
@@ -109,8 +140,24 @@ export function mapLinearValues(values, domain, range, options = {}) {
|
|
|
109
140
|
if (hasUnknown) return options.unknown;
|
|
110
141
|
throw new TypeError("Linear scale values must be finite numbers.");
|
|
111
142
|
}
|
|
112
|
-
|
|
143
|
+
if (value === domainStart) return rangeStart;
|
|
144
|
+
if (value === domainEnd) return rangeEnd;
|
|
145
|
+
const directProportion = (value - domainStart) / domainSpan;
|
|
146
|
+
const directResolved = clamp
|
|
147
|
+
? Math.max(0, Math.min(1, directProportion))
|
|
148
|
+
: directProportion;
|
|
149
|
+
if (Number.isFinite(domainSpan) && directResolved === 0) return rangeStart;
|
|
150
|
+
if (Number.isFinite(domainSpan) && directResolved === 1) return rangeEnd;
|
|
151
|
+
const directValue = rangeStart + directResolved * rangeSpan;
|
|
152
|
+
if (
|
|
153
|
+
Number.isFinite(domainSpan) &&
|
|
154
|
+
Number.isFinite(rangeSpan) &&
|
|
155
|
+
Number.isFinite(directValue)
|
|
156
|
+
) {
|
|
157
|
+
return directValue;
|
|
158
|
+
}
|
|
159
|
+
const proportion = inverseLerp(value, domainStart, domainEnd);
|
|
113
160
|
const resolved = clamp ? Math.max(0, Math.min(1, proportion)) : proportion;
|
|
114
|
-
return rangeStart
|
|
161
|
+
return interpolateNumber(rangeStart, rangeEnd, resolved);
|
|
115
162
|
}));
|
|
116
163
|
}
|
|
@@ -118,7 +118,7 @@ export function normalizeScaleDefinition({
|
|
|
118
118
|
if (type === "pow") definition.exponent = parameters.exponent;
|
|
119
119
|
if (type === "symlog") definition.constant = parameters.constant;
|
|
120
120
|
if (definition.domain !== "auto") {
|
|
121
|
-
validateTransformedDomain(type, definition.domain,
|
|
121
|
+
validateTransformedDomain(type, definition.domain, requested);
|
|
122
122
|
}
|
|
123
123
|
} else {
|
|
124
124
|
for (const property of Object.keys(requested)) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { cloneAndFreeze } from "../../core/immutable.js";
|
|
2
|
+
import { validateGeneratedItemLimit } from "../../core/validation.js";
|
|
3
|
+
import { interpolateNumber } from "../numeric.js";
|
|
2
4
|
import { resolveColorRange, validateColorRange } from "./appearance.js";
|
|
3
5
|
import { SCALE_ROLES, validateScaleTypeForRole } from "./types.js";
|
|
4
6
|
|
|
@@ -35,7 +37,7 @@ function quantile(sorted, probability) {
|
|
|
35
37
|
const position = (sorted.length - 1) * probability;
|
|
36
38
|
const lower = Math.floor(position);
|
|
37
39
|
const upper = Math.ceil(position);
|
|
38
|
-
return sorted[lower]
|
|
40
|
+
return interpolateNumber(sorted[lower], sorted[upper], position - lower);
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
export function validateDiscretizedColorDomain(type, domain) {
|
|
@@ -62,6 +64,12 @@ export function validateDiscretizedColorDomain(type, domain) {
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
export function validateDiscretizedColorRange(range) {
|
|
67
|
+
if (Array.isArray(range)) {
|
|
68
|
+
validateGeneratedItemLimit(
|
|
69
|
+
range.length,
|
|
70
|
+
"Discretized color range length"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
65
73
|
const validated = validateColorRange(range);
|
|
66
74
|
if (validated === "auto") return validated;
|
|
67
75
|
if (Array.isArray(validated) && validated.length < 2) {
|
|
@@ -76,7 +84,7 @@ export function resolveDiscretizedColorScale({ type, domain, range, values }) {
|
|
|
76
84
|
const requestedDomain = validateDiscretizedColorDomain(type, domain);
|
|
77
85
|
const sample = [...values].sort((left, right) => left - right);
|
|
78
86
|
const validatedRange = validateDiscretizedColorRange(range);
|
|
79
|
-
const colorCount = type === "threshold"
|
|
87
|
+
const colorCount = type === "threshold"
|
|
80
88
|
? requestedDomain.length + 1
|
|
81
89
|
: Array.isArray(range) ? range.length : 5;
|
|
82
90
|
const colors = resolveColorRange(
|
|
@@ -97,9 +105,20 @@ export function resolveDiscretizedColorScale({ type, domain, range, values }) {
|
|
|
97
105
|
}
|
|
98
106
|
thresholds = Array.from(
|
|
99
107
|
{ length: colors.length - 1 },
|
|
100
|
-
(_, index) =>
|
|
101
|
-
|
|
108
|
+
(_, index) => interpolateNumber(
|
|
109
|
+
resolvedDomain[0],
|
|
110
|
+
resolvedDomain[1],
|
|
111
|
+
(index + 1) / colors.length
|
|
112
|
+
)
|
|
102
113
|
);
|
|
114
|
+
if (thresholds.some((value, index) =>
|
|
115
|
+
value <= (index === 0 ? resolvedDomain[0] : thresholds[index - 1]) ||
|
|
116
|
+
value >= resolvedDomain[1]
|
|
117
|
+
)) {
|
|
118
|
+
throw new RangeError(
|
|
119
|
+
"Quantize color range requests more classes than its numeric domain can represent."
|
|
120
|
+
);
|
|
121
|
+
}
|
|
103
122
|
} else if (type === "quantile") {
|
|
104
123
|
const source = requestedDomain === "auto"
|
|
105
124
|
? sample
|
|
@@ -143,17 +162,28 @@ export function mapDiscretizedColors(values, scale) {
|
|
|
143
162
|
}));
|
|
144
163
|
}
|
|
145
164
|
|
|
146
|
-
function formatBoundary(value) {
|
|
147
|
-
return Number.isInteger(value) ? String(value) : value.toFixed(1);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
165
|
export function formatDiscretizedIntervals(thresholds) {
|
|
151
166
|
nondecreasing(thresholds, "Discretized color thresholds");
|
|
167
|
+
let resolution = Infinity;
|
|
168
|
+
for (let index = 1; index < thresholds.length; index += 1) {
|
|
169
|
+
const gap = thresholds[index] - thresholds[index - 1];
|
|
170
|
+
if (Number.isFinite(gap) && gap > 0) resolution = Math.min(resolution, gap);
|
|
171
|
+
}
|
|
172
|
+
let labels = thresholds.map(value =>
|
|
173
|
+
Number.isInteger(value) ? String(value) : value.toFixed(1)
|
|
174
|
+
);
|
|
175
|
+
if (thresholds.some((value, index) => {
|
|
176
|
+
const rounded = Number(labels[index]);
|
|
177
|
+
return value !== 0 && rounded === 0 ||
|
|
178
|
+
index > 0 && value !== thresholds[index - 1] &&
|
|
179
|
+
labels[index] === labels[index - 1] ||
|
|
180
|
+
!Number.isInteger(value) && Math.abs(rounded - value) > resolution / 4;
|
|
181
|
+
})) labels = thresholds.map(String);
|
|
152
182
|
return cloneAndFreeze([
|
|
153
|
-
`< ${
|
|
154
|
-
...
|
|
155
|
-
`${
|
|
183
|
+
`< ${labels[0]}`,
|
|
184
|
+
...labels.slice(0, -1).map((value, index) =>
|
|
185
|
+
`${value}–${labels[index + 1]}`
|
|
156
186
|
),
|
|
157
|
-
`≥ ${
|
|
187
|
+
`≥ ${labels.at(-1)}`
|
|
158
188
|
]);
|
|
159
189
|
}
|
|
@@ -55,8 +55,11 @@ export function readQuantitativeField(rows, field) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
function utcDate(year, month = 1, day = 1) {
|
|
58
|
-
const
|
|
59
|
-
|
|
58
|
+
const date = new Date(0);
|
|
59
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
60
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
61
|
+
const timestamp = date.getTime();
|
|
62
|
+
if (!Number.isFinite(timestamp)) return undefined;
|
|
60
63
|
return date.getUTCFullYear() === year &&
|
|
61
64
|
date.getUTCMonth() === month - 1 &&
|
|
62
65
|
date.getUTCDate() === day
|
|
@@ -64,6 +67,20 @@ function utcDate(year, month = 1, day = 1) {
|
|
|
64
67
|
: undefined;
|
|
65
68
|
}
|
|
66
69
|
|
|
70
|
+
function parseTemporalString(value) {
|
|
71
|
+
const isoDateTime =
|
|
72
|
+
/^(\d{4})[-/](\d{2})[-/](\d{2})[T ](\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?)(Z|[+-]\d{2}:?\d{2})?$/iu
|
|
73
|
+
.exec(value);
|
|
74
|
+
if (isoDateTime === null) return Date.parse(value);
|
|
75
|
+
const [, year, month, day, time, zone] = isoDateTime;
|
|
76
|
+
const normalized = `${year}-${month}-${day}T${time}${zone ?? "Z"}`;
|
|
77
|
+
return Date.parse(normalized);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function validTimestamp(value) {
|
|
81
|
+
return Number.isFinite(value) && Number.isFinite(new Date(value).getTime());
|
|
82
|
+
}
|
|
83
|
+
|
|
67
84
|
export function normalizeTemporalValue(value, field = "value", index = 0) {
|
|
68
85
|
let timestamp;
|
|
69
86
|
if (typeof value === "number") {
|
|
@@ -77,9 +94,9 @@ export function normalizeTemporalValue(value, field = "value", index = 0) {
|
|
|
77
94
|
? utcDate(Number(year[1]))
|
|
78
95
|
: date !== null
|
|
79
96
|
? utcDate(Number(date[1]), Number(date[2]), Number(date[3]))
|
|
80
|
-
:
|
|
97
|
+
: parseTemporalString(value);
|
|
81
98
|
}
|
|
82
|
-
if (!
|
|
99
|
+
if (!validTimestamp(timestamp)) {
|
|
83
100
|
throw new TypeError(
|
|
84
101
|
`Field "${field}" must contain a temporal string or finite timestamp ` +
|
|
85
102
|
`(including a valid date or four-digit year) at row ${index}.`
|
|
@@ -3,6 +3,54 @@ import { isNominalValue } from "./fields.js";
|
|
|
3
3
|
import { validateColorRange, validateStrokeDashRange } from "./appearance.js";
|
|
4
4
|
import { validatePair, validateScaleRange } from "./validation.js";
|
|
5
5
|
import { resolveScaleRange } from "./continuous.js";
|
|
6
|
+
import { interpolateNumber } from "../numeric.js";
|
|
7
|
+
|
|
8
|
+
function divideRange(range, denominator, label) {
|
|
9
|
+
const direct = (range[1] - range[0]) / denominator;
|
|
10
|
+
if (Number.isFinite(direct) && (direct !== 0 || range[0] === range[1])) {
|
|
11
|
+
return direct;
|
|
12
|
+
}
|
|
13
|
+
const step = interpolateNumber(range[0], range[1], 1 / denominator) - range[0];
|
|
14
|
+
if (!Number.isFinite(step) || (step === 0 && range[0] !== range[1])) {
|
|
15
|
+
throw new RangeError(`${label} exceeds the finite numeric range.`);
|
|
16
|
+
}
|
|
17
|
+
return step;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function requireDistinctPositions(scale, label) {
|
|
21
|
+
const positions = mapOrdinalPositionValues(scale.domain, scale);
|
|
22
|
+
if (positions.some((position, index) =>
|
|
23
|
+
!Number.isFinite(position) ||
|
|
24
|
+
index > 0 && position === positions[index - 1]
|
|
25
|
+
)) {
|
|
26
|
+
throw new RangeError(`${label} cannot represent every domain position.`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function finitePosition(value, scale, proportion) {
|
|
31
|
+
return Number.isFinite(value)
|
|
32
|
+
? value
|
|
33
|
+
: interpolateNumber(scale.range[0], scale.range[1], proportion);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function resolvePositionRange(range, channel, bounds, label) {
|
|
37
|
+
const resolved = range === "auto" && channel === "y"
|
|
38
|
+
? cloneAndFreeze([bounds.y, bounds.y + bounds.height])
|
|
39
|
+
: resolveScaleRange(range, channel, bounds);
|
|
40
|
+
if (!resolved.every(Number.isFinite)) {
|
|
41
|
+
throw new RangeError(`Automatic ${label} range exceeds the finite numeric range.`);
|
|
42
|
+
}
|
|
43
|
+
return resolved;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function validateDomainValues(domain, values, unknown, label) {
|
|
47
|
+
const domainValues = new Set(domain);
|
|
48
|
+
for (const value of values) {
|
|
49
|
+
if (!domainValues.has(value) && unknown === undefined) {
|
|
50
|
+
throw new Error(`Value "${value}" is outside the ${label} domain.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
6
54
|
|
|
7
55
|
export function validateOrdinalDomain(domain) {
|
|
8
56
|
if (domain === "auto") return domain;
|
|
@@ -21,14 +69,14 @@ export function validateOrdinalDomain(domain) {
|
|
|
21
69
|
|
|
22
70
|
export function validateOrdinalRange(range) {
|
|
23
71
|
if (range === "auto") return range;
|
|
24
|
-
if (Array.isArray(range)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return validateStrokeDashRange(range);
|
|
72
|
+
if (Array.isArray(range)) {
|
|
73
|
+
if (range.length === 2 && range.every(Number.isFinite)) {
|
|
74
|
+
return cloneAndFreeze(range);
|
|
75
|
+
}
|
|
76
|
+
if (range.every(item => typeof item === "string")) {
|
|
77
|
+
return validateColorRange(range);
|
|
78
|
+
}
|
|
79
|
+
if (range.every(Array.isArray)) return validateStrokeDashRange(range);
|
|
32
80
|
}
|
|
33
81
|
return validateColorRange(range);
|
|
34
82
|
}
|
|
@@ -51,30 +99,22 @@ export function resolveOrdinalPositionScale({
|
|
|
51
99
|
unknown
|
|
52
100
|
}) {
|
|
53
101
|
const resolvedDomain = resolveOrdinalDomain(domain, values);
|
|
54
|
-
const resolvedRange = range
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
const step = (resolvedRange[1] - resolvedRange[0]) / resolvedDomain.length;
|
|
64
|
-
return cloneAndFreeze({
|
|
102
|
+
const resolvedRange = resolvePositionRange(range, channel, bounds, "ordinal");
|
|
103
|
+
validateDomainValues(resolvedDomain, values, unknown, "ordinal");
|
|
104
|
+
const step = divideRange(
|
|
105
|
+
resolvedRange,
|
|
106
|
+
resolvedDomain.length,
|
|
107
|
+
"Ordinal position step"
|
|
108
|
+
);
|
|
109
|
+
const resolved = {
|
|
65
110
|
type: "ordinal",
|
|
66
111
|
domain: resolvedDomain,
|
|
67
112
|
range: resolvedRange,
|
|
68
113
|
step,
|
|
69
114
|
bandwidth: Math.abs(step)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
function validateAlign(value) {
|
|
74
|
-
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
75
|
-
throw new RangeError("Discrete scale align must be between 0 and 1.");
|
|
76
|
-
}
|
|
77
|
-
return value;
|
|
115
|
+
};
|
|
116
|
+
requireDistinctPositions(resolved, "Ordinal position range");
|
|
117
|
+
return cloneAndFreeze(resolved);
|
|
78
118
|
}
|
|
79
119
|
|
|
80
120
|
export function resolveDiscretePositionScale({
|
|
@@ -90,21 +130,17 @@ export function resolveDiscretePositionScale({
|
|
|
90
130
|
align = 0.5,
|
|
91
131
|
unknown
|
|
92
132
|
}) {
|
|
93
|
-
|
|
133
|
+
const band = type === "band";
|
|
134
|
+
if (!band && type !== "point") {
|
|
94
135
|
throw new Error(`Unsupported discrete position scale type "${type}".`);
|
|
95
136
|
}
|
|
96
137
|
const resolvedDomain = resolveOrdinalDomain(domain, values);
|
|
97
|
-
const resolvedRange = range
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
for (const value of values) {
|
|
102
|
-
if (!domainValues.has(value) && unknown === undefined) {
|
|
103
|
-
throw new Error(`Value "${value}" is outside the discrete domain.`);
|
|
104
|
-
}
|
|
138
|
+
const resolvedRange = resolvePositionRange(range, channel, bounds, "discrete");
|
|
139
|
+
validateDomainValues(resolvedDomain, values, unknown, "discrete");
|
|
140
|
+
if (!Number.isFinite(align) || align < 0 || align > 1) {
|
|
141
|
+
throw new RangeError("Discrete scale align must be between 0 and 1.");
|
|
105
142
|
}
|
|
106
|
-
|
|
107
|
-
if (type === "band") {
|
|
143
|
+
if (band) {
|
|
108
144
|
if (!Number.isFinite(paddingInner) || paddingInner < 0 || paddingInner >= 1) {
|
|
109
145
|
throw new RangeError(
|
|
110
146
|
"Band scale paddingInner must be from 0 (inclusive) to 1 (exclusive)."
|
|
@@ -120,20 +156,26 @@ export function resolveDiscretePositionScale({
|
|
|
120
156
|
"Point scale padding must be a non-negative finite number."
|
|
121
157
|
);
|
|
122
158
|
}
|
|
123
|
-
const span = Math.abs(resolvedRange[1] - resolvedRange[0]);
|
|
124
159
|
const direction = Math.sign(resolvedRange[1] - resolvedRange[0]) || 1;
|
|
125
160
|
const count = resolvedDomain.length;
|
|
126
|
-
const denominator =
|
|
161
|
+
const denominator = band
|
|
127
162
|
? Math.max(1, count - paddingInner + paddingOuter * 2)
|
|
128
163
|
: Math.max(1, count - 1 + padding * 2);
|
|
129
|
-
const step =
|
|
130
|
-
const bandwidth =
|
|
164
|
+
const step = divideRange(resolvedRange, denominator, "Discrete position step");
|
|
165
|
+
const bandwidth = band
|
|
131
166
|
? Math.abs(step) * (1 - paddingInner)
|
|
132
167
|
: 0;
|
|
133
|
-
const outerPadding =
|
|
134
|
-
const
|
|
168
|
+
const outerPadding = band ? paddingOuter : padding;
|
|
169
|
+
const directStart = resolvedRange[0] +
|
|
135
170
|
direction * Math.abs(step) * outerPadding * 2 * align;
|
|
136
|
-
|
|
171
|
+
const start = Number.isFinite(directStart)
|
|
172
|
+
? directStart
|
|
173
|
+
: interpolateNumber(
|
|
174
|
+
resolvedRange[0],
|
|
175
|
+
resolvedRange[1],
|
|
176
|
+
outerPadding * 2 * align / denominator
|
|
177
|
+
);
|
|
178
|
+
const resolved = {
|
|
137
179
|
type,
|
|
138
180
|
domain: resolvedDomain,
|
|
139
181
|
range: resolvedRange,
|
|
@@ -141,8 +183,10 @@ export function resolveDiscretePositionScale({
|
|
|
141
183
|
start,
|
|
142
184
|
bandwidth,
|
|
143
185
|
align,
|
|
144
|
-
...(
|
|
145
|
-
}
|
|
186
|
+
...(band ? { paddingInner, paddingOuter } : { padding })
|
|
187
|
+
};
|
|
188
|
+
requireDistinctPositions(resolved, "Discrete position range");
|
|
189
|
+
return cloneAndFreeze(resolved);
|
|
146
190
|
}
|
|
147
191
|
|
|
148
192
|
export function resolveOrdinalOffsetScale({
|
|
@@ -173,14 +217,16 @@ export function resolveOrdinalOffsetScale({
|
|
|
173
217
|
"Offset scale paddingOuter must be a non-negative finite number."
|
|
174
218
|
);
|
|
175
219
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
220
|
+
validateDomainValues(resolvedDomain, values, undefined, "ordinal");
|
|
221
|
+
const denominator = Math.max(
|
|
222
|
+
1,
|
|
223
|
+
resolvedDomain.length - paddingInner + paddingOuter * 2
|
|
224
|
+
);
|
|
225
|
+
const step = divideRange(
|
|
226
|
+
resolvedRange,
|
|
227
|
+
denominator,
|
|
228
|
+
"Offset scale padding must leave a positive bandwidth"
|
|
229
|
+
);
|
|
184
230
|
const bandwidth = Math.abs(step) * (1 - paddingInner);
|
|
185
231
|
if (!Number.isFinite(bandwidth) || bandwidth <= 0) {
|
|
186
232
|
throw new Error("Offset scale padding must leave a positive bandwidth.");
|
|
@@ -206,19 +252,29 @@ export function mapOrdinalPositionValues(values, scale) {
|
|
|
206
252
|
if (hasUnknown) return scale.unknown;
|
|
207
253
|
throw new Error(`Value "${value}" is outside the ordinal domain.`);
|
|
208
254
|
}
|
|
255
|
+
const positional = scale.type === "band" || scale.type === "point";
|
|
209
256
|
if (
|
|
257
|
+
!positional ||
|
|
210
258
|
(scale.type === "band" &&
|
|
211
259
|
scale.paddingInner === 0 &&
|
|
212
260
|
scale.paddingOuter === 0 &&
|
|
213
261
|
scale.align === 0.5) ||
|
|
214
262
|
(scale.type === "point" && scale.padding === 0.5 && scale.align === 0.5)
|
|
215
263
|
) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (["band", "point"].includes(scale.type)) {
|
|
219
|
-
const direction = Math.sign(scale.step) || 1;
|
|
220
|
-
return scale.start + index * scale.step + direction * scale.bandwidth / 2;
|
|
264
|
+
const direct = scale.range[0] + (index + 0.5) * scale.step;
|
|
265
|
+
return finitePosition(direct, scale, (index + 0.5) / scale.domain.length);
|
|
221
266
|
}
|
|
222
|
-
|
|
267
|
+
const direction = Math.sign(scale.step) || 1;
|
|
268
|
+
const direct = scale.start + index * scale.step +
|
|
269
|
+
direction * scale.bandwidth / 2;
|
|
270
|
+
const band = scale.type === "band";
|
|
271
|
+
const outer = band ? scale.paddingOuter : scale.padding;
|
|
272
|
+
const inner = band ? scale.paddingInner : 1;
|
|
273
|
+
return finitePosition(
|
|
274
|
+
direct,
|
|
275
|
+
scale,
|
|
276
|
+
(outer * 2 * scale.align + index + (1 - inner) / 2) /
|
|
277
|
+
Math.max(1, scale.domain.length - inner + outer * 2)
|
|
278
|
+
);
|
|
223
279
|
}));
|
|
224
280
|
}
|