Code covered by the BSD License  

Highlights from
ANIMATOR - animate data

image thumbnail

ANIMATOR - animate data

by

 

21 Aug 2007 (Updated )

This GUI tool allows you to animate your data with controls for playback speed and looping.

Editor's Notes:

This was a File Exchange Pick of the Week

animatorApp
classdef animatorApp < handle
    
    %ANIMATORAPP  Data animation viewer
    %   This GUI tool allows you to animate your data with controls for
    %   playback speed and looping.
    %
    %   ANIMATORAPP Properties:
    %       hAxes - handle to the animation axes
    %       hLine - array of handles to the graphics objects created
    %
    %   APP = ANIMATORAPP opens an animation viewer. There needs to be some
    %   valid data in the workspace. See next section for information on the
    %   valid data types. APP is an object representing the Application. It has
    %   two public properties, which are handles to the graphics. These can be
    %   used to do programmatic customizations to the plots.
    %
    %   APP = ANIMATORAPP(X, Y) animates the data. The data has to be in one of the
    %   following formats. The general form is a 3-D array. 1st dimension is
    %   the number of elements in a signal (m). 2nd dimension is the number of
    %   lines (n). 3rd dimension is the number of frames (p).
    %     1. X can be either an m by 1 (2D) array or an m by n by p (3D) array.
    %         If 2D, all frames will use the same X vector
    %
    %     2. X - m by 1
    %        Y - m by n by p
    %          (This is for animating n lines, with a single X vector for ALL of
    %          p frames)
    %
    %     3. X - m by 1 by p  OR  m by n by p
    %        Y - m by n by p
    %          (This is for animating n lines, with a fixed X vector for EACH
    %          of the p frames OR X-Y pairs per frame)
    %
    %     4. X - [] (empty)
    %        Y - m by n by p
    %          (Y will be animated against it's index 1:m)
    %
    %   APP = ANIMATORAPP(X, Y, PARAM1, VALUE1, ...) accepts additional arguments:
    %     'axis'     : {'auto'}, 'equal
    %     'xlim'     : 'auto', [XMIN, XMAX]. Default uses the full range
    %     'ylim'     : 'auto', [YMIN, YMAX]. Default uses the full range
    %     'title'    : <title text>
    %     'xlabel'   : <xlabel text>
    %     'ylabel'   : <ylabel text>
    %     'smooth'   : {'off'}, 'on'. Anti-aliasing
    %     'frame'    : {1}. Starting frame number
    %     'speed'    : {9}. Integer between -10 and 10. 10 is fastest, -10 is
    %                  fastest in the reverse obj.direction.
    %     'framerate': {1}, 2, 3, 5, 10. Animate every # frames.
    %
    % GUI Features:
    %   The controls allows you to speed up and slow down (or reverse) the
    %   playback. You can pause at any time. You can also drag the time line
    %   bar to go to arbitrary frames. Also, use the arrow keys to move between
    %   frames (left or right) or change the speed (up or down). Spacebar
    %   pauses/starts the animation. In addition to the animation speed, the
    %   animation frame interval rate can be set from the menu.
    %
    %   The graphics properties can be customized via a context menu on the
    %   objects. Right-click on the plotted lines to bring up the context menu.
    %
    %   The animation can be exported to an AVI (R2010b or newer) or an
    %   Animated GIF. The Animated GIF option requires the Image Processing
    %   Toolbox (if R2008b or older), for converting RGB to Indexed data.
    %
    %  Example 1:
    %     x = (0:.01:10)';
    %     y = nan(length(x), 2, 400);
    %     for idx = 1:400;
    %       y(:, 1, idx) = sin(2*x) + cos(0.25*sqrt(idx)*x);
    %       y(:, 2, idx) = -cos(0.7*x) + sin(0.4*sqrt(idx)*x);
    %     end
    %     animatorApp(x, y);
    %
    %  Example 2: Programmatic customization
    %     load animatorSampleData;
    %
    %     % Vibrating string
    %     app = animatorApp(X1,Y1,'ylim',[-.7 .7],'title','Vibrating String','smooth','on');
    %     set(app.hLine, 'marker', 'o');
    %
    %     % Two double-pendulum
    %     app = animatorApp(X2,Y2,'axis','equal','title','Double Double-Pendulum');
    %     set(app.hLine, 'LineWidth', 3, 'Marker', '.', 'MarkerSize', 20);
    %     set(app.hAxes, 'XGrid', 'on', 'YGrid', 'on');
    %
    % See also animator.
    
    % Versions:
    %   v1.0 - Original version (Aug, 2007)
    %   v1.1 - Added option for specifying initial frame and animation speed
    %   v2.0 - Added exporting option (AVI or Animated GIF) (Nov, 2007)
    %   v2.1 - Added settings dialog for AVI and Animated GIF (Nov, 2007)
    %   v2.3 - Refactor functions (Nov, 2007)
    %   v2.4 - Changed graphics to Painters. Some graphics card has problems (Oct 2008)
    %   v2.5 - Changed back to OpenGL.
    %   v3.0 - Converted to object oriented code and added the ability to load
    %          different data sets (Aug 2012)
    %
    %   Jiro Doke
    %   Copyright 2007-2012 The MathWorks, Inc.
    
    properties (Dependent)
        hAxes
        hLine
    end
    
    properties (Hidden, Access = protected)
        appVer
        
        MainAxes
        
        handles
        
        x
        y
        sigType
        numFrames
        
        aspectRatio
        xlimit
        ylimit
        titleText
        xlabelText
        ylabelText
        isSmooth
        frm
        RRateID
        frameInterval
        
        direction
        curRateID
        RRates
        isLoop = true
        isPlaying = false
        isSaving = false
        ptrID = 3
        
        tm
    end
    
    properties (Constant, Hidden, Access = protected)
        ptrTypes = {'hand', 'lrdrag', 'arrow'}
        cm =  [.3, .3, .3; .9, .9, .9; .2, .6, .2; .8,  1, .8; .2, .2, .6; .8, .8,  1];
        cc1a = [1 2 2 1]  % patch color for PAUSED
        cc1b = [3 4 4 3]  % patch color for PLAYING
    end
    
    methods
        function obj = animatorApp(x, y, varargin)
            
            
            obj.appVer = '3.0';
            
            if nargin > 0
                obj.x = x;
                obj.y = y;
                
                % Error checking
                errorcheck(obj);
            end
            
            
            % Check for additional input arguments
            if nargin < 3
                args = {};
            else
                if mod(nargin-2,2)
                    error('Additional arguments must be Param/Value pairs');
                end
                args = varargin;
            end
            processArguments(obj, args);
            
            % Define other constants
            obj.RRates      = [.01, .02, .05, .075, .1, .2, .5, .75, 1, 2];
            obj.RRates      = [-obj.RRates,0,obj.RRates(end:-1:1)]; % pre-defined refresh rates
            obj.direction   = sign(obj.RRates(obj.RRateID));        % forward(+) or reverse(-)
            obj.curRateID   = obj.RRateID;                      % for keeping track of current rate
            
            % Create main GUI
            createGUI(obj);
            
            % Create timer object for animation
            obj.tm = timer(...
                'ExecutionMode'       , 'fixedrate', ...
                'ObjectVisibility'    , 'off', ...
                'Tag'                 , 'animationTimer', ...
                'StartFcn'            , @obj.mystartFcn, ...
                'StopFcn'             , @obj.mystopFcn, ...
                'TimerFcn'            , @obj.timerUpdateFcn);
            if obj.RRateID == 11
                set(obj.tm, 'Period', 0.01           );
            else
                set(obj.tm, 'Period', obj.RRates(obj.RRateID));
            end
            
            % Start animation timer if data exists
            if ~isempty(obj.x) && ~isempty(obj.y)
                start(obj.tm);
            end
            
        end %animator
        
        function out = get.hAxes(obj)
            out = obj.MainAxes;
        end
        
        function out = get.hLine(obj)
            out = obj.handles.Lines;
        end
    end
    
    methods (Hidden, Access = protected)
        %--------------------------------------------------------------------------
        % Function for creating the GUI
        function createGUI(obj)
            
            % Create figure
            hFig = figure(...
                'Name'                            , 'ANIMATOR', ...
                'Numbertitle'                     , 'off', ...
                'Units'                           , 'pixels', ...
                'DockControls'                    , 'on', ...
                'Toolbar'                         , 'none', ...
                'Menubar'                         , 'none', ...
                'Color'                           , [.8 .8 .8], ...
                'Colormap'                        , obj.cm, ...
                'DoubleBuffer'                    , 'on', ...
                'Renderer'                        , 'OpenGL', ...
                'ResizeFcn'                       , @obj.figResizeFcn, ...
                'KeyPressFcn'                     , @obj.keypressFcn, ...
                'DeleteFcn'                       , @obj.figDeleteFcn, ...
                'Visible'                         , 'off', ...
                'HandleVisibility'                , 'callback', ...
                'WindowButtonMotionFcn'           , @obj.motionFcn, ...
                'Tag'                             , 'MainFigure', ...
                'defaultUIControlBackgroundColor' , [.8 .8 .8], ...
                'defaultAxesUnits'                , 'pixels', ...
                'defaultPatchEdgeColor'           , 'none', ...
                'defaultUIPanelUnits'             , 'pixels');
            
            % Create menu
            hMenu0 = uimenu('Parent', hFig, 'Label', 'Data');
            uimenu('Parent', hMenu0, 'Label', 'Load...', 'Callback', @obj.loadDataFcn);
            
            allFrameRates  = [1 2 3 5 10];
            frameChecked   = {'off'; 'off'; 'off'; 'off'; 'off'};
            frameChecked{allFrameRates == obj.frameInterval} = 'on';
            hMenu1         = uimenu('Parent',hFig,  'Label','Extra'           );
            hMenu2         = uimenu('Parent',hMenu1,'Label','Animate every...');
            hFrameMenu2(1) = uimenu('Parent',hMenu2,'Label','1 frame'         );
            hFrameMenu2(2) = uimenu('Parent',hMenu2,'Label','2 frames'        );
            hFrameMenu2(3) = uimenu('Parent',hMenu2,'Label','3 frames'        );
            hFrameMenu2(4) = uimenu('Parent',hMenu2,'Label','5 frames'        );
            hFrameMenu2(5) = uimenu('Parent',hMenu2,'Label','10 frames'       );
            hMenu3         = uimenu('Parent',hMenu1,'Label','Export to...'    );
            hFrameMenu3(1) = uimenu('Parent',hMenu3,'Label','AVI'             );
            hFrameMenu3(2) = uimenu('Parent',hMenu3,'Label','Animated GIF'    );
            set(hFrameMenu2, 'Callback' , @obj.changeStepRate );
            set(hFrameMenu2, 'Tag'      , 'FrameRateMenu' );
            set(hFrameMenu2, {'Checked'}, frameChecked    );
            set(hFrameMenu3, 'Callback' , @obj.exportAnimation);
            uimenu('Parent', hMenu1, 'Label', 'Edit title/labels...', 'Callback', @obj.labelCallback                   );
            uimenu('Parent', hMenu1, 'Label', 'Smooth lines'        , 'Callback', @obj.smoothLineCallback, 'Tag', 'SmoothLineMenu');
            uimenu('Parent', hMenu1, 'Label', 'Help...'             , 'Callback', @obj.helpFcn      , 'Separator', 'on');
            uimenu('Parent', hMenu1, 'Label', 'About...'            , 'Callback', @obj.aboutFcn                        );
            
            % UI Context Menu for plot customization
            hContext = uicontextmenu('Parent', hFig, 'Tag', 'ContextMenu');
            hCLineStyle = uimenu('Parent', hContext, 'Label', 'Line Style');
            hContextMenuItems(1) = uimenu('Parent', hCLineStyle, 'Label', '-');
            hContextMenuItems(2) = uimenu('Parent', hCLineStyle, 'Label', '--');
            hContextMenuItems(3) = uimenu('Parent', hCLineStyle, 'Label', ':');
            hContextMenuItems(4) = uimenu('Parent', hCLineStyle, 'Label', '-.');
            hCLineWidthMenu = uimenu('Parent', hContext, 'Label', 'Line Width');
            hContextMenuItems(5) = uimenu('Parent', hCLineWidthMenu, 'Label', '0.5');
            hContextMenuItems(6) = uimenu('Parent', hCLineWidthMenu, 'Label', '1');
            hContextMenuItems(7) = uimenu('Parent', hCLineWidthMenu, 'Label', '2');
            hContextMenuItems(8) = uimenu('Parent', hCLineWidthMenu, 'Label', '3');
            hContextMenuItems(9) = uimenu('Parent', hCLineWidthMenu, 'Label', '4');
            hContextMenuItems(10) = uimenu('Parent', hCLineWidthMenu, 'Label', '6');
            hCLineColorMenu = uimenu('Parent', hContext, 'Label', 'Line Color');
            hContextMenuItems(11) = uimenu('Parent', hCLineColorMenu, 'Label', '0, 0, 1', 'ForegroundColor', [0, 0, 1]);
            hContextMenuItems(12) = uimenu('Parent', hCLineColorMenu, 'Label', '0, 0.5, 0', 'ForegroundColor', [0, 0.5, 0]);
            hContextMenuItems(13) = uimenu('Parent', hCLineColorMenu, 'Label', '1, 0, 0', 'ForegroundColor', [1, 0, 0]);
            hContextMenuItems(14) = uimenu('Parent', hCLineColorMenu, 'Label', '0, .75, .75', 'ForegroundColor', [0, .75, .75]);
            hContextMenuItems(15) = uimenu('Parent', hCLineColorMenu, 'Label', '.75, 0, .75', 'ForegroundColor', [.75, 0, .75]);
            hContextMenuItems(16) = uimenu('Parent', hCLineColorMenu, 'Label', '.75, .75, 0', 'ForegroundColor', [.75, .75, 0]);
            hContextMenuItems(17) = uimenu('Parent', hCLineColorMenu, 'Label', '.25, .25, .25', 'ForegroundColor', [.25, .25, .25]);
            hCMarkerMenu = uimenu('Parent', hContext, 'Label', 'Marker');
            hContextMenuItems(18) = uimenu('Parent', hCMarkerMenu, 'Label', '.');
            hContextMenuItems(19) = uimenu('Parent', hCMarkerMenu, 'Label', 'o');
            hContextMenuItems(20) = uimenu('Parent', hCMarkerMenu, 'Label', 'x');
            hContextMenuItems(21) = uimenu('Parent', hCMarkerMenu, 'Label', '+');
            hContextMenuItems(22) = uimenu('Parent', hCMarkerMenu, 'Label', '*');
            hContextMenuItems(23) = uimenu('Parent', hCMarkerMenu, 'Label', 's');
            hContextMenuItems(24) = uimenu('Parent', hCMarkerMenu, 'Label', 'd');
            hContextMenuItems(25) = uimenu('Parent', hCMarkerMenu, 'Label', 'v');
            hContextMenuItems(26) = uimenu('Parent', hCMarkerMenu, 'Label', '^');
            hContextMenuItems(27) = uimenu('Parent', hCMarkerMenu, 'Label', '<');
            hContextMenuItems(28) = uimenu('Parent', hCMarkerMenu, 'Label', '>');
            hContextMenuItems(29) = uimenu('Parent', hCMarkerMenu, 'Label', 'p');
            hContextMenuItems(30) = uimenu('Parent', hCMarkerMenu, 'Label', 'h');
            hCMarkerSizeMenu = uimenu('Parent', hContext, 'Label', 'Marker Size');
            hContextMenuItems(31) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '5');
            hContextMenuItems(32) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '8');
            hContextMenuItems(33) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '10');
            hContextMenuItems(34) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '12');
            hContextMenuItems(35) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '15');
            hContextMenuItems(36) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '20');
            hContextMenuItems(37) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '25');
            hContextMenuItems(38) = uimenu('Parent', hCMarkerSizeMenu, 'Label', '30');
            
            set(hContextMenuItems, 'Callback', @obj.contextmenuFcn);
            
            % Create main axes
            obj.MainAxes = axes('Parent', hFig);
            
            % Control panel
            hPanel = uipanel(...
                'Parent'          , hFig              , ...
                'BackgroundColor' , [.8 .8 .8]        , ...
                'Tag'             , 'ControlPanelAxes', ...
                'BorderType'      , 'etchedin'        );
            
            % Time line bar
            hAx = axes(...
                'Parent'          , hPanel        , ...
                'Visible'         , 'off'         , ...
                'Tag'             , 'TimelineAxes', ...
                'XLim'            , [0 1]         , ...
                'YLim'            , [-1 1]        );
            patch([0, 0, 1, 1], [-.5, .5, .5, -.5], [.9 .9 .9], ...
                'Tag'             , 'timeLine', ...
                'ButtonDownFcn'   , @obj.initiateDragFcn, ...
                'Parent'          , hAx);
            patch([0, 0, 0, 0], [-.5, .5, .5, -.5], obj.cc1b, ...
                'HitTest'         , 'off', ...
                'CDataMapping'    , 'direct', ...
                'FaceColor'       , 'interp', ...
                'Tag'             , 'TimelinePatch', ...
                'Parent'          , hAx);
            
            % Message text boxes
            uicontrol(...
                'Parent'              , hPanel, ...
                'style'               , 'text', ...
                'Tag'                 , 'FrameText', ...
                'HorizontalAlignment' , 'left', ...
                'FontWeight'          , 'Bold');
            uicontrol(...
                'Parent'              , hPanel, ...
                'style'               , 'text', ...
                'Tag'                 , 'StatusText', ...
                'HorizontalAlignment'	, 'right');
            
            % Playback controls
            hAx2 = axes(...
                'Parent'              , hPanel, ...
                'Visible'             , 'off', ...
                'XLim'                , [ 0 1], ...
                'YLim'                , [-1 1], ...
                'Tag'                 , 'PlaybackAxes');
            patch([0, 0, 1, 1], [-.5, .5, .5, -.5], [5 6 6 5], ...
                'CDataMapping'        , 'direct', ...
                'FaceColor'           , 'interp', ...
                'Parent'              , hAx2);
            line(...
                [  0  0 NaN .25 .25 NaN   .5  .5 NaN .75 .75 NaN .99 .99], ...
                [-.3 .3 NaN -.3  .3 NaN -.75 .75 NaN -.3  .3 NaN -.3  .3], ...
                'Parent', hAx2);
            line([(obj.RRateID-1)/20, (obj.RRateID-1)/20], [-.9, .9], ...
                'LineWidth'           , 4, ...
                'Color'               , [.3 .3 .3], ...
                'Tag'                 , 'speedBar', ...
                'ButtonDownFcn'       , @obj.initiateDragFcn,...
                'Parent'              , hAx2);
            
            % Load icons for controls
            icons = load(fullfile(fileparts(mfilename), 'animatorIcons.mat'));
            
            % Create playback control buttons from the icons
            hBtn(1) = axes(...
                'Parent'              , hPanel    , ...
                'Tag'                 , 'slowerAxes');
            hBtn(2) = axes(...
                'Parent'              , hPanel    , ...
                'Tag'                 , 'fasterAxes');
            hBtn(3) = axes(...
                'Parent'              , hPanel    , ...
                'Tag'                 , 'pauseAxes' );
            hBtn(4) = axes(...
                'Parent'              , hPanel    , ...
                'Tag'                 , 'repeatAxes');
            image(icons.rewindCData, ...
                'Parent'              , hBtn(1), ...
                'AlphaData'           , icons.rewindAlpha, ...
                'Tag'                 , 'slowerImage', ...
                'ButtonDownFcn'       , @obj.speedBtnFcn);
            image(icons.fastforwardCData, ...
                'Parent'              , hBtn(2), ...
                'AlphaData'           , icons.fastforwardAlpha, ...
                'Tag'                 , 'fasterImage', ...
                'ButtonDownFcn'       , @obj.speedBtnFcn);
            image(icons.pauseCData, ...
                'Parent'              , hBtn(3), ...
                'AlphaData'           , icons.pauseAlpha, ...
                'Tag'                 , 'pauseImage', ...
                'ButtonDownFcn'       , @obj.speedBtnFcn);
            image(icons.repeatCData, ...
                'Parent'              , hBtn(4), ...
                'AlphaData'           , icons.repeatAlpha, ...
                'Tag'                 , 'repeatImage', ...
                'ButtonDownFcn'       , @obj.speedBtnFcn);
            axis(hBtn, 'off', 'image');
            set(hBtn, {'Tag'}, {'slowerAxes'; 'fasterAxes'; 'pauseAxes'; 'repeatAxes'});
            
            obj.handles = guihandles(hFig);
            obj.handles.icons = icons;
            
            % Load and prep data
            if isempty(obj.x) && isempty(obj.y)
                loadDataFcn(obj);
                
                % If still no data
                if isempty(obj.x) && isempty(obj.y)
                    obj.RRateID = 11;
                end
            else
                hL = setupData(obj);
                obj.handles.Lines = hL;
            end
            
            % Process additional settings (user-specified)
            if strcmp(obj.aspectRatio, 'equal')             % Aspect ratio
                set(obj.MainAxes, 'DataAspectRatio', [1 1 1]);
            elseif strcmp(obj.aspectRatio, 'auto')
                set(obj.MainAxes, 'DataAspectRatioMode', 'auto');
            end
            if ~isempty(obj.xlimit)                         % X-limits
                if strcmpi(obj.xlimit, 'auto')
                    set(obj.MainAxes, 'XLimMode', 'auto');
                else
                    set(obj.MainAxes, 'XLim', obj.xlimit);
                end
            end
            if ~isempty(obj.ylimit)                         % Y-limits
                if strcmpi(obj.ylimit, 'auto')
                    set(obj.MainAxes, 'YLimMode', 'auto');
                else
                    set(obj.MainAxes, 'YLim', obj.ylimit);
                end
            end
            if ~isempty(obj.titleText)                      % Axes title
                title(obj.MainAxes, obj.titleText);
            end
            if ~isempty(obj.xlabelText)                     % Axes x-label
                xlabel(obj.MainAxes, obj.xlabelText);
            end
            if ~isempty(obj.ylabelText)                     % Axes y-label
                ylabel(obj.MainAxes, obj.ylabelText);
            end
            
            set(obj.MainAxes, 'XColor', [.5 .5 .5], 'YColor', [.5 .5 .5]);
            if strcmp(obj.isSmooth, 'on')
                try
                    set(obj.handles.SmoothLineMenu, 'Checked', 'on');
                    set(hL, 'LineSmoothing', obj.isSmooth);
                catch %#ok<CTCH>
                    set(obj.handles.SmoothLineMenu, 'Checked', 'off');
                    obj.isSmooth = 'off';
                    disp('Anti-aliasing not supported.');
                end
            end
            
            % Make axes invisible from outside
            set(findobj(hFig, 'type', 'axes'), 'HandleVisibility', 'callback');
            
            movegui(hFig, 'center');
            
            set(hFig, 'Visible' , 'on');
            
        end %createGUI
        
        %--------------------------------------------------------------------------
        % Called when the "Smooth Line" menu item is selected
        function smoothLineCallback(obj, varargin)
            
            if strcmp(obj.isSmooth, 'on')
                smoothOnOff(obj, 'off');
            else
                smoothOnOff(obj, 'on');
            end
            
        end %smoothLineCallback
        
        %--------------------------------------------------------------------------
        % Called to turn smoothing on or off
        function smoothOnOff(obj, onoff)
            if strcmp(onoff, 'on')
                try
                    set(obj.hLine, 'LineSmoothing', 'on');
                    set(obj.handles.SmoothLineMenu, 'Checked', 'on');
                    obj.isSmooth = 'on';
                catch %#ok<CTCH>
                    set(obj.handles.SmoothLineMenu, 'Checked', 'off');
                    obj.isSmooth = 'off';
                end
            else
                set(obj.hLine, 'LineSmoothing', 'off');
                set(obj.handles.SmoothLineMenu, 'Checked', 'off');
                obj.isSmooth = 'off';
            end
        end
        
        %--------------------------------------------------------------------------
        % Called when the context menu on a graphics object is selected
        function contextmenuFcn(obj, hObj, varargin) %#ok<INUSL>
            
            val = get(hObj, 'Label');
            switch get(get(hObj, 'Parent'), 'Label')
                case 'Line Style'
                    set(gco, 'LineStyle', val);
                case 'Line Width'
                    set(gco, 'LineWidth', str2double(val));
                case 'Line Color'
                    set(gco, 'Color', str2num(val)); %#ok<ST2NM>
                case 'Marker'
                    set(gco, 'Marker', val);
                case 'Marker Size'
                    set(gco, 'MarkerSize', str2double(val));
            end
            
        end %contextmenuFcn
        
        %--------------------------------------------------------------------------
        % Called when the "Load Data" menu item is selected
        function loadDataFcn(obj, varargin)
            
            % Pause if current animation is playing
            if obj.isPlaying
                obj.curRateID = obj.RRateID;
                pauseAnimation(obj);
            end
            
            % Save original data in case of error
            origX = obj.x;
            origY = obj.y;
            
            % Bring up dialog to select variables from base workspace
            [obj.x, obj.y] = loadVars();
            
            % If nothing is selected, rever to original
            if isempty(obj.x) && isempty(obj.y)
                obj.x = origX;
                obj.y = origY;
                return;
            end
            
            try
                % Error checking
                errorcheck(obj);
            catch ME
                errordlg(ME.message, 'Error', 'modal');
                obj.x = origX;
                obj.y = origY;
                return;
            end
            
            hL = setupData(obj);
            obj.handles.Lines = hL;
            updateLines(obj);
            
            smoothOnOff(obj, obj.isSmooth)
            
        end %loadDataFcn
        
        %--------------------------------------------------------------------------
        % Called when a new data is loaded to create initial plot
        function hL = setupData(obj)
            
            if isfield(obj.handles, 'Lines') && ~isempty(obj.handles.Lines) && all(ishandle(obj.handles.Lines))
                delete(obj.handles.Lines);
            end
            
            % Go to frame 1
            obj.frm = 1;
            
            % Create plot
            switch obj.sigType
                case 1 % x: m by 1, y: m by 1 by p
                    hL = plot(obj.MainAxes, obj.x, obj.y(:, :, obj.frm), 'Tag', 'Lines');
                    xlim(obj.MainAxes, [min(obj.x), max(obj.x)]);
                    ylim(obj.MainAxes, [min(obj.y(:)), max(obj.y(:))]);
                case 2 % x: m by 1, y: m by n by p
                    hL = plot(obj.MainAxes, obj.x, cell2mat(obj.y(:,:,obj.frm)), 'Tag', 'Lines');
                    xlim(obj.MainAxes, [min(obj.x(:)), max(obj.x(:))]);
                    ylim(obj.MainAxes, [min(min(cell2mat(squeeze(obj.y)))), max(max(cell2mat(squeeze(obj.y))))]);
                case 3 % x: m by n by p, y: m by n by p
                    hL = plot(obj.MainAxes, cell2mat(obj.x(:,:,obj.frm)), cell2mat(obj.y(:,:,obj.frm)), 'Tag', 'Lines');
                    xlim(obj.MainAxes, [min(min(cell2mat(squeeze(obj.x)))), max(max(cell2mat(squeeze(obj.x))))]);
                    ylim(obj.MainAxes, [min(min(cell2mat(squeeze(obj.y)))), max(max(cell2mat(squeeze(obj.y))))]);
            end
            
            % Attach context menu to the plots
            set(hL, 'UIContextMenu', obj.handles.ContextMenu);
            
        end %setupData
        
        %--------------------------------------------------------------------------
        % Called when "Edit title/labels..." menu is selected
        function labelCallback(obj, varargin)
            
            answer = inputdlg({'Title:', 'X-Label:', 'Y-Label:'}, ...
                'Enter labels', 1, {get(get(obj.MainAxes, 'Title'), 'String'), ...
                get(get(obj.MainAxes, 'XLabel'), 'String'), ...
                get(get(obj.MainAxes, 'YLabel'), 'String')});
            
            if ~isempty(answer)
                title(obj.MainAxes , answer{1});
                xlabel(obj.MainAxes, answer{2});
                ylabel(obj.MainAxes, answer{3});
            end
            
        end %labelCallback
        
        %--------------------------------------------------------------------------
        % Called when "Help..." menu is selected
        function helpFcn(obj, varargin)  %#ok<INUSD>
            
            helpdlg({'Use the lower right controls to change the refresh rate.', ...
                'Individual frames can be viewed interactively by', ...
                'clicking and dragging on the time line bar.', '', ...
                'The following key controls are available:', ...
                '  Right/Left arrows: step through frames one by one', ...
                '  Up/Down arrows: increase/decrease refresh rate', ...
                '  Spacebar: toggle between run/pause'}, 'Help for Animator');
            
        end %helpFcn
        
        %--------------------------------------------------------------------------
        % Called when "About..." menu is selected
        function aboutFcn(obj, varargin)
            
            helpdlg({'Animator is a tool for visualizing animation data stored in array formats.','','Author:', ...
                '  Jiro Doke (jiro.doke@mathworks.com)', ...
                '  Copyright 2007-2010 The MathWorks, Inc.'}, ...
                sprintf('About Animator v%s', obj.appVer));
            
        end %aboutFcn
        
        %--------------------------------------------------------------------------
        % Called when changing the number of animation frame interval
        function changeStepRate(obj, varargin)
            
            obj.frameInterval = str2double(strtok(get(varargin{1}, 'Label')));
            set(obj.handles.FrameRateMenu  , 'Checked', 'off');
            set(varargin{1}       , 'Checked', 'on' );
            
        end %changeStepRate
        
        %--------------------------------------------------------------------------
        % Called when exporting animation to AVI or Animated GIF
        function exportAnimation(obj, varargin)
            
            % If the animation is playing, pause it.
            if obj.isPlaying
                obj.curRateID = obj.RRateID;
                pauseAnimation(obj);
            end
            
            switch get(varargin{1}, 'Label')
                case 'AVI'
                    
                    if verLessThan('matlab', '7.11')
                        errordlg('Creating AVI files only supported in R2010b or newer.', 'Error', 'modal');
                        return;
                    end
                    
                    isAVI = true;
                case 'Animated GIF'
                    isAVI = false;
            end
            
            if isAVI
                
                frameRate       = abs(1/(obj.RRates(obj.curRateID)));
                settings = aviSettings(frameRate, obj.frameInterval);
                
                if isempty(settings)  % Cancelled
                    return
                end
                
                vid = VideoWriter(settings.filename, settings.Profile);
                
                propNames = fieldnames(settings.WriteableProperties);
                for iProp = 1:length(propNames)
                    vid.(propNames{iProp}) = settings.WriteableProperties.(propNames{iProp});
                end
                
                open(vid);
                
                % Change the title of the figure window
                set(obj.handles.MainFigure, 'Name', 'ANIMATOR - Creating AVI file (press ''q'' to cancel)');
                
            else
                
                frameDelay = abs(obj.RRates(obj.curRateID));
                settings = gifSettings(frameDelay, obj.frameInterval);
                
                if isempty(settings)  % Cancelled
                    return
                end
                
                % Change the title of the figure window
                set(obj.handles.MainFigure, 'Name', 'ANIMATOR - Creating Animated GIF file (press ''q'' to cancel)');
                
            end
            
            % Turn on Saving mode
            obj.isSaving = true;
            
            % Get the current figure position - for GETFRAME (see below)
            set(obj.handles.MainFigure, 'Units', 'pixels');
            figPos = get(obj.handles.MainFigure, 'Position');
            
            % Start from the first frame
            obj.frm = 1;
            updateLines(obj);
            f = getframe(obj.handles.MainFigure, [5, 55, figPos(3)-10, figPos(4)-55]);
            if isAVI
                writeVideo(vid, f);
            else
                try
                    [indexCData, cMap] = rgb2ind(f.cdata, double(intmax(class(f.cdata)))+1);
                catch %#ok<CTCH>
                    errordlg('Requires Image Processing Toolbox...');
                    return;
                end
                try
                    imwrite(indexCData, cMap, settings.filename, ...
                        'WriteMode'     , 'overwrite'             , ...
                        'LoopCount'     , settings.Loop           , ...
                        'DisposalMethod', settings.DisposalMethod , ...
                        'DelayTime'     , settings.FrameDelay     );
                catch %#ok<CTCH>
                    errordlg('Writing to GIF files is not supported in this release of MATLAB');
                    return;
                end
            end
            
            % Loop through all frames until finished
            while true
                obj.frm = obj.frm + settings.FrameInterval;
                if obj.frm > obj.numFrames  % processed all frames
                    obj.frm = obj.numFrames;
                    break;
                end
                if ~obj.isSaving        % 'q' was pressed by the user
                    break;
                end
                updateLines(obj);
                f = getframe(obj.handles.MainFigure, [5, 55, figPos(3)-10, figPos(4)-55]);
                if isAVI
                    writeVideo(vid, f);
                else
                    [indexCData, cMap] = rgb2ind(f.cdata, double(intmax(class(f.cdata)))+1);
                    imwrite(indexCData, cMap, settings.filename, ...
                        'WriteMode'     , 'append'                , ...
                        'DisposalMethod', settings.DisposalMethod , ...
                        'DelayTime'     , settings.FrameDelay     );
                end
            end
            
            if isAVI
                % Save AVI file
                close(vid);
            end
            
            if ~obj.isSaving
                msgbox('Cancelled');
                delete(fname);
            else
                msgbox('Done');
            end
            
            obj.isSaving = false;
            set(obj.handles.MainFigure, 'Name', 'ANIMATOR - Paused');
            
        end %exportAnimation
        
        %--------------------------------------------------------------------------
        % Called when a key is pressed
        function keypressFcn(obj, varargin)
            
            k = get(obj.handles.MainFigure, 'CurrentKey');
            
            switch k
                case 'rightarrow'     % go to the next frame
                    pauseAnimation(obj);
                    obj.frm = obj.frm + 1;
                    if obj.isLoop && obj.frm > obj.numFrames
                        obj.frm = 1;
                    else
                        obj.frm = min([obj.numFrames, obj.frm]);
                    end
                    updateLines(obj);
                    
                case 'leftarrow'      % go to the previous frame
                    pauseAnimation(obj);
                    obj.frm = obj.frm - 1;
                    if obj.isLoop && obj.frm < 1
                        obj.frm = obj.numFrames;
                    else
                        obj.frm = max([1, obj.frm]);
                    end
                    updateLines(obj);
                    
                case 'uparrow'        % speed up the refresh rate
                    obj.RRateID = min([length(obj.RRates), obj.RRateID + 1]);
                    updateRefreshRate(obj);
                    
                case 'downarrow'      % slow down the refresh rate
                    obj.RRateID = max([1, obj.RRateID - 1]);
                    updateRefreshRate(obj);
                    
                case 'space'          % toggle play/pause
                    if obj.isPlaying
                        obj.curRateID = obj.RRateID;
                        pauseAnimation(obj);
                    else
                        obj.RRateID = obj.curRateID;
                        start(obj.tm);
                    end
                    
                case 'q'              % cancel exporting of animation
                    obj.isSaving = false;
                    
            end
            
        end %keypressFcn
        
        %--------------------------------------------------------------------------
        % Called when pause button is pressed
        function pauseAnimation(obj)
            
            obj.RRateID = 11; % this corresponds to zero refresh rate
            set(obj.handles.speedBar, 'XData', [0.5, 0.5]);
            stop(obj.tm);
            
        end %pauseAnimation
        
        %--------------------------------------------------------------------------
        % Called whenever the figure is resized to reposition the components
        % "nicely"
        function figResizeFcn(obj, varargin)
            
            set(obj.handles.MainFigure, 'Units', 'pixels');
            figPos   = get(obj.handles.MainFigure, 'Position');
            panelPos = [2, 2, figPos(3)-4, 50];
            
            set(obj.MainAxes                 , 'Position', [65, 100, figPos(3)-90, figPos(4)-130]);
            set(obj.handles.ControlPanelAxes , 'Position', panelPos);
            set(obj.handles.TimelineAxes     , 'Position', [5, panelPos(4)-25, panelPos(3)-130, 20]);
            set(obj.handles.PlaybackAxes     , 'Position', [panelPos(3)-80, 25, 70, panelPos(4)-30]);
            set(obj.handles.FrameText        , 'Position', [5, 2, (panelPos(3)-130)/2, panelPos(4)-35]);
            set(obj.handles.StatusText       , 'Position', [5+(panelPos(3)-130)/2, 2, (panelPos(3)-130)/2, panelPos(4)-35]);
            set(obj.handles.slowerAxes       , 'Position', [panelPos(3)-80 (panelPos(4)-36)/2 16 16]);
            set(obj.handles.fasterAxes       , 'Position', [panelPos(3)-24 (panelPos(4)-36)/2 16 16]);
            set(obj.handles.pauseAxes        , 'Position', [panelPos(3)-52 (panelPos(4)-36)/2 16 16]);
            set(obj.handles.repeatAxes       , 'Position', [panelPos(3)-120 panelPos(4)-23 16 16]);
            
        end %figResizeFcn
        
        %--------------------------------------------------------------------------
        % Called when the time line bar or the speed bar is clicked
        function initiateDragFcn(obj, varargin)
            
            switch get(varargin{1}, 'Tag')
                case 'speedBar'     % speed control is clicked
                    stop(obj.tm);
                    set(obj.handles.MainFigure   , 'WindowButtonMotionFcn', @obj.speedFcn);
                    setptr(obj.handles.MainFigure, 'closedhand');
                case 'timeLine'     % time line bar is clicked
                    pauseAnimation(obj);
                    set(obj.handles.MainFigure, 'WindowButtonMotionFcn', @obj.timelineFcn);
                    timelineFcn(obj);
            end
            set(obj.handles.MainFigure, 'WindowButtonUpFcn', {@obj.stopDragFcn, get(varargin{1}, 'Tag')});
            
        end %initiateDragFcn
        
        %--------------------------------------------------------------------------
        % Called when the speed control is being dragged
        function speedFcn(obj, varargin)
            
            pt = get(obj.handles.PlaybackAxes, 'CurrentPoint');
            xVal = max([0, min([1, pt(1)])]);
            set(obj.handles.speedBar, 'XData', [xVal, xVal]);
            
            obj.RRateID = round(xVal*20+1);
            
        end %speedFcn
        
        %--------------------------------------------------------------------------
        % Called when time line bar is being dragged
        function timelineFcn(obj, varargin)
            
            pt = get(obj.handles.TimelineAxes, 'CurrentPoint');
            xVal = max([0, min([1, pt(1)])]);
            
            obj.frm = max([1, round(obj.numFrames * xVal)]);
            updateLines(obj);
            
        end %timelineFcn
        
        %--------------------------------------------------------------------------
        % Called when the click-n-drag has completed
        function stopDragFcn(obj, varargin)
            
            set(obj.handles.MainFigure, 'WindowButtonMotionFcn', @obj.motionFcn);
            set(obj.handles.MainFigure, 'WindowButtonUpFcn'    , ''        );
            
            if strcmp(varargin{3}, 'speedBar')
                updateRefreshRate(obj);
                setptr(obj.handles.MainFigure, 'hand');
            end
            
        end %stopDragFcn
        
        %--------------------------------------------------------------------------
        % Sets the cursor pointer and status text based on pointer location
        function motionFcn(obj, varargin)
            
            loc = get([obj.handles.TimelineAxes, obj.handles.PlaybackAxes, obj.handles.slowerAxes, obj.handles.fasterAxes, obj.handles.pauseAxes, obj.handles.repeatAxes], 'CurrentPoint');
            if isInRect(loc{2}(1,1:2), ...  % mouse over speed control
                    [(obj.RRateID - 1)/20 - 0.05, (obj.RRateID - 1)/20 + 0.05, -0.9, 0.9])
                if obj.ptrID ~= 1
                    obj.ptrID = 1;
                    setptr(obj.handles.MainFigure, obj.ptrTypes{obj.ptrID});
                    set(obj.handles.StatusText, 'String', 'Drag to change speed');
                end
                
            elseif isInRect(loc{1}(1,1:2), [0, 1, -0.8, 0.8]) % mouse over time line bar
                if obj.ptrID ~=2
                    obj.ptrID = 2;
                    setptr(obj.handles.MainFigure, obj.ptrTypes{obj.ptrID});
                    set(obj.handles.StatusText, 'String', 'Drag to select frame');
                end
                
            elseif isInRect(loc{3}(1,1:2), [1, 16, 1, 16]) % mouse over "slower" icon
                set(obj.handles.StatusText, 'String', 'Slower');
                
            elseif isInRect(loc{4}(1,1:2), [1, 16, 1, 16]) % mouse over "faster" icon
                set(obj.handles.StatusText, 'String', 'Faster');
                
            elseif isInRect(loc{5}(1,1:2), [1, 16, 1, 16]) % mouse over pause/play
                if obj.isPlaying
                    set(obj.handles.StatusText, 'String', 'Pause');
                else
                    set(obj.handles.StatusText, 'String', 'Play');
                end
                
            elseif isInRect(loc{6}(1,1:2), [1, 16, 1, 16]) % mouse over loop
                if obj.isLoop
                    set(obj.handles.StatusText, 'String', 'Turn off Loop');
                else
                    set(obj.handles.StatusText, 'String', 'Turn on Loop');
                end
                
            else                                          % everywhere else
                if obj.ptrID ~= 3
                    obj.ptrID = 3;
                    setptr(obj.handles.MainFigure, obj.ptrTypes{obj.ptrID});
                end
                set(obj.handles.StatusText, 'String', '');
            end
            
            %--------------------------------------------------------------------------
            % Helper function for "motionFcn" - Checks to see if pointer is in a rectangular region
            function out = isInRect(xy, rect)
                
                out = xy(1) > rect(1) && xy(1) < rect(2) && ...
                    xy(2) > rect(3) && xy(2) < rect(4);
                
            end %isInRect
            
        end %motionFcn
        
        %--------------------------------------------------------------------------
        % Updates the timer refresh rate
        function updateRefreshRate(obj)
            
            stop(obj.tm);
            obj.direction = sign(obj.RRates(obj.RRateID));
            if obj.RRateID ~= 11
                obj.curRateID = obj.RRateID;  % store current refresh rate
                obj.tm.Period = abs(obj.RRates(obj.RRateID));
            end
            start(obj.tm);
            
        end %updateRefreshRate
        
        %--------------------------------------------------------------------------
        % Called when the control buttons are pressed
        function speedBtnFcn(obj, varargin)
            
            switch get(varargin{1}, 'Tag')
                case 'slowerImage'
                    obj.RRateID = max([1, obj.RRateID - 1]);
                    updateRefreshRate(obj);
                    
                case 'fasterImage'
                    obj.RRateID = min([length(obj.RRates), obj.RRateID + 1]);
                    updateRefreshRate(obj);
                    
                case 'pauseImage'
                    if obj.isPlaying
                        obj.curRateID = obj.RRateID;
                        pauseAnimation(obj);
                        set(obj.handles.StatusText, 'String', 'Play');
                    else
                        obj.RRateID = obj.curRateID;
                        start(obj.tm);
                        set(obj.handles.StatusText, 'String', 'Pause');
                    end
                    
                case 'repeatImage'
                    if obj.isLoop
                        set(obj.handles.repeatImage, 'cdata', obj.handles.icons.norepeatCData, 'alphadata', obj.handles.icons.norepeatAlpha);
                        set(obj.handles.StatusText, 'String', 'Turn on Loop');
                    else
                        set(obj.handles.repeatImage, 'cdata', obj.handles.icons.repeatCData, 'alphadata', obj.handles.icons.repeatAlpha);
                        set(obj.handles.StatusText, 'String', 'Turn off Loop');
                    end
                    obj.isLoop = ~obj.isLoop;
                    
                    return;
                    
            end
            
        end %speedBtnFcn
        
        %--------------------------------------------------------------------------
        % Called at every timer period
        function timerUpdateFcn(obj, varargin)
            %disp('timer update');
            
            if isempty(obj.x) && isempty(obj.y)
                return;
            end
            
            % get the new frame
            obj.frm = max([0, min([obj.numFrames + 1, obj.frm + obj.frameInterval * obj.direction])]);
            
            % if obj.isLoop, then loop back if at the end (or first) frame
            if obj.isLoop
                if obj.frm < 1
                    obj.frm = obj.numFrames;
                elseif obj.frm > obj.numFrames
                    obj.frm = 1;
                end
            end
            
            % stop if end or speed is zero
            if obj.frm < 1 || obj.frm > obj.numFrames || obj.RRateID == 11
                stop(obj.tm);
                obj.frm = max([1, min([obj.numFrames, obj.frm])]);
            end
            
            updateLines(obj);
            
        end %timerUpdateFcn
        
        %--------------------------------------------------------------------------
        % Start function for the timer
        function mystartFcn(obj, varargin)
            
            % Change time line bar to green
            set(obj.handles.TimelinePatch, 'CData', obj.cc1b);
            obj.direction = sign(obj.RRates(obj.RRateID));
            if obj.direction > 0
                dd = 'Forward';
            else
                dd = 'Reverse';
            end
            pp = abs(obj.RRates(obj.RRateID));
            set(obj.handles.MainFigure, 'Name', sprintf('ANIMATOR - Refresh Rate: %g sec, %s', pp, dd));
            set(obj.handles.speedBar, 'XData', [(obj.RRateID - 1)/20, (obj.RRateID - 1)/20]);
            obj.isPlaying = true;
            set(obj.handles.pauseImage, 'CData', obj.handles.icons.pauseCData, 'alphadata', obj.handles.icons.pauseAlpha);
            %set(obj.handles.StatusText, 'String', 'Pause');
            
        end %mystartFcn
        
        %--------------------------------------------------------------------------
        % Stop function for the timer
        function mystopFcn(obj, varargin)
            
            % Change time line bar to black
            set(obj.handles.TimelinePatch, 'CData', obj.cc1a);
            set(obj.handles.MainFigure, 'Name', 'ANIMATOR - Paused');
            obj.isPlaying = false;
            set(obj.handles.pauseImage, 'CData', obj.handles.icons.playCData, 'alphadata', obj.handles.icons.playAlpha);
            %set(obj.handles.StatusText, 'String', 'Play');
            
        end %mystopFcn
        
        %--------------------------------------------------------------------------
        % Updates lines based on the signal type
        function updateLines(obj)
            
            switch obj.sigType
                case 1
                    set(obj.handles.Lines,  'YData'          , obj.y(:, 1, obj.frm)                  );
                case 2
                    set(obj.handles.Lines, {'YData'}         , obj.y(:, :, obj.frm)'                 );
                case 3
                    set(obj.handles.Lines, {'XData', 'YData'}, [obj.x(:, :, obj.frm)', obj.y(:, :, obj.frm)']);
            end
            
            % Update time line slider bar
            set(obj.handles.TimelinePatch , 'XData', [0, 0, obj.frm/obj.numFrames, obj.frm/obj.numFrames]);
            
            % Update frame number text
            set(obj.handles.FrameText, 'String', sprintf('Frame: %d/%d', obj.frm, obj.numFrames));
            
        end %updateLines
        
        %--------------------------------------------------------------------------
        % Called when figure is closed
        function figDeleteFcn(obj, varargin)
            
            % stop and delete timer object
            stop(obj.tm);
            delete(obj.tm);
            
        end %figDeleteFcn
        
        %--------------------------------------------------------------------------
        % Defines constants/default values and process input arguments
        function processArguments(obj, args)
            
            obj.aspectRatio = 'auto';                       % aspectratio is AUTO
            obj.xlimit      = [];                           % no specified xlim
            obj.ylimit      = [];                           % no specified ylim
            obj.titleText   = '';                           % no specified title
            obj.xlabelText  = '';                           % no specified xlabel
            obj.ylabelText  = '';                           % no specified ylabel
            obj.isSmooth    = 'off';                        % no anti-aliasing
            obj.frm         = 1;                            % start at frame 1
            obj.RRateID     = 20;                           % default refresh rate index
            obj.frameInterval   = 1;                            % animate every frame
            
            % Check for additional input arguments
            for iVars = 1:length(args)/2
                val = args{iVars*2};
                switch lower(args{iVars*2-1})
                    case 'axis'
                        if ischar(val) && ismember(val, {'auto', 'equal'})
                            obj.aspectRatio = val;
                            continue;
                        end
                    case 'xlim'
                        if isnumeric(val) && isequal(size(val), [1 2])
                            obj.xlimit      = val;
                            continue;
                        end
                    case 'ylim'
                        if isnumeric(val) && isequal(size(val), [1 2])
                            obj.ylimit      = val;
                            continue;
                        end
                    case 'title'
                        if ischar(val)
                            obj.titleText   = val;
                            continue;
                        end
                    case 'xlabel'
                        if ischar(val)
                            obj.xlabelText  = val;
                            continue;
                        end
                    case 'ylabel'
                        if ischar(val)
                            obj.ylabelText  = val;
                            continue;
                        end
                    case 'smooth'
                        if ischar(val) && ismember(val, {'on', 'off'})
                            obj.isSmooth    = val;
                            continue;
                        end
                    case 'frame'
                        if isnumeric(val) && isequal(size(val), [1 1]) && val >=1 && val <= obj.numFrames
                            obj.frm         = round(val);
                            continue;
                        end
                    case 'speed'
                        if isnumeric(val) && isequal(size(val), [1 1]) && val >=-10 && val <= 10
                            obj.RRateID     = round(val + 11);
                            continue;
                        end
                    case 'framerate'
                        if isnumeric(val) && isequal(size(val), [1 1]) && ismember(val, [1 2 3 5 10])
                            obj.frameInterval   = val;
                            continue;
                        end
                    otherwise
                        error('Unknown argument: %s', args{iVars*2-1});
                end
                
                error('Invalid value for ''%s''', args{iVars*2-1});
                
            end
            
        end %processArguments
        
        %--------------------------------------------------------------------------
        % Error checking
        function errorcheck(obj)
            
            if ~isnumeric(obj.x) || ~isnumeric(obj.y)
                error('X and Y must be numeric arrays');
            end
            if ndims(obj.y) ~= 3
                error('Y must be a 3-D array of m (elements) - n (lines) - p (frames)');
            end
            if isempty(obj.x) % if empty, create an index vector
                obj.x = (1:size(obj.y, 1))';
            end
            
            if ndims(obj.x) == 2     %#ok<ISMAT>   % X is 2D
                if size(obj.x, 2) == 1                 % X is a column vector
                    if size(obj.x, 1) == size(obj.y, 1)      % X and Y has the same # of rows
                        if size(obj.y, 2) == 1             % Y is a column vector
                            obj.sigType = 1;
                        else % size(y, 2) > 2          % Y has multiple columns
                            obj.sigType = 2;
                            obj.y       = num2cell(obj.y, 1);
                        end
                        obj.numFrames = size(obj.y, 3);
                    else % size(x, 1) ~= size(y, 1)
                        error('X and Y must have the same number of rows');
                    end
                else % size(x, 2) > 1
                    error('If X is 2D, it must be a column vector');
                end
            elseif ndims(obj.x) == 3                % X is 3D
                if size(obj.x, 1) == size(obj.y, 1)       % X and Y has the same # of rows
                    if size(obj.x, 2) == 1 || size(obj.x, 2) == size(obj.y, 2)  % X has one column or same as Y
                        if size(obj.x, 3) == size(obj.y, 3)   % X and Y has the same # of frames
                            obj.sigType   = 3;
                            obj.numFrames = size(obj.y, 3);
                            obj.x         = num2cell(obj.x, 1);
                            obj.y         = num2cell(obj.y, 1);
                        else % size(x, 3) ~= size(y, 3)
                            error('X and Y must have the same number of frames (3rd dimension)');
                        end
                    else
                        error('X must have exactly one column OR the same number of column as Y');
                    end
                else % size(x, 1) ~= size(y, 1)
                    error('X and Y must have the same number of rows');
                end
            else % ndims(x) > 3
                error('X must be 2D or 3D');
            end
            
        end %errorcheck
        
        
    end
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Helper Functions

%--------------------------------------------------------------------------
% Dialog for loading variables from the base workspace
function [x, y] = loadVars()
% [x, y] = loadVars()
%
%   UI for selecting variables from the workspace.

vars = evalin('base', 'whos');

xID = arrayfun(@(x) (numel(x.size)==2 && x.size(2)==1) || numel(x.size)==3, vars);
yID = arrayfun(@(x) numel(x.size)==3, vars);

if nnz(yID) == 0
    errordlg('No data available in the base workspace', 'Error', 'modal');
    x = [];
    y = [];
    return;
end

xVars = [{'[ ]'}, {vars(xID).name}];
yVars = {vars(yID).name};

width = 50;
height = 10;

hFig = figure(...
    'Name', 'Load Data', ...
    'NumberTitle', 'off', ...
    'HandleVisibility', 'off', ...
    'Units', 'characters', ...
    'Position', [0 0 width height], ...
    'Toolbar', 'none', ...
    'Menu', 'none', ...
    'defaultUIControlUnits', 'characters', ...
    'defaultUIControlFontUnits', 'points', ...
    'defaultUIControlFontSize', 11, ...
    'Visible', 'off', ...
    'Tag', 'LoadDataDialog');

uicontrol(...
    'Parent', hFig, ...
    'Style', 'text', ...
    'String', 'X:', ...
    'FontWeight', 'bold', ...
    'BackgroundColor', get(hFig, 'Color'), ...
    'HorizontalAlignment', 'right', ...
    'Position', [1 height-3 15 1.5]);

hX = uicontrol(...
    'Parent', hFig, ...
    'Style', 'popupmenu', ...
    'String', xVars, ...
    'BackgroundColor', 'white', ...
    'HorizontalAlignment', 'right', ...
    'Position', [16.5 height-2.7 30 1.5]);

uicontrol(...
    'Parent', hFig, ...
    'Style', 'text', ...
    'String', 'Y:', ...
    'FontWeight', 'bold', ...
    'BackgroundColor', get(hFig, 'Color'), ...
    'HorizontalAlignment', 'right', ...
    'Position', [1 height-6 15 1.5]);

hY = uicontrol(...
    'Parent', hFig, ...
    'Style', 'popupmenu', ...
    'String', yVars, ...
    'BackgroundColor', 'white', ...
    'HorizontalAlignment', 'right', ...
    'Position', [16.5 height-5.7 30 1.5]);

uicontrol(...
    'Parent', hFig, ...
    'Style', 'pushbutton', ...
    'Position', [3 1 20 2], ...
    'String', 'Okay', ...
    'Callback', @acceptButtonFcn);
uicontrol(...
    'Parent', hFig, ...
    'Style', 'pushbutton', ...
    'Position', [27 1 20 2], ...
    'String', 'Cancel', ...
    'Callback', @cancelButtonFcn);

movegui(hFig, 'center');
set(hFig, 'Visible', 'on');
uiwait(hFig);

    function acceptButtonFcn(varargin)
        if get(hX, 'Value') == 1
            x = [];
        else
            x = evalin('base', xVars{get(hX, 'Value')});
        end
        y = evalin('base', yVars{get(hY, 'Value')});
        
        delete(hFig);
    end

    function cancelButtonFcn(varargin)
        x = [];
        y = [];
        
        delete(hFig);
    end

end


%--------------------------------------------------------------------------
% Dialog for specifying AVI recording settings
function out = aviSettings(frameRate, frameInterval)

% Get valid profiles for this machine
profiles = VideoWriter.getProfiles();

if nargin == 0
    frameRate = profiles(1).FrameRate;
    frameInterval = 1;
end

str = struct('Profile', {profiles.Name});

for iStr = 1:length(str)
    str(iStr).FrameInterval = frameInterval;
    str(iStr).WriteableProperties.FrameRate = frameRate;
    switch lower(str(iStr).Profile)
        case 'archival'
        case 'motion jpeg 2000'
            str(iStr).WriteableProperties.CompressionRatio = profiles(iStr).CompressionRatio;
        case {'motion jpeg avi', 'mpeg-4'}
            str(iStr).WriteableProperties.Quality = profiles(iStr).Quality;
        case 'uncompressed avi'
        otherwise
            error('Unknown profile name');
    end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%

profileNames = {str.Profile};

width = 70;
height = 15;
labelOffsetH = 1;
entryOffsetH = 31.5;
labelWidth = 30;
entryWidth = 35;
labelHeight = 1.7;
entryHeight = 1.7;
lineInterval = 3;

hOptions = [];
idx = find(strcmpi('Motion JPEG AVI', profileNames));

fh = figure(...
    'Units', 'characters', ...
    'Position', [0 0 width height], ...
    'Toolbar', 'none', ...
    'Menu', 'none', ...
    'Name', 'AVI Settings', ...
    'NumberTitle', 'off', ...
    'CloseRequestFcn', @cancelButtonFcn, ...
    'Resize', 'off', ...
    'defaultUIControlUnits', 'characters', ...
    'defaultUIControlFontUnits', 'points', ...
    'defaultUIControlFontSize', 11, ...
    'Visible', 'off');

uicontrol(...
    'Parent', fh, ...
    'Style', 'text', ...
    'Position', [labelOffsetH height-2 labelWidth labelHeight], ...
    'BackgroundColor', get(fh, 'Color'), ...
    'FontWeight', 'bold', ...
    'HorizontalAlignment', 'right', ...
    'String', 'Profile:');
uicontrol(...
    'Parent', fh, ...
    'Style', 'popupmenu', ...
    'Position', [entryOffsetH height-2 entryWidth entryHeight], ...
    'BackgroundColor', 'white', ...
    'Value', idx, ...
    'String', profileNames, ...
    'Callback', @changeCompressionMethod);

uicontrol(...
    'Parent', fh, ...
    'Style', 'text', ...
    'Position', [labelOffsetH height-2-lineInterval labelWidth labelHeight], ...
    'BackgroundColor', get(fh, 'Color'), ...
    'FontWeight', 'bold', ...
    'HorizontalAlignment', 'right', ...
    'String', 'Frame Interval:');
uicontrol(...
    'Parent', fh, ...
    'Style', 'edit', ...
    'Position', [entryOffsetH height-2-lineInterval entryWidth entryHeight], ...
    'BackgroundColor', 'white', ...
    'String', str(idx).FrameInterval, ...
    'Tooltip', 'Integer greater than 1', ...
    'Callback', {@editValidationFcn, 'FrameInterval'});

createAdditionalFields();

uicontrol(...
    'Parent', fh, ...
    'Style', 'pushbutton', ...
    'Position', [10 1 25 2], ...
    'String', 'Save...', ...
    'Callback', @acceptButtonFcn);
uicontrol(...
    'Parent', fh, ...
    'Style', 'pushbutton', ...
    'Position', [40 1 25 2], ...
    'String', 'Cancel', ...
    'Callback', @cancelButtonFcn);

movegui(fh, 'center');

set(fh, 'Visible', 'on');
uiwait(fh);

    function createAdditionalFields()
        delete(hOptions);
        hOptions = [];
        
        fields = fieldnames(str(idx).WriteableProperties);
        values = struct2cell(str(idx).WriteableProperties);
        
        for iOpt = 1:length(fields)
            switch fields{iOpt}
                case 'FrameRate'
                    tooltipstr = 'Frames per second';
                    textstr = 'Frame Rate:';
                case 'Quality'
                    tooltipstr = 'Integer between 0 and 100';
                    textstr = 'Quality:';
                case 'CompressionRatio'
                    tooltipstr = 'Integer greater than 1';
                    textstr = 'Compression Ratio:';
            end
            
            hOptions(iOpt*2 - 1) = uicontrol(...
                'Parent', fh, ...
                'Style', 'text', ...
                'Position', [labelOffsetH height-2-(iOpt+1)*lineInterval labelWidth labelHeight], ...
                'BackgroundColor', get(fh, 'Color'), ...
                'FontWeight', 'bold', ...
                'HorizontalAlignment', 'right', ...
                'String', textstr);
            hOptions(iOpt*2) = uicontrol(...
                'Parent', fh, ...
                'Style', 'edit', ...
                'Position', [entryOffsetH height-2-(iOpt+1)*lineInterval entryWidth entryHeight], ...
                'BackgroundColor', 'white', ...
                'String', values{iOpt}, ...
                'Tooltip', tooltipstr, ...
                'Callback', {@editValidationFcn, fields{iOpt}});
        end
        
    end

    function editValidationFcn(hObj, edata, propName) %#ok<INUSL>
        val = str2double(get(hObj, 'String'));
        switch propName
            case {'FrameRate', 'CompressionRatio'}
                try %#ok<TRYNC>
                    validateattributes(val, {'numeric'}, {'scalar', 'integer'});
                    assert(val >= 1);  % backwards compatibility for validateattributes
                    str(idx).WriteableProperties.(propName) = val;
                end
                set(hObj, 'String', str(idx).WriteableProperties.(propName));
            case 'Quality'
                try %#ok<TRYNC>
                    validateattributes(val, {'numeric'}, {'scalar', 'integer'});
                    assert(val >= 0 && val <= 100);  % backwards compatibility for validateattributes
                    str(idx).WriteableProperties.(propName) = val;
                end
                set(hObj, 'String', str(idx).WriteableProperties.(propName));
            case 'FrameInterval'
                try %#ok<TRYNC>
                    validateattributes(val, {'numeric'}, {'scalar', 'integer'});
                    assert(val >= 1);  % backwards compatibility for validateattributes
                    str(idx).(propName) = val;
                end
                set(hObj, 'String', str(idx).(propName));
        end
        
    end

    function changeCompressionMethod(hObj, edata) %#ok<INUSD>
        idx = get(hObj, 'Value');
        
        createAdditionalFields();
        
    end

    function acceptButtonFcn(varargin)
        
        switch lower(str(idx).Profile)
            case {'archival', 'motion jpeg 2000'}
                ext = '*.mj2';
            case {'motion jpeg avi', 'uncompressed avi'}
                ext = '*.avi';
            case 'mpeg-4'
                ext = '*.mp4;*.m4v';
            otherwise
                error('Unknown compression method');
        end
        
        [filename, pathname] = uiputfile(ext, 'Save as...');
        
        if isnumeric(filename)
            out = [];
        else
            out = str(idx);
            out.filename = fullfile(pathname, filename);
        end
        
        delete(fh);
        
    end

    function cancelButtonFcn(varargin)
        
        out = [];
        
        delete(fh);
        
    end

end


%--------------------------------------------------------------------------
% Dialog for specifying Animated GIF settings
function out = gifSettings(frameDelay, frameInterval)

disposalMethods = {'leaveInPlace', 'restoreBG', 'restorePrevious', 'doNotSpecify'};

out = struct('filename', '', 'FrameInterval', frameInterval, ...
    'FrameDelay', frameDelay, 'DisposalMethod', disposalMethods{1}, ...
    'Loop', inf);

width = 70;
height = 15;
labelOffsetH = 1;
entryOffsetH = 31.5;
labelWidth = 30;
entryWidth = 35;
labelHeight = 1.7;
entryHeight = 1.7;
lineInterval = 3;

fh = figure(...
    'Units', 'characters', ...
    'Position', [0 0 width height], ...
    'Toolbar', 'none', ...
    'Menu', 'none', ...
    'Name', 'GIF Settings', ...
    'NumberTitle', 'off', ...
    'CloseRequestFcn', @cancelButtonFcn, ...
    'Resize', 'off', ...
    'defaultUIControlUnits', 'characters', ...
    'defaultUIControlFontUnits', 'points', ...
    'defaultUIControlFontSize', 11, ...
    'Visible', 'off');

uicontrol(...
    'Parent', fh, ...
    'Style', 'text', ...
    'Position', [labelOffsetH height-2 labelWidth labelHeight], ...
    'BackgroundColor', get(fh, 'Color'), ...
    'FontWeight', 'bold', ...
    'HorizontalAlignment', 'right', ...
    'String', 'Frame Interval:');
uicontrol(...
    'Parent', fh, ...
    'Style', 'edit', ...
    'Position', [entryOffsetH height-2 entryWidth entryHeight], ...
    'BackgroundColor', 'white', ...
    'String', frameInterval, ...
    'Tooltip', 'Integer greater than 1', ...
    'Callback', {@editValidationFcn, 'FrameInterval'});

uicontrol(...
    'Parent', fh, ...
    'Style', 'text', ...
    'Position', [labelOffsetH height-2-lineInterval labelWidth labelHeight], ...
    'BackgroundColor', get(fh, 'Color'), ...
    'FontWeight', 'bold', ...
    'HorizontalAlignment', 'right', ...
    'String', 'Frame delay (sec):');
uicontrol(...
    'Parent', fh, ...
    'Style', 'edit', ...
    'Position', [entryOffsetH height-2-lineInterval entryWidth entryHeight], ...
    'BackgroundColor', 'white', ...
    'String', frameDelay, ...
    'Tooltip', 'Positive number', ...
    'Callback', {@editValidationFcn, 'FrameDelay'});

uicontrol(...
    'Parent', fh, ...
    'Style', 'text', ...
    'Position', [labelOffsetH height-2-2*lineInterval labelWidth labelHeight], ...
    'BackgroundColor', get(fh, 'Color'), ...
    'FontWeight', 'bold', ...
    'HorizontalAlignment', 'right', ...
    'String', 'Disposal Method:');
uicontrol(...
    'Parent', fh, ...
    'Style', 'popupmenu', ...
    'Position', [entryOffsetH height-2-2*lineInterval entryWidth entryHeight], ...
    'BackgroundColor', 'white', ...
    'String', disposalMethods, ...
    'Callback', @disposalMethodSelectionFcn);

uicontrol(...
    'Parent', fh, ...
    'Style', 'text', ...
    'Position', [labelOffsetH height-2-3*lineInterval labelWidth labelHeight], ...
    'BackgroundColor', get(fh, 'Color'), ...
    'FontWeight', 'bold', ...
    'HorizontalAlignment', 'right', ...
    'String', 'Loop:');
uicontrol(...
    'Parent', fh, ...
    'Style', 'checkbox', ...
    'Position', [entryOffsetH height-2-3*lineInterval entryWidth entryHeight], ...
    'BackgroundColor', get(fh, 'Color'), ...
    'Value', 1, ...
    'Callback', @loopFcn);

uicontrol(...
    'Parent', fh, ...
    'Style', 'pushbutton', ...
    'Position', [10 1 25 2], ...
    'String', 'Save...', ...
    'Callback', @acceptButtonFcn);
uicontrol(...
    'Parent', fh, ...
    'Style', 'pushbutton', ...
    'Position', [40 1 25 2], ...
    'String', 'Cancel', ...
    'Callback', @cancelButtonFcn);

movegui(fh, 'center');

set(fh, 'Visible', 'on');
uiwait(fh);

    function editValidationFcn(hObj, edata, propName) %#ok<INUSL>
        
        val = str2double(get(hObj, 'String'));
        switch propName
            case 'FrameDelay'
                try %#ok<TRYNC>
                    validateattributes(val, {'numeric'}, {'scalar'});
                    assert(val >= 0);  % backwards compatibility for validateattributes
                    out.FrameDelay = val;
                end
            case 'FrameInterval'
                try %#ok<TRYNC>
                    validateattributes(val, {'numeric'}, {'scalar', 'integer'});
                    assert(val >= 1);  % backwards compatibility for validateattributes
                    out.FrameInterval = val;
                end
        end
        set(hObj, 'String', out.(propName));
        
    end

    function disposalMethodSelectionFcn(hObj, edata) %#ok<INUSD>
        
        out.DisposalMethod = disposalMethods{get(hObj, 'Value')};
        
    end

    function loopFcn(hObj, edata) %#ok<INUSD>
        
        if get(hObj, 'Value')
            out.Loop = Inf;
        else
            out.Loop = 0;
        end
        
    end

    function acceptButtonFcn(varargin)
        
        [filename, pathname] = uiputfile('*.gif', 'Save as...');
        
        if isnumeric(filename)
            out = [];
        else
            out.filename = fullfile(pathname, filename);
        end
        
        delete(fh);
        
    end

    function cancelButtonFcn(varargin)
        
        out = [];
        
        delete(fh);
        
    end

end

Contact us