Skip to content

Commit b896b19

Browse files
authored
Merge pull request #4593 from plotly/full-json
add format "full-json" to Plotly.toImage and Plotly.dowloadImage
2 parents 8f049fd + c60990c commit b896b19

File tree

11 files changed

+110
-25
lines changed

11 files changed

+110
-25
lines changed

src/assets/geo_assets.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
var saneTopojson = require('sane-topojson');
1212

1313

14-
// package version injected by `npm run preprocess`
15-
exports.version = '1.52.3';
14+
exports.version = require('../version').version;
1615

1716
exports.topojson = saneTopojson;

src/core.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88

99
'use strict';
1010

11-
// package version injected by `npm run preprocess`
12-
exports.version = '1.52.3';
11+
exports.version = require('./version').version;
1312

1413
// inject promise polyfill
1514
require('es6-promise').polyfill();

src/plot_api/to_image.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,18 @@
1111
var isNumeric = require('fast-isnumeric');
1212

1313
var plotApi = require('./plot_api');
14+
var plots = require('../plots/plots');
1415
var Lib = require('../lib');
1516

1617
var helpers = require('../snapshot/helpers');
1718
var toSVG = require('../snapshot/tosvg');
1819
var svgToImg = require('../snapshot/svgtoimg');
20+
var version = require('../version').version;
1921

2022
var attrs = {
2123
format: {
2224
valType: 'enumerated',
23-
values: ['png', 'jpeg', 'webp', 'svg'],
25+
values: ['png', 'jpeg', 'webp', 'svg', 'full-json'],
2426
dflt: 'png',
2527
description: 'Sets the format of exported image.'
2628
},
@@ -170,8 +172,24 @@ function toImage(gd, opts) {
170172
var width = clonedGd._fullLayout.width;
171173
var height = clonedGd._fullLayout.height;
172174

173-
plotApi.purge(clonedGd);
174-
document.body.removeChild(clonedGd);
175+
function cleanup() {
176+
plotApi.purge(clonedGd);
177+
document.body.removeChild(clonedGd);
178+
}
179+
180+
if(format === 'full-json') {
181+
var json = plots.graphJson(clonedGd, false, 'keepdata', 'object', true, true);
182+
json.version = version;
183+
json = JSON.stringify(json);
184+
cleanup();
185+
if(imageDataOnly) {
186+
return resolve(json);
187+
} else {
188+
return resolve(helpers.encodeJSON(json));
189+
}
190+
}
191+
192+
cleanup();
175193

176194
if(format === 'svg') {
177195
if(imageDataOnly) {

src/plots/plots.js

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,9 +2037,10 @@ plots.didMarginChange = function(margin0, margin1) {
20372037
* keepall: keep data and src
20382038
* @param {String} output If you specify 'object', the result will not be stringified
20392039
* @param {Boolean} useDefaults If truthy, use _fullLayout and _fullData
2040+
* @param {Boolean} includeConfig If truthy, include _context
20402041
* @returns {Object|String}
20412042
*/
2042-
plots.graphJson = function(gd, dataonly, mode, output, useDefaults) {
2043+
plots.graphJson = function(gd, dataonly, mode, output, useDefaults, includeConfig) {
20432044
// if the defaults aren't supplied yet, we need to do that...
20442045
if((useDefaults && dataonly && !gd._fullData) ||
20452046
(useDefaults && !dataonly && !gd._fullLayout)) {
@@ -2050,26 +2051,29 @@ plots.graphJson = function(gd, dataonly, mode, output, useDefaults) {
20502051
var layout = (useDefaults) ? gd._fullLayout : gd.layout;
20512052
var frames = (gd._transitionData || {})._frames;
20522053

2053-
function stripObj(d) {
2054+
function stripObj(d, keepFunction) {
20542055
if(typeof d === 'function') {
2055-
return null;
2056+
return keepFunction ? '_function_' : null;
20562057
}
20572058
if(Lib.isPlainObject(d)) {
20582059
var o = {};
2059-
var v, src;
2060-
for(v in d) {
2060+
var src;
2061+
Object.keys(d).sort().forEach(function(v) {
20612062
// remove private elements and functions
20622063
// _ is for private, [ is a mistake ie [object Object]
2063-
if(typeof d[v] === 'function' ||
2064-
['_', '['].indexOf(v.charAt(0)) !== -1) {
2065-
continue;
2064+
if(['_', '['].indexOf(v.charAt(0)) !== -1) return;
2065+
2066+
// if a function, add if necessary then move on
2067+
if(typeof d[v] === 'function') {
2068+
if(keepFunction) o[v] = '_function';
2069+
return;
20662070
}
20672071

20682072
// look for src/data matches and remove the appropriate one
20692073
if(mode === 'keepdata') {
20702074
// keepdata: remove all ...src tags
20712075
if(v.substr(v.length - 3) === 'src') {
2072-
continue;
2076+
return;
20732077
}
20742078
} else if(mode === 'keepstream') {
20752079
// keep sourced data if it's being streamed.
@@ -2078,26 +2082,26 @@ plots.graphJson = function(gd, dataonly, mode, output, useDefaults) {
20782082
src = d[v + 'src'];
20792083
if(typeof src === 'string' && src.indexOf(':') > 0) {
20802084
if(!Lib.isPlainObject(d.stream)) {
2081-
continue;
2085+
return;
20822086
}
20832087
}
20842088
} else if(mode !== 'keepall') {
20852089
// keepref: remove sourced data but only
20862090
// if the source tag is well-formed
20872091
src = d[v + 'src'];
20882092
if(typeof src === 'string' && src.indexOf(':') > 0) {
2089-
continue;
2093+
return;
20902094
}
20912095
}
20922096

20932097
// OK, we're including this... recurse into it
2094-
o[v] = stripObj(d[v]);
2095-
}
2098+
o[v] = stripObj(d[v], keepFunction);
2099+
});
20962100
return o;
20972101
}
20982102

20992103
if(Array.isArray(d)) {
2100-
return d.map(stripObj);
2104+
return d.map(function(x) {return stripObj(x, keepFunction);});
21012105
}
21022106

21032107
if(Lib.isTypedArray(d)) {
@@ -2126,6 +2130,8 @@ plots.graphJson = function(gd, dataonly, mode, output, useDefaults) {
21262130

21272131
if(frames) obj.frames = stripObj(frames);
21282132

2133+
if(includeConfig) obj.config = stripObj(gd._context, true);
2134+
21292135
return (output === 'object') ? obj : JSON.stringify(obj);
21302136
};
21312137

src/snapshot/download.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function downloadImage(gd, opts) {
5151
var promise = toImage(gd, opts);
5252

5353
var filename = opts.filename || gd.fn || 'newplot';
54-
filename += '.' + opts.format;
54+
filename += '.' + opts.format.replace('-', '.');
5555

5656
promise.then(function(result) {
5757
if(_gd) _gd._snapshotInProgress = false;

src/snapshot/helpers.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ exports.encodeSVG = function(svg) {
3636
return 'data:image/svg+xml,' + encodeURIComponent(svg);
3737
};
3838

39+
exports.encodeJSON = function(json) {
40+
return 'data:application/json,' + encodeURIComponent(json);
41+
};
42+
3943
var DOM_URL = window.URL || window.webkitURL;
4044

4145
exports.createObjectURL = function(blob) {
@@ -49,6 +53,8 @@ exports.revokeObjectURL = function(url) {
4953
exports.createBlob = function(url, format) {
5054
if(format === 'svg') {
5155
return new window.Blob([url], {type: 'image/svg+xml;charset=utf-8'});
56+
} else if(format === 'full-json') {
57+
return new window.Blob([url], {type: 'application/json;charset=utf-8'});
5258
} else {
5359
var binary = fixBinary(window.atob(url));
5460
return new window.Blob([binary], {type: 'image/' + format});

src/version.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Copyright 2012-2020, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
// package version injected by `npm run preprocess`
12+
exports.version = '1.52.3';

tasks/preprocess.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ var updateVersion = require('./util/update_version');
99
// main
1010
makeBuildCSS();
1111
copyTopojsonFiles();
12-
updateVersion(constants.pathToPlotlyCore);
13-
updateVersion(constants.pathToPlotlyGeoAssetsSrc);
12+
updateVersion(constants.pathToPlotlyVersion);
1413

1514
// convert scss to css to js
1615
function makeBuildCSS() {

tasks/util/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ module.exports = {
4747

4848
pathToPlotlyIndex: path.join(pathToLib, 'index.js'),
4949
pathToPlotlyCore: path.join(pathToSrc, 'core.js'),
50+
pathToPlotlyVersion: path.join(pathToSrc, 'version.js'),
5051
pathToPlotlyBuild: path.join(pathToBuild, 'plotly.js'),
5152
pathToPlotlyDist: path.join(pathToDist, 'plotly.js'),
5253
pathToPlotlyDistMin: path.join(pathToDist, 'plotly.min.js'),

test/jasmine/tests/download_test.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ describe('Plotly.downloadImage', function() {
5252
.then(done);
5353
}, LONG_TIMEOUT_INTERVAL);
5454

55+
it('should create link, remove link, accept options', function(done) {
56+
downloadTest(gd, 'full-json')
57+
.catch(failTest)
58+
.then(done);
59+
}, LONG_TIMEOUT_INTERVAL);
60+
5561
it('should create link, remove link, accept options', function(done) {
5662
checkWebp(function(supported) {
5763
if(supported) {
@@ -203,7 +209,7 @@ function downloadTest(gd, format) {
203209
var linkdeleted = domchanges[domchanges.length - 1].removedNodes[0];
204210

205211
expect(linkadded.getAttribute('href').split(':')[0]).toBe('blob');
206-
expect(filename).toBe('plotly_download.' + format);
212+
expect(filename).toBe('plotly_download.' + format.replace('-', '.'));
207213
expect(linkadded).toBe(linkdeleted);
208214
});
209215
}

test/jasmine/tests/toimage_test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,43 @@ describe('Plotly.toImage', function() {
263263
done();
264264
});
265265
});
266+
267+
describe('with format `full-json`', function() {
268+
var imgOpts = {format: 'full-json', imageDataOnly: true};
269+
var gd;
270+
271+
beforeEach(function() {
272+
gd = createGraphDiv();
273+
});
274+
afterEach(destroyGraphDiv);
275+
276+
it('export a graph div', function(done) {
277+
Plotly.plot(gd, [{y: [1, 2, 3]}])
278+
.then(function(gd) { return Plotly.toImage(gd, imgOpts);})
279+
.then(function(fig) {
280+
fig = JSON.parse(fig);
281+
['data', 'layout', 'config'].forEach(function(key) {
282+
expect(fig.hasOwnProperty(key)).toBeTruthy('is missing key: ' + key);
283+
});
284+
expect(fig.data[0].mode).toBe('lines+markers', 'contain default mode');
285+
expect(fig.version).toBe(Plotly.version, 'contains Plotly version');
286+
})
287+
.catch(failTest)
288+
.then(done);
289+
});
290+
291+
it('export an object with data/layout/config', function(done) {
292+
Plotly.toImage({data: [{y: [1, 2, 3]}]}, imgOpts)
293+
.then(function(fig) {
294+
fig = JSON.parse(fig);
295+
['data', 'layout', 'config'].forEach(function(key) {
296+
expect(fig.hasOwnProperty(key)).toBeTruthy('is missing key: ' + key);
297+
});
298+
expect(fig.data[0].mode).toBe('lines+markers', 'contain default mode');
299+
expect(fig.version).toBe(Plotly.version, 'contains Plotly version');
300+
})
301+
.catch(failTest)
302+
.then(done);
303+
});
304+
});
266305
});

0 commit comments

Comments
 (0)