-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
add new promise-based toImage and downloadImage to Plotly #446
Changes from all commits
642ddba
41c3126
3fca377
649e067
0f5022e
5f8877f
05a2448
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/** | ||
* Copyright 2012-2016, 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'; | ||
|
||
var Plotly = require('../plotly'); | ||
|
||
var isNumeric = require('fast-isnumeric'); | ||
|
||
/** | ||
* @param {object} gd figure Object | ||
* @param {object} opts option object | ||
* @param opts.format 'jpeg' | 'png' | 'webp' | 'svg' | ||
* @param opts.width width of snapshot in px | ||
* @param opts.height height of snapshot in px | ||
*/ | ||
function toImage(gd, opts) { | ||
var Snapshot = require('../snapshot'); | ||
|
||
var promise = new Promise(function(resolve, reject) { | ||
// check for undefined opts | ||
opts = opts || {}; | ||
// default to png | ||
opts.format = opts.format || 'png'; | ||
|
||
var isSizeGood = function(size) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be good to explicitly return false on this one when size not good (just to be safe!) Edit: Nvm I see it's only used right below. Up to you! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added to be safe. |
||
// undefined and null are valid options | ||
if(size === undefined || size === null) { | ||
return true; | ||
} | ||
|
||
if(isNumeric(size) && size > 1) { | ||
return true; | ||
} | ||
|
||
return false; | ||
}; | ||
|
||
if(!isSizeGood(opts.width) || !isSizeGood(opts.height)) { | ||
reject(new Error('Height and width should be pixel values.')); | ||
} | ||
|
||
// first clone the GD so we can operate in a clean environment | ||
var clone = Snapshot.clone(gd, {format: 'png', height: opts.height, width: opts.width}); | ||
var clonedGd = clone.td; | ||
|
||
// put the cloned div somewhere off screen before attaching to DOM | ||
clonedGd.style.position = 'absolute'; | ||
clonedGd.style.left = '-5000px'; | ||
document.body.appendChild(clonedGd); | ||
|
||
function wait() { | ||
var delay = Snapshot.getDelay(clonedGd._fullLayout); | ||
|
||
return new Promise(function(resolve, reject) { | ||
setTimeout(function() { | ||
var svg = Snapshot.toSVG(clonedGd); | ||
|
||
var canvasContainer = window.document.createElement('div'); | ||
var canvas = window.document.createElement('canvas'); | ||
|
||
canvasContainer.appendChild(canvas); | ||
|
||
canvasContainer.id = Plotly.Lib.randstr(); | ||
canvas.id = Plotly.Lib.randstr(); | ||
|
||
Snapshot.svgToImg({ | ||
format: opts.format, | ||
width: clonedGd._fullLayout.width, | ||
height: clonedGd._fullLayout.height, | ||
canvas: canvas, | ||
svg: svg, | ||
// ask svgToImg to return a Promise | ||
// rather than EventEmitter | ||
// leave EventEmitter for backward | ||
// compatibility | ||
promise: true | ||
}).then(function(url) { | ||
if(clonedGd) document.body.removeChild(clonedGd); | ||
resolve(url); | ||
}).catch(function(err) { | ||
reject(err); | ||
}); | ||
}, delay); | ||
}); | ||
} | ||
|
||
var redrawFunc = Snapshot.getRedrawFunc(clonedGd); | ||
|
||
Plotly.plot(clonedGd, clone.data, clone.layout, clone.config) | ||
// TODO: the following is Plotly.Plots.redrawText but without the waiting. | ||
// we shouldn't need to do this, but in *occasional* cases we do. Figure | ||
// out why and take it out. | ||
|
||
// not sure the above TODO makes sense anymore since | ||
// we have converted to promises | ||
.then(redrawFunc) | ||
.then(wait) | ||
.then(function(url) { resolve(url); }) | ||
.catch(function(err) { | ||
reject(err); | ||
}); | ||
}); | ||
|
||
return promise; | ||
} | ||
|
||
module.exports = toImage; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/** | ||
* Copyright 2012-2016, 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'; | ||
|
||
var toImage = require('../plot_api/to_image'); | ||
var Lib = require('../lib'); // for isIE | ||
var fileSaver = require('./filesaver'); | ||
|
||
/** | ||
* @param {object} gd figure Object | ||
* @param {object} opts option object | ||
* @param opts.format 'jpeg' | 'png' | 'webp' | 'svg' | ||
* @param opts.width width of snapshot in px | ||
* @param opts.height height of snapshot in px | ||
* @param opts.filename name of file excluding extension | ||
*/ | ||
function downloadImage(gd, opts) { | ||
|
||
// check for undefined opts | ||
opts = opts || {}; | ||
|
||
// default to png | ||
opts.format = opts.format || 'png'; | ||
|
||
return new Promise(function(resolve,reject) { | ||
if(gd._snapshotInProgress) { | ||
reject(new Error('Snapshotting already in progress.')); | ||
} | ||
|
||
// see comments within svgtoimg for additional | ||
// discussion of problems with IE | ||
// can now draw to canvas, but CORS tainted canvas | ||
// does not allow toDataURL | ||
// svg format will work though | ||
if(Lib.isIE() && opts.format !== 'svg') { | ||
reject(new Error('Sorry IE does not support downloading from canvas. Try {format:\'svg\'} instead.')); | ||
} | ||
|
||
gd._snapshotInProgress = true; | ||
var promise = toImage(gd, opts); | ||
|
||
var filename = opts.filename || gd.fn || 'newplot'; | ||
filename += '.' + opts.format; | ||
|
||
promise.then(function(result) { | ||
gd._snapshotInProgress = false; | ||
return fileSaver(result,filename); | ||
}).then(function(name) { | ||
resolve(name); | ||
}).catch(function(err) { | ||
gd._snapshotInProgress = false; | ||
reject(err); | ||
}); | ||
}); | ||
} | ||
|
||
module.exports = downloadImage; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/** | ||
* Copyright 2012-2016, 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. | ||
*/ | ||
|
||
/* | ||
* substantial portions of this code from FileSaver.js | ||
* https://github.com/eligrey/FileSaver.js | ||
* License: https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md | ||
* FileSaver.js | ||
* A saveAs() FileSaver implementation. | ||
* 1.1.20160328 | ||
* | ||
* By Eli Grey, http://eligrey.com | ||
* License: MIT | ||
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md | ||
*/ | ||
|
||
'use strict'; | ||
|
||
var fileSaver = function(url, name) { | ||
var saveLink = document.createElement('a'); | ||
var canUseSaveLink = 'download' in saveLink; | ||
var isSafari = /Version\/[\d\.]+.*Safari/.test(navigator.userAgent); | ||
var promise = new Promise(function(resolve, reject) { | ||
// IE <10 is explicitly unsupported | ||
if(typeof navigator !== 'undefined' && /MSIE [1-9]\./.test(navigator.userAgent)) { | ||
reject(new Error('IE < 10 unsupported')); | ||
} | ||
|
||
// First try a.download, then web filesystem, then object URLs | ||
if(isSafari) { | ||
// Safari doesn't allow downloading of blob urls | ||
document.location.href = 'data:application/octet-stream' + url.slice(url.search(/[,;]/)); | ||
resolve(name); | ||
} | ||
|
||
if(!name) { | ||
name = 'download'; | ||
} | ||
|
||
if(canUseSaveLink) { | ||
saveLink.href = url; | ||
saveLink.download = name; | ||
document.body.appendChild(saveLink); | ||
saveLink.click(); | ||
document.body.removeChild(saveLink); | ||
resolve(name); | ||
} | ||
|
||
// IE 10+ (native saveAs) | ||
if(typeof navigator !== 'undefined' && navigator.msSaveBlob) { | ||
navigator.msSaveBlob(new Blob([url]), name); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something good in IE?! I'm shocked! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure I would classify as "good" but it does work |
||
resolve(name); | ||
} | ||
|
||
reject(new Error('download error')); | ||
}); | ||
|
||
return promise; | ||
}; | ||
|
||
module.exports = fileSaver; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@timelyportfolio why did you review this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind. I see. It's here now: https://github.com/plotly/plotly.js/pull/446/files#diff-40d98cbce238527d9bffcf2ecd4efa9bR33