Skip to content

Commit 4458dbf

Browse files
committed
use createObjectURL during Plotly.downloadImage
... to workaround Chrome URL length limit (#3771) and probably (not tested) have a perf boost. N.B. createObjectURL expects Blob object which itself expect data string w/o data:image prefix, so use imageDataOnly in the Plotly.toImage call
1 parent add19b3 commit 4458dbf

File tree

3 files changed

+65
-35
lines changed

3 files changed

+65
-35
lines changed

src/snapshot/download.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,23 @@ var toImage = require('../plot_api/to_image');
1515
var fileSaver = require('./filesaver');
1616
var helpers = require('./helpers');
1717

18-
/** Plotly.downloadImage
18+
/**
19+
* Plotly.downloadImage
1920
*
2021
* @param {object | string | HTML div} gd
2122
* can either be a data/layout/config object
2223
* or an existing graph <div>
2324
* or an id to an existing graph <div>
24-
* @param {object} opts (see ../plot_api/to_image)
25+
* @param {object} opts (see Plotly.toImage in ../plot_api/to_image)
2526
* @return {promise}
2627
*/
2728
function downloadImage(gd, opts) {
2829
var _gd;
2930
if(!Lib.isPlainObject(gd)) _gd = Lib.getGraphDiv(gd);
3031

31-
// check for undefined opts
3232
opts = opts || {};
33-
// default to png
3433
opts.format = opts.format || 'png';
34+
opts.imageDataOnly = true;
3535

3636
return new Promise(function(resolve, reject) {
3737
if(_gd && _gd._snapshotInProgress) {
@@ -55,7 +55,7 @@ function downloadImage(gd, opts) {
5555

5656
promise.then(function(result) {
5757
if(_gd) _gd._snapshotInProgress = false;
58-
return fileSaver(result, filename);
58+
return fileSaver(result, filename, opts.format);
5959
}).then(function(name) {
6060
resolve(name);
6161
}).catch(function(err) {

src/snapshot/filesaver.js

+58-24
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9+
'use strict';
10+
11+
var Lib = require('../lib');
12+
913
/*
1014
* substantial portions of this code from FileSaver.js
1115
* https://github.com/eligrey/FileSaver.js
@@ -18,52 +22,82 @@
1822
* License: MIT
1923
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
2024
*/
21-
22-
'use strict';
23-
24-
var fileSaver = function(url, name) {
25+
function fileSaver(url, name, format) {
2526
var saveLink = document.createElement('a');
2627
var canUseSaveLink = 'download' in saveLink;
28+
2729
var promise = new Promise(function(resolve, reject) {
28-
// IE <10 is explicitly unsupported
29-
if(typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent)) {
30+
var blob;
31+
var objectUrl;
32+
33+
if(isIE9orBelow()) {
3034
reject(new Error('IE < 10 unsupported'));
3135
}
3236

33-
// First try a.download, then web filesystem, then object URLs
34-
// Safari doesn't allow downloading of blob urls
35-
document.location.href = 'data:application/octet-stream' + url.slice(url.search(/[,;]/));
36-
resolve(name);
37+
// Safari doesn't allow downloading of blob urls
3738
if(Lib.isSafari()) {
39+
document.location.href = 'data:application/octet-stream' + url;
40+
return resolve();
3841
}
3942

40-
if(!name) {
41-
name = 'download';
43+
// IE 10+ (native saveAs)
44+
if(Lib.isIE()) {
45+
// At this point we are only dealing with a decoded SVG as
46+
// a data URL (since IE only supports SVG)
47+
blob = createBlob(url, format);
48+
window.navigator.msSaveBlob(blob, name);
49+
blob = null;
50+
return resolve(name);
4251
}
4352

4453
if(canUseSaveLink) {
45-
saveLink.href = url;
54+
blob = createBlob(url, format);
55+
objectUrl = window.URL.createObjectURL(blob);
56+
57+
saveLink.href = objectUrl;
4658
saveLink.download = name;
4759
document.body.appendChild(saveLink);
4860
saveLink.click();
61+
4962
document.body.removeChild(saveLink);
50-
resolve(name);
51-
}
63+
window.URL.revokeObjectURL(objectUrl);
64+
blob = null;
5265

53-
// IE 10+ (native saveAs)
54-
if(typeof navigator !== 'undefined' && navigator.msSaveBlob) {
55-
// At this point we are only dealing with a SVG encoded as
56-
// a data URL (since IE only supports SVG)
57-
var encoded = url.split(/^data:image\/svg\+xml,/)[1];
58-
var svg = decodeURIComponent(encoded);
59-
navigator.msSaveBlob(new Blob([svg]), name);
60-
resolve(name);
66+
return resolve(name);
6167
}
6268

6369
reject(new Error('download error'));
6470
});
6571

6672
return promise;
67-
};
73+
}
74+
75+
function isIE9orBelow() {
76+
return (
77+
Lib.isIE() &&
78+
typeof window.navigator !== 'undefined' &&
79+
/MSIE [1-9]\./.test(window.navigator.userAgent)
80+
);
81+
}
82+
83+
// Taken from https://bl.ocks.org/nolanlawson/0eac306e4dac2114c752
84+
function fixBinary(b) {
85+
var len = b.length;
86+
var buf = new ArrayBuffer(len);
87+
var arr = new Uint8Array(buf);
88+
for(var i = 0; i < len; i++) {
89+
arr[i] = b.charCodeAt(i);
90+
}
91+
return buf;
92+
}
93+
94+
function createBlob(url, format) {
95+
if(format === 'svg') {
96+
return new window.Blob([url]);
97+
} else {
98+
var binary = fixBinary(window.atob(url));
99+
return new window.Blob([binary], {type: 'image/' + format});
100+
}
101+
}
68102

69103
module.exports = fileSaver;

test/jasmine/tests/download_test.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,8 @@ function downloadTest(gd, format) {
180180
var linkadded = domchanges[domchanges.length - 2].addedNodes[0];
181181
var linkdeleted = domchanges[domchanges.length - 1].removedNodes[0];
182182

183-
// check for a <a element and proper file type
184-
expect(linkadded.getAttribute('href').split(format)[0]).toEqual('data:image/');
185-
// check that filename option handled properly
186-
expect(filename).toEqual('plotly_download.' + format);
187-
188-
// check that link removed
183+
expect(linkadded.getAttribute('href').split(':')[0]).toBe('blob');
184+
expect(filename).toBe('plotly_download.' + format);
189185
expect(linkadded).toBe(linkdeleted);
190186
});
191187
}

0 commit comments

Comments
 (0)