I’m animating a rotation of a 3D scatter plot. As the figure rotates, it changes size. This is because the camera distance to the plot changes to keep the field of view “full” as the plot rotates but it also avoids cutting off corners. As a result, the plot “pulsates” or “breathes” as it rotates. I’ve used chat to no end to try and fix this but nothing that has been suggested actually solves the problem. It seems the right thing to do would be to fix the distance between the camera and the 3dplot. But every attempt to do that has caused an error or left the problem intact. Anyone have any ideas?
Here is a MWE (not my actual plot).
using GLMakie
# Create your figure and axis
fig = Figure()
ax = Axis3(fig[1, 1])
# Your plot data here
# Example: surface plot
x = y = range(-5, 5, length=50)
z = [sin(sqrt(x^2 + y^2)) for x in x, y in y]
surface!(ax, x, y, z)
# Set up the camera with fixed axis
cam3d!(ax, fixed_axis=true)
# Now animate the rotation
record(fig, "rotation.mp4", 1:120) do frame
rotate_cam!(ax.scene, (0, 0.05, 0)) # Rotate around z-axis
end
This is controlled by the viewmode setting, you need :fitAxis3 | Makie
I emphasized the problem you mention in the docs snippet below
viewmode
Defaults to :fitzoom
The view mode affects the final projection of the axis by fitting the axis cuboid into the available space in different ways.
:fit uses a fixed scaling such that a tight sphere around the cuboid touches the frame edge. This means that the scaling doesn’t change when rotating the axis (the apparent size of the axis stays the same), but not all available space is used. The chosen aspect is maintained using this setting.
:fitzoom uses a variable scaling such that the closest cuboid corner touches the frame edge. When rotating the axis, the apparent size of the axis changes which can result in a “pumping” visual effect. The chosen aspect is also maintained using this setting.
:stretch pulls the cuboid corners to the frame edges such that the available space is filled completely. The chosen aspect is not maintained using this setting, so :stretch should not be used if a particular aspect is needed.
:free behaves like :fit but changes some interactions. Zooming affects the whole axis rather than just the content. This allows you to zoom in on content without it getting clipped by the 3D bounding box of the Axis3. zoommode = :cursor is disabled. Translations can no also affect the axis as a whole with control + right drag.
Also, you should not use the cam_ functions with Axis3, it’s not a plain Scene and controls its camera through different attributes. elevation and azimuth control the two camera rotations
That is very good to know! I don’t remember seeing these options.
Is there any updated guide on choosing between LScene and Axis3? Should I assume that Axis3 is always preferred for simple visualization of 3D objects in a coordinate system?
Will experiment with the options above, and possibly replace LScene by Axis3 in our stack.
Axis3 is doing a lot more work for each interaction, which is usually not a problem, but e.g. for WGLMakie it can be quite a bit slower (e.g. all ticks get updated for a simple move of the camera).
Also, WGLMakie has a javascript fallback for the camera for LScene, so you can still move the camera when exporting a plot to static HTML.
We do want to upgrade LScene to keep the simpler camera, while improving the static axis to be more modern (the LScene axis is probably one of the oldest plot code living unchanged in Makie).
Depends on your focus
If you dont care about WGLMakie as much but care about a nice axis, then you should definitely switch to Axis3, or at least make it simple to switch between them.
I would like to give it a try with Axis3, but don’t know how to fix the viewmode option in the recipe. Can I just return a lambda function in Make.args_preferred_axis to set the axis with pre-fixed options?
Fabulous! That did the trick. I appreciate the advice on not using cam_ functions too. I feel like the Cursor AI also suggested viewmode=:fit but it didn’t work for some reason. Maybe because I was still using cam_? In any case, this does just what I wanted. Thanks!
using GLMakie
# Create your figure and axis
fig = Figure()
ax = Axis3(fig[1, 1],viewmode=:fit) # <--- This was the key change
# Your plot data here
# Example: surface plot
x = y = range(-5, 5, length=50)
z = [sin(sqrt(x^2 + y^2)) for x in x, y in y]
surface!(ax, x, y, z)
# Animate using elevation and azimuth
record(fig, "rotation.mp4", 1:120) do frame
# Rotate around z-axis by changing azimuth
ax.azimuth = 2π * frame / 120 # Full rotation over 120 frames
# Keep elevation constant or vary it slightly
ax.elevation = 15.0 # Fixed elevation angle
end
But why are the axis panels in front of the plot, they should always switch to the back. Looks like a bug, I don’t see anything obviously wrong in the code
Alternatively, it could make sense to make viewmode = :fit the default in Axis3. That way people writing recipes could simply choose between Axis2 and Axis3 and get the desired behavior. I believe many people still choose LScene because of the “bad” default viewmode of Axis3.