www.gusucode.com > mbctools 工具箱 matlab 源码程序 > mbctools/+xregdatagui/TssfClusterView.m

    classdef TssfClusterView < xregdatagui.AbstractDataView
    %xregdatagui.TssfClusterView class
    %   xregdatagui.TssfClusterView extends xregdatagui.AbstractDataView.
    %
    %    xregdatagui.TssfClusterView properties:
    %       Parent - Property is of type 'MATLAB array'
    %       Position - Property is of type 'rect'
    %       Enable - Property is of type 'on/off'
    %       Visible - Property is of type 'on/off'
    %       UserData - Property is of type 'MATLAB array'
    %       Tag - Property is of type 'string'
    %       MessageService - Property is of type 'handle' (read only)
    %       UIContextMenu - Property is of type 'MATLAB array'
    %       Listeners - Property is of type 'handle vector' (read only)
    %       Graph - Property is of type 'MATLAB array' (read only)
    %       Axes - Property is of type 'MATLAB array' (read only)
    %       MatchedDesignLine - Property is of type 'MATLAB array' (read only)
    %       MatchedDesign - Property is of type 'MATLAB array'
    %       UnmatchedDesignLine - Property is of type 'MATLAB array' (read only)
    %       UnmatchedDesign - Property is of type 'MATLAB array'
    %       HighlightDesignLine - Property is of type 'MATLAB array' (read only)
    %       HighlightDesign - Property is of type 'MATLAB array'
    %       MatchedDataLine - Property is of type 'MATLAB array' (read only)
    %       MatchedData - Property is of type 'MATLAB array'
    %       UnmatchedDataLine - Property is of type 'MATLAB array' (read only)
    %       UnmatchedData - Property is of type 'MATLAB array'
    %       ExcludedDataLine - Property is of type 'MATLAB array' (read only)
    %       ExcludedData - Property is of type 'MATLAB array'
    %       HighlightDataLine - Property is of type 'MATLAB array' (read only)
    %       HighlightData - Property is of type 'MATLAB array'
    %       DataDesignLine - Property is of type 'MATLAB array' (read only)
    %       DataDesign - Property is of type 'MATLAB array'
    %       DataLabelHandles - Property is of type 'MATLAB array' (read only)
    %       DataLabels - Property is of type 'MATLAB array'
    %       DesignLabelHandles - Property is of type 'MATLAB array' (read only)
    %       DesignLabels - Property is of type 'MATLAB array'
    %       LabelsVisible - Property is of type 'bool'
    %       ClusterPatches - Property is of type 'MATLAB array' (read only)
    %       ClusterVisible - Property is of type 'MATLAB array'
    %       GlobalDataCache - Property is of type 'MATLAB array' (read only)
    %       DisplayOptions - Property is of type 'MATLAB array' (read only)
    %       StaticInfo - Property is of type 'MATLAB array' (read only)
    %       CurrentClusterInfo - Property is of type 'MATLAB array' (read only)
    %       LegendAxes - Property is of type 'MATLAB array' (read only)
    %
    %    xregdatagui.TssfClusterView methods:
    %       ClusterTypesToDisplay - A short description of the function
    %       gettitle - returns string describing this view
    
    %  Copyright 2015-2016 The MathWorks, Inc. and Ford Global Technologies, Inc.
    
    properties (Constant)
        %ViewInfo view description
        ViewInfo = { @xregdatagui.TssfClusterView;
            'Design &Match';
            xregrespath('clusterplot.bmp');
            'Match data to design';
            1};
    end
    
    properties (AbortSet, SetObservable)
        %MATCHEDDESIGN Property is of type 'MATLAB array'
        MatchedDesign = [];
        %UNMATCHEDDESIGN Property is of type 'MATLAB array'
        UnmatchedDesign = [];
        %HIGHLIGHTDESIGN Property is of type 'MATLAB array'
        HighlightDesign = [];
        %MATCHEDDATA Property is of type 'MATLAB array'
        MatchedData = [];
        %UNMATCHEDDATA Property is of type 'MATLAB array'
        UnmatchedData = [];
        %EXCLUDEDDATA Property is of type 'MATLAB array'
        ExcludedData = [];
        %HIGHLIGHTDATA Property is of type 'MATLAB array'
        HighlightData = [];
        %DATADESIGN Property is of type 'MATLAB array'
        DataDesign = [];
        %DATALABELS Property is of type 'MATLAB array'
        DataLabels = [];
        %DESIGNLABELS Property is of type 'MATLAB array'
        DesignLabels = [];
        %LABELSVISIBLE Property is of type 'bool'
        LabelsVisible = false;
        %CLUSTERVISIBLE Property is of type 'MATLAB array'
        ClusterVisible = [];
    end
    
    properties (SetAccess=protected, AbortSet, SetObservable)
        %GRAPH Property is of type 'MATLAB array' (read only)
        Graph = [];
        %AXES Property is of type 'MATLAB array' (read only)
        Axes = [];
        %MATCHEDDESIGNLINE Property is of type 'MATLAB array' (read only)
        MatchedDesignLine = [];
        %UNMATCHEDDESIGNLINE Property is of type 'MATLAB array' (read only)
        UnmatchedDesignLine = [];
        %HIGHLIGHTDESIGNLINE Property is of type 'MATLAB array' (read only)
        HighlightDesignLine = [];
        %MATCHEDDATALINE Property is of type 'MATLAB array' (read only)
        MatchedDataLine = [];
        %UNMATCHEDDATALINE Property is of type 'MATLAB array' (read only)
        UnmatchedDataLine = [];
        %EXCLUDEDDATALINE Property is of type 'MATLAB array' (read only)
        ExcludedDataLine = [];
        %HIGHLIGHTDATALINE Property is of type 'MATLAB array' (read only)
        HighlightDataLine = [];
        %DATADESIGNLINE Property is of type 'MATLAB array' (read only)
        DataDesignLine = [];
        %DATALABELHANDLES Property is of type 'MATLAB array' (read only)
        DataLabelHandles = [];
        %DESIGNLABELHANDLES Property is of type 'MATLAB array' (read only)
        DesignLabelHandles = [];
        %CLUSTERPATCHES Property is of type 'MATLAB array' (read only)
        ClusterPatches = [];
        %GLOBALDATACACHE Property is of type 'MATLAB array' (read only)
        GlobalDataCache = [];
        %DISPLAYOPTIONS Property is of type 'MATLAB array' (read only)
        DisplayOptions = [];
        %STATICINFO Property is of type 'MATLAB array' (read only)
        StaticInfo = [];
        %CURRENTCLUSTERINFO Property is of type 'MATLAB array' (read only)
        CurrentClusterInfo = [];
        %LEGENDAXES Property is of type 'MATLAB array' (read only)
        LegendAxes = [];
        %ClusterInfo cluster info view embedded in cluster plot
        ClusterInfo
    end
    
    
    methods  % constructor block
        function obj = TssfClusterView(varargin)
        %TSSFCLUSTERVIEW constructor
        %   OUT = TSSFCLUSTERVIEW(VARARGIN)
        
        % Do the setup stuff from inside the abstractDataView constructor
        obj@xregdatagui.AbstractDataView(varargin{ : }); % converted super class constructor call
        
        % Create the graphical objects and put in the Display field
        obj.pCreateDisplay;
        
        % Update the display
        obj.pUpdateDisplay
        
        createActions(obj);
        end  % tssfclusterview
        
        
        
    end  % constructor block
    
    methods
        function set.MatchedDesign(obj,value)
        obj.MatchedDesign = i_setDesign(obj,value);
        end
        
        function set.UnmatchedDesign(obj,value)
        obj.UnmatchedDesign = i_setDesign(obj,value);
        end
        
        function set.HighlightDesign(obj,value)
        obj.HighlightDesign = i_setDesign(obj,value);
        end
        
        function set.MatchedData(obj,value)
        obj.MatchedData = i_setData(obj,value);
        end
        
        function set.UnmatchedData(obj,value)
        obj.UnmatchedData = i_setData(obj,value);
        end
        
        function set.ExcludedData(obj,value)
        obj.ExcludedData = i_setData(obj,value);
        end
        
        function set.HighlightData(obj,value)
        obj.HighlightData = i_setData(obj,value);
        end
        
        function set.DataDesign(obj,value)
        obj.DataDesign = i_setData(obj,value);
        end
        
        function set.DataLabels(obj,value)
        obj.DataLabels = i_setData(obj,value);
        end
        
        function set.DesignLabels(obj,value)
        obj.DesignLabels = i_setDesign(obj,value);
        end
        
        function set.ClusterVisible(obj,value)
        obj.ClusterVisible = i_setCluster(obj,value);
        end
        
    end   % set and get functions
    
    methods  % public methods
        %----------------------------------------
        function types = ClusterTypesToDisplay(obj)
        %ClusterTypesToDisplay A short description of the function
        %   OUT = ClusterTypesToDisplay(IN)
        
        % What are the possible types
        validTypes = {'same' 'moredata' 'moredesign' 'unmatcheddata' 'unmatcheddesign' 'designonly'};
        % Which ones are selected
        s(1) = get(obj.DisplayOptions(3), 'Value') == 1;
        s(2) = get(obj.DisplayOptions(6), 'Value') == 1;
        s(3) = get(obj.DisplayOptions(9), 'Value') == 1;
        % Create the chosen array
        chosen = [s true true s(1)];
        % Subselect the possible types
        types = validTypes(chosen);
        
        end  % ClusterTypesToDisplay
        
        %----------------------------------------
        function out = gettitle(obj) %#ok<MANU>
        %GETTITLE returns string describing this view
        %   OUT = GETTITLE(OBJ)
        
        out = 'Design Match';
        end  % gettitle
        
        %----------------------------------------
        function pSendCurrentClusterChanged(obj, ClusterIdx)
        %PSENDCURRENTCLUSTERCHANGED A short description of the function
        %   OUT = PSENDCURRENTCLUSTERCHANGED(IN)
        
        obj.MessageService.setCurrentCluster(ClusterIdx);
        
        % And update me in this event
        obj.pUpdateCurrentClusterInfo;
        
        % Update selected tests
        obj.pSendSelectedTestsChanged;
        end  % pSendCurrentClusterChanged
        
        %----------------------------------------
        function pSendSelectedTestsChanged(obj)
        %PSENDSELECTEDTESTSCHANGED A short description of the function
        %   PSENDSELECTEDTESTSCHANGED(obj)
        
        dms = obj.MessageService;
        tssf = dms.TestplanSweepsetFilter;
        ClusterIdx = dms.CurrentClusterIndex;
        if ~dms.isOneStage && ClusterIdx>0
            % Get the information about the clusters
            % Set the selected tests for two-stage data only
            thisCluster = get(dms.TestplanSweepsetFilter, 'clusters', ClusterIdx);
            
            dms.setSelectedTests( convertTestIndices(tssf, thisCluster.selecteddata) );
        end
        
        end  % pSendSelectedTestsChanged
        
        %----------------------------------------
        function pUpdateCurrentClusterInfo(obj, ~,~)
        %PUPDATECURRENTCLUSTERINFO update the information about the selected cluster
        %   PUPDATECURRENTCLUSTERINFO(OBJ)
        
        % Reset all patches with magenta borders to have normal borders and replace
        % in the correct z-order
        h = findobj(obj.Axes, '-depth', 1, 'Type', 'rectangle', 'EdgeColor', 'm');
        if ~isempty(h)
            set(h, 'EdgeColor', 'k',...
                'LineWidth', 1);
        end
        % Reset all data and design to highlight
        dataToHighlight = false(size(get(obj.HighlightDataLine, 'XData')));
        designToHighlight = false(size(get(obj.HighlightDesignLine, 'XData')));
        
        ClusterIdx = obj.MessageService.CurrentClusterIndex;
        
        % Check the validity of the cluster index
        if ClusterIdx> 0
            % Get the selected cluster
            thisCluster = get(obj.MessageService.TestplanSweepsetFilter, 'clusters', ClusterIdx);
            % Check it isn't the dataonly cluster
            if ~any(strcmp(thisCluster.status, {'unmatcheddata' 'unmatcheddesign'}))
                % Highlight this patch
                thisClusterH = obj.ClusterPatches{ClusterIdx};
                set(thisClusterH, 'EdgeColor', 'm',...
                    'LineWidth', 2.5);
                % Highlight the data and design associated with the patch
                dataToHighlight(thisCluster.data)     = true;
                designToHighlight(thisCluster.design) = true;
                % Determine the top most objects
                topMost = [...
                    findobj(obj.Axes, '-depth', 1, 'type', 'line', '-or', 'type', 'text') ;...
                    thisClusterH'];
                % Reoder the children
                set(obj.Axes, 'Children', [topMost ; setdiff(get(obj.Axes, 'Children'), topMost)]);
            end
        end
        
        % Ensure correct Highlights in data and design
        obj.HighlightData   = dataToHighlight;
        obj.HighlightDesign = designToHighlight;
        
        % Have we got a cluster index of zero - indicating a reset
        if ClusterIdx < 1
            obj.CurrentClusterInfo.ListText = {};
            return
        end
        
        % OK - What sort of cluster is it
        switch thisCluster.status
            case 'unmatcheddata'
                % Write this cluster info to the stats box
                obj.CurrentClusterInfo.ListText =...
                    {'Unmatched Data Points', num2str(length(thisCluster.data))};
            case 'unmatcheddesign'
                % Write this cluster info to the stats box
                obj.CurrentClusterInfo.ListText =...
                    {'Unmatched Design Points', num2str(length(thisCluster.design))};
            case {'moredesign' 'moredata' 'same' 'designonly'}
                % Write this cluster info to the stats box - note transpose on Test
                % Number to ensure it prints nicely
                obj.CurrentClusterInfo.ListText = {...
                    'Design Points', num2str(length(thisCluster.design));...
                    'Data Points', num2str(length(thisCluster.data));...
                    'Design Index', num2str(thisCluster.design);...
                    'Test Number',  num2str(obj.GlobalDataCache(thisCluster.data, end)');...
                    };
            otherwise
                warning(message('mbc:xregdatagui:tssfclusterview:InvalidState', thisCluster.status));
        end
        
        end  % pUpdateCurrentClusterInfo
        
        %----------------------------------------
        function pUpdateDisplay(obj)
        %PUPDATEDISPLAY complete update of the view display
        %   PUPDATEDISPLAY(OBJ)
        
        if hasData(obj.MessageService)
            % Simulate a data type changed update
            obj.pdmsDataTypeChangedUpdate;
            % Check the class of the current data object
            if obj.MessageService.isaTSSF
                obj.ptssfActualDesignChangedUpdate;
                obj.ptssfClustersCreatedUpdate;
                obj.ptssfClustersChangedUpdate;
            end
        end
        end  % pUpdateDisplay
        
        %----------------------------------------
        function pUpdateLabelVisibility(obj)
        %PUPDATELABELVISIBILITY A short description of the function
        %   OUT = PUPDATELABELVISIBILITY(IN)
        
        % Which data labels should be visible
        dataLabelsToDisplay = ...
            ((obj.MatchedData & strcmp(get(obj.MatchedDataLine, 'Visible'), 'on') ) | ...
            (obj.UnmatchedData & strcmp(get(obj.UnmatchedDataLine, 'Visible'), 'on') ) | ...
            (obj.ExcludedData & strcmp(get(obj.ExcludedDataLine, 'Visible'), 'on') )) & obj.LabelsVisible;
        
        designLabelsToDisplay = ...
            ((obj.MatchedDesign & strcmp(get(obj.MatchedDesignLine, 'Visible'), 'on') ) | ...
            (obj.UnmatchedDesign & strcmp(get(obj.UnmatchedDesignLine, 'Visible'), 'on') )) & obj.LabelsVisible;
        
        % This should fire off the listeners in pCreateDisplay - however if
        % obj.DataLabels is already the same as it's new value then the event isn't
        % fired. However, after the view is made visible all childern of axes are
        % set visible on and so this call doesn't correctly reset the visibility.
        %
        % For now we'll explicitly set the visibility, as we do in
        % pUpdateLineVisibility - However this does mean that obj.DataLabels is
        % perhaps a little redundant
        % obj.DataLabels = dataLabelsToDisplay;
        % obj.DesignLabels = designLabelsToDisplay;
        set(obj.DataLabelHandles(dataLabelsToDisplay),  'Visible', 'on');
        set(obj.DataLabelHandles(~dataLabelsToDisplay), 'Visible', 'off');
        set(obj.DesignLabelHandles(designLabelsToDisplay),  'Visible', 'on');
        set(obj.DesignLabelHandles(~designLabelsToDisplay), 'Visible', 'off');
        
        end  % pUpdateLabelVisibility
        
        %----------------------------------------
        function pUpdateLineAndPatchData(obj)
        %PUPDATELINEANDPATCHDATA reset the line and patch data
        %   PUPDATELINEANDPATCHDATA(OBJ)
        %   This method is triggered by a change in the factors being displayed
        
        % Get the data
        tssf = obj.MessageService.TestplanSweepsetFilter;
        % Get my data - note, this excludes any sweeps I remove - PROBABLY NOT A
        % GOOD THING!
        X = obj.GlobalDataCache;
        % Get the clusters and actual design
        clusters = get(tssf, 'clusters');
        [D, ~] = get(tssf, {'actualdesign' 'actualdata'});
        tol = get(tssf, 'tolerances');
        
        if size(D,2)==0
            D = zeros(0,1);
            X = D;
            tol = 1e-3;
        end
        
        xFacInd = obj.Graph.XFactor;
        yFacInd = obj.Graph.YFactor;
        
        % Put data into each line on the plot. These do not change and will only
        % get turned vis on/off
        set([obj.MatchedDesignLine obj.UnmatchedDesignLine obj.HighlightDesignLine],...
            'XData', D(:, xFacInd),...
            'YData', D(:, yFacInd));
        
        set([obj.MatchedDataLine obj.UnmatchedDataLine obj.ExcludedDataLine obj.DataDesignLine obj.HighlightDataLine],...
            'XData', X(:, xFacInd),...
            'YData', X(:, yFacInd));
        
        % Add the data and design test numbers
        obj.DataLabelHandles   = mbctagdata(X(:, [xFacInd yFacInd]), obj.GlobalDataCache(:, end), obj.Axes, obj.DataLabelHandles, 'on', 'simple');
        obj.DesignLabelHandles = mbctagdata(D(:, [xFacInd yFacInd]), 1:size(D, 1)               , obj.Axes, obj.DesignLabelHandles, 'on', 'simple');
        % Make the design labels left aligned
        set(obj.DesignLabelHandles, 'HorizontalAlignment', 'left', 'VerticalAlignment', 'top');
        
        tolx = tol(xFacInd);
        toly = tol(yFacInd);
        
        % Cluster-by-cluster, plot patch around each design point in the cluster
        for i = 1:length(clusters)
            % Get the design indices for this cluster
            desInds = clusters(i).design;
            % OK - set the patch data
            for j = 1:length(obj.ClusterPatches{i})
                xval = D(desInds(j), xFacInd);
                yval = D(desInds(j), yFacInd);
                set(obj.ClusterPatches{i}(j),...
                    'Position', [xval-tolx yval-toly 2*tolx 2*toly]);
            end
        end
        
        set(obj.Axes, 'XLimMode', 'auto', 'YLimMode', 'auto');
        end  % pUpdateLineAndPatchData
        
        
        %----------------------------------------
        function pUpdateLineVisibility(obj)
        %PUPDATELINEVISIBILITY A short description of the function
        %  OUT = PUPDATELINEVISIBILITY(IN)
        
        DispOpts = get(obj.DisplayOptions, {'Value'});
        
        set(obj.MatchedDataLine,     'Visible', mbconoff(DispOpts{1}));
        set(obj.UnmatchedDataLine,   'Visible', mbconoff(DispOpts{2}));
        
        set(obj.MatchedDesignLine,   'Visible', mbconoff(DispOpts{4}));
        set(obj.UnmatchedDesignLine, 'Visible', mbconoff(DispOpts{5}));
        
        set(obj.ExcludedDataLine,    'Visible', mbconoff(DispOpts{7}));
        set(obj.DataDesignLine,      'Visible', mbconoff(DispOpts{8}));
        
        obj.pUpdateLabelVisibility;
        end  % pUpdateLineVisibility
        
        %----------------------------------------
        function pdmsDataTypeChangedUpdate(obj, ~,~)
        %PDMSDATATYPECHANGEDUPDATE event handler for the dmsDataTypeChanged event
        %   PDMSDATATYPECHANGEDUPDATE(OBJ, SRC, EVENT)
        
        % Find the tssf listeners
        tssfListeners = obj.MessageService.findListeners(obj.Listeners, 'TestplanSweepsetFilter');
        % If the dataObject isn't a sweepsetfilter then...
        
        % Turn on the sweepsetfilter listener
        [tssfListeners.Enabled] = deal(true);
        % Disable all the check boxes
        set(obj.DisplayOptions, 'Enable', 'on');
        tolAction = findobj(obj.Options.Actions,'Description','Set input tolerances');
        if ~isempty(tolAction)
            % disable Tolerances action when data is readonly
            tolAction.Enabled = ~obj.MessageService.IsReadOnly;
        end
        
        end  % pdmsDataTypeChangedUpdate
        
        %----------------------------------------
        function ptssfActualDesignChangedUpdate(obj, ~,~)
        %ptssfActualDesignChangedUpdate  update of the view display for design changes
        %   ptssfActualDesignChangedUpdate(OBJ)
        
        % What signal names are being used in the design
        signalNames = globalsignalnames(obj.MessageService.TestplanSweepsetFilter);
        set(obj.Graph, 'factors', signalNames);
        end  % ptssfActualDesignChangedUpdate
        
        %----------------------------------------
        function ptssfClustersChangedUpdate(obj, ~,~)
        %ptssfClustersChangedUpdate redraws the cluster patches
        %   ptssfClustersChangedUpdate(OBJ)
        
        clusters = get(obj.MessageService.TestplanSweepsetFilter, 'clusters');
        % Ensure that the clusterVisible listener isn't fired
        listener = obj.Listeners.findobj('Source', {obj.findprop('ClusterVisible')});
        % Disable listener
        if ~isempty(listener)
            listener.Enabled = false;
        end
        % Update the cluster visibility
        obj.ClusterVisible = ismember({clusters.status}, obj.ClusterTypesToDisplay);
        if ~isempty(listener)
            % Re-enable listener
            listener.Enabled = true;
        end
        % Update the patches
        obj.pUpdateClusterVisibility;
        
        end  % ptssfClustersChangedUpdate
        
        %----------------------------------------
        function ptssfClustersCreatedUpdate(obj, ~,~)
        %PTSSFCLUSTERSCREATEDUPDATE redraws the cluster patches
        %   PTSSFCLUSTERSCREATEDUPDATE(OBJ, EVENT)
        
        % Need to create the clusters patches correctly
        
        % Get the data
        tssf = obj.MessageService.TestplanSweepsetFilter;
        % Faster to get all these together
        tssfInfo = get(tssf, { ...
            'clusters' ...
            'actualdata' ...
            'numberdesignpoints' ...
            });
        
        % Get my data
        obj.GlobalDataCache = [mean(tssfInfo{2})  testnum(tssfInfo{2})'];
        
        % Get the clusters and actual design
        clusters = tssfInfo{1};
        
        % Get info on the new clusters
        numDataPoints   = size(tssfInfo{2}, 3);
        numDesignPoints = tssfInfo{3};
        
        % Remove all the patches
        if ~isempty(obj.ClusterPatches)
            patches = [obj.ClusterPatches{:}];
            delete(patches(isgraphics(patches)));
        end
        
        obj.ClusterPatches = cell(1, length(clusters));
        % Set the unmatched data buttondown callback to not be associated with a cluster
        set(obj.UnmatchedDataLine, 'DataOnlyClusterIndex', 0);
        set(obj.UnmatchedDesignLine, 'DesignOnlyClusterIndex', 0);
        
        % Need to reorder the axes children to ensure that the lines are drawn on
        % top of the rectangles
        lines = get(obj.Axes, 'Children');
        
        % Cluster-by-cluster, create a patch for each design point
        PatchCB = @i_patchClicked;
        for i = 1:length(clusters)
            % Is this the data only cluster
            if strcmp(clusters(i).status, 'unmatcheddata')
                % Set the unmatched data buttondown callback to have the index of
                % this cluster
                set(obj.UnmatchedDataLine, 'DataOnlyClusterIndex', i);
                % Don't create any patches for this cluster
                continue
            end
            if strcmp(clusters(i).status, 'unmatcheddesign')
                % Set the unmatched data buttondown callback to have the index of
                % this cluster
                set(obj.UnmatchedDesignLine, 'DesignOnlyClusterIndex', i);
                % Don't create any patches for this cluster
                continue
            end
            % Get the design indices for this cluster
            numDesignInds = length(clusters(i).design);
            % Pre-initialise the number of patch to be drawn
            obj.ClusterPatches{i} = gobjects(1, numDesignInds);
            % OK - create the patches
            for j = 1:numDesignInds
                obj.ClusterPatches{i}(j) = rectangle('Parent', obj.Axes,...
                    'ButtonDownFcn', {PatchCB, obj, i},...
                    'EdgeColor', 'k');
            end
        end
        
        % Re-order the axes children to put the lines at the top
        set(obj.Axes, 'Children', [lines ; setdiff(get(obj.Axes, 'Children'), lines)]);
        
        % OK - all patches created, lets give them the correct data
        obj.pUpdateLineAndPatchData;
        
        % Turn all highlight lines off to start with
        falseData = false(numDataPoints, 1);
        falseDesign = false(numDesignPoints, 1);
        set(obj, 'HighlightData', falseData,...
            'HighlightDesign', falseDesign);
        
        % And reset current cluster info
        obj.CurrentClusterInfo.ListText = {};
        end  % ptssfClustersCreatedUpdate
        
    end  % public methods
    
    
    methods (Hidden) % possibly private or hidden
        %----------------------------------------
        function pCreateDisplay(obj)
        %PCREATEDISPLAY create main view
        
        % -------------------------------------------------------------------------
        % create Right hand side factor seletor and legend
        % -------------------------------------------------------------------------
        SC = mbcgui.util.SystemColorsDbl;
        
        dataAxesLayout = mbcgui.container.layoutpanel(...
            'Parent', obj.Parent, ...
            'BorderType', 'beveledin', ...
            'LayoutBorder', [3 3 3 3]);
        obj.Graph = mbcwidgets.factorselector2d('parent', dataAxesLayout,...
            'FactorChangecallback',{@i_graphFactorChangeCallback, obj},...
            'UIContextMenu',obj.UIContextMenu,...
            'SelectionType', 'exclusive');
        set(dataAxesLayout, 'LayoutComponent', obj.Graph);
        
        obj.Axes = obj.Graph.Axes;
        set(obj.Axes, 'ButtonDownFcn', {@i_axesButtonDown, obj},...
            'Box','on',...
            'XGrid','on',...
            'YGrid','on',...
            'UIContextMenu',obj.UIContextMenu)
        
        legendLayout = mbcgui.container.layoutpanel(...
            'Parent', obj.Parent, ...
            'BorderType', 'beveledin');
        legAxPanel = mbcgui.widget.AxesContainer(...
            'Parent', legendLayout,...
            'UIContextMenu',obj.UIContextMenu);
        obj.LegendAxes = legAxPanel.AxesHandle;
        set(obj.LegendAxes,...
            'Color', [1 1 1], ...
            'XTickLabel', '',...
            'XTick', [],...
            'XLim', [0 100],...
            'XColor', [1 1 1],...
            'YTickLabel', '',...
            'YTick', [],...
            'YLim', [0 100],...
            'YColor', [1 1 1],...
            'ZColor', [1 1 1],...
            'ButtonDownFcn', {@i_legendAxesButtonDown, obj});
        
        % Line types
        xdata  = [6 6 38 38 70 70];
        ydata  = [80 55 80 55 80 55];
        marker = {'.' '.' 'o' 'o' 'x' '+'};
        color  = {'b' 'r' 'b' 'r' 'k' 'm'};
        string = {'Matched data' 'Unmatched data' 'Matched design' ...
            'Unmatched design' 'Excluded data' 'Data in design'};
        
        for i = 1:length(xdata)
            line('Parent', obj.LegendAxes,...
                'XData', xdata(i),...
                'YData', ydata(i),...
                'Marker', marker{i},...
                'MarkerSize', 6,...
                'Color', color{i},...
                'LineStyle' ,'none');
            
            text(xdata(i) + 2, ydata(i), string{i},...
                'Parent', obj.LegendAxes);
        end
        
        % Patch types
        xPos = [0 0 2 2 0];
        yPos = [0 10 10 0 0];
        xOffset = [5 37 69];
        yOffset = [25 25 25];
        string  = {'Equal data and design' 'More data than design' 'Less data than design'};
        col = [188 231 191 ; 107 185 214 ; 197 137 166]./255;
        
        for i = 1:length(xOffset)
            patch('Parent', obj.LegendAxes,...
                'XData', xPos + xOffset(i),...
                'YData', yPos + yOffset(i),...
                'FaceColor', col(i, :),...
                'EdgeColor', 'k');
            
            text(xOffset(i) + 3, yOffset(i) + 5, string{i},...
                'Parent', obj.LegendAxes);
        end
        
        % Try and layer a checkbox next to the legend
        callbacks = {...
            {@i_checkClicked, obj},...
            {@i_checkClicked, obj},...
            {@i_clusterCheckClicked, obj},...
            {@i_checkClicked, obj},...
            {@i_checkClicked, obj},...
            {@i_clusterCheckClicked, obj},...
            {@i_checkClicked, obj},...
            {@i_checkClicked, obj},...
            {@i_clusterCheckClicked, obj}};
        
        tags = {'matcheddata' ...
            'unmatcheddata' ...
            'same' ...
            'matcheddesign' ...
            'unmatcheddesign' ...
            'moredata' ...
            'excludeddata' ...
            'dataindesign' ...
            'moredesign'};
        
        check = cell(length(tags),1);
        for i = 1:length(tags)
            check{i} = uicontrol(...
                'Style', 'checkbox',...
                'Value',1,...
                'Parent', legendLayout,...
                'BackgroundColor', [1 1 1],...
                'String','',...
                'HitTest', 'off',...
                'Tag', tags{i},...
                'Callback', callbacks{i});
        end
        
        obj.DisplayOptions = [check{:}];
        oneCol = repmat({[]}, 1, 5);
        
        checkGrid = xreggridbaglayout(legendLayout,...
            'packgroup', 'xregdatagui.tssfclusterview',...
            'dimension', [5 7],...
            'elements', {oneCol{:} ...
            [] check{1:3} [] ...
            oneCol{:} ...
            [] check{4:6} [] ...
            oneCol{:} ...
            [] check{7:9} []},...
            'rowratios', [0.075 0.25 0.25 0.25 0.175],...
            'colratios', [0.01 0.03 0.29 0.03 0.29 0.03 0.33]); %#ok<*CCAT>
        
        % Note border to stop white axes interfering with edge
        legendLayer = xreglayerlayout(legendLayout,...
            'packgroup', 'xregdatagui.tssfclusterview',...
            'elements', {legAxPanel, checkGrid},...
            'border', [3 3 3 3]);
        
        set(legendLayout, 'LayoutComponent', {legendLayer});
        
        dataSplitLayout = xregsnapsplitlayout(obj.Parent,...
            'packgroup', 'xregdatagui.tssfclusterview',...
            'barstyle', 1,...
            'orientation', 'ud',...
            'style','tobottom',...
            'split', [0.99 0.01],...
            'minwidth', [210 90],...
            'top', dataAxesLayout,...
            'bottom', legendLayout);
        
        % -------------------------------------------------------------------------
        % create Grid layout on left hand side
        % -------------------------------------------------------------------------
        
        statisticsTitle = mbcgui.container.titlebarpanel(...
            'Parent', obj.Parent, ...
            'BarTitle', 'Statistics');
        statisticsList = mbcgui.table.InfoPane('Parent', statisticsTitle,...
            'SplitPosition', .7);
        set(statisticsTitle, 'ContentHandle', statisticsList);
        
        currentClusterTitle = mbcgui.container.titlebarpanel(...
            'Parent', obj.Parent, ...
            'BarTitle', 'Current Cluster');
        currentClusterList = mbcgui.table.InfoPane('Parent', currentClusterTitle,...
            'SplitPosition', .4);
        set(currentClusterTitle, 'ContentHandle', currentClusterList);
        
        
        graypatch =  uicontrol('Parent', obj.Parent, ...
            'Style', 'text', ...
            'BackgroundColor', SC.CTRL_BG, ...
            'HitTest', 'off', ...
            'Enable', 'inactive');
        
        listsGridLayout = xreggridbaglayout(obj.Parent,...
            'packgroup', 'xregdatagui.tssfclusterview',...
            'dimension', [3, 1],...
            'rowsizes', [-1, 2, -1],...
            'elements', {statisticsTitle graypatch currentClusterTitle});
        
        clusterView = xregsplitlayout(obj.Parent,...
            'packgroup', 'xregdatagui.tssfclusterview',...
            'orientation','lr',...
            'dividerstyle','flat',...
            'left',listsGridLayout,...
            'right',dataSplitLayout,...
            'split',[.2 .8],...
            'dividerwidth',4);
        
        % add cluster information
        lowerPanel = mbcgui.container.titlebarpanel('Parent',obj.Parent,...
            'BarTitle','Cluster Information');
        obj.ClusterInfo = xregdatagui.TssfInfoView('Parent',lowerPanel,...
            'UIContextMenu',obj.UIContextMenu,...
            'MessageService',obj.MessageService);
        
        lowerPanel.ContentHandle = obj.ClusterInfo;
        
        mainLayout = xregsplitlayout(obj.Parent,...
            'packgroup', 'xregdatagui.tssfclusterview',...
            'dividerstyle','flat',...
            'orientation','ud',...
            'split',[1 0],...
            'minwidthunits','pixels',...
            'minwidth',[300 100],...
            'rowsizes', [-1, 80],...
            'elements', {clusterView lowerPanel});
        
        obj.ContentHandle = mainLayout;
        
        obj.StaticInfo = statisticsList;
        obj.CurrentClusterInfo = currentClusterList;
        
        createClusterGraphics(obj);

        % Create Listeners to changes in internal properties
        lineIndexListeners = [...
            event.proplistener(obj, obj.findprop('MatchedDesign'), 'PostSet', @(h,evt) i_updateLineDisplay(h,evt,obj.MatchedDesignLine));...
            event.proplistener(obj, obj.findprop('UnmatchedDesign'), 'PostSet', @(h,evt) i_updateLineDisplay(h,evt,obj.UnmatchedDesignLine));...
            event.proplistener(obj, obj.findprop('HighlightDesign'), 'PostSet', @(h,evt) i_updateLineDisplay(h,evt,obj.HighlightDesignLine));...
            event.proplistener(obj, obj.findprop('MatchedData'), 'PostSet', @(h,evt) i_updateLineDisplay(h,evt,obj.MatchedDataLine));...
            event.proplistener(obj, obj.findprop('UnmatchedData'), 'PostSet', @(h,evt) i_updateLineDisplay(h,evt,obj.UnmatchedDataLine));...
            event.proplistener(obj, obj.findprop('ExcludedData'), 'PostSet', @(h,evt) i_updateLineDisplay(h,evt,obj.ExcludedDataLine));...
            event.proplistener(obj, obj.findprop('HighlightData'), 'PostSet', @(h,evt) i_updateLineDisplay(h,evt,obj.HighlightDataLine));...
            event.proplistener(obj, obj.findprop('DataDesign'), 'PostSet', @(h,evt) i_updateLineDisplay(h,evt,obj.DataDesignLine));...
            ];
        
        clusterListeners = event.proplistener(obj, obj.findprop('ClusterVisible'), 'PostSet', @i_updateClusterDisplay);
        labelListeners = [...
            event.proplistener(obj, obj.findprop('LabelsVisible'), 'PostSet', @i_updateLabelVisibility);...
            event.proplistener(obj, obj.findprop('DataLabels'), 'PostSet', @(h,evt) i_updateLabelDisplay(h,evt,'DataLabelHandles'));...
            event.proplistener(obj, obj.findprop('DesignLabels'), 'PostSet', @(h,evt) i_updateLabelDisplay(h,evt,'DesignLabelHandles'));...
            ];
        
        obj.Listeners = [obj.Listeners ; lineIndexListeners; clusterListeners ; labelListeners];
        end  % pCreateDisplay
        
        
        function createClusterGraphics(obj)
        %createClusterGraphics create the lines used to display the different types of data
        obj.MatchedDesignLine = line('Parent', obj.Axes,...
            'XData', [],...
            'YData', [],...
            'Marker', 'o',...
            'MarkerSize',6,...
            'Color', 'b',...
            'ButtonDownFcn',{@i_pointClicked obj},...
            'LineStyle' ,'none');
        
        obj.UnmatchedDesignLine = line('Parent', obj.Axes,...
            'XData', [],...
            'YData', [],...
            'Marker', 'o',...
            'MarkerSize',6,...
            'ButtonDownFcn',{@i_pointClicked obj},...
            'Color', 'r',...
            'LineStyle' ,'none');
        
        obj.HighlightDesignLine = line('Parent', obj.Axes,...
            'XData', [],...
            'YData', [],...
            'Marker', 'd',...
            'MarkerSize', 12,...
            'LineWidth', 1.5,...
            'ButtonDownFcn',{@i_pointClicked obj},...
            'Color', 'm',...
            'LineStyle' ,'none');
        
        obj.MatchedDataLine = line('Parent', obj.Axes,...
            'XData', [],...
            'YData', [],...
            'Marker', '.',...
            'MarkerSize',6,...
            'ButtonDownFcn',{@i_pointClicked obj},...
            'Color', 'b',...
            'LineStyle' ,'none');
        
        obj.UnmatchedDataLine = line('Parent', obj.Axes,...
            'XData', [],...
            'YData', [],...
            'Marker', '.',...
            'MarkerSize',6,...
            'ButtonDownFcn',{@i_pointClicked obj},...
            'Color', 'r',...
            'LineStyle' ,'none');
        
        % Add a property to this line that holds the data only cluster information
        mbcgui.hgclassesutil.addprop(obj.UnmatchedDataLine, 'DataOnlyClusterIndex');
        mbcgui.hgclassesutil.addprop(obj.UnmatchedDesignLine, 'DesignOnlyClusterIndex');
        
        obj.ExcludedDataLine = line('Parent', obj.Axes,...
            'XData', [],...
            'YData', [],...
            'Marker', 'x',...
            'MarkerSize',6,...
            'ButtonDownFcn',{@i_pointClicked obj},...
            'Color', 'k',...
            'LineStyle' ,'none');
        
        obj.HighlightDataLine = line('Parent', obj.Axes,...
            'XData', [],...
            'YData', [],...
            'Marker', 's',...
            'MarkerSize', 12,...
            'LineWidth', 1.5,...
            'ButtonDownFcn',{@i_pointClicked obj},...
            'Color', 'm',...
            'LineStyle' ,'none');
        
        obj.DataDesignLine = line('Parent', obj.Axes,...
            'XData', [],...
            'YData', [],...
            'Marker', '+',...
            'MarkerSize',6,...
            'ButtonDownFcn',{@i_pointClicked obj},...
            'Color', 'm',...
            'LineStyle' ,'none');
        
        obj.ClusterPatches = {};
        
        end
        
        
        function createActions(obj)
        %createActions creat view option actions
        obj.Options.Actions = [mbcgui.actions.StatefulAction(@obj.onSelectUnmatchedData,'&Select Unmatched Data','Select unmatched data',[])
            mbcgui.actions.StatefulAction(@obj.onSelectUnmatchedDesign,'Select Unmatched D&esign','Select Unmatched data',[])
            mbcgui.actions.ToggleAction(@obj.onShowLabels,'Show &Labels','Show Labels',[])
            mbcgui.actions.StatefulAction(@obj.onSetTolerance,'T&olerances...','Set input tolerances',[])
            ];
        obj.Options.Actions(3).Selected = obj.LabelsVisible;
        obj.Options.Actions(4).Enabled = ~obj.MessageService.IsReadOnly;
        
        end
        
        %----------------------------------------
        function pPostSetDataMessageService(obj, ~,~)
        %PPOSTSETDATAMESSAGESERVICE setup MessageService listeners
        %    PPOSTSETDATAMESSAGESERVICE(OBJ, SRC, EVT)
        
        % Call the super pPostSetDataMessageService
        pPostSetDataMessageService@xregdatagui.AbstractDataView(obj);
        
        dms = obj.MessageService;
        
        dmsListeners = [...
            event.listener(dms, 'dmsDataTypeChanged',      @obj.pdmsDataTypeChangedUpdate);...
            event.listener(dms, 'tssfActualDesignChanged', @obj.ptssfActualDesignChangedUpdate);...
            event.listener(dms, 'tssfClustersCreated',     @obj.ptssfClustersCreatedUpdate);...
            event.listener(dms, 'tssfClustersChanged',     @obj.ptssfClustersChangedUpdate);...
            event.listener(dms, 'CurrentClusterChanged',   @obj.pUpdateCurrentClusterInfo);...
            ];
        
        obj.Listeners = [obj.Listeners(:); dmsListeners(:)];
        
        end  % pPostSetDataMessageService
        
        %----------------------------------------
        function  pUpdateClusterVisibility(obj)
        %PUPDATECLUSTERVISIBILITY update cluster visibility
        %   OUT = PUPDATECLUSTERVISIBILITY(IN)
        
        % Get the data
        tssf = obj.MessageService.TestplanSweepsetFilter;
        % Get information from tssf in one call - this is an optimisation
        [...
            clusters,...
            numDesignPoints,...
            numDataPoints,...
            matchedDesign,...
            unmatchedDesign,...
            matchedData,...
            unmatchedData,...
            excludedData,...
            dataInDesign...
            ] ...
            = get(tssf, { ...
            'clusters' ...
            'NumberDesignPoints' ...
            'NumberDataPoints',...
            'MatchedDesignInds' ...
            'UnmatchedDesignInds' ...
            'MatchedDataInds' ...
            'UnmatchedDataInds' ...
            'ExcludedDataInds' ...
            'DataInDesignInds' ...
            });
        
        numUnmatchedClusters = sum(strcmp({clusters(:).status}, 'unmatcheddata')) + sum(strcmp({clusters(:).status}, 'unmatcheddesign'));
        % Update the static test info display
        obj.StaticInfo.ListText = {...
            'Design Points',    num2str(numDesignPoints) ;...
            'Matched Design',   num2str(length(matchedDesign)) ;...
            'Unmatched Design', num2str(length(unmatchedDesign)) ;...
            'Data in Design',   num2str(length(dataInDesign)) ;...
            'Data Points',      num2str(numDataPoints) ;...
            'Matched Data',     num2str(length(matchedData)) ;...
            'Unmatched Data',   num2str(length(unmatchedData)) ;...
            'Excluded Data',    num2str(length(excludedData));...
            'Clusters',         num2str(length(clusters) - numUnmatchedClusters);...
            'Green Clusters',   num2str(sum(strcmp({clusters(:).status}, 'same'))) ;...
            'Blue Clusters',    num2str(sum(strcmp({clusters(:).status}, 'moredata'))) ;...
            'Red Clusters',     num2str(sum(strcmp({clusters(:).status}, 'moredesign'))) ;...
            };
        
        % Get the data indices of those clusters not being displayed
        unselectedData = [clusters(~obj.ClusterVisible).data];
        % Get the design indices of those clusters not being displayed
        unselectedDesign = [clusters(~obj.ClusterVisible).design];
        
        % Set the design line visibility
        obj.MatchedDesign   = i_indexToLogical(setdiff(matchedDesign, unselectedDesign), numDesignPoints);
        obj.UnmatchedDesign = i_indexToLogical(setdiff(unmatchedDesign, unselectedDesign), numDesignPoints);
        
        % Set the data line visibility
        obj.MatchedData     = i_indexToLogical(setdiff(matchedData, unselectedData), numDataPoints);
        obj.UnmatchedData   = i_indexToLogical(setdiff(unmatchedData, unselectedData), numDataPoints);
        obj.ExcludedData    = i_indexToLogical(setdiff(excludedData, unselectedData), numDataPoints);
        
        % Set the dataInDesign visibility
        % MOVE THIS SOME WHERE ELSE
        obj.DataDesign      = i_indexToLogical(dataInDesign, numDataPoints);
        
        obj.pUpdateLabelVisibility;
        
        % Set the cluster visibility
        if any(obj.ClusterVisible)
            set([obj.ClusterPatches{obj.ClusterVisible}], 'Visible', 'on');
        end
        if any(~obj.ClusterVisible)
            set([obj.ClusterPatches{~obj.ClusterVisible}], 'Visible', 'off');
        end
        
        % Set the Cluster colors and zdata
        status = {clusters.status};
        % More Design Points
        inds = strcmp('moredesign', status);
        col = [197 137 166]./255;
        if any(inds)
            set([obj.ClusterPatches{inds}],...
                'Tag','MoreDesign',...
                'FaceColor', col);
        end
        % More Data Points
        inds = strcmp('moredata', status);
        col = [107 185 214]./255;
        if any(inds)
            set([obj.ClusterPatches{inds}],...
                'Tag','MoreData',...
                'FaceColor', col);
        end
        % Same number of Points
        inds = strcmp('same', status) | strcmp('designonly', status);
        col = [188 231 191]./255;
        if any(inds)
            set([obj.ClusterPatches{inds}],...
                'Tag','Same',...
                'FaceColor', col);
        end
        
        % Ensure that highlights are set correctly
        obj.HighlightData(unselectedData) = false;
        obj.HighlightDesign(unselectedDesign) = false;
        end  % pUpdateClusterVisibility
        
    end  % possibly private or hidden
    
    methods (Access=protected)
        %----------------------------------------
        function setVisible(obj,Vis)
        %setVisible set component visible
        %   OUT = setVisible(IN)

        setVisible@xregdatagui.AbstractDataView(obj,Vis);
        
        % After setting this view visible on we need to ensure that the correct
        % lines are visible or invisible
        if strcmp(Vis, 'on')
            obj.pUpdateLineVisibility;
            obj.pUpdateClusterVisibility;
        end
        
        end  % pPostSetVisible
        
        
        function onSelectUnmatchedData(obj, ~,~)
        %onSelectUnmatchedData action callback for select unmatched data
        
        % Get the current clusters
        clusters = get(obj.MessageService.TestplanSweepsetFilter, 'clusters');
        % Find the data only clusters
        dataOnlyIndices = find(strcmp({clusters.status}, 'unmatcheddata'));
        if ~isempty(dataOnlyIndices)
            % Update the current cluster info with the data only cluster
            obj.pSendCurrentClusterChanged(dataOnlyIndices(end));
        end
        end  % i_selectUnmatchedData
        
        
        %------------------------------------------------------------------------
        function onSelectUnmatchedDesign(obj, ~,~)
        %onSelectUnmatchedDesign action callback for select unmatched design
        
        % Get the current clusters
        clusters = get(obj.MessageService.TestplanSweepsetFilter, 'clusters');
        % Find the data only clusters
        designOnlyIndices = find(strcmp({clusters.status}, 'unmatcheddesign'));
        if ~isempty(designOnlyIndices)
            % Update the current cluster info with the data only cluster
            obj.pSendCurrentClusterChanged(designOnlyIndices(end));
        end
        end  % i_selectUnmatchedDesign
        
        %------------------------------------------------------------------------
        function onShowLabels(obj,src, ~)
        %onShowLabels show labels on cluster plot
        
        obj.LabelsVisible = src.Selected;
        end  % onShowLabels
        
        % ------------------------------------------------------------------------------
        function onSetTolerance(obj,~,~)
        %onSetTolerance redefine matching tolerances
        
        % Indicate this task might take a little time
        busy(obj.MessageService,'Changing Tolerances...');
        
        % Get the data object to work on
        tssf = obj.MessageService.TestplanSweepsetFilter;
        % Call the tolerances GUI
        f = allchild(0);
        [OK, newTolerance] = toleranceEditDlg(tssf, f(1));
        % Did the user click OK
        if OK
            % Set the new tolerances
            tssf = setTolerance(tssf, newTolerance);
            % Flush the event queue
            obj.MessageService.flushEventQueue(tssf);
            % Remove the pointer
        end
        
        % Remove the message and the pointer
        idle(obj.MessageService)
        end  % onSetTolerance
        
    end
    
end  % classdef

function design = i_setDesign(obj, design)
% Check that the design is the correct length
if length(design) ~= length(get(obj.MatchedDesignLine, 'XData'))
    error(message('mbc:xregdatagui:tssfclusterview:InvalidProperty'));
end
end  % i_setDesign

%------------------------------------------------------------------------
function data = i_setData(obj, data)
% Check that the data is the correct length
if length(data) ~= length(get(obj.MatchedDataLine, 'XData'))
    error(message('mbc:xregdatagui:tssfclusterview:InvalidProperty1'));
end
end  % i_setData

%------------------------------------------------------------------------
function clusters = i_setCluster(obj, clusters)
% Check that the clusters is the correct length
if length(clusters) ~= length(obj.ClusterPatches)
    error(message('mbc:xregdatagui:tssfclusterview:InvalidProperty2'));
end
end  % i_setCluster

function i_updateLineDisplay(prop, evt, line)
% Take the logical array and create a double array that contains NaN where
% the property is one. The extra 1 is to ensure that the zdata has the
% correct length - we will only use 1:end-1
lPointsToKeep = get(evt.AffectedObject, prop.Name);
zdata = zeros(size(lPointsToKeep));
zdata(~lPointsToKeep) = NaN;
% Convert true to NaN and false to zero
set(line, 'ZData', zdata(1:end));
end  % i_updateLineDisplay

%------------------------------------------------------------------------
function i_updateLabelDisplay(prop, evt, handleName)
handles = get(evt.AffectedObject, handleName);
% Create a logical variable that indicates which handles should be displayed
lLabelsToDisplay = get(evt.AffectedObject, prop.Name);
set(handles(lLabelsToDisplay), 'Visible', 'on');
set(handles(~lLabelsToDisplay), 'Visible', 'off');
end  % i_updateLabelDisplay

%------------------------------------------------------------------------
function i_updateLabelVisibility(~, evt)
evt.AffectedObject.pUpdateLabelVisibility;
end  % i_updateLabelVisibility

%------------------------------------------------------------------------
function i_updateClusterDisplay(~, evt)
% Take the logical array and make the relevant patches visible or invisible
evt.AffectedObject.pUpdateClusterVisibility;
end  % i_updateClusterDisplay

%------------------------------------------------------------------------
function i_axesButtonDown(ax, ~, obj)
% First dispatch to the container
notify(obj, 'ButtonDown');
% Then call mv_zoom
mv_zoom(ax);
end  % i_axesButtonDown

%------------------------------------------------------------------------
function i_legendAxesButtonDown(~, ~, obj)
% Dispatch to the container
notify(obj, 'ButtonDown');
end  % i_legendAxesButtonDown

%------------------------------------------------------------------------
function i_checkClicked(~, ~, obj)
obj.pUpdateLineVisibility;
end  % i_checkClicked

%------------------------------------------------------------------------
function i_clusterCheckClicked(~, ~, obj)
% Get the clusters
clusters = get(obj.MessageService.TestplanSweepsetFilter, 'clusters');
% Changed which clusters need to be displayed
obj.ClusterVisible = ismember({clusters.status}, obj.ClusterTypesToDisplay);
end  % i_clusterCheckClicked

%------------------------------------------------------------------------
function i_graphFactorChangeCallback(~, ~, obj)
% Update the line and patch data
obj.pUpdateLineAndPatchData;
end  % i_graphFactorChangeCallback

%------------------------------------------------------------------------
function i_pointClicked(~, ~, obj)
% Get the current point on the axes
cp = get(obj.Axes, 'CurrentPoint');
cp = cp(1,1:2);

% X & Y values for data and design
data = get(obj.MatchedDataLine, {'XData' 'YData'});
design = get(obj.MatchedDesignLine, {'XData' 'YData'});
% Get the axes limits
axesLims = get(obj.Axes, {'XLim' 'YLim'});
% Calculate a distance metric that takes account of the current axes limits
dataDist = abs((data{1} - cp(1))/diff(axesLims{1})) + abs((data{2} - cp(2))/diff(axesLims{2}));
designDist = abs((design{1} - cp(1))/diff(axesLims{1})) + abs((design{2} - cp(2))/diff(axesLims{2}));
% Which data points are visible
dataVisible = ...
    (obj.MatchedData & strcmp(get(obj.MatchedDataLine, 'Visible'), 'on')) | ...
    (obj.ExcludedData & strcmp(get(obj.ExcludedDataLine, 'Visible'), 'on')) | ...
    (obj.UnmatchedData & strcmp(get(obj.UnmatchedDataLine, 'Visible'), 'on')) | ...
    (obj.DataDesign & strcmp(get(obj.DataDesignLine, 'Visible'), 'on'));
% Which design points are visible
designVisible = ...
    (obj.MatchedDesign & strcmp(get(obj.MatchedDesignLine, 'Visible'), 'on')) | ...
    (obj.UnmatchedDesign & strcmp(get(obj.UnmatchedDesignLine, 'Visible'), 'on'));

% Find those that are within 1% of the axes limits and visible
nearestData = find(dataDist' < 0.02 & dataVisible);
nearestDesign = find(designDist' < 0.02 & designVisible);
% Switch highlight the clicked points
obj.HighlightData(nearestData) = ~obj.HighlightData(nearestData);
obj.HighlightDesign(nearestDesign) = ~obj.HighlightDesign(nearestDesign);
% Are any of the nearest highlighted?
if any(obj.HighlightData(nearestData)) || any(obj.HighlightDesign(nearestDesign))
    % Display info about all highlighted points
    dataIndex = find(obj.HighlightData);
    designIndex = find(obj.HighlightDesign);
    % Get the data object
    tssf = obj.MessageService.TestplanSweepsetFilter;
    % Get the names of the global factors but put 2 blank lines above
    names = char([{' '; ' '}; globalsignalnames(tssf)]);
    % Need to get all coods into strings and concatenate in blocks
    % saves playing around with tabbing the text to get alignment
    design = get(tssf, 'actualdesign');
    % Ensure suitable output
    outStr = '';
    % Iterate through all points "hit"
    for i = 1:length(dataIndex)
        dataStr = char('TEST', sprintf(' %.4g', obj.GlobalDataCache(dataIndex(i), end)));
        % Iterate through all coords
        for j = 1:size(obj.GlobalDataCache, 2)-1
            thisStr = sprintf(' %.4g',obj.GlobalDataCache(dataIndex(i), j));
            dataStr = char(dataStr,thisStr);
        end
        outStr = [outStr dataStr]; %#ok<AGROW>
    end
    % Iterate through all points "hit"
    for i = 1:length(designIndex)
        dataStr = char('INDEX', sprintf(' %.4g', designIndex(i)));
        % Iterate through all coords
        for j = 1:nfactors(design)
            thisStr = sprintf(' %.4g',design(designIndex(i), j));
            dataStr = char(dataStr, thisStr);
        end
        outStr = [outStr dataStr]; %#ok<AGROW>
    end
    % Display the data patch
    xregDisplayDataPatch(obj.Axes, [names outStr], cp);
end
end  % i_pointClicked

function l = i_indexToLogical(i, outputSize)
% Create the correct sized output
l = false(outputSize, 1);
% And turn the correct bits on
l(i) =  true;
end  % i_indexToLogical


function i_patchClicked(~, ~, obj, thisClusterInd)
% What sort of click?
fig = ancestor(obj.Parent,'figure');
if strcmp(get(fig, 'SelectionType'), 'normal')
    % Update the current cluster info
    obj.pSendCurrentClusterChanged(thisClusterInd);
else
    % Pass to the patch parent
    xregcallback(get(obj.Axes, 'ButtonDownFcn'), obj.Axes, []);
end
end  % i_patchClicked