@internetstiftelsen/charts 0.5.1 → 0.6.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/README.md +4 -2
- package/area.d.ts +26 -0
- package/area.js +331 -0
- package/base-chart.d.ts +10 -3
- package/base-chart.js +33 -19
- package/chart-interface.d.ts +1 -1
- package/donut-center-content.d.ts +1 -1
- package/donut-center-content.js +7 -6
- package/donut-chart.d.ts +3 -0
- package/donut-chart.js +23 -6
- package/export-tabular.d.ts +4 -4
- package/export-tabular.js +8 -0
- package/export-xlsx.d.ts +2 -2
- package/gauge-chart.d.ts +140 -0
- package/gauge-chart.js +1051 -0
- package/grouped-data.d.ts +19 -0
- package/grouped-data.js +122 -0
- package/grouped-tabular.d.ts +26 -0
- package/grouped-tabular.js +149 -0
- package/legend.d.ts +15 -1
- package/legend.js +203 -46
- package/package.json +2 -1
- package/pie-chart.d.ts +82 -0
- package/pie-chart.js +675 -0
- package/theme.d.ts +1 -0
- package/theme.js +31 -16
- package/tooltip.d.ts +3 -2
- package/tooltip.js +40 -39
- package/types.d.ts +52 -0
- package/validation.d.ts +4 -0
- package/validation.js +25 -0
- package/x-axis.d.ts +10 -2
- package/x-axis.js +205 -15
- package/xy-chart.d.ts +11 -1
- package/xy-chart.js +310 -93
package/theme.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
export const DEFAULT_COLOR_PALETTE = [
|
|
2
|
+
'#50b2fc', // ocean
|
|
3
|
+
'#ff4069', // ruby
|
|
4
|
+
'#55c7b4', // jade
|
|
5
|
+
'#ffce2e', // lemon
|
|
6
|
+
'#c27fec', // peacock
|
|
7
|
+
'#f99963', // sandstone
|
|
8
|
+
'#ff9fb4', // ruby-light
|
|
9
|
+
'#1f2a36', // cyberspace
|
|
10
|
+
];
|
|
1
11
|
export const defaultTheme = {
|
|
2
12
|
width: 928,
|
|
3
13
|
height: 600,
|
|
@@ -5,32 +15,30 @@ export const defaultTheme = {
|
|
|
5
15
|
margins: {
|
|
6
16
|
top: 20,
|
|
7
17
|
right: 20,
|
|
8
|
-
bottom:
|
|
9
|
-
left:
|
|
18
|
+
bottom: 20,
|
|
19
|
+
left: 20,
|
|
10
20
|
},
|
|
11
21
|
grid: {
|
|
12
22
|
color: '#e0e0e0',
|
|
13
23
|
opacity: 0.5,
|
|
14
24
|
},
|
|
15
|
-
colorPalette: [
|
|
16
|
-
'#50b2fc', // ocean
|
|
17
|
-
'#ff4069', // ruby
|
|
18
|
-
'#55c7b4', // jade
|
|
19
|
-
'#ffce2e', // lemon
|
|
20
|
-
'#c27fec', // peacock
|
|
21
|
-
'#f99963', // sandstone
|
|
22
|
-
'#ff9fb4', // ruby-light
|
|
23
|
-
'#1f2a36', // cyberspace
|
|
24
|
-
],
|
|
25
|
+
colorPalette: [...DEFAULT_COLOR_PALETTE],
|
|
25
26
|
axis: {
|
|
26
27
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
|
|
27
28
|
fontSize: '14',
|
|
28
29
|
fontWeight: 'normal',
|
|
30
|
+
groupLabel: {
|
|
31
|
+
fontWeight: '700',
|
|
32
|
+
color: '#111827',
|
|
33
|
+
},
|
|
29
34
|
},
|
|
30
35
|
legend: {
|
|
31
36
|
boxSize: 24,
|
|
32
37
|
uncheckedColor: '#d0d0d0',
|
|
33
38
|
fontSize: 14,
|
|
39
|
+
paddingX: 0,
|
|
40
|
+
itemSpacingX: 20,
|
|
41
|
+
itemSpacingY: 8,
|
|
34
42
|
},
|
|
35
43
|
line: {
|
|
36
44
|
strokeWidth: 4,
|
|
@@ -45,7 +53,7 @@ export const defaultTheme = {
|
|
|
45
53
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
|
|
46
54
|
fontWeight: '600',
|
|
47
55
|
color: '#1f2a36',
|
|
48
|
-
background: '
|
|
56
|
+
background: '#ffffff',
|
|
49
57
|
border: '#e0e0e0',
|
|
50
58
|
borderRadius: 4,
|
|
51
59
|
padding: 4,
|
|
@@ -80,8 +88,8 @@ export const newspaperTheme = {
|
|
|
80
88
|
margins: {
|
|
81
89
|
top: 20,
|
|
82
90
|
right: 20,
|
|
83
|
-
bottom:
|
|
84
|
-
left:
|
|
91
|
+
bottom: 20,
|
|
92
|
+
left: 20,
|
|
85
93
|
},
|
|
86
94
|
grid: {
|
|
87
95
|
color: '#2c2c2c',
|
|
@@ -101,11 +109,18 @@ export const newspaperTheme = {
|
|
|
101
109
|
fontFamily: 'Georgia, "Times New Roman", Times, serif',
|
|
102
110
|
fontSize: '13',
|
|
103
111
|
fontWeight: '600',
|
|
112
|
+
groupLabel: {
|
|
113
|
+
fontWeight: '700',
|
|
114
|
+
color: '#1a1a1a',
|
|
115
|
+
},
|
|
104
116
|
},
|
|
105
117
|
legend: {
|
|
106
118
|
boxSize: 18,
|
|
107
119
|
uncheckedColor: '#d3d3d3',
|
|
108
120
|
fontSize: 13,
|
|
121
|
+
paddingX: 0,
|
|
122
|
+
itemSpacingX: 20,
|
|
123
|
+
itemSpacingY: 8,
|
|
109
124
|
},
|
|
110
125
|
line: {
|
|
111
126
|
strokeWidth: 2.5,
|
|
@@ -121,7 +136,7 @@ export const newspaperTheme = {
|
|
|
121
136
|
fontFamily: 'Georgia, "Times New Roman", Times, serif',
|
|
122
137
|
fontWeight: '600',
|
|
123
138
|
color: '#1a1a1a',
|
|
124
|
-
background: '
|
|
139
|
+
background: '#ffffff',
|
|
125
140
|
border: '#2c2c2c',
|
|
126
141
|
borderRadius: 2,
|
|
127
142
|
padding: 3,
|
package/tooltip.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
|
-
import type { TooltipConfig, DataItem, DataValue, D3Scale, ChartTheme, ExportHooks, TooltipConfigBase } from './types.js';
|
|
2
|
+
import type { TooltipConfig, DataItem, DataValue, D3Scale, ChartTheme, ExportHooks, TooltipConfigBase, ScaleType } from './types.js';
|
|
3
3
|
import type { ChartComponent } from './chart-interface.js';
|
|
4
4
|
import type { Line } from './line.js';
|
|
5
5
|
import type { Bar } from './bar.js';
|
|
6
|
+
import type { Area } from './area.js';
|
|
6
7
|
import type { PlotAreaBounds } from './layout-manager.js';
|
|
7
8
|
export declare class Tooltip implements ChartComponent<TooltipConfigBase> {
|
|
8
9
|
readonly id = "iisChartTooltip";
|
|
@@ -20,6 +21,6 @@ export declare class Tooltip implements ChartComponent<TooltipConfigBase> {
|
|
|
20
21
|
getExportConfig(): TooltipConfigBase;
|
|
21
22
|
createExportComponent(override?: Partial<TooltipConfigBase>): ChartComponent;
|
|
22
23
|
initialize(theme: ChartTheme): void;
|
|
23
|
-
attachToArea(svg: Selection<SVGSVGElement, undefined, null, undefined>, data: DataItem[], series: (Line | Bar)[], xKey: string, x: D3Scale, y: D3Scale, theme: ChartTheme, plotArea: PlotAreaBounds, parseValue: (value: unknown) => number, isHorizontal?: boolean): void;
|
|
24
|
+
attachToArea(svg: Selection<SVGSVGElement, undefined, null, undefined>, data: DataItem[], series: (Line | Bar | Area)[], xKey: string, x: D3Scale, y: D3Scale, theme: ChartTheme, plotArea: PlotAreaBounds, parseValue: (value: unknown) => number, isHorizontal?: boolean, categoryScaleType?: ScaleType, resolveSeriesValue?: (series: Line | Bar | Area, dataPoint: DataItem, index: number) => number): void;
|
|
24
25
|
cleanup(): void;
|
|
25
26
|
}
|
package/tooltip.js
CHANGED
|
@@ -86,31 +86,40 @@ export class Tooltip {
|
|
|
86
86
|
.style('z-index', '1000');
|
|
87
87
|
this.tooltipDiv = tooltip;
|
|
88
88
|
}
|
|
89
|
-
attachToArea(svg, data, series, xKey, x, y, theme, plotArea, parseValue, isHorizontal = false) {
|
|
89
|
+
attachToArea(svg, data, series, xKey, x, y, theme, plotArea, parseValue, isHorizontal = false, categoryScaleType = 'band', resolveSeriesValue = (targetSeries, dataPoint) => {
|
|
90
|
+
const rawValue = dataPoint[targetSeries.dataKey];
|
|
91
|
+
if (rawValue === null || rawValue === undefined) {
|
|
92
|
+
return NaN;
|
|
93
|
+
}
|
|
94
|
+
return parseValue(rawValue);
|
|
95
|
+
}) {
|
|
90
96
|
if (!this.tooltipDiv)
|
|
91
97
|
return;
|
|
92
98
|
const tooltip = this.tooltipDiv;
|
|
93
99
|
const formatter = this.formatter;
|
|
94
100
|
const labelFormatter = this.labelFormatter;
|
|
95
101
|
const customFormatter = this.customFormatter;
|
|
96
|
-
|
|
102
|
+
const getCategoryScaleValue = (value, scaleType) => {
|
|
103
|
+
switch (scaleType) {
|
|
104
|
+
case 'band':
|
|
105
|
+
return String(value);
|
|
106
|
+
case 'time':
|
|
107
|
+
return value instanceof Date
|
|
108
|
+
? value
|
|
109
|
+
: new Date(String(value));
|
|
110
|
+
case 'linear':
|
|
111
|
+
case 'log':
|
|
112
|
+
return typeof value === 'number' ? value : Number(value);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
97
115
|
const getXPosition = (dataPoint) => {
|
|
98
116
|
const xValue = dataPoint[xKey];
|
|
99
|
-
const scaled = x(xValue
|
|
100
|
-
? xValue
|
|
101
|
-
: typeof xValue === 'string'
|
|
102
|
-
? xValue
|
|
103
|
-
: new Date(xValue));
|
|
117
|
+
const scaled = x(getCategoryScaleValue(xValue, categoryScaleType));
|
|
104
118
|
return (scaled || 0) + (x.bandwidth ? x.bandwidth() / 2 : 0);
|
|
105
119
|
};
|
|
106
|
-
// Helper to get y position for category scale (used in horizontal orientation)
|
|
107
120
|
const getYPosition = (dataPoint) => {
|
|
108
121
|
const yValue = dataPoint[xKey];
|
|
109
|
-
const scaled = y(yValue
|
|
110
|
-
? yValue
|
|
111
|
-
: typeof yValue === 'string'
|
|
112
|
-
? yValue
|
|
113
|
-
: new Date(yValue));
|
|
122
|
+
const scaled = y(getCategoryScaleValue(yValue, categoryScaleType));
|
|
114
123
|
return (scaled || 0) + (y.bandwidth ? y.bandwidth() / 2 : 0);
|
|
115
124
|
};
|
|
116
125
|
// Create overlay rect for mouse tracking using plot area bounds
|
|
@@ -123,11 +132,9 @@ export class Tooltip {
|
|
|
123
132
|
.attr('height', plotArea.height)
|
|
124
133
|
.style('fill', 'none')
|
|
125
134
|
.style('pointer-events', 'all');
|
|
126
|
-
|
|
127
|
-
const lineSeries = series.filter((s) => s.type === 'line');
|
|
135
|
+
const lineSeries = series.filter((s) => s.type === 'line' || s.type === 'area');
|
|
128
136
|
const barSeries = series.filter((s) => s.type === 'bar');
|
|
129
137
|
const hasBarSeries = barSeries.length > 0;
|
|
130
|
-
// Create focus circles only for line series
|
|
131
138
|
const focusCircles = lineSeries.map((s) => {
|
|
132
139
|
const seriesColor = getSeriesColor(s);
|
|
133
140
|
return svg
|
|
@@ -143,11 +150,9 @@ export class Tooltip {
|
|
|
143
150
|
overlay
|
|
144
151
|
.on('mousemove', (event) => {
|
|
145
152
|
const [mouseX, mouseY] = pointer(event, svg.node());
|
|
146
|
-
// Find closest data point based on orientation
|
|
147
153
|
let closestIndex = 0;
|
|
148
154
|
let dataPointPosition;
|
|
149
155
|
if (isHorizontal) {
|
|
150
|
-
// For horizontal charts, find closest by Y position (categories on Y axis)
|
|
151
156
|
const yPositions = data.map((d) => getYPosition(d));
|
|
152
157
|
let minDistance = Math.abs(mouseY - yPositions[0]);
|
|
153
158
|
for (let i = 1; i < yPositions.length; i++) {
|
|
@@ -160,7 +165,6 @@ export class Tooltip {
|
|
|
160
165
|
dataPointPosition = yPositions[closestIndex];
|
|
161
166
|
}
|
|
162
167
|
else {
|
|
163
|
-
// For vertical charts, find closest by X position (categories on X axis)
|
|
164
168
|
const xPositions = data.map((d) => getXPosition(d));
|
|
165
169
|
let minDistance = Math.abs(mouseX - xPositions[0]);
|
|
166
170
|
for (let i = 1; i < xPositions.length; i++) {
|
|
@@ -173,40 +177,40 @@ export class Tooltip {
|
|
|
173
177
|
dataPointPosition = xPositions[closestIndex];
|
|
174
178
|
}
|
|
175
179
|
const dataPoint = data[closestIndex];
|
|
176
|
-
// Update focus circles for line series
|
|
177
180
|
lineSeries.forEach((s, i) => {
|
|
178
|
-
const value =
|
|
181
|
+
const value = resolveSeriesValue(s, dataPoint, closestIndex);
|
|
182
|
+
if (!Number.isFinite(value)) {
|
|
183
|
+
focusCircles[i].style('opacity', 0);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
179
186
|
if (isHorizontal) {
|
|
180
|
-
// Horizontal: cx = value position (X), cy = category position (Y)
|
|
181
187
|
focusCircles[i]
|
|
182
188
|
.attr('cx', x(value))
|
|
183
189
|
.attr('cy', dataPointPosition)
|
|
184
190
|
.style('opacity', 1);
|
|
185
191
|
}
|
|
186
192
|
else {
|
|
187
|
-
// Vertical: cx = category position (X), cy = value position (Y)
|
|
188
193
|
focusCircles[i]
|
|
189
194
|
.attr('cx', dataPointPosition)
|
|
190
195
|
.attr('cy', y(value))
|
|
191
196
|
.style('opacity', 1);
|
|
192
197
|
}
|
|
193
198
|
});
|
|
194
|
-
// Fade non-hovered bars
|
|
195
199
|
if (hasBarSeries) {
|
|
196
200
|
barSeries.forEach((s) => {
|
|
197
201
|
const sanitizedKey = sanitizeForCSS(s.dataKey);
|
|
198
202
|
svg.selectAll(`.bar-${sanitizedKey}`).style('opacity', (_, i) => (i === closestIndex ? 1 : 0.5));
|
|
199
203
|
});
|
|
200
204
|
}
|
|
201
|
-
// Build tooltip content
|
|
202
205
|
let content;
|
|
203
206
|
if (customFormatter) {
|
|
204
207
|
content = customFormatter(dataPoint, series);
|
|
205
208
|
}
|
|
206
209
|
else {
|
|
210
|
+
const labelValue = dataPoint[xKey];
|
|
207
211
|
const label = labelFormatter
|
|
208
|
-
? labelFormatter(
|
|
209
|
-
:
|
|
212
|
+
? labelFormatter(String(labelValue), dataPoint)
|
|
213
|
+
: String(labelValue);
|
|
210
214
|
content = `<strong>${label}</strong><br/>`;
|
|
211
215
|
series.forEach((s) => {
|
|
212
216
|
const value = dataPoint[s.dataKey];
|
|
@@ -220,23 +224,24 @@ export class Tooltip {
|
|
|
220
224
|
}
|
|
221
225
|
});
|
|
222
226
|
}
|
|
223
|
-
// Position tooltip: X anchored to data point, Y at midpoint of values
|
|
224
227
|
tooltip.style('visibility', 'visible').html(content);
|
|
225
|
-
// Get tooltip dimensions after content is set
|
|
226
228
|
const tooltipNode = tooltip.node();
|
|
227
229
|
const tooltipRect = tooltipNode.getBoundingClientRect();
|
|
228
230
|
const tooltipWidth = tooltipRect.width;
|
|
229
231
|
const tooltipHeight = tooltipRect.height;
|
|
230
232
|
const svgRect = svg.node().getBoundingClientRect();
|
|
231
233
|
const offsetX = 12;
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
const minValue =
|
|
235
|
-
|
|
234
|
+
const values = series.map((s) => resolveSeriesValue(s, dataPoint, closestIndex));
|
|
235
|
+
const finiteValues = values.filter((value) => Number.isFinite(value));
|
|
236
|
+
const minValue = finiteValues.length
|
|
237
|
+
? Math.min(...finiteValues)
|
|
238
|
+
: 0;
|
|
239
|
+
const maxValue = finiteValues.length
|
|
240
|
+
? Math.max(...finiteValues)
|
|
241
|
+
: 0;
|
|
236
242
|
let tooltipX;
|
|
237
243
|
let tooltipY;
|
|
238
244
|
if (isHorizontal) {
|
|
239
|
-
// Horizontal: X at midpoint of values, Y anchored to category
|
|
240
245
|
const minX = x(minValue);
|
|
241
246
|
const maxX = x(maxValue);
|
|
242
247
|
const midX = (minX + maxX) / 2;
|
|
@@ -248,8 +253,7 @@ export class Tooltip {
|
|
|
248
253
|
tooltipHeight / 2;
|
|
249
254
|
}
|
|
250
255
|
else {
|
|
251
|
-
|
|
252
|
-
const minY = y(maxValue); // Note: Y scale is inverted
|
|
256
|
+
const minY = y(maxValue);
|
|
253
257
|
const maxY = y(minValue);
|
|
254
258
|
const midY = (minY + maxY) / 2;
|
|
255
259
|
tooltipX =
|
|
@@ -260,7 +264,6 @@ export class Tooltip {
|
|
|
260
264
|
tooltipY =
|
|
261
265
|
svgRect.top + window.scrollY + midY - tooltipHeight / 2;
|
|
262
266
|
}
|
|
263
|
-
// Edge detection - flip horizontally if approaching right edge
|
|
264
267
|
const viewportWidth = window.innerWidth;
|
|
265
268
|
if (tooltipX + tooltipWidth > viewportWidth - 10) {
|
|
266
269
|
if (isHorizontal) {
|
|
@@ -281,7 +284,6 @@ export class Tooltip {
|
|
|
281
284
|
offsetX;
|
|
282
285
|
}
|
|
283
286
|
}
|
|
284
|
-
// Ensure tooltip doesn't go off edges
|
|
285
287
|
tooltipX = Math.max(10, tooltipX);
|
|
286
288
|
tooltipY = Math.max(10, Math.min(tooltipY, window.innerHeight +
|
|
287
289
|
window.scrollY -
|
|
@@ -294,7 +296,6 @@ export class Tooltip {
|
|
|
294
296
|
.on('mouseout', () => {
|
|
295
297
|
tooltip.style('visibility', 'hidden');
|
|
296
298
|
focusCircles.forEach((circle) => circle.style('opacity', 0));
|
|
297
|
-
// Reset bar opacity
|
|
298
299
|
if (hasBarSeries) {
|
|
299
300
|
barSeries.forEach((s) => {
|
|
300
301
|
const sanitizedKey = sanitizeForCSS(s.dataKey);
|
package/types.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export type DataValue = string | number | boolean | Date | null | undefined;
|
|
2
2
|
export type DataItem = Record<string, any>;
|
|
3
|
+
export type GroupedDataGroup = {
|
|
4
|
+
group: string;
|
|
5
|
+
data: DataItem[];
|
|
6
|
+
};
|
|
7
|
+
export type ChartData = DataItem[] | GroupedDataGroup[];
|
|
3
8
|
export type ExportFormat = 'svg' | 'json' | 'csv' | 'xlsx' | 'png' | 'jpg' | 'pdf';
|
|
4
9
|
export type ExportOptions = {
|
|
5
10
|
download?: boolean;
|
|
@@ -47,11 +52,20 @@ export type ChartTheme = {
|
|
|
47
52
|
fontFamily: string;
|
|
48
53
|
fontSize: string;
|
|
49
54
|
fontWeight?: string;
|
|
55
|
+
groupLabel?: {
|
|
56
|
+
fontFamily?: string;
|
|
57
|
+
fontSize?: string;
|
|
58
|
+
fontWeight?: string;
|
|
59
|
+
color?: string;
|
|
60
|
+
};
|
|
50
61
|
};
|
|
51
62
|
legend: {
|
|
52
63
|
boxSize: number;
|
|
53
64
|
uncheckedColor: string;
|
|
54
65
|
fontSize: number;
|
|
66
|
+
paddingX: number;
|
|
67
|
+
itemSpacingX: number;
|
|
68
|
+
itemSpacingY: number;
|
|
55
69
|
};
|
|
56
70
|
line: {
|
|
57
71
|
strokeWidth: number;
|
|
@@ -136,10 +150,32 @@ export type BarConfigBase = {
|
|
|
136
150
|
export type BarConfig = BarConfigBase & {
|
|
137
151
|
exportHooks?: ExportHooks<BarConfigBase>;
|
|
138
152
|
};
|
|
153
|
+
export type AreaCurveType = 'linear' | 'monotone' | 'step' | 'natural' | 'basis' | 'cardinal';
|
|
154
|
+
export type AreaConfigBase = {
|
|
155
|
+
dataKey: string;
|
|
156
|
+
fill?: string;
|
|
157
|
+
stroke?: string;
|
|
158
|
+
strokeWidth?: number;
|
|
159
|
+
opacity?: number;
|
|
160
|
+
curve?: AreaCurveType;
|
|
161
|
+
stackId?: string | number;
|
|
162
|
+
baseline?: number;
|
|
163
|
+
showLine?: boolean;
|
|
164
|
+
showPoints?: boolean;
|
|
165
|
+
pointSize?: number;
|
|
166
|
+
valueLabel?: LineValueLabelConfig;
|
|
167
|
+
};
|
|
168
|
+
export type AreaConfig = AreaConfigBase & {
|
|
169
|
+
exportHooks?: ExportHooks<AreaConfigBase>;
|
|
170
|
+
};
|
|
139
171
|
export type BarStackConfig = {
|
|
140
172
|
mode?: BarStackMode;
|
|
141
173
|
gap?: number;
|
|
142
174
|
};
|
|
175
|
+
export type AreaStackMode = 'none' | 'normal' | 'percent';
|
|
176
|
+
export type AreaStackConfig = {
|
|
177
|
+
mode?: AreaStackMode;
|
|
178
|
+
};
|
|
143
179
|
export declare function getSeriesColor(series: {
|
|
144
180
|
stroke?: string;
|
|
145
181
|
fill?: string;
|
|
@@ -147,6 +183,10 @@ export declare function getSeriesColor(series: {
|
|
|
147
183
|
export type LabelOversizedBehavior = 'truncate' | 'wrap' | 'hide';
|
|
148
184
|
export type XAxisConfigBase = {
|
|
149
185
|
dataKey?: string;
|
|
186
|
+
labelKey?: string;
|
|
187
|
+
groupLabelKey?: string;
|
|
188
|
+
showGroupLabels?: boolean;
|
|
189
|
+
groupLabelGap?: number;
|
|
150
190
|
rotatedLabels?: boolean;
|
|
151
191
|
maxLabelWidth?: number;
|
|
152
192
|
oversizedBehavior?: LabelOversizedBehavior;
|
|
@@ -190,6 +230,9 @@ export type LegendConfigBase = {
|
|
|
190
230
|
position?: 'bottom';
|
|
191
231
|
marginTop?: number;
|
|
192
232
|
marginBottom?: number;
|
|
233
|
+
paddingX?: number;
|
|
234
|
+
itemSpacingX?: number;
|
|
235
|
+
itemSpacingY?: number;
|
|
193
236
|
};
|
|
194
237
|
export type LegendConfig = LegendConfigBase & {
|
|
195
238
|
exportHooks?: ExportHooks<LegendConfigBase>;
|
|
@@ -219,6 +262,7 @@ export type ScaleConfig = {
|
|
|
219
262
|
domain?: ScaleDomainValue[];
|
|
220
263
|
range?: number[];
|
|
221
264
|
padding?: number;
|
|
265
|
+
groupGap?: number;
|
|
222
266
|
nice?: boolean;
|
|
223
267
|
min?: number;
|
|
224
268
|
max?: number;
|
|
@@ -237,3 +281,11 @@ export type BarStackingContext = {
|
|
|
237
281
|
totalData: Map<string, number>;
|
|
238
282
|
gap: number;
|
|
239
283
|
};
|
|
284
|
+
export type AreaStackingContext = {
|
|
285
|
+
mode: AreaStackMode;
|
|
286
|
+
stackId: string | number;
|
|
287
|
+
seriesIndex: number;
|
|
288
|
+
totalSeries: number;
|
|
289
|
+
cumulativeData: Map<string, number>;
|
|
290
|
+
totalData: Map<string, number>;
|
|
291
|
+
};
|
package/validation.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export declare class ChartValidator {
|
|
|
18
18
|
* Validates that data contains at least one valid numeric value for the specified key
|
|
19
19
|
*/
|
|
20
20
|
static validateNumericData(data: DataItem[], dataKey: string, componentName: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Validates that all non-null values are positive for logarithmic scales
|
|
23
|
+
*/
|
|
24
|
+
static validatePositiveData(data: DataItem[], dataKey: string, componentName: string): void;
|
|
21
25
|
/**
|
|
22
26
|
* Validates scale configuration
|
|
23
27
|
*/
|
package/validation.js
CHANGED
|
@@ -55,6 +55,31 @@ export class ChartValidator {
|
|
|
55
55
|
throw new ChartValidationError(`${componentName}: No valid numeric values found for dataKey "${dataKey}"`);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Validates that all non-null values are positive for logarithmic scales
|
|
60
|
+
*/
|
|
61
|
+
static validatePositiveData(data, dataKey, componentName) {
|
|
62
|
+
const invalidIndices = [];
|
|
63
|
+
data.forEach((item, index) => {
|
|
64
|
+
const rawValue = item[dataKey];
|
|
65
|
+
if (rawValue === null || rawValue === undefined) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const value = typeof rawValue === 'number'
|
|
69
|
+
? rawValue
|
|
70
|
+
: parseFloat(String(rawValue));
|
|
71
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
72
|
+
invalidIndices.push(index);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
if (invalidIndices.length > 0) {
|
|
76
|
+
const indices = invalidIndices.slice(0, 3).join(', ');
|
|
77
|
+
const more = invalidIndices.length > 3
|
|
78
|
+
? ` and ${invalidIndices.length - 3} more`
|
|
79
|
+
: '';
|
|
80
|
+
throw new ChartValidationError(`${componentName}: Logarithmic scale requires values > 0 for dataKey "${dataKey}" (invalid at indices: ${indices}${more})`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
58
83
|
/**
|
|
59
84
|
* Validates scale configuration
|
|
60
85
|
*/
|
package/x-axis.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { type Selection } from 'd3';
|
|
2
|
-
import type { XAxisConfig, ChartTheme, D3Scale, ExportHooks, XAxisConfigBase } from './types.js';
|
|
2
|
+
import type { XAxisConfig, ChartTheme, D3Scale, DataItem, ExportHooks, XAxisConfigBase } from './types.js';
|
|
3
3
|
import type { LayoutAwareComponent, ComponentSpace } from './chart-interface.js';
|
|
4
4
|
export declare class XAxis implements LayoutAwareComponent<XAxisConfigBase> {
|
|
5
5
|
readonly type: "xAxis";
|
|
6
6
|
readonly dataKey?: string;
|
|
7
|
+
readonly labelKey?: string;
|
|
8
|
+
readonly groupLabelKey?: string;
|
|
9
|
+
readonly showGroupLabels: boolean;
|
|
10
|
+
readonly groupLabelGap: number;
|
|
7
11
|
private readonly rotatedLabels;
|
|
8
12
|
private readonly tickPadding;
|
|
9
13
|
private fontSize;
|
|
@@ -16,6 +20,7 @@ export declare class XAxis implements LayoutAwareComponent<XAxisConfigBase> {
|
|
|
16
20
|
private readonly minLabelGap;
|
|
17
21
|
private readonly preserveEndLabels;
|
|
18
22
|
readonly exportHooks?: ExportHooks<XAxisConfigBase>;
|
|
23
|
+
private resolveGroupLabelStyle;
|
|
19
24
|
constructor(config?: XAxisConfig);
|
|
20
25
|
getExportConfig(): XAxisConfigBase;
|
|
21
26
|
createExportComponent(override?: Partial<XAxisConfigBase>): LayoutAwareComponent;
|
|
@@ -25,7 +30,10 @@ export declare class XAxis implements LayoutAwareComponent<XAxisConfigBase> {
|
|
|
25
30
|
getRequiredSpace(): ComponentSpace;
|
|
26
31
|
estimateLayoutSpace(labels: unknown[], theme: ChartTheme, svg: SVGSVGElement): void;
|
|
27
32
|
clearEstimatedSpace(): void;
|
|
28
|
-
render(svg: Selection<SVGSVGElement, undefined, null, undefined>, x: D3Scale, theme: ChartTheme, yPosition: number): void;
|
|
33
|
+
render(svg: Selection<SVGSVGElement, undefined, null, undefined>, x: D3Scale, theme: ChartTheme, yPosition: number, data?: DataItem[]): void;
|
|
34
|
+
private buildLabelLookup;
|
|
35
|
+
private renderGroupLabels;
|
|
36
|
+
private buildGroupRanges;
|
|
29
37
|
private applyLabelConstraints;
|
|
30
38
|
private wrapTextElement;
|
|
31
39
|
private addTitleTooltip;
|