(Plots.jl) aspect_ratio=1.0 for 3d plot

Issue

I don’t know how to set aspect_ratio=:equal for 3d plot (like axis_equal in MATLAB).
It seems that only x and y axes’ aspect ratio are equal.

Example code

using Plots


function test()
    N = 10
    xs = range(0, stop=10, length=N)
    ys = range(0, stop=20, length=N)
    zs = range(0, stop=50, length=N)
    p = plot(; aspect_ratio=1.0)
    plot!(xs, ys, zs)
end
test()

Result

@iHany, see revised improved solution using gr() backend below.

using Plots; gr(size=(600,600))

function plot_iso3d(xs, ys, zs; lw=3, lc=:red, title="Isometric 3D plot",label=false, camera=(45,30))
    # condition data for nearly isometric 3D plot 
    x12, y12, z12 = extrema(xs), extrema(ys), extrema(zs)
    d = maximum([diff([x12...]),diff([y12...]),diff([z12...])])[1] / 2
    xm, ym, zm = mean(x12),  mean(y12),  mean(z12) 

    # plot data
    p = Plots.plot(; xlabel="x",ylabel="y",zlabel="z", aspect_ratio=:equal, grid=:true)
    Plots.plot!(xlims=(xm-d,xm+d), ylims=(ym-d,ym+d), zlims=(zm-d,zm+d))
    Plots.plot!(;camera=camera)    #(azimuth,elevation) ???
    Plots.plot!(xs, ys, zs, title=title,lw=lw,lc=lc,label=label)
    Plots.plot!(xs, ys, zlims(p)[1] .+ 0*zs, lw=1, lc=:lightgray, label=false)
    Plots.plot!(xs, ylims(p)[2]  .+ 0*ys, zs, lw=1, lc=:lightgray, label=false)
    Plots.plot!(xlims(p)[1]  .+ 0*xs, ys, zs, lw=1, lc=:lightgray, label=false)
end

#input data
N = 100
xs = LinRange(0,40,N)
ys = LinRange(0,20,N) .+ 10*sin.(xs)
zs = LinRange(-10,50,N) .+ 15*sin.(xs)
plot_iso3d(xs, ys, zs)

2 Likes

Minor addition: one thing that always bothered me about doing this sort of thing in Matlab was that it wouldn’t actually do the exact same thing as automatic axis limits, because it didn’t expose the limit widening algorithm.

In Julia,

plot(x, y; xlims=Plots.widen(xmin, xmax))

gets rid of that problem. Just a bit of added sugar.

1 Like

Oh no, it’s the best? Seriously?

It works well but is quite uncomfortable :frowning:

Anyhow, thanks :slight_smile:

“improved” != best
:slight_smile:

There were related questions and PRs in github, etc…, the best solution is in the hands of Plots.jl code developers.

NB: The road ahead is straight but the slope is steep…

1 Like

@iHany, updated iso plot 3d above, with shadow projections added.

Hi, I think that the key line of @rafael.guerra’s solution is:

using Plots; gr(size=(600,600))

At least at first sight, it seems that the attribute aspect_ratio=1.0 (or aspect_ratio=:equal) does work for 3d, but assumes that the figure canvas has square proportions – which is not true by default.

If you don’t want to reset the default figure size, you can also set it for a particular plot:

plot(x,y,z, aspect_ratio=1.0, size=(600,600))

@heliosdrm, the key lines in the solution posted above lie immediately below the comment:

# condition data for nearly isometric 3D plot 
...

Maybe I’m understanding a different point from @iHany 's original question? I interpreted that the problem was that the Z axis in his example looked squashed with respect to the X and Y axes, in spite of having set the attribute aspect_ratio=1.0. My point was that the only that has to be done to see the Z axis with the desired proportion is adding size=(600,600) in the call to either gr or plot.

(Edit: or size=(n,n), for whatever n is wanted).

That is obviously not enough, and will be data dependent.
For the original example given by OP, it will produce the plot below which is far from having aspect ratio = 1:

1 Like

You are right: silly oversight from my side. :blush:

1 Like

I’m wondering if there is any update to this because I’m having problems with 3D drawing in which the axes are not all equally spaced.

I am using Plots with the GR backend.

I was able to draw this ball, centered at the origin, just fine:

But now I want to draw a rectangle to show the horizontal plane. When I do, the ball gets squished:

No amount of playing around with plot!(aspectratio=??) seems to help. How to fix??

Thanks.

If you use the workaround provided above, which defines isometric cubic plot limits around the plot center, then the keyword aspect_ratio=:equal will work fine:

Code: Plots 3D isometric globe + plane
using Plots; gr(dpi=600)

# Plot meridians and parallels
p = plot(framestyle=:none, ticks=:none)
v = range(π/20, 19π/20, 100)
for u in range(0, 2*π, 13)
    plot!(cos(u) * sin.(v), sin(u) * sin.(v), cos.(v), c=:lightgrey, lw=0.5)
end
u = range(0, 2π, 100)
for v in range(π/20, 19π/20, 9)
    plot!(cos.(u) * sin(v), sin.(u) * sin(v), fill(cos(v),100), c=:lightgrey, lw=0.5)
end

# Plot plane
d = 3/2         # plane edge half-length (also equal here to the plot limits half-length cube edge) 
plot!([-d,d,d,-d,-d], [-d,-d,d,d,-d], [0,0,0,0,0], c=:lightgrey)

# define isometric cube limits around plot center
xm, ym, zm = 0, 0, 0    # center of the plot
plot!(xlims=(xm-d,xm+d), ylims=(ym-d,ym+d), zlims=(zm-d,zm+d), aspect_ratio=:equal)

Wow! @rafael.guerra, you created the figure I was starting with. (I have more to do, but this is where I got into trouble.)

I’m trying to extract from the code in the examples what I need to do in my plotting. Can you provide the code you used to create the image I was hoping to make?

PS Is this issue that I need to do something like this?

plot!(xlims=(-r,r),ylims=(-r,r),zlims=(-r,r))

Correct.

For plots whose centroid is not at the origin, the plot boundary cube is better defined around the centroid.

Note that xlims=(-r,r),ylims=(-r,r),zlims=(-r,r) === lims=(-r,r)

PS: I have included the code I used below the plot above.

Got it! And now it’s working for me. Thank you!!

1 Like