JuliaPlots / Plots.jl

Powerful convenience for Julia visualizations and data analysis
https://docs.juliaplots.org
Other
1.84k stars 358 forks source link

[BUG] savefig not honoring size=() and dpi=() attributes with PlotlyJS backend #3199

Open nbsmokee opened 3 years ago

nbsmokee commented 3 years ago

I think this has not been reported before. I found that, when using the PlotlyJS backend, the size and dpi kwargs seem to be ingored and thus don't produce the expected results.

Details

y = randn(20, 4) plot(y, size=(300,400), show=true) savefig("test.png")


- Considering `dpi`, this value seems to be ignored at all, since the plot that shows up in the plot pane has the same size as it has when omitting the `dpi` kwarg (contrary to GR, where the size of the plot in the plot pane will adjust accordingly (which I don't find helpful, but that's another topic :sweat_smile: )) and the resulting image file  always ends up having the standard resolution of 700 * 500.
```julia
# dpi
using Plots
plotlyjs()

y = randn(20, 4)
plot(y, dpi=1200, show=true)
savefig("test.png")

Backends

This bug occurs on ( insert x below )

Backend yes no untested
gr (default) x
pyplot x
plotly x
plotlyjs x
pgfplotsx x
inspectdr x

Versions

Plots.jl version: v1.9.1 Backend version (]st -m): PlotlyJS v0.14.0 Output of versioninfo():

Julia Version 1.5.3
Commit 788b2c77c1* (2020-11-09 13:37 UTC)
Platform Info:
  OS: Linux (x86_64-pc-linux-gnu)
  CPU: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-10.0.1 (ORCJIT, skylake)
Environment:
  JULIA_EDITOR = "/usr/lib/electron9/electron"
  JULIA_NUM_THREADS = 
Ezequiel92 commented 3 years ago

pgfplotsx has the same problem with the size parameter., it somewhat ignores it. Setting a size changes the saved image, but the result is not the one chosen and appears to depend on the data to be plotted.

Default size

GR defaults to 600x400 px, while PGFPlotsX default to 914x539 px, using the following MWE

using Plots, LaTeXStrings  
data = rand(10) 

plot(data) 
savefig("size_gr.png") 

pgfplotsx() 
plot(data) 
savefig("size_pgfplotsx.png")  

The result is difficult to replicate, because with random data each run can end (or not) in a different final image size for PGFPlotsX.

The aspect ratio of the actual plot (everything but the legend) seems similar enough though, doing

using Plots, LaTeXStrings  
data = rand(10) 

plot(data) 
savefig("size_gr.png") 

pgfplotsx() 
plot(data, legend=false) 
savefig("size_pgfplotsx.png")  

I get 600x400 px (600/400 = 1.5) for GR, as it should, and 860x546 px (860/546 = 1.575) for PGFPlotsX. So, in the ballpark.

Personalized size

The fact that kind of tries to respect the aspect ratio can be seen again doing

using Plots, LaTeXStrings  
pgfplotsx() 

for i in 1:30
    plot(rand(10), size=(600,600)) 
    savefig("size_pgfplotsx_" * string(i) * ".png") 
end 

I get 30 images with varying sizes: 914x849 px (x2), 914x841 px (x1), 914x839 px (x26), 923x839 px (x1), but all close to 1:1, if it weren’t for the legend. A different run would give different results, obviously. Clearly is taking the input size, the data and then choosing the final size, but as far as I know it is not documented which algorithm is used, as to reversed it and control effectively the final size.

In practice this is "solved" by trying different inputs until you get one output you like/can use. What is especially annoying is the preference for odd values making impossible to use a series of images for a video with VideoIO.jl (in the example above not a single one got an even x even value).

This is maybe tangentially related to #2303, where GR produces results off by one pixel.

Versions

From julia> versioninfo()

Julia Version 1.5.3 
Commit 788b2c77c1 (2020-11-09 13:37 UTC) 
Platform Info: 
  OS: Windows (x86_64-w64-mingw32) 
  CPU: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz 
  WORD_SIZE: 64 
  LIBM: libopenlibm 
  LLVM: libLLVM-9.0.1 (ORCJIT, skylake) 

From (@v1.5) pkg> status

[b964fa9f] LaTeXStrings v1.2.0 
[8314cec4] PGFPlotsX v1.2.10 
[91a5bcdd] Plots v1.9.1 
[f3b207a7] StatsPlots v0.14.17 
tamits60 commented 3 years ago

I had this problem too. For Plotly, this works for me as a workaround.

using Plots
import Plotly
plotlyjs()

y = randn(20, 4)
plt = plot(y, size=(300,400), show=true)

width, height = plt.attr[:size]
Plots.prepare_output(plt)
Plotly.savefig(Plots.plotlybase_syncplot(plt), "test.png", width=width, height=height)
ultrapoci commented 2 years ago

I'm trying to use PlotlyJS in Pluto, but I cannot save files using the desired resolution. I've tried the above method but it doesn't work. I'm forced to use the GR backend to save files, but it lacks the nice interactive features plotlyjs has.

ranocha commented 1 year ago

For reference, a similar workaround for PlotlyJS in Pluto is

import PlotlyJS
width, height = plt.attr[:size]
Plots.prepare_output(plt)
PlotlyJS.savefig(Plots.plotlyjs_syncplot(plt), "plot.pdf", width=width, height=height)
mattcbro commented 11 months ago

This problem definitely occurs using the Plotly backend in Plots.jl . Unfortunately the savefig exported in Plots.jl does NOT have width and height arguments. I haven't yet tried @ranocha solution.

This is an important issue for automating and creating plots for documents. I hope that the Plots.jl crew can remedy this. Interestingly, if you save the figure directly from within the vscode Julia plots pane, the svg file will have the correct sizing.

eduardkieser commented 9 months ago

This is still a problem for my setup, but I can also not use the work proposed by @ranocha because plotlybase_syncplot is not available:

Julia Version 1.9.2

Plotly v0.4.1 PlotlyBase v0.8.19 PlotlyJS v0.18.13 PlotlyKaleido v2.2.2 Plots v1.39.0

using Plots
import Plotly
plotlyjs()

function save_pdf(p, filename)
    width, height = p.attr[:size]
    Plots.prepare_output(p)
    Plotly.savefig(Plots.plotlybase_syncplot(p), filename, width=width, height=height)
end

p = plot(...)
save_pdf(p, filename)

Results in

ERROR: UndefVarError: `plotlybase_syncplot` not defined

Any suggestion or alternative work around would be greatly appreciated.

axla-io commented 2 months ago

It's now called plotlyjs_syncplot