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

    classdef AbstractBdrySlice < xregbdrygui.AbstractBdryView
    %xregbdrygui.AbstractBdrySlice class
    %   xregbdrygui.AbstractBdrySlice extends xregbdrygui.AbstractBdryView.
    %
    %    xregbdrygui.AbstractBdrySlice 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'
    %       Options - Property is of type 'handle vector'
    %       Actions - Property is of type 'handle vector'
    %       UIContextMenu - Property is of type 'MATLAB array'
    %       InputFactors - Property is of type 'MATLAB array' (read only)
    %       NAxes - Property is of type 'int' (read only)
    %       Axes - Property is of type 'MATLAB array' (read only)
    %       hAxes - Property is of type 'MATLAB array' (read only)
    %       hPatch - Property is of type 'MATLAB array' (read only)
    %       hBlackDots - Property is of type 'MATLAB array' (read only)
    %       hRedRings - Property is of type 'MATLAB array' (read only)
    %       hValidationInside - Property is of type 'MATLAB array' (read only)
    %       hValidationOutside - Property is of type 'MATLAB array' (read only)
    %       hNoDisplayText - Property is of type 'MATLAB array' (read only)
    %       hSelectButton - Property is of type 'MATLAB array' (read only)
    %       hSliceTable - Property is of type 'handle' (read only)
    %
    %    xregbdrygui.AbstractBdrySlice methods:
    %       createDefaultWindowContainer - Create an appropriate container for the view
    %       getEvaluationPoints - Get evaluation points from slice control table
    %       getSliceRange - Get slice range from slice control table
    %       gettitle - Return a suitable title for a window
    %       setAxes - Set the indices of the axes in a boundary slice view
    %       setInputFactors - Sets the input factors in the view
    %       setSlicePoint - Set the point where the current slice is at.
    
    % Copyright 2005-2015 The MathWorks, Inc. and Ford Global Technologies, Inc.

    properties (Access=protected, AbortSet)
        %HAXISLABELS Property is of type 'MATLAB array'
        hAxisLabels = [];
        %HAXISPOPUPS Property is of type 'MATLAB array'
        hAxisPopups = [];
    end
    
    properties (SetAccess=protected, AbortSet)
        %HAXES Property is of type 'MATLAB array' (read only)
        hAxes = [];
        %HPATCH Property is of type 'MATLAB array' (read only)
        hPatch = [];
        %HBLACKDOTS Property is of type 'MATLAB array' (read only)
        hBlackDots = [];
        %HREDRINGS Property is of type 'MATLAB array' (read only)
        hRedRings = [];
        %HVALIDATIONINSIDE Property is of type 'MATLAB array' (read only)
        hValidationInside = [];
        %HVALIDATIONOUTSIDE Property is of type 'MATLAB array' (read only)
        hValidationOutside = [];
        %HNODISPLAYTEXT Property is of type 'MATLAB array' (read only)
        hNoDisplayText = [];
        %HSELECTBUTTON Property is of type 'MATLAB array' (read only)
        hSelectButton = [];
        %HSLICETABLE Property is of type 'handle' (read only)
        hSliceTable = [];
    end
    
    properties (SetAccess=protected, AbortSet, SetObservable)
        %INPUTFACTORS Property is of type 'MATLAB array' (read only)
        InputFactors = [];
        %NAXES Property is of type 'int' (read only)
        NAxes
        %AXES Property is of type 'MATLAB array' (read only)
        Axes = [];
    end
    
    
    methods  % constructor block
        function obj = AbstractBdrySlice( varargin )
        % XREGBDRYGUI.ABSTRACTBDRYSLICE class constructor
        %
        % Also does the GUI layout
        
        % Call the inherited constructor
        obj@xregbdrygui.AbstractBdryView(varargin{ : }); % converted super class constructor call
        
        SC = xregGui.SystemColorsDbl;
        
        % Set the number of axes (usually via the sub-class)
        % -- this will fixed from now for this object
        obj.NAxes = pNAxes(obj);
        
        % Generate GUI primitives and link together into a layout
        % Create the layout for the axes
        lytAxesPanel = mbcgui.widget.AxesContainer(...
            'Parent',obj.Parent, ...
            'Border', [60 45 10 10]);
        obj.hAxes = lytAxesPanel.AxesHandle;
        set(obj.hAxes,...
            'Box','on',...
            'XGrid','on',...
            'YGrid','on',...
            'ZGrid','on',...
            'Units','pixels',...
            'Layer','top', ...
            'HitTest', 'off');
        xlabel( obj.hAxes, '', 'Interpreter', 'none' );
        ylabel( obj.hAxes, '', 'Interpreter', 'none' );
        zlabel( obj.hAxes, '', 'Interpreter', 'none' );
        
        obj.pSetupAxes( obj.hAxes );
        
        obj.hPatch = patch( ...
            'Parent',obj.hAxes, ...
            'Visible',obj.Visible,...
            'XData', NaN, ...
            'YData', NaN, ...
            'ZData', NaN, ...
            'EdgeColor','none',...
            'FaceColor','b',...
            'HitTest','off' );
        
        obj.hBlackDots = line( ...
            'Parent',obj.hAxes, ...
            'Visible',obj.Visible,...
            'LineStyle', 'none', ...
            'Marker', '.', ...
            'MarkerEdgeColor', 'k', ...
            'MarkerFaceColor', 'k', ...
            'MarkerSize', 15, ...
            'XData', [], ...
            'YData', [], ...
            'ZData', [], ...
            'ButtonDownFcn', @(s, e) obj.pViewDot );
        
        obj.hRedRings = line(...
            'Parent',obj.hAxes, ...
            'Visible',obj.Visible,...
            'LineStyle', 'none', ...
            'LineWidth', 2, ...
            'Marker', 'o', ...
            'MarkerEdgeColor', 'r', ...
            'MarkerFaceColor', 'none', ...
            'MarkerSize', 10, ...
            'XData', [], ...
            'YData', [], ...
            'ZData', [], ...
            'HitTest', 'off' );
        
        obj.hValidationInside = line( ...
            'Parent',obj.hAxes, ...
            'LineStyle', 'none', ...
            'Marker', '^', ...
            'MarkerFaceColor', [50, 200, 50]/255, ...
            'MarkerSize', 6, ...
            'XData', [], ...
            'YData', [], ...
            'ZData', [], ...
            'HitTest', 'off', ...
            'Visible', 'off', ...
            'Tag', 'BdrySliceValidationInside');
        
        obj.hValidationOutside = line( ...
            'Parent',obj.hAxes, ...
            'LineStyle', 'none', ...
            'Marker', '^', ...
            'MarkerFaceColor', [200, 50, 50]/255, ...
            'MarkerSize', 6, ...
            'XData', [], ...
            'YData', [], ...
            'ZData', [], ...
            'HitTest', 'off', ...
            'Visible', 'off', ...
            'Tag', 'BdrySliceValidationOutside');
        
        obj.hNoDisplayText = text( ...
            'Parent', obj.hAxes,...
            'String', sprintf( 'Too few factors for %s.', obj.gettitle ), ...
            'Visible', obj.Visible,...
            'Position', [0.5, 0.5, 0.5],...
            'HorizontalAlignment', 'center',...
            'VerticalAlignment', 'middle',...
            'Clipping', 'on',...
            'HitTest', 'off' );
        
        SlicePanel = mbcgui.container.layoutpanel(...
            'Parent', obj.Parent, ...
            'Visible', obj.Visible);
        % Slice control table
        % -- This table is what the user used to control the evlaution points for
        %    the slice and the associated tolerance on the data points to be shown
        %    in the slice.
        obj.hSliceTable = mbcwidgets.VariableEditorTable( ...
            'Parent', SlicePanel, ...
            'ValueColumnCount', 2, ...
            'ValueColumnHeader', {'Value', 'Tolerance'}, ...
            'ValueChangedCallback', {@i_SliceTableEdited, obj}, ...
            'ValueConstraint', 'plot');
        
        obj.hSelectButton = uicontrol(...
            'Parent', SlicePanel, ...
            'Style','pushbutton',...
            'String','Select Data Point...',...
            'Interruptible','off',...
            'Callback', {@i_SelectTest, obj} );
        
        % Create the pop-up menus and label controls for the axes chooser
        axisNames = {'X', 'Y', 'Z'};
        hLabels = cell(1, obj.NAxes );
        hPopups = cell(1, obj.NAxes );
        
        SelectorPanel = mbcgui.container.layoutpanel(...
            'Parent', SlicePanel, ...
            'BorderType', 'etchedin');
        for i = 1:obj.NAxes,
            hPopups{i} = uicontrol(...
                'Parent', SelectorPanel, ...
                'Style', 'popupmenu', ...
                'String', ' ', ...
                'Value', 1, ...
                'BackgroundColor', SC.WINDOW_BG, ...
                'Callback', {@i_EditAxis, i, obj} );
            hLabels{i} = xregGui.labelcontrol( ...
                'parent',  SelectorPanel, ...
                'String',  sprintf('%s-axis:', axisNames{i}), ...
                'LabelSize', 40, ...
                'LabelSizeMode', 'absolute', ...
                'ControlSize', 1, ...
                'ControlSizeMode', 'relative', ...
                'Control', hPopups{i} );
        end
        hPopups = [hPopups{:}];
        hLabels = [hLabels{:}];
        
        obj.hAxisLabels = hLabels;
        obj.hAxisPopups = hPopups;
        
        % Layouts
        
        % Create the layout for the axes selectors
        lytSelectors = xreggridbaglayout( SelectorPanel, ...
            'packgroup', 'XREG_PERM_ON', ...
            'packstatus', 'off', ...
            'dimension', [obj.NAxes, 1], ...
            'rowsizes', repmat( 21, 1, obj.NAxes ), ...
            'gapy', 5, ...
            'border', [2, 2, 2, 2], ...
            'elements', num2cell( obj.hAxisLabels ));
        set(SelectorPanel, 'LayoutComponent', {lytSelectors});
        
        % Create the layout that combines the slice table and axes selectors. This
        % is the slice control
        lytSliceControl = xreggridbaglayout( SlicePanel, ...
            'packgroup', 'XREG_PERM_ON', ...
            'Dimension', [3, 2], ...
            'ColSizes', [-1, 105], ...
            'RowSizes', [25, -1, obj.NAxes*26+3], ...
            'gapy', 2, ...
            'border', [0 0 0 2], ...
            'MergeBlock', {[2 2], [1 2]}, ...
            'MergeBlock', {[3 3], [1 2]}, ...
            'elements', {[], obj.hSliceTable, SelectorPanel, obj.hSelectButton});
        set(SlicePanel, 'LayoutComponent', {lytSliceControl});
        
        % Create the main layout for the view
        obj.ContentHandle = xregsplitlayout( obj.Parent,...
            'packgroup', 'XREG_PERM_ON',...
            'orientation', 'lr', ...
            'dividerstyle', 'flat', ...
            'dividerwidth', 4, ...
            'split', [0.2, 0.8], ...
            'left',  SlicePanel,...
            'right', lytAxesPanel,...
            'position', obj.Position, ...
            'packstatus','on' );
        
        % We should be already to rock & roll now. So we the BMS to get everything
        % fully connected.
        obj.pPostSetMessageService;
        
        % Attach listeners to properties and slice control events
        obj.addPropertyListeners( 'Axes', @(s, e) obj.pPostSetAxes );
        
        % Setting the input factors sets variables & values in the slice table
        % -- This will cause a full draw
        obj.setInputFactors;
        end  % AbstractBdrySlice
        
    end  % constructor block
    
    methods  % public methods
        
        %----------------------------------------
        function [vectors, grid] = getEvaluationPoints(obj)
        %GETEVALUATIONPOINTS Get evaluation points from slice control table
        %  [VECTORS, GRID] = GETEVALUATIONPOINTS(OBJ)
        %
        %  VECTORS is a cell array with the x-, y- and (possibly) z-axis evaluation
        %  points are single vectors. These should be suitable for being passed to
        %  CONTOUR or similar as the X, Y arguments.
        %
        %  GRID is an nPoints by nFactors matrix that is the MESHGRID form of VECTORS
        %  but combined with the input factors that determine the slice space. This
        %  matrix should be suitable for passing directly to constraint evaluation
        %  functions.
        %
        %  See also XREGBDRYGUI.ABSTRACTBDRYSLICE.
        
        
        % CONTOURS and ISOSURFACE don't play with NDGRID for some unknown reason. Hence
        % we have to use MESHGRID!
        
        % Get raw values from tables
        values = obj.hSliceTable.getNumericValues;
        values = values(:,1);
        
        % Get the values from inside the slice space
        vectors = values(obj.Axes,1);
        vectors = cellfun( @sort, vectors, 'UniformOutput', false );
        
        % Form the grid
        if nargout > 1,
            nf = length( values );
            
            % Perform the NDGRID
            ndVectors = cell( size( vectors ) );
            [ndVectors{:}] = meshgrid( vectors{:} );
            
            % Fill out the GRID with all the required values
            % -- For columns that correspond to factors slected as axes, we use the
            %    NDGRIDed values.
            % -- For the other columns, put in the appropriate constants
            grid = zeros( numel( ndVectors{1} ), nf );
            for i = 1:nf,
                loc = (i == obj.Axes);
                if any( loc ),
                    grid(:,i) = ndVectors{loc}(:);
                else
                    grid(:,i) = values{i};
                end
            end
        end
        
        end  % getEvaluationPoints
        
        %----------------------------------------
        function range = getSliceRange(obj)
        %GETSLICERANGE Get slice range from slice control table
        %  [RANGE, INDICES] = GETSLICERANGE(OBJ)
        %
        %  RANGE is a two by nFactors matrix. The first row is the minimum value
        %  of the slice range and the second row is the maximum. For factors where
        %  there is no constraint on the slice, i.e., those factors that are the
        %  axes of the slice, the min and max value will be -Inf and +Inf.
        %
        %  See also XREGBDRYGUI.ABSTRACTBDRYSLICE.
        
        
        % Get raw values from tables
        values = obj.hSliceTable.getNumericValues;
        types  = obj.hSliceTable.VariableTypes;
        
        nf = size( values, 1 );
        
        boundedFactors = (types(:,1) == 1);
        
        range = zeros( 2, nf );
        for i = 1:nf,
            if boundedFactors( i ),
                % Min
                range(1,i) = values{i,1} - values{i,2};
                % Max
                range(2,i) = values{i,1} + values{i,2};
            else
                % This factor is a axis of the slice
                range(:,i) = [-Inf; Inf];
            end
            
        end
        
        end  % getSliceRange
        
        %----------------------------------------
        function setAxes(obj, newAxes, index)
        %SETAXES Set the indices of the axes in a boundary slice view
        %  SETAXES(OBJ, AXES)
        %  SETAXES(OBJ, AXES, INDEX) allows one axis to be changed by specifying
        %  the index of the axis to change in this case the newly created AXES
        %  vector is checked to ensure that there are no two entries the same and
        %  it there are, other entries are modified to fix this. No such checking
        %  happens in the first form.
        %
        %  See also XREGBDRYGUI.ABSTRACTBDRYSLICE.
        
        
        narginchk(2,3);
        
        if nargin == 3,
            % Need to check the new axes value to ensure that the same axis
            % isn't slected twice. In the case that one axis is selected twice,
            % we modify a different value in the "axes" vector to the one being
            % set by the pop-up.
            axes = obj.Axes;
            axes(index) = newAxes;
            tf = axes == axes(index);
            tf(index) = false;
            if any( tf ),
                % Two pop-ups have the same value
                otherValues = setdiff( 1:obj.NAxes, axes );
                axes(tf) = otherValues(1:nnz( tf ));
                % -- If the previous line errors it could be because there are
                %    insufficient factors for the view. We shouldn't get into
                %    this code in that situation.
            end
            
        elseif length( newAxes ) ~= obj.NAxes || ~isnumeric( newAxes ),
            error(message('mbc:xregbdrygui:AbstractBdrySlice:InvalidArgument', obj.NAxes));
        else
            axes = newAxes;
        end
        
        % Set the axes in the object
        % -- also updates the pop-ups
        obj.Axes = axes;
        
        % Set the slice table to have the correct columns linked and the other
        % columns
        obj.pUpdateTableValues;
        
        % Draw the new constraint
        obj.pDrawConstraint;
        
        end  % setAxes
        
        %----------------------------------------
        function setInputFactors(obj, cif)
        %SETINPUTFACTORS Sets the input factors in the view
        %  SETINPUTFACTORS(OBJ, CIF) sets the input factors that are stored in the
        %  view based on the input factors from the current node.
        %
        %  SETINPUTFACTORS(OBJ) gets the input factors from the boundary message
        %  service.
        %
        %  If the old input factors in the view differ from those from the node
        %  then slice control table is reset with new variables and values.
        %
        %  See also XREGBDRYGUI.ABSTRACTBDRYSLICE.
        
        
        % Check input arguments
        if nargin < 2,
            if obj.hasData,
                % Get the input factors from the BMS
                cif = obj.MessageService.getInputFactors;
            else
                % No input factors are available so create an empty array of them
                % to process in this function.  We want to go ahead with a refresh
                % because having no data means we ought to be displaying nothing,
                % not what was previously being drawn.
                cif = coninputfactor({});
            end
        end
        
        % Check the new input factors and set properties as appropriate
        nCif = length( cif );
        if nCif < obj.NAxes,
            % Insufficient factors
            % -- Need to disable the controls and hide the plot
            set( obj.hAxisLabels, 'Enable', 'off' );
            set( obj.hSliceTable, 'Enable', 'off' );
            obj.pTurnOffPlot;
        else
            % Ensure that the controls are enabled and that the plot is visible
            set( obj.hAxisLabels, 'Enable', 'on' );
            set( obj.hSliceTable, 'Enable', 'on' );
            obj.pTurnOnPlot;
        end
        
        if isequal( cif, obj.InputFactors ),
            % Nothing to do
        else
            % Need to update the stored input factors and the slice table
            obj.InputFactors = cif;
            
            % Update slice table
            %
            % Names
            names = cif.Name;
            %
            % Types
            % -- Default is a single value (code = 1) in both the first and second
            %    column
            types = ones( nCif, 2 );
            %
            % Values
            % -- By default these will be the mid points of the ranges of the input
            %    factors
            % -- For the axes, the default will be 30 evenly spaced points
            values = cell( nCif, 2 );
            [lb, ub] = getRange( cif );
            for i = 1:nCif,
                values(i,:) = { 0.5*(lb(i)+ub(i)), 0.1*(ub(i)-lb(i)) };
            end
            
            obj.hSliceTable.setVariables( names, types, values );
            
            % Set the axes
            if nCif < obj.NAxes
                set( obj.hAxisPopups, 'String', ' ' );
                obj.setAxes( ones(1, obj.NAxes) );
            else
                set( obj.hAxisPopups, 'String', names );
                if length(unique(obj.Axes))<obj.NAxes || any(obj.Axes>nCif)
                    ax = 1:obj.NAxes;
                else
                    ax = obj.Axes(1:obj.NAxes);
                end
                obj.setAxes( ax );
            end
        end
        
        end  % setInputFactors
        
        %----------------------------------------
        function setSlicePoint(obj, point)
        %SETSLICEPOINT Set the point where the current slice is at.
        %   SETSLICEPOINT(OBJ, POINT)
        
        
        % #define
        TYPE_SCALAR = 1;
        
        hTable = obj.hSliceTable;
        for i = 1:length( point ),
            % Only set those points that are of scalar type, i.e., those that
            % define where the slice is.
            if hTable.VariableTypes(i,1) == TYPE_SCALAR,
                hTable.VariableValues{i,1} = point(i);
            end
        end
        
        % Redraw
        obj.pDrawConstraint;
        
        end  % setSlicePoint
        
    end  % public methods
    
    methods (Access=protected)
        
        %----------------------------------------
        function pDrawConstraint(obj) %#ok<MANU>
        %pDrawConstraint abstract
        end
        
        %----------------------------------------
        function pDrawPoints(obj)
        %PDRAWPOINTS Draw the data/validation points that are in the current slice
        %  PDRAWPOINTS(OBJ)
        %  Assign the X-, Y- and Z-data for points and rings in the current slice.
        %
        %  See also XREGBDRYGUI.BDRYSLICE2.
        
        if ~obj.hasData,
            return
        end
        
        XYZDATA = {'XData', 'YData', 'ZData'};
        BMS = obj.MessageService;
        
        % Put up a message in the status bar to explain the delay and change the
        % pointer to an hour-glass to make it clear to the user that stuff is
        % happening
        %>> msgID = i_addmessage( psbar, 'Drawing 2D graph...' );
        %>> ptrID = i_setpointer( psbar, 'watch');
        % FIX ME:  Does this need to be done via the editor?
        
        % Get the current node and the data associated with that node.
        data = BMS.getDataPoints;
        [valData, valIsInside] = BMS.getValidationPoints;
        
        if size( data, 2 ) < obj.NAxes
            return
        end
        nData = size( data, 1 );
        nValData = size(valData, 1);
        
        % Get the slice information
        % -- Slice subspace factors
        % -- Slice point and thickness (tolerance)
        sliceRange = obj.getSliceRange;
        
        lb = repmat( sliceRange(1,:), nData, 1 );
        ub = repmat( sliceRange(2,:), nData, 1 );
        lbVal = repmat( sliceRange(1,:), nValData, 1 );
        ubVal = repmat( sliceRange(2,:), nValData, 1 );
        
        % Work out which points are in the slice
        ind = find( all( lb <= data & data <= ub, 2 ) );
        valInd = find( all( lbVal <= valData & valData <= ubVal, 2 ) );
        
        bind = intersect( ind, BMS.getBoundaryPoints );
        
        if isempty(valData)
            indInside = zeros(0, 1);
            indOutside = zeros(0, 1);
        else
            indInside = valInd(valIsInside(valInd));
            indOutside = valInd(~valIsInside(valInd));
        end
        
        % Assign the data to the line objects
        for i = 1:obj.NAxes,
            set( obj.hBlackDots, XYZDATA{i}, data(ind,obj.Axes(i)) );
            set( obj.hRedRings,  XYZDATA{i}, data(bind,obj.Axes(i)) );
            set( obj.hValidationInside,  XYZDATA{i}, valData(indInside,obj.Axes(i)) );
            set( obj.hValidationOutside,  XYZDATA{i}, valData(indOutside,obj.Axes(i)) );
        end
        for i =(obj.NAxes+1):3,
            set( obj.hBlackDots, XYZDATA{i}, zeros( size( ind  ) ) );
            set( obj.hRedRings,  XYZDATA{i}, zeros( size( bind ) ) );
            set( obj.hValidationInside,  XYZDATA{i}, zeros( size( indInside  ) ));
            set( obj.hValidationOutside, XYZDATA{i}, zeros( size( indOutside  ) ));
        end
        
        % The callback on the dots needs to be able to map between the points that
        % are displayed and the actual data points. Hence for displayed point we
        % store the indices of the that point in the original data set
        set( obj.hBlackDots, 'UserData', ind  );
        set( obj.hRedRings,  'UserData', bind );
        
        % set correct visibility of validation data
        set(obj.hValidationInside, 'Visible', BMS.ValidationPointHighlight);
        set(obj.hValidationOutside, 'Visible', BMS.ValidationPointHighlight);
        
        % Return the mouse pointer back to what it was before and remove the
        % message from the status bar.
        %>> i_deletemessage( psbar, msgID);
        %>> i_deletepointer( psbar, ptrID );
        % FIX ME:  Does this need to be done via the editor?
        
        end  % pDrawPoints
        
        %----------------------------------------
        function v = pGetAxisPopupValue(obj, index)
        %PGETAXISPOPUPVALUE Get the value of one of the axis popups
        %  PGETAXISPOPUPVALUE(OBJ, INDEX)
        
        
        v = get(obj.hAxisPopups(index), 'Value');
        
        end  % pGetAxisPopupValue
        
        %----------------------------------------
        function n = pNAxes(obj)
        %PNAXES Number of axes for a slice view
        %  N = PNAXES(OBJ)
        %  This should be overloaded by sub-classes to return the correct number of
        %  axes.
        %
        %  See also XREGBDRYGUI.ABSTRACTBDRYSLICE.
        
        
        n = 2;
        
        end  % pNAxes
        
        %----------------------------------------
        function pPostSetAxes(obj)
        %PPOSTSETAXES If the axes get set then the pop-ups need updating
        %  PPOSTSETAXES(OBJ)
        %  This method does not foce a redraw of the constraint.
        %
        %  See also XREGBDRYGUI.ABSTRACTBDRYSLICE.
        
        
        % Set the correct values in the pop-up menus
        for i = 1:obj.NAxes,
            set(obj.hAxisPopups(i), 'Value', obj.Axes(i));
        end
        
        end  % pPostSetAxes
        
        %----------------------------------------
        function pPostSetMessageService(obj)
        %PPOSTSETMESSAGESERVICE Respond to change of MessageService
        %  PPOSTSETMESSAGESERVICE(OBJ) is called when the MessageService property
        %  is changed.
        %
        %  See also XREGBDRYGUI.ABSTRACTBDRYSLICE.
        
        
        % Clear the listeners
        obj.clearMessageServiceListeners;
        
        % Add new listeners
        obj.addMessageServiceListener( 'NodeChange',       @(src, evt) obj.setInputFactors );
        obj.addMessageServiceListener( 'ViewModeChange',   @(src, evt) obj.setInputFactors );
        obj.addMessageServiceListener( 'ViewTestChange',   @(src, evt) obj.setInputFactors );
        
        obj.addMessageServiceListener( 'ConstraintChange',        @(src, evt) obj.pDrawConstraint );
        obj.addMessageServiceListener( 'BoundaryPointsHighlight', @(src, evt) obj.pShowBoundaryPoints );
        obj.addMessageServiceListener( 'ValidationPointsToggled', @(src, evt) obj.pShowValidationPoints );
        
        end  % pPostSetMessageService
        
        %----------------------------------------
        function pSetupAxes(obj, hAxes)
        %PSETUPAXES Setup the axes for the boundary slice view
        %  PSETUPAXES(OBJ, HAXES)
        %
        %  This should be overloaded by sub-classes to set up the axes as required.
        %
        %  See also XREGBDRYGUI.ABSTRACTBDRYSLICE.
        
        % Nothing to do for abstract class
        
        end  % pSetupAxes
        
        %----------------------------------------
        function pShowBoundaryPoints(obj)
        %PSHOWBOUNDARYPOINTS Turn boundary point highlighting on or off
        %  PSHOWBOUNDARYPOINTS(OBJ)
        %
        %  See also XREGBDRYGUI.ABSTRACTBDRYSLICE.
        
        BMS = obj.MessageService;
        set( obj.hRedRings, 'Visible', BMS.BoundaryPointHighlight );
        
        end  % pShowBoundaryPoints
        
        %----------------------------------------
        function pShowValidationPoints(obj)
        %PSHOWVALIDATIONPOINTS Turn validation point highlighting on or off
        %  PSHOWVALIDATIONPOINTS(OBJ)
        %
        %  See also XREGBDRYGUI.ABSTRACTBDRYSLICE.
        
        BMS = obj.MessageService;
        set(obj.hValidationInside, 'Visible', BMS.ValidationPointHighlight);
        set(obj.hValidationOutside, 'Visible', BMS.ValidationPointHighlight);
        end  % pShowValidationPoints
        
        %----------------------------------------
        function pTurnOffPlot(obj)
        %PTURNOFFPLOT Display "No Display" text
        %  PTURNOFFPLOT(OBJ)
        %
        %  See also XREGBDRYGUI.ABSTRACTBDRYSLICE.
        
        
        set([obj.hPatch, obj.hBlackDots, obj.hRedRings], 'Visible', 'off');
        
        set(obj.hAxes, 'XGrid', 'off', 'YGrid', 'off', 'ZGrid', 'off');
        set(obj.hNoDisplayText, 'Visible', 'on');
        
        % Set the limits of the axes so that the text appears in the center
        set( obj.hAxes, 'XLim', [0, 1], 'YLim', [0, 1], 'ZLim', [0, 1] );
        
        end  % pTurnOffPlot
        
        %----------------------------------------
        function pTurnOnPlot(obj)
        %PTURNONPLOT Un-display "No Display" text
        %  PTURNONPLOT(OBJ)
        %  See also XREGBDRYGUI.ABSTRACTBDRYSLICE.
        
        
        BMS = obj.MessageService;
        
        set([obj.hPatch, obj.hBlackDots], 'Visible', 'on');
        set(obj.hRedRings, 'Visible', BMS.BoundaryPointHighlight);
        
        set(obj.hAxes, 'XGrid', 'on', 'YGrid', 'on', 'ZGrid', 'on');
        set(obj.hNoDisplayText, 'Visible', 'off');
        
        end  % pTurnOnPlot
        
        %----------------------------------------
        function pUpdateTableValues(obj)
        %PUPDATETABLEVALUES Update the values in the slice table
        %  PUPDATETABLEVALUES(OBJ)
        %  This is usually called in response to change in the pop-ups that control
        %  which subspace the slice is in.
        %
        %  Will cause a redraw of the constraint
        %
        %  See also XREGBDRYGUI.ABSTRACTBDRYSLICE.
        
        
        % #define
        TYPE_SCALAR    = 1;
        TYPE_LINEAR    = 2;
        TYPE_FREEFORM  = 3;
        TYPE_LINKED    = 4;
        
        AXES_NAMES = {'X-Axis', 'Y-Axis', 'Z-Axis'};
        
        % Get some input factor data from the object
        cif = obj.InputFactors;
        nCif = length( cif );
        
        % Get the current variable type and values
        types  = obj.hSliceTable.VariableTypes;
        values = obj.hSliceTable.VariableValues;
        
        % Check each one in turn
        % -- If the type matches what it should be for the obj.Axes then we leave
        %    it as is. If it is the wrong type then we change the type and set the
        %    value to some default.
        
        switch length(unique(obj.Axes));
            case {1,2}
                Resolution = 101;
            otherwise
                Resolution = 21;
        end
        
        [lb, ub] = getRange( cif );
        for i = 1:nCif,
            [tf, loc] = ismember( i, obj.Axes );
            if tf,
                % This factor is an axis of the plot and we want it to be a vector
                % type
                if types(i,1) == TYPE_LINEAR || types(i,1) == TYPE_FREEFORM,
                    % This factor has the right type, so we can leave it alone
                else
                    % This factor needs it type set [TYPE_LINEAR, TYPE_LINKED] and
                    % appropriate values set
                    types(i,:) = [TYPE_LINEAR, TYPE_LINKED];
                    values{i,1} = [lb(i), ub(i), Resolution];
                    values{i,2} = AXES_NAMES{loc};
                end
            else
                % This factor is not an axis of the plot and so it must be a scalar
                % type and it must have a tolerance
                if types(i,1) == TYPE_SCALAR,
                    % This factor has the right type,
                else
                    % We need to set the type for this factor to scalar and the
                    % values to the midpoint and to 10% of the range
                    types(i,:) = [TYPE_SCALAR, TYPE_SCALAR];
                    values(i,:) = { 0.5*(lb(i)+ub(i)), 0.1*(ub(i)-lb(i)) };
                end
            end
        end
        
        obj.hSliceTable.setVariables( cif.Name, types, values );
        
        % Redraw?
        obj.pDrawConstraint;
        
        end  % pUpdateTableValues
        
        %----------------------------------------
        function pViewDot(obj)
        %PVIEWDOT Callback for when user clicks on the data points in the view
        %   PVIEWDOT(OBJ)
        %   Two possibilities for this callback:-
        %   -- Single Click:- display information about the points, e.g., the
        %      values of the input factors for this point, how far it is from the
        %      constraint boundary.
        %   -- Double Click:- change the values of the slice control so that the
        %      slice is at the selected point
        %
        %   See also XREGBDRYGUI.ABSTRACTBDRYSLICE.
        
        
        if ~obj.hasData,
            return
        end
        
        hFigure = obj.Parent;
        
        BMS = obj.MessageService;
        data = BMS.getDataPoints;
        cif  = BMS.getInputFactors;
        con  = BMS.getConstraint;
        
        currentPoint = get(obj.hAxes, 'CurrentPoint');
        
        % Find the points that were clicked on by the user
        % ===========
        % Get the current data points
        blackDots = get( obj.hBlackDots, {'XData', 'YData', 'ZData'} );
        % Get the limits of the axes
        limits = get( obj.hAxes, {'XLim', 'YLim', 'ZLim'} );
        % Calculate a distance metric that takes account of the current axes limits
        distance = i_PointToLineDistance( currentPoint(1,:).', currentPoint(2,:).', ...
            vertcat( blackDots{:} ), [diff( limits{1} ); diff( limits{2} ); diff( limits{3} )] );
        % The points with the smallest distance are the ones clicked on
        %
        % In the user data we store the indices of the points displayed, i.e., the
        % map between the indices of the line data and the actual data
        indexMap = get( obj.hBlackDots, 'UserData' );
        
        % Switch the action based on what type click the user made
        switch lower( get(ancestor(hFigure,'figure'),'SelectionType') )
            case 'normal', % left click
                i_DataTooltip;
            case 'open', % double click
                i_MoveToPoint;
            case {'alt', 'extend'}, % right, middle click
                %ignore
            otherwise
                warning(message('mbc:xregbdrygui:AbstractBdrySlice:InvalidState'));
                return
        end
        
            function i_MoveToPoint
            [~, i] = min( distance );
            i = indexMap(i);
            obj.setSlicePoint( data(i,:) );
            end % of nested function i_MoveToPoint
        
            function i_DataTooltip
            % Find those that are within 3% of the axes limits and visible
            i = find( distance < 1.5e-2 );
            if isempty( i ),
                [~, i] = min( distance );
            end
            i = indexMap(i);
            
            % Generate the text for the patch
            if isempty( con ),
                space = repmat( blanks(1), length( cif ), 1 );
                txt = [space, char( cif.Symbol ), space, space, num2str( data(i,:).', 4 ), space ];
            else
                space = repmat( blanks(1), length( cif )+1, 1 );
                symbols = cif.Symbol;
                d = constraintDistance( con, data(i,:) );
                txt = [space, char( symbols{:}, 'Dist:' ), space, space, ...
                    num2str( [data(i,:), d].', 4 ), space ];
            end
            
            xregDisplayDataPatch( obj.hAxes, txt, [], false );
            end % of nested function i_DataTooltip
        
        end % of main function pViewDot
 
    end
    
end  % classdef

function i_SliceTableEdited(hTable, evt, obj)
% If the user edits the first column (value) then a complete
% re-draw is required. However, if they only edit a tolerance then
% we only need to redisplay the points.
if evt.data.Columns == 2,
    % The user is editing column one, the evaluation points
    obj.pDrawConstraint;
else
    % The user is editing column two, the tolerances
    obj.pDrawPoints;
end
end  % i_SliceTableEdited



function i_EditAxis(src, evt, index, obj )
obj.setAxes( obj.pGetAxisPopupValue( index ), index );
end  % i_EditAxis



function i_SelectTest( src, evt, obj )
point = obj.MessageService.guiSelectPoint;
if ~isempty( point ),
    obj.setSlicePoint( point );
end
end  % i_SelectTest

function d = i_PointToLineDistance( x1, x2, x0, w )
% Distance from the points in x0 to the line though <x1, x2>
% The last argument, w, is a scaling vector.

nPoints = size( x0, 2 );

delta = (x2 - x1);
T = (delta/sum( delta.^2 ))';

d = zeros( 1, nPoints );
for i = 1:nPoints,
    t = T * (x0(:,i) - x1);
    % A "nice" formula for the point-line distance is:
    %    d(i) = norm( cross( x2 - x1, x1 - x0(:,i) ) )/norm( x2 - x1 );
    % (note that this doesn't take into account the scaling vector)
    % However, it is numerically more efficient to use
    d(i) = norm( (x0(:,i) - (x1 + t*delta))./w );
end
end