Matlab - Rotate 3D subplots in unison [duplicate] - matlab

Say I have two 2-dimensional matrices of equal size and create a surface plot for each of them.
Is there a way to link the axes of both plots, so that one can 3D-rotate both of them simultaneously in the same direction?

Playing with ActionPostCallback and ActionPreCallback is certainly a solution, but probably not the most efficient one. One may use linkprop function to synchronize the camera position property.
linkprop([h(1) h(2)], 'CameraPosition'); %h is the axes handle
linkprop can synchronize any of graphical properties of two or more axes (2D or 3D). It can be seen as an extension of the linkaxes function that works for 2D plots and synchronizes the axes limits only. Here, we can use linkprop to synchronize the camera position property, CameraPosition, the one that is modify when one rotate an axes.
Here is some code
% DATA
[X,Y] = meshgrid(-8:.5:8);
R = sqrt(X.^2 + Y.^2) + eps;
Z1 = sin(R)./R;
Z2 = sin(R);
% FIGURE
figure;
hax(1) = subplot(1,2,1); %give the first axes a handle
surf(Z1);
hax(2) = subplot(1,2,2); %give the second axes a handle
surf(Z2)
% synchronize the camera position
linkprop(hax, 'CameraPosition');
You can have a list of the graphical properties with
graph_props = fieldnames(get(gca));

One way is to register a callback on rotation events and synchronize the new state across both axes.
function syncPlots(A, B)
% A and B are two matrices that will be passed to surf()
s1 = subplot(1, 2, 1);
surf(A);
r1 = rotate3d;
s2 = subplot(1, 2, 2);
surf(B);
r2 = rotate3d;
function sync_callback(~, evd)
% Get view property of the plot that changed
newView = get(evd.Axes,'View');
% Synchronize View property of both plots
set(s1, 'View', newView);
set(s2, 'View', newView);
end
% Register Callbacks
set(r1,'ActionPostCallback',#sync_callback);
set(r1,'ActionPreCallback',#sync_callback);
set(r2,'ActionPostCallback',#sync_callback);
set(r2,'ActionPreCallback',#sync_callback);
end

Related

Separate initialization and display of plot

What is a proper way to separate the initialization and the display of plots in Matlab? (I mean plots in a wide sense here; could be plot, plot3, scatter etc.) To give a concrete example, I have a pretty complex 3D visualization that uses sphere and mesh to draw a static sphere mesh and then scatter3 to plot a moving trajectory on the sphere. To be able to do this in real time I have implemented some simple optimizations, such as only updating the scatter3 object each frame. But the code is a bit messy, making it hard to add additional features that I want, so I would like improve code separation.
I also feel like it might sometimes be useful to return some kind of plot object from a function without displaying it, for example to combine it with other plots in a nice modular way.
An example of what I have in mind would be something like this:
function frames = spherePlot(solution, options)
% Initialize sphere mesh and scatter objects, configure properties.
...
% Configure axes, maybe figure as well.
...
% Draw sphere.
...
if options.display
% Display figure.
end
for step = 1:solution.length
% Update scatter object, redraw, save frame.
% The frames are saved for use with 'movie' or 'VideoWriter'.
end
end
Each step might also be separated out as a function.
So, what is a neat and proper way to do stuff like this? All documentation seems to assume that one wants to display everything right away.
For example
% some sample data
N = 100;
phi = linspace(-pi, pi, N);
theta = linspace(-pi, pi, N);
f = #(phi, theta) [sin(phi).*cos(theta); sin(phi).*sin(theta); cos(phi)];
data = f(phi, theta);
% init plot
figure(1); clf
plot3(data(1,:), data(2,:), data(3,:)); % plot path, not updated
hold on
p = plot3([0 data(1,1)], [0 data(2,1)], [0 data(3,1)]); % save handle to graphics objects to update
s = scatter3(data(1,1), data(2,1), data(3,1), 'filled');
axis equal
xlabel('x'); ylabel('y'); zlabel('z');
t = title('first frame'); % also store handle for title or labels to update during animation
% now animate the figure
for k = 1:N
p.XData = [0 data(1,k)]; % update line data
p.YData = [0 data(2,k)];
p.ZData = [0 data(3,k)];
s.XData = data(1,k); % update scatter data
s.YData = data(2,k);
s.ZData = data(3,k);
t.String = sprintf('frame %i', k); % update title
drawnow % update figure
end
Basically you can update all values for a graphics handle, in this case 'p' and 's'. If you open the matlab doc for plot or plot3 you will find a link to all properties of that primitive: e.g. Line Properties. Similar documentation pages exist for scatter/imagesc etc.
So the general idea is to first create a figure with the first frame, save the handles to the objects you would like to update (p = plot(...), and then enter a loop in which you update the required property of that graphics object (e.g. p.Color = 'r', or p.XData = ...).

Resetting axes after moving plots around?

I am currently working on a GUI. I would like to have one 'axes' on which I can display multiple plots depending on which the user selects. Currently I have 2 bar plots and 2 surfc plots. I am setting the plots using
set(p1, 'Parent', axes1)
However it looks like when I set a bar plot to an axes that had a surfc there is still a z axis, and the same problem exists the other way around but wuth the lack of a z axis. This sample scripts demonstrates.
figure(1);
a1 = axes();
p1 = bar(1:5);
figure(2);
a2 = axes();
x = [1 2];
z = zeros(2);
p2 = surfc(x, x, z);
set(p1, 'Parent', a2)
set(p2, 'Parent', a1)
What is the best way to go about this?
If you're only working with a single axes, then you can change the view when you change from 3D (for the surfc plot) to 2D (the bar plot).
% Default 2D View
view(hax, 2);
% Default 3D View
view(hax, 3);
If you're allowing a user to toggle between the two, it may be worth not using the default 2D and 3D views, but rather in your button click callback, store the current view in a variable and then when they go back to the plot it keeps any custom viewpoint that the user applied. You can get the current viewpoint with the following:
[az, el] = view(hax);
Mini-rant
Also, in general it is best to assign the parent of your plot objects on construction. Most every graphics object constructor accepts the Parent parameter/value pair. It's a lot more robust that way because then the plot object is never drawn to the wrong axes.
fig1 = figure();
ax1 = axes('Parent', fig1);
p1 = bar(1:5, 'Parent', ax1);
fig2 = figure();
ax2 = axes('Parent', fig2);
p2 = surfc([1 2], [1 2], zeros(2), 'Parent', ax2);
When dealing with MATLAB graphics, I have always found it beneficial to be explicit about the parent when creating axes, plots, and other graphics objects. Never rely on gca, gcf, etc. as those all change if the user somehow clicks in the middle of your rendering.

MATLAB: update surface plot and change location of line objects in while loop

So I am encountering a couple errors and am not sure where they're coming from. I won't post the whole code, but I will do my best to explain (fear I will be accused of plagiarism).
The assignment is to create a window with two plots: a surface plot and a contour plot. The surface plot represents elevation of a portion of the map represented by the contour plot. The portion is indicated by a blue box on the contour plot. I've successfully plotted the surface and contour plots and have identified the first area with a blue box. However, now I need to implement a menu system wherein the user enters 1, 2, 3, or 4 to move the box north, south, east or west.
Here is how I instantiated the plots:
fig = figure(1);
[xGrid, yGrid] = meshgrid(xMeters(xStart:xEnd), yMeters(yStart:yEnd));
%surface plot
elevationBox = ELEV(xStart:xEnd, yStart:yEnd);
surface = subplot(2, 1, 1);
surf(xGrid, yGrid, elevationBox);
axis([0 max(xGrid(:)) 0 max(yGrid(:)) min(elevationBox(:)) max(elevationBox(:))]);
axis tight;
%contour plot
elevation = ELEV;
[xMap, yMap] = meshgrid(xMeters(1:335), yMeters(1:230));
map = subplot(2, 1, 2); contour(xMap, yMap, elevation);
axis([0 max(xMeters(:)) 0 max(yMeters(:))]);
axis fill;
set(fig, 'Position', [500 100 600 700]);
right = line(xRight, xLeft);
left = line(xLeft, yLeft);
top = line(xTop, yTop);
bottom = line(xBottom, yBottom);
So all that works fine. Obviously I didn't include the parts where I defined the data and everything. After this comes a switch statement that changes the values of xLeft, yLeft, etc as well as xStart, xEnd etc.
Here is my attempt to update the plots:
[xGrid, yGrid] = meshgrid(xMeters(xStart:xEnd), yMeters(yStart:yEnd));
elevationBox = ELEV(xStart:xEnd, yStart:yEnd);
subplot(2, 1, 1)
set(surface, 'XData', xGrid, 'YData', yGrid, 'ZData', elevationBox);
drawnow;
I can choose an option and there is no error, but the plot doesn't update and when I quit the program I get this error:
Error using matlab.graphics.axis.Axes/set
There is no XData property on the Axes class.
Error in A9 (line 129)
set(surface, 'XData', xGrid);
The setting is inside the while loop but outside the switch statement. I also had a few lines using set on the line objects but those weren't working either so I thought I'd focus here first. I'm figuring whatever I'm doing wrong applies to both object types but if not let me know.
Thanks!
Rather than grab a handle to the axis (which you call surface), you want the data object. So grab a handle at surf_h = surf(...);.
A little example:
% Generate a random surface grid
n = 10;
[x, y] = meshgrid(1:n, 1:n);
z = rand(n);
% Plot
fig_h = figure(1);
axis_h = subplot(2, 1, 1);
surf_h = surf(x,y,z);
% Modify XData
set(surf_h, 'XData', -x);
drawnow;

Overlaying two axes in a Matlab plot

I am looking for a way to overlay an x-y time series, say created with 'plot', on top of a display generated by 'contourf', with different scaling on the y-axes.
It seems that the typical way to do this in the case of two x-y plots is to use the built-in function 'plotyy', which can even be driven by functions other than 'plot' (such as 'loglog') as long as the input arguments remain the same (x,y). However, since in my case contourf requires three input arguments, 'plotyy' seems to not be applicable. Here is some sample code describing what I would like to do:
x1 = 1:1:50;
y1 = 1:1:10;
temp_data = rand(10,50);
y2 = rand(50,1)*20;
figure; hold on;
contourf(x1,y1,temp_data);
colormap('gray');
plot(x1,y2,'r-');
Ideally, I would like the timeseries (x1,y2) to have its own y-axes displayed on the right, and be scaled to the same vertical extent as the contourf plot.
Thanks for your time.
I don't think there's a "clean" way to do this, but you can fake it by overlaying two axes over each other.
x1 = 1:1:50;
y1 = 1:1:10;
temp_data = rand(10,50);
y2 = rand(50,1)*20;
figure;
contourf(x1, y1, temp_data);
colormap('gray');
h_ax = gca;
h_ax_line = axes('position', get(h_ax, 'position')); % Create a new axes in the same position as the first one, overlaid on top
plot(x1,y2,'r-');
set(h_ax_line, 'YAxisLocation', 'right', 'xlim', get(h_ax, 'xlim'), 'color', 'none'); % Put the new axes' y labels on the right, set the x limits the same as the original axes', and make the background transparent
ylabel(h_ax, 'Contour y-values');
ylabel(h_ax_line, 'Line y-values');
In fact, this "plot overlay" is almost definitely what the plotyy function does internally.
Here's example output (I increased the font size for legibility):

Is it possible to link the axes of two surface plots for 3d-rotation?

Say I have two 2-dimensional matrices of equal size and create a surface plot for each of them.
Is there a way to link the axes of both plots, so that one can 3D-rotate both of them simultaneously in the same direction?
Playing with ActionPostCallback and ActionPreCallback is certainly a solution, but probably not the most efficient one. One may use linkprop function to synchronize the camera position property.
linkprop([h(1) h(2)], 'CameraPosition'); %h is the axes handle
linkprop can synchronize any of graphical properties of two or more axes (2D or 3D). It can be seen as an extension of the linkaxes function that works for 2D plots and synchronizes the axes limits only. Here, we can use linkprop to synchronize the camera position property, CameraPosition, the one that is modify when one rotate an axes.
Here is some code
% DATA
[X,Y] = meshgrid(-8:.5:8);
R = sqrt(X.^2 + Y.^2) + eps;
Z1 = sin(R)./R;
Z2 = sin(R);
% FIGURE
figure;
hax(1) = subplot(1,2,1); %give the first axes a handle
surf(Z1);
hax(2) = subplot(1,2,2); %give the second axes a handle
surf(Z2)
% synchronize the camera position
linkprop(hax, 'CameraPosition');
You can have a list of the graphical properties with
graph_props = fieldnames(get(gca));
One way is to register a callback on rotation events and synchronize the new state across both axes.
function syncPlots(A, B)
% A and B are two matrices that will be passed to surf()
s1 = subplot(1, 2, 1);
surf(A);
r1 = rotate3d;
s2 = subplot(1, 2, 2);
surf(B);
r2 = rotate3d;
function sync_callback(~, evd)
% Get view property of the plot that changed
newView = get(evd.Axes,'View');
% Synchronize View property of both plots
set(s1, 'View', newView);
set(s2, 'View', newView);
end
% Register Callbacks
set(r1,'ActionPostCallback',#sync_callback);
set(r1,'ActionPreCallback',#sync_callback);
set(r2,'ActionPostCallback',#sync_callback);
set(r2,'ActionPreCallback',#sync_callback);
end