I have the following function that is supposed to create subplots that are tightly spaced within a row, and distributed with spacing in each column. It also lets you set buffer sizes for the edges:
function fill_graph()
for x=1:3
for y=1:3
ax = subplot(3,3,(x-1)*3+y);
left_buffer = .05;
right_buffer = .025;
x_pos = left_buffer+(x-1)*(1/3*(1-left_buffer-right_buffer));
width = 1/3*(1-left_buffer-right_buffer);
bottom_buffer = .1;
top_buffer = .05;
spacing=.07;
height = (1/3)*(1-2*spacing-top_buffer-bottom_buffer);
y_pos = bottom_buffer+(y-1)*(spacing+height);
set(ax,'position',[x_pos,y_pos,width,height])
if x>1
set(gca,'yTickLabel','');
ylabel('');
end
if y>1 || x~=2
xlabel('')
end
end
end
When I run the function, the bottom row of plots and the leftmost plot on the middle row all disappear.
I'm aware this probably has something to do with the fact that subplots will disappear when they overlap with one another, but regardless of how far apart I set the spacing, I can't get the bottom row to appear (the only way I've found is setting the bottom buffer above .34, which only works some of the time). I've tried switching 'position' to 'outerposition', which should make it so that none of them overlap, but that doesn't seem to work either.
I have seen Second subplot disappearing, but when I specify the position in that manner (subplot('position',x_pos,y_pos,width,height)), it writes over the plots that are already there. If I place that code before I plot, the plots resize as soon as I issue the plot command.
Try replacing subplot(....) with axes().
The command subplot checks all existing axes on the figure to see if any come near the required space, or were likely generated by a previous, identical subplot command. Sounds like you are spending all of your time/effort reverse engineering this part of Matlab's algorithm.
The command axes just makes a new axis on the figure, no matter what else is already there. For example, I have built plots where the axes intentionally overlap. As long as you are doing all the positioning work yourself, don't waste your time with subplot.
Related
I'm creating a plot in Matlab with two axes, and I want to copy the entire figure to a smaller window with a specific size, and then save it (this makes it easier to format plots for display in Word documents). The function I'm using is as follows:
function printStandardFigure(figInput,filename)
dpi = 300;
newFig = copyobj(figInput,groot);
newFig.Units = 'inches';
newFig.InnerPosition = [1,1,6.5/2,6.5/2];
print(filename,'-dpng',['-r',num2str(dpi)]);
close(newFig);
end
In theory, this should create a copy of the figure I've made with all its axes, etc. identical to the original but resized to fit the new window size. What actually happens is that the 'Position' property for one of the axes gets reset to [0, 0, 1, 1], but the other one displays just fine. This still happens even when I set the ActivePositionProperty to 'position', which normally causes the axis to hold the Position property constant.
I've also tried using copyobj to copy just the two axes over to the new figure, but I have similar issues with resizing, and this doesn't import any legends even though they are set as children of the axes.
This code works just fine if I only have one axis, so why does the second axis cause it to screw up? I've tried making the original figure have the same aspect ratio as the new one, and I've tried setting their sizes to be exactly equal using the InnerPosition property. I can't figure out what's going on and Matlab's documentation isn't helping.
Here's the original chart
And here's what the output of my function looks like.
Edit: Cris Luengo in the comments suggested I try the subplot command, which I thought was a good idea, so I tried it out. However, the export process is still causing a problem Here are the original and the exported versions of the plot when I do that. The original figure was created to have the same dimensions as the exported plot, just to make sure that resizing the axes wasn't causing the issue. I also get the same problem when I remove lines 6 and 7 from the export function (where I set the size of the new figure).
I have a scatterplot of data in Matlab, along with a horizontal linegraph which divides two sub groups of this data - all on the same plot. I have plotted these two entities separately using the hold on command.
Ideally, I want the plot window to automatically adjust to just the scatterplot data, and would prefer the horizontal line I plotted to simply extend off the screen in all cases. Is there a simple way to do this?
At the moment when I change the limits of the horizontal line, the graph window shifts to accommodate these points, skewing the view of the scatterplot data I'm actually interested in.
Example:
% central line segment
boundary_line = plot(csv_results.data(:,9),csv_results.data(:,10));
% negative extension of line segment off screen
line_negext = plot([-10,csv_results.data(1,9)],[csv_results.data(1,10),csv_results.data(1,10)]);
% positive extension of line segment off screen
line_posext = plot([10,csv_results.data(6,9)],[csv_results.data(6,10),csv_results.data(6,10)]);
% scatterplot data of interest
scatt_data = plot(csv_results.data(:,3),csv_results.data(:,4));
UPDATE: My problem is that, as seen in my code above, I need to plot two line segments at different y values that continue to positive and negative infinity, which link up to an existing plot in the middle. If I use yline I can simply draw one horizontal line - if I use xlim I risk cropping out data for subsequent runs..
If you want to adjust the axis to more restrictive portion (reduce), then xlim() and ylim() get the job done. Horizontal lines drawn by yline() will persist, as will vertical lines drawn by xline(). Note that xline() and yline() should work for releases including R2018b and later.
% MATLAB R2019a
% Sample Data
n = 10;
X1 = 5*rand(n,1);
Y1 = 5*rand(n,1);
X2 = 5 + 5*rand(n,1);
Y2 = 5 + 5*rand(n,1);
figure, hold on
yline(5)
scatter(X1,Y1,'bo')
scatter(X2,Y2,'rd')
scatter(X1,Y2,'ks')
xlim([0 5])
Notice that by calling xlim() for a larger x-axis range also expands the horizontal line.
xlim([-1,12])
If you plot new data after xlim() outside the range, the plot won't automatically adjust. However, if you do so before calling xlim(), then the horizontal line will expand. Try the example below.
figure, hold on
yline(5)
scatter(X1,Y1,'bo')
scatter(X2,Y2,'rd')
scatter(X1,Y2,'ks')
Then immediately execute
scatter(100*rand(n,1),Y1)
and see that the horizontal line has expanded to cover the new, much longer x-axis.
After posting this answer, I found: How to draw horizontal and vertical lines in MATLAB?
Quite a simple question but just couldn't find the answer online... I want to visualise a point cloud gathered from a lidar. I can plot the individual frames but wanted to loop them to create a "animation". I know how to do it for normal plots with drawnow but can't get it working with a scatter3. If I simply call scatter3 again like I have done in the commented code then the frame that I am viewing in the scatter plot jumps around with every update (Very uncomfortable). How do i get the scatter3 plot to update to the new points without changing the UI of the scatter ie. Still be able to pan and zoom around the visualised point cloud while it loops through.
EDIT: The file is a rosbag file, I cannot attach it because it is 170MB. The problem doesn't happen when using scatter3 in a loop with a normal array seems to be something with using scatter3 to call a PointCloud2 type file using frame = readMessages(rawBag, i).
EDIT: The problem does not seem to be with the axis limits but rather with the view of the axis within the figure window. When the scatter is initialised it is viewed with the positive x to the right side, positive y out of the screen and positive z upwards, as shown in view 1. Then after a short while it jumps to the second view, where the axis have changed, positive x is now out of the screen, positive y to the right and positive z upwards (both views shown in figures). This makes it not possible to view in a loop as it is constantly switching. So basically how to update the plot without calling scatter3(pointCloudData)?
rawBag = rosbag('jackwalking.bag');
frame = readMessages(rawBag, 1);
scatter3(frame{1});
hold on
for i = 1:length(readMessages(rawBag))
disp(i)
frame = readMessages(rawBag, i);
% UPDATE the 3D Scatter %
% drawnow does not work?
% Currently using:
scatter3(frame{1})
pause(.01)
end
The trick is to not use functions such as scatter or plot in an animation, but instead modify the data in the plot that is already there. These functions always reset axes properties, which is why you see the view reset. When modifying the existing plot, the axes are not affected.
The function scatter3 (as do all plotting functions) returns a handle to the graphics object that renders the plot. In the case of scatter3, this handle has three properties of interest here: XData, YData, and ZData. You can update these properties to change the location of the points:
N = 100;
data = randn(N,3) * 40;
h = scatter3(data(:,1),data(:,2),data(:,3));
for ii = 1:500
data = data + randn(N,3);
set(h,'XData',data(:,1),'YData',data(:,2),'ZData',data(:,3));
drawnow
pause(1/5)
end
The new data can be totally different too, it doesn't even need to contain the same number of points.
But when modifying these three properties, you will see the XLim, YLim and ZLim properties of the axes change. That is, the axes will rescale to accommodate all the data. If you need to prevent this, set the axes' XLimMode, YLimMode and ZLimMode to 'manual':
set(gca,'XLimMode','manual','YLimMode','manual','ZLimMode','manual')
When manually setting the limits, the limit mode is always set to manual.
As far as I understood what you describe as "plots jumpying around", the reason for this are the automatically adjusted x,y,z limits of the scatter3 plot. You can change the XLimMode, YLimMode, ZLimMode behaviour to manual to force the axis to stay fixed. You have to provide initial axes limits, though.
% Mock data, since you haven't provided a data sample
x = randn(200,50);
y = randn(200,50);
z = randn(200,50);
% Plot first frame before loop
HS = scatter3(x(:,1), y(:,1), z(:,1));
hold on
% Provide initial axes limits (adjust to your data)
xlim([-5,5])
ylim([-5,5])
zlim([-5,5])
% Set 'LimModes' to 'manual' to prevent auto resaling of the plot
set(gca, 'XLimMode', 'manual', 'YLimMode', 'manual', 'ZLimMode', 'manual')
for i=2:len(x,2)
scatter3(x(:,i), y(:,i), z(:,i))
pause(1)
end
This yields an "animation" of plots, where you can pan and zoom into the data while continuous points are added in the loop
I am generating multiple plots of different datasets in succession using MATLAB. I would like the legend positions to be such that they don't overlap on the plotted lines and it would be ideal if this placement could be done automatically.
I am aware of setting the 'Location' to 'best' to achieve this but the placement of the legend tends to be awkward when 'best' is used (below). Also, I would like the legend to be inside the plot. I also came across a way to make the legend transparent (here) so that it does not render the plotted data invisible, but explicitly placing the legend elsewhere is what I am looking for.
Is there a way to place the legend at the extremes of the image ('NorthWest', 'SouthWest' etc) automatically such that it does not overlap on the plotted data (apart from the methods suggested above)?
So, you have tried using Location instead of Position? For example:
x =1:100;
y = x.^2;
lgd = legend('y = x.^2');
set(lgd,'Location','best')
and you are getting odd results correct? A quick way of solving this would be to still use Location, with best, and extract the coordinates:
lgd.Position
You should get something like this:
ans =
0.7734 0.3037 0.1082 0.0200
which maps to:
[left bottom width height]
You will need to focus on left and bottom. These two values, left and bottom, specify the distance from the lower left corner of the figure to the lower left corner of the legend, and they are analogous to the grid frame you are using.
Then, depending on the size of the frame (I would suggest you use axis([XMIN XMAX YMIN YMAX]) for this, if possible), you can pinpoint the position of the legend within the grid. What you can do next, is check if and which of your graphs in the plot cross paths with the legend (maybe define a relative distance function based on some distance threshold) and if they do, then randomly reposition the legend (i.e. change the values of left and bottom) and repeat until your conditions are met.
If this still troubles you I can write a short snippet. Finally, know that you can always opt for placing the legend on the outside:
set(lgd,'Location','BestOutside')
I'm trying to make a legend in a matlab figure that takes less space, in particular vertical size. Ideally, if I could change the inter-line spacing of the legend, this would have solved it, but I can't seem to find a way to do it.
I've searched around in Mathworks questions and Google. I've even tried to "write" from scratch the legend, but it doesn't work when I try to export to eps.
Is there a way to control the inter-line spacing in Matlab's legend? something undocumented maybe?
One way would be to adjust the aspect ratio of the legend:
set(h,'PlotBoxAspectRatioMode','manual');
set(h,'PlotBoxAspectRatio',[1 0.8 1]);
(Default is [1 1 1]).
You can also play with the exact positioning of the various legend elements. If h is the handle for your legend:
hc = get(h,'Children');
Now, hc will be of length 3 x the number of items in your legend.
hc(1) is the marker, hc(2) is the line, hc(3) is the text.
(and so on for subsequent items).
hc(1) will have a entry YData(vertical position, single value), hc(2) also has YData (vertical position - vector of two identical values), and hc(3) contains Position - [x y z] vector. The Y values for all these three should be the same.
Obtain the y-positions:
yd = zeros(length(hc)/3,1);
for n = 1:length(yd);
yd(n) = get(hc(1+(n-1)*3),'YData');
end
Assuming your legend doesn't have any text that goes over more than one line,yd should be evenly spaced steps. Define a new spacing of your choice, yd2.
Set new positions:
% markers
for n = 1:length(yd2)
set(hc(1+(n-1)*3),'YData',yd2(n));
end
% lines
for n = 1:3
set(hc(2+(n-1)*3),'YData',[yd2(n),yd2(n)]);
end
% text
for n = 1:3;
pos = get(hc(3+(n-1)*3),'Position');
pos(2) = yd(n);
set(hc(3+(n-1)*3),'Position',pos);
end
Problem - this shifts all the text and markers but doesn't change the border box. The easiest way if trying to make fine adjustments is to first define the final size/position of the legend, and then reposition/resize elements within to taste.
Also, you may find that when writing out images MATLAB helpfully redraws the image, resetting your adjustments. See this blog post on Undocumented MATLAB for info on that.