diff --git a/.gitignore b/.gitignore index 01c4bce..ddd1745 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ # File indicating that this directory should be kept on the path matlab_path +*.asv diff --git a/LinePlotExplorer.m b/LinePlotExplorer.m index 647bc75..aa57626 100644 --- a/LinePlotExplorer.m +++ b/LinePlotExplorer.m @@ -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 @@ -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'); @@ -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. @@ -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); @@ -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{:}); @@ -240,4 +248,4 @@ function execute_callback(cb, h, event, varargin) eval(cb); end end -end +end \ No newline at end of file diff --git a/LinePlotReducer.m b/LinePlotReducer.m index 8e668af..3ead2e3 100644 --- a/LinePlotReducer.m +++ b/LinePlotReducer.m @@ -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 @@ -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. @@ -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 @@ -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 @@ -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. @@ -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 @@ -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 @@ -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); @@ -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 \ No newline at end of file diff --git a/reduce_to_width.m b/reduce_to_width.m index 88f5ae4..2244e54 100644 --- a/reduce_to_width.m +++ b/reduce_to_width.m @@ -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) @@ -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), ... @@ -110,4 +125,4 @@ U = C; end end -end +end \ No newline at end of file