@juspay/svelte-ui-components 2.31.0 → 2.32.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.
Files changed (43) hide show
  1. package/dist/AreaChart/AreaChart.svelte +421 -0
  2. package/dist/AreaChart/AreaChart.svelte.d.ts +4 -0
  3. package/dist/AreaChart/properties.d.ts +62 -0
  4. package/dist/AreaChart/properties.js +1 -0
  5. package/dist/BarChart/BarChart.svelte +372 -0
  6. package/dist/BarChart/BarChart.svelte.d.ts +4 -0
  7. package/dist/BarChart/properties.d.ts +44 -0
  8. package/dist/BarChart/properties.js +1 -0
  9. package/dist/LineChart/LineChart.svelte +361 -0
  10. package/dist/LineChart/LineChart.svelte.d.ts +4 -0
  11. package/dist/LineChart/properties.d.ts +59 -0
  12. package/dist/LineChart/properties.js +1 -0
  13. package/dist/PieChart/PieChart.svelte +236 -0
  14. package/dist/PieChart/PieChart.svelte.d.ts +4 -0
  15. package/dist/PieChart/properties.d.ts +36 -0
  16. package/dist/PieChart/properties.js +1 -0
  17. package/dist/SankeyChart/SankeyChart.svelte +312 -0
  18. package/dist/SankeyChart/SankeyChart.svelte.d.ts +4 -0
  19. package/dist/SankeyChart/properties.d.ts +52 -0
  20. package/dist/SankeyChart/properties.js +1 -0
  21. package/dist/_chart/Axis.svelte +143 -0
  22. package/dist/_chart/Axis.svelte.d.ts +4 -0
  23. package/dist/_chart/ChartContainer.svelte +81 -0
  24. package/dist/_chart/ChartContainer.svelte.d.ts +4 -0
  25. package/dist/_chart/ChartTooltip.svelte +81 -0
  26. package/dist/_chart/ChartTooltip.svelte.d.ts +4 -0
  27. package/dist/_chart/Legend.svelte +59 -0
  28. package/dist/_chart/Legend.svelte.d.ts +4 -0
  29. package/dist/_chart/colors.d.ts +4 -0
  30. package/dist/_chart/colors.js +36 -0
  31. package/dist/_chart/format.d.ts +3 -0
  32. package/dist/_chart/format.js +28 -0
  33. package/dist/_chart/geometry.d.ts +24 -0
  34. package/dist/_chart/geometry.js +204 -0
  35. package/dist/_chart/paths.d.ts +4 -0
  36. package/dist/_chart/paths.js +138 -0
  37. package/dist/_chart/scales.d.ts +5 -0
  38. package/dist/_chart/scales.js +77 -0
  39. package/dist/_chart/types.d.ts +128 -0
  40. package/dist/_chart/types.js +1 -0
  41. package/dist/index.d.ts +10 -0
  42. package/dist/index.js +5 -0
  43. package/package.json +1 -1
@@ -0,0 +1,59 @@
1
+ <script lang="ts">
2
+ import type { LegendProperties } from './types';
3
+
4
+ let { items, position = 'bottom', customSnippet, classes }: LegendProperties = $props();
5
+ </script>
6
+
7
+ {#if items.length > 0}
8
+ <div class="chart-legend position-{position} {classes ?? ''}">
9
+ {#if typeof customSnippet === 'function'}
10
+ {@render customSnippet(items)}
11
+ {:else}
12
+ {#each items as item, i (i)}
13
+ <div class="legend-item">
14
+ <span class="legend-swatch" style="background: {item.color}"></span>
15
+ <span class="legend-label">{item.label}</span>
16
+ </div>
17
+ {/each}
18
+ {/if}
19
+ </div>
20
+ {/if}
21
+
22
+ <style>
23
+ .chart-legend {
24
+ display: flex;
25
+ flex-wrap: wrap;
26
+ align-items: center;
27
+ justify-content: center;
28
+ gap: var(--chart-legend-gap, 16px);
29
+ font-family: var(--chart-font-family, inherit);
30
+ padding: 8px 0;
31
+ }
32
+
33
+ .position-top {
34
+ padding-bottom: 12px;
35
+ }
36
+
37
+ .position-bottom {
38
+ padding-top: 12px;
39
+ }
40
+
41
+ .legend-item {
42
+ display: flex;
43
+ align-items: center;
44
+ gap: 6px;
45
+ }
46
+
47
+ .legend-swatch {
48
+ display: inline-block;
49
+ width: var(--chart-legend-swatch-size, 12px);
50
+ height: var(--chart-legend-swatch-size, 12px);
51
+ border-radius: 2px;
52
+ flex-shrink: 0;
53
+ }
54
+
55
+ .legend-label {
56
+ font-size: var(--chart-legend-font-size, 12px);
57
+ color: var(--chart-legend-color, #333);
58
+ }
59
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { LegendProperties } from './types';
2
+ declare const Legend: import("svelte").Component<LegendProperties, {}, "">;
3
+ type Legend = ReturnType<typeof Legend>;
4
+ export default Legend;
@@ -0,0 +1,4 @@
1
+ export declare const DEFAULT_PALETTE: string[];
2
+ export declare function getColor(index: number, palette?: string[]): string;
3
+ export declare function generatePalette(baseColor: string, count: number): string[];
4
+ export declare function getContrastColor(hex: string): string;
@@ -0,0 +1,36 @@
1
+ import { hexToRgb, hexToHsv, hsvToHex } from '../utils';
2
+ export const DEFAULT_PALETTE = [
3
+ '#4e79a7',
4
+ '#f28e2b',
5
+ '#e15759',
6
+ '#76b7b2',
7
+ '#59a14f',
8
+ '#edc948',
9
+ '#b07aa1',
10
+ '#ff9da7',
11
+ '#9c755f',
12
+ '#bab0ac'
13
+ ];
14
+ export function getColor(index, palette = DEFAULT_PALETTE) {
15
+ return palette[index % palette.length];
16
+ }
17
+ export function generatePalette(baseColor, count) {
18
+ const hsv = hexToHsv(baseColor);
19
+ if (hsv === null) {
20
+ return DEFAULT_PALETTE.slice(0, count);
21
+ }
22
+ const colors = [];
23
+ for (let i = 0; i < count; i++) {
24
+ const h = (hsv.h + i / count) % 1;
25
+ colors.push(hsvToHex(h, hsv.s, hsv.v));
26
+ }
27
+ return colors;
28
+ }
29
+ export function getContrastColor(hex) {
30
+ const rgb = hexToRgb(hex);
31
+ if (rgb === null) {
32
+ return '#000000';
33
+ }
34
+ const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
35
+ return luminance > 0.5 ? '#000000' : '#ffffff';
36
+ }
@@ -0,0 +1,3 @@
1
+ export declare function formatNumber(value: number): string;
2
+ export declare function formatPercent(value: number, total: number): string;
3
+ export declare function defaultTickFormat(value: number | string): string;
@@ -0,0 +1,28 @@
1
+ export function formatNumber(value) {
2
+ const abs = Math.abs(value);
3
+ if (abs >= 1e9) {
4
+ return (value / 1e9).toFixed(1).replace(/\.0$/, '') + 'B';
5
+ }
6
+ if (abs >= 1e6) {
7
+ return (value / 1e6).toFixed(1).replace(/\.0$/, '') + 'M';
8
+ }
9
+ if (abs >= 1e3) {
10
+ return (value / 1e3).toFixed(1).replace(/\.0$/, '') + 'K';
11
+ }
12
+ if (Number.isInteger(value)) {
13
+ return value.toString();
14
+ }
15
+ return value.toFixed(1);
16
+ }
17
+ export function formatPercent(value, total) {
18
+ if (total === 0) {
19
+ return '0%';
20
+ }
21
+ return Math.round((value / total) * 100) + '%';
22
+ }
23
+ export function defaultTickFormat(value) {
24
+ if (typeof value === 'string') {
25
+ return value;
26
+ }
27
+ return formatNumber(value);
28
+ }
@@ -0,0 +1,24 @@
1
+ import type { Margin, ChartDimensions, PieSliceLayout, ComputedSankeyNode, ComputedSankeyLink, StackedPoint } from './types';
2
+ export declare function computeChartDimensions(width: number, height: number, margin?: Partial<Margin>): ChartDimensions;
3
+ export declare function computePieLayout(data: Array<{
4
+ label: string;
5
+ value: number;
6
+ color?: string;
7
+ }>, startAngle?: number, padAngle?: number): PieSliceLayout[];
8
+ export declare function computeSankeyLayout(nodes: Array<{
9
+ id: string;
10
+ label?: string;
11
+ color?: string;
12
+ }>, links: Array<{
13
+ source: string;
14
+ target: string;
15
+ value: number;
16
+ color?: string;
17
+ }>, width: number, height: number, nodeWidth?: number, nodePadding?: number, iterations?: number): {
18
+ nodes: ComputedSankeyNode[];
19
+ links: ComputedSankeyLink[];
20
+ };
21
+ export declare function computeStackedValues(seriesData: Array<Array<{
22
+ x: number;
23
+ y: number;
24
+ }>>): StackedPoint[][];
@@ -0,0 +1,204 @@
1
+ export function computeChartDimensions(width, height, margin = {}) {
2
+ const m = {
3
+ top: margin.top ?? 20,
4
+ right: margin.right ?? 20,
5
+ bottom: margin.bottom ?? 40,
6
+ left: margin.left ?? 50
7
+ };
8
+ return {
9
+ width,
10
+ height,
11
+ margin: m,
12
+ innerWidth: Math.max(0, width - m.left - m.right),
13
+ innerHeight: Math.max(0, height - m.top - m.bottom)
14
+ };
15
+ }
16
+ // ── Pie layout ──────────────────────────────────────────────────
17
+ export function computePieLayout(data, startAngle = -Math.PI / 2, padAngle = 0) {
18
+ const total = data.reduce((sum, d) => sum + Math.max(0, d.value), 0);
19
+ if (total === 0) {
20
+ return [];
21
+ }
22
+ const slices = [];
23
+ let angle = startAngle;
24
+ for (let i = 0; i < data.length; i++) {
25
+ const d = data[i];
26
+ const val = Math.max(0, d.value);
27
+ const sliceAngle = (val / total) * Math.PI * 2;
28
+ const start = angle + padAngle / 2;
29
+ const end = angle + sliceAngle - padAngle / 2;
30
+ slices.push({
31
+ index: i,
32
+ startAngle: start,
33
+ endAngle: end,
34
+ midAngle: (start + end) / 2,
35
+ value: val,
36
+ percentage: (val / total) * 100,
37
+ label: d.label,
38
+ color: d.color
39
+ });
40
+ angle += sliceAngle;
41
+ }
42
+ return slices;
43
+ }
44
+ // ── Sankey layout ───────────────────────────────────────────────
45
+ export function computeSankeyLayout(nodes, links, width, height, nodeWidth = 16, nodePadding = 8, iterations = 6) {
46
+ if (nodes.length === 0) {
47
+ return { nodes: [], links: [] };
48
+ }
49
+ // Build adjacency
50
+ const outgoing = new Map();
51
+ const incoming = new Map();
52
+ for (const n of nodes) {
53
+ outgoing.set(n.id, []);
54
+ incoming.set(n.id, []);
55
+ }
56
+ for (const l of links) {
57
+ outgoing.get(l.source)?.push({ target: l.target, value: l.value });
58
+ incoming.get(l.target)?.push({ source: l.source, value: l.value });
59
+ }
60
+ // Assign columns via topological ordering
61
+ const columns = new Map();
62
+ const visited = new Set();
63
+ function assignColumn(id) {
64
+ if (columns.has(id)) {
65
+ return columns.get(id);
66
+ }
67
+ if (visited.has(id)) {
68
+ return 0;
69
+ }
70
+ visited.add(id);
71
+ const deps = incoming.get(id) ?? [];
72
+ const col = deps.length === 0 ? 0 : Math.max(...deps.map((d) => assignColumn(d.source) + 1));
73
+ columns.set(id, col);
74
+ return col;
75
+ }
76
+ for (const n of nodes) {
77
+ assignColumn(n.id);
78
+ }
79
+ const maxCol = Math.max(0, ...columns.values());
80
+ // Compute node values (max of incoming/outgoing)
81
+ const nodeValues = new Map();
82
+ for (const n of nodes) {
83
+ const outVal = (outgoing.get(n.id) ?? []).reduce((s, l) => s + l.value, 0);
84
+ const inVal = (incoming.get(n.id) ?? []).reduce((s, l) => s + l.value, 0);
85
+ nodeValues.set(n.id, Math.max(outVal, inVal));
86
+ }
87
+ // Group nodes by column
88
+ const columnGroups = new Map();
89
+ for (const n of nodes) {
90
+ const col = columns.get(n.id) ?? 0;
91
+ if (!columnGroups.has(col)) {
92
+ columnGroups.set(col, []);
93
+ }
94
+ columnGroups.get(col).push(n.id);
95
+ }
96
+ const colWidth = maxCol === 0 ? 0 : (width - nodeWidth) / maxCol;
97
+ // Initialize y positions
98
+ const nodeY = new Map();
99
+ const nodeH = new Map();
100
+ for (const [, ids] of columnGroups) {
101
+ const totalValue = ids.reduce((s, id) => s + (nodeValues.get(id) ?? 0), 0);
102
+ const availableHeight = height - (ids.length - 1) * nodePadding;
103
+ let y = 0;
104
+ for (const id of ids) {
105
+ const val = nodeValues.get(id) ?? 0;
106
+ const h = totalValue > 0 ? (val / totalValue) * availableHeight : availableHeight / ids.length;
107
+ nodeY.set(id, y);
108
+ nodeH.set(id, Math.max(1, h));
109
+ y += h + nodePadding;
110
+ }
111
+ }
112
+ // Iterative relaxation (upstream pass)
113
+ for (let iter = 0; iter < iterations; iter++) {
114
+ for (const [, ids] of columnGroups) {
115
+ for (const id of ids) {
116
+ const deps = incoming.get(id) ?? [];
117
+ if (deps.length > 0) {
118
+ const weightedY = deps.reduce((s, d) => {
119
+ const sy = nodeY.get(d.source) ?? 0;
120
+ const sh = nodeH.get(d.source) ?? 0;
121
+ return s + (sy + sh / 2) * d.value;
122
+ }, 0) / deps.reduce((s, d) => s + d.value, 0);
123
+ nodeY.set(id, Math.max(0, weightedY - (nodeH.get(id) ?? 0) / 2));
124
+ }
125
+ }
126
+ // Resolve overlaps
127
+ ids.sort((a, b) => (nodeY.get(a) ?? 0) - (nodeY.get(b) ?? 0));
128
+ let y = 0;
129
+ for (const id of ids) {
130
+ const cy = nodeY.get(id) ?? 0;
131
+ if (cy < y) {
132
+ nodeY.set(id, y);
133
+ }
134
+ y = (nodeY.get(id) ?? 0) + (nodeH.get(id) ?? 0) + nodePadding;
135
+ }
136
+ }
137
+ }
138
+ // Build computed nodes
139
+ const computedNodes = nodes.map((n) => ({
140
+ id: n.id,
141
+ label: n.label ?? n.id,
142
+ x: (columns.get(n.id) ?? 0) * colWidth,
143
+ y: nodeY.get(n.id) ?? 0,
144
+ width: nodeWidth,
145
+ height: nodeH.get(n.id) ?? 0,
146
+ value: nodeValues.get(n.id) ?? 0,
147
+ color: n.color,
148
+ column: columns.get(n.id) ?? 0
149
+ }));
150
+ const nodeById = new Map(computedNodes.map((n) => [n.id, n]));
151
+ // Build computed links with Bezier paths
152
+ const sourceOffsets = new Map();
153
+ const targetOffsets = new Map();
154
+ for (const n of nodes) {
155
+ sourceOffsets.set(n.id, nodeY.get(n.id) ?? 0);
156
+ targetOffsets.set(n.id, nodeY.get(n.id) ?? 0);
157
+ }
158
+ const computedLinks = links.map((l) => {
159
+ const sourceNode = nodeById.get(l.source);
160
+ const targetNode = nodeById.get(l.target);
161
+ const sVal = nodeValues.get(l.source) ?? 1;
162
+ const sourceH = nodeH.get(l.source) ?? 0;
163
+ const linkWidth = Math.max(1, (l.value / Math.max(sVal, 1)) * sourceH);
164
+ const sx = (sourceNode?.x ?? 0) + nodeWidth;
165
+ const sy = (sourceOffsets.get(l.source) ?? 0) + linkWidth / 2;
166
+ const tx = targetNode?.x ?? 0;
167
+ const ty = (targetOffsets.get(l.target) ?? 0) + linkWidth / 2;
168
+ sourceOffsets.set(l.source, (sourceOffsets.get(l.source) ?? 0) + linkWidth);
169
+ targetOffsets.set(l.target, (targetOffsets.get(l.target) ?? 0) + linkWidth);
170
+ const midX = (sx + tx) / 2;
171
+ const path = `M ${sx} ${sy} C ${midX} ${sy}, ${midX} ${ty}, ${tx} ${ty}`;
172
+ return {
173
+ source: l.source,
174
+ target: l.target,
175
+ value: l.value,
176
+ color: l.color,
177
+ sourceX: sx,
178
+ sourceY: sy,
179
+ targetX: tx,
180
+ targetY: ty,
181
+ width: linkWidth,
182
+ path
183
+ };
184
+ });
185
+ return { nodes: computedNodes, links: computedLinks };
186
+ }
187
+ // ── Stacked values ──────────────────────────────────────────────
188
+ export function computeStackedValues(seriesData) {
189
+ if (seriesData.length === 0) {
190
+ return [];
191
+ }
192
+ const stacked = [];
193
+ const baselines = new Map();
194
+ for (const series of seriesData) {
195
+ const stackedSeries = [];
196
+ for (const point of series) {
197
+ const base = baselines.get(point.x) ?? 0;
198
+ stackedSeries.push({ x: point.x, y0: base, y1: base + point.y });
199
+ baselines.set(point.x, base + point.y);
200
+ }
201
+ stacked.push(stackedSeries);
202
+ }
203
+ return stacked;
204
+ }
@@ -0,0 +1,4 @@
1
+ import type { Point, CurveType } from './types';
2
+ export declare function arcPath(cx: number, cy: number, innerR: number, outerR: number, startAngle: number, endAngle: number): string;
3
+ export declare function linePath(points: Point[], curve?: CurveType): string;
4
+ export declare function areaPath(points: Point[], baseline: number, curve?: CurveType): string;
@@ -0,0 +1,138 @@
1
+ export function arcPath(cx, cy, innerR, outerR, startAngle, endAngle) {
2
+ const fullCircle = Math.abs(endAngle - startAngle) >= Math.PI * 2 - 0.001;
3
+ if (fullCircle) {
4
+ if (innerR > 0) {
5
+ return (`M ${cx + outerR} ${cy}` +
6
+ `A ${outerR} ${outerR} 0 1 1 ${cx - outerR} ${cy}` +
7
+ `A ${outerR} ${outerR} 0 1 1 ${cx + outerR} ${cy}` +
8
+ `M ${cx + innerR} ${cy}` +
9
+ `A ${innerR} ${innerR} 0 1 0 ${cx - innerR} ${cy}` +
10
+ `A ${innerR} ${innerR} 0 1 0 ${cx + innerR} ${cy}Z`);
11
+ }
12
+ return (`M ${cx + outerR} ${cy}` +
13
+ `A ${outerR} ${outerR} 0 1 1 ${cx - outerR} ${cy}` +
14
+ `A ${outerR} ${outerR} 0 1 1 ${cx + outerR} ${cy}Z`);
15
+ }
16
+ const x1 = cx + outerR * Math.cos(startAngle);
17
+ const y1 = cy + outerR * Math.sin(startAngle);
18
+ const x2 = cx + outerR * Math.cos(endAngle);
19
+ const y2 = cy + outerR * Math.sin(endAngle);
20
+ const largeArc = endAngle - startAngle > Math.PI ? 1 : 0;
21
+ if (innerR > 0) {
22
+ const x3 = cx + innerR * Math.cos(endAngle);
23
+ const y3 = cy + innerR * Math.sin(endAngle);
24
+ const x4 = cx + innerR * Math.cos(startAngle);
25
+ const y4 = cy + innerR * Math.sin(startAngle);
26
+ return (`M ${x1} ${y1}` +
27
+ `A ${outerR} ${outerR} 0 ${largeArc} 1 ${x2} ${y2}` +
28
+ `L ${x3} ${y3}` +
29
+ `A ${innerR} ${innerR} 0 ${largeArc} 0 ${x4} ${y4}Z`);
30
+ }
31
+ return `M ${cx} ${cy}` + `L ${x1} ${y1}` + `A ${outerR} ${outerR} 0 ${largeArc} 1 ${x2} ${y2}Z`;
32
+ }
33
+ function linearPath(points) {
34
+ if (points.length === 0) {
35
+ return '';
36
+ }
37
+ let d = `M ${points[0].x} ${points[0].y}`;
38
+ for (let i = 1; i < points.length; i++) {
39
+ d += ` L ${points[i].x} ${points[i].y}`;
40
+ }
41
+ return d;
42
+ }
43
+ function stepPath(points) {
44
+ if (points.length === 0) {
45
+ return '';
46
+ }
47
+ let d = `M ${points[0].x} ${points[0].y}`;
48
+ for (let i = 1; i < points.length; i++) {
49
+ const midX = (points[i - 1].x + points[i].x) / 2;
50
+ d += ` L ${midX} ${points[i - 1].y} L ${midX} ${points[i].y}`;
51
+ }
52
+ if (points.length > 1) {
53
+ d += ` L ${points[points.length - 1].x} ${points[points.length - 1].y}`;
54
+ }
55
+ return d;
56
+ }
57
+ function monotoneTangents(points) {
58
+ const n = points.length;
59
+ const slopes = [];
60
+ const tangents = new Array(n);
61
+ for (let i = 0; i < n - 1; i++) {
62
+ const dx = points[i + 1].x - points[i].x;
63
+ slopes.push(dx === 0 ? 0 : (points[i + 1].y - points[i].y) / dx);
64
+ }
65
+ tangents[0] = slopes[0] ?? 0;
66
+ tangents[n - 1] = slopes[n - 2] ?? 0;
67
+ for (let i = 1; i < n - 1; i++) {
68
+ if (slopes[i - 1] * slopes[i] <= 0) {
69
+ tangents[i] = 0;
70
+ }
71
+ else {
72
+ tangents[i] = (slopes[i - 1] + slopes[i]) / 2;
73
+ }
74
+ }
75
+ // Fritsch-Carlson: ensure monotonicity
76
+ for (let i = 0; i < n - 1; i++) {
77
+ if (Math.abs(slopes[i]) < 1e-10) {
78
+ tangents[i] = 0;
79
+ tangents[i + 1] = 0;
80
+ }
81
+ else {
82
+ const alpha = tangents[i] / slopes[i];
83
+ const beta = tangents[i + 1] / slopes[i];
84
+ const s = alpha * alpha + beta * beta;
85
+ if (s > 9) {
86
+ const t = 3 / Math.sqrt(s);
87
+ tangents[i] = t * alpha * slopes[i];
88
+ tangents[i + 1] = t * beta * slopes[i];
89
+ }
90
+ }
91
+ }
92
+ return tangents;
93
+ }
94
+ function monotonePath(points) {
95
+ if (points.length === 0) {
96
+ return '';
97
+ }
98
+ if (points.length === 1) {
99
+ return `M ${points[0].x} ${points[0].y}`;
100
+ }
101
+ if (points.length === 2) {
102
+ return linearPath(points);
103
+ }
104
+ const tangents = monotoneTangents(points);
105
+ let d = `M ${points[0].x} ${points[0].y}`;
106
+ for (let i = 0; i < points.length - 1; i++) {
107
+ const dx = (points[i + 1].x - points[i].x) / 3;
108
+ const cp1x = points[i].x + dx;
109
+ const cp1y = points[i].y + dx * tangents[i];
110
+ const cp2x = points[i + 1].x - dx;
111
+ const cp2y = points[i + 1].y - dx * tangents[i + 1];
112
+ d += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${points[i + 1].x} ${points[i + 1].y}`;
113
+ }
114
+ return d;
115
+ }
116
+ export function linePath(points, curve = 'linear') {
117
+ if (points.length < 2) {
118
+ return points.length === 1 ? `M ${points[0].x} ${points[0].y}` : '';
119
+ }
120
+ switch (curve) {
121
+ case 'monotone':
122
+ case 'natural':
123
+ return monotonePath(points);
124
+ case 'step':
125
+ return stepPath(points);
126
+ default:
127
+ return linearPath(points);
128
+ }
129
+ }
130
+ export function areaPath(points, baseline, curve = 'linear') {
131
+ if (points.length === 0) {
132
+ return '';
133
+ }
134
+ const topPath = linePath(points, curve);
135
+ const lastPoint = points[points.length - 1];
136
+ const firstPoint = points[0];
137
+ return topPath + ` L ${lastPoint.x} ${baseline} L ${firstPoint.x} ${baseline} Z`;
138
+ }
@@ -0,0 +1,5 @@
1
+ import type { LinearScale, BandScale } from './types';
2
+ export declare function niceLinearDomain(min: number, max: number): [number, number];
3
+ export declare function computeLinearTicks(domain: [number, number], count?: number): number[];
4
+ export declare function createLinearScale(domain: [number, number], range: [number, number]): LinearScale;
5
+ export declare function createBandScale(domain: string[], range: [number, number], padding?: number): BandScale;
@@ -0,0 +1,77 @@
1
+ export function niceLinearDomain(min, max) {
2
+ if (min === max) {
3
+ return min === 0 ? [0, 1] : [min > 0 ? 0 : min * 2, max > 0 ? max * 2 : 0];
4
+ }
5
+ const range = max - min;
6
+ const exp = Math.floor(Math.log10(range));
7
+ const step = Math.pow(10, exp);
8
+ const niceMin = Math.floor(min / step) * step;
9
+ const niceMax = Math.ceil(max / step) * step;
10
+ return [niceMin, niceMax];
11
+ }
12
+ export function computeLinearTicks(domain, count = 5) {
13
+ const [min, max] = domain;
14
+ if (min === max) {
15
+ return [min];
16
+ }
17
+ const rawStep = (max - min) / count;
18
+ const exp = Math.floor(Math.log10(rawStep));
19
+ const base = Math.pow(10, exp);
20
+ let step;
21
+ const ratio = rawStep / base;
22
+ if (ratio <= 1.5) {
23
+ step = base;
24
+ }
25
+ else if (ratio <= 3) {
26
+ step = base * 2;
27
+ }
28
+ else if (ratio <= 7) {
29
+ step = base * 5;
30
+ }
31
+ else {
32
+ step = base * 10;
33
+ }
34
+ const ticks = [];
35
+ let tick = Math.ceil(min / step) * step;
36
+ while (tick <= max + step * 0.001) {
37
+ ticks.push(Math.round(tick * 1e10) / 1e10);
38
+ tick += step;
39
+ }
40
+ return ticks;
41
+ }
42
+ export function createLinearScale(domain, range) {
43
+ const [d0, d1] = domain;
44
+ const [r0, r1] = range;
45
+ const dSpan = d1 - d0 || 1;
46
+ const rSpan = r1 - r0;
47
+ const fn = (value) => r0 + ((value - d0) / dSpan) * rSpan;
48
+ return Object.assign(fn, {
49
+ domain,
50
+ range,
51
+ ticks: (count) => computeLinearTicks(domain, count),
52
+ invert: (pixel) => d0 + ((pixel - r0) / rSpan) * dSpan
53
+ });
54
+ }
55
+ export function createBandScale(domain, range, padding = 0.1) {
56
+ const [r0, r1] = range;
57
+ const n = domain.length || 1;
58
+ const totalRange = r1 - r0;
59
+ // d3-scale band formula (paddingOuter = 0): step = range / (n - padding)
60
+ // Derivation: n*bandwidth + (n-1)*gap = totalRange,
61
+ // with bandwidth = step*(1-padding) and gap = step*padding
62
+ // → step*(n - padding) = totalRange
63
+ const step = totalRange / Math.max(1, n - padding);
64
+ const bandwidth = step * (1 - padding);
65
+ const indexMap = new Map();
66
+ domain.forEach((label, i) => indexMap.set(label, i));
67
+ const fn = (label) => {
68
+ const idx = indexMap.get(label) ?? 0;
69
+ return r0 + idx * step;
70
+ };
71
+ return Object.assign(fn, {
72
+ domain,
73
+ range,
74
+ bandwidth,
75
+ step
76
+ });
77
+ }