-
-
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
Improve Plotly.toImage
#1939
Improve Plotly.toImage
#1939
Conversation
- which should fail ;)
- i.e. not crash when called multiple times
- instead of in `plot_config.js` declaration
- which is currently used in the image server
- + add 'webp' test case
- accept data/layout/config figure (plain) object as input, along side existing graph div or (string) id of existing graph div - use Lib.validate & Lib.coerce to sanatize options - bypass Snapshot.cloneplot (Lib.extendDeep is really all we need) - handle 'setBackground` (same as config option) - add 'imageDataOnly' option to strip 'data:image/' prefix
src/plot_api/plot_api.js
Outdated
@@ -404,6 +404,11 @@ function opaqueSetBackground(gd, bgColor) { | |||
setBackground(gd, bgColor); | |||
} | |||
|
|||
function blendSetBackground(gd, bgColor) { | |||
var blend = Color.combine(bgColor, 'white'); |
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.
Implements this (old) image server logic in plotly.js
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.
doesn't this end up visually equivalent to opaqueSetBackground
? And if so can we remove opaqueSetBackground
?
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.
Maybe?
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.
RE 'opaque'
vs 'blend'
, they don't generate the same PNG (gm
compare generates a diff) but I can't find a case where the diff is detectable to my 👀
So, I guess a can make the new image server use 'opaque'
instead of 'blend'
and 🔪 blendSetBackground
.
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.
done in 22a598b
src/plot_api/to_image.js
Outdated
reject(new Error('Height and width should be pixel values.')); | ||
} | ||
if(format === 'svg' && imageDataOnly) { | ||
return resolve(svg); |
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.
Fast pass for svg
with imageDataOnly
where Snapshot.toSVG
gives the correct result.
Note that PDF and EPS images should first be generated as svg
and then converted to pdf or eps using another library. In-house PDF and EPS image generation is outside the scope of plotly.js.
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.
is it worth handling imageDataOnly=false
here too - and just adding the prefix? Or is it more complicated than that?
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.
Yeah, it shouldn't be too hard. I'll add that in.
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.
done in 4ec3e0f
src/plot_api/to_image.js
Outdated
|
||
if(!isBadlySet('format')) { | ||
throw new Error('Image format is not jpeg, png, svg or webp.'); | ||
} |
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.
Nice job using coerce
for this! But isBadlySet
is confusing me... it's true
if you either haven't set the attribute or if it has a valid input? Wouldn't that be isValidOrImplied
or something? Anyway I don't see how you can use the same logic for format
(which has a default) as for width
and height
(which don't).
Dimensions you could just look at fullOpts
after coerce
to see if you have something that's not undefined
(the implicit dflt
)...
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.
Oh I see, it is OK to omit dimensions, then they default to what's in the layout... might be clearer if this was just used as the default width = coerce('width', layout.width)
?
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.
might be clearer if this was just used as the default width = coerce('width', layout.width)?
right, but layout.width
might not exist at this stage.
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.
OK, but if it's not in opts
and it's not in layout
then what width do we get?
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.
We get the dflt from plots/layout_attributes.js
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.
Alright, I don't want to beat a dead horse and it's not a big deal, but I believe it would still work just fine if you coerce with a dflt
which might be undefined
at this point, then you coerce with the real dflt
later on, overriding that undefined
.
Anyway my initial comment stands, about isBadlySet
being misnamed and confusing logic.
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.
isBadlySet -> isImpliedOrValid in 3c42efb
src/plot_api/to_image.js
Outdated
// extend config for static plot | ||
var configImage = Lib.extendFlat({}, config, { | ||
staticPlot: true, | ||
plotGlPixelRatio: 2, |
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.
is plotGlPixelRatio
something people might want to override? Would it work if they could?
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.
Yeah, potentially. Good 👀
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.
done in 7565d0b
test/jasmine/tests/toimage_test.js
Outdated
}) | ||
.then(function() { | ||
expect(errors.length).toBe(1); | ||
expect(errors[0]).toBe('Image format is not jpeg, png, svg or webp.'); |
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.
You're not a fan of expect(function() { ... }).toThrow(...)
?
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.
done in 43a6150
- and make 'opaque' use Color.combine instead of hard-setting _paperdiv.
- 🔪 config assignments to we already get for free via staticPlot: true
- make Plotly.toImage bypass svgToImg even when imageDataOnly is false.
if(imageDataOnly) { | ||
return resolve(svg); | ||
} else { | ||
return resolve('data:image/svg+xml,' + encodeURIComponent(svg)); |
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.
as on
plotly.js/src/snapshot/svgtoimg.js
Line 64 in f428026
var url = 'data:image/svg+xml,' + encodeURIComponent(svg); |
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.
This broke a lot of tests. It made the toImage
promise resolved before removing the clone graph div.
Fixed in 816df16
Love it. 💃 |
- so that even 'svg' formats (which bypass svgToImg) clear the clone graph div - no need to remove *all* graph div in toImage test anymore 🎉
The bulk of this PR is in commit 8866525 (where several improvement to
Plotly.toImage
are made) and dd003ce (where thesetBackground
config option is now a little more portable)With 8866525,
Plotly.toImage
can now acceptdata/layout/config
(plain) object as in input. Previously, only existing graph divs were supported. This is important for image generation performance.In details, to generate an image using only official API methods (i.e. none of the
Plotly.Snapshot
methods), currently one must first callPlotly.newPlot
and then pass thegd
toPlotly.toImage
. To ensure thattoImage
does not mutate the existing graph div,toImage
clones it (i.e. extends the graph's data/layout) and callsPlotly.plot
on an off-screen<div>
. That is,Plotly.plot
is called twice for each generated image. Needless to say, we can do better.So, now calling
toImage
as:assumes that the data/layout/config containers are new (i.e. aren't linked to an existing graph) meaning that the containers aren't extended and
Plotly.plot
is only called once per generated image 🐎