From 547f2b59e61b67bd4b790709f906c2d494606a6a Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Wed, 6 Apr 2016 16:37:35 -0500 Subject: [PATCH 01/26] begin thinking through the snapshot issue --- snapshot.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 snapshot.md diff --git a/snapshot.md b/snapshot.md new file mode 100644 index 00000000000..ec7fcc05844 --- /dev/null +++ b/snapshot.md @@ -0,0 +1,23 @@ +# Plotly Snapshots + +## Purpose +The purpose of this markdown document is to document exploration of how to best attach the `Plotly.Snapshot.toImage` function to the plot/`div` itself most fully discussed in [issue 83](https://github.com/plotly/plotly.js/issues/83). Another very nice ability would be to offer resize options for the snapshot. + + + +## Questions +Where do we attach toImage on the graph div? + Is it _toImage? + Do we just require /snapshot and bind to `this`? + +How do we piggyback on the snapshot button in the toolbar? + +How do we ask for new size? + + +## Thoughts +`Plotly.Snapshot.clone` already has thumbnail ability by specifying [options tileClass:"thumbnail"](https://github.com/plotly/plotly.js/blob/master/src/snapshot/cloneplot.js#L76). + + +`Plotly.Snapshot.clone` could be used to resize by adding this to `options` when/if we use `Plotly.plot` with our cloned `div`. + From 89e05ce5b015c4b9428fe4e1bf8f715e2c430aa6 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Wed, 6 Apr 2016 17:08:26 -0500 Subject: [PATCH 02/26] little more --- snapshot.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/snapshot.md b/snapshot.md index ec7fcc05844..bac4bb8792f 100644 --- a/snapshot.md +++ b/snapshot.md @@ -9,15 +9,20 @@ The purpose of this markdown document is to document exploration of how to best Where do we attach toImage on the graph div? Is it _toImage? Do we just require /snapshot and bind to `this`? + +What is the expected use case of our new ability? How do we piggyback on the snapshot button in the toolbar? How do we ask for new size? +Are there reference points from other libraries that we could mimic or learn from? + ## Thoughts `Plotly.Snapshot.clone` already has thumbnail ability by specifying [options tileClass:"thumbnail"](https://github.com/plotly/plotly.js/blob/master/src/snapshot/cloneplot.js#L76). -`Plotly.Snapshot.clone` could be used to resize by adding this to `options` when/if we use `Plotly.plot` with our cloned `div`. +`Plotly.Snapshot.clone` could be used to resize by adding this to `options` when/if we use `Plotly.plot` with our cloned `div`. We could also dynamically show a resulting view in a modal or something similar and adjust with `Plotly.relayout`. +`Plotly.Snapshot.clone` by default sets `staticPlot:true` in `config`. \ No newline at end of file From fb52fe71446bfe214e3c5e5e8ce0bd2beb808ed9 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Thu, 7 Apr 2016 12:59:00 -0500 Subject: [PATCH 03/26] add some starter code to a `_toImage` attached to the `div` leveraging the snapshot button --- snapshot.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/snapshot.md b/snapshot.md index bac4bb8792f..3445d753483 100644 --- a/snapshot.md +++ b/snapshot.md @@ -10,6 +10,8 @@ Where do we attach toImage on the graph div? Is it _toImage? Do we just require /snapshot and bind to `this`? +Will any of the chart types require special snapshot abilities or features? + What is the expected use case of our new ability? How do we piggyback on the snapshot button in the toolbar? @@ -25,4 +27,16 @@ Are there reference points from other libraries that we could mimic or learn fro `Plotly.Snapshot.clone` could be used to resize by adding this to `options` when/if we use `Plotly.plot` with our cloned `div`. We could also dynamically show a resulting view in a modal or something similar and adjust with `Plotly.relayout`. -`Plotly.Snapshot.clone` by default sets `staticPlot:true` in `config`. \ No newline at end of file +`Plotly.Snapshot.clone` by default sets `staticPlot:true` in `config`. + +A very basic way to attach this assuming there is a modebar would be to do something like this. + +``` +gd._toImage = function(){this._fullLayout._modeBar.buttons.filter(function(btn){return btn[0].name==="toImage"})[0][0].click(this)} +``` + +``` +library(plotly) + +ggplotly(ggplot(cars,aes(speed,dist))+geom_point()) +``` \ No newline at end of file From 4436f3ccfcc22fa353d4fa9aeea3228b6dd8de52 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Thu, 7 Apr 2016 15:11:39 -0500 Subject: [PATCH 04/26] add bullets and codepen example --- snapshot.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/snapshot.md b/snapshot.md index 3445d753483..901c013e8ce 100644 --- a/snapshot.md +++ b/snapshot.md @@ -25,16 +25,20 @@ Are there reference points from other libraries that we could mimic or learn fro `Plotly.Snapshot.clone` already has thumbnail ability by specifying [options tileClass:"thumbnail"](https://github.com/plotly/plotly.js/blob/master/src/snapshot/cloneplot.js#L76). -`Plotly.Snapshot.clone` could be used to resize by adding this to `options` when/if we use `Plotly.plot` with our cloned `div`. We could also dynamically show a resulting view in a modal or something similar and adjust with `Plotly.relayout`. +- `Plotly.Snapshot.clone` could be used to resize by adding this to `options` when/if we use `Plotly.plot` with our cloned `div`. We could also dynamically show a resulting view in a modal or something similar and adjust with `Plotly.relayout`. -`Plotly.Snapshot.clone` by default sets `staticPlot:true` in `config`. - -A very basic way to attach this assuming there is a modebar would be to do something like this. +- `Plotly.Snapshot.clone` by default sets `staticPlot:true` in `config`. +- A very basic way to attach this assuming there is a modebar would be to do something like this. See [codepen](http://codepen.io/timelyportfolio/pen/ZWvyYM). ``` -gd._toImage = function(){this._fullLayout._modeBar.buttons.filter(function(btn){return btn[0].name==="toImage"})[0][0].click(this)} +gd._toImage = function(){ + this._fullLayout._modeBar.buttons.filter( + function(btn){return btn[0].name==="toImage" + })[0][0].click(this) +} ``` +- Quick code to experiment from R ``` library(plotly) From 760a93bc76da940cefdef94cee5e4eab566efbb7 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Thu, 7 Apr 2016 15:12:06 -0500 Subject: [PATCH 05/26] change `toImage` to use height and width if specified in opts --- src/snapshot/toimage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/snapshot/toimage.js b/src/snapshot/toimage.js index 5eac155d5af..ca0af41a1fe 100644 --- a/src/snapshot/toimage.js +++ b/src/snapshot/toimage.js @@ -24,7 +24,7 @@ function toImage(gd, opts) { var Snapshot = Plotly.Snapshot; var ev = new EventEmitter(); - var clone = Snapshot.clone(gd, {format: 'png'}); + 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 From cff741ac186988454fbb99a2c3715f84d7c9b720 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Thu, 7 Apr 2016 16:10:21 -0500 Subject: [PATCH 06/26] copy snapshot click handler to `toSnapshot` for generic use --- src/snapshot/index.js | 3 +- src/snapshot/tosnapshot.js | 74 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/snapshot/tosnapshot.js diff --git a/src/snapshot/index.js b/src/snapshot/index.js index 2b662846928..0c702db382e 100644 --- a/src/snapshot/index.js +++ b/src/snapshot/index.js @@ -34,7 +34,8 @@ var Snapshot = { clone: require('./cloneplot'), toSVG: require('./tosvg'), svgToImg: require('./svgtoimg'), - toImage: require('./toimage') + toImage: require('./toimage'), + toSnapshot: require('./tosnapshot') }; module.exports = Snapshot; diff --git a/src/snapshot/tosnapshot.js b/src/snapshot/tosnapshot.js new file mode 100644 index 00000000000..08f73bf9bd7 --- /dev/null +++ b/src/snapshot/tosnapshot.js @@ -0,0 +1,74 @@ +/** +* 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 Lib = require('../lib'); + +/** + * @param {object} gd figure Object + * @param {object} opts option object + * @param opts.format 'jpeg' | 'png' | 'webp' | 'svg' + */ +function toSnapshot(gd, opts) { + + var Snapshot = Plotly.Snapshot; + var Lib = Plotly.Lib; + + // check for undefined opts + opts = (opts) ? opts : {}; + + // default to png + opts.format = (opts.format) ? opts.format : 'png'; + + if(Lib.isIE()) { + Lib.notifier('Snapshotting is unavailable in Internet Explorer. ' + + 'Consider exporting your images using the Plotly Cloud', 'long'); + return; + } + + if(gd._snapshotInProgress) { + Lib.notifier('Snapshotting is still in progress - please hold', 'long'); + return; + } + + gd._snapshotInProgress = true; + Lib.notifier('Taking snapshot - this may take a few seconds', 'long'); + + var ev = Snapshot.toImage(gd, opts); + + var filename = gd.fn || 'newplot'; + filename += '.' + opts.format; + + ev.once('success', function(result) { + gd._snapshotInProgress = false; + + var downloadLink = document.createElement('a'); + downloadLink.href = result; + downloadLink.download = filename; // only supported by FF and Chrome + + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); + + ev.clean(); + }); + + ev.once('error', function(err) { + gd._snapshotInProgress = false; + + Lib.notifier('Sorry there was a problem downloading your ' + format, 'long'); + console.error(err); + + ev.clean(); + }); +} + +module.exports = toSnapshot; From 937340c51d8c9b99df7e5d73f26f9a9bbffbfcd3 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Fri, 8 Apr 2016 11:07:45 -0500 Subject: [PATCH 07/26] convert `svgtoimg` to `Promise` from `EventEmitter` --- src/snapshot/svgtoimg.js | 107 +++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/src/snapshot/svgtoimg.js b/src/snapshot/svgtoimg.js index 8dc868cb937..7e4b8361581 100644 --- a/src/snapshot/svgtoimg.js +++ b/src/snapshot/svgtoimg.js @@ -9,62 +9,61 @@ 'use strict'; -var EventEmitter = require('events').EventEmitter; - function svgToImg(opts) { - var ev = opts.emitter ? opts.emitter : new EventEmitter(); - - var Image = window.Image; - var Blob = window.Blob; - - var svg = opts.svg; - var format = opts.format || 'png'; - var canvas = opts.canvas; - - var ctx = canvas.getContext('2d'); - var img = new Image(); - var DOMURL = window.URL || window.webkitURL; - var svgBlob = new Blob([svg], {type: 'image/svg+xml;charset=utf-8'}); - var url = DOMURL.createObjectURL(svgBlob); - - canvas.height = opts.height || 150; - canvas.width = opts.width || 300; - - img.onload = function() { - var imgData; - - DOMURL.revokeObjectURL(url); - ctx.drawImage(img, 0, 0); - - switch(format) { - case 'jpeg': - imgData = canvas.toDataURL('image/jpeg'); - break; - case 'png': - imgData = canvas.toDataURL('image/png'); - break; - case 'webp': - imgData = canvas.toDataURL('image/webp'); - break; - case 'svg': - imgData = svg; - break; - default: - return ev.emit('error', 'Image format is not jpeg, png or svg'); - } - - ev.emit('success', imgData); - }; - - img.onerror = function(err) { - DOMURL.revokeObjectURL(url); - return ev.emit('error', err); - }; - - img.src = url; - - return ev; + var promise = new Promise(function(resolve, reject) { + + var Image = window.Image; + var Blob = window.Blob; + + var svg = opts.svg; + var format = opts.format || 'png'; + var canvas = opts.canvas; + + var ctx = canvas.getContext('2d'); + var img = new Image(); + var DOMURL = window.URL || window.webkitURL; + var svgBlob = new Blob([svg], {type: 'image/svg+xml;charset=utf-8'}); + var url = DOMURL.createObjectURL(svgBlob); + + canvas.height = opts.height || 150; + canvas.width = opts.width || 300; + + img.onload = function() { + var imgData; + + DOMURL.revokeObjectURL(url); + ctx.drawImage(img, 0, 0); + + switch(format) { + case 'jpeg': + imgData = canvas.toDataURL('image/jpeg'); + break; + case 'png': + imgData = canvas.toDataURL('image/png'); + break; + case 'webp': + imgData = canvas.toDataURL('image/webp'); + break; + case 'svg': + imgData = svg; + break; + default: + reject(new Error('Image format is not jpeg, png or svg')); + } + + resolve(imgData); + }; + + img.onerror = function(err) { + DOMURL.revokeObjectURL(url); + reject(err); + }; + + img.src = url; + }); + + return promise; } module.exports = svgToImg; From ac869317edda6b6c84ba01fdda3704065782add9 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Fri, 8 Apr 2016 13:31:09 -0500 Subject: [PATCH 08/26] convert `toimage` to Promises from EventEmitter --- src/snapshot/toimage.js | 122 +++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 57 deletions(-) diff --git a/src/snapshot/toimage.js b/src/snapshot/toimage.js index ca0af41a1fe..79729d07276 100644 --- a/src/snapshot/toimage.js +++ b/src/snapshot/toimage.js @@ -10,7 +10,6 @@ 'use strict'; -var EventEmitter = require('events').EventEmitter; var Plotly = require('../plotly'); /** @@ -19,64 +18,73 @@ var Plotly = require('../plotly'); * @param opts.format 'jpeg' | 'png' | 'webp' | 'svg' */ function toImage(gd, opts) { - - // first clone the GD so we can operate in a clean environment - var Snapshot = Plotly.Snapshot; - var ev = new EventEmitter(); - - 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); - - setTimeout(function() { - var svg = Plotly.Snapshot.toSVG(clonedGd); - - var canvasContainer = window.document.createElement('div'); - var canvas = window.document.createElement('canvas'); - - // window.document.body.appendChild(canvasContainer); - canvasContainer.appendChild(canvas); - - canvasContainer.id = Plotly.Lib.randstr(); - canvas.id = Plotly.Lib.randstr(); - - ev = Plotly.Snapshot.svgToImg({ - format: opts.format, - width: clonedGd._fullLayout.width, - height: clonedGd._fullLayout.height, - canvas: canvas, - emitter: ev, - svg: svg + var promise = new Promise(function(resolve, reject) { + // check for undefined opts + opts = (opts) ? opts : {}; + // default to png + opts.format = (opts.format) ? opts.format : 'png'; + + // first clone the GD so we can operate in a clean environment + var Snapshot = Plotly.Snapshot; + + 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 = Plotly.Snapshot.toSVG(clonedGd); + + var canvasContainer = window.document.createElement('div'); + var canvas = window.document.createElement('canvas'); + + // window.document.body.appendChild(canvasContainer); + canvasContainer.appendChild(canvas); + + canvasContainer.id = Plotly.Lib.randstr(); + canvas.id = Plotly.Lib.randstr(); + + Plotly.Snapshot.svgToImg({ + format: opts.format, + width: clonedGd._fullLayout.width, + height: clonedGd._fullLayout.height, + canvas: canvas, + svg: svg + }).then(function(url) { + if(clonedGd) clonedGd.remove(); + 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); + }); + }); - ev.clean = function() { - if(clonedGd) clonedGd.remove(); - }; - - }, 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. - .then(redrawFunc) - .then(wait) - .catch(function(err) { - ev.emit('error', err); - }); - - - return ev; + return promise; } module.exports = toImage; From ef0906673b55686ddfd7b915282bfb45af9958fd Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Fri, 8 Apr 2016 14:29:33 -0500 Subject: [PATCH 09/26] change snapshot modebar to work with promises instead of eventemitter --- src/components/modebar/buttons.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js index 67d118fc3b9..5a55f0636ab 100644 --- a/src/components/modebar/buttons.js +++ b/src/components/modebar/buttons.js @@ -64,12 +64,12 @@ modeBarButtons.toImage = { gd._snapshotInProgress = true; Lib.notifier('Taking snapshot - this may take a few seconds', 'long'); - var ev = Snapshot.toImage(gd, {format: format}); + var promise = Snapshot.toImage(gd, {format: format}); var filename = gd.fn || 'newplot'; filename += '.' + format; - ev.once('success', function(result) { + promise.then(function(result) { gd._snapshotInProgress = false; var downloadLink = document.createElement('a'); @@ -80,16 +80,12 @@ modeBarButtons.toImage = { downloadLink.click(); document.body.removeChild(downloadLink); - ev.clean(); - }); - - ev.once('error', function(err) { + }) + .catch(function(err) { gd._snapshotInProgress = false; Lib.notifier('Sorry there was a problem downloading your ' + format, 'long'); console.error(err); - - ev.clean(); }); } }; From 099acf3e46db2465f0c8f894eb6693a0756e64f1 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Mon, 11 Apr 2016 13:55:12 -0500 Subject: [PATCH 10/26] changes to snapshot thoughts; move to discuss on pull --- snapshot.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/snapshot.md b/snapshot.md index 901c013e8ce..2b8450012e9 100644 --- a/snapshot.md +++ b/snapshot.md @@ -22,8 +22,6 @@ Are there reference points from other libraries that we could mimic or learn fro ## Thoughts -`Plotly.Snapshot.clone` already has thumbnail ability by specifying [options tileClass:"thumbnail"](https://github.com/plotly/plotly.js/blob/master/src/snapshot/cloneplot.js#L76). - - `Plotly.Snapshot.clone` could be used to resize by adding this to `options` when/if we use `Plotly.plot` with our cloned `div`. We could also dynamically show a resulting view in a modal or something similar and adjust with `Plotly.relayout`. @@ -38,6 +36,10 @@ gd._toImage = function(){ } ``` +- `Plotly.Snapshot.clone` already has thumbnail ability by specifying [options tileClass:"thumbnail"](https://github.com/plotly/plotly.js/blob/master/src/snapshot/cloneplot.js#L76) for the specific thumbnail use case. + + + - Quick code to experiment from R ``` library(plotly) From a22354641419bb18f247b3f29764774a11473d99 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Wed, 13 Apr 2016 21:52:16 -0500 Subject: [PATCH 11/26] let `svgToImg` support both `EventEmitter` and `Promise`; temporary for backward compatibility --- src/snapshot/svgtoimg.js | 28 ++++++++++++++++++++++++++-- src/snapshot/toimage.js | 11 ++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/snapshot/svgtoimg.js b/src/snapshot/svgtoimg.js index 7e4b8361581..d1298a0079f 100644 --- a/src/snapshot/svgtoimg.js +++ b/src/snapshot/svgtoimg.js @@ -6,10 +6,13 @@ * LICENSE file in the root directory of this source tree. */ +var EventEmitter = require('events').EventEmitter; 'use strict'; function svgToImg(opts) { + + var ev = opts.emitter || new EventEmitter(); var promise = new Promise(function(resolve, reject) { @@ -50,20 +53,41 @@ function svgToImg(opts) { break; default: reject(new Error('Image format is not jpeg, png or svg')); + // eventually remove the ev + // in favor of promises + if(!opts.promise){ + return ev.emit('error', 'Image format is not jpeg, png or svg'); + } } - resolve(imgData); + // eventually remove the ev + // in favor of promises + if(!opts.promise){ + ev.emit('success', imgData); + } }; img.onerror = function(err) { DOMURL.revokeObjectURL(url); reject(err); + // eventually remove the ev + // in favor of promises + if(!opts.promise){ + return ev.emit('error', err); + } }; img.src = url; }); - return promise; + // temporary for backward compatibility + // move to only Promise in 2.0.0 + // and eliminate the EventEmitter + if(opts.promise) { + return promise; + } + + return ev; } module.exports = svgToImg; diff --git a/src/snapshot/toimage.js b/src/snapshot/toimage.js index 79729d07276..92d1cf54b65 100644 --- a/src/snapshot/toimage.js +++ b/src/snapshot/toimage.js @@ -20,9 +20,9 @@ var Plotly = require('../plotly'); function toImage(gd, opts) { var promise = new Promise(function(resolve, reject) { // check for undefined opts - opts = (opts) ? opts : {}; + opts = opts || {}; // default to png - opts.format = (opts.format) ? opts.format : 'png'; + opts.format = opts.format || 'png'; // first clone the GD so we can operate in a clean environment var Snapshot = Plotly.Snapshot; @@ -56,7 +56,12 @@ function toImage(gd, opts) { width: clonedGd._fullLayout.width, height: clonedGd._fullLayout.height, canvas: canvas, - svg: svg + svg: svg, + // ask svgToImg to return a Promise + // rather than EventEmitter + // leave EventEmitter for backward + // compatibility + promise: true }).then(function(url) { if(clonedGd) clonedGd.remove(); resolve(url); From 1f09a8e6f1437fc88c0dbb93b21d109f1a77cbbb Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Wed, 13 Apr 2016 22:28:02 -0500 Subject: [PATCH 12/26] start working toward a `downloadImage` method --- src/snapshot/{tosnapshot.js => download.js} | 23 ++++++++------------- src/snapshot/index.js | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) rename src/snapshot/{tosnapshot.js => download.js} (83%) diff --git a/src/snapshot/tosnapshot.js b/src/snapshot/download.js similarity index 83% rename from src/snapshot/tosnapshot.js rename to src/snapshot/download.js index 08f73bf9bd7..f8390e5ea1e 100644 --- a/src/snapshot/tosnapshot.js +++ b/src/snapshot/download.js @@ -17,16 +17,16 @@ var Lib = require('../lib'); * @param {object} opts option object * @param opts.format 'jpeg' | 'png' | 'webp' | 'svg' */ -function toSnapshot(gd, opts) { +function downloadImage(gd, opts) { var Snapshot = Plotly.Snapshot; var Lib = Plotly.Lib; // check for undefined opts - opts = (opts) ? opts : {}; + opts = opts || {}; // default to png - opts.format = (opts.format) ? opts.format : 'png'; + opts.format = opts.format || 'png'; if(Lib.isIE()) { Lib.notifier('Snapshotting is unavailable in Internet Explorer. ' + @@ -42,12 +42,12 @@ function toSnapshot(gd, opts) { gd._snapshotInProgress = true; Lib.notifier('Taking snapshot - this may take a few seconds', 'long'); - var ev = Snapshot.toImage(gd, opts); + var promise = Snapshot.toImage(gd, opts); var filename = gd.fn || 'newplot'; filename += '.' + opts.format; - ev.once('success', function(result) { + promise.then(function(result) { gd._snapshotInProgress = false; var downloadLink = document.createElement('a'); @@ -57,18 +57,13 @@ function toSnapshot(gd, opts) { document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); - - ev.clean(); - }); - - ev.once('error', function(err) { + }) + .catch(function(err) { gd._snapshotInProgress = false; Lib.notifier('Sorry there was a problem downloading your ' + format, 'long'); console.error(err); - - ev.clean(); }); -} +}; -module.exports = toSnapshot; +module.exports = downloadImage; diff --git a/src/snapshot/index.js b/src/snapshot/index.js index 0c702db382e..5bfd8a0dfa1 100644 --- a/src/snapshot/index.js +++ b/src/snapshot/index.js @@ -35,7 +35,7 @@ var Snapshot = { toSVG: require('./tosvg'), svgToImg: require('./svgtoimg'), toImage: require('./toimage'), - toSnapshot: require('./tosnapshot') + downloadImage: require('./download') }; module.exports = Snapshot; From 00623e29aef0673d7e0d368f047cf3d5825ea39e Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Thu, 14 Apr 2016 08:30:03 -0500 Subject: [PATCH 13/26] return the Snapshot `toimage` to its original state for backward compatibility --- src/snapshot/toimage.js | 127 ++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 70 deletions(-) diff --git a/src/snapshot/toimage.js b/src/snapshot/toimage.js index 92d1cf54b65..5eac155d5af 100644 --- a/src/snapshot/toimage.js +++ b/src/snapshot/toimage.js @@ -10,6 +10,7 @@ 'use strict'; +var EventEmitter = require('events').EventEmitter; var Plotly = require('../plotly'); /** @@ -18,78 +19,64 @@ var Plotly = require('../plotly'); * @param opts.format 'jpeg' | 'png' | 'webp' | 'svg' */ function toImage(gd, opts) { - var promise = new Promise(function(resolve, reject) { - // check for undefined opts - opts = opts || {}; - // default to png - opts.format = opts.format || 'png'; - - // first clone the GD so we can operate in a clean environment - var Snapshot = Plotly.Snapshot; - - 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 = Plotly.Snapshot.toSVG(clonedGd); - - var canvasContainer = window.document.createElement('div'); - var canvas = window.document.createElement('canvas'); - - // window.document.body.appendChild(canvasContainer); - canvasContainer.appendChild(canvas); - - canvasContainer.id = Plotly.Lib.randstr(); - canvas.id = Plotly.Lib.randstr(); - - Plotly.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) clonedGd.remove(); - 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); + + // first clone the GD so we can operate in a clean environment + var Snapshot = Plotly.Snapshot; + var ev = new EventEmitter(); + + var clone = Snapshot.clone(gd, {format: 'png'}); + 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); + + setTimeout(function() { + var svg = Plotly.Snapshot.toSVG(clonedGd); + + var canvasContainer = window.document.createElement('div'); + var canvas = window.document.createElement('canvas'); + + // window.document.body.appendChild(canvasContainer); + canvasContainer.appendChild(canvas); + + canvasContainer.id = Plotly.Lib.randstr(); + canvas.id = Plotly.Lib.randstr(); + + ev = Plotly.Snapshot.svgToImg({ + format: opts.format, + width: clonedGd._fullLayout.width, + height: clonedGd._fullLayout.height, + canvas: canvas, + emitter: ev, + svg: svg }); - }); - return promise; + ev.clean = function() { + if(clonedGd) clonedGd.remove(); + }; + + }, 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. + .then(redrawFunc) + .then(wait) + .catch(function(err) { + ev.emit('error', err); + }); + + + return ev; } module.exports = toImage; From 1f7c8e30b1049dcca1359f2502dde0583b5a83fb Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Thu, 14 Apr 2016 09:16:36 -0500 Subject: [PATCH 14/26] attach new promise-based `toImage` to `Plotly` --- src/core.js | 1 + src/plot_api/to_image.js | 95 ++++++++++++++++++++++++++++++++++++++++ src/plotly.js | 6 +++ 3 files changed, 102 insertions(+) create mode 100644 src/plot_api/to_image.js diff --git a/src/core.js b/src/core.js index 00da65fcfba..a6454b2e4e4 100644 --- a/src/core.js +++ b/src/core.js @@ -31,6 +31,7 @@ exports.moveTraces = Plotly.moveTraces; exports.purge = Plotly.purge; exports.setPlotConfig = require('./plot_api/set_plot_config'); exports.register = Plotly.register; +exports.toImage = Plotly.toImage; // plot icons exports.Icons = require('../build/ploticon'); diff --git a/src/plot_api/to_image.js b/src/plot_api/to_image.js new file mode 100644 index 00000000000..a372a6cdd9d --- /dev/null +++ b/src/plot_api/to_image.js @@ -0,0 +1,95 @@ +/** +* 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. +*/ + +/*eslint dot-notation: [2, {"allowPattern": "^catch$"}]*/ + +'use strict'; + +var Plotly = require('../plotly'); + +/** + * @param {object} gd figure Object + * @param {object} opts option object + * @param opts.format 'jpeg' | 'png' | 'webp' | 'svg' + */ +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'; + + // 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'); + + // window.document.body.appendChild(canvasContainer); + 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) clonedGd.remove(); + 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; diff --git a/src/plotly.js b/src/plotly.js index 3ae37158882..86a0af39658 100644 --- a/src/plotly.js +++ b/src/plotly.js @@ -79,6 +79,12 @@ exports.register(require('./traces/scatter')); // plot api require('./plot_api/plot_api'); exports.PlotSchema = require('./plot_api/plot_schema'); +// toImage to attach directly to Plotly +// to allow us to get rid of Snapshot below +// for version 2.0.0 +exports.toImage = require('./plot_api/to_image'); + // imaging routines exports.Snapshot = require('./snapshot'); + From 4b618dbfe27a9806e5d27449f624e7dd660956bb Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Thu, 14 Apr 2016 09:19:38 -0500 Subject: [PATCH 15/26] add `downloadImage` to `Plotly.Snapshot` and use that for click handler on snapshot modebar --- src/components/modebar/buttons.js | 42 ++----------------------------- src/snapshot/download.js | 9 +++---- 2 files changed, 5 insertions(+), 46 deletions(-) diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js index 5a55f0636ab..3befa6b8267 100644 --- a/src/components/modebar/buttons.js +++ b/src/components/modebar/buttons.js @@ -47,46 +47,8 @@ modeBarButtons.toImage = { name: 'toImage', title: 'Download plot as a png', icon: Icons.camera, - click: function(gd) { - var format = 'png'; - - if(Lib.isIE()) { - Lib.notifier('Snapshotting is unavailable in Internet Explorer. ' + - 'Consider exporting your images using the Plotly Cloud', 'long'); - return; - } - - if(gd._snapshotInProgress) { - Lib.notifier('Snapshotting is still in progress - please hold', 'long'); - return; - } - - gd._snapshotInProgress = true; - Lib.notifier('Taking snapshot - this may take a few seconds', 'long'); - - var promise = Snapshot.toImage(gd, {format: format}); - - var filename = gd.fn || 'newplot'; - filename += '.' + format; - - promise.then(function(result) { - gd._snapshotInProgress = false; - - var downloadLink = document.createElement('a'); - downloadLink.href = result; - downloadLink.download = filename; // only supported by FF and Chrome - - document.body.appendChild(downloadLink); - downloadLink.click(); - document.body.removeChild(downloadLink); - - }) - .catch(function(err) { - gd._snapshotInProgress = false; - - Lib.notifier('Sorry there was a problem downloading your ' + format, 'long'); - console.error(err); - }); + click: function(gd){ + Snapshot.downloadImage(gd) } }; diff --git a/src/snapshot/download.js b/src/snapshot/download.js index f8390e5ea1e..0adaee96eb2 100644 --- a/src/snapshot/download.js +++ b/src/snapshot/download.js @@ -9,7 +9,7 @@ 'use strict'; -var Plotly = require('../plotly'); +var toImage = require('../plot_api/to_image'); var Lib = require('../lib'); /** @@ -19,9 +19,6 @@ var Lib = require('../lib'); */ function downloadImage(gd, opts) { - var Snapshot = Plotly.Snapshot; - var Lib = Plotly.Lib; - // check for undefined opts opts = opts || {}; @@ -42,7 +39,7 @@ function downloadImage(gd, opts) { gd._snapshotInProgress = true; Lib.notifier('Taking snapshot - this may take a few seconds', 'long'); - var promise = Snapshot.toImage(gd, opts); + var promise = toImage(gd, opts); var filename = gd.fn || 'newplot'; filename += '.' + opts.format; @@ -61,7 +58,7 @@ function downloadImage(gd, opts) { .catch(function(err) { gd._snapshotInProgress = false; - Lib.notifier('Sorry there was a problem downloading your ' + format, 'long'); + Lib.notifier('Sorry there was a problem downloading your ' + opts.format, 'long'); console.error(err); }); }; From e1b031e8d795d27d08fea6abbe268bd85e1e562d Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Fri, 15 Apr 2016 16:18:46 -0500 Subject: [PATCH 16/26] lint buttons.js --- src/components/modebar/buttons.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js index 3befa6b8267..e991b7a8026 100644 --- a/src/components/modebar/buttons.js +++ b/src/components/modebar/buttons.js @@ -47,8 +47,8 @@ modeBarButtons.toImage = { name: 'toImage', title: 'Download plot as a png', icon: Icons.camera, - click: function(gd){ - Snapshot.downloadImage(gd) + click: function(gd) { + Snapshot.downloadImage(gd); } }; From dc33644dbadd3bd79b1028c528b1d895f5b697b3 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Fri, 15 Apr 2016 16:36:49 -0500 Subject: [PATCH 17/26] remove unnecessary `toImage` from plotly.js --- src/plotly.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/plotly.js b/src/plotly.js index 86a0af39658..a4caceb3f59 100644 --- a/src/plotly.js +++ b/src/plotly.js @@ -79,11 +79,6 @@ exports.register(require('./traces/scatter')); // plot api require('./plot_api/plot_api'); exports.PlotSchema = require('./plot_api/plot_schema'); -// toImage to attach directly to Plotly -// to allow us to get rid of Snapshot below -// for version 2.0.0 -exports.toImage = require('./plot_api/to_image'); - // imaging routines exports.Snapshot = require('./snapshot'); From 5116a8431611472d17af954a95b932dcd6fc6e50 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Fri, 15 Apr 2016 17:45:35 -0500 Subject: [PATCH 18/26] move snapshot notifier to modebar handler and out of `downloadImage`; add filename `opts` for download --- src/components/modebar/buttons.js | 20 ++++++++++-- src/snapshot/download.js | 53 +++++++++++++------------------ 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js index e991b7a8026..f40b6e16443 100644 --- a/src/components/modebar/buttons.js +++ b/src/components/modebar/buttons.js @@ -11,7 +11,7 @@ var Plotly = require('../../plotly'); var Lib = require('../../lib'); -var Snapshot = require('../../snapshot'); +var downloadImage = require('../../snapshot/download'); var Icons = require('../../../build/ploticon'); @@ -48,7 +48,23 @@ modeBarButtons.toImage = { title: 'Download plot as a png', icon: Icons.camera, click: function(gd) { - Snapshot.downloadImage(gd); + if(Lib.isIE()) { + Lib.notifier('Snapshotting is unavailable in Internet Explorer. ' + + 'Consider exporting your images using the Plotly Cloud', 'long'); + return; + } + + if(gd._snapshotInProgress) { + Lib.notifier('Snapshotting is still in progress - please hold', 'long'); + return; + } + + gd._snapshotInProgress = true; + Lib.notifier('Taking snapshot - this may take a few seconds', 'long'); + + downloadImage(gd).catch(function(err){ + Lib.notifier('Sorry there was a problem downloading your snapshot', 'long'); + }); } }; diff --git a/src/snapshot/download.js b/src/snapshot/download.js index 0adaee96eb2..9f4e0346327 100644 --- a/src/snapshot/download.js +++ b/src/snapshot/download.js @@ -10,12 +10,14 @@ 'use strict'; var toImage = require('../plot_api/to_image'); -var Lib = require('../lib'); /** * @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) { @@ -25,41 +27,30 @@ function downloadImage(gd, opts) { // default to png opts.format = opts.format || 'png'; - if(Lib.isIE()) { - Lib.notifier('Snapshotting is unavailable in Internet Explorer. ' + - 'Consider exporting your images using the Plotly Cloud', 'long'); - return; - } - - if(gd._snapshotInProgress) { - Lib.notifier('Snapshotting is still in progress - please hold', 'long'); - return; - } - gd._snapshotInProgress = true; - Lib.notifier('Taking snapshot - this may take a few seconds', 'long'); - var promise = toImage(gd, opts); - var filename = gd.fn || 'newplot'; + var filename = opts.filename || gd.fn || 'newplot'; filename += '.' + opts.format; - promise.then(function(result) { - gd._snapshotInProgress = false; - - var downloadLink = document.createElement('a'); - downloadLink.href = result; - downloadLink.download = filename; // only supported by FF and Chrome - - document.body.appendChild(downloadLink); - downloadLink.click(); - document.body.removeChild(downloadLink); - }) - .catch(function(err) { - gd._snapshotInProgress = false; - - Lib.notifier('Sorry there was a problem downloading your ' + opts.format, 'long'); - console.error(err); + return new Promise(function(resolve,reject){ + promise.then(function(result) { + gd._snapshotInProgress = false; + + var downloadLink = document.createElement('a'); + downloadLink.href = result; + downloadLink.download = filename; // only supported by FF and Chrome + + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); + resolve(); + }) + .catch(function(err) { + gd._snapshotInProgress = false; + console.error(err); + reject(err); + }); }); }; From 8fbf716d3dc393b734c18f51c0b54c54642299d6 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Fri, 15 Apr 2016 17:46:03 -0500 Subject: [PATCH 19/26] doc height and width in `toImage` --- src/plot_api/to_image.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plot_api/to_image.js b/src/plot_api/to_image.js index a372a6cdd9d..f7ce30accaf 100644 --- a/src/plot_api/to_image.js +++ b/src/plot_api/to_image.js @@ -16,6 +16,8 @@ var Plotly = require('../plotly'); * @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'); From e7da65d6f33c6201f34b3f3a1d7fcb8a54b00bd3 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Mon, 18 Apr 2016 11:48:28 -0500 Subject: [PATCH 20/26] add `require` for `toImage` in core.js --- src/core.js | 2 +- test/jasmine/tests/toimage_test.js | 39 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 test/jasmine/tests/toimage_test.js diff --git a/src/core.js b/src/core.js index a6454b2e4e4..c2fa04424f4 100644 --- a/src/core.js +++ b/src/core.js @@ -31,7 +31,7 @@ exports.moveTraces = Plotly.moveTraces; exports.purge = Plotly.purge; exports.setPlotConfig = require('./plot_api/set_plot_config'); exports.register = Plotly.register; -exports.toImage = Plotly.toImage; +exports.toImage = require('./plot_api/to_image'); // plot icons exports.Icons = require('../build/ploticon'); diff --git a/test/jasmine/tests/toimage_test.js b/test/jasmine/tests/toimage_test.js new file mode 100644 index 00000000000..ffdf6e183e0 --- /dev/null +++ b/test/jasmine/tests/toimage_test.js @@ -0,0 +1,39 @@ +// move toimage to plot_api_test.js +// once established and confirmed + +var Plotly = require('@lib/index'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var subplotMock = require('../../image/mocks/multiple_subplots.json'); +var annotationMock = require('../../image/mocks/annotations.json'); + + +describe('Plotly.toImage', function() { + 'use strict'; + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(destroyGraphDiv); + + it('should be attached to Plotly', function() { + debugger; + expect(Plotly.toImage).toBeDefined(); + }); + + it('should return a promise', function(done) { + debugger; + function isPromise(x){ + return !!x.then || typeof x.then === 'function'; + } + + var returnValue = Plotly.plot(gd, subplotMock.data, subplotMock.layout) + .then(Plotly.toImage); + + expect(isPromise(returnValue)).toBe(true); + + returnValue.then(done); + }); +}); \ No newline at end of file From 74e9bf069c49006bb35d0b29aad247a03a705dea Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Mon, 18 Apr 2016 11:49:05 -0500 Subject: [PATCH 21/26] begin tests for `toImage` --- test/jasmine/tests/toimage_test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/jasmine/tests/toimage_test.js b/test/jasmine/tests/toimage_test.js index ffdf6e183e0..22a054bd10d 100644 --- a/test/jasmine/tests/toimage_test.js +++ b/test/jasmine/tests/toimage_test.js @@ -19,12 +19,10 @@ describe('Plotly.toImage', function() { afterEach(destroyGraphDiv); it('should be attached to Plotly', function() { - debugger; expect(Plotly.toImage).toBeDefined(); }); it('should return a promise', function(done) { - debugger; function isPromise(x){ return !!x.then || typeof x.then === 'function'; } From 9a8559e03670d3dd249135e29cffe403fd8a1018 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Mon, 18 Apr 2016 12:54:03 -0500 Subject: [PATCH 22/26] add error on download if snapshot already in progress --- src/snapshot/download.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/snapshot/download.js b/src/snapshot/download.js index 9f4e0346327..19291d3a20f 100644 --- a/src/snapshot/download.js +++ b/src/snapshot/download.js @@ -27,13 +27,18 @@ function downloadImage(gd, opts) { // default to png opts.format = opts.format || 'png'; - gd._snapshotInProgress = true; - var promise = toImage(gd, opts); - - var filename = opts.filename || gd.fn || 'newplot'; - filename += '.' + opts.format; - return new Promise(function(resolve,reject){ + if(gd._snapshotInProgress){ + reject('Snapshotting is unavailable in Internet Explorer. ' + + 'Consider exporting your images using the Plotly Cloud'); + } + + 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; From 166724bf865d28c31e09feebca9778243d67356f Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Mon, 18 Apr 2016 15:30:16 -0500 Subject: [PATCH 23/26] do some error checking on height and width of `toImage` --- src/plot_api/to_image.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/plot_api/to_image.js b/src/plot_api/to_image.js index f7ce30accaf..8e3beeb5bf0 100644 --- a/src/plot_api/to_image.js +++ b/src/plot_api/to_image.js @@ -27,6 +27,12 @@ function toImage(gd, opts) { opts = opts || {}; // default to png opts.format = opts.format || 'png'; + + if( (opts.width && opts.width < 1) || + (opts.height && opts.height < 1) + ){ + 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}); From 9dc94231272730f6f4db1663b65a9f6c78ae10e0 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Mon, 18 Apr 2016 15:30:53 -0500 Subject: [PATCH 24/26] test errors, file formats, and sizes of `toImage` --- test/jasmine/tests/toimage_test.js | 76 +++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/test/jasmine/tests/toimage_test.js b/test/jasmine/tests/toimage_test.js index 22a054bd10d..649232ced00 100644 --- a/test/jasmine/tests/toimage_test.js +++ b/test/jasmine/tests/toimage_test.js @@ -5,7 +5,6 @@ var Plotly = require('@lib/index'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var subplotMock = require('../../image/mocks/multiple_subplots.json'); -var annotationMock = require('../../image/mocks/annotations.json'); describe('Plotly.toImage', function() { @@ -34,4 +33,79 @@ describe('Plotly.toImage', function() { returnValue.then(done); }); + + it('should throw error with unsupported file type', function(done){ + // error should actually come in the svgToImg step + + Plotly.plot(gd, subplotMock.data, subplotMock.layout) + .then(function(gd) { + Plotly.toImage(gd, {format: 'x'}) + .catch(function(err){ + expect(err.message).toEqual('Image format is not jpeg, png or svg'); + done(); + }) + }); + + }); + + it('should throw error with height and width < 1', function(done){ + // let user know that Plotly expects pixel values + Plotly.plot(gd, subplotMock.data, subplotMock.layout) + .then(function(gd) { + Plotly.toImage(gd, {height: 0.5}) + .catch(function(err){ + expect(err.message).toEqual('Height and width should be pixel values.'); + done(); + }) + }); + + }); + + it('should create img with proper height and width', function(done){ + var img = document.createElement('img'); + + // specify height and width + subplotMock.layout.height = 600; + subplotMock.layout.width = 700; + + Plotly.plot(gd, subplotMock.data, subplotMock.layout) + .then(function(gd) { + Plotly.toImage(gd).then(function(url){ + img.src = url; + expect(img.height).toBe(600); + expect(img.width).toBe(700); + // now provide height and width in opts + Plotly.toImage(gd, {height: 400, width: 400}).then(function(url){ + img.src = url; + expect(img.height).toBe(400); + expect(img.width).toBe(400); + done(); + }); + }); + }) + + + }); + + it('should create proper file type', function(done){ + var plot = Plotly.plot(gd, subplotMock.data, subplotMock.layout); + + plot.then(function(gd) { + Plotly.toImage(gd, {format: 'png'}).then(function(url){ + expect(url.substr(0,15)).toBe('data:image/png;'); + // now do jpeg + Plotly.toImage(gd, {format: 'jpeg'}).then(function(url){ + expect(url.substr(0,16)).toBe('data:image/jpeg;'); + // now do svg + Plotly.toImage(gd, {format: 'webp'}).then(function(url){ + expect(url.substr(0,16)).toBe('data:image/webp;'); + Plotly.toImage(gd, {format: 'svg'}).then(function(url){ + expect(url.substr(1,3)).toBe('svg'); + done(); + }); + }); + }); + }) + }); + }); }); \ No newline at end of file From 13eef9f99202ad8221c6ffc9d410f86d4f8ea076 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Mon, 18 Apr 2016 15:57:08 -0500 Subject: [PATCH 25/26] lint `toImage` and `toImage` tests --- src/plot_api/to_image.js | 11 +++-- test/jasmine/tests/toimage_test.js | 79 ++++++++++++++---------------- 2 files changed, 43 insertions(+), 47 deletions(-) diff --git a/src/plot_api/to_image.js b/src/plot_api/to_image.js index 8e3beeb5bf0..867b0274518 100644 --- a/src/plot_api/to_image.js +++ b/src/plot_api/to_image.js @@ -21,17 +21,18 @@ var Plotly = require('../plotly'); */ 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'; - - if( (opts.width && opts.width < 1) || + + if( + (opts.width && opts.width < 1) || (opts.height && opts.height < 1) - ){ - reject(new Error("Height and width should be pixel values.")); + ) { + reject(new Error('Height and width should be pixel values.')); } // first clone the GD so we can operate in a clean environment diff --git a/test/jasmine/tests/toimage_test.js b/test/jasmine/tests/toimage_test.js index 649232ced00..9ef51285207 100644 --- a/test/jasmine/tests/toimage_test.js +++ b/test/jasmine/tests/toimage_test.js @@ -16,96 +16,91 @@ describe('Plotly.toImage', function() { }); afterEach(destroyGraphDiv); - + it('should be attached to Plotly', function() { expect(Plotly.toImage).toBeDefined(); }); - + it('should return a promise', function(done) { - function isPromise(x){ + function isPromise(x) { return !!x.then || typeof x.then === 'function'; } - + var returnValue = Plotly.plot(gd, subplotMock.data, subplotMock.layout) .then(Plotly.toImage); - + expect(isPromise(returnValue)).toBe(true); - + returnValue.then(done); }); - - it('should throw error with unsupported file type', function(done){ + + it('should throw error with unsupported file type', function(done) { // error should actually come in the svgToImg step Plotly.plot(gd, subplotMock.data, subplotMock.layout) .then(function(gd) { - Plotly.toImage(gd, {format: 'x'}) - .catch(function(err){ - expect(err.message).toEqual('Image format is not jpeg, png or svg'); - done(); - }) + Plotly.toImage(gd, {format: 'x'}).catch(function(err) { + expect(err.message).toEqual('Image format is not jpeg, png or svg'); + done(); + }); }); - + }); - - it('should throw error with height and width < 1', function(done){ + + it('should throw error with height and width < 1', function(done) { // let user know that Plotly expects pixel values Plotly.plot(gd, subplotMock.data, subplotMock.layout) .then(function(gd) { - Plotly.toImage(gd, {height: 0.5}) - .catch(function(err){ - expect(err.message).toEqual('Height and width should be pixel values.'); - done(); - }) + Plotly.toImage(gd, {height: 0.5}).catch(function(err){ + expect(err.message).toEqual('Height and width should be pixel values.'); + done(); + }); }); - }); - - it('should create img with proper height and width', function(done){ + + it('should create img with proper height and width', function(done) { var img = document.createElement('img'); - + // specify height and width subplotMock.layout.height = 600; subplotMock.layout.width = 700; - + Plotly.plot(gd, subplotMock.data, subplotMock.layout) .then(function(gd) { - Plotly.toImage(gd).then(function(url){ + Plotly.toImage(gd).then(function(url) { img.src = url; expect(img.height).toBe(600); expect(img.width).toBe(700); // now provide height and width in opts - Plotly.toImage(gd, {height: 400, width: 400}).then(function(url){ + Plotly.toImage(gd, {height: 400, width: 400}).then(function(url) { img.src = url; expect(img.height).toBe(400); expect(img.width).toBe(400); done(); }); }); - }) - - + }); }); - - it('should create proper file type', function(done){ + + it('should create proper file type', function(done) { var plot = Plotly.plot(gd, subplotMock.data, subplotMock.layout); - + plot.then(function(gd) { - Plotly.toImage(gd, {format: 'png'}).then(function(url){ + Plotly.toImage(gd, {format: 'png'}).then(function(url) { expect(url.substr(0,15)).toBe('data:image/png;'); // now do jpeg - Plotly.toImage(gd, {format: 'jpeg'}).then(function(url){ + Plotly.toImage(gd, {format: 'jpeg'}).then(function(url) { expect(url.substr(0,16)).toBe('data:image/jpeg;'); // now do svg - Plotly.toImage(gd, {format: 'webp'}).then(function(url){ + Plotly.toImage(gd, {format: 'webp'}).then(function(url) { expect(url.substr(0,16)).toBe('data:image/webp;'); - Plotly.toImage(gd, {format: 'svg'}).then(function(url){ + Plotly.toImage(gd, {format: 'svg'}).then(function(url) { expect(url.substr(1,3)).toBe('svg'); done(); - }); + }); }); }); - }) + }); }); - }); -}); \ No newline at end of file + }); +}); From f124a034a0837888a21a8378671dd67d1356c168 Mon Sep 17 00:00:00 2001 From: timelyportfolio Date: Mon, 18 Apr 2016 17:37:48 -0500 Subject: [PATCH 26/26] address line notes --- src/core.js | 1 + src/plot_api/to_image.js | 6 ++- src/plotly.js | 1 - src/snapshot/download.js | 4 +- test/jasmine/tests/toimage_test.js | 61 +++++++++++++++--------------- 5 files changed, 38 insertions(+), 35 deletions(-) diff --git a/src/core.js b/src/core.js index c2fa04424f4..8cc898d1f10 100644 --- a/src/core.js +++ b/src/core.js @@ -32,6 +32,7 @@ exports.purge = Plotly.purge; exports.setPlotConfig = require('./plot_api/set_plot_config'); exports.register = Plotly.register; exports.toImage = require('./plot_api/to_image'); +exports.downloadImage = require('./snapshot/download'); // plot icons exports.Icons = require('../build/ploticon'); diff --git a/src/plot_api/to_image.js b/src/plot_api/to_image.js index 867b0274518..ccfc34ff980 100644 --- a/src/plot_api/to_image.js +++ b/src/plot_api/to_image.js @@ -12,6 +12,8 @@ var Plotly = require('../plotly'); +var isNumeric = require('fast-isnumeric'); + /** * @param {object} gd figure Object * @param {object} opts option object @@ -29,8 +31,8 @@ function toImage(gd, opts) { opts.format = opts.format || 'png'; if( - (opts.width && opts.width < 1) || - (opts.height && opts.height < 1) + (opts.width && isNumeric(opts.width) && opts.width < 1) || + (opts.height && isNumeric(opts.height) && opts.height < 1) ) { reject(new Error('Height and width should be pixel values.')); } diff --git a/src/plotly.js b/src/plotly.js index a4caceb3f59..3ae37158882 100644 --- a/src/plotly.js +++ b/src/plotly.js @@ -82,4 +82,3 @@ exports.PlotSchema = require('./plot_api/plot_schema'); // imaging routines exports.Snapshot = require('./snapshot'); - diff --git a/src/snapshot/download.js b/src/snapshot/download.js index 19291d3a20f..c799c1f5914 100644 --- a/src/snapshot/download.js +++ b/src/snapshot/download.js @@ -29,8 +29,8 @@ function downloadImage(gd, opts) { return new Promise(function(resolve,reject){ if(gd._snapshotInProgress){ - reject('Snapshotting is unavailable in Internet Explorer. ' + - 'Consider exporting your images using the Plotly Cloud'); + reject(new Error('Snapshotting is unavailable in Internet Explorer. ' + + 'Consider exporting your images using the Plotly Cloud')); } gd._snapshotInProgress = true; diff --git a/test/jasmine/tests/toimage_test.js b/test/jasmine/tests/toimage_test.js index 9ef51285207..2270edec4ae 100644 --- a/test/jasmine/tests/toimage_test.js +++ b/test/jasmine/tests/toimage_test.js @@ -5,6 +5,7 @@ var Plotly = require('@lib/index'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); var subplotMock = require('../../image/mocks/multiple_subplots.json'); +var plot3dMock = require('../../image/mocks/gl3d_bunny.json'); describe('Plotly.toImage', function() { @@ -65,42 +66,42 @@ describe('Plotly.toImage', function() { subplotMock.layout.height = 600; subplotMock.layout.width = 700; - Plotly.plot(gd, subplotMock.data, subplotMock.layout) - .then(function(gd) { - Plotly.toImage(gd).then(function(url) { - img.src = url; - expect(img.height).toBe(600); - expect(img.width).toBe(700); - // now provide height and width in opts - Plotly.toImage(gd, {height: 400, width: 400}).then(function(url) { - img.src = url; - expect(img.height).toBe(400); - expect(img.width).toBe(400); - done(); - }); - }); - }); + Plotly.plot(gd, subplotMock.data, subplotMock.layout).then(function(gd) { + return Plotly.toImage(gd); + }).then(function(url) { + img.src = url; + expect(img.height).toBe(600); + expect(img.width).toBe(700); + // now provide height and width in opts + return Plotly.toImage(gd, {height: 400, width: 400}); + }).then(function(url) { + img.src = url; + expect(img.height).toBe(400); + expect(img.width).toBe(400); + done(); + }); }); it('should create proper file type', function(done) { var plot = Plotly.plot(gd, subplotMock.data, subplotMock.layout); plot.then(function(gd) { - Plotly.toImage(gd, {format: 'png'}).then(function(url) { - expect(url.substr(0,15)).toBe('data:image/png;'); - // now do jpeg - Plotly.toImage(gd, {format: 'jpeg'}).then(function(url) { - expect(url.substr(0,16)).toBe('data:image/jpeg;'); - // now do svg - Plotly.toImage(gd, {format: 'webp'}).then(function(url) { - expect(url.substr(0,16)).toBe('data:image/webp;'); - Plotly.toImage(gd, {format: 'svg'}).then(function(url) { - expect(url.substr(1,3)).toBe('svg'); - done(); - }); - }); - }); - }); + return Plotly.toImage(gd, {format: 'png'}); + }).then(function(url) { + expect(url.substr(0,15)).toBe('data:image/png;'); + // now do jpeg + return Plotly.toImage(gd, {format: 'jpeg'}); + }).then(function(url) { + expect(url.substr(0,16)).toBe('data:image/jpeg;'); + // now do webp + return Plotly.toImage(gd, {format: 'webp'}); + }).then(function(url) { + expect(url.substr(0,16)).toBe('data:image/webp;'); + // now do svg + return Plotly.toImage(gd, {format: 'svg'}); + }).then(function(url) { + expect(url.substr(1,3)).toBe('svg'); + done(); }); }); });