Multiple plots: some refinements

Hi to everybody,
I am trying to make some plots for my research (Fisher contour plots) using Plots.jl. I am quite satisfied with the results I have obtained (in a very short amount of time!)

FisherMatrix = [16523.02 3319.2230;3319.2230 711.09] #Fisher matrix used in the MWE
CovarianceMatrix = inv(FisherMatrix)
σw0 = sqrt(CovarianceMatrix[1,1])
σwa = sqrt(CovarianceMatrix[2,2])
σw0wa = (CovarianceMatrix[1,2])
plot_font = "Computer Modern"

Plots.default(titlefont = (16, plot_font), fontfamily=plot_font, linewidth=1, framestyle=:box, fg_legend =:black, label=nothing, grid=false, tickfontsize=12, size = (600, 500), labelfontsize = 15, dpi = 100)


#Needed for normalization
function gaussian(μ::Float64, σ::Float64, x)
    return 1/(sqrt(2π*σ^2))*exp(-0.5*(x-μ)^2/σ^2)
end

#Drawing ellipses
t = LinRange(0,2π, 200)
θ = 0.01*atan(2σw0wa/(σw0^2-σwa^2))/2#the 0.0
a = (σw0^2+σwa^2)/2+sqrt(((σw0^2-σwa^2)^2)/4+σw0wa^2)
b = (σw0^2+σwa^2)/2-sqrt(((σw0^2-σwa^2)^2)/4+σw0wa^2)
x = a .* cos.(θ) .* cos.(t) - b .* sin.(θ) .* sin.(t)
y = a .* sin.(θ) .* cos.(t) + b .* cos.(θ) .* sin.(t)
p_ell = Plots.plot(x, y,  linewidth = 1, legend = false, fill = (0, 0.5, :red),color="red")
Plots.plot!(3x, 3y,  linewidth = 1, legend = false, fill = (0, 0.5, :red), xticks = round.(range(-3a,stop=3a,length = 3),digits=2), yticks = round.(range(-3b,stop=3b,length = 3),digits=4), xlim=(-4a,4a), ylim=(-4b,4b), color="red", ylabel = L"w_a", xlabel = L"w_0")

x = Array(LinRange(-4a,4a, 200))
pwa = Plots.plot(x, gaussian.(0., a, x)./gaussian.(0., a, 0), fill=(0, .5,:red), legend = false, xticks = round.(range(-3a,stop=3a,length = 3),digits=2), yticks = round.(range(0,stop=1,length=2),digits=2), color="red")
x = Array(LinRange(-a,a, 200))
Plots.plot!(pwa, x, gaussian.(0., a, x)./gaussian.(0., a, 0), fill=(0, .5,:red), legend = false, xticks = round.(range(-3a,stop=3a,length = 3),digits=2), yticks = round.(range(0,stop=1,length=2),digits=2), color="red", ylabel = L"w_0")

x = Array(LinRange(-4b,4b, 200))
pw0 = Plots.plot(x, gaussian.(0., b, x) ./gaussian.(0., b, 0.), fill=(0, .5,:red), legend = false,  xticks = round.(range(-3b,stop=3b,length = 3),digits=4), yticks = round.(range(0,stop=1,length = 2)),figsize=(10,10), color="red")
x = Array(LinRange(-b,b, 200))
Plots.plot!(pw0, x, gaussian.(0., b, x) ./gaussian.(0., b, 0.), fill=(0, .5,:red), legend = false, xticks = round.(range(-3b,stop=3b,length = 3),digits=4), yticks = round.(range(0,stop=1,length = 2)),figsize=(10,10), color="red", xlabel = L"w_a", ymirror=true,)

p_contour = Plots.plot(pwa, emptyplot(), p_ell, pw0, layout = (2, 2), link =:both)

plot
The result is nice, but I would like to add a couple of things.

  1. Whitespace: can I reduce/remove the white space between the subplots?
  2. Legend: I’d like to set a single legend for all the plots (possibly in the top right corner)

I’d like to obtain something such as this, obtained with matplotlib

Do youhave any suggestion? Any other Julia plot library I could use?
Best regards,
Marco

1 Like

Yes it’s quite simple to do this kind of layout in Makie, you should have no whitespace issues and can place the legend wherever you like.

For starters, have a look at legends here Legend · Makie Plotting Ecosystem and at general layouting here Layout Tutorial · Makie Plotting Ecosystem

I don’t have time right now to do an example but your request should be easily manageable with those references.

2 Likes
using CairoMakie 
fig = Figure(; resolution = (700, 700))
ax1 = Axis(fig, ylabel = "y")
ax2 = Axis(fig, xlabel = "x", ylabel = "y")
ax3 = Axis(fig, xlabel = "x", yaxisposition =:right)
pltobj1 = lines!(ax1, rand(10), rand(10), color = :red)
pltobj2 = lines!(ax2, rand(10), rand(10))
pltobt3 = lines!(ax3, rand(10), rand(10), color = :black)
leg = Legend(fig, [pltobj1, pltobj2, pltobt3],  ["one", "two", "three"], 
    tellheight = false, tellwidth = false, halign = :left, valign = :top, 
    framecolor = :orange)
fig[1,1] = ax1
fig[2,1] = ax2 
fig[2,2] = ax3
fig[1,2] = leg 
colsize!(fig.layout, 1, Relative(1/2))
rowsize!(fig.layout, 1, Relative(1/2))
fig
save("layout4.png", fig, px_per_unit = 2)

6 Likes

Thank you all.
After your suggestions, I tried using Makie.
Here is the result!

I am quite satisfied. There are a couple of minor issues I should be able to solve by myself (set number of ticks, put ticks on the lower right plot on the right y side). I am more concerned about Latex. I want to use it and it looks that it requires MakieTex.jl.
Thank you again!

The following examples are not exactly what you want, but may give you many hints.

Plots.jl examples

Jupyter notebook: public/marginal plots.ipynb at main · genkuroki/public · GitHub

using Plots
using Distributions
using KernelDensity

# generate test sample
dist_true = MvNormal([1.0, 2.0], [2.0 -1.0; -1.0 4.0])
n = 2^10
sample = rand(dist_true, n)

# kernel density estimation
X, Y = sample[1, :], sample[2, :]
k, kx, ky = kde.(((X, Y), X, Y))
ik, ikx, iky = InterpKDE.((k, kx, ky));

Example 1

layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false
a = plot(kx.x, kx.density; xlim, legend)
c = plot(ky.density, ky.x; ylim, legend, xrotation=90)
b = contour(k.x, k.y, k.density; xlim, ylim, legend)

plot(a, b, c; layout, link=:both, size=(500, 500))

2021-07-17 (0)

Example 2

layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false
a = plot(kx.x, kx.density; xlim, legend)
c = plot(ky.density, ky.x; ylim, legend, xrotation=90)
b = heatmap(k.x, k.y, k.density; xlim, ylim, legend)

plot(a, b, c; layout, link=:both, size=(500, 500))

2021-07-17 (1)

Example 3

layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false
a = plot(kx.x, kx.density; xlim, legend)
c = plot(ky.density, ky.x; ylim, legend, xrotation=90)
b = scatter(X, Y; xlim, ylim, legend, marker_z=pdf.(Ref(ik), X, Y), alpha=0.7, msw=0)

plot(a, b, c; layout, link=:both, size=(500, 500))

Example 4 (Only color is changed)

layout = @layout [
    a             _
    b{0.8w, 0.8h} c
]

xlim, ylim = extrema.((k.x, k.y))
legend = colorbar = false
a = plot(kx.x, kx.density; xlim, legend)
c = plot(ky.density, ky.x; ylim, legend, xrotation=90)
b = scatter(X, Y; xlim, ylim, legend, marker_z=pdf.(Ref(ik), X, Y), alpha=0.7, msw=0, color=:rainbow)

plot(a, b, c; layout, link=:both, size=(500, 500))

Edit: Jupyter notebook is added.

2 Likes

As far as I understand, LaTeX labels are now included in Make 0.15.

2 Likes

Following your suggestions, I have used Makie, obtaining the following result

Code (not yet polished)
c = [16523.02 3319.2230;3319.2230 711.09]
invc = inv(c)
σw0 = sqrt(invc[1,1])
σwa = sqrt(invc[2,2])
σw0wa = (invc[1,2])

function my_gaussian(μ::Float64, σ::Float64, x)
	return 1/(sqrt(2π*σ^2))*exp(-0.5*(x-μ)^2/σ^2)
end
fig = CairoMakie.Figure(resolution = (1100, 1000), textsize = 32);
t = LinRange(0,2π, 200)

θ = 0.01*atan(2σw0wa/(σw0^2-σwa^2))/2
a = (σw0^2+σwa^2)/2+sqrt(((σw0^2-σwa^2)^2)/4+σw0wa^2)
b = (σw0^2+σwa^2)/2-sqrt(((σw0^2-σwa^2)^2)/4+σw0wa^2)

x = a .* cos.(θ) .* cos.(t) - b .* sin.(θ) .* sin.(t)
y = a .* sin.(θ) .* cos.(t) + b .* cos.(θ) .* sin.(t)

dimticklabel = 36

ax2 = fig[1, 1] = Axis(fig , labelsize=50, xticklabelsize = dimticklabel,yaxisposition = (:right), yticklabelsize = dimticklabel, aspect = AxisAspect(1), ylabel = L"P/P_{max}", ylabelsize = 39, yticks = ([0,1], [L"%$i" for i in 0:1]))


ax1 = fig[2, 1] = Axis(fig, xticklabelsize = dimticklabel, yticklabelsize = dimticklabel, aspect = AxisAspect(1), xlabel = L"w_a",
ylabel = L"w_0", ylabelsize = 39, xlabelsize = 39, yticks = ([-3b, 0, 3b], [L"%$i" for i in round.(-3b:3b:3b, digits = 4)]), xticks = ([-3a, 0, 3a], [L"%$i" for i in round.(-3a:3a:3a, digits = 2)]))


ax3 = fig[2, 2] = Axis(fig, yaxisposition = (:right), xticklabelsize = dimticklabel, yticklabelsize = dimticklabel, aspect = AxisAspect(1), xlabel = L"w_0", xlabelsize = 39, xticks = ([-3b, 0, 3b], [L"%$i" for i in round.(-3b:3b:3b, digits = 4)]),ylabelsize = 39, yticks = ([0,1], [L"%$i" for i in 0:1]),ylabel = L"P/P_{max}")

CairoMakie.lines!(ax1, x, y, color = "red" )
CairoMakie.lines!(ax1, 3x, 3y, color = "red")

band!(ax1, x, 0, y , color=("red", 1.0))
band!(ax1, 3x, 0, 3y , color=("red", 0.5))

x = Array(LinRange(-4a,4a, 200))
CairoMakie.lines!(ax2, x, my_gaussian.(0., a, x)./my_gaussian.(0., a, 0), color = "red")
band!(ax2, x, 0, my_gaussian.(0., a, x)./my_gaussian.(0., a, 0) , color=("red", 0.5), textsize = 32)
x = Array(LinRange(-1a,1a, 200))
pltobj1 = band!(ax2, x, 0, my_gaussian.(0., a, x)./my_gaussian.(0., a, 0) , color=("red", 1.0))

x = Array(LinRange(-4b,4b, 200))
CairoMakie.lines!(ax3, x, my_gaussian.(0., b, x)./my_gaussian.(0., b, 0), color = "red")
band!(ax3, x, 0, my_gaussian.(0., b, x)./my_gaussian.(0., b, 0) , color=("red", 0.5))
x = Array(LinRange(-1b,1b, 200))
band!(ax3, x, 0, my_gaussian.(0., b, x)./my_gaussian.(0., b, 0) , color=("red", 1.0))


ax2.xticks = -3a:3a:3a

linkxaxes!(ax1, ax2)
leg = Legend(fig, [pltobj1],  ["WL"], 
    tellheight = false, tellwidth = false, halign = :right, valign = :top, 
    framecolor = :black, labelsize =32)
fig[1,2] = leg

hidexdecorations!(ax2, ticks = false, ticklabels = true, label = false)
hideydecorations!(ax2, ticks = false, ticklabels = false, label = false)
hidexdecorations!(ax3, ticks = false, ticklabels = false, label = false)
hideydecorations!(ax3, ticks = false, ticklabels = false, label = false)
hidexdecorations!(ax1, ticks = false, ticklabels = false, label = false)
hideydecorations!(ax1, ticks = false, ticklabels = false, label = false)


colsize!(fig.layout, 1, Relative(0.5))
rowsize!(fig.layout, 1, Relative(0.5))
colsize!(fig.layout, 2, Relative(0.5))
rowsize!(fig.layout, 2, Relative(0.5))
trim!(fig.layout)
CairoMakie.save("contour.png",fig)
fig

There are still a couple of things I’d like to improve.

  • I’d like to write “WL” and “max” using an equivalent of \mathrm. Actually it looks it is not supported yet, but I maybe wrong (@sdanisch )
  • I’d like the legend box to be perfectly aligned, while now is a bit higher than the [1,1] box
  • I’d like to remove the white space between the subplots, as much as possible. In this situation it does not matter, but in general does kind of plots are 8x8 and removing the white space is crucial.

Overall, I am quite satisfied. Makie is quite beautiful!

3 Likes

The reason why you have unnecessary whitespace is that you have set your axes to aspect = 1. The layout can’t optimize for multiple objects having their own aspect ratios, so what happens is that the axes get assigned one rectangular area each which is probably not square, but they adjust themselves to a square inside that rectangle. That leaves whitespace on the sides, and your legend at the top is aligned with the edge of that whitespace (which is of course not directly visible).

You can also think about it this way: If you specify the aspects of all axes in the Figure, it’s really unlikely that they fit into the given Figure size without whitespace, by chance. A workaround is to go the other way, specify the sizes of the axes (or other content), then adjust the Figure to the combined layout size. It’s currently not a documented approach but it works (with reaching into internals, though):

f = Figure(backgroundcolor = :gray90)
axs = [Axis(f[i, j], width = 200, height = 200)
    for i in 1:4, j in 1:2 if (i, j) != (1, 2)]

ls = map(axs) do ax
    lines!(ax, cumsum(randn(100)), color = rand(RGBf0))
end

hidexdecorations!.(axs[[1, 2, 3, 5, 6]])

Legend(f[1, 2], ls, string.(1:7), valign = :top, halign = :right, nbanks = 2)

# resize figure to the size reported by its top layout. this will only work
# if the layout size is determinable (known size elements in all rows / columns)
resize!(
    f.scene,
    f.layout.layoutobservables.reportedsize[]...
)

save("test.png", f)

2 Likes

Using Plots and GR you can more or less obtain what you wanted originally by using the margin attribute and creating an fake plot at the empty space:

empty = Plots.scatter(
  rand(1,3),rand(1,3),
  color=["red" "blue" "green"],
  ticks=:none,axis=false,
  label=[ "Series 1" "Series 2" "Series 3" ],
  legendfont = (10, plot_font),
  markersize=0.5,
  marker=:square,
  markerstrokewidth=0,
  markeralpha=0.5,
  legend=:topleft
)


using Plots.Measures
p_contour = Plots.plot(
  pwa, 
  empty, 
  p_ell, 
  pw0, 
  layout = (2, 2), #link =:both,                              
  margin=0mm,
)

image

full code
using Plots, LaTeXStrings

FisherMatrix = [16523.02 3319.2230;3319.2230 711.09] #Fisher matrix used in the MWE
CovarianceMatrix = inv(FisherMatrix)
σw0 = sqrt(CovarianceMatrix[1,1])
σwa = sqrt(CovarianceMatrix[2,2])
σw0wa = (CovarianceMatrix[1,2])
plot_font = "Computer Modern"

Plots.default(
  titlefont = (16, plot_font), 
  fontfamily=plot_font, linewidth=1, 
  framestyle=:box, fg_legend =:black, 
  label=nothing, grid=false, tickfontsize=12, 
  size = (600, 500), labelfontsize = 15, dpi = 100,
  margin=-2mm
)


#Needed for normalization
function gaussian(μ::Float64, σ::Float64, x)
    return 1/(sqrt(2π*σ^2))*exp(-0.5*(x-μ)^2/σ^2)
end


#Drawing ellipses
t = LinRange(0,2π, 200)
θ = 0.01*atan(2σw0wa/(σw0^2-σwa^2))/2#the 0.0
a = (σw0^2+σwa^2)/2+sqrt(((σw0^2-σwa^2)^2)/4+σw0wa^2)
b = (σw0^2+σwa^2)/2-sqrt(((σw0^2-σwa^2)^2)/4+σw0wa^2)
x = a .* cos.(θ) .* cos.(t) - b .* sin.(θ) .* sin.(t)
y = a .* sin.(θ) .* cos.(t) + b .* cos.(θ) .* sin.(t)
p_ell = Plots.plot(x, y,  linewidth = 1, legend = false, fill = (0, 0.5, :red),color="red")
Plots.plot!(3x, 3y,  linewidth = 1, legend = false, fill = (0, 0.5, :red), xticks = round.(range(-3a,stop=3a,length = 3),digits=2), yticks = round.(range(-3b,stop=3b,length = 3),digits=4), xlim=(-4a,4a), ylim=(-4b,4b), color="red", ylabel = L"w_a", xlabel = L"w_0")

x = Array(LinRange(-4a,4a, 200))
pwa = Plots.plot(x, gaussian.(0., a, x)./gaussian.(0., a, 0), fill=(0, .5,:red), legend = false, xticks = round.(range(-3a,stop=3a,length = 3),digits=2), yticks = round.(range(0,stop=1,length=2),digits=2), color="red")
x = Array(LinRange(-a,a, 200))
Plots.plot!(pwa, x, gaussian.(0., a, x)./gaussian.(0., a, 0), fill=(0, .5,:red), legend = false, xticks = round.(range(-3a,stop=3a,length = 3),digits=2), yticks = round.(range(0,stop=1,length=2),digits=2), color="red", ylabel = L"w_0")

x = Array(LinRange(-4b,4b, 200))
pw0 = Plots.plot(x, gaussian.(0., b, x) ./gaussian.(0., b, 0.), fill=(0, .5,:red), legend = false,  xticks = round.(range(-3b,stop=3b,length = 3),digits=4), yticks = round.(range(0,stop=1,length = 2)),figsize=(10,10), color="red")
x = Array(LinRange(-b,b, 200))
Plots.plot!(pw0, x, gaussian.(0., b, x) ./gaussian.(0., b, 0.), fill=(0, .5,:red), legend = false, xticks = round.(range(-3b,stop=3b,length = 3),digits=4), yticks = round.(range(0,stop=1,length = 2)),figsize=(10,10), color="red", xlabel = L"w_a", ymirror=true,)


empty = Plots.scatter(
  rand(1,3),rand(1,3),
  color=["red" "blue" "green"],
  ticks=:none,axis=false,
  label=[ "Series 1" "Series 2" "Series 3" ],
  legendfont = (10, plot_font),
  markersize=0.5,
  marker=:square,
  markerstrokewidth=0,
  markeralpha=0.5,
  legend=:topleft
)


using Plots.Measures
p_contour = Plots.plot(pwa, empty, p_ell, pw0, layout = (2, 2), #link =:both, 
  margin=0mm,
)
1 Like

[quote=“marcobonici, post:1, topic:64800”]

How to resolve that alert?

using LaTeXStrings

julia> Plots.plot!(3x, 3y, linewidth = 1, legend = false, fill = (0, 0.5, :red), xticks = round.(range(-3a,stop=3a,length = 3),digits=2), yticks = round.(range(-3b,stop=3b,length = 3),digits=4), xlim=(-4a,4a), ylim=(-4b,4b), color=“red”, ylabel = L"w_a", xlabel = L"w_0")
latex: failed to create a dvi file
latex: failed to create a dvi file
latex: failed to create a dvi file
latex: failed to create a dvi file
latex: failed to create a dvi file
latex: failed to create a dvi file
latex: failed to create a dvi file
latex: failed to create a dvi file

Thank you @jules ! Using your suggestions, the plot is quite nicer now.

The last issue is about the distance between the subplots. In this example (made with matplotlib) the distance between the subplots is constant

Is it possible to set the distance between the subplots in Makie? In particular, when I add P/P_{max} on the y label, the distance between plots grows. If I understand correctly, Makie does so in order to avoid overlap between plots, am I right?

A possible workaround, is removing the ylabel, which is not necessary, obtaining the following result.

Do you have any further suggestions?
Thank you again,
Marco

In this case you have to tell Makie that you want the right protrusion of the upper-left axis to be 0, so that it doesn’t grow the column gap. This can be done with a different alignmode. Try setting ax.alignmode = Mixed(right = MakieLayout.Protrusion(0)) for the upper-left axis.

f = Figure(resolution = (600, 600))

ax = Axis(f[1, 1], ylabel = "a label", yaxisposition = :right,
    alignmode = Mixed(right = MakieLayout.Protrusion(0))
)
Axis(f[2, 1])
Axis(f[2, 2])

f

2 Likes

Thank you!
Using protrusion, I obtained the following.

About the y label, it’s exactly what I wanted.
The last point (and then I am done) regards the shapes of the subplots. Manually setting the (overall) plot size I can fix it. Is there a way to use both protrusin and fix the subplots size?

Thank you again,
Marco

1 Like

What exactly is your goal regarding the figure or subplot size? Looks good to me

1 Like

Indeed, this is true! This plot looks good!
However, I had to set manually the size of the figure to obtain (almost) squared subplots. In general, I make plots with higher number of rows and columns (from 5 to 8) and, if possible, I would prefer not to set manually the plot size to obtain squared subplots. However, if this is not possible, the result obtained is very good and requires only a bit of manual work.

So the way I showed above where you set the subplot size, and then the figure size is derived from that, doesn’t work for you? That sounds like what you want to me.

1 Like

Yeah, but I am not being able to combine the two methods together.
I obtain the following error

type Mixed has no field padding
Code
f = Figure(backgroundcolor = :gray90)
axs = [Axis(f[i, j], width = 200, height = 200, alignmode = Mixed(right = MakieLayout.Protrusion(0)))
	for i in 1:4, j in 1:2 if (i, j) != (1, 2)]

ls = map(axs) do ax
	lines!(ax, cumsum(randn(100)), color = rand(RGBf0))
end

hidexdecorations!.(axs[[1, 2, 3, 5, 6]])

Legend(f[1, 2], ls, string.(1:7), valign = :top, halign = :right, nbanks = 2)

# resize figure to the size reported by its top layout. this will only work
# if the layout size is determinable (known size elements in all rows / columns)
resize!(
	f.scene,
	f.layout.layoutobservables.reportedsize[]...
)

f

Am I doing something bad?

Hi @marcobonici When you are done, can you post your full codes here? I found it very useful. Thanks!

3 Likes

Oh that is a bug, I’ll have a look tonight.

1 Like

Thank you, I really appreciate that:)