classdef LinePlotReducer < handle
% LinePlotReducer
%
% Manages the information in a standard MATLAB plot so that only the
% necessary number of data points are shown. For instance, if the width of
% the axis in the plot is only 500 pixels, there's no reason to have more
% than 1000 data points along the width. This tool selects which data
% points to show so that, for each pixel, all of the data mapping to that
% pixel is crushed down to just two points, a minimum and a maximum. Since
% all of the data is between the minimum and maximum, the user will not see
% any difference in the reduced plot compared to the full plot. Further, as
% the user zooms in or changes the figure size, this tool will create a new
% map of reduced points for the new axes limits automatically (it requires
% no further user input).
%
% Using this tool, users can plot huge amounts of data without their
% machines becoming unresponsive, and yet they will still "see" all of the
% data that they would if they had plotted every single point.
%
% To keep things simple, the interface allows a user to pass in arguments
% in the same way those arguments would be passed directly to most line
% plot commands. For instance:
%
% plot(t, x);
%
% Becomes:
%
% LinePlotReducer(t, x);
%
% More arguments work as well.
%
% plot(t, x, 'r:', t, y, 'b', 'LineWidth', 3);
%
% Becomes:
%
% LinePlotReducer(t, x, 'r:', t, y, 'b', 'LineWidth', 3);
%
% Note that LinePlotReducer returns a LinePlotReducer object as output.
%
% lpr = LinePlotReducer(t, x);
%
% Another function, reduce_plot, takes exactly the same arguments as
% LinePlotReducer, but returns the plot handles instead of a
% LinePlotReducer object.
%
% h_plots = reduce_plot(t, x);
%
% One can use reduce_plot or LinePlotReducer according to one's comfort
% with using objects in MATLAB. By using reduce_plot, one does not need to
% use objects if one doesn't want to.
%
% The plot handles are also available as a public property of
% LinePlotReducer called h_plot. These handles would allow one to, e.g.,
% change a line color or marker.
%
% By default 'plot' is the function used to display the data, however,
% other functions can be used as well. For instance, to use 'stairs':
%
% LinePlotReducer(@stairs, t, x);
%
% Alternately, if one already has an existing plot, but wants a
% LinePlotReducer to manage it, one can simply pass the plot handles to a
% new LinePlotReducer, such as:
%
% h = plot(t, x, 'r:');
% LinePlotReducer(h);
%
% Finally, one can also set up a plot with a "small" set of data, then pass
% the plot handle and full x and y data to LinePlotReducer. This allows a
% user to create a detailed custom plot, but still use the LinePlotReducer
% without ever having to plot all of the data. For instance:
%
% h = plot(t([1 end]), x([1 end]), 'rd--', t([1 end]), y([1 end]), 'bs');
% LinePlotReducer(h, t, x, t, y);
%
% One can still use normal zooming and panning tools, whether in the figure
% window or from the command line, and the LinePlotReducer will still
% notice that the axes limits or size are changing and will automatically
% create a new, reduced data set to fit the current size.
%
% LinePlotReducer looks best on continuous lines. When plotting points
% only (with no connecting line), it might be noticeable that only the
% minimum and maximum are showing up in a plot. A user can still explore
% the data quickly, and details will always be filled in when the user
% zooms (all the way down to the raw data).
%
% Finally, for those who need to zoom and pan frequently, a utility is
% included to make this a little faster. When a LinePlotExplorer is applied
% to a figure, it allows the user to zoom in and out with the scroll wheel
% and pan by clicking and dragging. Left and right bounds can also be
% passed to LinePlotExplorer.
%
% lpe = LinePlotExplorer(gcf(), 0, 5);
%
% The LinePlotExplorer is not strictly related to the LinePlotReducer.
% However, frequent zooming and handling large data so frequently occur at
% the same time that this class was included for convenience.
%
% --- Change Log ---
%
% 2015-05-31: Reduced long-term memory usage by making a LinePlotReducer
% delete its listener for events from the figure. Without explicitly
% deleting this listener, the LinePlotReducer remains registered and so is
% not deleted. As new LinePlotReducers are used in the same figure, memory
% usage grows. Thanks to Jack for pointing this out. Changes copyright 2015
% Tucker McClure.
%
% 2014-06-04: Now allows multiple LinePlotReducer objects in the same axes.
% Changes copyright 2014 Tucker McClure.
%
% 2014-01-15: Now allows input as combination of rows and columns, just
% like the regular plot functions. Changes copyright 2014 Tucker McClure.
%
% 2014-01-06: Fixed a bug when "taking over" a plot with only a single
% line. Also fixed a bug with the final line spec being ignored. Changes
% copyright 2014 Tucker McClure.
%
% 2013-03-15: Original. Copyright 2013, The MathWorks, Inc.
%
% ---
%
% Copyright 2015, The MathWorks, Inc. and Tucker McClure
properties
% Handles
h_figure;
h_axes;
h_plot;
% Original data
x;
y;
y_to_x_map;
% Extrema
x_min;
x_max;
% Status
busy = false; % Set when we're working so we don't
% trigger new callbacks.
calls_to_ignore = 0; % Sometimes we ignore callbacks when
% triggered by callbacks from outside of
% LinePlotReducer.
% Last updated state
last_width = 0; % We only update when the width and
last_lims = [0 0]; % limits change.
% We need to keep track of the figure listener so that we can
% delete it later.
figure_listener;
% We'll delete the figure listener once all of the plots we manage
% have been deleted (cleared from axes, closed figure, etc.).
deleted_plots;
end
methods
% Create a ReductiveViewer for the x and y variables.
function o = LinePlotReducer(varargin)
% We're busy. Ignore resizing and things.
o.busy = true;
% If the user is just passing in an array of plot handles,
% we'll take over managing the data shown in the plot.
taking_over_existing_plot = nargin >= 1 ...
&& isvector(varargin{1}) ...
&& all(ishandle(varargin{1}));
if taking_over_existing_plot
% Record the handles.
o.h_plot = varargin{1};
o.h_axes = get(o.h_plot(1), 'Parent');
o.h_figure = get(o.h_axes, 'Parent');
% Get the original data either from the plot or from input
% arguments.
if nargin == 1
o.x = get(o.h_plot, 'XData');
o.y = get(o.h_plot, 'YData');
o.y_to_x_map = 1:size(o.y, 1);
% If there are multiple lines, o.x will be a cell
% array, which is how we use it from here on. If
% there's only one line, we need to make it a cell
% array.
if ~iscell(o.x)
o.x = {o.x};
end
if ~iscell(o.y)
o.y = {o.y};
end
% Format the data as columns.
for k = 1:length(o.x)
o.x{k} = o.x{k}(:);
o.y{k} = o.y{k}(:);
end
end
star