@pushwoosh/kit-charts 0.0.1
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/LICENSE +21 -0
- package/ai-documentation.md +378 -0
- package/core/Chart/Axes.d.ts +19 -0
- package/core/Chart/Axes.js +204 -0
- package/core/Chart/Chart.d.ts +33 -0
- package/core/Chart/Chart.js +616 -0
- package/core/Chart/Chart.styles.d.ts +109 -0
- package/core/Chart/Chart.styles.js +227 -0
- package/core/Chart/ChartErrorBoundary.d.ts +15 -0
- package/core/Chart/ChartErrorBoundary.js +27 -0
- package/core/Chart/Hinted.d.ts +11 -0
- package/core/Chart/Hinted.js +63 -0
- package/core/Chart/Legend.d.ts +15 -0
- package/core/Chart/Legend.js +92 -0
- package/core/Chart/PlotDecorations.d.ts +17 -0
- package/core/Chart/PlotDecorations.js +172 -0
- package/core/Chart/StackLabels.d.ts +6 -0
- package/core/Chart/StackLabels.js +85 -0
- package/core/Chart/Titles.d.ts +6 -0
- package/core/Chart/Titles.js +67 -0
- package/core/Chart/Tooltip.d.ts +13 -0
- package/core/Chart/Tooltip.js +87 -0
- package/core/Chart/useSize.d.ts +5 -0
- package/core/Chart/useSize.js +45 -0
- package/core/animation/motion.d.ts +20 -0
- package/core/animation/motion.js +20 -0
- package/core/animation/useTweenedNumbers.d.ts +1 -0
- package/core/animation/useTweenedNumbers.js +51 -0
- package/core/data/points.d.ts +14 -0
- package/core/data/points.js +73 -0
- package/core/format/time.d.ts +2 -0
- package/core/format/time.js +38 -0
- package/core/geometry/path.d.ts +19 -0
- package/core/geometry/path.js +76 -0
- package/core/layout/legend-layout.d.ts +21 -0
- package/core/layout/legend-layout.js +103 -0
- package/core/layout/measure-text.d.ts +9 -0
- package/core/layout/measure-text.js +57 -0
- package/core/layout/metrics.d.ts +63 -0
- package/core/layout/metrics.js +66 -0
- package/core/layout/point-width.d.ts +11 -0
- package/core/layout/point-width.js +86 -0
- package/core/layout/title-layout.d.ts +30 -0
- package/core/layout/title-layout.js +99 -0
- package/core/model/build-model.d.ts +69 -0
- package/core/model/build-model.js +556 -0
- package/core/model/palette.d.ts +15 -0
- package/core/model/palette.js +17 -0
- package/core/options/normalize.d.ts +8 -0
- package/core/options/normalize.js +44 -0
- package/core/registry/registry.d.ts +5 -0
- package/core/registry/registry.js +32 -0
- package/core/scale/band.d.ts +10 -0
- package/core/scale/band.js +33 -0
- package/core/scale/color.d.ts +9 -0
- package/core/scale/color.js +69 -0
- package/core/scale/linear.d.ts +17 -0
- package/core/scale/linear.js +64 -0
- package/core/scale/time.d.ts +19 -0
- package/core/scale/time.js +187 -0
- package/core/stack/stack.d.ts +9 -0
- package/core/stack/stack.js +76 -0
- package/core/types/formatter.d.ts +59 -0
- package/core/types/formatter.js +1 -0
- package/core/types/geometry.d.ts +16 -0
- package/core/types/geometry.js +1 -0
- package/core/types/options.d.ts +331 -0
- package/core/types/options.js +2 -0
- package/core/types/renderer.d.ts +101 -0
- package/core/types/renderer.js +1 -0
- package/core/types/scale.d.ts +34 -0
- package/core/types/scale.js +1 -0
- package/index.d.ts +16 -0
- package/index.js +10 -0
- package/package.json +64 -0
- package/presets/AreaChart.d.ts +3 -0
- package/presets/AreaChart.js +13 -0
- package/presets/ColumnChart.d.ts +3 -0
- package/presets/ColumnChart.js +13 -0
- package/presets/LineChart.d.ts +3 -0
- package/presets/LineChart.js +13 -0
- package/presets/PieChart.d.ts +3 -0
- package/presets/PieChart.js +13 -0
- package/presets/StackedColumnChart.d.ts +7 -0
- package/presets/StackedColumnChart.js +31 -0
- package/presets/index.d.ts +5 -0
- package/presets/index.js +5 -0
- package/series/area/area.d.ts +2 -0
- package/series/area/area.js +57 -0
- package/series/area/index.d.ts +1 -0
- package/series/area/index.js +1 -0
- package/series/column/column.d.ts +2 -0
- package/series/column/column.js +163 -0
- package/series/column/index.d.ts +1 -0
- package/series/column/index.js +1 -0
- package/series/heatmap/heatmap.d.ts +2 -0
- package/series/heatmap/heatmap.js +172 -0
- package/series/heatmap/index.d.ts +1 -0
- package/series/heatmap/index.js +1 -0
- package/series/line/index.d.ts +1 -0
- package/series/line/index.js +1 -0
- package/series/line/line.d.ts +2 -0
- package/series/line/line.js +49 -0
- package/series/pie/index.d.ts +1 -0
- package/series/pie/index.js +1 -0
- package/series/pie/pie.d.ts +2 -0
- package/series/pie/pie.js +224 -0
- package/series/shared/DataLabels.d.ts +14 -0
- package/series/shared/DataLabels.js +124 -0
- package/series/shared/Markers.d.ts +9 -0
- package/series/shared/Markers.js +34 -0
- package/series/shared/color.d.ts +1 -0
- package/series/shared/color.js +19 -0
- package/series/shared/domain.d.ts +3 -0
- package/series/shared/domain.js +14 -0
- package/series/shared/hit.d.ts +3 -0
- package/series/shared/hit.js +33 -0
- package/series/shared/legend.d.ts +3 -0
- package/series/shared/legend.js +12 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type BandScale } from '../types/scale';
|
|
2
|
+
export interface BandScaleOptions {
|
|
3
|
+
domain: readonly string[];
|
|
4
|
+
range: readonly [number, number];
|
|
5
|
+
paddingInner?: number;
|
|
6
|
+
paddingOuter?: number;
|
|
7
|
+
/** Assign slots from the far end of the range, keeping the domain order intact. */
|
|
8
|
+
reversed?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function createBandScale(options: BandScaleOptions): BandScale;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function createBandScale(options) {
|
|
2
|
+
const {
|
|
3
|
+
domain,
|
|
4
|
+
range,
|
|
5
|
+
paddingInner = 0.2,
|
|
6
|
+
paddingOuter = 0.1,
|
|
7
|
+
reversed = false
|
|
8
|
+
} = options;
|
|
9
|
+
const [r0, r1] = range;
|
|
10
|
+
const count = domain.length;
|
|
11
|
+
const width = r1 - r0;
|
|
12
|
+
const step = count > 0 ? width / Math.max(1e-6, count - paddingInner + 2 * paddingOuter) : width;
|
|
13
|
+
const bandwidth = step * (1 - paddingInner);
|
|
14
|
+
const start = r0 + step * paddingOuter;
|
|
15
|
+
const index = new Map(domain.map((value, i) => [value, i]));
|
|
16
|
+
const slotOf = i => reversed ? count - 1 - i : i;
|
|
17
|
+
return {
|
|
18
|
+
kind: 'band',
|
|
19
|
+
map: value => {
|
|
20
|
+
const i = index.get(value);
|
|
21
|
+
return i === undefined ? NaN : start + slotOf(i) * step;
|
|
22
|
+
},
|
|
23
|
+
invert: px => {
|
|
24
|
+
const slot = Math.floor((px - start) / step);
|
|
25
|
+
return slot >= 0 && slot < count ? domain[slotOf(slot)] : undefined;
|
|
26
|
+
},
|
|
27
|
+
ticks: () => [...domain],
|
|
28
|
+
domain: () => domain,
|
|
29
|
+
range: () => [r0, r1],
|
|
30
|
+
bandwidth: () => bandwidth,
|
|
31
|
+
step: () => step
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ColorScale, type ColorStop } from '../types/scale';
|
|
2
|
+
export interface ColorScaleOptions {
|
|
3
|
+
domain: readonly [number, number];
|
|
4
|
+
stops: readonly ColorStop[];
|
|
5
|
+
}
|
|
6
|
+
type Rgba = [number, number, number, number];
|
|
7
|
+
export declare const parseColor: (color: string) => Rgba | null;
|
|
8
|
+
export declare function createColorScale(options: ColorScaleOptions): ColorScale;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const clampChannel = value => Math.min(255, Math.max(0, Math.round(value)));
|
|
2
|
+
const parseHex = color => {
|
|
3
|
+
const match = /^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(color);
|
|
4
|
+
if (!match) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
const raw = match[1];
|
|
8
|
+
const short = raw.length === 3 || raw.length === 4;
|
|
9
|
+
const hex = short ? raw.split('').map(char => char + char).join('') : raw;
|
|
10
|
+
const channel = at => parseInt(hex.slice(at, at + 2), 16);
|
|
11
|
+
return [channel(0), channel(2), channel(4), hex.length === 8 ? channel(6) / 255 : 1];
|
|
12
|
+
};
|
|
13
|
+
const parseFunctional = color => {
|
|
14
|
+
const match = /^rgba?\(([^)]+)\)$/i.exec(color);
|
|
15
|
+
if (!match) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const parts = match[1].split(/[,/\s]+/).filter(Boolean).map(Number);
|
|
19
|
+
if (parts.length < 3 || parts.some(part => !Number.isFinite(part))) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return [parts[0], parts[1], parts[2], parts[3] === undefined ? 1 : parts[3]];
|
|
23
|
+
};
|
|
24
|
+
export const parseColor = color => parseHex(color.trim()) ?? parseFunctional(color.trim());
|
|
25
|
+
const toCss = ([r, g, b, a]) => a >= 1 ? `#${[r, g, b].map(channel => clampChannel(channel).toString(16).padStart(2, '0')).join('')}` : `rgba(${clampChannel(r)}, ${clampChannel(g)}, ${clampChannel(b)}, ${Number(a.toFixed(3))})`;
|
|
26
|
+
const mix = (from, to, t) => [from[0] + (to[0] - from[0]) * t, from[1] + (to[1] - from[1]) * t, from[2] + (to[2] - from[2]) * t, from[3] + (to[3] - from[3]) * t];
|
|
27
|
+
// A stop whose colour cannot be parsed still holds its position: it is returned
|
|
28
|
+
// verbatim on an exact hit and skipped when interpolating a neighbour.
|
|
29
|
+
const resolveStops = stops => stops.map(([at, color]) => ({
|
|
30
|
+
at,
|
|
31
|
+
color,
|
|
32
|
+
rgba: parseColor(color) ?? [0, 0, 0, 0]
|
|
33
|
+
})).sort((left, right) => left.at - right.at);
|
|
34
|
+
export function createColorScale(options) {
|
|
35
|
+
const stops = resolveStops(options.stops);
|
|
36
|
+
const [min, max] = options.domain;
|
|
37
|
+
const span = max - min;
|
|
38
|
+
const positionOf = value => {
|
|
39
|
+
if (!Number.isFinite(value) || !Number.isFinite(span) || span === 0) {
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
return Math.min(1, Math.max(0, (value - min) / span));
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
kind: 'color',
|
|
46
|
+
map: value => {
|
|
47
|
+
if (!stops.length) {
|
|
48
|
+
return 'transparent';
|
|
49
|
+
}
|
|
50
|
+
const at = positionOf(value);
|
|
51
|
+
const upperIndex = stops.findIndex(stop => stop.at >= at);
|
|
52
|
+
if (upperIndex === -1) {
|
|
53
|
+
return stops[stops.length - 1].color;
|
|
54
|
+
}
|
|
55
|
+
const upper = stops[upperIndex];
|
|
56
|
+
const lower = stops[upperIndex - 1];
|
|
57
|
+
if (!lower || upper.at === at) {
|
|
58
|
+
return upper.color;
|
|
59
|
+
}
|
|
60
|
+
const local = (at - lower.at) / (upper.at - lower.at);
|
|
61
|
+
return toCss(mix(lower.rgba, upper.rgba, local));
|
|
62
|
+
},
|
|
63
|
+
domain: () => [min, max],
|
|
64
|
+
stops: () => stops.map(({
|
|
65
|
+
at,
|
|
66
|
+
color
|
|
67
|
+
}) => [at, color])
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ContinuousScale } from '../types/scale';
|
|
2
|
+
/** Which ends of the domain may be rounded outwards to a whole tick step. */
|
|
3
|
+
export interface NiceEnds {
|
|
4
|
+
min?: boolean;
|
|
5
|
+
max?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface LinearScaleOptions {
|
|
8
|
+
domain: readonly [number, number];
|
|
9
|
+
range: readonly [number, number];
|
|
10
|
+
nice?: boolean | NiceEnds;
|
|
11
|
+
tickHint?: number;
|
|
12
|
+
/** False keeps every tick a whole number, as a count axis needs. */
|
|
13
|
+
allowDecimals?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function niceStep(rough: number, allowDecimals?: boolean): number;
|
|
16
|
+
export declare function linearTicks(min: number, max: number, count: number, allowDecimals?: boolean): number[];
|
|
17
|
+
export declare function createLinearScale(options: LinearScaleOptions): ContinuousScale;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const STEPS = [1, 2, 2.5, 5, 10];
|
|
2
|
+
export function niceStep(rough, allowDecimals = true) {
|
|
3
|
+
if (!(rough > 0)) {
|
|
4
|
+
return 1;
|
|
5
|
+
}
|
|
6
|
+
const magnitude = 10 ** Math.floor(Math.log10(rough));
|
|
7
|
+
const normalized = rough / magnitude;
|
|
8
|
+
// A whole-number axis keeps only the candidates that stay whole at this
|
|
9
|
+
// magnitude — below magnitude 1 that leaves 10, which lands the step on 1.
|
|
10
|
+
const steps = allowDecimals ? STEPS : STEPS.filter(candidate => Number.isInteger(candidate * magnitude));
|
|
11
|
+
const step = (steps.find(candidate => normalized <= candidate) ?? 10) * magnitude;
|
|
12
|
+
return allowDecimals ? step : Math.max(1, step);
|
|
13
|
+
}
|
|
14
|
+
export function linearTicks(min, max, count, allowDecimals = true) {
|
|
15
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || count < 1) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
if (min === max) {
|
|
19
|
+
return [min];
|
|
20
|
+
}
|
|
21
|
+
const step = niceStep((max - min) / count, allowDecimals);
|
|
22
|
+
const first = Math.ceil(min / step) * step;
|
|
23
|
+
const ticks = [];
|
|
24
|
+
for (let i = 0; first + i * step <= max + step * 1e-9; i += 1) {
|
|
25
|
+
ticks.push(Number((first + i * step).toPrecision(12)));
|
|
26
|
+
}
|
|
27
|
+
return ticks;
|
|
28
|
+
}
|
|
29
|
+
export function createLinearScale(options) {
|
|
30
|
+
const {
|
|
31
|
+
range,
|
|
32
|
+
nice = false,
|
|
33
|
+
tickHint = 5,
|
|
34
|
+
allowDecimals = true
|
|
35
|
+
} = options;
|
|
36
|
+
let [min, max] = options.domain;
|
|
37
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
|
38
|
+
[min, max] = [0, 1];
|
|
39
|
+
} else if (min === max) {
|
|
40
|
+
const pad = Math.abs(min) || 1;
|
|
41
|
+
[min, max] = [min - pad, max + pad];
|
|
42
|
+
}
|
|
43
|
+
const niceMin = nice === true || typeof nice === 'object' && nice.min === true;
|
|
44
|
+
const niceMax = nice === true || typeof nice === 'object' && nice.max === true;
|
|
45
|
+
if (niceMin || niceMax) {
|
|
46
|
+
const step = niceStep((max - min) / tickHint, allowDecimals);
|
|
47
|
+
if (niceMin) {
|
|
48
|
+
min = Math.floor(min / step) * step;
|
|
49
|
+
}
|
|
50
|
+
if (niceMax) {
|
|
51
|
+
max = Math.ceil(max / step) * step;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const [r0, r1] = range;
|
|
55
|
+
const scale = (r1 - r0) / (max - min);
|
|
56
|
+
return {
|
|
57
|
+
kind: 'linear',
|
|
58
|
+
map: value => r0 + (value - min) * scale,
|
|
59
|
+
invert: px => min + (px - r0) / scale,
|
|
60
|
+
ticks: (hint = tickHint) => linearTicks(min, max, hint, allowDecimals),
|
|
61
|
+
domain: () => [min, max],
|
|
62
|
+
range: () => [r0, r1]
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ContinuousScale, type TimeUnit } from '../types/scale';
|
|
2
|
+
export declare const SECOND = 1000;
|
|
3
|
+
export declare const MINUTE: number;
|
|
4
|
+
export declare const HOUR: number;
|
|
5
|
+
export declare const DAY: number;
|
|
6
|
+
export declare const WEEK: number;
|
|
7
|
+
export interface TimeTicksOptions {
|
|
8
|
+
count?: number;
|
|
9
|
+
unit?: TimeUnit;
|
|
10
|
+
}
|
|
11
|
+
export declare function timeTicks(min: number, max: number, options?: TimeTicksOptions): number[];
|
|
12
|
+
export declare function defaultTimeFormat(min: number, max: number): string;
|
|
13
|
+
export interface TimeScaleOptions {
|
|
14
|
+
domain: readonly [number, number];
|
|
15
|
+
range: readonly [number, number];
|
|
16
|
+
tickHint?: number;
|
|
17
|
+
tickUnit?: TimeUnit;
|
|
18
|
+
}
|
|
19
|
+
export declare function createTimeScale(options: TimeScaleOptions): ContinuousScale;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
export const SECOND = 1000;
|
|
2
|
+
export const MINUTE = 60 * SECOND;
|
|
3
|
+
export const HOUR = 60 * MINUTE;
|
|
4
|
+
export const DAY = 24 * HOUR;
|
|
5
|
+
export const WEEK = 7 * DAY;
|
|
6
|
+
const LADDER = [{
|
|
7
|
+
unit: 'second',
|
|
8
|
+
step: 1,
|
|
9
|
+
approx: SECOND
|
|
10
|
+
}, {
|
|
11
|
+
unit: 'second',
|
|
12
|
+
step: 5,
|
|
13
|
+
approx: 5 * SECOND
|
|
14
|
+
}, {
|
|
15
|
+
unit: 'second',
|
|
16
|
+
step: 15,
|
|
17
|
+
approx: 15 * SECOND
|
|
18
|
+
}, {
|
|
19
|
+
unit: 'second',
|
|
20
|
+
step: 30,
|
|
21
|
+
approx: 30 * SECOND
|
|
22
|
+
}, {
|
|
23
|
+
unit: 'minute',
|
|
24
|
+
step: 1,
|
|
25
|
+
approx: MINUTE
|
|
26
|
+
}, {
|
|
27
|
+
unit: 'minute',
|
|
28
|
+
step: 5,
|
|
29
|
+
approx: 5 * MINUTE
|
|
30
|
+
}, {
|
|
31
|
+
unit: 'minute',
|
|
32
|
+
step: 15,
|
|
33
|
+
approx: 15 * MINUTE
|
|
34
|
+
}, {
|
|
35
|
+
unit: 'minute',
|
|
36
|
+
step: 30,
|
|
37
|
+
approx: 30 * MINUTE
|
|
38
|
+
}, {
|
|
39
|
+
unit: 'hour',
|
|
40
|
+
step: 1,
|
|
41
|
+
approx: HOUR
|
|
42
|
+
}, {
|
|
43
|
+
unit: 'hour',
|
|
44
|
+
step: 3,
|
|
45
|
+
approx: 3 * HOUR
|
|
46
|
+
}, {
|
|
47
|
+
unit: 'hour',
|
|
48
|
+
step: 6,
|
|
49
|
+
approx: 6 * HOUR
|
|
50
|
+
}, {
|
|
51
|
+
unit: 'hour',
|
|
52
|
+
step: 12,
|
|
53
|
+
approx: 12 * HOUR
|
|
54
|
+
}, {
|
|
55
|
+
unit: 'day',
|
|
56
|
+
step: 1,
|
|
57
|
+
approx: DAY
|
|
58
|
+
}, {
|
|
59
|
+
unit: 'day',
|
|
60
|
+
step: 2,
|
|
61
|
+
approx: 2 * DAY
|
|
62
|
+
}, {
|
|
63
|
+
unit: 'week',
|
|
64
|
+
step: 1,
|
|
65
|
+
approx: WEEK
|
|
66
|
+
}, {
|
|
67
|
+
unit: 'month',
|
|
68
|
+
step: 1,
|
|
69
|
+
approx: 30 * DAY,
|
|
70
|
+
calendar: true
|
|
71
|
+
}, {
|
|
72
|
+
unit: 'month',
|
|
73
|
+
step: 3,
|
|
74
|
+
approx: 91 * DAY,
|
|
75
|
+
calendar: true
|
|
76
|
+
}, {
|
|
77
|
+
unit: 'month',
|
|
78
|
+
step: 6,
|
|
79
|
+
approx: 182 * DAY,
|
|
80
|
+
calendar: true
|
|
81
|
+
}, {
|
|
82
|
+
unit: 'year',
|
|
83
|
+
step: 1,
|
|
84
|
+
approx: 365 * DAY,
|
|
85
|
+
calendar: true
|
|
86
|
+
}];
|
|
87
|
+
const pickRung = (span, count) => {
|
|
88
|
+
const target = span / Math.max(1, count);
|
|
89
|
+
return LADDER.find(rung => rung.approx >= target) ?? LADDER[LADDER.length - 1];
|
|
90
|
+
};
|
|
91
|
+
const alignUp = (from, rung) => {
|
|
92
|
+
const date = new Date(from);
|
|
93
|
+
if (rung.unit === 'year') {
|
|
94
|
+
const year = date.getUTCFullYear();
|
|
95
|
+
const aligned = Date.UTC(Math.ceil(year / rung.step) * rung.step, 0, 1);
|
|
96
|
+
return aligned;
|
|
97
|
+
}
|
|
98
|
+
if (rung.unit === 'month') {
|
|
99
|
+
const month = date.getUTCMonth();
|
|
100
|
+
const stepped = Math.ceil(month / rung.step) * rung.step;
|
|
101
|
+
return Date.UTC(date.getUTCFullYear(), stepped, 1);
|
|
102
|
+
}
|
|
103
|
+
if (rung.unit === 'week') {
|
|
104
|
+
const midnight = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
|
|
105
|
+
const shift = (8 - (new Date(midnight).getUTCDay() || 7)) % 7;
|
|
106
|
+
return midnight + shift * DAY;
|
|
107
|
+
}
|
|
108
|
+
const size = rung.approx;
|
|
109
|
+
const midnight = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
|
|
110
|
+
const sinceMidnight = from - midnight;
|
|
111
|
+
return midnight + Math.ceil(sinceMidnight / size) * size;
|
|
112
|
+
};
|
|
113
|
+
const advance = (timestamp, rung) => {
|
|
114
|
+
if (!rung.calendar) {
|
|
115
|
+
return timestamp + rung.approx;
|
|
116
|
+
}
|
|
117
|
+
const date = new Date(timestamp);
|
|
118
|
+
const months = rung.unit === 'year' ? rung.step * 12 : rung.step;
|
|
119
|
+
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + months, date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
|
|
120
|
+
};
|
|
121
|
+
const rungFor = unit => LADDER.find(rung => rung.unit === unit && rung.step === 1) ?? LADDER[LADDER.length - 1];
|
|
122
|
+
export function timeTicks(min, max, options = {}) {
|
|
123
|
+
const {
|
|
124
|
+
count = 6,
|
|
125
|
+
unit
|
|
126
|
+
} = options;
|
|
127
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || max < min) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
if (min === max) {
|
|
131
|
+
return [min];
|
|
132
|
+
}
|
|
133
|
+
const rung = unit ? rungFor(unit) : pickRung(max - min, count);
|
|
134
|
+
const ticks = [];
|
|
135
|
+
// Calendar alignment lands on the start of the current unit, which can sit
|
|
136
|
+
// before the domain — a tick there would be drawn outside the plot.
|
|
137
|
+
let cursor = alignUp(min, rung);
|
|
138
|
+
if (cursor < min) {
|
|
139
|
+
cursor = advance(cursor, rung);
|
|
140
|
+
}
|
|
141
|
+
for (let i = 0; cursor <= max && i < 1000; i += 1) {
|
|
142
|
+
ticks.push(cursor);
|
|
143
|
+
cursor = advance(cursor, rung);
|
|
144
|
+
}
|
|
145
|
+
return ticks.length ? ticks : [min, max];
|
|
146
|
+
}
|
|
147
|
+
const sameUtcDay = (a, b) => {
|
|
148
|
+
const left = new Date(a);
|
|
149
|
+
const right = new Date(b);
|
|
150
|
+
return left.getUTCFullYear() === right.getUTCFullYear() && left.getUTCMonth() === right.getUTCMonth() && left.getUTCDate() === right.getUTCDate();
|
|
151
|
+
};
|
|
152
|
+
export function defaultTimeFormat(min, max) {
|
|
153
|
+
const span = max - min;
|
|
154
|
+
if (span <= 2 * DAY) {
|
|
155
|
+
return sameUtcDay(min, max) ? '%H:%M' : '%b %d, %H:%M';
|
|
156
|
+
}
|
|
157
|
+
if (span <= 200 * DAY) {
|
|
158
|
+
return '%b %d';
|
|
159
|
+
}
|
|
160
|
+
return '%b, %Y';
|
|
161
|
+
}
|
|
162
|
+
export function createTimeScale(options) {
|
|
163
|
+
const {
|
|
164
|
+
range,
|
|
165
|
+
tickHint = 6,
|
|
166
|
+
tickUnit
|
|
167
|
+
} = options;
|
|
168
|
+
let [min, max] = options.domain;
|
|
169
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
|
170
|
+
[min, max] = [0, DAY];
|
|
171
|
+
} else if (min === max) {
|
|
172
|
+
[min, max] = [min - HOUR, max + HOUR];
|
|
173
|
+
}
|
|
174
|
+
const [r0, r1] = range;
|
|
175
|
+
const scale = (r1 - r0) / (max - min);
|
|
176
|
+
return {
|
|
177
|
+
kind: 'time',
|
|
178
|
+
map: value => r0 + (value - min) * scale,
|
|
179
|
+
invert: px => min + (px - r0) / scale,
|
|
180
|
+
ticks: (hint = tickHint) => timeTicks(min, max, {
|
|
181
|
+
count: hint,
|
|
182
|
+
unit: tickUnit
|
|
183
|
+
}),
|
|
184
|
+
domain: () => [min, max],
|
|
185
|
+
range: () => [r0, r1]
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type NormalizedPoint } from '../data/points';
|
|
2
|
+
import { type SeriesOptionsBase } from '../types/options';
|
|
3
|
+
import { type StackedValue } from '../types/renderer';
|
|
4
|
+
export interface StackInput {
|
|
5
|
+
index: number;
|
|
6
|
+
options: SeriesOptionsBase;
|
|
7
|
+
points: readonly NormalizedPoint[];
|
|
8
|
+
}
|
|
9
|
+
export declare function computeStacks(series: readonly StackInput[]): Map<number, Array<StackedValue | undefined>>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const groupKey = series => series.stacking ? `${series.yAxis ?? 0}:${series.stacking}` : null;
|
|
2
|
+
const keyOf = point => String(point.x);
|
|
3
|
+
const emptyRunning = () => ({
|
|
4
|
+
positive: 0,
|
|
5
|
+
negative: 0
|
|
6
|
+
});
|
|
7
|
+
export function computeStacks(series) {
|
|
8
|
+
const result = new Map();
|
|
9
|
+
const groups = new Map();
|
|
10
|
+
series.forEach(entry => {
|
|
11
|
+
const key = groupKey(entry.options);
|
|
12
|
+
if (key === null) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const existing = groups.get(key);
|
|
16
|
+
if (existing) {
|
|
17
|
+
existing.push(entry);
|
|
18
|
+
} else {
|
|
19
|
+
groups.set(key, [entry]);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
groups.forEach((members, key) => {
|
|
23
|
+
const percent = key.endsWith(':percent');
|
|
24
|
+
const totals = new Map();
|
|
25
|
+
members.forEach(({
|
|
26
|
+
points
|
|
27
|
+
}) => {
|
|
28
|
+
points.forEach(point => {
|
|
29
|
+
if (point.y === null) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const running = totals.get(keyOf(point)) ?? {
|
|
33
|
+
signed: 0,
|
|
34
|
+
absolute: 0
|
|
35
|
+
};
|
|
36
|
+
running.signed += point.y;
|
|
37
|
+
running.absolute += Math.abs(point.y);
|
|
38
|
+
totals.set(keyOf(point), running);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
const cursors = new Map();
|
|
42
|
+
members.forEach(({
|
|
43
|
+
index,
|
|
44
|
+
points
|
|
45
|
+
}) => {
|
|
46
|
+
const stacked = points.map(point => {
|
|
47
|
+
if (point.y === null) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const pointKey = keyOf(point);
|
|
51
|
+
const cursor = cursors.get(pointKey) ?? emptyRunning();
|
|
52
|
+
const total = totals.get(pointKey) ?? {
|
|
53
|
+
signed: 0,
|
|
54
|
+
absolute: 0
|
|
55
|
+
};
|
|
56
|
+
const negative = point.y < 0;
|
|
57
|
+
const from = negative ? cursor.negative : cursor.positive;
|
|
58
|
+
const magnitude = percent && total.absolute !== 0 ? point.y / total.absolute * 100 : point.y;
|
|
59
|
+
const to = from + magnitude;
|
|
60
|
+
if (negative) {
|
|
61
|
+
cursor.negative = to;
|
|
62
|
+
} else {
|
|
63
|
+
cursor.positive = to;
|
|
64
|
+
}
|
|
65
|
+
cursors.set(pointKey, cursor);
|
|
66
|
+
return {
|
|
67
|
+
from,
|
|
68
|
+
to,
|
|
69
|
+
total: total.signed
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
result.set(index, stacked);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type ScaleKind } from './scale';
|
|
3
|
+
export interface AxisLabelContext {
|
|
4
|
+
value: number | string;
|
|
5
|
+
index: number;
|
|
6
|
+
isFirst: boolean;
|
|
7
|
+
isLast: boolean;
|
|
8
|
+
axis: {
|
|
9
|
+
kind: ScaleKind;
|
|
10
|
+
min: number | undefined;
|
|
11
|
+
max: number | undefined;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export type AxisLabelFormatter = (ctx: AxisLabelContext) => ReactNode;
|
|
15
|
+
export interface LegendLabelContext<TSeriesMeta = unknown> {
|
|
16
|
+
seriesIndex: number;
|
|
17
|
+
name: string;
|
|
18
|
+
color: string;
|
|
19
|
+
visible: boolean;
|
|
20
|
+
meta: TSeriesMeta | undefined;
|
|
21
|
+
}
|
|
22
|
+
export type LegendLabelFormatter<TSeriesMeta = unknown> = (ctx: LegendLabelContext<TSeriesMeta>) => ReactNode;
|
|
23
|
+
export interface DataLabelContext<TSeriesMeta = unknown> {
|
|
24
|
+
x: number | string;
|
|
25
|
+
y: number | null;
|
|
26
|
+
value: number | null;
|
|
27
|
+
pointIndex: number;
|
|
28
|
+
seriesIndex: number;
|
|
29
|
+
seriesName: string;
|
|
30
|
+
meta: TSeriesMeta | undefined;
|
|
31
|
+
}
|
|
32
|
+
export type DataLabelFormatter<TSeriesMeta = unknown> = (ctx: DataLabelContext<TSeriesMeta>) => ReactNode;
|
|
33
|
+
export interface TooltipNeighbour {
|
|
34
|
+
x: number | string;
|
|
35
|
+
y: number | null;
|
|
36
|
+
index: number;
|
|
37
|
+
}
|
|
38
|
+
export interface TooltipPoint<TSeriesMeta = unknown> {
|
|
39
|
+
seriesIndex: number;
|
|
40
|
+
seriesName: string;
|
|
41
|
+
color: string;
|
|
42
|
+
x: number | string;
|
|
43
|
+
y: number | null;
|
|
44
|
+
value: number | null;
|
|
45
|
+
index: number;
|
|
46
|
+
meta: TSeriesMeta | undefined;
|
|
47
|
+
prev: TooltipNeighbour | undefined;
|
|
48
|
+
next: TooltipNeighbour | undefined;
|
|
49
|
+
}
|
|
50
|
+
export interface TooltipContext<TChartMeta = unknown, TSeriesMeta = unknown> {
|
|
51
|
+
x: number | string;
|
|
52
|
+
points: readonly TooltipPoint<TSeriesMeta>[];
|
|
53
|
+
chart: {
|
|
54
|
+
title: string | undefined;
|
|
55
|
+
meta: TChartMeta | undefined;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export type TooltipFormatter<TChartMeta = unknown, TSeriesMeta = unknown> = (ctx: TooltipContext<TChartMeta, TSeriesMeta>) => ReactNode;
|
|
59
|
+
export type TooltipCellFormatter<TSeriesMeta = unknown> = (point: TooltipPoint<TSeriesMeta>) => ReactNode;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface Point {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
}
|
|
5
|
+
export interface Size {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
export interface Rect extends Point, Size {
|
|
10
|
+
}
|
|
11
|
+
export type Spacing = [number, number, number, number];
|
|
12
|
+
export interface SlotGeometry {
|
|
13
|
+
width: number;
|
|
14
|
+
offset: number;
|
|
15
|
+
step: number;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|