Skip to content

add format "full-json" to Plotly.toImage and Plotly.dowloadImage #4593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 11, 2020
3 changes: 1 addition & 2 deletions src/assets/geo_assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
var saneTopojson = require('sane-topojson');


// package version injected by `npm run preprocess`
exports.version = '1.52.3';
exports.version = require('../version').version;

exports.topojson = saneTopojson;
3 changes: 1 addition & 2 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@

'use strict';

// package version injected by `npm run preprocess`
exports.version = '1.52.3';
exports.version = require('./version').version;

// inject promise polyfill
require('es6-promise').polyfill();
Expand Down
24 changes: 21 additions & 3 deletions src/plot_api/to_image.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@
var isNumeric = require('fast-isnumeric');

var plotApi = require('./plot_api');
var plots = require('../plots/plots');
var Lib = require('../lib');

var helpers = require('../snapshot/helpers');
var toSVG = require('../snapshot/tosvg');
var svgToImg = require('../snapshot/svgtoimg');
var version = require('../version').version;

var attrs = {
format: {
valType: 'enumerated',
values: ['png', 'jpeg', 'webp', 'svg'],
values: ['png', 'jpeg', 'webp', 'svg', 'full-json'],
dflt: 'png',
description: 'Sets the format of exported image.'
},
Expand Down Expand Up @@ -170,8 +172,24 @@ function toImage(gd, opts) {
var width = clonedGd._fullLayout.width;
var height = clonedGd._fullLayout.height;

plotApi.purge(clonedGd);
document.body.removeChild(clonedGd);
function cleanup() {
plotApi.purge(clonedGd);
document.body.removeChild(clonedGd);
}

if(format === 'full-json') {
var json = plots.graphJson(clonedGd, false, 'keepdata', 'object', true, true);
json.version = version;
json = JSON.stringify(json);
cleanup();
if(imageDataOnly) {
return resolve(json);
} else {
return resolve(helpers.encodeJSON(json));
}
}

cleanup();

if(format === 'svg') {
if(imageDataOnly) {
Expand Down
34 changes: 20 additions & 14 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -2037,9 +2037,10 @@ plots.didMarginChange = function(margin0, margin1) {
* keepall: keep data and src
* @param {String} output If you specify 'object', the result will not be stringified
* @param {Boolean} useDefaults If truthy, use _fullLayout and _fullData
* @param {Boolean} includeConfig If truthy, include _context
* @returns {Object|String}
*/
plots.graphJson = function(gd, dataonly, mode, output, useDefaults) {
plots.graphJson = function(gd, dataonly, mode, output, useDefaults, includeConfig) {
// if the defaults aren't supplied yet, we need to do that...
if((useDefaults && dataonly && !gd._fullData) ||
(useDefaults && !dataonly && !gd._fullLayout)) {
Expand All @@ -2050,26 +2051,29 @@ plots.graphJson = function(gd, dataonly, mode, output, useDefaults) {
var layout = (useDefaults) ? gd._fullLayout : gd.layout;
var frames = (gd._transitionData || {})._frames;

function stripObj(d) {
function stripObj(d, keepFunction) {
if(typeof d === 'function') {
return null;
return keepFunction ? '_function_' : null;
}
if(Lib.isPlainObject(d)) {
var o = {};
var v, src;
for(v in d) {
var src;
Object.keys(d).sort().forEach(function(v) {
// remove private elements and functions
// _ is for private, [ is a mistake ie [object Object]
if(typeof d[v] === 'function' ||
['_', '['].indexOf(v.charAt(0)) !== -1) {
continue;
if(['_', '['].indexOf(v.charAt(0)) !== -1) return;

// if a function, add if necessary then move on
if(typeof d[v] === 'function') {
if(keepFunction) o[v] = '_function';
return;
}

// look for src/data matches and remove the appropriate one
if(mode === 'keepdata') {
// keepdata: remove all ...src tags
if(v.substr(v.length - 3) === 'src') {
continue;
return;
}
} else if(mode === 'keepstream') {
// keep sourced data if it's being streamed.
Expand All @@ -2078,26 +2082,26 @@ plots.graphJson = function(gd, dataonly, mode, output, useDefaults) {
src = d[v + 'src'];
if(typeof src === 'string' && src.indexOf(':') > 0) {
if(!Lib.isPlainObject(d.stream)) {
continue;
return;
}
}
} else if(mode !== 'keepall') {
// keepref: remove sourced data but only
// if the source tag is well-formed
src = d[v + 'src'];
if(typeof src === 'string' && src.indexOf(':') > 0) {
continue;
return;
}
}

// OK, we're including this... recurse into it
o[v] = stripObj(d[v]);
}
o[v] = stripObj(d[v], keepFunction);
});
return o;
}

if(Array.isArray(d)) {
return d.map(stripObj);
return d.map(function(x) {return stripObj(x, keepFunction);});
}

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

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

if(includeConfig) obj.config = stripObj(gd._context, true);

return (output === 'object') ? obj : JSON.stringify(obj);
};

Expand Down
2 changes: 1 addition & 1 deletion src/snapshot/download.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function downloadImage(gd, opts) {
var promise = toImage(gd, opts);

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

promise.then(function(result) {
if(_gd) _gd._snapshotInProgress = false;
Expand Down
6 changes: 6 additions & 0 deletions src/snapshot/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ exports.encodeSVG = function(svg) {
return 'data:image/svg+xml,' + encodeURIComponent(svg);
};

exports.encodeJSON = function(json) {
return 'data:application/json,' + encodeURIComponent(json);
};

var DOM_URL = window.URL || window.webkitURL;

exports.createObjectURL = function(blob) {
Expand All @@ -49,6 +53,8 @@ exports.revokeObjectURL = function(url) {
exports.createBlob = function(url, format) {
if(format === 'svg') {
return new window.Blob([url], {type: 'image/svg+xml;charset=utf-8'});
} else if(format === 'full-json') {
return new window.Blob([url], {type: 'application/json;charset=utf-8'});
} else {
var binary = fixBinary(window.atob(url));
return new window.Blob([binary], {type: 'image/' + format});
Expand Down
12 changes: 12 additions & 0 deletions src/version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright 2012-2020, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

// package version injected by `npm run preprocess`
exports.version = '1.52.3';
3 changes: 1 addition & 2 deletions tasks/preprocess.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ var updateVersion = require('./util/update_version');
// main
makeBuildCSS();
copyTopojsonFiles();
updateVersion(constants.pathToPlotlyCore);
updateVersion(constants.pathToPlotlyGeoAssetsSrc);
updateVersion(constants.pathToPlotlyVersion);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nicely done !!


// convert scss to css to js
function makeBuildCSS() {
Expand Down
1 change: 1 addition & 0 deletions tasks/util/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ module.exports = {

pathToPlotlyIndex: path.join(pathToLib, 'index.js'),
pathToPlotlyCore: path.join(pathToSrc, 'core.js'),
pathToPlotlyVersion: path.join(pathToSrc, 'version.js'),
pathToPlotlyBuild: path.join(pathToBuild, 'plotly.js'),
pathToPlotlyDist: path.join(pathToDist, 'plotly.js'),
pathToPlotlyDistMin: path.join(pathToDist, 'plotly.min.js'),
Expand Down
8 changes: 7 additions & 1 deletion test/jasmine/tests/download_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ describe('Plotly.downloadImage', function() {
.then(done);
}, LONG_TIMEOUT_INTERVAL);

it('should create link, remove link, accept options', function(done) {
downloadTest(gd, 'full-json')
.catch(failTest)
.then(done);
}, LONG_TIMEOUT_INTERVAL);

it('should create link, remove link, accept options', function(done) {
checkWebp(function(supported) {
if(supported) {
Expand Down Expand Up @@ -203,7 +209,7 @@ function downloadTest(gd, format) {
var linkdeleted = domchanges[domchanges.length - 1].removedNodes[0];

expect(linkadded.getAttribute('href').split(':')[0]).toBe('blob');
expect(filename).toBe('plotly_download.' + format);
expect(filename).toBe('plotly_download.' + format.replace('-', '.'));
expect(linkadded).toBe(linkdeleted);
});
}
Expand Down
39 changes: 39 additions & 0 deletions test/jasmine/tests/toimage_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,4 +263,43 @@ describe('Plotly.toImage', function() {
done();
});
});

describe('with format `full-json`', function() {
var imgOpts = {format: 'full-json', imageDataOnly: true};
var gd;

beforeEach(function() {
gd = createGraphDiv();
});
afterEach(destroyGraphDiv);

it('export a graph div', function(done) {
Plotly.plot(gd, [{y: [1, 2, 3]}])
.then(function(gd) { return Plotly.toImage(gd, imgOpts);})
.then(function(fig) {
fig = JSON.parse(fig);
['data', 'layout', 'config'].forEach(function(key) {
expect(fig.hasOwnProperty(key)).toBeTruthy('is missing key: ' + key);
});
expect(fig.data[0].mode).toBe('lines+markers', 'contain default mode');
expect(fig.version).toBe(Plotly.version, 'contains Plotly version');
})
.catch(failTest)
.then(done);
});

it('export an object with data/layout/config', function(done) {
Plotly.toImage({data: [{y: [1, 2, 3]}]}, imgOpts)
.then(function(fig) {
fig = JSON.parse(fig);
['data', 'layout', 'config'].forEach(function(key) {
expect(fig.hasOwnProperty(key)).toBeTruthy('is missing key: ' + key);
});
expect(fig.data[0].mode).toBe('lines+markers', 'contain default mode');
expect(fig.version).toBe(Plotly.version, 'contains Plotly version');
})
.catch(failTest)
.then(done);
});
});
});