Reputation: 579
I have opened a highly complex .fig
figure from file in matlab with openfig
.
After this I have cropped the figure with
axis([X1, X2, Y1, Y2])
I now want save the cropped figure so that it is of a reduced size when saved. The problem is that savefig
will save the complete image zoomed to the axis specified. How can I save the figure, permanently cropped to the axis I have specified? e.g. it should not be possible to zoom out when opening the new image.
Upvotes: 4
Views: 1341
Reputation: 3914
I don't have MATLAB in front of me, and I don't have your data to test this with, so this is an approximate answer, but as @natan mentions in the comments above, you can harvest the data from the figure, which is stored in 'XData' and 'YData'. You can then crop the data to just the parts you want and then replace the existing data with the cropped data. It will end up looking something like this:
kids = findobj(gca,'Type','line');
K = length(kids);
Xrange = get(gca,'XLim');
Yrange = get(gca,'YLim');
for k = 1:K
X = get(kids(k),'XData');
Y = get(kids(k),'YData');
idx = X >= min(Xrange) & X <= max(Xrange) & Y >= min(Yrange) & Y <= max(Yrange);
set(kids(k),'XDATA',X(idx),'YDATA',Y(idx));
end
You'll have to modify this if your plots have patch objects, bar objects, etc., but the idea is there.
Edge cases:
There are a few edge cases to consider as @Jan has rightly pointed out. The first is that the approach above assumes a rather high density of points even in the expanded scales and will leave a small gap between the end of the line and the axes. For low-point-density lines, you need to expand the idx
variable to capture the next point outside the axes in either direction:
idx_center = X >= min(Xrange) & X <= max(Xrange) & Y >= min(Yrange) & Y <= max(Yrange);
idx_plus = [false idx(1:end-1)];
idx_minus = [idx(2:end) false];
idx = idx_center | idx_plus | idx_minus;
That will only expand the number of points if there is at least one point inside the window. The other edge case is where the line passes through the window but does not contain any points inside the window, which would be the case if idx
is all false. In that case, you need the largest point outside the left axis and the smallest point outside the right axis. If either of these searches come up empty, then the line doesn't pass through the window and can be discarded.
if ~any(idx)
idx_low = find(X < min(Xrange),1,'last');
idx_high = find(X > max(Xrange),1,'first');
if isempty(idx_low) | isempty(idx_high)
% if there aren't points outside either side of the axes then the line doesn't pass through
idx = false(size(X));
else
idx = idx_low:idx_high;
end
end
This doesn't test whether the line is inside the y-bounds, so it may be possible that the line will pass above or below the window without intersecting. If that is important you will need to test the line connecting the found points. An extra 2 points in memory should not be a big deal. If it is an issue, then I leave it as an exercise to the student to work out the test of whether a line passes through the axes.
Putting it all together:
kids = findobj(gca,'Type','line'); % find children of the axes that are lines
K = length(kids); % count them
Xrange = get(gca,'XLim'); % get the axis limits
Yrange = get(gca,'YLim');
for k = 1:K
X = get(kids(k),'XData'); % pull the x-y data for the line
Y = get(kids(k),'YData');
% find the points inside the window and then dilate the window by one in
% each direction
idx_center = X >= min(Xrange) & X <= max(Xrange) & Y >= min(Yrange) & Y <= max(Yrange);
idx_plus = [false idx(1:end-1)];
idx_minus = [idx(2:end) false];
idx = idx_center | idx_plus | idx_minus;
% handle the edge case where there are no points in the window but the line still passes through
if ~any(idx)
idx_low = find(X < min(Xrange),1,'last');
idx_high = find(X > max(Xrange),1,'first');
if isempty(idx_low) | isempty(idx_high)
% if there aren't points outside either side of the axes then the line doesn't pass
% through
idx = false(size(X));
else
% numerical indexing instead of logical, but it all works the same
idx = idx_low:idx_high;
end
end
if any(idx)
% reset the line with just the desired data
set(kids(k),'XDATA',X(idx),'YDATA',Y(idx));
else
% if there still aren't points in the window, remove the object from the figure
delete(kids(k));
end
end
Upvotes: 2