www.gusucode.com > mbctools 工具箱 matlab 源码程序 > mbctools/+xregdatagui/OutlierLine.m
classdef OutlierLine < matlab.mixin.SetGet & matlab.mixin.Copyable %xregdatagui.OutlierLine class % xregdatagui.OutlierLine properties: % ManagedLines - Property is of type 'MATLAB array' (read only) % OutlierLines - Property is of type 'MATLAB array' (read only) % ManagedGuids - Property is of type 'MATLAB array' (read only) % OutlierGuids - Property is of type 'MATLAB array' % GuidsToHandles - Property is of type 'MATLAB array' (read only) % OutlierIndices - Property is of type 'MATLAB array' % AltClickCallback - Property is of type 'MATLAB array' % Marker - Property is of type 'string' % Color - Property is of type 'string' % MarkerSize - Property is of type 'double' % LineWidth - Property is of type 'double' % Point - Property is of type 'mxArray' % XData - Property is of type 'mxArray' % YData - Property is of type 'mxArray' % Box - Property is of type 'mxArray' % BD - Property is of type 'mxArray' % BD_indx - Property is of type 'mxArray' % XLimMode - Property is of type 'string' % YLimMode - Property is of type 'string' % % xregdatagui.OutlierLine methods: % add - Add handles to an outlierline % clear - % OUTLIERLINE/CLEAR % multiselect - Start multiselection of outliers % Copyright 2015-2015 The MathWorks, Inc. and Ford Global Technologies, Inc. properties (Dependent,AbortSet, SetObservable) %OUTLIERGUIDS Property is of type 'MATLAB array' OutlierGuids = guidarray; end properties (AbortSet, SetObservable) %OUTLIERINDICES Property is of type 'MATLAB array' OutlierIndices = []; %ALTCLICKCALLBACK Property is of type 'MATLAB array' AltClickCallback = []; %MARKER Property is of type 'string' Marker = 'o'; %COLOR Property is of type 'string' Color = 'r'; %MARKERSIZE Property is of type 'double' MarkerSize = 10; %LINEWIDTH Property is of type 'double' LineWidth = 1.5; %POINT Property is of type 'mxArray' Point = []; %XDATA Property is of type 'mxArray' XData = []; %YDATA Property is of type 'mxArray' YData = []; %BOX Property is of type 'mxArray' Box = []; %BD Property is of type 'mxArray' BD = []; %BD_INDX Property is of type 'mxArray' BD_indx = []; %XLIMMODE Property is of type 'string' XLimMode = ''; %YLIMMODE Property is of type 'string' YLimMode = ''; end properties (Access=protected, AbortSet, SetObservable) %LISTENERS Property is of type 'handle vector' Listeners = []; end properties (SetAccess=protected, AbortSet, SetObservable) %MANAGEDLINES Property is of type 'MATLAB array' (read only) ManagedLines = gobjects( 0 ); %OUTLIERLINES Property is of type 'MATLAB array' (read only) OutlierLines = gobjects( 0 ); %MANAGEDGUIDS Property is of type 'MATLAB array' (read only) ManagedGuids = guidarray; %GUIDSTOHANDLES Property is of type 'MATLAB array' (read only) GuidsToHandles = []; end methods % constructor block function obj = OutlierLine(lineHandles) %OutlierLine constructor % L=OUTLIERLINE(lineHandle) % L=OUTLIERLINE([lineHandles]) % L=OUTLIERLINE([lineHandles],'Property1',Value1,...) if nargin < 1 lineHandles = []; end if ~isempty(lineHandles) % add the input line handles to the outlierline add(obj,lineHandles); end end % guidoutlierline end % constructor block methods function value = get.OutlierGuids(obj) value = obj.ManagedGuids(obj.OutlierIndices); end function set.OutlierGuids(obj,value) if ~isa(value, 'guidarray') error(message('mbc:xregGui:guidoutlierline:InvalidProperty')); end % Which indices have been selected index = getIndices(obj.ManagedGuids, value); % Remove invalid selection index(index == 0) = []; % Set the internal outlierIndices obj.OutlierIndices = index; end function set.Marker(obj,Marker) obj.Marker = Marker; updateLine(obj); end function set.Color(obj,Color) obj.Color = Color; updateLine(obj); end function set.MarkerSize(obj,MarkerSize) obj.MarkerSize = MarkerSize; updateLine(obj); end function set.LineWidth(obj,LineWidth) obj.LineWidth = LineWidth; updateLine(obj); end function set.OutlierIndices(obj,OutlierIndices) obj.OutlierIndices = OutlierIndices; redraw(obj); end end % set and get functions methods % public methods %---------------------------------------- function add(obj, lines, guids) %ADD Add handles to an outlierline % ADD(OL, NEWHANDLES, GUIDS) checks NEWHANDLES are line handles and adds % them to the outlierline OL % % A series of handles are associated with the relevant parts of a single % guid array. Hence an array of handles will index individually into the % portions of the guid array. This function will throw an error if the guid % array is shorter than the amount of data % Throw an error if we try to include something that isn't a handle if ~all(isgraphics(lines, 'line')) error(message('mbc:xregGui:guidoutlierline:InvalidArgument')); end % Only want truly new lines, so remove those we already have lines(ismember(lines, obj.ManagedLines)) = []; % Anything left? if isempty(lines) return end % Better check the number of points being indexed is the same as or more % than the length of the guid array data = get(lines, {'XData'}); if length([data{:}]) < length(guids) error(message('mbc:xregGui:guidoutlierline:InvalidArgument1')); end % now set deletefcn and ButtonDownFcn for the new lines set(lines, 'ButtonDownFcn', {@i_lineClick, obj}); newLineLengths = zeros(1, numel(lines)); newLineIndex = cell(1, numel(lines)); % do the drawing on the new lines labelled by newH outlierLines = gobjects(1,numel(lines)); for i = 1:numel(lines) thisLine = lines(i); % Create the outlier line for this managed line - note handle % visibility of the outlier line set to off. Users of outlier lines % should try to avoid deleteing them before the line they shadow, % otherwise we end up in trouble during deletion and holding invalid % handles to lines which doesn't seem to work in UDD. outlierLines(i) = line(... 'Color', obj.Color,... 'Marker', obj.Marker,... 'LineStyle', 'none',... 'Parent', thisLine.Parent,... 'HitTest', 'off',... 'MarkerSize', obj.MarkerSize,... 'LineWidth', obj.LineWidth,... 'HandleVisibility', 'off',... 'Tag','Outliers',... 'Visible', thisLine.Visible); mbcgui.hgclassesutil.setNotPickable(outlierLines(i)); % Add a property to hold the listeners to the new outlier line mbcgui.hgclassesutil.addprop(thisLine, 'OL_Listeners'); % === listen for line being destroyed, data changed, etc === listeners = {... mbcgui.hgclassesutil.listener(thisLine, 'ObjectBeingDestroyed', mbcutils.callback(@i_lineDestroyed, obj));... mbcgui.hgclassesutil.proplistener(thisLine, 'XData', 'PostSet', mbcutils.callback(@i_dataChanged, obj));... mbcgui.hgclassesutil.proplistener(thisLine, 'YData', 'PostSet', mbcutils.callback(@i_dataChanged, obj));... mbcgui.hgclassesutil.proplistener(thisLine, 'ZData', 'PostSet', mbcutils.callback(@i_dataChanged, obj));... mbcgui.hgclassesutil.proplistener(thisLine, 'Visible', 'PostSet', mbcutils.callback(@i_visibleChanged, outlierLines(i)));... }; % Add the listeners to the line set(thisLine, 'OL_Listeners', listeners); % Remember how much data there is in each line newLineLengths(i) = length(get(thisLine, 'XData')); % Has the line reordered it's XData (see sweepset/plot.. if isprop(thisLine, 'OriginalXDataIndex') newLineIndex{i} = get(thisLine, 'OriginalXDataIndex'); else newLineIndex{i} = 1:newLineLengths(i); end end numLinesBefore = length(obj.ManagedLines); % Add new lines to the property fields obj.ManagedLines = [obj.ManagedLines ; lines(:) ]; obj.OutlierLines = [obj.OutlierLines ; outlierLines(:) ]; % Do these lines have any new guids? newGuids = guids(~ismember(guids, obj.ManagedGuids)); % Add them to the ManagedGuids obj.ManagedGuids = [obj.ManagedGuids ; newGuids(:)]; % Need to update the GuidsToHandles array - first resize by placing a zero % in the bottom right corner obj.GuidsToHandles(length(obj.ManagedGuids), length(obj.ManagedLines)) = 0; % Calculate the start and end points for the guidarray gStart = cumsum([1 newLineLengths]); gEnd = cumsum(newLineLengths); tmpGuidsToHandles = obj.GuidsToHandles; tmpManagedGuids = obj.ManagedGuids; % Now indicate which line handles contain which guids for i = 1:length(newLineLengths) % Find the rows that hold these elements index = getIndices(tmpManagedGuids, guids(gStart(i):gEnd(i))); tmpGuidsToHandles(index, i + numLinesBefore) = newLineIndex{i}(:); end obj.GuidsToHandles = tmpGuidsToHandles; % Update the new lines obj.redraw([], lines); end % add %---------------------------------------- function clear(obj) %CLEAR clear outlier indices % CLEAR(OBJ) % clear the ol indices obj.OutlierIndices = []; end % clear %---------------------------------------- function multiselect(oL) %MULTISELECT Start multiselection of outliers % OBJ.MULTISELECT starts a multi-selection action in the GUI. % set up the button down fcn OK = isgraphics(oL.ManagedLines); oL.ManagedLines = oL.ManagedLines(OK); oL.OutlierLines = oL.OutlierLines(OK); lineParents = get(oL.ManagedLines,{'Parent'}); lineParents = [lineParents{:}]; allFigs = ancestor(oL.ManagedLines,'figure'); if length(allFigs)>1 allFigs = unique([allFigs{:}]); end ws = warning('off','mbc:mv_rotate3d:InvalidState1'); restoreWarns = onCleanup(@() warning(ws)); old_btn_down = get(allFigs, 'WindowButtonDownFcn'); set(allFigs, 'WindowButtonDownFcn', @i_point, ... 'Pointer', 'crosshair'); xtickmode = []; ytickmode = []; MManager= []; function i_point(hFig, ~) % capture mouse click and get ready for dragging (or abort) set(allFigs, 'WindowButtonDownFcn', old_btn_down, ... 'Pointer','arrow'); set(hFig, 'Pointer','crosshair'); axH = get(hFig, 'CurrentAxes'); ind = ismember(lineParents, axH); SweepLines = oL.ManagedLines(ind); if isempty(SweepLines) || strcmp(get(axH, 'Visible'),'off'); % abort set(hFig, 'Pointer','arrow'); return end % check we clicked inside the axes? cp = get(hFig,'CurrentPoint'); axpos = get(axH, 'Position'); cp = mbcgui.util.convertLocation(cp, axH); if cp(1)< 1 || cp(1)>axpos(3) ... || cp(2)<1 || cp(2)>axpos(4) % abort set(hFig, 'Pointer','arrow'); return end bm = xregGui.ButtonUpManager(hFig); bm.getNextEvent({@i_clean, oL, axH}); MManager = MotionManager(hFig); MManager.MouseMoveFcn = {@i_motion, axH}; MManager.EnableTree = false; % get the current axes_limmodes and tickmodes oL.XLimMode = get(axH,'XLimMode'); oL.YLimMode = get(axH,'YLimMode'); xtickmode = get(axH,'XTickMode'); ytickmode = get(axH,'YTickMode'); set(axH,'XLimMode','manual','YLimMode','manual', ... 'XTickMode','manual','YTickMode','manual'); cp = get(axH,'CurrentPoint'); point = cp(1,1:2); xdata = zeros(1,5); ydata = zeros(1,5); xdata(:) = point(1); ydata(:) = point(2); oL.Point = point; oL.XData = xdata; oL.YData = ydata; % the stretchy box boxl = line('Parent',axH,... 'LineStyle',':',... 'XData',xdata,... 'YData',ydata,... 'Color','r',... 'Tag','box'); % the line marking points in the box oL.BD = line('Parent',axH,... 'LineStyle','none',... 'Marker','o',... 'Color',[1 0 0],... 'XData',NaN,... 'YData',NaN,... 'LineWidth',2,... 'MarkerSize',12); oL.Box = boxl; % oL.BD_indx = false(1,oL.datasize); end function i_motion(~, ~, axH) cp = get(axH,'CurrentPoint'); point = cp(1,1:2); box = oL.Box; xdata = oL.XData; ydata = oL.YData; xdata([2,3]) = point(1); ydata([3,4]) = point(2); % mark on the points in the stretchy box i_highlight(oL,xdata,ydata, axH,lineParents); % draw the stretchy box set(box,'XData',xdata,'YData',ydata); end function i_clean(bm, ~, oL, axH) set(bm.Figure, 'Pointer', 'arrow'); MManager.MouseMoveFcn = ''; MManager.EnableTree = true; % restore axes limits mode set(axH,'XLimMode',oL.XLimMode,'YLimMode',oL.YLimMode); %restore the tickmodes as well set(axH,'XTickMode',xtickmode,'YTickMode',ytickmode); % delete the red box... delete(oL.Box); % and the big red circles delete(oL.BD); % update the x and ydata in the outlier line. oL.OutlierIndices= union(oL.OutlierIndices,oL.BD_indx); end end end % public methods methods (Hidden) % possibly private or hidden %---------------------------------------- function click(obj, hLine) %CLICK click callback for outlier lines % CLICK(obj, hLine) % CLICK is the line callback from one of the OL lines. % New outlier added or current outlier removed from all OL lines % Find the line in the list of handled lines lineIndex = find(hLine == obj.ManagedLines); % Is it one of ours? if isempty(lineIndex) return end % Find the point index in the data of the line that we have clicked on. ax = get(hLine, 'Parent'); cp = get(ax, 'CurrentPoint'); p1 = cp(1, :)'; p2 = cp(2, :)'; % Make some decisions SELECTION_IS_3D = ~isequal(p1(1:2), p2(1:2)); DATA_IS_3D = ~isempty(get(hLine, 'ZData')); % We are going to need to code everything by the axis limits and offset by % CurrentPoint(1,:) sX = diff(get(ax, 'XLim')); sY = diff(get(ax, 'YLim')); sZ = diff(get(ax, 'ZLim')); % Make the required data correctly if SELECTION_IS_3D if DATA_IS_3D data = [(get(hLine, 'XData') - p1(1))/sX ;... (get(hLine, 'YData') - p1(2))/sY ;... (get(hLine, 'ZData') - p1(3))/sZ ]; else data = [(get(hLine, 'XData') - p1(1))/sX ;... (get(hLine, 'YData') - p1(2))/sY ;... (zeros(size(get(hLine, 'XData'))) - p1(3))/sZ ]; end else data = [(get(hLine, 'XData') - p1(1))/sX ;... (get(hLine, 'YData') - p1(2))/sY ]; end % Only bother with the 3D selection if the selection is in 3D if SELECTION_IS_3D % Get the vector that needs to be rotated to the z-axis v = (p2 - p1)./[sX ; sY ; sZ]; % Create x, y and z from the vector x = v(1);y = v(2);z = v(3); % Find the rotation matrix - first need to az and el - Note that we % require a negative rotation around the z-axis but a positive rotation % around the y-axis - Hence p is positive and t is negative p = atan2(sqrt(x*x + y*y), z); t = -atan2(y, x); % Form cos and sin of p and t ct = cos(t);st = sin(t);cp = cos(p);sp = sin(p); % Form the matrix - But we don't care about z now, so ignore it M = [cp*ct -cp*st -sp ;... st ct 0 ]; %;... %sp*ct -sp*st cp ]; % Rotate and translate the data appropriately data = M * data; % % Try an alternative metric - Mark H's approach % q = [xdata ; ydata ; zdata]; % fv = repmat(v, 1, size(q, 2)); % % Try Marks approach % n = q - v * (dot(fv, q)/sum(v.^2)); % nmetric = sqrt(sum(n.^2)); end % Calculate a distance metric for the clicked point - NOTE it doesn't % matter what metric we use for closeness - using L-Inf metric = sqrt(sum(data.^2)); [~, pointIndex] = min(metric); % OK - which guid was just clicked guidIndex = find(obj.GuidsToHandles(:, lineIndex) == pointIndex); % Did we find a valid guidIndex if isempty(guidIndex) return end obj.OutlierIndices = setxor(obj.OutlierIndices, guidIndex); % %% outlierIndices listener changes calls redraw callback end % click %---------------------------------------- function redraw(obj, ~, linesToUpdate) %REDRAW deletes then redraws all the current outlierlines of OL % REDRAW(OL, ~, linesToUpdate) % Get a local copy of ManagedLines and GuidsToHandles managedLines = obj.ManagedLines; outlierLines = obj.OutlierLines; guidToHandle = obj.GuidsToHandles; outlierIndices = obj.OutlierIndices; % The lines requiring outlierlines to be drawn on them if nargin < 3 indexToUpdate = 1:length(managedLines); else indexToUpdate = find(ismember(managedLines, linesToUpdate)); end % Anything to update? if isempty(indexToUpdate) return end % Iterate over the lines that need updateing for i = indexToUpdate(:)' % Get this line and it's associated outlier line thisOutlierLine = outlierLines(i); thisManagedLine = managedLines(i); % Only bother attempting to redraw if the outlierLine is still valid if ~isgraphics(thisOutlierLine) continue end % Which indices for this line have been selected selectedLineIndex = guidToHandle(outlierIndices, i); % Remove any zeros that indicate that that guid is not present in this % line selectedLineIndex(selectedLineIndex == 0) = []; % Get the relevant data for the outlier line xdata = get(thisManagedLine,'XData'); xdata = xdata(selectedLineIndex); ydata = get(thisManagedLine, 'YData'); ydata = ydata(selectedLineIndex); % Possible that zdata isn't the same size as x and y if isempty(get(thisManagedLine, 'ZData')) set(outlierLines(i), 'XData', xdata, 'YData', ydata); else % Only include zdata if it is relevant zdata = get(thisManagedLine, 'ZData'); zdata = zdata(selectedLineIndex); set(outlierLines(i), 'XData', xdata, 'YData', ydata, 'ZData', zdata); end end end % redraw %---------------------------------------- function remove(obj, lines) %REMOVE remove lines from OutlierLine % REMOVE(OL, HNDS) check HNDS are current line handles of OL, % delete their outlier lines and remove the line handles from the % OL userdata % % called by listner to ObjectBeingDestroyed of line on % line/axes/figure delete. % NOTE: all lines can be removed, the outlierindices persist % Check they are current OL line handles index = ismember(obj.ManagedLines, lines, 'legacy'); % Did we find any? if any(index) set(obj.ManagedLines(index), 'ButtonDownFcn', ''); % Which of the outlier lines need to be removed linesToDelete = obj.OutlierLines(index); % Are they still valid - i.e. has a figure deletion already got rid of % them delete(linesToDelete(isgraphics(linesToDelete))); % remove line handles etc from ud obj.ManagedLines = obj.ManagedLines(~index); obj.OutlierLines = obj.OutlierLines(~index); % Remove those line handles from the GuidsToHandles Array obj.GuidsToHandles = obj.GuidsToHandles(:, ~index); % Has this left some guids we are no longer managing validGuids = any(obj.GuidsToHandles, 2); % Need a logical outlierIndices array to correctly dereference lOutlierIndices = false(1, length(obj.ManagedGuids)); lOutlierIndices(obj.OutlierIndices) = true; % Keep these valid guids obj.GuidsToHandles = obj.GuidsToHandles(validGuids, :); obj.ManagedGuids = obj.ManagedGuids(validGuids); % Convert the outlierIndices to a logical to remove, and then convert % back lOutlierIndices = lOutlierIndices(validGuids); obj.OutlierIndices = find(lOutlierIndices); end end % remove end % possibly private or hidden end % classdef function updateLine(obj) % Need to update the properties of the outlier lines if isgraphics(obj.OutlierLines) set(obj.OutlierLines, ... 'Marker', obj.Marker,... 'Color', obj.Color,... 'MarkerSize', obj.MarkerSize,... 'LineWidth', obj.LineWidth); end end % i_redraw function i_lineDestroyed(src, ~, obj) % Need to ensure that we still exist if isvalid(obj) remove(obj, src); end end % i_lineDestroyed % ------------------------------------------------------------------------------ function i_visibleChanged(~, event, outlierLine) % Replicate the visibility in the outlier line set(outlierLine, 'Visible', get(event.AffectedObject, 'Visible')); end % i_visibleChanged % ------------------------------------------------------------------------------ function i_dataChanged(src, event, obj) % Which outlier line needs it's data updateing obj.redraw(event, src); end % i_dataChanged % ------------------------------------------------------------------------------ function i_lineClick(lineH, event, obj) % function handle call clicktype = get(gcbf,'SelectionType'); switch clicktype case 'alt' xregcallback(obj.AltClickCallback, lineH, event); otherwise click(obj, lineH); end end % i_lineClick function i_highlight(oL,xcoords,ycoords, axH,lineParents) %i_highlight(oL,xcoords,ycoords, axH,lineParents) % find all lines on these axes (axH) ind = ismember(lineParents, axH); SweepLines=oL.ManagedLines(ind); % find the indices of all datapoints in the box bd_xdata=[]; bd_ydata = []; for i = 1:length(SweepLines) lineIndex = SweepLines(i) == oL.ManagedLines; xdata=get(SweepLines(i),'XData'); ydata=get(SweepLines(i),'YData'); xindx=xdata>min(xcoords)&xdata<max(xcoords); yindx=ydata>min(ycoords)&ydata<max(ycoords); thisIndx=find(xindx & yindx); guidIndex = find( ismember(oL.GuidsToHandles(:, lineIndex),thisIndx )); if i==1 totalIndx = guidIndex; else totalIndx = union(guidIndex, totalIndx); end bd_xdata = [bd_xdata, xdata(thisIndx)]; %#ok<AGROW> bd_ydata = [bd_ydata, ydata(thisIndx)]; %#ok<AGROW> end oL.BD_indx=totalIndx; bd_hnd=oL.BD; if isgraphics(bd_hnd) set(bd_hnd,'XData',bd_xdata,... 'YData',bd_ydata); end end