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

    classdef SurfaceViewer < mbcgui.multiview.View
%mbcmodelview.SurfaceViewer model browser surface viewer

%  Copyright 2015-2016 The MathWorks, Inc. and Ford Global Technologies, Inc.

    properties (Constant)
        %ViewInfo view description
        ViewInfo = { @mbcmodelview.SurfaceViewer;
            '&Response Surface';
            xregrespath('surfviewer.bmp');
            'Response surface';
            1};
    end 
    
    
    properties
        %InputIndices input indices for axes
        InputIndices = 1:2
        %NumAxes number of axes (1 or 2)
        NumAxes = 2;
        %ZoomToBoundary show region outside boundary model (to model range)
        ZoomToBoundary = false;
        %ShowLight show light (only for surface)
        ShowLight = false;
        %PlotType type of plot  {'Line','Surface','Contour','Multi-line'}
        PlotType = 'Surface';
    end
    
    properties (Dependent,SetAccess=private)
       SlicePoints 
    end

    properties (SetAccess=private)
        %hPlotType handle to popup for plot type
        hPlotType 
        %hAxes handle to axes
        hAxes
        %hDataPoints handle to line for data points
        hDataPoints
        %hValidationPoints handle to line for validation data points
        hValidationPoints
        %hSliceTable handle to table for slice and tolerance points
        hSliceTable
        %hSelectButton handle to button for popup
        hSelectButton
        %hAxisLabels handle to label controls for axes popups
        hAxisLabels
        %hAxisPopups handle to axes popups
        hAxisPopups
        %InputRange range for inputs
        InputRange
        %InputSymbols input symbols
        InputSymbols
        %CrossSection cross section points [Values,Tolerance]
        CrossSection
    end
    
    methods
        
        function obj=SurfaceViewer(varargin)
        %SurfaceViewer constructor
        obj@mbcgui.multiview.View(varargin{:});
        create(obj)
        
        if strcmp(obj.Visible,'on') && hasData(obj.MessageService)
            reset(obj)
            update(obj)
        end
        % add message service listeners
        addMessageServiceListener(obj, 'NodeUpdated',@obj.onNodeUpdated)        
        addMessageServiceListener(obj, 'NodeReset',@obj.onNodeReset)        
        
        
        end
        
        function x=get.SlicePoints(obj)
        
        values = obj.hSliceTable.getNumericValues;
        types  = obj.hSliceTable.VariableTypes;
        
        x = cell(1,size(values,1));
        inp = getInputs(obj.MessageService.Model);
        for i=1:length(x)
           switch types(i,1)
               case 1 % scalar
                   x{i}= values{i,1};
               case 2 % linspace
                   x{i} = values{i,1};
                   if isscalar(x{i})
                       % can't have scalars
                       x{i}= linspace(inp(i).Range(1),inp(i).Range(2),51);
                   end
           end
        end
        
        if obj.ZoomToBoundary
            % zoom ranges inside boundary model
            x = zoomConstraints(obj,x);
        end
        
        end        
        
        
        function str = gettitle(obj) %#ok<MANU>
        %GETTITLE Return string to use as title for view
        %
        %  STR = GETTITLE(OBJ) returns a string that should be used as a title for
        %  the container the view sits in.
        
        str = 'Response Surface';
        
        end  % gettitle
    
        function update(obj)
        %update main update method
        
        ms = obj.MessageService;
        [LB,UB]=range(ms.Model);
        s = get(ms.Model,'symbols');
        if ~isequal(obj.InputRange,[LB,UB]) || ~isequal(obj.InputSymbols,s)
            obj.InputRange = [LB,UB];
            obj.InputSymbols = s;
            initialize(obj);
        end
        
        if obj.MessageService.Status~=0 && ~istransient(ms.Model)
            set(allchild(obj.hAxes),'Visible','on');
            drawResponseSurface(obj)
        else
            set(allchild(obj.hAxes),'Visible','off');
        end
        title(obj.hAxes,obj.MessageService.Title,'Interpreter','none')
        
        end
        
        function s = serializeView(obj) 
        %SERIALIZEVIEW Get serializable setup data for the view
        %
        %  OUT = SERIALIZEVIEW(OBJ) retuns a MATLAB array that contains the setup
        %  data that will allow this view to be recreated via the deserializeView
        %  function in the future.  Typically this function will return a cell
        %  array of property names and values, however any MATLAB array type is
        %  allowed.
        
        s.InputIndices = obj.InputIndices;
        s.ZoomToBoundary = obj.ZoomToBoundary;
        s.ShowLight = obj.ShowLight;
        s.PlotType = obj.PlotType;
        s.CrossSection = obj.CrossSection;
        end  % serializeView        
        
        function deserializeView(obj,s) 
        %DESERIALIZEVIEW Set saved state data
        %  DESERIALIZEVIEW(OBJ, DATA) sets a copy of the serialized state of this
        %  View.  DATA is a MATLAB array that will have been created by a
        %  previous call to SERIALIZEVIEW on this View object.
        if  obj.hasData && isstruct(s) && isfield(s,'CrossSection')
            
            obj.InputIndices = s.InputIndices;
            
            obj.NumAxes = length(obj.InputIndices);
            obj.PlotType = s.PlotType;
            obj.CrossSection = s.CrossSection;
            if isfield(s,'ZoomToBoundary')
                obj.ZoomToBoundary = s.ZoomToBoundary;
                obj.Actions(1).Selected = s.ZoomToBoundary;
            end
            obj.ShowLight = s.ShowLight;
            obj.Actions(2).Selected = s.ShowLight;
            
        end
        end  % serializeView     
        
        function ah = printCopy(obj, fig)
        %PRINTCOPY Create a printable version of the component
        %   NEWOBJ = PRINTCOPY(H, FIG) creates and returns a handle to a new
        %   component, NEWOBJ, parented by the specified figure, FIG. The new
        %   object will be used for printing a copy of this component.
        %
        %   Normally the printable version of a component is not a complete
        %   copy of the object.  There are often controls that should not
        %   appear, such as popup menus, or components whose information needs
        %   to be transformed, such as edit boxes.
        %
        %   See also canPrint, print, printSize.
        
        if isgraphics(obj.hAxes)
           ah = copyobj(obj.hAxes,fig);
           set(ah,'Units',get(fig,'DefaultAxesUnits'),...
               'Position',get(fig,'DefaultAxesPosition'));
        else
            ah = [];
        end
        end
        
        function OK = canPrint(obj) %#ok<MANU>
        
        OK = true;
        end        
        
        function reset(obj)
        %reset reset slice point 
        
        % reset cross-section point to the mid-point of the model range
        cs= obj.MessageService.CrossSection;
        inp = getInputs(obj.MessageService.Model);
        nInputs = length( inp );
        needsReset = size(obj.CrossSection,1)~=nInputs;
        for i=1:nInputs
            % check whether current slice point is outside input range
            needsReset = needsReset || obj.CrossSection(i,1)<inp(i).Range(1) || obj.CrossSection(i,1)>inp(i).Range(2);
            % center slice at mean with a tolerance of 2%
            cs(i,:) = [mean(inp(i).Range), diff(inp(i).Range)/50];
        end
        
        if needsReset
            % update cross-section values if current value is outside input
            % range
            obj.MessageService.CrossSection = cs;
            obj.CrossSection = cs;
        end
        initialize(obj)
        
        end
        
    end
    
    methods (Access=private)
        
        function create(obj)
        %create create view graphics
        
        SC = xregGui.SystemColorsDbl;

        lytAxesPanel = mbcgui.widget.AxesContainer(...
            'Parent',obj.Parent, ...
            'Border',[10 10 10 10],...
            'PositionSetting', 'outer');
        obj.hAxes = lytAxesPanel.AxesHandle;
        set(obj.hAxes,...
            'Box','on',...
            'XGrid','on',...
            'YGrid','on',...
            'ZGrid','on',...
            'Units','pixels',...
            'Layer','top', ...
            'NextPlot','add',...
            'HitTest', 'off');
        mbcxlabel( obj.hAxes, '', 'Interpreter', 'none' );
        mbcylabel( obj.hAxes, '', 'Interpreter', 'none' );
        mbczlabel( obj.hAxes, '', 'Interpreter', 'none' );
        
        obj.hDataPoints = line('Parent',obj.hAxes,...
            'LineStyle','none',...
            'Marker','.',...
            'MarkerSize',15, ...
            'XData', [], ...
            'YData', [], ...
            'ZData', []);
        
        obj.hValidationPoints = line('Parent',obj.hAxes,...
            'LineStyle','none',...
            'Marker', '^', ...
            'MarkerFaceColor', [50, 200, 50]/255, ...
            'MarkerSize', 5, ...
            'XData', [], ...
            'YData', [], ...
            'ZData', [], ...
            'PickableParts', 'none');
        
        SlicePanel = mbcgui.container.layoutpanel(...
            'Parent', obj.Parent);
        % 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', @obj.editSlice, ...
            'ValueConstraint', 'plot');
        
        obj.hSelectButton = uicontrol(...
            'Parent', SlicePanel, ...
            'Style','pushbutton',...
            'String','Select Data Point...',...
            'Interruptible','off',...
            'Callback', @obj.selectDataPoint );
        
        % Create the pop-up menus and label controls for the axes chooser
        axisNames = {'X', 'Y', 'Z'};
        hLabels = cell(1, obj.NumAxes );
        hPopups = cell(1, obj.NumAxes );
        
        SelectorPanel = mbcgui.container.layoutpanel(...
            'Parent', SlicePanel, ...
            'BorderType', 'none');
        for i = 1:obj.NumAxes
            hPopups{i} = uicontrol(...
                'Parent', SelectorPanel, ...
                'Style', 'popupmenu', ...
                'String', ' ', ...
                'Value', 1, ...
                'BackgroundColor', SC.WINDOW_BG, ...
                'Callback', @(h,evt) obj.editInputIndex(i));
            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.NumAxes, 1], ...
            'rowsizes', repmat( 21, 1, obj.NumAxes ), ...
            'gapy', 5, ...
            'elements', num2cell( obj.hAxisLabels ));
        set(SelectorPanel, 'LayoutComponent', {lytSelectors});

        
        obj.hPlotType = uicontrol(...
            'Parent', SlicePanel, ...
            'Style', 'popupmenu', ...
            'String', {'Line','Surface','Contour','Multi-line'}, ...
            'Value', 2, ...
            'BackgroundColor', SC.WINDOW_BG, ...
            'Callback', @obj.onSelectPlotType );
        hPlotTypeLabel = xregGui.labelcontrol( ...
            'parent',  SlicePanel, ...
            'String',  'Plot:', ...
            'LabelSize', 40, ...
            'LabelSizeMode', 'absolute', ...
            'ControlSize', 1, ...
            'ControlSizeMode', 'relative', ...
            'Control', obj.hPlotType );        
        
        % Create the layout that combines the slice table and axes selectors. This
        % is the slice control
        lytSliceControl = xreggridbaglayout( SlicePanel, ...
            'packgroup', 'XREG_PERM_ON', ...
            'Dimension', [4, 2], ...
            'ColSizes', [-1, 105], ...
            'RowSizes', [20 obj.NumAxes*26+3,-1,25], ...
            'gapy', 2, ...
            'border', [0 0 0 2], ...
            'MergeBlock', {[1 1], [1 2]}, ...
            'MergeBlock', {[2 2], [1 2]}, ...
            'MergeBlock', {[3 3], [1 2]}, ...
            'elements', {hPlotTypeLabel,SelectorPanel, obj.hSliceTable,[],...
            [],[],[],obj.hSelectButton});
        set(SlicePanel, 'LayoutComponent', {lytSliceControl});
        
        % Create the main layout for the view
        lyt = xregsplitlayout( obj.Parent,...
            'packgroup', 'XREG_PERM_ON',...
            'orientation', 'lr', ...
            'dividerstyle', 'flat', ...
            'dividerwidth', 4, ...
            'minwidth',[180,300],...
            'split', [0.1, 0.9], ...
            'left',  SlicePanel,...
            'right', lytAxesPanel,...
            'packstatus','on' );
        attachContentHandle(obj,lyt);        

        createActions(obj)
        end
        %Axes Selector X,Y fixed =1,2 for now
        
        function initialize(obj)
        %initialize initialize controls

        nf = nfactors(obj.MessageService.Model);
        inp = getInputs(obj.MessageService.Model);
        
        if size(obj.CrossSection,1)~=nf
            % initialize cross section point from testplan
            reset(obj)
        end
        
        if obj.hAxisPopups(1).Value>nf
            setInputIndex(obj, 1, 1)
        end
        set(obj.hAxisPopups(1),...
            'String',{inp.Symbol},...
            'Value',obj.InputIndices(1))
        set(obj.hPlotType,'Value',find( strcmp(obj.PlotType, obj.hPlotType.String) ))
        if nf==1
            % change to a line plot
            obj.PlotType = 'Line';
            obj.NumAxes = 1;
            obj.InputIndices = 1;
            set(obj.hPlotType,'Value',1,'Enable','off');
            set(obj.hAxisLabels(2),'Visible','off');
            set(obj.hAxisPopups(2),'String',{'<none>'},...
                'Value',1,...
                'Enable','off');
        else
            set(obj.hPlotType,'Enable','on');
            if obj.NumAxes>1
                set(obj.hAxisLabels(2),'Visible','on');
                set(obj.hAxisPopups(2),'Enable','on')
                if obj.hAxisPopups(2).Value>nf
                    setInputIndex(obj, 2, 2)
                end
                set(obj.hAxisPopups(2),...
                    'String',{inp.Symbol},...
                    'Value',obj.InputIndices(2))
            else
                set(obj.hAxisPopups(2),'Enable','off')
                set(obj.hAxisPopups(2),...
                    'String',{inp.Symbol},...
                    'Value',1)
            end
            
        end
        updateTableValues(obj);
        end        
        
        function range = getSliceRange(obj,sliceData)
        %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.
        %
        
        if nargin<2
            sliceData = obj.SlicePoints;
        end
        
        % 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) = [sliceData{i}(1); sliceData{i}(end)];
            end
            
        end
        end
        
        % Surface
        function drawResponseSurface(obj)
        %drawResponseSurface
        
        sliceData=obj.SlicePoints;
        delete(  get(obj.hAxes,'Children') )
        
        if obj.MessageService.Status
            switch obj.PlotType
                case 'Line'
                    plotLine(obj,sliceData);
                case 'Surface'
                    plotSurface(obj,sliceData);
                case 'Contour'
                    plotContour(obj,sliceData);
                case 'Multi-line'
                    plotMultiline(obj,sliceData);
            end
            drawPoints(obj,sliceData)
        end
        end
        
        function drawPoints(obj,sliceData)
        %drawPoints Draw the data/validation points that are in the current slice
        %
        %  drawPoints(OBJ,sliceData)
        %
        %  Assign the X-, Y- and Z-data for points and rings in the current slice.
        %
        
         if ~obj.MessageService.Status
             return
         end
        
        if nargin<2
            sliceData = obj.SlicePoints;
        end
        
        % Get the current node and the data associated with that node.
        data = double(obj.MessageService.XData);
        dataOK = obj.MessageService.DataOK;
        % Get the slice information
        % -- Slice subspace factors
        % -- Slice point and thickness (tolerance)
        sliceRange = obj.getSliceRange(sliceData);
        
        % Work out which points are in the slice
        ind = find( all( bsxfun(@le,sliceRange(1,:), data) & bsxfun(@ge,sliceRange(2,:),  data), 2 ) & dataOK);

        if ~isempty(obj.MessageService.ValidationXData) 
            xvaldata = double(obj.MessageService.ValidationXData);
            yvaldata = double(obj.MessageService.ValidationYData);
            
            valind = find( all( bsxfun(@le,sliceRange(1,:) , xvaldata) & bsxfun(@ge,sliceRange(2,:) , xvaldata), 2 ) );
        else
            xvaldata = [];
            valind = [];
        end
        
        XValData = [];
        YValData = [];
        ZValData = [];
        
        % Assign the data to the line objects
        switch lower(obj.PlotType)
            case {'line','multi-line'}
                Xdata = data(ind,obj.InputIndices(1));
                Ydata =double(obj.MessageService.YData(ind));
                Zdata = [];
                if ~isempty(xvaldata)
                    XValData = xvaldata(valind,obj.InputIndices(1));
                    YValData = yvaldata(valind);
                    ZValData = [];
                end
            case 'surface'
                % 2D plot
                Xdata = data(ind,obj.InputIndices(1));
                Ydata = data(ind,obj.InputIndices(2));
                Zdata = double(obj.MessageService.YData(ind));
                if ~isempty(xvaldata)
                    XValData = xvaldata(valind,obj.InputIndices(1));
                    YValData = xvaldata(valind,obj.InputIndices(2));
                    ZValData = yvaldata(valind);
                end
            case 'contour'
                % 1D plot
                Xdata = data(ind,obj.InputIndices(1));
                Ydata = data(ind,obj.InputIndices(2));
                Zdata = [];
                if ~isempty(xvaldata)
                    XValData = xvaldata(valind,obj.InputIndices(1));
                    YValData = xvaldata(valind,obj.InputIndices(2));
                    ZValData = [];
                end
        end
        obj.hDataPoints = line('Parent',obj.hAxes,...
            'LineStyle','none',...
            'Marker','.',...
            'MarkerSize',15, ...
            'XData', Xdata,...
            'YData', Ydata,...
            'ZData', Zdata);
        
        obj.hValidationPoints = line('Parent',obj.hAxes,...
            'LineStyle','none',...
            'Marker', '^', ...
            'MarkerFaceColor', [50, 200, 50]/255, ...
            'MarkerSize', 5, ...
            'XData', XValData,...
            'YData', YValData,...
            'ZData', ZValData,...
            'PickableParts', 'none');
        
        % 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.hDataPoints, 'UserData', ind  );
        set( obj.hValidationPoints,'UserData', valind  );
        end
        
        function updateTableValues(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
         
        % #define
        TYPE_SCALAR    = 1;
        TYPE_LINEAR    = 2;
        TYPE_LINKED    = 4;
        
        if strcmp(obj.PlotType,'Multi-line')
            AXES_NAMES = {'X-Axis', 'Lines'};
        else
            AXES_NAMES = {'X-Axis', 'Y-Axis', 'Z-Axis'};
        end
        
        % Get some input factor data from the object
        cs= obj.CrossSection;
        
        inp = getInputs(obj.MessageService.Model);
        nInputs = length( inp );
        
        % Get the current variable type and values
        types = zeros(nInputs,2);
        values = cell(nInputs,2);
        
        % Check each one in turn
        % -- If the type matches what it should be for the obj.InputIndices 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.InputIndices))
            case 1
                Resolution = 101;
            case 2
                Resolution = 51;
            otherwise
                Resolution = 21;
        end
        
        for i = 1:nInputs
            [tf, loc] = ismember( i, obj.InputIndices );
            if tf
                % This factor is an axis of the plot and we want it to be a vector
                % type
                % This factor needs it type set [TYPE_LINEAR, TYPE_LINKED] and
                % appropriate values set
                types(i,:) = [TYPE_LINEAR, TYPE_LINKED];
                if strcmp(obj.PlotType,'Multi-line') && i==obj.InputIndices(2)
                    values{i,1} = [inp(i).Range, 11];
                else
                    values{i,1} = [inp(i).Range, Resolution];
                end
                values{i,2} = AXES_NAMES{loc};
            else
                % This factor is not an axis of the plot and so it must be a scalar
                % type and it must have a tolerance
                % We need to set the type for this factor to scalar and the
                % values to the midpoint and to 2% of the range
                types(i,:) = [TYPE_SCALAR, TYPE_SCALAR];
                values(i,:) = { cs(i,1), cs(i,2) };
            end
        end
        
        obj.hSliceTable.setVariables( {inp.Symbol}', types, values );
        
        for i=1:length(obj.InputIndices)
            set(obj.hAxisPopups(i),...
                'String', {inp.Symbol}',...
                'Value',obj.InputIndices(i));
        end
        
        end
        
        function setInputIndex(obj, newIndex, index)
        %setInputIndex Set the indices of the axes in a boundary slice view
        %
        %  setInputIndex(OBJ, newIndex)
        %
        %  setInputIndex(OBJ, newIndex, 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.
        
        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.
            currentIndices = obj.InputIndices;
            currentIndices(index) = newIndex;
            tf = currentIndices == currentIndices(index);
            tf(index) = false;
            if any( tf )
                % Two pop-ups have the same value
                otherValues = setdiff( 1:obj.NumAxes, currentIndices );
                currentIndices(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( newIndex ) ~= obj.NumAxes || ~isnumeric( newIndex )
            error(message('mbc:xregbdrygui:AbstractBdrySlice:InvalidArgument', obj.NumAxes));
        else
            currentIndices = newIndex;
        end
        
        % Set the axes in the object
        % -- also updates the pop-ups
        obj.InputIndices = currentIndices;
        
        % Set the slice table to have the correct columns linked and the other
        % columns
        obj.updateTableValues;
        
        % Draw the new constraint
        obj.drawResponseSurface;
        end
        
        function setSlicepoint(obj,point)
        
        cs = obj.CrossSection;
        cs(:,1) = point(:);
        obj.CrossSection = cs;
        end
        
        function updateCrossSection(obj)
        
        cs = obj.CrossSection;
        
        values = obj.hSliceTable.getNumericValues;
        types  = obj.hSliceTable.VariableTypes;
        
        for i=1:length(values)
           switch types(i,1)
               case 1 % scalar
                   cs(i,:) = [values{i,:}];
           end
        end
        obj.CrossSection = cs;
        
        end      
        
        function onNodeUpdated(obj,~,~)
        update(obj)
        end
        
        function onNodeChanged(obj,~,~)
        initialize(obj)
        end
        
        function onNodeReset(obj,~,~)
        %onNodeReset reset inputs to mid range
        
        reset(obj)

        end
        
        function onZoomToBoundary(obj,~,~)
        
        obj.ZoomToBoundary = ~obj.ZoomToBoundary;
        update(obj);
        end
        
        function onShowLight(obj,~,~)
        obj.ShowLight = ~obj.ShowLight;
        update(obj);
        end
        
        function onSelectPlotType(obj,~,~)
        
        NewType = obj.hPlotType.String{obj.hPlotType.Value};
        if ~strcmp(NewType,obj.PlotType)
            obj.PlotType = obj.hPlotType.String{obj.hPlotType.Value};
            if strcmp(obj.PlotType,'Line')
                % Line only has one axes
                obj.NumAxes = 1;
                obj.InputIndices = obj.InputIndices(1);
                set(obj.hAxisLabels(2),'Enable','off','Visible','off')
            else
                set(obj.hAxisLabels(2),'Enable','on','Visible','on')
                obj.NumAxes = 2;
                if isscalar(obj.InputIndices)
                    % change from Line (one axes) to two
                    obj.InputIndices = [obj.InputIndices,rem(obj.InputIndices,obj.MessageService.NumInputs)+1];
                    set(obj.hAxisPopups(2),'Value',obj.InputIndices(2));
                end
            end
            initialize(obj)
            update(obj);
        end
        end
        
        function createActions(obj)
        obj.Actions = [mbcgui.actions.ToggleAction(@obj.onZoomToBoundary,...
            '&Zoom to Boundary','Zoom to boundary constraint')
            mbcgui.actions.ToggleAction(@obj.onShowLight,...
            'Show &Light','Show light')];
        obj.Actions(1).Selected = obj.ZoomToBoundary;
        obj.Actions(2).Selected = obj.ShowLight;
        end
        
        function plotSurface(obj,x)
        [az,el] = view(obj.hAxes);
        hs=surface(obj.MessageService.Model,x,obj.hAxes,...
            [0,obj.InputIndices(1)>obj.InputIndices(2),~obj.ZoomToBoundary,obj.ShowLight],...
            obj.MessageService.BoundaryModel);
        set(hs,'FaceAlpha',0.7);
        set(hs,'Tag','MainResponse')
        
        % If the old view angle was 2d we swap to 3d default, other wise we leave
        % it alone.  So if we swap between models we retain the same view angle.
        if( az==0 && el==90 )
            view(obj.hAxes, 3);
        else
            view(obj.hAxes, [az, el]);
        end
        mv_rotate3d(obj.hAxes,'ON');
        end
        
        function plotContour(obj,x)
        view(obj.hAxes,[0 90]);
        mv_rotate3d(obj.hAxes,'off');
        cmodel = obj.MessageService.BoundaryModel;

        [~,hs]=contour(obj.MessageService.Model,x,obj.hAxes,[],...
            [1,1,obj.InputIndices(1)<obj.InputIndices(2)],...
            cmodel);
        set(hs,'HitTest','off','Tag','MainResponse');
        end
        
        function plotLine(obj,x)
        %plotLine 
        
        view(obj.hAxes,[0 90]);
        mv_rotate3d(obj.hAxes,'off');
        
        xi = x{obj.InputIndices(1)};

        mdl = obj.MessageService.Model;
        bdry = xregGui.intervalPatch1D('Parent', obj.hAxes, ...
            'FaceColor', mbcbdrycolor);
        col = obj.hAxes.ColorOrder;
        if pevcheck(mdl)
            % can calculate confidence interval
            df = dferror(mdl);
            if isfinite(df) && df<1000
                ni = tinv(0.975,df);
            else
                ni = norminv(0.975);
            end
            % use pevgrid to generate pe and yhat
            [PE,~,~,yi]= pevgrid(mdl,x);
            yi= squeeze(yi);
            p  = squeeze(sqrt(PE));
            % ci's
            yhi= yi+ni*p;
            ylo= yi-ni*p;
            
            % include confidence intervals in plot
            h = plot(obj.hAxes,xi,yi,'-',...
                xi,yhi,'--',xi,ylo,'--',...
                'Color',col(1,:));
        else
            % just evaluate response
            YI = GenTable(mdl,x);
            yi = squeeze(YI);
            h = plot(obj.hAxes,xi,yi,'-',...
                'Color',col(1,:));
        end
        set(h(1),'Tag','MainResponse')
        
        % boundary model
        cmodel = obj.MessageService.BoundaryModel;
        if ~isempty(cmodel)
            cvals= squeeze(constraintDistanceGrid(cmodel,x));
            ConstraintEdges = xregGui.intervalPatch1D.convertConstraintVector( xi, cvals);
            set(bdry,'OpenIntervals',ConstraintEdges);
        end
        xlab =InputLabels(mdl);
        xlabel(obj.hAxes,xlab{obj.InputIndices},'Interpreter','none')
        ylabel(obj.hAxes,ResponseLabel(mdl),'Interpreter','none')

        end
        
        function x = zoomConstraints(obj,x)
        %zoomConstraints zoom ranges inside boundary model
        
        cmodel = obj.MessageService.BoundaryModel;
        if ~isempty(cmodel)
            cvals= squeeze(constraintDistanceGrid(cmodel,x));
            indices = sort(obj.InputIndices);
            
            
            if length(obj.InputIndices)>1
                inside = any(cvals<1e-6,2);
                x{indices(1)} = findRegion(x{indices(1)},inside);
                inside = any(cvals<1e-6,1);
                x{indices(2)} = findRegion(x{indices(2)},inside);
            else
                inside = cvals<1e-6;
                x{indices} = findRegion(x{indices},inside);
            end
        end
        end
        
        function plotMultiline(obj,x)
        
        view(obj.hAxes,[0 90]);
        mv_rotate3d(obj.hAxes,'off');   
        if obj.ZoomToBoundary
            cmodel = [];
        else
            cmodel = obj.MessageService.BoundaryModel;
        end
        mdl = obj.MessageService.Model;
        h = multiline(mdl,x,obj.hAxes,obj.InputIndices(1)<obj.InputIndices(2),cmodel,'parula');
        set(h,'Tag','MainResponse')

        
        end
        
        function selectDataPoint( obj, ~, ~ )
        X = double(obj.MessageService.XData);
        X = X(obj.MessageService.DataOK,:);
        
        nX = size( X, 1 );
        list = num2str( [(1:nX).', X], 4 );
        
        % Throw up a list dialog
        [index, ok] = mv_listdlg( ...
            'ListString', list, ...
            'SelectionMode', 'Single', ...
            'InitialValue', 1, ...
            'ListSize', [320 300], ... % [width height] of listbox in pixels
            'Name', 'Select Data Point', ...
            'OkString', 'OK', ...
            'uh', 25, ...
            'ffs', 3 );
        
        if ok
            point = X(index,:);
            
            obj.setSlicepoint(point)
            
            obj.updateTableValues
            % Draw the new surface
            obj.drawResponseSurface;
            
        end
        
        end
        
        function editSlice(obj,~, evt)
        % 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.
        types  = obj.hSliceTable.VariableTypes;
        
        switch evt.Data.Columns
            case 2
                % The user is editing column one, the evaluation points
                if types(evt.Data.Rows,1)==1
                    obj.CrossSection(evt.Data.Rows,1) = evt.Data.NewValue.getScalar;
                    obj.drawResponseSurface;
                end
            case 3
                % The user is editing column two, the tolerances
                obj.CrossSection(evt.Data.Rows,2) = evt.Data.NewValue.getScalar;
                obj.drawPoints;
        end
        end

        
        function editInputIndex(obj, index )
        
        obj.setInputIndex( obj.hAxisPopups(index).Value, index );
        end
        
        
    end
    
    
end


function x = findRegion(x,inside)
%findRegion find region inside boundary 
%  inside is a logical array
first = find(inside,1,'first');
last = find(inside,1,'last');
if ~isempty(first) 
    first = max(first-1,1);
    last = min(last+1,length(inside));
   x = linspace(x(first),x(last),length(x));
end
end