offline-exporting.src.js
8.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/**
* @license Highcharts JS v4.1.9 (2015-10-07)
* Client side exporting module
*
* (c) 2015 Torstein Honsi / Oystein Moseng
*
* License: www.highcharts.com/license
*/
// JSLint options:
/*global Highcharts, HighchartsAdapter, document, window, Blob, MSBlobBuilder */
(function (Highcharts) {
// Dummy object so we can reuse our canvas-tools.js without errors
Highcharts.CanVGRenderer = {};
/**
* Add a new method to the Chart object to perform a local download
*/
Highcharts.Chart.prototype.exportChartLocal = function (exportingOptions, chartOptions) {
var chart = this,
options = Highcharts.merge(chart.options.exporting, exportingOptions),
webKit = navigator.userAgent.indexOf('WebKit') > -1 && navigator.userAgent.indexOf("Chrome") < 0, // Webkit and not chrome
scale = options.scale || 2,
chartCopyContainer,
domurl = window.URL || window.webkitURL || window,
images,
imagesEmbedded = 0,
el,
i,
l,
fallbackToExportServer = function () {
if (options.fallbackToExportServer === false) {
throw 'Fallback to export server disabled';
}
chart.exportChart(options);
},
// Get data:URL from image URL
// Pass in callbacks to handle results. finallyCallback is always called at the end of the process. Supplying this callback is optional.
// All callbacks receive two arguments: imageURL, and callbackArgs. callbackArgs is used only by callbacks and can contain whatever.
imageToDataUrl = function (imageURL, callbackArgs, successCallback, taintedCallback, noCanvasSupportCallback, failedLoadCallback, finallyCallback) {
var img = new Image();
if (!webKit) {
img.crossOrigin = 'Anonymous'; // For some reason Safari chokes on this attribute
}
img.onload = function () {
var canvas = document.createElement('canvas'),
ctx = canvas.getContext && canvas.getContext('2d'),
dataURL;
if (!ctx) {
noCanvasSupportCallback(imageURL, callbackArgs);
} else {
canvas.height = img.height * scale;
canvas.width = img.width * scale;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// Now we try to get the contents of the canvas.
try {
dataURL = canvas.toDataURL();
successCallback(dataURL, callbackArgs);
} catch (e) {
// Failed - either tainted canvas or something else went horribly wrong
if (e.name === 'SecurityError' || e.name === 'SECURITY_ERR' || e.message === 'SecurityError') {
taintedCallback(imageURL, callbackArgs);
} else {
throw e;
}
}
}
if (finallyCallback) {
finallyCallback(imageURL, callbackArgs);
}
};
img.onerror = function () {
failedLoadCallback(imageURL, callbackArgs);
if (finallyCallback) {
finallyCallback(imageURL, callbackArgs);
}
};
img.src = imageURL;
},
// Get blob URL from SVG code. Falls back to normal data URI.
svgToDataUrl = function (svg) {
try {
// Safari requires data URI since it doesn't allow navigation to blob URLs
// Firefox has an issue with Blobs and internal references, leading to gradients not working using Blobs (#4550)
if (!webKit && navigator.userAgent.toLowerCase().indexOf('firefox') < 0) {
return domurl.createObjectURL(new Blob([svg], { type: 'image/svg+xml;charset-utf-16'}));
}
} catch (e) {
// Ignore
}
return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg);
},
// Download contents by dataURL/blob
download = function (dataURL, extension) {
var a = document.createElement('a'),
filename = (options.filename || 'chart') + '.' + extension,
windowRef;
// IE specific blob implementation
if (navigator.msSaveOrOpenBlob) {
navigator.msSaveOrOpenBlob(dataURL, filename);
return;
}
// Try HTML5 download attr if supported
if (typeof a.download !== 'undefined') {
a.href = dataURL;
a.download = filename; // HTML5 download attribute
a.target = '_blank';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
} else {
// No download attr, just opening data URI
try {
windowRef = window.open(dataURL, 'chart');
if (typeof windowRef === 'undefined' || windowRef === null) {
throw 1;
}
} catch (e) {
// window.open failed, trying location.href
window.location.href = dataURL;
}
}
},
// Get data URL to an image of the chart and call download on it
initiateDownload = function () {
var svgurl,
blob,
svg = chart.sanitizeSVG(chartCopyContainer.innerHTML); // SVG of chart copy
// Initiate download depending on file type
if (options && options.type === 'image/svg+xml') {
// SVG download. In this case, we want to use Microsoft specific Blob if available
try {
if (navigator.msSaveOrOpenBlob) {
blob = new MSBlobBuilder();
blob.append(svg);
svgurl = blob.getBlob('image/svg+xml');
} else {
svgurl = svgToDataUrl(svg);
}
download(svgurl, 'svg');
} catch (e) {
fallbackToExportServer();
}
} else {
// PNG download - create bitmap from SVG
// First, try to get PNG by rendering on canvas
svgurl = svgToDataUrl(svg);
imageToDataUrl(svgurl, { /* args */ }, function (imageURL) {
// Success
try {
download(imageURL, 'png');
} catch (e) {
fallbackToExportServer();
}
}, function () {
// Failed due to tainted canvas
// Create new and untainted canvas
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
imageWidth = svg.match(/^<svg[^>]*width\s*=\s*\"?(\d+)\"?[^>]*>/)[1] * scale,
imageHeight = svg.match(/^<svg[^>]*height\s*=\s*\"?(\d+)\"?[^>]*>/)[1] * scale,
downloadWithCanVG = function () {
ctx.drawSvg(svg, 0, 0, imageWidth, imageHeight);
try {
download(navigator.msSaveOrOpenBlob ? canvas.msToBlob() : canvas.toDataURL('image/png'), 'png');
} catch (e) {
fallbackToExportServer();
}
};
canvas.width = imageWidth;
canvas.height = imageHeight;
if (window.canvg) {
// Use preloaded canvg
downloadWithCanVG();
} else {
// Must load canVG first
chart.showLoading();
HighchartsAdapter.getScript(Highcharts.getOptions().global.canvasToolsURL, function () {
chart.hideLoading();
downloadWithCanVG();
});
}
},
// No canvas support
fallbackToExportServer,
// Failed to load image
fallbackToExportServer,
// Finally
function () {
try {
domurl.revokeObjectURL(svgurl);
} catch (e) {
// Ignore
}
});
}
};
// Hook into getSVG to get a copy of the chart copy's container
Highcharts.wrap(Highcharts.Chart.prototype, 'getChartHTML', function (proceed) {
chartCopyContainer = this.container.cloneNode(true);
return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
});
// Trigger hook to get chart copy
chart.getSVGForExport(options, chartOptions);
images = chartCopyContainer.getElementsByTagName('image');
try {
// If there are no images to embed, just go ahead and start the download process
if (!images.length) {
initiateDownload();
}
// Success handler, we converted image to base64!
function embeddedSuccess(imageURL, callbackArgs) {
++imagesEmbedded;
// Change image href in chart copy
callbackArgs.imageElement.setAttributeNS('http://www.w3.org/1999/xlink', 'href', imageURL);
// Start download when done with the last image
if (imagesEmbedded === images.length) {
initiateDownload();
}
}
// Go through the images we want to embed
for (i = 0, l = images.length; i < l; ++i) {
el = images[i];
imageToDataUrl(el.getAttributeNS('http://www.w3.org/1999/xlink', 'href'), { imageElement: el },
embeddedSuccess,
// Tainted canvas
fallbackToExportServer,
// No canvas support
fallbackToExportServer,
// Failed to load source
fallbackToExportServer
);
}
} catch (e) {
fallbackToExportServer();
}
};
// Extend the default options to use the local exporter logic
Highcharts.getOptions().exporting.buttons.contextButton.menuItems = [{
textKey: 'printChart',
onclick: function () {
this.print();
}
}, {
separator: true
}, {
textKey: 'downloadPNG',
onclick: function () {
this.exportChartLocal();
}
}, {
textKey: 'downloadSVG',
onclick: function () {
this.exportChartLocal({
type: 'image/svg+xml'
});
}
}];
}(Highcharts));