Plotting a filled contour and saving as eps or svg

I would like to simply plot a filled contour for given x, y, and z variables (or a function z(x,y)), and save it to eps (or svg). My goal is to make a GitHub repo of simple plotting examples that export to eps, svg, pdf, etc., that I can then use as a basis for making publication-quality figures. I believe getting such simple examples out there will help the Julia users who struggle for simple tasks like me :slight_smile: Anyway, here is my current non-working MWE using julia v0.7 (it’s in the repo too):

using Pkg
Pkg.activate(".")

function peaks(x, y)
    z = 3 * (1 - x)^2 * exp(-x^2 - (y + 1)^2)
    z += -10 * (x / 5 - x^3 - y^5) * exp(-x^2 - y^2)
    z += -1/3 * exp(-(x+1)^2 - y^2)
    return z
end

x = range(-3, stop = 3, length = 100)
y = range(-2, stop = 2, length = 80)
levels = collect(-1:0.2:1)

using Plots
# using PyPlot
# using Plotly, ORCA
plt = contourf(x, y, peaks; levels = levels)
savefig(plt, "test.eps")

Now I can comment/uncomment the package (or backend) being used. The code above is in a file called plot_a_contour_of_peaks.jl and lives in a project where I have added the packages Plots, PyPlot, Plotly, and ORCA.

I then run (julia version 0.7)

julia> include("plot_a_contour_of_peaks.jl")

and get the following errors. I’m looking for help to figure out how to solve these errors!

  • using Plots (for which the default backend is GR.jl if I understand correctly)

    ERROR: LoadError: MethodError: no method matching _show(::IOStream, ::MIME{Symbol("image/eps")}, ::Plots.Plot{Plots.GRBackend})
    Closest candidates are:
      _show(::IO, ::MIME{Symbol("text/html")}, ::Plots.Plot) at /Users/benoitpasquier/.julia/packages/Plots/ex9On/src/output.jl:171
      _show(::IO, ::MIME{Symbol("text/plain")}, ::Plots.Plot) at /Users/benoitpasquier/.julia/packages/Plots/ex9On/src/output.jl:212
      _show(::IO, ::MIME{Symbol("application/postscript")}, ::Plots.Plot{Plots.GRBackend}) at /Users/benoitpasquier/.julia/packages/Plots/ex9On/src/backends/gr.jl:1379
      ...
    Stacktrace:
     [1] show(::IOStream, ::MIME{Symbol("image/eps")}, ::Plots.Plot{Plots.GRBackend}) at /Users/benoitpasquier/.julia/packages/Plots/ex9On/src/output.jl:206
     [2] eps(::Plots.Plot{Plots.GRBackend}, ::String) at /Users/benoitpasquier/.julia/packages/Plots/ex9On/src/output.jl:42
     [3] savefig(::Plots.Plot{Plots.GRBackend}, ::String) at /Users/benoitpasquier/.julia/packages/Plots/ex9On/src/output.jl:123
     [4] top-level scope at none:0
     [5] include at ./boot.jl:317 [inlined]
     [6] include_relative(::Module, ::String) at ./loading.jl:1038
     [7] include(::Module, ::String) at ./sysimg.jl:29
     [8] include(::String) at ./client.jl:398
     [9] top-level scope at none:0
    in expression starting at /Users/benoitpasquier/Projects/JuliaPlotting/plot_a_contour_of_peaks.jl:16
    
  • using PyPlot.jl

    ERROR: LoadError: PyError ($(Expr(:escape, :(ccall(#= /Users/benoitpasquier/.julia/packages/PyCall/rUul9/src/pyfncall.jl:44 =# @pysym(:PyObject_Call), PyPtr, (PyPtr, PyPtr, PyPtr), o, pyargsptr, kw))))) <class 'TypeError'>
    TypeError("float() argument must be a string or a number, not 'PyCall.jlwrap'")
      File "/Users/benoitpasquier/.julia/packages/Conda/m7vem/deps/usr/lib/python3.7/site-packages/matplotlib/pyplot.py", line 2938, in contourf
        ret = ax.contourf(*args, **kwargs)
      File "/Users/benoitpasquier/.julia/packages/Conda/m7vem/deps/usr/lib/python3.7/site-packages/matplotlib/__init__.py", line 1867, in inner
        return func(ax, *args, **kwargs)
      File "/Users/benoitpasquier/.julia/packages/Conda/m7vem/deps/usr/lib/python3.7/site-packages/matplotlib/axes/_axes.py", line 6290, in contourf
        contours = mcontour.QuadContourSet(self, *args, **kwargs)
      File "/Users/benoitpasquier/.julia/packages/Conda/m7vem/deps/usr/lib/python3.7/site-packages/matplotlib/contour.py", line 890, in __init__
        kwargs = self._process_args(*args, **kwargs)
      File "/Users/benoitpasquier/.julia/packages/Conda/m7vem/deps/usr/lib/python3.7/site-packages/matplotlib/contour.py", line 1476, in _process_args
        x, y, z = self._contour_args(args, kwargs)
      File "/Users/benoitpasquier/.julia/packages/Conda/m7vem/deps/usr/lib/python3.7/site-packages/matplotlib/contour.py", line 1534, in _contour_args
        x, y, z = self._check_xyz(args[:3], kwargs)
      File "/Users/benoitpasquier/.julia/packages/Conda/m7vem/deps/usr/lib/python3.7/site-packages/matplotlib/contour.py", line 1565, in _check_xyz
        z = ma.asarray(args[2], dtype=np.float64)
      File "/Users/benoitpasquier/.julia/packages/Conda/m7vem/deps/usr/lib/python3.7/site-packages/numpy/ma/core.py", line 7810, in asarray
        subok=False, order=order)
      File "/Users/benoitpasquier/.julia/packages/Conda/m7vem/deps/usr/lib/python3.7/site-packages/numpy/ma/core.py", line 2785, in __new__
        order=order, subok=True, ndmin=ndmin)
    
    Stacktrace:
     [1] pyerr_check at /Users/benoitpasquier/.julia/packages/PyCall/rUul9/src/exception.jl:60 [inlined]
     [2] pyerr_check at /Users/benoitpasquier/.julia/packages/PyCall/rUul9/src/exception.jl:64 [inlined]
     [3] macro expansion at /Users/benoitpasquier/.julia/packages/PyCall/rUul9/src/exception.jl:84 [inlined]
     [4] __pycall!(::PyCall.PyObject, ::Ptr{PyCall.PyObject_struct}, ::PyCall.PyObject, ::PyCall.PyObject) at /Users/benoitpasquier/.julia/packages/PyCall/rUul9/src/pyfncall.jl:44
     [5] _pycall!(::PyCall.PyObject, ::PyCall.PyObject, ::Tuple{StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},typeof(peaks)}, ::Int64, ::PyCall.PyObject) at /Users/benoitpasquier/.julia/packages/PyCall/rUul9/src/pyfncall.jl:22
     [6] _pycall!(::PyCall.PyObject, ::PyCall.PyObject, ::Tuple{StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}},typeof(peaks)}, ::Base.Iterators.Pairs{Symbol,Array{Float64,1},Tuple{Symbol},NamedTuple{(:levels,),Tuple{Array{Float64,1}}}}) at /Users/benoitpasquier/.julia/packages/PyCall/rUul9/src/pyfncall.jl:11
     [7] #pycall#88(::Base.Iterators.Pairs{Symbol,Array{Float64,1},Tuple{Symbol},NamedTuple{(:levels,),Tuple{Array{Float64,1}}}}, ::Function, ::PyCall.PyObject, ::Type{PyCall.PyAny}, ::StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}, ::Vararg{Any,N} where N) at /Users/benoitpasquier/.julia/packages/PyCall/rUul9/src/pyfncall.jl:86
     [8] (::getfield(PyCall, Symbol("#kw##pycall")))(::NamedTuple{(:levels,),Tuple{Array{Float64,1}}}, ::typeof(PyCall.pycall), ::PyCall.PyObject, ::Type{PyCall.PyAny}, ::StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}, ::Vararg{Any,N} where N) at ./none:0
     [9] #contourf#34(::Base.Iterators.Pairs{Symbol,Array{Float64,1},Tuple{Symbol},NamedTuple{(:levels,),Tuple{Array{Float64,1}}}}, ::Function, ::StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}, ::Vararg{Any,N} where N) at /Users/benoitpasquier/.julia/packages/PyPlot/fZuOQ/src/PyPlot.jl:179
     [10] (::getfield(PyPlot, Symbol("#kw##contourf")))(::NamedTuple{(:levels,),Tuple{Array{Float64,1}}}, ::typeof(contourf), ::StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}, ::StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}, ::Function) at ./none:0
     [11] top-level scope at none:0
     [12] include at ./boot.jl:317 [inlined]
     [13] include_relative(::Module, ::String) at ./loading.jl:1038
     [14] include(::Module, ::String) at ./sysimg.jl:29
     [15] include(::String) at ./client.jl:398
     [16] top-level scope at none:0
    in expression starting at /Users/benoitpasquier/Projects/JuliaPlotting/plot_a_contour_of_peaks.jl:18
    
  • using Plotly and ORCA:

    ERROR: LoadError: UndefVarError: contourf not defined
    Stacktrace:
     [1] top-level scope at none:0
     [2] include at ./boot.jl:317 [inlined]
     [3] include_relative(::Module, ::String) at ./loading.jl:1038
     [4] include(::Module, ::String) at ./sysimg.jl:29
     [5] include(::String) at ./client.jl:398
     [6] top-level scope at none:0
    in expression starting at /Users/benoitpasquier/Projects/JuliaPlotting/plot_a_contour_of_peaks.jl:18

The first error reads as though it didn’t recognise the .eps filetype, so you could try savefig with a .svg / .html or .(postscript?) typed filename
Third error says that the contourf function wasn’t found, so the plotly docs may have some more info on contours

Thank you for chiming in!

The first error reads as though it didn’t recognise the .eps filetype, so you could try savefig with a .svg / .html or .(postscript?) typed filename

Yes, I can save this as a .svg and .html file. However, the .eps format seems to currently be the preferred format for publication in the scientific literature. But maybe I am wrong about that? I am also sure there are some workarounds. But I’d like to reiterate what I am trying to do here: I would like a simple MWE of plotting a filled contour in Julia and exporting it to a flawless figure in .eps format ready for being included in a LaTeX document for publication. The reason I emphasized “flawless” is because there is a problem with the GR backend that I forgot to mention—it does not do filled contours. Instead, GR’s contourf shows a heatmap. (I was just told about this on slack a few hours ago.)

Third error says that the contourf function wasn’t found, so the plotly docs may have some more info on contours

I’ll try to work this one out.

So I got Plotly.jl to save an .eps but the background is all black for some reason. Note that this requires the ORCA.jl package, which does not work with using Plots and the plotly() backend—It needs to be using Plotly directly and the arguments for ORCA.savefig must be of the PlotlyBase.Plot types specifically.

I am giving up for now. If somebody has a MWE of julia code in v0.7+ plotting a filled contour given x, y, z, and levels, and exporting it into a flawless .eps (flawless = usable for publication)—I am looking for that.

But so far it seems my best solution is to learn how to transfer julia-created data to a python readable format and then (learn and) use python to plot things. Or figure out a way to transfer that data to MATLAB because MATLAB just works the expected way in this case:

contourf(X, Y, Z, levels)
export_fig("test.eps")

I find myself spending way too much time trying to figure out how to plot basic figures, and this is not encouraging considering that I fear the real problems and complications will come when I will want to add details to these plots.

So I did not give up in the end, and figured out a way with PyPlot. New MWE:

using Pkg
Pkg.activate(".")

function peaks(x, y)
    z = 3 * (1 - x)^2 * exp(-x^2 - (y + 1)^2)
    z += -10 * (x / 5 - x^3 - y^5) * exp(-x^2 - y^2)
    z += -1/3 * exp(-(x+1)^2 - y^2)
    return z
end

x = range(-3, stop = 3, length = 100)
y = range(-2, stop = 2, length = 80)
levels = collect(-4:0.25:4)
vmin = minimum(levels)
vmax = maximum(levels)

using Plots
pyplot() # Set the backend to PyPlot
plt = contourf(x, y, peaks, levels = levels, clim = (vmin, vmax))
display(plt)
savefig(plt, "test.eps")
savefig(plt, "test.png")
savefig(plt, "test.svg")
savefig(plt, "test.html")

And now the real fight begins for tweaking it to my liking :confused:

I don’t know if you are aware of PGFPlotsX:

I thought I read somewhere that PGFPlots did not do well with filled contours - and I cannot find an example of it either…

I am generally wary of filled contours because of low data-ink ratio, but it should be possible natively in pgfplots. If you want to contribute a demo graph, please make a PR or just post it here and I will do it.

Out of a certain experience i have some doubts, that

exists, but still, you need vector graphics with reasonable support for fonts.
SVG 2 EPS exists in various flavours - based on librsvg or inkscape. GitHub - lobingera/Rsvg.jl: adaptation of the rsvg lib can be used for eps output, within julia. And yes, there might be font issues.

Just to be clear, I meant flawless in showing the data, not the fonts. (I personally do not really care that much about the fonts as long as I can have a LaTeX one.) What I mean by flawless is that I want my filled contour plot to:

  • match the colorbar exactly
  • be vectorized (not a rasterized image),
  • with the contour fillings inside of polygons that are at least as fine as the data
  • not show artefact lines appearing between fillings or inside polygons (A major issue with MATLAB’s plotting since version 2014b I think)

I strongly disagree with the data-ink ratio argument against filled contours. Evaluating the color of a line is much harder than the color of a somewhat thicker filling. Actually in (most?) cases, the filling can be physically interpreted as a coloring of the data. On the other hand, non-filled contours require an added interpretation for the reader—that the data between contours must be between values of contours surrounding it. Additionally non-filled contours can be ambiguous (e.g., a single contour will not give any information on the gradients). Finally, overall, filled-contours are easier to interpret, and (personally) aesthetically gentler on the eyes.

Certainly. This is why many packages just make them B&W and add labels on the contour lines by default.

just for my curiosity: What tool does fit to all your points? And can you reference an example?

Well I figured it out using PyPlot I think, and I keep updating some tiny examples in the repo I linked in the first post (this one). My goal is to use this repo as a little personal archive of julia MWEs for plotting that do what I want and can output to .eps for publication.

Otherwise before that I was using MATLAB, but it involved doing a lot of tweaking. (For example, you can find examples of what I did with MATLAB in this paper, Figures 2, 5, 6, 8, 9, 10, 11, A1, and C1)

[edit: I did not mean this to be a reply to Tamas but I don’t know how to remvove that part of my post]

That’s peanuts for GMT (whose output format is PostScript). Just a bit more elaborated example

My take is that EPS is obsolete in the scientific literature and everywhere accepts PDF now.

1 Like

I guess you are right… I did not know about this. If you don’t mind me asking, what are the advantages of PDF over EPS? I just thought that I wanted a vectorized format (but not SVG because that’s not generally accepted by journals), so I have been using EPS for a little while now. But I shall print everything in PDF from now on :slight_smile: Thanks for the suggestion!

Oh I did not know about GMT, thanks for the suggestion. However, I think your example is actually quite representative of what I do not want: Contours in the colorbars that do not match the data being plotted, and the data is a rasterized heatmap rather than vectorized filled contours.

I didn’t intend to say that the example was exactly what you were looking for. It doesn’t even have contours. If you want contours see simple example. That one doesn’t fill the contours but could be made to do it.

EPS is mostly Postscript + some extras, so this comparison may be informative. PS is effectively a programming language with global state etc, that renders the output, whereas PDF, which can be considered its successor, is effectively data about paths, allowing programs to interpret and edit it in an easier way. Also, PDF has some other nifty features for fonts, transparency, color management, etc.

1 Like