Resetting axes after moving plots around? - matlab

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.

Related

Matlab - Rotate 3D subplots in unison [duplicate]

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

Labeling plots such that label is aligned with the ylabel outside the axes

Please see the following code which creates a 2 by 2 subplot with some plots:
x = linspace(0,2*pi);
y = sin(x);
hfig = figure('Position',[1317 474 760 729]);
subplot(2,2,1)
plot(x,y)
ylabel('plot1');
subplot(2,2,2)
plot(x,y.^2)
ylabel('plot2');
subplot(2,2,3)
plot(x,y.^3)
ylabel('plot3');
subplot(2,2,4)
plot(x,abs(y))
ylabel('plot4');
in each one, I have added labels by hand in Tools: Edit plot (a) (b) (c) (d) producing this figure:
The problem is, if I resize the plot they are no longer aligned with the ylabel text:
Is there a way to add these labels programmatically and have them automatically align to the ylabel text? I am surprised MATLAB does not have something like this built in already.
Thanks
This is not something that is easy to do without attaching a listener to the figure resize event (see example), and doing some computations related to aspect ratios.
It's not entirely clear what sort of objects your labels are (text or annotation), so I'll just show how to do this programmatically using the text command, which creates labels in axes coordinates (as opposed to figure coordinates). This doesn't solve the problem entirely, but it looks better, possibly to an acceptable degree:
function q56624258
x = linspace(0,2*pi);
y = sin(x);
hF = figure('Position',[-1500 174 760 729]);
%% Create plots
[hAx,hYL] = deal(gobjects(4,1));
for ind1 = 1:3
hAx(ind1) = subplot(2,2,ind1, 'Parent' , hF);
plot(hAx(ind1), x,y.^ind1);
hYL(ind1) = ylabel("plot" + ind1);
end
hAx(4) = subplot(2,2,4);
plot(hAx(4), x,abs(y));
hYL(4) = ylabel('plot4');
%% Add texts (in data coordinates; x-position is copied from the y-label)
for ind1 = 1:4
text(hAx(ind1), hYL(ind1).Position(1), 1.1, ['(' char('a'+ind1-1) ')'], ...
'HorizontalAlignment', 'center');
end
Note several modifications to your code:
The handles returned by some functions that create graphical elements are now stored (mainly: hAx, hYL).
All functions that create graphical elements (subplot, plot, ylabel) now have the target (i.e. parent or container) specified.
I changed the 'Position' of the figure so that it works in my setup (you might want to change it back).

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

Erratic data cursor behavior for triangulated 3d surfaces in MATLAB R2011b

I'm seeing erratic behavior from the data cursor in MATLAB R2011b when applied to plots of triangulated 3d surfaces: Clicking on certain points selects completely different points instead. Example with a cylinder:
[r, phi, h] = meshgrid(1, 0:pi/10:2*pi, 0:0.05:1);
x = r.*cos(phi);
y = r.*sin(phi);
z = h;
xyz = [x(:) y(:) z(:)];
tri = delaunay(xyz);
trimesh(tri, xyz(:,1), xyz(:,2), xyz(:,3), ...
'LineStyle', 'none', 'Marker', '.', 'MarkerSize', 30)
view(-37, 28)
Then enable data cursor mode and try to select the topmost dot of one of the columns in front. On my installation, MATLAB does not select the point under the cursor but a different one seemingly chosen at random.
Is this a bug or am I doing something wrong?
I think it's because you have so many lines in your mesh; if you remove the 'linestyle','none' and plot a small section of the mesh (using e.g. [r, phi, h] = meshgrid(1, 0:pi/10:2*pi, 0:0.5:1); as your first line you can see they are going through your cylinder. The data cursor mode will go to the closest vertex on a line that you click on, so if you are clicking on a line that is hidden beneath a face, it may jump to a vertex on that line rather than only the dots you have made visible. I am not sure if there is an easy way to change this behaviour.
I found a solution to this problem in a File Exchange contribution by Jochen Rau. You can define which data is selectable with the data cursor via the 'HitTest' property. So for the example I provided, where I wanted only the markers to be selectable, the solution is to plot the mesh without markers and with 'HitTest' set to 'off' and then use 'scatter3' to plot the markers.
[r, phi, h] = meshgrid(1, 0:pi/10:2*pi, 0:0.05:1);
x = r.*cos(phi);
y = r.*sin(phi);
z = h;
xyz = [x(:) y(:) z(:)];
tri = delaunay(xyz);
figure
hold on
trimesh(tri, xyz(:,1), xyz(:,2), xyz(:,3), ...
'LineStyle', 'none', 'Marker', 'none', 'HitTest', 'off')
scatter3(xyz(:,1), xyz(:,2), xyz(:,3))
view(-37, 28)
If you're wondering what the point of plotting the triangulation is: it's to aid in visualizing point clouds by obscuring points that are in the back. The 'trimesh' call accomplishes this because it still draws the faces in white.
A solution for me has been to set the "SnapToDataVertex" property of the data cursor object to "off".
dc = datacursormode;
set(dc,'SnapToDataVertex','off')
When set to the default, "on," clicking on a patch object causes the datacursor to move to the vertex nearest the line of sight, even if the face is occluded. When off, the datacursor moves to the point where line of sight intersects the nearest face, which is usually what I want.