mintwaterfall 0.8.6
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/CHANGELOG.md +223 -0
- package/CONTRIBUTING.md +199 -0
- package/README.md +363 -0
- package/dist/index.d.ts +149 -0
- package/dist/mintwaterfall.cjs.js +7978 -0
- package/dist/mintwaterfall.esm.js +7907 -0
- package/dist/mintwaterfall.min.js +7 -0
- package/dist/mintwaterfall.umd.js +7978 -0
- package/index.d.ts +149 -0
- package/package.json +126 -0
- package/src/enterprise/enterprise-core.js +0 -0
- package/src/enterprise/enterprise-feature-template.js +0 -0
- package/src/enterprise/feature-registry.js +0 -0
- package/src/enterprise/features/breakdown.js +0 -0
- package/src/features/breakdown.js +0 -0
- package/src/features/conditional-formatting.js +0 -0
- package/src/index.js +111 -0
- package/src/mintwaterfall-accessibility.ts +680 -0
- package/src/mintwaterfall-advanced-data.ts +1034 -0
- package/src/mintwaterfall-advanced-interactions.ts +649 -0
- package/src/mintwaterfall-advanced-performance.ts +582 -0
- package/src/mintwaterfall-animations.ts +595 -0
- package/src/mintwaterfall-brush.ts +471 -0
- package/src/mintwaterfall-chart-core.ts +296 -0
- package/src/mintwaterfall-chart.ts +1915 -0
- package/src/mintwaterfall-data.ts +1100 -0
- package/src/mintwaterfall-export.ts +475 -0
- package/src/mintwaterfall-hierarchical-layouts.ts +724 -0
- package/src/mintwaterfall-layouts.ts +647 -0
- package/src/mintwaterfall-performance.ts +573 -0
- package/src/mintwaterfall-scales.ts +437 -0
- package/src/mintwaterfall-shapes.ts +385 -0
- package/src/mintwaterfall-statistics.ts +821 -0
- package/src/mintwaterfall-themes.ts +391 -0
- package/src/mintwaterfall-tooltip.ts +450 -0
- package/src/mintwaterfall-zoom.ts +399 -0
- package/src/types/js-modules.d.ts +25 -0
- package/src/utils/compatibility-layer.js +0 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
// MintWaterfall Export System - TypeScript Version
|
|
2
|
+
// Provides SVG, PNG, PDF, and data export capabilities with full type safety
|
|
3
|
+
|
|
4
|
+
import * as d3 from 'd3';
|
|
5
|
+
|
|
6
|
+
// Type definitions for export system
|
|
7
|
+
export interface ExportConfig {
|
|
8
|
+
filename: string;
|
|
9
|
+
quality: number;
|
|
10
|
+
scale: number;
|
|
11
|
+
background: string;
|
|
12
|
+
padding: number;
|
|
13
|
+
includeStyles: boolean;
|
|
14
|
+
includeData: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ExportOptions extends Partial<ExportConfig> {
|
|
18
|
+
width?: number;
|
|
19
|
+
height?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ExportResult {
|
|
23
|
+
blob: Blob;
|
|
24
|
+
url: string;
|
|
25
|
+
data: string;
|
|
26
|
+
download: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PNGExportOptions extends Partial<ExportConfig> {
|
|
30
|
+
width?: number;
|
|
31
|
+
height?: number;
|
|
32
|
+
canvas?: HTMLCanvasElement;
|
|
33
|
+
context?: CanvasRenderingContext2D;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface PDFExportOptions extends Partial<ExportConfig> {
|
|
37
|
+
width?: number;
|
|
38
|
+
height?: number;
|
|
39
|
+
orientation?: 'portrait' | 'landscape';
|
|
40
|
+
pageFormat?: 'a4' | 'letter' | 'legal' | [number, number];
|
|
41
|
+
margin?: number | { top: number; right: number; bottom: number; left: number };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DataExportOptions extends Partial<ExportConfig> {
|
|
45
|
+
dataFormat?: 'json' | 'csv' | 'tsv';
|
|
46
|
+
includeMetadata?: boolean;
|
|
47
|
+
delimiter?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ChartContainer {
|
|
51
|
+
select(selector: string): d3.Selection<any, any, any, any>;
|
|
52
|
+
node(): any;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ExportSystem {
|
|
56
|
+
exportSVG(chartContainer: ChartContainer, options?: ExportOptions): ExportResult;
|
|
57
|
+
exportPNG(chartContainer: ChartContainer, options?: PNGExportOptions): Promise<ExportResult>;
|
|
58
|
+
exportPDF(chartContainer: ChartContainer, options?: PDFExportOptions): Promise<ExportResult>;
|
|
59
|
+
exportData(data: any[], options?: DataExportOptions): ExportResult;
|
|
60
|
+
configure(newConfig: Partial<ExportConfig>): ExportSystem;
|
|
61
|
+
downloadFile(content: string | Blob, filename: string, mimeType?: string): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type ExportFormat = 'svg' | 'png' | 'pdf' | 'json' | 'csv';
|
|
65
|
+
|
|
66
|
+
export function createExportSystem(): ExportSystem {
|
|
67
|
+
|
|
68
|
+
let config: ExportConfig = {
|
|
69
|
+
filename: "waterfall-chart",
|
|
70
|
+
quality: 1.0,
|
|
71
|
+
scale: 1,
|
|
72
|
+
background: "#ffffff",
|
|
73
|
+
padding: 20,
|
|
74
|
+
includeStyles: true,
|
|
75
|
+
includeData: true
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Export chart as SVG
|
|
79
|
+
function exportSVG(chartContainer: ChartContainer, options: ExportOptions = {}): ExportResult {
|
|
80
|
+
const opts: ExportConfig = { ...config, ...options };
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const svg = chartContainer.select("svg");
|
|
84
|
+
if (svg.empty()) {
|
|
85
|
+
throw new Error("No SVG element found in chart container");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const svgNode = svg.node() as SVGSVGElement;
|
|
89
|
+
const serializer = new XMLSerializer();
|
|
90
|
+
|
|
91
|
+
// Clone SVG to avoid modifying original
|
|
92
|
+
const clonedSvg = svgNode.cloneNode(true) as SVGSVGElement;
|
|
93
|
+
|
|
94
|
+
// Add styles if requested
|
|
95
|
+
if (opts.includeStyles) {
|
|
96
|
+
addInlineStyles(clonedSvg);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Add background if specified
|
|
100
|
+
if (opts.background && opts.background !== "transparent") {
|
|
101
|
+
addBackground(clonedSvg, opts.background);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Serialize to string
|
|
105
|
+
const svgString = serializer.serializeToString(clonedSvg);
|
|
106
|
+
|
|
107
|
+
// Create downloadable blob
|
|
108
|
+
const blob = new Blob([svgString], { type: "image/svg+xml;charset=utf-8" });
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
blob,
|
|
112
|
+
url: URL.createObjectURL(blob),
|
|
113
|
+
data: svgString,
|
|
114
|
+
download: () => downloadBlob(blob, `${opts.filename}.svg`)
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error("SVG export failed:", error);
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Export chart as PNG with enhanced features
|
|
124
|
+
function exportPNG(chartContainer: ChartContainer, options: PNGExportOptions = {}): Promise<ExportResult> {
|
|
125
|
+
const opts: ExportConfig & PNGExportOptions = {
|
|
126
|
+
...config,
|
|
127
|
+
scale: 2, // Default to 2x for high-DPI
|
|
128
|
+
quality: 0.95,
|
|
129
|
+
...options
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
try {
|
|
134
|
+
const svg = chartContainer.select("svg");
|
|
135
|
+
if (svg.empty()) {
|
|
136
|
+
reject(new Error("No SVG element found in chart container"));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const svgNode = svg.node() as SVGSVGElement;
|
|
141
|
+
const bbox = svgNode.getBBox();
|
|
142
|
+
const width = (bbox.width + opts.padding * 2) * opts.scale;
|
|
143
|
+
const height = (bbox.height + opts.padding * 2) * opts.scale;
|
|
144
|
+
|
|
145
|
+
// Create high-DPI canvas
|
|
146
|
+
const canvas = document.createElement("canvas");
|
|
147
|
+
canvas.width = width;
|
|
148
|
+
canvas.height = height;
|
|
149
|
+
const ctx = canvas.getContext("2d");
|
|
150
|
+
|
|
151
|
+
if (!ctx) {
|
|
152
|
+
reject(new Error("Failed to get canvas context"));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Enable high-quality rendering
|
|
157
|
+
ctx.imageSmoothingEnabled = true;
|
|
158
|
+
ctx.imageSmoothingQuality = "high";
|
|
159
|
+
|
|
160
|
+
// Set background
|
|
161
|
+
if (opts.background && opts.background !== "transparent") {
|
|
162
|
+
ctx.fillStyle = opts.background;
|
|
163
|
+
ctx.fillRect(0, 0, width, height);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Convert SVG to image with enhanced error handling
|
|
167
|
+
const svgExport = exportSVG(chartContainer, {
|
|
168
|
+
...opts,
|
|
169
|
+
includeStyles: true,
|
|
170
|
+
background: "transparent" // Let canvas handle background
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const img = new Image();
|
|
174
|
+
img.onload = () => {
|
|
175
|
+
try {
|
|
176
|
+
// Draw image with proper scaling and positioning
|
|
177
|
+
ctx.drawImage(img, opts.padding * opts.scale, opts.padding * opts.scale);
|
|
178
|
+
|
|
179
|
+
// Convert to blob
|
|
180
|
+
canvas.toBlob((blob) => {
|
|
181
|
+
if (!blob) {
|
|
182
|
+
reject(new Error("Failed to create PNG blob"));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Clean up
|
|
187
|
+
URL.revokeObjectURL(svgExport.url);
|
|
188
|
+
|
|
189
|
+
resolve({
|
|
190
|
+
blob,
|
|
191
|
+
url: URL.createObjectURL(blob),
|
|
192
|
+
data: svgExport.data,
|
|
193
|
+
download: () => downloadBlob(blob, `${opts.filename}.png`)
|
|
194
|
+
});
|
|
195
|
+
}, "image/png", opts.quality);
|
|
196
|
+
|
|
197
|
+
} catch (drawError) {
|
|
198
|
+
reject(new Error(`PNG rendering failed: ${drawError}`));
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
img.onerror = () => {
|
|
203
|
+
reject(new Error("Failed to load SVG image for PNG conversion"));
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Load SVG as data URL
|
|
207
|
+
img.src = `data:image/svg+xml;base64,${btoa(svgExport.data as string)}`;
|
|
208
|
+
|
|
209
|
+
} catch (error) {
|
|
210
|
+
reject(new Error(`PNG export failed: ${error}`));
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Export chart as PDF (requires external library like jsPDF)
|
|
216
|
+
function exportPDF(chartContainer: ChartContainer, options: PDFExportOptions = {}): Promise<ExportResult> {
|
|
217
|
+
const opts: ExportConfig & PDFExportOptions = {
|
|
218
|
+
...config,
|
|
219
|
+
orientation: 'landscape',
|
|
220
|
+
pageFormat: 'a4',
|
|
221
|
+
...options
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return new Promise((resolve, reject) => {
|
|
225
|
+
// Check if jsPDF is available
|
|
226
|
+
if (typeof window === 'undefined' || !(window as any).jsPDF) {
|
|
227
|
+
reject(new Error('jsPDF library is required for PDF export. Please include it: <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>'));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
// Get PNG data first
|
|
233
|
+
exportPNG(chartContainer, {
|
|
234
|
+
...opts,
|
|
235
|
+
scale: 2,
|
|
236
|
+
quality: 0.95
|
|
237
|
+
}).then((pngResult) => {
|
|
238
|
+
const jsPDF = (window as any).jsPDF;
|
|
239
|
+
const pdf = new jsPDF({
|
|
240
|
+
orientation: opts.orientation,
|
|
241
|
+
unit: 'mm',
|
|
242
|
+
format: opts.pageFormat
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Calculate dimensions
|
|
246
|
+
const pdfWidth = pdf.internal.pageSize.getWidth();
|
|
247
|
+
const pdfHeight = pdf.internal.pageSize.getHeight();
|
|
248
|
+
|
|
249
|
+
// Add image to PDF
|
|
250
|
+
const reader = new FileReader();
|
|
251
|
+
reader.onload = () => {
|
|
252
|
+
try {
|
|
253
|
+
const imgData = reader.result as string;
|
|
254
|
+
|
|
255
|
+
// Calculate aspect ratio and size
|
|
256
|
+
const img = new Image();
|
|
257
|
+
img.onload = () => {
|
|
258
|
+
const aspectRatio = img.width / img.height;
|
|
259
|
+
let width = pdfWidth - 20; // 10mm margin on each side
|
|
260
|
+
let height = width / aspectRatio;
|
|
261
|
+
|
|
262
|
+
// Adjust if height is too large
|
|
263
|
+
if (height > pdfHeight - 20) {
|
|
264
|
+
height = pdfHeight - 20;
|
|
265
|
+
width = height * aspectRatio;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const x = (pdfWidth - width) / 2;
|
|
269
|
+
const y = (pdfHeight - height) / 2;
|
|
270
|
+
|
|
271
|
+
pdf.addImage(imgData, 'PNG', x, y, width, height);
|
|
272
|
+
|
|
273
|
+
// Generate PDF blob
|
|
274
|
+
const pdfBlob = pdf.output('blob');
|
|
275
|
+
|
|
276
|
+
// Clean up
|
|
277
|
+
URL.revokeObjectURL(pngResult.url);
|
|
278
|
+
|
|
279
|
+
resolve({
|
|
280
|
+
blob: pdfBlob,
|
|
281
|
+
url: URL.createObjectURL(pdfBlob),
|
|
282
|
+
data: pdfBlob,
|
|
283
|
+
download: () => downloadBlob(pdfBlob, `${opts.filename}.pdf`)
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
img.src = imgData;
|
|
288
|
+
|
|
289
|
+
} catch (pdfError) {
|
|
290
|
+
reject(new Error(`PDF generation failed: ${pdfError}`));
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
reader.onerror = () => {
|
|
295
|
+
reject(new Error("Failed to read PNG data for PDF conversion"));
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
reader.readAsDataURL(pngResult.blob);
|
|
299
|
+
|
|
300
|
+
}).catch(reject);
|
|
301
|
+
|
|
302
|
+
} catch (error) {
|
|
303
|
+
reject(new Error(`PDF export failed: ${error}`));
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Export data in various formats
|
|
309
|
+
function exportData(data: any[], options: DataExportOptions = {}): ExportResult {
|
|
310
|
+
const opts: ExportConfig & DataExportOptions = {
|
|
311
|
+
...config,
|
|
312
|
+
dataFormat: 'json',
|
|
313
|
+
includeMetadata: true,
|
|
314
|
+
delimiter: ',',
|
|
315
|
+
...options
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
let content: string;
|
|
320
|
+
let mimeType: string;
|
|
321
|
+
let extension: string;
|
|
322
|
+
|
|
323
|
+
switch (opts.dataFormat) {
|
|
324
|
+
case 'json':
|
|
325
|
+
const jsonData = opts.includeMetadata
|
|
326
|
+
? {
|
|
327
|
+
data,
|
|
328
|
+
metadata: {
|
|
329
|
+
exportDate: new Date().toISOString(),
|
|
330
|
+
count: data.length
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
: data;
|
|
334
|
+
content = JSON.stringify(jsonData, null, 2);
|
|
335
|
+
mimeType = 'application/json';
|
|
336
|
+
extension = 'json';
|
|
337
|
+
break;
|
|
338
|
+
|
|
339
|
+
case 'csv':
|
|
340
|
+
content = convertToCSV(data, opts.delimiter || ',');
|
|
341
|
+
mimeType = 'text/csv';
|
|
342
|
+
extension = 'csv';
|
|
343
|
+
break;
|
|
344
|
+
|
|
345
|
+
case 'tsv':
|
|
346
|
+
content = convertToCSV(data, '\t');
|
|
347
|
+
mimeType = 'text/tab-separated-values';
|
|
348
|
+
extension = 'tsv';
|
|
349
|
+
break;
|
|
350
|
+
|
|
351
|
+
default:
|
|
352
|
+
throw new Error(`Unsupported data export format: ${opts.dataFormat}`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const blob = new Blob([content], { type: `${mimeType};charset=utf-8` });
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
blob,
|
|
359
|
+
url: URL.createObjectURL(blob),
|
|
360
|
+
data: content,
|
|
361
|
+
download: () => downloadBlob(blob, `${opts.filename}.${extension}`)
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.error("Data export failed:", error);
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Helper function to add inline styles to SVG
|
|
371
|
+
function addInlineStyles(svgElement: SVGSVGElement): void {
|
|
372
|
+
try {
|
|
373
|
+
const styleSheets = Array.from(document.styleSheets);
|
|
374
|
+
let styles = '';
|
|
375
|
+
|
|
376
|
+
styleSheets.forEach(sheet => {
|
|
377
|
+
try {
|
|
378
|
+
const rules = Array.from(sheet.cssRules || sheet.rules);
|
|
379
|
+
rules.forEach(rule => {
|
|
380
|
+
if (rule.type === CSSRule.STYLE_RULE) {
|
|
381
|
+
const styleRule = rule as CSSStyleRule;
|
|
382
|
+
if (styleRule.selectorText &&
|
|
383
|
+
(styleRule.selectorText.includes('.mintwaterfall') ||
|
|
384
|
+
styleRule.selectorText.includes('svg') ||
|
|
385
|
+
styleRule.selectorText.includes('chart'))) {
|
|
386
|
+
styles += styleRule.cssText;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
} catch (e) {
|
|
391
|
+
// Skip inaccessible stylesheets (CORS)
|
|
392
|
+
console.warn('Could not access stylesheet:', e);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
if (styles) {
|
|
397
|
+
const styleElement = document.createElementNS('http://www.w3.org/2000/svg', 'style');
|
|
398
|
+
styleElement.textContent = styles;
|
|
399
|
+
svgElement.insertBefore(styleElement, svgElement.firstChild);
|
|
400
|
+
}
|
|
401
|
+
} catch (error) {
|
|
402
|
+
console.warn('Failed to add inline styles:', error);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Helper function to add background to SVG
|
|
407
|
+
function addBackground(svgElement: SVGSVGElement, backgroundColor: string): void {
|
|
408
|
+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
409
|
+
rect.setAttribute('width', '100%');
|
|
410
|
+
rect.setAttribute('height', '100%');
|
|
411
|
+
rect.setAttribute('fill', backgroundColor);
|
|
412
|
+
svgElement.insertBefore(rect, svgElement.firstChild);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Helper function to convert data to CSV
|
|
416
|
+
function convertToCSV(data: any[], delimiter: string = ','): string {
|
|
417
|
+
if (!data || data.length === 0) return '';
|
|
418
|
+
|
|
419
|
+
// Get headers from first object
|
|
420
|
+
const headers = Object.keys(data[0]);
|
|
421
|
+
|
|
422
|
+
// Create CSV content
|
|
423
|
+
const csvContent = [
|
|
424
|
+
headers.join(delimiter),
|
|
425
|
+
...data.map(row =>
|
|
426
|
+
headers.map(header => {
|
|
427
|
+
const value = row[header];
|
|
428
|
+
// Escape quotes and wrap in quotes if contains delimiter
|
|
429
|
+
const stringValue = value != null ? String(value) : '';
|
|
430
|
+
if (stringValue.includes(delimiter) || stringValue.includes('"') || stringValue.includes('\n')) {
|
|
431
|
+
return `"${stringValue.replace(/"/g, '""')}"`;
|
|
432
|
+
}
|
|
433
|
+
return stringValue;
|
|
434
|
+
}).join(delimiter)
|
|
435
|
+
)
|
|
436
|
+
].join('\n');
|
|
437
|
+
|
|
438
|
+
return csvContent;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Helper function to download blob
|
|
442
|
+
function downloadBlob(blob: Blob, filename: string): void {
|
|
443
|
+
const url = URL.createObjectURL(blob);
|
|
444
|
+
const link = document.createElement('a');
|
|
445
|
+
link.href = url;
|
|
446
|
+
link.download = filename;
|
|
447
|
+
document.body.appendChild(link);
|
|
448
|
+
link.click();
|
|
449
|
+
document.body.removeChild(link);
|
|
450
|
+
URL.revokeObjectURL(url);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Configure export system
|
|
454
|
+
function configure(newConfig: Partial<ExportConfig>): ExportSystem {
|
|
455
|
+
config = { ...config, ...newConfig };
|
|
456
|
+
return exportSystem;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Download file utility
|
|
460
|
+
function downloadFile(content: string | Blob, filename: string, mimeType: string = 'text/plain'): void {
|
|
461
|
+
const blob = content instanceof Blob ? content : new Blob([content], { type: mimeType });
|
|
462
|
+
downloadBlob(blob, filename);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const exportSystem: ExportSystem = {
|
|
466
|
+
exportSVG,
|
|
467
|
+
exportPNG,
|
|
468
|
+
exportPDF,
|
|
469
|
+
exportData,
|
|
470
|
+
configure,
|
|
471
|
+
downloadFile
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
return exportSystem;
|
|
475
|
+
}
|