@internetstiftelsen/charts 0.1.0 → 0.1.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/line.js +11 -4
- package/package.json +1 -1
- package/types.d.ts +1 -0
- package/x-axis.d.ts +1 -0
- package/x-axis.js +22 -4
- package/xy-chart.d.ts +0 -3
- package/xy-chart.js +4 -29
package/line.js
CHANGED
|
@@ -63,7 +63,13 @@ export class Line {
|
|
|
63
63
|
// Handle band scales with bandwidth
|
|
64
64
|
return (scaled || 0) + (x.bandwidth ? x.bandwidth() / 2 : 0);
|
|
65
65
|
};
|
|
66
|
+
// Helper to check if a data point has a valid (non-null) value
|
|
67
|
+
const hasValidValue = (d) => {
|
|
68
|
+
const value = d[this.dataKey];
|
|
69
|
+
return value !== null && value !== undefined;
|
|
70
|
+
};
|
|
66
71
|
const lineGenerator = line()
|
|
72
|
+
.defined(hasValidValue)
|
|
67
73
|
.x(getXPosition)
|
|
68
74
|
.y((d) => y(parseValue(d[this.dataKey])) || 0);
|
|
69
75
|
const lineStrokeWidth = this.strokeWidth ?? theme.line.strokeWidth;
|
|
@@ -79,11 +85,12 @@ export class Line {
|
|
|
79
85
|
.attr('stroke', this.stroke)
|
|
80
86
|
.attr('stroke-width', lineStrokeWidth)
|
|
81
87
|
.attr('d', lineGenerator);
|
|
82
|
-
// Add data point circles
|
|
88
|
+
// Add data point circles (only for valid values)
|
|
89
|
+
const validData = data.filter(hasValidValue);
|
|
83
90
|
const sanitizedKey = sanitizeForCSS(this.dataKey);
|
|
84
91
|
plotGroup
|
|
85
92
|
.selectAll(`.circle-${sanitizedKey}`)
|
|
86
|
-
.data(
|
|
93
|
+
.data(validData)
|
|
87
94
|
.join('circle')
|
|
88
95
|
.attr('class', `circle-${sanitizedKey}`)
|
|
89
96
|
.attr('cx', getXPosition)
|
|
@@ -92,9 +99,9 @@ export class Line {
|
|
|
92
99
|
.attr('fill', pointColor)
|
|
93
100
|
.attr('stroke', pointStrokeColor)
|
|
94
101
|
.attr('stroke-width', pointStrokeWidth);
|
|
95
|
-
// Render value labels if enabled
|
|
102
|
+
// Render value labels if enabled (only for valid values)
|
|
96
103
|
if (this.valueLabel?.show) {
|
|
97
|
-
this.renderValueLabels(plotGroup,
|
|
104
|
+
this.renderValueLabels(plotGroup, validData, y, parseValue, theme, getXPosition);
|
|
98
105
|
}
|
|
99
106
|
}
|
|
100
107
|
renderValueLabels(plotGroup, data, y, parseValue, theme, getXPosition) {
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -97,6 +97,7 @@ export type XAxisConfig = {
|
|
|
97
97
|
rotatedLabels?: boolean;
|
|
98
98
|
maxLabelWidth?: number;
|
|
99
99
|
oversizedBehavior?: LabelOversizedBehavior;
|
|
100
|
+
tickFormat?: string | ((value: number) => string) | null;
|
|
100
101
|
};
|
|
101
102
|
export type YAxisConfig = {
|
|
102
103
|
tickFormat?: string | ((value: number) => string) | null;
|
package/x-axis.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare class XAxis implements LayoutAwareComponent {
|
|
|
9
9
|
private readonly fontSize;
|
|
10
10
|
private readonly maxLabelWidth?;
|
|
11
11
|
private readonly oversizedBehavior;
|
|
12
|
+
private readonly tickFormat;
|
|
12
13
|
private wrapLineCount;
|
|
13
14
|
constructor(config?: XAxisConfig);
|
|
14
15
|
/**
|
package/x-axis.js
CHANGED
|
@@ -44,6 +44,13 @@ export class XAxis {
|
|
|
44
44
|
writable: true,
|
|
45
45
|
value: void 0
|
|
46
46
|
});
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
|
+
Object.defineProperty(this, "tickFormat", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
configurable: true,
|
|
51
|
+
writable: true,
|
|
52
|
+
value: void 0
|
|
53
|
+
});
|
|
47
54
|
Object.defineProperty(this, "wrapLineCount", {
|
|
48
55
|
enumerable: true,
|
|
49
56
|
configurable: true,
|
|
@@ -54,6 +61,7 @@ export class XAxis {
|
|
|
54
61
|
this.rotatedLabels = config?.rotatedLabels ?? false;
|
|
55
62
|
this.maxLabelWidth = config?.maxLabelWidth;
|
|
56
63
|
this.oversizedBehavior = config?.oversizedBehavior ?? 'truncate';
|
|
64
|
+
this.tickFormat = config?.tickFormat ?? null;
|
|
57
65
|
}
|
|
58
66
|
/**
|
|
59
67
|
* Returns the space required by the x-axis
|
|
@@ -74,14 +82,24 @@ export class XAxis {
|
|
|
74
82
|
};
|
|
75
83
|
}
|
|
76
84
|
render(svg, x, theme, yPosition) {
|
|
85
|
+
const axisGenerator = axisBottom(x)
|
|
86
|
+
.tickSizeOuter(0)
|
|
87
|
+
.tickSize(0)
|
|
88
|
+
.tickPadding(this.tickPadding);
|
|
89
|
+
// Apply tick formatting if specified
|
|
90
|
+
if (this.tickFormat) {
|
|
91
|
+
if (typeof this.tickFormat === 'function') {
|
|
92
|
+
axisGenerator.tickFormat(this.tickFormat);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
axisGenerator.ticks(5, this.tickFormat);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
77
98
|
const axis = svg
|
|
78
99
|
.append('g')
|
|
79
100
|
.attr('class', 'x-axis')
|
|
80
101
|
.attr('transform', `translate(0,${yPosition})`)
|
|
81
|
-
.call(
|
|
82
|
-
.tickSizeOuter(0)
|
|
83
|
-
.tickSize(0)
|
|
84
|
-
.tickPadding(this.tickPadding))
|
|
102
|
+
.call(axisGenerator)
|
|
85
103
|
.attr('font-size', theme.axis.fontSize)
|
|
86
104
|
.attr('font-family', theme.axis.fontFamily)
|
|
87
105
|
.attr('font-weight', theme.axis.fontWeight || 'normal')
|
package/xy-chart.d.ts
CHANGED
|
@@ -6,8 +6,6 @@ export type XYChartConfig = BaseChartConfig & {
|
|
|
6
6
|
};
|
|
7
7
|
export declare class XYChart extends BaseChart {
|
|
8
8
|
private readonly series;
|
|
9
|
-
private sortedDataCache;
|
|
10
|
-
private xKeyCache;
|
|
11
9
|
private barStackMode;
|
|
12
10
|
private barStackGap;
|
|
13
11
|
constructor(config: XYChartConfig);
|
|
@@ -15,7 +13,6 @@ export declare class XYChart extends BaseChart {
|
|
|
15
13
|
private rerender;
|
|
16
14
|
protected renderChart(): void;
|
|
17
15
|
private getXKey;
|
|
18
|
-
private getSortedData;
|
|
19
16
|
private setupScales;
|
|
20
17
|
private isHorizontalOrientation;
|
|
21
18
|
private createScale;
|
package/xy-chart.js
CHANGED
|
@@ -11,18 +11,6 @@ export class XYChart extends BaseChart {
|
|
|
11
11
|
writable: true,
|
|
12
12
|
value: []
|
|
13
13
|
});
|
|
14
|
-
Object.defineProperty(this, "sortedDataCache", {
|
|
15
|
-
enumerable: true,
|
|
16
|
-
configurable: true,
|
|
17
|
-
writable: true,
|
|
18
|
-
value: null
|
|
19
|
-
});
|
|
20
|
-
Object.defineProperty(this, "xKeyCache", {
|
|
21
|
-
enumerable: true,
|
|
22
|
-
configurable: true,
|
|
23
|
-
writable: true,
|
|
24
|
-
value: null
|
|
25
|
-
});
|
|
26
14
|
Object.defineProperty(this, "barStackMode", {
|
|
27
15
|
enumerable: true,
|
|
28
16
|
configurable: true,
|
|
@@ -97,9 +85,7 @@ export class XYChart extends BaseChart {
|
|
|
97
85
|
if (this.xAxis?.dataKey) {
|
|
98
86
|
ChartValidator.validateDataKey(this.data, this.xAxis.dataKey, 'XAxis');
|
|
99
87
|
}
|
|
100
|
-
// Cache sorted data
|
|
101
88
|
const xKey = this.getXKey();
|
|
102
|
-
const sortedData = this.getSortedData(xKey);
|
|
103
89
|
this.setupScales();
|
|
104
90
|
// Render title if present
|
|
105
91
|
if (this.title) {
|
|
@@ -124,7 +110,7 @@ export class XYChart extends BaseChart {
|
|
|
124
110
|
// Render tooltip
|
|
125
111
|
if (this.tooltip && this.x && this.y) {
|
|
126
112
|
this.tooltip.initialize(this.theme);
|
|
127
|
-
this.tooltip.attachToArea(this.svg,
|
|
113
|
+
this.tooltip.attachToArea(this.svg, this.data, this.series, xKey, this.x, this.y, this.theme, this.plotArea, this.parseValue.bind(this), this.isHorizontalOrientation());
|
|
128
114
|
}
|
|
129
115
|
// Render legend if present
|
|
130
116
|
if (this.legend) {
|
|
@@ -138,16 +124,6 @@ export class XYChart extends BaseChart {
|
|
|
138
124
|
}
|
|
139
125
|
return (Object.keys(this.data[0]).find((key) => !this.series.some((s) => s.dataKey === key)) || 'column');
|
|
140
126
|
}
|
|
141
|
-
getSortedData(xKey) {
|
|
142
|
-
// Return cached data if xKey matches
|
|
143
|
-
if (this.sortedDataCache && this.xKeyCache === xKey) {
|
|
144
|
-
return this.sortedDataCache;
|
|
145
|
-
}
|
|
146
|
-
// Sort and cache
|
|
147
|
-
this.xKeyCache = xKey;
|
|
148
|
-
this.sortedDataCache = [...this.data].sort((a, b) => String(a[xKey]).localeCompare(String(b[xKey])));
|
|
149
|
-
return this.sortedDataCache;
|
|
150
|
-
}
|
|
151
127
|
setupScales() {
|
|
152
128
|
const xKey = this.getXKey();
|
|
153
129
|
const isHorizontal = this.isHorizontalOrientation();
|
|
@@ -285,7 +261,6 @@ export class XYChart extends BaseChart {
|
|
|
285
261
|
if (!this.plotGroup || !this.x || !this.y)
|
|
286
262
|
return;
|
|
287
263
|
const xKey = this.getXKey();
|
|
288
|
-
const sortedData = this.getSortedData(xKey);
|
|
289
264
|
const isHorizontal = this.isHorizontalOrientation();
|
|
290
265
|
// For horizontal bars, the category scale is on Y (user's X config becomes Y)
|
|
291
266
|
// For vertical bars, the category scale is on X (user's X config stays X)
|
|
@@ -299,7 +274,7 @@ export class XYChart extends BaseChart {
|
|
|
299
274
|
// Get only bar series for stacking calculations
|
|
300
275
|
const barSeries = visibleSeries.filter((s) => s.type === 'bar');
|
|
301
276
|
// Compute stacking data for bar charts
|
|
302
|
-
const { cumulativeDataBySeriesIndex, totalData } = this.computeStackingData(
|
|
277
|
+
const { cumulativeDataBySeriesIndex, totalData } = this.computeStackingData(this.data, xKey, barSeries);
|
|
303
278
|
visibleSeries.forEach((series) => {
|
|
304
279
|
if (series.type === 'bar') {
|
|
305
280
|
const barIndex = barSeries.indexOf(series);
|
|
@@ -311,10 +286,10 @@ export class XYChart extends BaseChart {
|
|
|
311
286
|
totalData,
|
|
312
287
|
gap: this.barStackGap,
|
|
313
288
|
};
|
|
314
|
-
series.render(this.plotGroup,
|
|
289
|
+
series.render(this.plotGroup, this.data, xKey, this.x, this.y, this.parseValue, categoryScaleType, this.theme, stackingContext);
|
|
315
290
|
}
|
|
316
291
|
else {
|
|
317
|
-
series.render(this.plotGroup,
|
|
292
|
+
series.render(this.plotGroup, this.data, xKey, this.x, this.y, this.parseValue, categoryScaleType, this.theme);
|
|
318
293
|
}
|
|
319
294
|
});
|
|
320
295
|
}
|