fixingstuff
fixingstuff

Reputation: 579

Save cropped figure in Matlab

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

Answers (1)

craigim
craigim

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

Related Questions