Skip to content

Commit 1c3b6b5

Browse files
authored
Merge pull request #2068 from plotly/IE-svg-export
IE/Edge SVG export fixes
2 parents 5e8777b + 69cdbd8 commit 1c3b6b5

File tree

3 files changed

+71
-18
lines changed

3 files changed

+71
-18
lines changed

src/snapshot/filesaver.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ var fileSaver = function(url, name) {
5353

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

src/snapshot/tosvg.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ module.exports = function toSVG(gd, format, scale) {
165165
// url in svg are single quoted
166166
// since we changed double to single
167167
// we'll need to change these to double-quoted
168-
s = s.replace(/(\('#)([^']*)('\))/gi, '(\"$2\")');
168+
s = s.replace(/(\('#)([^']*)('\))/gi, '(\"#$2\")');
169169
// font names with spaces will be escaped single-quoted
170170
// we'll need to change these to double-quoted
171171
s = s.replace(/(\\')/gi, '\"');

test/jasmine/tests/download_test.js

+65-16
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,37 @@ var Plotly = require('@lib/index');
22
var createGraphDiv = require('../assets/create_graph_div');
33
var destroyGraphDiv = require('../assets/destroy_graph_div');
44
var textchartMock = require('@mocks/text_chart_arrays.json');
5+
var fail = require('../assets/fail_test');
6+
7+
var Lib = require('@src/lib');
58

69
var LONG_TIMEOUT_INTERVAL = 2 * jasmine.DEFAULT_TIMEOUT_INTERVAL;
710

811
describe('Plotly.downloadImage', function() {
912
'use strict';
1013
var gd;
1114

12-
// override click handler on createElement
13-
// so these tests will not actually
14-
// download an image each time they are run
15-
// full credit goes to @etpinard; thanks
1615
var createElement = document.createElement;
17-
beforeAll(function() {
18-
document.createElement = function(args) {
19-
var el = createElement.call(document, args);
20-
el.click = function() {};
21-
return el;
22-
};
23-
});
24-
25-
afterAll(function() {
26-
document.createElement = createElement;
27-
});
16+
var slzProto = (new window.XMLSerializer()).__proto__;
17+
var serializeToString = slzProto.serializeToString;
2818

2919
beforeEach(function() {
3020
gd = createGraphDiv();
21+
22+
// override click handler on createElement
23+
// so these tests will not actually
24+
// download an image each time they are run
25+
// full credit goes to @etpinard; thanks
26+
spyOn(document, 'createElement').and.callFake(function(args) {
27+
var el = createElement.call(document, args);
28+
el.click = function() {};
29+
return el;
30+
});
3131
});
3232

3333
afterEach(function() {
3434
destroyGraphDiv();
35+
delete navigator.msSaveBlob;
3536
});
3637

3738
it('should be attached to Plotly', function() {
@@ -59,8 +60,56 @@ describe('Plotly.downloadImage', function() {
5960
it('should create link, remove link, accept options', function(done) {
6061
downloadTest(gd, 'svg', done);
6162
}, LONG_TIMEOUT_INTERVAL);
62-
});
6363

64+
it('should produce the right SVG output in IE', function(done) {
65+
// mock up IE behavior
66+
spyOn(Lib, 'isIE').and.callFake(function() { return true; });
67+
spyOn(slzProto, 'serializeToString').and.callFake(function() {
68+
return serializeToString.apply(this, arguments)
69+
.replace(/(\(#)([^")]*)(\))/gi, '(\"#$2\")');
70+
});
71+
var savedBlob;
72+
navigator.msSaveBlob = function(blob) { savedBlob = blob; };
73+
74+
var expectedStart = '<svg class=\'main-svg\' xmlns=\'http://www.w3.org/2000/svg\' xmlns:xlink=\'http://www.w3.org/1999/xlink\'';
75+
var plotClip = /clip-path='url\("#clip[0-9a-f]{6}xyplot"\)/;
76+
var legendClip = /clip-path=\'url\("#legend[0-9a-f]{6}"\)/;
77+
78+
Plotly.plot(gd, textchartMock.data, textchartMock.layout)
79+
.then(function(gd) {
80+
savedBlob = undefined;
81+
return Plotly.downloadImage(gd, {
82+
format: 'svg',
83+
height: 300,
84+
width: 300,
85+
filename: 'plotly_download'
86+
});
87+
})
88+
.then(function() {
89+
if(savedBlob === undefined) {
90+
fail('undefined saveBlob');
91+
}
92+
93+
return new Promise(function(resolve, reject) {
94+
var reader = new FileReader();
95+
reader.onloadend = function() {
96+
var res = reader.result;
97+
98+
expect(res.substr(0, expectedStart.length)).toBe(expectedStart);
99+
expect(res.match(plotClip)).not.toBe(null);
100+
expect(res.match(legendClip)).not.toBe(null);
101+
102+
resolve();
103+
};
104+
reader.onerror = function(e) { reject(e); };
105+
106+
reader.readAsText(savedBlob);
107+
});
108+
})
109+
.catch(fail)
110+
.then(done);
111+
}, LONG_TIMEOUT_INTERVAL);
112+
});
64113

65114
function downloadTest(gd, format, done) {
66115
// use MutationObserver to monitor the DOM

0 commit comments

Comments
 (0)