@internetstiftelsen/charts 0.14.3 → 0.15.0
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 +3 -4
- package/dist/base-chart.d.ts +1 -2
- package/dist/base-chart.js +2 -21
- package/dist/chart-group.d.ts +1 -2
- package/dist/chart-group.js +1 -16
- package/dist/types.d.ts +1 -2
- package/docs/chart-group.md +0 -1
- package/docs/components.md +3 -6
- package/package.json +17 -18
- package/dist/export-pdf.d.ts +0 -8
- package/dist/export-pdf.js +0 -67
package/README.md
CHANGED
|
@@ -385,17 +385,16 @@ their own centers on initial render and `chart.update(...)`.
|
|
|
385
385
|
|
|
386
386
|
## Export
|
|
387
387
|
|
|
388
|
-
`chart.export()` supports `svg`, `json`, `csv`, `xlsx`, `png`,
|
|
388
|
+
`chart.export()` supports `svg`, `json`, `csv`, `xlsx`, `png`, and `jpg`.
|
|
389
389
|
|
|
390
390
|
```javascript
|
|
391
391
|
await chart.export('png', { download: true });
|
|
392
392
|
await chart.export('csv', { download: true, delimiter: ';' });
|
|
393
393
|
await chart.export('xlsx', { download: true, sheetName: 'Data' });
|
|
394
|
-
await chart.export('pdf', { download: true, pdfMargin: 16 });
|
|
395
394
|
```
|
|
396
395
|
|
|
397
|
-
`xlsx`
|
|
398
|
-
|
|
396
|
+
`xlsx` is lazy-loaded and requires the optional `write-excel-file` dependency
|
|
397
|
+
only when that format is used.
|
|
399
398
|
|
|
400
399
|
## Import
|
|
401
400
|
|
package/dist/base-chart.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { Legend } from './legend.js';
|
|
|
9
9
|
import type { Title } from './title.js';
|
|
10
10
|
import { LayoutManager, type PlotAreaBounds } from './layout-manager.js';
|
|
11
11
|
import { LegendStateController } from './legend-state.js';
|
|
12
|
-
type VisualExportFormat = 'svg' | 'png' | 'jpg'
|
|
12
|
+
type VisualExportFormat = 'svg' | 'png' | 'jpg';
|
|
13
13
|
type RenderDimensions = {
|
|
14
14
|
width: number;
|
|
15
15
|
height: number;
|
|
@@ -262,7 +262,6 @@ export declare abstract class BaseChart {
|
|
|
262
262
|
private exportSize;
|
|
263
263
|
private exportXLSX;
|
|
264
264
|
private exportImage;
|
|
265
|
-
private exportPDF;
|
|
266
265
|
protected exportSVG(options?: ExportOptions, formatForHooks?: VisualExportFormat): Promise<string>;
|
|
267
266
|
private requireRenderedSvg;
|
|
268
267
|
private resolveExportContext;
|
package/dist/base-chart.js
CHANGED
|
@@ -5,7 +5,6 @@ import { LayoutManager } from './layout-manager.js';
|
|
|
5
5
|
import { serializeCSV } from './export-tabular.js';
|
|
6
6
|
import { exportRasterBlob } from './export-image.js';
|
|
7
7
|
import { exportXLSXBlob } from './export-xlsx.js';
|
|
8
|
-
import { exportPDFBlob } from './export-pdf.js';
|
|
9
8
|
import { normalizeChartData } from './grouped-data.js';
|
|
10
9
|
import { LegendStateController } from './legend-state.js';
|
|
11
10
|
import { mergeDeep } from './utils.js';
|
|
@@ -1251,7 +1250,7 @@ export class BaseChart {
|
|
|
1251
1250
|
content = await this.exportImage(format, options);
|
|
1252
1251
|
}
|
|
1253
1252
|
else {
|
|
1254
|
-
|
|
1253
|
+
throw new Error(`Unsupported export format: ${format}`);
|
|
1255
1254
|
}
|
|
1256
1255
|
if (options?.download) {
|
|
1257
1256
|
this.downloadContent(content, format, options);
|
|
@@ -1296,7 +1295,7 @@ export class BaseChart {
|
|
|
1296
1295
|
if (format === 'jpg') {
|
|
1297
1296
|
return 'image/jpeg';
|
|
1298
1297
|
}
|
|
1299
|
-
|
|
1298
|
+
throw new Error(`Unsupported export format: ${format}`);
|
|
1300
1299
|
}
|
|
1301
1300
|
exportCSV(options) {
|
|
1302
1301
|
return serializeCSV(this.sourceData, options);
|
|
@@ -1325,24 +1324,6 @@ export class BaseChart {
|
|
|
1325
1324
|
jpegQuality: options?.jpegQuality ?? 0.92,
|
|
1326
1325
|
});
|
|
1327
1326
|
}
|
|
1328
|
-
async exportPDF(options) {
|
|
1329
|
-
const { width, height } = this.exportSize(options);
|
|
1330
|
-
const svg = await this.exportSVG(options, 'pdf');
|
|
1331
|
-
const pngBlob = await exportRasterBlob({
|
|
1332
|
-
format: 'png',
|
|
1333
|
-
svg,
|
|
1334
|
-
width,
|
|
1335
|
-
height,
|
|
1336
|
-
pixelRatio: options?.pixelRatio ?? 1,
|
|
1337
|
-
backgroundColor: options?.backgroundColor ?? '#ffffff',
|
|
1338
|
-
jpegQuality: options?.jpegQuality ?? 0.92,
|
|
1339
|
-
});
|
|
1340
|
-
return exportPDFBlob(pngBlob, {
|
|
1341
|
-
width,
|
|
1342
|
-
height,
|
|
1343
|
-
margin: options?.pdfMargin ?? 0,
|
|
1344
|
-
});
|
|
1345
|
-
}
|
|
1346
1327
|
async exportSVG(options, formatForHooks = 'svg') {
|
|
1347
1328
|
const liveSvg = this.requireRenderedSvg();
|
|
1348
1329
|
await this.whenReady();
|
package/dist/chart-group.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export type ChartGroupChartOptions = {
|
|
|
40
40
|
order?: number;
|
|
41
41
|
responsive?: ChartGroupItemResponsiveConfig;
|
|
42
42
|
};
|
|
43
|
-
export type ChartGroupExportFormat = 'svg' | 'png' | 'jpg'
|
|
43
|
+
export type ChartGroupExportFormat = 'svg' | 'png' | 'jpg';
|
|
44
44
|
export declare class ChartGroup {
|
|
45
45
|
private readonly cols;
|
|
46
46
|
private readonly gap;
|
|
@@ -127,7 +127,6 @@ export declare class ChartGroup {
|
|
|
127
127
|
private requireRenderedContainer;
|
|
128
128
|
private resolveExportContent;
|
|
129
129
|
private createRasterExportOptions;
|
|
130
|
-
private exportPdfContent;
|
|
131
130
|
private resolveExportLayoutState;
|
|
132
131
|
private createExportLayoutState;
|
|
133
132
|
private createExportRenderContext;
|
package/dist/chart-group.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { create } from 'd3';
|
|
2
2
|
import { exportRasterBlob } from './export-image.js';
|
|
3
|
-
import { exportPDFBlob } from './export-pdf.js';
|
|
4
3
|
import { LegendStateController } from './legend-state.js';
|
|
5
4
|
import { DEFAULT_CHART_HEIGHT, DEFAULT_CHART_WIDTH, defaultTheme, } from './theme.js';
|
|
6
5
|
import { ChartValidator } from './validation.js';
|
|
@@ -988,9 +987,6 @@ export class ChartGroup {
|
|
|
988
987
|
return svg;
|
|
989
988
|
}
|
|
990
989
|
const rasterOptions = this.createRasterExportOptions(svg, width, height, options);
|
|
991
|
-
if (format === 'pdf') {
|
|
992
|
-
return this.exportPdfContent(rasterOptions, width, height, options);
|
|
993
|
-
}
|
|
994
990
|
return exportRasterBlob({
|
|
995
991
|
format,
|
|
996
992
|
...rasterOptions,
|
|
@@ -1006,17 +1002,6 @@ export class ChartGroup {
|
|
|
1006
1002
|
jpegQuality: options?.jpegQuality ?? 0.92,
|
|
1007
1003
|
};
|
|
1008
1004
|
}
|
|
1009
|
-
async exportPdfContent(rasterOptions, width, height, options) {
|
|
1010
|
-
const pngBlob = await exportRasterBlob({
|
|
1011
|
-
format: 'png',
|
|
1012
|
-
...rasterOptions,
|
|
1013
|
-
});
|
|
1014
|
-
return exportPDFBlob(pngBlob, {
|
|
1015
|
-
width,
|
|
1016
|
-
height,
|
|
1017
|
-
margin: options?.pdfMargin ?? 0,
|
|
1018
|
-
});
|
|
1019
|
-
}
|
|
1020
1005
|
resolveExportLayoutState(width, context) {
|
|
1021
1006
|
const textComponents = this.textComponents
|
|
1022
1007
|
.map((component) => {
|
|
@@ -1327,6 +1312,6 @@ export class ChartGroup {
|
|
|
1327
1312
|
if (format === 'jpg') {
|
|
1328
1313
|
return 'image/jpeg';
|
|
1329
1314
|
}
|
|
1330
|
-
|
|
1315
|
+
throw new Error(`Unsupported export format: ${format}`);
|
|
1331
1316
|
}
|
|
1332
1317
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type GroupedDataGroup = {
|
|
|
9
9
|
data: DataItem[];
|
|
10
10
|
};
|
|
11
11
|
export type ChartData = DataItem[] | GroupedDataGroup[];
|
|
12
|
-
export type ExportFormat = 'svg' | 'json' | 'csv' | 'xlsx' | 'png' | 'jpg'
|
|
12
|
+
export type ExportFormat = 'svg' | 'json' | 'csv' | 'xlsx' | 'png' | 'jpg';
|
|
13
13
|
export type ExportOptions = {
|
|
14
14
|
download?: boolean;
|
|
15
15
|
filename?: string;
|
|
@@ -21,7 +21,6 @@ export type ExportOptions = {
|
|
|
21
21
|
pixelRatio?: number;
|
|
22
22
|
backgroundColor?: string;
|
|
23
23
|
sheetName?: string;
|
|
24
|
-
pdfMargin?: number;
|
|
25
24
|
};
|
|
26
25
|
export type ExportRenderContext = {
|
|
27
26
|
format: ExportFormat;
|
package/docs/chart-group.md
CHANGED
package/docs/components.md
CHANGED
|
@@ -344,7 +344,7 @@ new Text({
|
|
|
344
344
|
|
|
345
345
|
Components can provide `exportHooks` to adjust export-only settings or mutate
|
|
346
346
|
the exported SVG right before serialization. Hooks run only for visual exports
|
|
347
|
-
(`svg`, `png`, `jpg
|
|
347
|
+
(`svg`, `png`, `jpg`) and never touch the live chart instance. Use
|
|
348
348
|
`beforeRender` to return config overrides and `before` to mutate the export
|
|
349
349
|
SVG.
|
|
350
350
|
|
|
@@ -403,24 +403,21 @@ breakpoints in chart constructor config (see
|
|
|
403
403
|
- `xlsx` (lazy-loads optional `write-excel-file`)
|
|
404
404
|
- `png`
|
|
405
405
|
- `jpg`
|
|
406
|
-
- `pdf` (lazy-loads optional `jspdf`)
|
|
407
406
|
|
|
408
407
|
```typescript
|
|
409
408
|
await chart.export('png', { download: true });
|
|
410
409
|
await chart.export('csv', { download: true, delimiter: ';' });
|
|
411
410
|
await chart.export('xlsx', { download: true, sheetName: 'Data' });
|
|
412
|
-
await chart.export('pdf', { download: true, pdfMargin: 16 });
|
|
413
411
|
```
|
|
414
412
|
|
|
415
413
|
Export option highlights:
|
|
416
414
|
|
|
417
415
|
- `columns`: choose specific columns for `csv` and `xlsx`
|
|
418
416
|
- `delimiter`: CSV delimiter (default `,`)
|
|
419
|
-
- `pixelRatio`: render scale for `png
|
|
420
|
-
- `backgroundColor`: defaults transparent for `png
|
|
417
|
+
- `pixelRatio`: render scale for `png` and `jpg`
|
|
418
|
+
- `backgroundColor`: defaults transparent for `png` and white for `jpg`
|
|
421
419
|
- `jpegQuality`: JPEG quality for `jpg` (default `0.92`)
|
|
422
420
|
- `sheetName`: sheet name for `xlsx` (default `Sheet1`)
|
|
423
|
-
- `pdfMargin`: page margin for `pdf` (default `0`)
|
|
424
421
|
|
|
425
422
|
`json` export returns only the chart data payload
|
|
426
423
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.15.0",
|
|
3
3
|
"name": "@internetstiftelsen/charts",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
@@ -38,48 +38,47 @@
|
|
|
38
38
|
"d3-cloud": "^1.2.9"
|
|
39
39
|
},
|
|
40
40
|
"optionalDependencies": {
|
|
41
|
-
"
|
|
42
|
-
"write-excel-file": "^4.0.7"
|
|
41
|
+
"write-excel-file": "^4.1.1"
|
|
43
42
|
},
|
|
44
43
|
"devDependencies": {
|
|
45
44
|
"@eslint/js": "^10.0.1",
|
|
46
45
|
"@handsontable/react-wrapper": "^17.1.0",
|
|
47
46
|
"@internetstiftelsen/styleguide": "^5.1.27",
|
|
48
|
-
"@radix-ui/react-label": "^2.1.
|
|
49
|
-
"@radix-ui/react-select": "^2.
|
|
50
|
-
"@radix-ui/react-switch": "^1.
|
|
51
|
-
"@radix-ui/react-tabs": "^1.1.
|
|
52
|
-
"@speed-highlight/core": "^1.2.
|
|
53
|
-
"@tailwindcss/vite": "^4.3.
|
|
47
|
+
"@radix-ui/react-label": "^2.1.9",
|
|
48
|
+
"@radix-ui/react-select": "^2.3.0",
|
|
49
|
+
"@radix-ui/react-switch": "^1.3.0",
|
|
50
|
+
"@radix-ui/react-tabs": "^1.1.14",
|
|
51
|
+
"@speed-highlight/core": "^1.2.17",
|
|
52
|
+
"@tailwindcss/vite": "^4.3.1",
|
|
54
53
|
"@testing-library/dom": "^10.4.1",
|
|
55
54
|
"@testing-library/jest-dom": "^6.9.1",
|
|
56
55
|
"@testing-library/react": "^16.3.2",
|
|
57
56
|
"@types/d3": "^7.4.3",
|
|
58
57
|
"@types/d3-cloud": "^1.2.9",
|
|
59
|
-
"@types/node": "^25.9.
|
|
60
|
-
"@types/react": "^19.2.
|
|
58
|
+
"@types/node": "^25.9.3",
|
|
59
|
+
"@types/react": "^19.2.17",
|
|
61
60
|
"@types/react-dom": "^19.2.3",
|
|
62
61
|
"@vitejs/plugin-react-swc": "^4.3.1",
|
|
63
62
|
"class-variance-authority": "^0.7.1",
|
|
64
63
|
"clsx": "^2.1.1",
|
|
65
|
-
"eslint": "^10.
|
|
64
|
+
"eslint": "^10.5.0",
|
|
66
65
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
67
66
|
"eslint-plugin-react-refresh": "^0.5.2",
|
|
68
67
|
"globals": "^17.6.0",
|
|
69
68
|
"handsontable": "^17.1.0",
|
|
70
69
|
"jsdom": "^29.1.1",
|
|
71
|
-
"lucide-react": "^1.
|
|
72
|
-
"prettier": "3.8.
|
|
73
|
-
"radix-ui": "^1.
|
|
70
|
+
"lucide-react": "^1.18.0",
|
|
71
|
+
"prettier": "3.8.4",
|
|
72
|
+
"radix-ui": "^1.5.0",
|
|
74
73
|
"react": "^19.2.7",
|
|
75
74
|
"react-dom": "^19.2.7",
|
|
76
|
-
"sass": "^1.
|
|
75
|
+
"sass": "^1.101.0",
|
|
77
76
|
"tailwind-merge": "^3.6.0",
|
|
78
|
-
"tailwindcss": "^4.3.
|
|
77
|
+
"tailwindcss": "^4.3.1",
|
|
79
78
|
"tsc-alias": "^1.8.17",
|
|
80
79
|
"tw-animate-css": "^1.4.0",
|
|
81
80
|
"typescript": "~6.0.3",
|
|
82
|
-
"typescript-eslint": "^8.
|
|
81
|
+
"typescript-eslint": "^8.61.0",
|
|
83
82
|
"vite": "^8.0.16",
|
|
84
83
|
"vitest": "^4.1.8"
|
|
85
84
|
}
|
package/dist/export-pdf.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
type JsPdfLoader = () => Promise<unknown>;
|
|
2
|
-
export type PDFExportOptions = {
|
|
3
|
-
width: number;
|
|
4
|
-
height: number;
|
|
5
|
-
margin: number;
|
|
6
|
-
};
|
|
7
|
-
export declare function exportPDFBlob(imageBlob: Blob, options: PDFExportOptions, loader?: JsPdfLoader): Promise<Blob>;
|
|
8
|
-
export {};
|
package/dist/export-pdf.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
function isObject(value) {
|
|
2
|
-
return typeof value === 'object' && value !== null;
|
|
3
|
-
}
|
|
4
|
-
function resolveJsPdfConstructor(moduleValue) {
|
|
5
|
-
if (!isObject(moduleValue)) {
|
|
6
|
-
throw new Error('Invalid "jspdf" module export');
|
|
7
|
-
}
|
|
8
|
-
const moduleCandidate = moduleValue;
|
|
9
|
-
const defaultCandidate = moduleCandidate.default;
|
|
10
|
-
let ctor = moduleCandidate.jsPDF;
|
|
11
|
-
if (!ctor && isObject(defaultCandidate)) {
|
|
12
|
-
ctor = defaultCandidate.jsPDF;
|
|
13
|
-
}
|
|
14
|
-
if (!ctor && typeof defaultCandidate === 'function') {
|
|
15
|
-
ctor = defaultCandidate;
|
|
16
|
-
}
|
|
17
|
-
if (typeof ctor !== 'function') {
|
|
18
|
-
throw new Error('Invalid "jspdf" module export');
|
|
19
|
-
}
|
|
20
|
-
return ctor;
|
|
21
|
-
}
|
|
22
|
-
async function defaultJsPdfLoader() {
|
|
23
|
-
return import('jspdf');
|
|
24
|
-
}
|
|
25
|
-
function blobToDataUrl(blob) {
|
|
26
|
-
return new Promise((resolve, reject) => {
|
|
27
|
-
const reader = new FileReader();
|
|
28
|
-
reader.onload = () => resolve(String(reader.result));
|
|
29
|
-
reader.onerror = () => reject(new Error('Failed to create PDF image data'));
|
|
30
|
-
reader.readAsDataURL(blob);
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
export async function exportPDFBlob(imageBlob, options, loader = defaultJsPdfLoader) {
|
|
34
|
-
let jsPdfModule;
|
|
35
|
-
try {
|
|
36
|
-
jsPdfModule = await loader();
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
throw new Error('PDF export requires optional dependency "jspdf". Install optional dependency "jspdf".');
|
|
40
|
-
}
|
|
41
|
-
const JsPdfCtor = resolveJsPdfConstructor(jsPdfModule);
|
|
42
|
-
const margin = Math.max(0, options.margin);
|
|
43
|
-
const pageWidth = Math.max(1, options.width + margin * 2);
|
|
44
|
-
const pageHeight = Math.max(1, options.height + margin * 2);
|
|
45
|
-
const orientation = pageWidth >= pageHeight ? 'landscape' : 'portrait';
|
|
46
|
-
const pdf = new JsPdfCtor({
|
|
47
|
-
unit: 'px',
|
|
48
|
-
format: [pageWidth, pageHeight],
|
|
49
|
-
orientation,
|
|
50
|
-
hotfixes: ['px_scaling'],
|
|
51
|
-
});
|
|
52
|
-
const actualPageWidth = pdf.internal.pageSize.getWidth();
|
|
53
|
-
const actualPageHeight = pdf.internal.pageSize.getHeight();
|
|
54
|
-
const availableWidth = actualPageWidth - margin * 2;
|
|
55
|
-
const availableHeight = actualPageHeight - margin * 2;
|
|
56
|
-
if (availableWidth <= 0 || availableHeight <= 0) {
|
|
57
|
-
throw new Error('Invalid PDF margin: no drawable area remains');
|
|
58
|
-
}
|
|
59
|
-
const imageDataUrl = await blobToDataUrl(imageBlob);
|
|
60
|
-
const scale = Math.min(availableWidth / options.width, availableHeight / options.height);
|
|
61
|
-
const drawWidth = options.width * scale;
|
|
62
|
-
const drawHeight = options.height * scale;
|
|
63
|
-
const x = (actualPageWidth - drawWidth) / 2;
|
|
64
|
-
const y = (actualPageHeight - drawHeight) / 2;
|
|
65
|
-
pdf.addImage(imageDataUrl, 'PNG', x, y, drawWidth, drawHeight);
|
|
66
|
-
return pdf.output('blob');
|
|
67
|
-
}
|