@internetstiftelsen/charts 0.7.0 → 0.7.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 +36 -0
- package/base-chart.js +2 -5
- package/package.json +10 -3
- package/title.d.ts +1 -0
- package/title.js +18 -0
- package/types.d.ts +3 -0
- package/x-axis.d.ts +3 -0
- package/x-axis.js +47 -2
- package/y-axis.d.ts +1 -0
- package/y-axis.js +18 -0
package/README.md
CHANGED
|
@@ -20,6 +20,42 @@ A framework-agnostic, composable charting library built on D3.js with TypeScript
|
|
|
20
20
|
npm install @internetstiftelsen/charts
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
## Local Development
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm dev
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Runs the interactive demo app (`index.html`) with sidebar controls and
|
|
30
|
+
Chart/Data/Showcase tabs.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm dev:docs
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Runs the marketing landing page (`docs.html`) built on
|
|
37
|
+
`@internetstiftelsen/styleguide`.
|
|
38
|
+
|
|
39
|
+
## Build Targets
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pnpm build
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Builds the publishable chart library output into `dist`.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm build:docs
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Builds the static marketing site into `dist-docs` (used for Pages deploys).
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pnpm build:demo
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Builds the demo app using the default Vite config.
|
|
58
|
+
|
|
23
59
|
## Quick Start
|
|
24
60
|
|
|
25
61
|
```javascript
|
package/base-chart.js
CHANGED
|
@@ -377,15 +377,12 @@ export class BaseChart {
|
|
|
377
377
|
const matchesIndex = match.index === undefined || match.index === index;
|
|
378
378
|
const matchesType = match.type === undefined || match.type === component.type;
|
|
379
379
|
const matchesDataKey = match.dataKey === undefined ||
|
|
380
|
-
(typeof dataKey === 'string' &&
|
|
381
|
-
dataKey === match.dataKey);
|
|
380
|
+
(typeof dataKey === 'string' && dataKey === match.dataKey);
|
|
382
381
|
if (!matchesIndex || !matchesType || !matchesDataKey) {
|
|
383
382
|
return;
|
|
384
383
|
}
|
|
385
384
|
const existing = componentOverrides.get(component);
|
|
386
|
-
componentOverrides.set(component, existing
|
|
387
|
-
? mergeDeep(existing, override)
|
|
388
|
-
: { ...override });
|
|
385
|
+
componentOverrides.set(component, existing ? mergeDeep(existing, override) : { ...override });
|
|
389
386
|
});
|
|
390
387
|
});
|
|
391
388
|
return {
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.7.
|
|
2
|
+
"version": "0.7.1",
|
|
3
3
|
"name": "@internetstiftelsen/charts",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
@@ -16,13 +16,17 @@
|
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
18
|
"dev": "vite",
|
|
19
|
-
"
|
|
19
|
+
"dev:docs": "vite --config vite.docs.config.ts --open",
|
|
20
|
+
"build": "tsc --project tsconfig.lib.json && tsc-alias --project tsconfig.lib.json",
|
|
21
|
+
"build:demo": "tsc -b && vite build",
|
|
22
|
+
"build:docs": "vite build --config vite.docs.config.ts && cp -R docs dist-docs/docs",
|
|
20
23
|
"lint": "eslint .",
|
|
21
24
|
"format": "prettier --write ./src",
|
|
22
25
|
"preview": "vite preview",
|
|
26
|
+
"preview:docs": "vite preview --config vite.docs.config.ts",
|
|
23
27
|
"test": "vitest",
|
|
24
28
|
"test:run": "vitest run",
|
|
25
|
-
"build:lib": "
|
|
29
|
+
"build:lib": "npm run build",
|
|
26
30
|
"prepub": "rm -rf dist && npm run build:lib && cp package.json dist && cp README.md dist",
|
|
27
31
|
"pub": "npm run prepub && cd dist && npm publish --access public"
|
|
28
32
|
},
|
|
@@ -49,6 +53,8 @@
|
|
|
49
53
|
},
|
|
50
54
|
"devDependencies": {
|
|
51
55
|
"@eslint/js": "^9.39.2",
|
|
56
|
+
"@internetstiftelsen/styleguide": "^5.1.23",
|
|
57
|
+
"@speed-highlight/core": "^1.2.14",
|
|
52
58
|
"@tailwindcss/vite": "^4.1.18",
|
|
53
59
|
"@testing-library/dom": "^10.4.1",
|
|
54
60
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -63,6 +69,7 @@
|
|
|
63
69
|
"globals": "^16.5.0",
|
|
64
70
|
"jsdom": "^27.4.0",
|
|
65
71
|
"prettier": "3.6.2",
|
|
72
|
+
"sass": "^1.97.3",
|
|
66
73
|
"tsc-alias": "^1.8.16",
|
|
67
74
|
"tw-animate-css": "^1.4.0",
|
|
68
75
|
"typescript": "~5.9.3",
|
package/title.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { TitleConfig, ChartTheme, ExportHooks, TitleConfigBase } from './ty
|
|
|
3
3
|
import type { LayoutAwareComponent, ComponentSpace } from './chart-interface.js';
|
|
4
4
|
export declare class Title implements LayoutAwareComponent<TitleConfigBase> {
|
|
5
5
|
readonly type: "title";
|
|
6
|
+
readonly display: boolean;
|
|
6
7
|
readonly text: string;
|
|
7
8
|
readonly exportHooks?: ExportHooks<TitleConfigBase>;
|
|
8
9
|
private readonly fontSize;
|
package/title.js
CHANGED
|
@@ -7,6 +7,12 @@ export class Title {
|
|
|
7
7
|
writable: true,
|
|
8
8
|
value: 'title'
|
|
9
9
|
});
|
|
10
|
+
Object.defineProperty(this, "display", {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true,
|
|
14
|
+
value: void 0
|
|
15
|
+
});
|
|
10
16
|
Object.defineProperty(this, "text", {
|
|
11
17
|
enumerable: true,
|
|
12
18
|
configurable: true,
|
|
@@ -55,6 +61,7 @@ export class Title {
|
|
|
55
61
|
writable: true,
|
|
56
62
|
value: void 0
|
|
57
63
|
});
|
|
64
|
+
this.display = config.display ?? true;
|
|
58
65
|
this.text = config.text;
|
|
59
66
|
this.fontSize = config.fontSize ?? 18;
|
|
60
67
|
this.fontWeight = config.fontWeight ?? 'bold';
|
|
@@ -66,6 +73,7 @@ export class Title {
|
|
|
66
73
|
}
|
|
67
74
|
getExportConfig() {
|
|
68
75
|
return {
|
|
76
|
+
display: this.display,
|
|
69
77
|
text: this.text,
|
|
70
78
|
fontSize: this.fontSize,
|
|
71
79
|
fontWeight: this.fontWeight,
|
|
@@ -86,6 +94,13 @@ export class Title {
|
|
|
86
94
|
* Returns the space required by the title
|
|
87
95
|
*/
|
|
88
96
|
getRequiredSpace() {
|
|
97
|
+
if (!this.display) {
|
|
98
|
+
return {
|
|
99
|
+
width: 0,
|
|
100
|
+
height: 0,
|
|
101
|
+
position: 'top',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
89
104
|
return {
|
|
90
105
|
width: 0, // Title spans full width
|
|
91
106
|
height: this.marginTop + this.fontSize + this.marginBottom,
|
|
@@ -93,6 +108,9 @@ export class Title {
|
|
|
93
108
|
};
|
|
94
109
|
}
|
|
95
110
|
render(svg, theme, width, x = 0, y = 0) {
|
|
111
|
+
if (!this.display) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
96
114
|
const titleGroup = svg
|
|
97
115
|
.append('g')
|
|
98
116
|
.attr('class', 'title')
|
package/types.d.ts
CHANGED
|
@@ -218,6 +218,7 @@ export declare function getSeriesColor(series: {
|
|
|
218
218
|
}): string;
|
|
219
219
|
export type LabelOversizedBehavior = 'truncate' | 'wrap' | 'hide';
|
|
220
220
|
export type XAxisConfigBase = {
|
|
221
|
+
display?: boolean;
|
|
221
222
|
dataKey?: string;
|
|
222
223
|
labelKey?: string;
|
|
223
224
|
groupLabelKey?: string;
|
|
@@ -235,6 +236,7 @@ export type XAxisConfig = XAxisConfigBase & {
|
|
|
235
236
|
exportHooks?: ExportHooks<XAxisConfigBase>;
|
|
236
237
|
};
|
|
237
238
|
export type YAxisConfigBase = {
|
|
239
|
+
display?: boolean;
|
|
238
240
|
tickFormat?: string | ((value: number) => string) | null;
|
|
239
241
|
rotatedLabels?: boolean;
|
|
240
242
|
maxLabelWidth?: number;
|
|
@@ -287,6 +289,7 @@ export type LegendItem = {
|
|
|
287
289
|
visible: boolean;
|
|
288
290
|
};
|
|
289
291
|
export type TitleConfigBase = {
|
|
292
|
+
display?: boolean;
|
|
290
293
|
text: string;
|
|
291
294
|
fontSize?: number;
|
|
292
295
|
fontWeight?: string;
|
package/x-axis.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { XAxisConfig, ChartTheme, D3Scale, DataItem, ExportHooks, XAxisConf
|
|
|
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
|
+
readonly display: boolean;
|
|
6
7
|
readonly dataKey?: string;
|
|
7
8
|
readonly labelKey?: string;
|
|
8
9
|
readonly groupLabelKey?: string;
|
|
@@ -16,6 +17,7 @@ export declare class XAxis implements LayoutAwareComponent<XAxisConfigBase> {
|
|
|
16
17
|
private readonly tickFormat;
|
|
17
18
|
private wrapLineCount;
|
|
18
19
|
private estimatedHeight;
|
|
20
|
+
private estimatedTickLabelVerticalFootprint;
|
|
19
21
|
private readonly autoHideOverlapping;
|
|
20
22
|
private readonly minLabelGap;
|
|
21
23
|
private readonly preserveEndLabels;
|
|
@@ -30,6 +32,7 @@ export declare class XAxis implements LayoutAwareComponent<XAxisConfigBase> {
|
|
|
30
32
|
getRequiredSpace(): ComponentSpace;
|
|
31
33
|
estimateLayoutSpace(labels: unknown[], theme: ChartTheme, svg: SVGSVGElement): void;
|
|
32
34
|
clearEstimatedSpace(): void;
|
|
35
|
+
private getTickLabelVerticalFootprint;
|
|
33
36
|
render(svg: Selection<SVGSVGElement, undefined, null, undefined>, x: D3Scale, theme: ChartTheme, yPosition: number, data?: DataItem[]): void;
|
|
34
37
|
private buildLabelLookup;
|
|
35
38
|
private renderGroupLabels;
|
package/x-axis.js
CHANGED
|
@@ -25,6 +25,12 @@ export class XAxis {
|
|
|
25
25
|
writable: true,
|
|
26
26
|
value: 'xAxis'
|
|
27
27
|
});
|
|
28
|
+
Object.defineProperty(this, "display", {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true,
|
|
32
|
+
value: void 0
|
|
33
|
+
});
|
|
28
34
|
Object.defineProperty(this, "dataKey", {
|
|
29
35
|
enumerable: true,
|
|
30
36
|
configurable: true,
|
|
@@ -104,6 +110,12 @@ export class XAxis {
|
|
|
104
110
|
writable: true,
|
|
105
111
|
value: null
|
|
106
112
|
});
|
|
113
|
+
Object.defineProperty(this, "estimatedTickLabelVerticalFootprint", {
|
|
114
|
+
enumerable: true,
|
|
115
|
+
configurable: true,
|
|
116
|
+
writable: true,
|
|
117
|
+
value: null
|
|
118
|
+
});
|
|
107
119
|
Object.defineProperty(this, "autoHideOverlapping", {
|
|
108
120
|
enumerable: true,
|
|
109
121
|
configurable: true,
|
|
@@ -128,6 +140,7 @@ export class XAxis {
|
|
|
128
140
|
writable: true,
|
|
129
141
|
value: void 0
|
|
130
142
|
});
|
|
143
|
+
this.display = config?.display ?? true;
|
|
131
144
|
this.dataKey = config?.dataKey;
|
|
132
145
|
this.labelKey = config?.labelKey;
|
|
133
146
|
this.groupLabelKey = config?.groupLabelKey;
|
|
@@ -144,6 +157,7 @@ export class XAxis {
|
|
|
144
157
|
}
|
|
145
158
|
getExportConfig() {
|
|
146
159
|
return {
|
|
160
|
+
display: this.display,
|
|
147
161
|
dataKey: this.dataKey,
|
|
148
162
|
labelKey: this.labelKey,
|
|
149
163
|
groupLabelKey: this.groupLabelKey,
|
|
@@ -169,6 +183,13 @@ export class XAxis {
|
|
|
169
183
|
* Returns the space required by the x-axis
|
|
170
184
|
*/
|
|
171
185
|
getRequiredSpace() {
|
|
186
|
+
if (!this.display) {
|
|
187
|
+
return {
|
|
188
|
+
width: 0,
|
|
189
|
+
height: 0,
|
|
190
|
+
position: 'bottom',
|
|
191
|
+
};
|
|
192
|
+
}
|
|
172
193
|
if (this.estimatedHeight !== null) {
|
|
173
194
|
return {
|
|
174
195
|
width: 0,
|
|
@@ -198,6 +219,8 @@ export class XAxis {
|
|
|
198
219
|
estimateLayoutSpace(labels, theme, svg) {
|
|
199
220
|
if (!labels.length) {
|
|
200
221
|
this.estimatedHeight = null;
|
|
222
|
+
this.estimatedTickLabelVerticalFootprint = null;
|
|
223
|
+
this.wrapLineCount = 1;
|
|
201
224
|
return;
|
|
202
225
|
}
|
|
203
226
|
const parsedFontSize = typeof theme.axis.fontSize === 'string'
|
|
@@ -232,9 +255,13 @@ export class XAxis {
|
|
|
232
255
|
if (this.rotatedLabels) {
|
|
233
256
|
const radians = Math.PI / 4;
|
|
234
257
|
const verticalFootprint = Math.sin(radians) * maxWidth + Math.cos(radians) * textHeight;
|
|
258
|
+
this.estimatedTickLabelVerticalFootprint = verticalFootprint;
|
|
235
259
|
this.estimatedHeight = this.tickPadding + verticalFootprint + 5;
|
|
236
260
|
}
|
|
237
261
|
else {
|
|
262
|
+
const wrappedExtraHeight = Math.max(0, maxLines - 1) * lineHeight;
|
|
263
|
+
this.estimatedTickLabelVerticalFootprint =
|
|
264
|
+
this.fontSize + wrappedExtraHeight;
|
|
238
265
|
this.estimatedHeight = this.tickPadding + textHeight + 5;
|
|
239
266
|
}
|
|
240
267
|
if (this.showGroupLabels) {
|
|
@@ -242,12 +269,29 @@ export class XAxis {
|
|
|
242
269
|
this.estimatedHeight +=
|
|
243
270
|
this.groupLabelGap + groupLabelStyle.fontSize + 5;
|
|
244
271
|
}
|
|
245
|
-
this.wrapLineCount =
|
|
272
|
+
this.wrapLineCount = maxLines;
|
|
246
273
|
}
|
|
247
274
|
clearEstimatedSpace() {
|
|
248
275
|
this.estimatedHeight = null;
|
|
276
|
+
this.estimatedTickLabelVerticalFootprint = null;
|
|
277
|
+
}
|
|
278
|
+
getTickLabelVerticalFootprint() {
|
|
279
|
+
if (this.estimatedTickLabelVerticalFootprint !== null) {
|
|
280
|
+
return this.estimatedTickLabelVerticalFootprint;
|
|
281
|
+
}
|
|
282
|
+
if (this.rotatedLabels) {
|
|
283
|
+
// Fallback to the same rough factor used by getRequiredSpace().
|
|
284
|
+
const baseHeight = this.tickPadding + this.fontSize + 5;
|
|
285
|
+
return Math.max(baseHeight * 2.5 - this.tickPadding - 5, 0);
|
|
286
|
+
}
|
|
287
|
+
const lineHeight = this.fontSize * 1.2;
|
|
288
|
+
const wrappedExtraHeight = Math.max(0, this.wrapLineCount - 1) * lineHeight;
|
|
289
|
+
return this.fontSize + wrappedExtraHeight;
|
|
249
290
|
}
|
|
250
291
|
render(svg, x, theme, yPosition, data = []) {
|
|
292
|
+
if (!this.display) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
251
295
|
const labelLookup = this.buildLabelLookup(data);
|
|
252
296
|
const axisGenerator = axisBottom(x)
|
|
253
297
|
.tickSizeOuter(0)
|
|
@@ -321,7 +365,8 @@ export class XAxis {
|
|
|
321
365
|
if (groupRanges.length === 0) {
|
|
322
366
|
return;
|
|
323
367
|
}
|
|
324
|
-
const
|
|
368
|
+
const tickLabelVerticalFootprint = this.getTickLabelVerticalFootprint();
|
|
369
|
+
const yOffset = this.tickPadding + tickLabelVerticalFootprint + this.groupLabelGap;
|
|
325
370
|
const groupLabelStyle = this.resolveGroupLabelStyle(theme);
|
|
326
371
|
const groupLayer = svg
|
|
327
372
|
.append('g')
|
package/y-axis.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { ChartTheme, YAxisConfig, D3Scale, ExportHooks, YAxisConfigBase } f
|
|
|
3
3
|
import type { LayoutAwareComponent, ComponentSpace } from './chart-interface.js';
|
|
4
4
|
export declare class YAxis implements LayoutAwareComponent<YAxisConfigBase> {
|
|
5
5
|
readonly type: "yAxis";
|
|
6
|
+
readonly display: boolean;
|
|
6
7
|
private readonly tickPadding;
|
|
7
8
|
private readonly fontSize;
|
|
8
9
|
private readonly maxLabelWidth;
|
package/y-axis.js
CHANGED
|
@@ -8,6 +8,12 @@ export class YAxis {
|
|
|
8
8
|
writable: true,
|
|
9
9
|
value: 'yAxis'
|
|
10
10
|
});
|
|
11
|
+
Object.defineProperty(this, "display", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: void 0
|
|
16
|
+
});
|
|
11
17
|
Object.defineProperty(this, "tickPadding", {
|
|
12
18
|
enumerable: true,
|
|
13
19
|
configurable: true,
|
|
@@ -51,6 +57,7 @@ export class YAxis {
|
|
|
51
57
|
writable: true,
|
|
52
58
|
value: void 0
|
|
53
59
|
});
|
|
60
|
+
this.display = config?.display ?? true;
|
|
54
61
|
this.tickFormat = config?.tickFormat ?? null;
|
|
55
62
|
this.rotatedLabels = config?.rotatedLabels ?? false;
|
|
56
63
|
this.maxLabelWidth = config?.maxLabelWidth ?? 40; // Default 40 for backward compatibility
|
|
@@ -59,6 +66,7 @@ export class YAxis {
|
|
|
59
66
|
}
|
|
60
67
|
getExportConfig() {
|
|
61
68
|
return {
|
|
69
|
+
display: this.display,
|
|
62
70
|
tickFormat: this.tickFormat,
|
|
63
71
|
rotatedLabels: this.rotatedLabels,
|
|
64
72
|
maxLabelWidth: this.maxLabelWidth,
|
|
@@ -76,6 +84,13 @@ export class YAxis {
|
|
|
76
84
|
* Returns the space required by the y-axis
|
|
77
85
|
*/
|
|
78
86
|
getRequiredSpace() {
|
|
87
|
+
if (!this.display) {
|
|
88
|
+
return {
|
|
89
|
+
width: 0,
|
|
90
|
+
height: 0,
|
|
91
|
+
position: 'left',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
79
94
|
// Width = max label width + tick padding
|
|
80
95
|
// Rotated labels need less width (cos(45°) ≈ 0.7 of horizontal width)
|
|
81
96
|
const baseWidth = this.maxLabelWidth + this.tickPadding;
|
|
@@ -87,6 +102,9 @@ export class YAxis {
|
|
|
87
102
|
};
|
|
88
103
|
}
|
|
89
104
|
render(svg, y, theme, xPosition) {
|
|
105
|
+
if (!this.display) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
90
108
|
const axis = axisLeft(y).tickSize(0).tickPadding(this.tickPadding);
|
|
91
109
|
// Apply tick formatting if specified
|
|
92
110
|
if (this.tickFormat) {
|