Code covered by the BSD License  

Highlights from
tmark.m

image thumbnail
from tmark.m by J. Caspar
Annotation class which generates peak marks that are automatically updated when axes are rescaled.

tmark
classdef tmark < hgsetget
%TMARK  Tickmark annotation class for 2-D plots.
%
%Usage:     t = tmark([x,y],varargin)
%           t = tmark(ginput(1),'color','r','label','Peak 1')
%
%Tmarks are user positioned annotation objects whos position's remain fixed
%in axes units when plot axes are rescaled (unlike standard annotation
%objects whos positions are not updated as a result of axis rescaling).
%Tmarks are intended to be used to label features like peak positions in
%spectroscopic data.  The functionality is like the "Pin to Axes" function
%except tmarks are available from the command line and in scripts.  Tmarks
%can be created either from the command line or interactively using
%tmulti.m.  Once created tmarks properties may be edited via either a
%context menu (right click on the mark) or via the commaqnd line using the
%utility routines tedit.m, tfind.m and trestore.m, or using the set
%formalism as for other types of handle object.
%
%Known Limitations:
%     Currently when a figure containing tmarks is saved to disk and then
%reloaded the tmarks become 'dormant' (i.e. they will no longer be
%repositioned as a result of axis rescaling).  The utility routine
%trestore.m will revive the tmarks subject to some limitations - in
%particular tmarks in figures containing more than one subplot may end up
%being attached to the wrong axis.  See the help info in trestore.m for
%details and a workaround for this problem.
%
%Example
%
% %Plot some data
%   x = 0:0.01:2*pi;
%   plot(x,sin(x))
%   ylim([-1.5 1.5])
% 
% %Add a tmark from the command line.
%
%   tm = tmark([pi,sin(pi)],'color','r','label','Tmark')
% 
% %Modify the settings for the mark.  Editing is also available by right
% %clicking on the mark with the mouse.
%
%   set(tm,'resolution',0.001)  %increase the resolution
%   set(tm,'textonly',1)    %display only the text label
% 
% %Create marks interactively on the current figure using ginput.  Left click
% %to create marks.  Press escape to exit.
%
%   tm = tmulti  %tm will be a cell array of tmark handles.
%
%End example
%
%
%Required Inputs
%===============
%   pos .................... [x,y] coordinate of mark in experimental units
%
%Optional Inputs
%===============
%   label .................. Text label to be displayed in addition to the
%                            x-coordinate of the mark.
%   resolution ............. The numeric resolution of the displayed
%                            position.  For examples, a resolution of 1
%                            will display the position rounded to the
%                            nearest integer, a value of 0.02 will give the
%                            position rounded to the nearest 0.02.  When
%                            tmarks are created they script picks the
%                            initial resolution based upon the xaxis range
%                            as reported by xlim.
%   textonly ............... If set to 1 only the label text is displayed
%                            (no x-axis value).  Default = 0 (label and
%                            x position displated).
%   color .................. The color of the mark.
%   fontsize ............... Font size for the mark.
%   hdlAx .................. The handle of theaxis to which the tick mark
%                            is attached.
%
%Outputs
%=======
%   TM ..................... Tickmark object (tmark)
%
%See also:  tedit   tfind   trestore    tmulti

% $Author: jcaspar $
% $Date: 2009/11/06 23:33:47 $
% $Revision: 1.6 $

    properties (Hidden)
        locked = 0; %used to prevent access of multiple listeners at same time
        listenerX   %handle of listener for x-axis change
        listenerY   %handle of listener for y-axis change
        string = ''
        arrowx
        arrowy
        linestyle = '-';
        textrotation = 90;
        verticalalignment = 'middle';
        horizontalalignment = 'center';
    end
    properties (SetAccess = private)
        hdl     %handle of annotation object
    end
    properties (SetAccess = public)
        hdlAx   %handle of axis to which mark is attached
        pos = [0,0]; %mark location in real x,y coordinates (from ginput)        
        label = '';   %label text
        color = [0,0,0];
        resolution = 1;
        textonly = 0;
        fontsize = 9;
    end
    
    events
    end
    
    methods
        
        %Class constructor
        function TM = tmark(pos,varargin)
            TM.hdlAx = [];
            if ~isempty(get(0,'currentfigure'))
                if ~isempty(get(get(0,'currentfigure'),'currentaxes'))
                    TM.hdlAx = gca;
                end
            end
            TM.pos = pos;
            
            %confirm active axis
            if ~isempty(varargin)
                for k=1:2:size(varargin,2)
                    switch lower(varargin{k})
                        case 'axis'
                            TM.hdlAx = varargin{k+1};
                    end
                end
            end

            if isempty(TM.hdlAx)
                %reloading data
                return
            end
            
            %figure out default resolution
            TM.resolution = 10.^floor(log10(diff(xlim(TM.hdlAx))/100));
            
            if ~isempty(varargin)
                for k=1:2:size(varargin,2)
                    switch lower(varargin{k})
                        case 'resolution'
                            TM.resolution = varargin{k+1};
                        case 'color'
                            TM.color = varargin{k+1};
                        case 'label'
                            TM.label = varargin{k+1};
                        case 'axis'
                            TM.hdlAx = varargin{k+1};
                        case 'textonly'
                            TM.textonly = varargin{k+1};
                            if TM.textonly == 1
                                TM.textrotation = 0;
                                TM.linestyle = 'none'
                            else
                                TM.textrotation = 90;
                                TM.linestyle = '-';
                            end
                        case 'fontsize'
                            TM.fontsize = varargin{k+1};
                    end
                end
            end

            %create uicontextmenu
            mnu = uicontextmenu;
            m1 = uimenu(mnu,'label','Resolution  ');
            m11 = uimenu(m1,'label','10',...
                'callback',@(src,evt)tmarkresolution(gco,10));
            m12 = uimenu(m1,'label','1',...
                'callback',@(src,evt)tmarkresolution(gco,1));
            m13 = uimenu(m1,'label','0.1',...
                'callback',@(src,evt)tmarkresolution(gco,0.1));
            m14 = uimenu(m1,'label','0.01',...
                'callback',@(src,evt)tmarkresolution(gco,0.01));
            m15 = uimenu(m1,'label','Other',...
                'callback',@(src,evt)tmarkresolution(gco));
                
            
            m2 = uimenu(mnu,'label','Color');
            m21 = uimenu(m2,'label','Black','foregroundcolor','k',...
                'callback',@(src,evt)tmarkcolor(gco,'k'));
            m22 = uimenu(m2,'label','Red','foregroundcolor','r',...
                'callback',@(src,evt)tmarkcolor(gco,'r'));
            m23 = uimenu(m2,'label','Green','foregroundcolor',[0,0.75,0],...
                'callback',@(src,evt)tmarkcolor(gco,[0,0.75,0]));
            m24 = uimenu(m2,'label','Blue','foregroundcolor','b',...
                'callback',@(src,evt)tmarkcolor(gco,'b'));
            m25 = uimenu(m2,'label','Cyan','foregroundcolor','c',...
                'callback',@(src,evt)tmarkcolor(gco,'c'));
            m26 = uimenu(m2,'label','Magenta','foregroundcolor','m',...
                'callback',@(src,evt)tmarkcolor(gco,'m'));
            m27 = uimenu(m2,'label','Yellow','foregroundcolor','y',...
                'callback',@(src,evt)tmarkcolor(gco,'y'));
            m28 = uimenu(m2,'label','Custom','foregroundcolor','k',...
                'callback',@(src,evt)tmarkcolor(gco,uisetcolor));
            
            m3 = uimenu(mnu,'label','Fontsize');
            m31 = uimenu(m3,'label',' 7',...
                'callback',@(src,evt)tmarkfontsize(gco,7));
            m32 = uimenu(m3,'label',' 9',...
                'callback',@(src,evt)tmarkfontsize(gco,9));
            m33 = uimenu(m3,'label','10',...
                'callback',@(src,evt)tmarkfontsize(gco,10));
            m34 = uimenu(m3,'label','12',...
                'callback',@(src,evt)tmarkfontsize(gco,12));
            m35 = uimenu(m3,'label','14',...
                'callback',@(src,evt)tmarkfontsize(gco,14));
            m36 = uimenu(m3,'label','16',...
                'callback',@(src,evt)tmarkfontsize(gco,16));
            
            m9 = uimenu(mnu,'label','Font...',...
                'callback',@(src,evt)myfont(gco));
            
            m4 = uimenu(mnu,'label','Label',...
                'callback',@(src,evt)tmarkrelabel(gco));
            
            m5 = uimenu(mnu,'label','Text only',...
                'callback',@(src,evt)tmarktxtonly(src,gco));
            
            m6 = uimenu(mnu,'label','Reposition');
            m61 = uimenu(m6,'label','Cursor',...
                'callback',@(src,evt)tmarkmove(gco,'cursor'));            
            m62 = uimenu(m6,'label','Numeric',...
                'callback',@(src,evt)tmarkmove(gco,'numeric'));
            
%             m7 = uimenu(mnu,'label','Restore  ','separator','on',...
%                 'callback',@(src,evt)mnurestore(gco));
            
            m8 = uimenu(mnu,'label','Delete','separator','on',...
                'callback',@(src,evt)delete(gco));
            
            
            TM = MakeArrows(TM);
            TM = MakeLabel(TM);
            TM.hdl = annotation(get(TM.hdlAx,'parent'),...
                'textarrow',TM.arrowx,TM.arrowy,...
                'string',TM.string,...
                'textrotation',TM.textrotation,...
                'linestyle',TM.linestyle,...
                'color',TM.color,...
                'fontsize',TM.fontsize,...
                'tag','tmark',...
                'horizontalalignment',TM.horizontalalignment,...
                'verticalalignment',TM.verticalalignment,...
                'headstyle','none',...
                'uicontextmenu',mnu);
            
            local_modifyuserdata(TM.hdl,'tmark',TM)   %store tmark for future access
            
            set(TM.hdl,'deleteFcn',@(src,evt)tmark.deleteMark(TM));

            TM.listenerX = addlistener(TM.hdlAx, 'XLim', 'PostSet',...
                @(src,evt)tmark.axisrescale(TM,src,evt));
            TM.listenerY = addlistener(TM.hdlAx, 'YLim', 'PostSet',...
                @(src,evt)tmark.axisrescale(TM,src,evt));
        end
        
        %Restore functionality of a tmark reloaded from disk
        function TM = tmarkrestore(TM,hdl,hdlAx)
            TM.hdl = hdl;
            TM.hdlAx = hdlAx;
            TM.listenerX = addlistener(TM.hdlAx, 'XLim', 'PostSet',...
                @(src,evt)tmark.axisrescale(TM,src,evt));
            TM.listenerY = addlistener(TM.hdlAx, 'YLim', 'PostSet',...
                @(src,evt)tmark.axisrescale(TM,src,evt));
        end
                
        %Set commands
        function TM = set.pos(TM,value)
            TM.pos = value;
            if ~isempty(TM.hdl)
                TM = MakeArrows(TM);
                set(TM.hdl,'x',TM.arrowx,'y',TM.arrowy);
                TM = MakeLabel(TM);
                 set(TM.hdl,'string',TM.string);
            end
        end
        function TM = set.hdlAx(TM,value)
            TM.hdlAx = value;
        end
        function TM = set.label(TM,value)
            TM.label = value;
            if ~isempty(TM.hdl)
                TM = MakeLabel(TM);
                set(TM.hdl,'string',TM.string)
            end
        end
        function TM = set.color(TM,value)
            TM.color = value;
            if ~isempty(TM.hdl)
                set(TM.hdl,'color',value,'textcolor',value)
            end
        end
        function TM = set.linestyle(TM,value)
            TM.linestyle = value;
            if ~isempty(TM.hdl)
                set(TM.hdl,'linestyle',value)
            end
        end
        function TM = set.horizontalalignment(TM,value)
            TM.horizontalalignment = value;
            if ~isempty(TM.hdl)
                set(TM.hdl,'horizontalalignment',value)
            end
        end            
        function TM = set.verticalalignment(TM,value)
            TM.verticalalignment = value;
            if ~isempty(TM.hdl)
                set(TM.hdl,'verticalalignment',value)
            end
        end            
        function TM = set.textrotation(TM,value)
            TM.textrotation = value;
            if ~isempty(TM.hdl)
                set(TM.hdl,'textrotation',value)
            end
        end            
        function TM = set.resolution(TM,value)
            TM.resolution = value;
            if ~isempty(TM.hdl)
                TM = MakeLabel(TM);
                set(TM.hdl,'string',TM.string);
            end
        end
        function TM = set.textonly(TM,value)
            TM.textonly = value;
            if value == 1
                TM.textrotation = 0;
                TM.linestyle = '-';
                TM.verticalalignment = 'bottom';
            else
                TM.textrotation = 90;
                TM.linestyle = '-';
                TM.verticalalignment = 'middle';
            end
            if ~isempty(TM.hdl)
                TM = MakeLabel(TM);
                set(TM.hdl,'string',TM.string);
            end
        end
        function TM = set.fontsize(TM,value)
            TM.fontsize = value;
            if ~isempty(TM.hdl)
                set(TM.hdl,'fontsize',TM.fontsize);
            end
        end
        function TM = set(TM,varargin)
            if ~isempty(varargin)
                for k=1:2:size(varargin,2)
                    switch lower(varargin{k})
                        case 'pos'
                            TM.pos = varargin{k+1};
                        case 'hdlax'
                            TM.hdlAx = varargin{k+1};
                            delete(TM.listenerX);
                            delete(TM.listenerY);
                            TM.listenerX = addlistener(TM.hdlAx, 'XLim', 'PostSet',...
                                @(src,evt)tmark.axisrescale(TM,src,evt));
                            TM.listenerY = addlistener(TM.hdlAx, 'YLim', 'PostSet',...
                                @(src,evt)tmark.axisrescale(TM,src,evt));
                        case 'label'
                            TM.label = varargin{k+1};
                            if ~isempty(TM.hdl)
                                set(TM.hdl,'string',TM.string)  %???
                            end
                        case 'color'
                            TM.color = varargin{k+1};
                        case 'resolution'
                            TM.resolution = varargin{k+1};
                        case 'textonly'
                            TM.textonly = varargin{k+1};
                        case 'fontsize'
                            TM.fontsize = varargin{k+1};
                    end
                end
            end
        end
        
        %Class destructor
        function delete(TM)
            try delete(TM.listenerX);catch;end
            try delete(TM.listenerY);catch;end
            try delete(TM.hdl);catch;end
        end
        
        function sobj = saveobj(obj)
            sobj.hdl = [];
            sobj.hdlAx = [];
            sobj.locked = 0;
            sobj.listenerX = [];
            sobj.listenerY = [];
            sobj.string = obj.string;
            sobj.arrowx = obj.arrowx;
            sobj.arrowy = obj.arrowy;
            sobj.linestyle = obj.linestyle;
            sobj.textrotation = obj.textrotation;
            sobj.pos = obj.pos;
            sobj.label = obj.label;
            sobj.color = obj.color;
            sobj.resolution = obj.resolution;
            sobj.textonly = obj.textonly;
            sobj.fontsize = obj.fontsize;
        end
            
    end
    
    methods (Static) 
        
        %Load tmark from disk.  Due to dependence of tmark on handles to
        %graphic objects, a reloaded tmark is not fully functional. Use
        %tmarkrestore (or utility trestore.m) to re-enable loaded tmarks.
        function sobj = loadobj(obj)
            sobj = tmark(obj.pos,'axis',[]);
            sobj.hdlAx = [];
            sobj.locked = 0;
            sobj.listenerX = [];
            sobj.listenerY = [];
            sobj.string = obj.string;
            sobj.arrowx = obj.arrowx;
            sobj.arrowy = obj.arrowy;
            sobj.linestyle = obj.linestyle;
            sobj.textrotation = obj.textrotation;
            sobj.label = obj.label;
            sobj.color = obj.color;
            sobj.resolution = obj.resolution;
            sobj.textonly = obj.textonly;
            sobj.fontsize = obj.fontsize;            
        end
        
        %relocate tmark as a result of axis rescaling
        function axisrescale(TM,src,evt) %#ok<INUSD>
            while TM.locked == 1
                %wait until object is free
            end
            TM.locked = 1; %lock it
            try
                TM = MakeArrows(TM);
                set(TM.hdl,'x',TM.arrowx,'y',TM.arrowy);
                TM.locked = 0;
            catch ME
                TM.locked = 0;
                getReport(ME)
                disp('error in axis rescale')
            end
        end
        
        %handle deletion when user deletes the annotation mark directly
        function deleteMark(TM)
            try delete(TM.listenerX);catch;end               
            try delete(TM.listenerY);catch;end
            try delete(TM);catch;end
        end 
                
    end
end
 
%===================================================================
%Utility functions
%===================================================================

%Convert experimental coords to annotation units
function TM = MakeArrows(TM)
    [ax,ay] = local_dsxy2figxy(TM.hdlAx, TM.pos(1), TM.pos(2));
    ax(2) = ax(1);
    ay(2) = ay(1) + 0.03;
    ay = fliplr(ay);
    TM.arrowx = ax;
    TM.arrowy = ay;
end

%Convert label to final string for display
function TM = MakeLabel(TM)            
    xtext = num2str(local_round2n(TM.pos(1),TM.resolution),'%g');           
    if isempty(TM.label)
        %create text for mark using only the peak position
        n = numel(xtext) + 2;
        s = char(ones(1,(2*n)+1)*32);%space padding
        TM.string = sprintf('%s%s',s,xtext);
    else
        %label includes additional text
        if TM.textonly == 0
            %text and peak position in label
            n = numel(TM.label) + numel(xtext) + 1;
            s = char(ones(1,(2*n) + 5)*32);%space padding
            TM.string = sprintf('%s%s (%s)',s,TM.label,xtext);        
        else
            %use labe only with no position info
            n = numel(TM.label) + numel(xtext) + 1;
            s = char(ones(1,2*n)*32);%space padding
            s = '';
            TM.string = sprintf('%s%s',s,TM.label);
        end
    end

end

%Edit the label via context menu
function tmarkrelabel(h)
    ud = get(h,'userdata');
    txtin = get(ud.tmark,'label');  
    prompt = {'Enter peakmark label:'};
    dlg_title = 'Re-Label Peakmark';
    num_lines = 1;
    def = {txtin};
    txtin = inputdlg(prompt,dlg_title,num_lines,def);
    if isempty(txtin)
        %cancel
        return
    end

    set(ud.tmark,'label',txtin{1});
end

%Edit the label position via context menu
function tmarkmove(h,mtd)
    ud = get(h,'userdata');
    switch mtd
        case 'cursor'
            [x,y] = ginput(1);
        case 'numeric'
            pos = get(ud.tmark,'pos');
            prompt = {'Enter X Position:','Enter Y Position:'};
            dlg_title = 'Relocate Peakmark';
            num_lines = 1;
            def = {num2str(pos(1)),num2str(pos(2))};
            answer = inputdlg(prompt,dlg_title,num_lines,def);
            if isempty(answer)
                %cancel
                return
            end            
            x = str2num(answer{1});
            y = str2num(answer{2});
    end
    set(ud.tmark,'pos',[x,y]);
end

%Restore functionality of a tmark in a figure reloaded from disk
function mnurestore(h)
%this routine is not active - too unreliable due to bug described below.
    l = legend;
    if ~isempty(l)
        %legend is on; legend interferes with restoration of tmarks because
        %for unknown reasons the legend becomes the gca.  Workaround is to
        %turn off the legend.  This trick does not work if there are
        %multiple subplots on the figure.
        legend('off');
    end
    ud = get(h,'userdata');
    hdlAx = get(gcf,'currentaxes');    
    tmarkrestore(ud.tmark,h,hdlAx);   
    if ~isempty(l)
        legend('show');
    end
end

%Edit the fontsize via context menu
function tmarkfontsize(h,fsize)
    ud = get(h,'userdata');
    set(ud.tmark,'fontsize',fsize);
end

%Edit the font settings via context menu
function myfont(h)
    ud = get(h,'userdata');
    f = uisetfont;
    if ~isstruct(f)
        return
    end
    set(h,'fontname',f.FontName);
    set(h,'fontweight',f.FontWeight);
    set(h,'fontangle',f.FontAngle);
    set(ud.tmark,'fontsize',f.FontSize);
end

%Edit the display resolution via context menu
function tmarkresolution(h,res)
    ud = get(h,'userdata');
    if ~ismember('res',who)
        %get value from user
        def = num2str(get(ud.tmark,'resolution'));  
        prompt = {'Enter resolution:'};
        dlg_title = 'Set Resolution';
        num_lines = 1;
        def = {def};
        answer = inputdlg(prompt,dlg_title,num_lines,def);
        if isempty(answer)
            %cancel
            return
        end
        res = str2num(answer{1});
    end    
    set(ud.tmark,'resolution',res);
end

%Edit the color via context menu
function tmarkcolor(h,markcolor)
    ud = get(h,'userdata');
    set(ud.tmark,'color',markcolor);
end

%Edit the text only property via context menu
function tmarktxtonly(src,h)
    ud = get(h,'userdata');
    c = get(src,'checked');
    if strcmp(c,'off')
        set(src,'checked','on');
        set(ud.tmark,'textonly',1);
    else
        set(src,'checked','off');
        set(ud.tmark,'textonly',0);
    end        
end

%Coordinate transform
function varargout = local_dsxy2figxy(varargin)
    % dsxy2figxy -- Transform point or position from axis to figure coords
    % Transforms [axx axy] or [xypos] from axes hAx (data) coords into coords
    % wrt GCF for placing annotation objects that use figure coords into data
    % space. The annotation objects this can be used for are
    %    arrow, doublearrow, textarrow
    %    ellipses (coordinates must be transformed to [x, y, width, height])
    % Note that line, text, and rectangle anno objects already are placed
    % on a plot using axes coordinates and must be located within an axes.
    % Usage: Compute a position and apply to an annotation, e.g.,
    %   [axx axy] = ginput(2);
    %   [figx figy] = getaxannopos(gca, axx, axy);
    %   har = annotation('textarrow',figx,figy);
    %   set(har,'String',['(' num2str(axx(2)) ',' num2str(axy(2)) ')'])
    %
    % Based on an old version of the dsxy2figxy.m which is a Matlab
    % example located at:
    %
    %       \Matlab\R209b\help\techdoc\creating_plots\examples\dsxy2figxy.m
    %
    % Code has been modified by JVC to correctly treat logarithmic x and y-axis
    % scales and x-axes plotted in reverse mode (i.e. high-to-low).  Never
    % got around to fixing reversed y-axes yet.

    % Obtain arguments (only limited argument checking is performed).
    % Determine if axes handle is specified
    if length(varargin{1})== 1 && ishandle(varargin{1}) && ...
      strcmp(get(varargin{1},'type'),'axes')	
        hAx = varargin{1};
        varargin = varargin(2:end);
    else
        hAx = gca;
    end;
    % Parse either a position vector or two 2-D point tuples
    if length(varargin) == 1	% Must be a 4-element POS vector
        pos = varargin{1};
    else
        [x,y] = deal(varargin{:});  % Two tuples (start & end points)
    end

    % Get limits
    axun = get(hAx,'Units');
    set(hAx,'Units','normalized');  % Need normaized units to do the xform
    axpos = get(hAx,'Position');    %left,bottom, width, height
    axlim = axis(hAx);              % Get the axis limits [xlim ylim (zlim)]
    axwidth = diff(axlim(1:2));
    axheight = diff(axlim(3:4));

    %Transform data from figure space to data space
    if exist('x','var')     % Transform a and return pair of points
        if strcmp(get(hAx,'xscale'),'linear')
            if strcmp(get(hAx,'xdir'),'reverse') 
                varargout{1} = (axlim(2) - x)*axpos(3)/axwidth + axpos(1)  
            else
                % ((x - xmin) * width) / (width + left)
                varargout{1} = (x - axlim(1))*axpos(3)/axwidth + axpos(1);
            end
        else
            varargout{1} = (log10(x/axlim(1)) / log10(axlim(2)/axlim(1))) * axpos(3) + axpos(1);
        end        
        if strcmp(get(hAx,'yscale'),'linear')
            varargout{2} = (y-axlim(3))*axpos(4)/axheight + axpos(2);
        else
            varargout{2} = (log10(y/axlim(3)) / log10(axlim(4)/axlim(3))) * axpos(4) + axpos(2);
        end
    else                    % Transform and return a position rectangle
        pos(1) = (pos(1)-axlim(1))/axwidth*axpos(3) + axpos(1);
        pos(2) = (pos(2)-axlim(3))/axheight*axpos(4) + axpos(2);
        pos(3) = pos(3)*axpos(3)/axwidth;
        pos(4) = pos(4)*axpos(4)/axheight;
        varargout{1} = pos;
    end

    % Restore axes units
    set(hAx,'Units',axun)
end

%Utility to get userdata from peak mark
function local_modifyuserdata(hdl,myfield, mydata)
    %modifyuserdata   Update userdata for an object without destroying existing contents
    %
    %Usage:  modifyuserdata(hdl,myfield, mydata)
    %        modifyuserdata(hdl,myfield,'-delete')
    %
    %Inputs
    %======
    %   hdl .................... Handle(s) for userdata
    %   myfield ................ Field to update.  If userdata contains the
    %                            named field its value is modified to the
    %                            specified value.  If the field does not exist
    %                            it is created and set to the specified value.
    %   mydata ................. Data to store.  If this argument is set to
    %                            '-delete' then the specified field is removed
    %                            from userdata.  Requesting deletion of a field
    %                            which does not exist will leave userdata
    %                            unchanged.
    %
    %Outputs
    %=======
    %   None

    % $Author: jcaspar $
    % $Date: 2009/11/06 23:33:47 $
    % $Revision: 1.6 $

    for k=1:numel(hdl)
        u = get(hdl(k),'userdata');
        if isfield(u,myfield)
            %existing field, need to update
            if strcmp(mydata,'-delete')
                %remove field
                u = rmfield(u,myfield);
            else
                u.(myfield) = mydata;
            end
        else
            %new field
            if strcmp(mydata,'-delete')
                %asking to delete field, so do not create it
            else
                u.(myfield) = mydata;
            end
        end
        set(hdl(k),'userdata',u)
    end
end

%Utility function to round to specified display resolution
function r = local_round2n(x,m)
    %round2n    Generalized rounding function.  Rounds to closest multiple of m.
    %
    %Usage:         r = round2n(x,m)
    %               r = round2n(x,3)    %round x to nearest multiple of 3
    %               r = round2n(x,0.05) %round to nearest multiple of 0.05
    %
    %Required Inputs
    %===============
    %   x ...................... Value to be rounded
    %   m ...................... Multiple to round to.  If m=1 or if m is
    %                            absent the function defaults to standard
    %                            rounding (round)
    %
    %Outputs
    %=======
    %   r ...................... Rounded output
    %
    %See also:  round

    % $Author: jcaspar $
    % $Date: 2009/11/06 23:33:47 $
    % $Revision: 1.6 $

    if nargin == 1
        %simple rounding
        r = round(x);
        return
    end

    r = m .* round(x ./ m);

end

Contact us at files@mathworks.com