Code covered by the BSD License  

Highlights from
Plot (Big)

image thumbnail

Plot (Big)

by

 

15 Mar 2013 (Updated )

Makes MATLAB's line plots much faster.

Editor's Notes:

This file was selected as MATLAB Central Pick of the Week

reduce_to_width(x, y, width, lims)
function [x_reduced, y_reduced] = reduce_to_width(x, y, width, lims)

% [x_reduced, y_reduced] = reduce_to_width(x, y, width, lims)
% 
% (This function is primarily used by LinePlotReducer, but has been
%  provided as a stand-alone function outside of that class so that it can
%  be used potentially in other projects. See help LinePlotReducer for 
%  more.)
%
% Reduce the data contained in x and y for display on width pixels,
% stretching from lims(1) to lims(2).
%
% For example, if x and y are 20m points long, but we only need to plot
% them over 500 pixels from x=5 to x=10, this function will return 1000
% points representing the first point of the window (x=5), the last point
% of the window (x=10), and the maxima and minima for each pixel between 
% those two points. The result is that x_reduced and y_reduced can be 
% plotted and will look exactly like x and y, without all of the waste of 
% plotting too many points.
%
% x can be n-by-1 or n-by-m with n samples of m columns (that is, there can
% be 1 x for all y or 1 x for each y.
%
% y must be n-by-m with n samples of m columns
%
% [xr, yr] = reduce_to_width(x, y, 500, [5 10]); % Reduce the data.
%
% plot(xr, yr); % This contains many fewer points than plot(x, y) but looks
%                 the same.
%
% Tucker McClure
% Copyright 2013, The MathWorks, Inc.

    % We'll need the first point to the left of the limits, the first point
    % to the right to the right of the limits, and the min and max at every
    % pixel inbetween. That's 1 + 1 + 2*(width - 2) = 2*width total points.
    n_points = 2*width;
    
    % If the data is already small, there's no need to reduce.
    if size(x, 1) <= n_points
        x_reduced = x;
        y_reduced = y;
        return;
    end

    % Reduce the data to the new axis size.
    x_reduced = nan(n_points, size(y, 2));
    y_reduced = nan(n_points, size(y, 2));
    for k = 1:size(y, 2)

        % Find the starting and stopping indices for the current limits.
        if k <= size(x, 2)
            
            % Rename the column. This actually makes stuff substantially 
            % faster than referencing x(:, k) all over the place. On my
            % timing trials, this was 20x faster than referencing x(:, k)
            % in the loop below.
            xt = x(:, k);

            % Map the lower and upper limits to indices.
            nx = size(x, 1);
            lower_limit      = binary_search(xt, lims(1), 1,           nx);
            [~, upper_limit] = binary_search(xt, lims(2), lower_limit, nx);
            
            % Make the windows mapping to each pixel.
            x_divisions = linspace(x(lower_limit, k), ...
                                   x(upper_limit, k), ...
                                   width + 1);
                               
        end

        % Create a place to store the indices we'll need.
        indices = [lower_limit, zeros(1, n_points-2), upper_limit];
        
        % For each pixel...
        right = lower_limit;
        for z = 1:width-1
            
            % Find the window bounds.
            left               = right;
            [~, right]         = binary_search(xt, ...
                                               x_divisions(z+1), ...
                                               left, upper_limit);
            
            % Get the indices of the max and min.
            yt = y(left:right, k);
            [~, max_index]     = max(yt);
            [~, min_index]     = min(yt);
            
            % Record those indices.
            indices(2*z:2*z+1) = sort([min_index max_index]) + left - 1;
            
        end

        % Sample the original x and y at the indices we found.
        x_reduced(:, k) = xt(indices);
        y_reduced(:, k) = y(indices, k);

    end
    
end

% Binary search to find boundaries of the ordered x data.
function [L, U] = binary_search(x, v, L, U)
    while L < U - 1                 % While there's space between them...
        C = floor((L+U)/2);         % Find the midpoint
        if x(C) < v                 % Move the lower or upper bound in.
            L = C;
        else
            U = C;
        end
    end
end

Contact us