Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

# File indicating that this directory should be kept on the path
matlab_path
*.asv
76 changes: 42 additions & 34 deletions LinePlotExplorer.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
%
% Tucker McClure
% Copyright 2013, The MathWorks, Inc.
%
% Edit Peter Cook 2018
% Only trigger relevant (wbdf, wbmf) callbacks if an axes is clicked
% directly (whoClicked...) otherwise this can interfere with callbacks
% placed directly on graphics objects (like imrect)

properties

Expand Down Expand Up @@ -132,7 +137,7 @@ function AttachCallback(o, varargin)

% When the user moves the mouse wheel...
function Scroll(o, h, event, varargin)

% Get current axes for this figure.
h_axes = get(o.h_fig, 'CurrentAxes');

Expand All @@ -153,37 +158,40 @@ function Scroll(o, h, event, varargin)
end

% Get where and in what direction user scrolled.
exponent = double(event.VerticalScrollCount);

% Calculate new limits.
range = diff(old_x_lims);
alpha = (point(1) - old_x_lims(1)) / range;
new_lims = 2.25^exponent * range *[-alpha 1-alpha] + point(1);

% Don't zoom out too far.
new_lims = max(min(new_lims, o.x_max), o.x_min);

% Update the axes.
set(h_axes, 'XLim', new_lims);

% If there's a callback attachment, execute it.
execute_callback(o.wswf, h, event, varargin{:});
if ismember(event.VerticalScrollCount,[-1,1])
exponent = double(event.VerticalScrollCount);

% Calculate new limits.
range = diff(old_x_lims);
alpha = (point(1) - old_x_lims(1)) / range;
new_lims = 2.25^exponent * range *[-alpha 1-alpha] + point(1);

% Don't zoom out too far.
new_lims = max(min(new_lims, o.x_max), o.x_min);

% Update the axes.
set(h_axes, 'XLim', new_lims);

% If there's a callback attachment, execute it.
execute_callback(o.wswf, h, event, varargin{:});
end

end

% The user has clicked and is holding.
function ButtonDown(o, h, event, varargin)

% Record what the axes were when the user clicked and where the
% user clicked.
o.button_down_axes = get(o.h_fig, 'CurrentAxes');
point = get(o.button_down_axes, 'CurrentPoint');
o.button_down_point = point(1, 1:2);
o.button_down = true;

% If there's a callback attachment, execute it.
execute_callback(o.wbdf, h, event, varargin{:});

whoClicked = event.HitObject;
if isa(whoClicked,'matlab.graphics.axis.Axes')
% Record what the axes were when the user clicked and where the
% user clicked.
o.button_down_axes = get(o.h_fig, 'CurrentAxes');
point = get(o.button_down_axes, 'CurrentPoint');
o.button_down_point = point(1, 1:2);
o.button_down = true;

% If there's a callback attachment, execute it.
execute_callback(o.wbdf, h, event, varargin{:});
end
end

% The user has released the button.
Expand All @@ -198,9 +206,9 @@ function ButtonUp(o, h, event, varargin)

% When the user moves the mouse with the button down, pan.
function Motion(o, h, event, varargin)

if o.button_down

whoClicked = event.HitObject;
if o.button_down && isa(whoClicked,'matlab.graphics.axis.Axes')
% Get the mouse position and movement from original point.
point = get(o.button_down_axes, 'CurrentPoint');
movement = point(1, 1) - o.button_down_point(1);
Expand All @@ -214,12 +222,12 @@ function Motion(o, h, event, varargin)
if new_lims(2) > o.x_max
new_lims = o.x_max - [diff(new_lims) 0];
end

% Update the axes.
set(o.button_down_axes, 'XLim', new_lims);

end

% If there's a callback attachment, execute it.
execute_callback(o.wbmf, h, event, varargin{:});

Expand All @@ -240,4 +248,4 @@ function execute_callback(cb, h, event, varargin)
eval(cb);
end
end
end
end
121 changes: 97 additions & 24 deletions LinePlotReducer.m
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@
%
% Copyright 2015, The MathWorks, Inc. and Tucker McClure

% Edit Peter Cook 2018
% 1. Allow `datetime` class input for x
% 2. Allow data to be either "long" (reduce x-dimension)
% or "tall" (reduce y-dimension).

properties

% Handles
Expand All @@ -146,8 +151,10 @@
% LinePlotReducer.

% Last updated state
last_width = 0; % We only update when the width and
last_lims = [0 0]; % limits change.
last_width = 0; % We only update when the width and
last_xlim = [0 0]; % limits change.
last_height = 0;
last_ylim = [0 0];

% We need to keep track of the figure listener so that we can
% delete it later.
Expand All @@ -157,6 +164,14 @@
% have been deleted (cleared from axes, closed figure, etc.).
deleted_plots;

% Determine if dataset is "long" or "tall"
% We need to capture both of these because should they both take on a
% false value, we need to throw an error. This should really be an
% XOR check, but for now assume that if they both take on a true
% value, we will just ignore the tall dimension for the reduction.
isLong = true;
isTall = true;

end

methods
Expand Down Expand Up @@ -272,7 +287,7 @@
for k = start:nargin+1

% If it's a bunch of numbers...
if k <= nargin && isnumeric(varargin{k})
if k <= nargin && (isnumeric(varargin{k}) || isdatetime(varargin{k}))

% If we already have an x, then this must be y.
if km1_was_x
Expand Down Expand Up @@ -370,17 +385,34 @@
y_r = cell(1, length(o.y));

% Get the axes width once.
width = get_axes_width(o.h_axes);
%width = get_axes_width(o.h_axes);
axPos = round(getpixelposition(o.h_axes));
width = axPos(3);
o.last_width = width;
o.last_lims = [-inf inf];

o.last_xlim = [-inf inf];
height = axPos(4);
o.last_height = height;
o.last_ylim = [-inf inf];
o.CheckDataShape();

% Reduce the data!
for k = 1:length(o.y)
[x_r{k}, y_r{k}] = reduce_to_width(...
o.x{o.y_to_x_map(k)}(:), ...
o.y{k}(:), ...
width, ...
[-inf inf]);
if o.isLong
for k = 1:length(o.y)
[x_r{k}, y_r{k}] = reduce_to_width(...
o.x{o.y_to_x_map(k)}(:), ...
o.y{k}(:), ...
width, ...
[-inf inf]);
end
else
%OK to ignore the logical value of isTall if isLong == true
for k = 1:length(o.y)
[y_r{k}, x_r{k}] = reduce_to_width(...
o.y{k}(:), ...
o.x{o.y_to_x_map(k)}(:), ...
height, ...
[-inf inf]);
end
end

% If taking over a plot, just update it. Otherwise, plot it.
Expand Down Expand Up @@ -495,7 +527,21 @@ function DeletePlot(o,k)

% Redraw all of the data.
function RefreshData(o)


persistent lastTime
try
tnow = datenummx(clock);
catch
tnow = now();
end

%1/864000 = 10 updates per second
if ~isempty(lastTime) && tnow - lastTime < 1/864000
o.busy = false; % re-enable callback
return;
end
lastTime = tnow;

% When we set the axes units to 'pixels' and back, it will
% trigger a callback each time for *both* 'Position' and
% 'Units' (and in that order). Since we've set up callbacks to
Expand All @@ -515,13 +561,19 @@ function RefreshData(o)
% We're busy now.
o.busy = true;



% Get the new limits. Sometimes there are multiple axes stacked
% on top of each other. Just grab the first. This is really
% just for plotyy.
lims = get(o.h_axes(1), 'XLim');
xL = get(o.h_axes(1), 'XLim');
yL = get(o.h_axes(1), 'YLim');

% Get axes width in pixels.
width = get_axes_width(o.h_axes(1));
%width = get_axes_width(o.h_axes(1));
axPos = round(getpixelposition(o.h_axes(1)));
width = axPos(3);
height = axPos(4);

% Just in case...
if width < 0
Expand All @@ -530,24 +582,30 @@ function RefreshData(o)
end

% Return if there's nothing to do.
if width == o.last_width && all(lims == o.last_lims)
if width == o.last_width && all(xL == o.last_xlim)
o.busy = false;
return;
end

% Record the last values for which we resized the data so we
% can skip inconsequential updates later.
o.last_width = width;
o.last_lims = lims;
o.last_width = width;
o.last_xlim = xL;
o.last_height = height;
o.last_ylim = yL;

% For all data we manage...
for k = 1:length(o.h_plot)

% Reduce the data.
[x_r, y_r] = reduce_to_width(o.x{o.y_to_x_map(k)}(:), ...
o.y{k}(:), ...
width, lims);

if o.isLong
[x_r, y_r] = reduce_to_width(o.x{o.y_to_x_map(k)}(:), ...
o.y{k}(:), ...
width, xL);
else
[y_r, x_r] = reduce_to_width(o.y{k}(:), ...
o.x{o.y_to_x_map(k)}(:), ...
height, yL);
end
% Update the plot.
set(o.h_plot(k), 'XData', x_r, 'YData', y_r);

Expand All @@ -571,7 +629,22 @@ function UnitsPreSet(o, ~, ~)
end

end

function CheckDataShape(o)
%check to see if the data is "long" or "tall"
for k = 1:length(o.y)
o.isLong = o.isLong && all(diff(o.x{o.y_to_x_map(k)}(:))>0);
o.isTall = o.isTall && all(diff(o.y{k}(:))>0);
end
if or(o.isLong,o.isTall)
%pass
else
error('LinePlotReducer:CheckDataShape:BadShape',...
'Error: Input Data is Neither Long nor Tall.')
end

end

end

end
end
23 changes: 19 additions & 4 deletions reduce_to_width.m
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@
end

% Reduce the data to the new axis size.
x_reduced = nan(n_points, size(y, 2));
if isdatetime(x)
%preallocate NaT if x is datetime class
x_reduced = NaT(n_points, size(y, 2));
else
x_reduced = nan(n_points, size(y, 2));
end
y_reduced = nan(n_points, size(y, 2));
for k = 1:size(y, 2)

Expand All @@ -58,9 +63,19 @@
xt = x(:, k);

% Map the lower and upper limits to indices.
% Skip binary search if -inf or inf : avoids error thrown for
% datetime class
nx = size(x, 1);
lower_limit = binary_search(xt, lims(1), 1, nx);
[~, upper_limit] = binary_search(xt, lims(2), lower_limit, nx);
if isinf(lims(1))
lower_limit = 1;
else
lower_limit = binary_search(xt, lims(1), 1, nx);
end
if isinf(lims(2))
upper_limit = length(xt);
else
[~, upper_limit] = binary_search(xt, lims(2), lower_limit, nx);
end

% Make the windows mapping to each pixel.
x_divisions = linspace(x(lower_limit, k), ...
Expand Down Expand Up @@ -110,4 +125,4 @@
U = C;
end
end
end
end