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

    classdef MonitorPlotView < xregdatagui.AbstractDataView
    %xregdatagui.MonitorPlotView class
    %   xregdatagui.MonitorPlotView extends xregdatagui.AbstractDataView.
    %
    %    xregdatagui.MonitorPlotView 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)
    %       Container - Property is of type 'handle'
    %       UIContextMenu - Property is of type 'MATLAB array'
    %       Listeners - Property is of type 'handle vector' (read only)
    %       ListControl - Property is of type 'MATLAB array' (read only)
    %       AxesHandles - Property is of type 'MATLAB array' (read only)
    %       PlotProperties - Property is of type 'MATLAB array'
    %
    %    xregdatagui.MonitorPlotView methods:
    %       canPrint - Check whether component can be printed
    %       gettitle - returns string describing this view
    %       serializeView - A short description of the function
    
    %  Copyright 2015 The MathWorks, Inc. and Ford Global Technologies, Inc.
    
    properties (Constant)
        %ViewInfo view description
        ViewInfo = { @xregdatagui.MonitorPlotView;
            '&Multiple Plots';
            xregrespath('dataplots.bmp');
            'Multiple Plots';
            1};
    end
    
    properties (AbortSet, SetObservable)
        %PLOTPROPERTIES Property is of type 'MATLAB array'
        PlotProperties = xregmonitorplotproperties;
        %SelectAllTests select all tests for display
        SelectAllTests = false;
    end
    
    properties (SetAccess=protected, AbortSet, SetObservable)
        %LISTCONTROL Property is of type 'MATLAB array' (read only)
        ListControl = [];
        %AXESHANDLES Property is of type 'MATLAB array' (read only)
        AxesHandles = [];
        %CardLayout card layout to contain instructions when there are no plots
        CardLayout
    end
    
    
    methods  % constructor block
        function obj = MonitorPlotView(varargin)
        %MONITORPLOTVIEW constructor
        %   OBJ = MONITORPLOTVIEW(VARARGIN)
        
        % Do the setup stuff from inside the abstractDataView constructor
        obj@xregdatagui.AbstractDataView(varargin{ : }); % converted super class constructor call
        
        obj.createActions;
        
        
        % Create the graphical objects and put in the Display field
        obj.pCreateDisplay;

        
        % Update the display
        obj.pUpdateDisplay
        
        end  % monitorplotview
        
    end  % constructor block
    
    methods
        function set.PlotProperties(obj,value)
        
        if ~isa(value, 'xregmonitorplotproperties')
            error(message('mbc:xregdatagui:monitorplotview:InvalidProperty'));
        end
        obj.PlotProperties = value;
        
        pPlotPropertiesChanged(obj);
        end
        
    end   % set and get functions
    
    methods  % public methods
        %----------------------------------------
        function out = canPrint(obj) %#ok<MANU>
        %CANPRINT Check whether component can be printed
        %   CANPRINT(OBJ) returns true for monitorplotview components.
        
        out = true;
        end  % canPrint
       
        %----------------------------------------
        function out = gettitle(obj) %#ok<MANU>
        %GETTITLE returns string describing this view
        %   OUT = GETTITLE(OBJ)
        
        out = 'Multiple Data Plots';
        end  % gettitle
        
        %----------------------------------------
        function pAddPlot(obj, ~,~)
        %PADDPLOT add a plot to the current list of plots
        %   PADDPLOT(OBJ, EVENT)
        
        % Add a plot to the current properties object
        obj.PlotProperties = addPlot(obj.PlotProperties);
        % What variables are to be displayed
        obj.pModifyPlotVariables();
        end  % pAddPlot
        
        %----------------------------------------
        function pCreateDisplay(obj)
        %pCreateDisplay Create all uicontrols and visual components for this view
        %    pCreateDisplay(OBJ)
        
        hPromptPanel = mbcgui.container.layoutpanel('Parent', obj.Parent, ...
            'BorderType', 'none', ...
            'HitTest','off',...
            'Tag','MonitorPromptPanel',...
            'Units', 'pixels');
        hTxt = uicontrol('Parent', hPromptPanel,...
            'Style', 'text',...
            'HitTest', 'off',...
            'FontSize',10,...
            'String', 'You have no plots setup yet. Right click to choose which variables to plot.');
        hButtton = createButton(obj.Options.Actions(1),hPromptPanel);
        
        lyt = xreggridbaglayout(hPromptPanel,...
            'dimension',[4 3],...
            'gapy',10,...
            'rowsizes',[-1 40 25 -1],...
            'colsizes',[-1 100 -1],...
            'elements',{[],[],[]
            hTxt,[],[]
            [],hButtton,[]
            [],[],[]},...
            'MergeBlock',{[2 2],[ 1 3]});
        hPromptPanel.LayoutComponent = lyt;
        
        % Create the list control to hold the axes
        obj.ListControl = xreglistctrl(obj.Parent,...
            'border', [-1 -1 -1 -1],...
            'UIContextMenu',obj.UIContextMenu,...
            'fixnumcells', 2);
        
        % create a set of cards - one holds a prompt for the user, the other the
        % axes that we plot on
        obj.CardLayout = xregcardlayout(obj.Parent,...
            'currentcard', 1,...
            'numcards', 2);
        attach( obj.CardLayout, hPromptPanel, 1);
        attach( obj.CardLayout, obj.ListControl, 2);
        obj.ContentHandle = obj.CardLayout;
        
        end  % pCreateDisplay
        
        function createActions(obj)
        %createActions create view actions (available from contextmenu)
        ms = obj.MessageService;
        obj.Options.Actions = [mbcgui.actions.StatefulAction(@obj.pAddPlot,...
            '&Add Plot...','Add plot')
            mbcgui.actions.StatefulAction(@obj.pModifyPlotProperties,...
            '&Plot Properties...','Plot properties')
            mbcgui.actions.StatefulAction(@obj.pModifyPlotVariables,...
            'Plot &Variables','Plot variables')
            mbcgui.actions.StatefulAction(@obj.onRemoveAllPlots,...
            'Remove &All Plots','Remove all plots')
            mbcgui.actions.StatefulAction(@obj.onRemovePlot,...
            '&Remove Plot','Remove plot')
            mbcgui.actions.ToggleAction(@obj.onSelectAllTests,...
            '&Select All Tests','Select all tests')];
        obj.Actions = ms.Actions.OutlierTools;        % add outlier tools to menu
        [obj.Options.Actions(2:end-1).Enabled] = deal(false);
        % select all tests
        obj.Options.Actions(6).Selected = obj.SelectAllTests;
        end
        
        %----------------------------------------
        function pModifyPlotVariables(obj, ~,~)
        %PMODIFYPLOTVARIABLES change variables displayed on the plot
        %   PMODIFYPLOTVARIABLES(OBJ, ~, ~)
        
        if length(obj.PlotProperties)==0 %#ok<ISMT>
            % add a new plot if there are not any
            obj.PlotProperties = addPlot(obj.PlotProperties);
        end
        
        % Is the current axes of the figure one of ours
        [OK, index] = ismember(get(ancestor(obj.Parent,'figure'), 'CurrentAxes'), obj.AxesHandles);
        % Only modify if the currentAxes is one or ours
        if OK
            obj.PlotProperties = editPropertiesDlg(obj.PlotProperties, index, obj.MessageService.Sweepset, obj.Parent);
            obj.pUpdateDisplay;
        end
        
        end  % pModifyPlotVariables
        
        %----------------------------------------
        function pObjectBeingDestroyed(obj, ~)
        %POBJECTBEINGDESTROYED called when the view is being destroyed
        %   POBJECTBEINGDESTROYED(OBJ, EVENT)
        
        if isgraphics(obj.Parent) && ~mbcgui.util.isBeingDestroyed(obj.Parent)
            % Delete the viewer menu items used, by deleting their parent
            delete(get(obj.OptionMenuItems(1), 'Parent'));
        end
        
        end  % pObjectBeingDestroyed
        
        %----------------------------------------
        function pPlotPropertiesChanged(obj, ~,~)
        %PPLOTPROPERTIESCHANGED post-set obj.PlotProperties
        %   PPLOTPROPERTIESCHANGED(OBJ, EVENT)
        
        if length(obj.PlotProperties)==0 %#ok<ISMT>
            set(obj.CardLayout,'CurrentCard',1);
        else
            set(obj.CardLayout,'CurrentCard',2);
        end
        
        % Have we got the correct number of axes to display the requested data
        if length(obj.PlotProperties) ~= length(obj.AxesHandles)
            % NO ... Do we need to add or remove axes?
            difference = length(obj.PlotProperties) - length(obj.AxesHandles);
            % Lets add or remove the correct number of axes
            if difference > 0
                i_addAxes(obj, difference);
            else
                i_removeAxes(obj, -difference);
            end
        end
        % Are a maximal number of axis visible?
        topItem = get(obj.ListControl, 'top');
        numItems = length(get(obj.ListControl, 'elements'));
        if  numItems > 1 && topItem > numItems - 1
            set(obj.ListControl, 'top', numItems - 1);
        end
        % OK - We have the correct number of axes for the plots now so it's as
        % though the data had changed
        obj.pssDataChangedUpdate();
        % Enable of disable the options menu items
        if isempty(obj.PlotProperties)
            [obj.Options.Actions(2:end-1).Enabled] = deal(false);
        else
            [obj.Options.Actions(2:end-1).Enabled] = deal(true);
        end
        end  % pPlotPropertiesChanged
        
        
        function onRemoveAllPlots(obj,~,~)
        %onRemoveAllPlots remove all axes callback
        obj.pRemovePlot(obj.AxesHandles);
        end
        
        function onRemovePlot(obj,~,~)
        %onRemovePlot remove current axes callback
        
        obj.pRemovePlot( get(ancestor(obj.Parent,'figure'), 'CurrentAxes'));
        end
        
        function onSelectAllTests(obj,~,~)
        %onSelectAllTests select all tests callback
        obj.SelectAllTests = ~obj.SelectAllTests;
        
        obj.pssDataChangedUpdate();
        end
        
        
        %----------------------------------------
        function pRemovePlot(obj, axesHandles)
        %PREMOVEPLOT remove the current plot from the view
        %   PREMOVEPLOT(OBJ, EVENT)
        
        % Are the handles to axes to remove any of ours?
        [OK, index] = ismember(axesHandles, obj.AxesHandles);
        % Get their specific indices
        indexToRemove = index(OK);
        % Only remove if the currentAxes is one or ours
        if ~isempty(indexToRemove)
            % Remove from our list
            %obj.AxesHandles(indexToRemove) = [];
            % And delete the properties associated
            obj.PlotProperties = removePlot(obj.PlotProperties, indexToRemove);
            obj.pUpdateDisplay
        end
        
        end  % pRemovePlot
        
        %----------------------------------------
        function pUpdateDisplay(obj,~,~)
        %PUPDATEDISPLAY complete update of the view display
        %   PUPDATEDISPLAY(OBJ)
        
        % Update the data via the plot properties
        obj.pPlotPropertiesChanged([]);
        
        end  % pUpdateDisplay
        
        %----------------------------------------
        function pdmsDataTypeChangedUpdate(obj, ~,~)
        %PDMSDATATYPECHANGEDUPDATE event handler for the dmsDataTypeChanged event
        %   PDMSDATATYPECHANGEDUPDATE(OBJ, EVENT)
        
        % Find the sweepset listener
        ssListeners = obj.MessageService.findListeners(obj.Listeners, 'Sweepset');
        % And sweepsetfilter listeners
        ssfListeners = obj.MessageService.findListeners(obj.Listeners, 'SweepsetFilter');
        % If the dataObject isn't a sweepset then...
        if ~isobject(obj.MessageService.DataObject)
            % Turn off the sweepset listener
            [ssListeners.Enabled] = deal(false);
            [ssfListeners.Enabled] = deal(false);
        else
            % Turn on the sweepset listener
            [ssListeners.Enabled] = deal(true);
            [ssfListeners.Enabled] = deal(true);
        end
        
        end  % pdmsDataTypeChangedUpdate
        
        %----------------------------------------
        function sz = printSize(obj)
        %PRINTSIZE Returns the preferred printing size for the component
        %   SZ = PRINTSIZE(OBJ) returns a two element vector containing the
        %   preferred width and height for printing the component.
        
        % How many elements are being displayed by the ListControl
        numElements = length(get(obj.ListControl, 'element'));
        
        % Multiply the view y size by half the number of elements (2 rows are
        % shown at a time on screen) and add 50 for the title.
        
        sz = [700 numElements*175+50];
        
        end  % printSize
        
        %----------------------------------------
        function layout = printCopy(obj, newFigure)
        %PRINTCOPY copy the layout to the newfigure
        %   LAYOUT = PRINTCOPY(OBJ, FIGURE)
        
        % Create the correct number of new axes (-1 is an invalid handle)
        newAxes = gobjects(length(obj.PlotProperties), 1);
        elements = cell(size(newAxes));
        for i = 1:length(obj.PlotProperties)
            elements{i} = mbcgui.widget.AxesContainer(...
                'Parent', newFigure,...
                'PositionSetting','outer',...
                'Border', [5 5 5 5]);
            newAxes(i) = elements{i}.AxesHandle;
        end
        
        % Hold the old axes handle
        oldAxes = obj.AxesHandles;
        UIContextMenu = obj.UIContextMenu;
        try
            % Make the object think it's using the new axes to print
            obj.AxesHandles = newAxes;
            obj.UIContextMenu = [];
            % Plot the graphs
            obj.pssDataChangedUpdate([]);
            % Make sure the button down functions are unset
            set(newAxes, 'ButtonDownFcn', [],'Tag','dataPlot');
            set(findobj(newAxes,'Type','Line'), 'ButtonDownFcn', []);
        catch E
            obj.AxesHandles = oldAxes;
            obj.UIContextMenu = UIContextMenu;
            rethrow(E);
        end
        % Put the old axes back
        obj.UIContextMenu = UIContextMenu;
        obj.AxesHandles = oldAxes;
        
        numRows = length(get(obj.ListControl, 'controls'));
        numCols = 2;
        
        % Need to ensure that elements has an even number of entries for the
        % transform below
        if mod(length(elements), 2)
            elements{end+1} = [];
        end
        % Need to ensure the elements are in column order for the grid layout
        elements = reshape(elements, [numCols numRows])';
        
        % Place the axes in a layout to comply with the tenets of printcopy
        plotlayout = xreggridbaglayout(newFigure,...
            'packstatus','on',...
            'dimension', [numRows, numCols],...
            'elements', elements);
        
        % Add a title
        titleH = axestext(newFigure, ...
            'string',[obj.gettitle ' for "' obj.MessageService.ObjectName '"'],...
            'fontsize', 11,...
            'fontweight', 'bold',...
            'HorizontalAlignment', 'center',...
            'VerticalAlignment', 'middle',...
            'interpreter', 'none');
        layout = xreggridbaglayout(newFigure, ...
            'packstatus','on',...
            'Dimension', [2 1], ...
            'Rowsizes', [25 -1], ...
            'gapy', 20, ...
            'border', [0 0 0 5], ...
            'elements', {titleH, plotlayout});
        
        end  % printcopy
        
        %----------------------------------------
        function pssDataChangedUpdate(obj, ~,~)
        %pDataChangedUpdate updates the view on receipt of a "DataChanged" event from the DMS
        %   PSSDATACHANGEDUPDATE(OBJ,SRC,EVT)
        %
        
        % For now we will just re-plot the whole thing
        xlabels  = get(obj.AxesHandles, {'XLabel'});
        titles   = get(obj.AxesHandles, {'Title'});
        outlierLine = obj.MessageService.OutlierLine;
        for i = 1:length(obj.AxesHandles)
            % Remove lines from outlier control
            lines = findobj(obj.AxesHandles(i), 'Type', 'line');
            outlierLine.remove(lines);
            
            % Delete remaining lines in the axes
            lines = findobj(obj.AxesHandles(i), 'Type', 'line');
            delete(lines);
            
            set(xlabels{i}, 'String', '');
            set(titles{i}, 'String', '');
        end
        
        dispStr = '.';
        
        ms = obj.MessageService;
        % get tests to plot
        if obj.SelectAllTests
            selectedIndices = 1:size(ms.Sweepset,3);
        else
            selectedIndices = ms.SelectedTests;
        end
        ssGoodData = ms.Sweepset(:,:,selectedIndices);
        ssBadData = [];
        for i = 1:length(obj.PlotProperties)
            plotProperties = obj.PlotProperties(i);
            xName  = plotProperties.xName;
            yNames = plotProperties.yNames;
            props  = plotProperties.properties;
            % Is there anything to plot?
            if isempty(yNames)
                continue
            end
            % Which data are we plotting here
            DISPLAY_BAD_DATA = strcmp(props.showBadData, 'on');
            if DISPLAY_BAD_DATA
                selectedTests = ms.GoodToBadIndexMap(selectedIndices);
                if isempty(ssBadData)
                    % retrieve sweepset with bad data only if needed
                    ssBadData = ms.SweepsetWithBadData;
                end
                ss = ssBadData(:,:,selectedTests);
            else
                selectedTests = selectedIndices;
                ss = ssGoodData;
            end
            varYInd = find(ss, yNames);
            if ~isempty(varYInd)
                % Check that the yNames are valid
                Ysweepset = ss(:, varYInd);
                varXInd = find(ss, xName);
                if isempty(varXInd)
                    % Is there a valid X Name
                    if length(yNames) > 1 && length(selectedTests) > 1
                        % separate tests with magenta sweepline when more
                        % than 1 y variable and more than 1 test
                        [h, ~] = sweepplot(Ysweepset, dispStr,...
                            'parent', obj.AxesHandles(i),...
                            'plotproperties', props,...
                            'sweeplines', 'm');
                    else
                        
                        [h, ~] = sweepplot(Ysweepset, dispStr,...
                            'parent', obj.AxesHandles(i),...
                            'plotproperties', props);
                    end
                else
                    % Check that the yNames are valid
                    [h, ~] = sweepplot(ss(:, varXInd), Ysweepset, dispStr,...
                        'parent', obj.AxesHandles(i),...
                        'plotproperties', props);
                end
                % Need to work out if we have multiple variables, multiple line types
                % etc. Note that the return of sweepsetplot is a nx1 array of handles
                % which references all variables in test 1 then test 2 etc.
                numVariables = length(yNames);
                numTests     = numel(h)/numVariables;
                % Need to reshape the handles correctly
                reshapedH = reshape(h, numVariables, numTests);
                guids = getGuids(Ysweepset);
                for j = 1:numVariables
                    outlierLine.add(reshapedH(j, :), guids);
                end
            end
        end
        
        % Ensure zoom is on after an update plot
        set(obj.AxesHandles, 'ButtonDownFcn', {@i_axesClick obj},...
            'UIContextMenu',obj.UIContextMenu);
        end  % pssDataChangedUpdate
        
        %----------------------------------------
        function pssUnitsChangedUpdate(obj, ~,~)
        %pUnitsChangedUpdate updates the view on receipt of a "ssUnitsChanged" event from the DMS
        %    pUnitsChangedUpdate(OBJ,SRC,EVT)
        
        % If units change without a data change we still need to update the plot
        if ~any(strcmp(obj.MessageService.EventQueue, 'ssDataChanged'))
            obj.pssDataChangedUpdate();
        end
        end  % pssUnitsChangedUpdate
        
        %----------------------------------------
        function pssfBadDataChangedUpdate(obj, ~,~)
        %pssfBadDataChangedUpdate updates the view on receipt of a "ssfBadDataChanged" event from the DMS
        %   PSSFBADDATACHANGEDUPDATE(OBJ)
        
        % Peek in the message queue to see if there is going to be an ssDataChanged
        % event. If not initiate one
        if ~ismember('ssDataChanged', obj.MessageService.EventQueue)
            obj.pssDataChangedUpdate([]);
        end
        end  % pssfBadDataChangedUpdate
        
        function pssTestsChangedUpdate(obj,~,~)
        %pssTestsChangedUpdate test definition changed
        
        if obj.MessageService.isOneStage
            % hide test selector and controls
            obj.SelectAllTests = false;
            % disable select all tests
            set(obj.Options.Actions(6),'Enabled',true,'Selected',false,'Enabled',false);
        else
            % enable select all tests and separate test selection
            set(obj.Options.Actions(6),'Enabled',true);
        end
        
        if ~any(strcmp(obj.MessageService.EventQueue, 'ssDataChanged'))
            obj.pssDataChangedUpdate();
        end        
        
        end % pssTestsChangedUpdate
        
        %----------------------------------------
        function [out] = serializeView(obj)
        %SERIALIZEVIEW save settings for multiple data plots
        %    OUT = SERIALIZEVIEW(IN)
        
        out = {'PlotProperties' obj.PlotProperties,'SelectAllTests',obj.SelectAllTests};
        end  % serializeView
        
        function deserializeView(obj,varargin)
        %deserializeView appled saved settings for multiple data plots
        
        if nargin>1
            deserializeView@xregdatagui.AbstractDataView(obj,varargin{:});
            % set Action state for SelectAllTests
            obj.Options.Actions(6).Selected = obj.SelectAllTests;
            obj.pUpdateDisplay
        end
        end
        
    end  % public methods
    
    
    methods (Hidden) % possibly private or hidden
        %----------------------------------------
        function pModifyPlotProperties(obj, ~,~)
        %PMODIFYPLOTPROPERTIES plot properties dialog for current axes
        %  PMODIFYPLOTPROPERTIES(OBJ, SRC, EVENT)
        
        % Is the current axes of the figure one of ours
        [OK, index] = ismember(get(ancestor(obj.Parent,'figure'), 'CurrentAxes'), obj.AxesHandles);
        % Only modify if the currentAxes is one or ours
        if OK
            obj.PlotProperties = plotPropertiesDlg(obj.PlotProperties, index, obj.MessageService.Sweepset, obj.Parent);
            obj.pUpdateDisplay;
        end
        
        end  % pModifyPlotProperties
        
        %----------------------------------------
        function pPostSetDataMessageService(obj, ~,~)
        %PPOSTSETDATAMESSAGESERVICE setup MessageService listeners
        %   PPOSTSETDATAMESSAGESERVICE(OBJ, EVENT)
        
        % Call the super pPostSetDataMessageService
        pPostSetDataMessageService@xregdatagui.AbstractDataView(obj);
        
        dms = obj.MessageService;
        
        dmsListeners = [...
            event.listener(dms, 'dmsDataTypeChanged', @obj.pdmsDataTypeChangedUpdate);...
            event.listener(dms, 'ssfBadDataChanged',  @obj.pssfBadDataChangedUpdate);...
            event.listener(dms, 'ssUnitsChanged',     @obj.pssUnitsChangedUpdate);...
            event.listener(dms, 'ssDataChanged',      @obj.pUpdateDisplay);...
            event.listener(dms, 'ssTestsChanged',     @obj.pssTestsChangedUpdate);...
            event.listener(dms, 'SelectedTestsChanged',@obj.pssDataChangedUpdate);...
            ];
        obj.Listeners = [obj.Listeners(:); dmsListeners(:)];
        
        end  % pPostSetDataMessageService
        
    end  % possibly private or hidden
    
end  % classdef

function i_addAxes(obj, numToAdd)
%i_addAxes add axes to monitor plots
%   i_addAxes(obj, numToAdd)

% Get the list controls elements
elements = get(obj.ListControl, 'elements');
% How many axes in the last element of the control list
if ~isempty(elements) && length(get(elements{end}, 'elements')) == 1
    % Get the axes in the last control
    controlAx = get(elements{end}, 'axes');
    newAxes = axes('Parent',get(controlAx,'Parent'),...
        'Units','pixels',...
        'XGrid','on',...
        'YGrid','on',...
        'Box','on',...
        'Tag','dataPlot',...
        'UIContextMenu',get(obj,'UIContextMenu'),...
        'NextPlot','replacechildren',...
        'DefaultLineHitTest','on');
    set(newAxes, 'ButtonDownFcn', {@i_axesClick obj});
    
    % Add the first axes
    set(elements{end}, 'axes', [controlAx newAxes]);
    obj.AxesHandles = [obj.AxesHandles ; newAxes];
    numToAdd = numToAdd-1;
end
% Anything left to add?
if numToAdd>0
    % Create the new controls cell array
    newControls = cell(ceil(numToAdd/2), 1);
    % Now stuff them into the list control
    for i = 1:2:numToAdd
        % Which axes should be added (make sure we don't overflow the length)
        if i+1>numToAdd
            n = 1;
        else
            n = 2;
        end
        % Create a new axesinput object
        ind = ceil(i/2);
        newControls{ind} = xregaxesinput(obj.Parent, n, ...
            'visible', 'on',...
            'gapx', 60,...
            'numcells', 2);
        newAxes = get(newControls{ind},'axes');
        set(newAxes,'XGrid','on',...
            'YGrid','on',...
            'Box','on',...
            'NextPlot','replacechildren',...
            'UIContextMenu',get(obj,'UIContextMenu'),...
            'DefaultLineHitTest','on')
        obj.AxesHandles = [obj.AxesHandles ; newAxes];
        set(newAxes, 'ButtonDownFcn', {@i_axesClick obj});
    end
    % And append to the display
    append(obj.ListControl, newControls);
    
end
end  % i_addAxes

%------------------------------------------------------------------------
function i_removeAxes(obj, numToRemove)
%i_removeAxes remove (last) axes from monitor plots
%   i_removeAxes(obj, numToRemove)

% How many axes are there after
numAfter = length(obj.AxesHandles) - numToRemove;
% Will we be left with an odd or even number of axes
numLeftOver = rem(numAfter, 2);
% Get the current list of controls
controls = get(obj.ListControl, 'elements');
% How many controls are going to be left
numControlsAfter = ceil(numAfter/2);
% Remove the offending controls
remove(obj.ListControl, numControlsAfter+1:length(controls));
% What about the odd axes left over?
if numLeftOver
    % Get the axes from the last control
    ax = get(controls{numControlsAfter}, 'axes');
    
    % Set the axes we want to keep back.
    set(controls{numControlsAfter}, 'axes', ax(1:numLeftOver));
end

% Finally remove from the axes handles
obj.AxesHandles = obj.AxesHandles(1:numAfter);
end  % i_removeAxes

function i_axesClick(ax, ~, obj)
%i_axesClick button down callback for axes

% First dispatch to the container
notify(obj, 'ButtonDown');
% Then zoom
mv_zoom(ax);
end  % i_axesClick

function onClick(~,~,obj)
% First dispatch to the container
notify(obj, 'ButtonDown');

end