Code covered by the BSD License  

Highlights from
OverlayTable

image thumbnail

OverlayTable

by

 

01 Aug 2012 (Updated )

Create table overlays for figures which include full text formatting and support for symbols.

OverlayTable(hfig,tableData,left,top,width,height,varargin)
function [h,hbox] = OverlayTable(hfig,tableData,left,top,width,height,varargin)
%OverlayTable   Make a fully formatable table displayed in a figure
%
%Usage:  [h,hbox] = OverlayTable(gcf,dataTable,left,top,width,height,varargin)
%        [h,hbox] = OverlayTable(gcf,dataTable)
%        [h,hbox] = OverlayTable(gcf,dataTable,[],[],[],[],'fontsize',12)
%
%Note:  The table is composed of annotation textboxes with one per cell in
%the table. Full formatting of the table including fonts, colors, symbols,
%superscripts and substripts is supported.    Right click on the table to
%display a context menu with extensive formatting and editing tools to
%modify the appearance of the table (see below).
%
%Acknowledgements
%OverlayTable requires the wonderfully useful "GUI Layout Toolbox" written
%by Ben Tordoff which is available from the MATLAB File Exchange.
%OverlayTable also relies on a modified version of dsxy2figxy.m which
%appears as an example in the MATLAB documentation.
%
%Inputs
%======
%   hfig ................... Handle of figure in which the table will be
%                            created.
%   tableData .............. Cell array of strings or scalars to appear in
%                            the table. The dimensions of the table (cells)
%                            will match the dimensions of cell array.
%   left ................... Left edge of table in normalized figure units
%                            (0 = left edge of figure).  If empty or absent
%                            the user is prompted to select upper left
%                            corner via the cursor.
%   top .................... Top edge of table (1 = top of figure)
%   width .................. Column widths in normalized units.  If width
%                            is a scalar then all columns have the same
%                            width; if a vector each column has specified
%                            width. If set to zero, empty or absent width
%                            for each column is determined automatically.
%   height ................. Column height.  If set to zero, empty or
%                            absent height for each row is set
%                            automatically.
%   varargin ............... Arguments passed directly to to annotation.
%                            Any valid property,value pair for a textbox
%                            annotation object may be included in this
%                            argument.
%
%Outputs
%=======
%   h ...................... Matrix of handles to text boxes (table cells).
%                            Each entry in h is a handle of an individual
%                            annotation textbox.  Settings in the table can
%                            be changed programatically using
%                            set(h,prop,value) or interactively via the
%                            context menu (see below).
%   hbox ................... Handle to the bounding box for the table.
%
%
%Context Menu Usage
%===================
%   Edit ................... Edit cell contents via OverlayTableEdit
%   Move Table ............. Move the table on the current figure.  User is
%                            presented with cursors which should be used to
%                            select the location of the upper left corner
%                            of the new table location.
%   Format
%       Table .............. Apply selected formatting operation to the
%                            entire table.
%           Font Size ...... Change the table font size
%           Font Weight .... Change the table font weight (bold/normal)
%           Font Color ..... Change the table font color
%           Font ........... Access to all font settings
%           Alignment ...... Set the horizontal or vertical alignment of
%                            the table entries.
%           All ............ Access to ALL table settings via the
%                            properties editor.  WARNING - you have access
%                            here to some settings that should not be
%                            changed.
%       Row ................ As above except settings are only applied to
%                            the currently selected row .
%       Column ............. Settings applied to the current column
%       Cell ............... Settings applied only to the selected cell
%
%   Insert ................. Insert a new column/row to the left/above the
%                            currently selected cell.
%   Append ................. Insert a new column/row to the right/below the
%                            currently selected cell.
%   Delete ................. Delete the selected column, row, or entire
%                            table.
%   Resize Now ............. Force an update to resize the table to match
%                            the cell contents.
%   Enable Auto-Size ....... If checked automatically update the table size
%                            when settings/contents are changed.  (Default
%                            = enabled).
%                            Notes on Auto-Size:
%                               1.  Changes to cell properties or contents
%                               that are initiated in the built-in property
%                               editor will not trigger an automatic resize.
%
%                               2.  In a complex table automatic resize can
%                               generate a lot of overhead especially when
%                               moving figure windows.  The resize function
%                               is triggered by a listener which is
%                               attached to the current window position.  I
%                               am not an expert in this - if anybody could
%                               tell me how to listen to a parameter which
%                               tracks only an actual resize of the window
%                               rather than just the window position
%                               performance of the auto-resize option could
%                               be dramatically improved.
%==========================================================================
%
%Example
%======
%figure
%plot(rand(1,10))
% dTable = {'Column 1','Column 2','Column 3';...
%     '300\circC','\Omega_0','Text Entry'};
%OverlayTable(gcf,dTable);  %User is prompted to use the cursors to place
%the table in the active figure.  Right click on the resulting table to
%further customize appearance or contents.
%
%See also: annotation  OverlayTableEdit

% Jon Caspar
% $Date: 2012/07/31 21:20:24 $
% $Revision: 1.6 $

    %% Initializing
    set(hfig,'Pointer','watch')
    drawnow
        
    [nrows,ncols] = size(tableData);

    if ~ismember('width',who)
        width = 0;
    end
    if ~ismember('height',who)
        height = 0;
    end
    if ~ismember('left',who)
        left = [];
    end
    if~ismember('top',who)
        top = [];
    end
    
    if numel(width) == 1
        width = ones(1,ncols) * width;
    end
    if numel(height) == 1
        height = ones(nrows,1) * height;
    end
        
    h = nan(nrows,ncols);

    %input checking
    if ~iscell(tableData)
        set(hfig,'Pointer','arrow')
        error('Table contents must be provided in a cell array')
    end
    if numel(width) > ncols
        set(hfig,'Pointer','arrow')
        error('Number of columns must match number of specified column widths');
    end
    if numel(height) > nrows
        set(hfig,'Pointer','arrow')
        error('Number of rows must match number of specified row heights');
    end

    if isempty(left) || isempty(top)
        %No position specified, get user to enter position with cursors
        figure(hfig)
        myhappysound
        [x,y] = ginput(1);
        [left,top] = mydsxy2figxy(x,y);
    end

    %% Build context menu
    mnu = uicontextmenu;
    uimenu('Parent',mnu,'Label','Edit','Callback',@EditCell)
    uimenu('Parent',mnu,'Label','Move','Callback',@MoveTable)
    
    uimenu('Parent',mnu,'Label','Format','Separator','on');
    mnuTable = uimenu('Parent',mnu,'Label','    Table...','Tag','Table');
    mnuRow = uimenu('Parent',mnu,'Label','    Row...','Tag','Row');
    mnuCol = uimenu('Parent',mnu,'Label','    Column...','Tag','Col');
    mnuCell = uimenu('Parent',mnu,'Label','    Cell...','Tag','Cell');
    mnuBox = uimenu('Parent',mnu,'Label','    Box...');
    
    mnuBoxBorder = uimenu('Parent',mnuBox,'Label','Border Width');
    uimenu('Parent',mnuBoxBorder,'Label','None',...
        'Callback',@(src,evt)BoxBorder(src,evt,0))
    uimenu('Parent',mnuBoxBorder,'Label','1',...
        'Callback',@(src,evt)BoxBorder(src,evt,1))
    uimenu('Parent',mnuBoxBorder,'Label','2',...
        'Callback',@(src,evt)BoxBorder(src,evt,2))
    uimenu('Parent',mnuBoxBorder,'Label','3',...
        'Callback',@(src,evt)BoxBorder(src,evt,3))
    uimenu('Parent',mnuBox,'Label','Format Box','Callback',@FormatBox);
    
    mnufontsize = uimenu('Parent',mnuTable,'Label','Font Size');
    uimenu('Parent',mnufontsize,'Label','8',...
        'Callback',@(src,evt)FontSize(src,evt,8))
    uimenu('Parent',mnufontsize,'Label','9',...
        'Callback',@(src,evt)FontSize(src,evt,9))
    uimenu('Parent',mnufontsize,'Label','10',...
        'Callback',@(src,evt)FontSize(src,evt,10))
    uimenu('Parent',mnufontsize,'Label','12',...
        'Callback',@(src,evt)FontSize(src,evt,12))
    uimenu('Parent',mnufontsize,'Label','14',...
        'Callback',@(src,evt)FontSize(src,evt,14))
    uimenu('Parent',mnufontsize,'Label','16',...
        'Callback',@(src,evt)FontSize(src,evt,16))

    mnufontweight = uimenu('Parent',mnuTable,'Label','Font Weight');
    uimenu('Parent',mnufontweight,'Label','Normal',...
        'Callback',@(src,evt)FontWeight(src,evt,'normal'))
    uimenu('Parent',mnufontweight,'Label','Bold',...
        'Callback',@(src,evt)FontWeight(src,evt,'bold'))

    mnufontcolor = uimenu('Parent',mnuTable,'Label','Font Color');
    uimenu(mnufontcolor,'Label','Black','ForegroundColor','k',...
        'Callback',@(src,evt)FontColor(src,gco,'k'));
    uimenu(mnufontcolor,'Label','Red','ForegroundColor','r',...
        'Callback',@(src,evt)FontColor(src,gco,'r'));
    uimenu(mnufontcolor,'Label','Green','ForegroundColor',[0,0.75,0],...
        'Callback',@(src,evt)FontColor(src,gco,[0,0.75,0]));
    uimenu(mnufontcolor,'Label','Blue','ForegroundColor','b',...
        'Callback',@(src,evt)FontColor(src,gco,'b'));
    uimenu(mnufontcolor,'Label','Cyan','ForegroundColor','c',...
        'Callback',@(src,evt)FontColor(src,gco,'c'));
    uimenu(mnufontcolor,'Label','Magenta','ForegroundColor','m',...
        'Callback',@(src,evt)FontColor(src,gco,'m'));
    uimenu(mnufontcolor,'Label','Yellow','ForegroundColor','y',...
        'Callback',@(src,evt)FontColor(src,gco,'y'));
    uimenu(mnufontcolor,'Label','Custom','ForegroundColor','k',...
        'Callback',@(src,evt)FontColor(src,gco,uisetcolor));

    uimenu('Parent',mnuTable,'Label','Font...','Callback',@ModifyFont)
    
    mnuAlignment = uimenu('Parent',mnuTable,'Label','Alignment');
    mnuHoriz = uimenu('Parent',mnuAlignment,'Label','Horizontal');
    uimenu('Parent',mnuHoriz,'Label','Left',...
        'Callback',@(src,evt)HorizAlign(src,evt,'left'))
    uimenu('Parent',mnuHoriz,'Label','Center',...
        'Callback',@(src,evt)HorizAlign(src,evt,'center'))
    uimenu('Parent',mnuHoriz,'Label','Right',...
        'Callback',@(src,evt)HorizAlign(src,evt,'right'))
    
    mnuVert = uimenu('Parent',mnuAlignment,'Label','Vertical');
    uimenu('Parent',mnuVert,'Label','Top',...
        'Callback',@(src,evt)VertAlign(src,evt,'top'))
    uimenu('Parent',mnuVert,'Label','Cap',...
        'Callback',@(src,evt)VertAlign(src,evt,'cap'))
    uimenu('Parent',mnuVert,'Label','Middle',...
        'Callback',@(src,evt)VertAlign(src,evt,'middle'))
    uimenu('Parent',mnuVert,'Label','Baseline',...
        'Callback',@(src,evt)VertAlign(src,evt,'baseline'))
    uimenu('Parent',mnuVert,'Label','Bottom',...
        'Callback',@(src,evt)VertAlign(src,evt,'bottom'))
    
    uimenu('Parent',mnuTable,'Label','All...','CallBack',@FormatTable)
    
    copyobj(mnufontsize,[mnuRow,mnuCell,mnuCol]);    
    copyobj(mnufontweight,[mnuRow,mnuCell,mnuCol]);
    copyobj(mnufontcolor,[mnuRow,mnuCell,mnuCol]);
    
    uimenu('Parent',mnuRow,'Label','Font...','Callback',@ModifyFont)
    uimenu('Parent',mnuCell,'Label','Font...','Callback',@ModifyFont)
    uimenu('Parent',mnuCol,'Label','Font...','Callback',@ModifyFont)

    copyobj(mnuAlignment,[mnuRow,mnuCell,mnuCol]);

    uimenu('Parent',mnuRow,'Label','All...','CallBack',@FormatTable)
    uimenu('Parent',mnuCell,'Label','All...','CallBack',@FormatTable)
    uimenu('Parent',mnuCol,'Label','All...','CallBack',@FormatTable)
    
    mnuInsert = uimenu('Parent',mnu,'Label','Insert...','Separator','on');
        uimenu('Parent',mnuInsert,'Label','Row','Callback',@insertRow)
        uimenu('Parent',mnuInsert,'Label','Column','Callback',@insertColumn)

    mnuAppend = uimenu('Parent',mnu,'Label','Append...');
        uimenu('Parent',mnuAppend,'Label','Row','Callback',@appendRow)
        uimenu('Parent',mnuAppend,'Label','Column','Callback',@appendColumn)
        
    mnuDelete = uimenu('Parent',mnu,'Label','Delete...');
        uimenu('Parent',mnuDelete,'Label','Row','Callback',@deleteRow)
        uimenu('Parent',mnuDelete,'Label','Column','Callback',@deleteColumn)
        uimenu('Parent',mnuDelete,'Label','Table','Callback',@DeleteTable,...
            'Separator','on')

    uimenu('Parent',mnu,'Label','Resize Now','Callback',@AutoFitTableNow,...
        'Separator','on')
    hAutoEnable = uimenu('Parent',mnu,'Label','Enable Auto-Size',...
        'Checked','off','Callback',@AutoSizeEnable);
    
    %% Auto-size table
    if sum(width) == 0 || sum(height) == 0
        %Autoscale row height and column width
        [rowHeight,colWidth] = deal(zeros(size(tableData)));
        for j=1:nrows
            for k=1:ncols                
                if ischar(tableData{j,k})
                    str = tableData{j,k};
                elseif isnumeric(tableData{j,k})
                    if ~isempty(tableData{j,k})
                        str = num2str(tableData{j,k});
                    else
                        str = '-';
                    end
                else
                    %unsupported value type
                    str = '??';
                end
                htest = annotation('textbox',[0.1,0.9,0,0],...
                    'visible','off',...
                    'FitBoxtoText','on',...
                    'VerticalAlignment','middle',...
                    'string',str,...
                    varargin{:});
                p = get(htest,'Position');
                delete(htest)
                rowHeight(j,k) = p(4);
                colWidth(j,k) = p(3);
            end
        end
        for k=1:ncols
            width(k) = max(colWidth(:,k));
        end
        for j=1:nrows
            height(j) = max(rowHeight(j,:));
        end
    end
    
    %% Create the outer bounding box
    hbox = annotation('textbox',...
        [left, top-sum(height(:)), sum(width(:)),sum(height(:))],...
        'linewidth',1,...
        'BackgroundColor','k',...
        'DeleteFcn',@TableCleanup,...
        'Visible','off');
    set(hbox,'units','pixels')
    p = get(hbox,'position');
    boxweight = 1;
    p = p + [-1,-1,2,2] * boxweight;%add 1 pixel margin to box size
    set(hbox,'position',p)
    set(hbox,'units','normalized','Visible','on')
    
    T = top;
    L = left;

    %% Create final table
    for j = 1:nrows
        T = T - height(j);
        for k = 1:ncols
            h(j,k) = annotation('textbox',...
                'position',[L,T,width(k),height(j)],...
                'FontName','Arial',...
                'BackgroundColor','w',...
                'VerticalAlignment','middle',...
                'string',tableData{j,k},...
                'UIContextMenu',mnu,...
                varargin{:});
            L = L + width(k);
        end
        L = left;
    end
    ud.handles = h;
    ud.hbox = hbox;
    ud.autosize = 0;
    ud.boxweight = 1;
    ud.boxTop = top;
    ud.boxLeft = left;
    set([h(:);hbox],'UserData',ud)%store handles in each cell for future reference
    AutoFitTable(ud.handles,ud.hbox)    
    ud.listener = addlistener(gcf,'Position',...
        'PostSet',@(src,evt)AutoFitTableListener(ud.hbox,src,evt));
    ud.autosize = 1;
    set([h(:);hbox],'UserData',ud)%store settings in each cell for future reference
    set(hAutoEnable,'Checked','on')
    set(hfig,'Pointer','arrow')
    
end
%% ==== Callback Functions ================================================
function DeleteTable(~,~)
    ud = get(gco,'UserData');
    delete(ud.handles)
    delete(ud.hbox)
end
function FontSize(hmnu,~,fs)
    myhandle = gco;
    ud = get(myhandle,'UserData');
    caller = get(get(get(hmnu,'Parent'),'Parent'),'Tag');
    switch caller
        case 'Table'
            set(ud.handles,'fontsize',fs)
        case 'Cell'
            set(myhandle,'fontsize',fs)
        case 'Row'
            [j,~] = find(ud.handles == myhandle);
            set(ud.handles(j,:),'fontsize',fs)            
        case 'Col'            
            [~,k] = find(ud.handles == myhandle);
            set(ud.handles(:,k),'fontsize',fs)            
    end
    if ud.autosize == 1
        AutoFitTable(ud.handles,ud.hbox)
    end    
end
function ModifyFont(hmnu,~)
    myhandle = gco;
    ud = get(myhandle,'UserData');    
    caller =  get(get(hmnu,'Parent'),'Tag');
    switch caller
        case 'Table'
            F = uisetfont(myhandle);
            set(ud.handles,F)            
        case 'Cell'
            F = uisetfont(myhandle);
            set(myhandle,F)
        case 'Row'
            [j,~] = find(ud.handles == myhandle);
            F = uisetfont(ud.handles(j,1));
            set(ud.handles(j,:),F)
        case 'Col'            
            [~,k] = find(ud.handles == myhandle);
            F = uisetfont(ud.handles(1,k));
            set(ud.handles(:,k),F)            
    end
    
    if ud.autosize == 1
        AutoFitTable(ud.handles,ud.hbox)
    end
end
function MoveTable(~,~)
    ud = get(gco,'UserData');
    h = ud.handles(:);
    h(end+1) = ud.hbox;
    set(h,'Visible','off')
    myhappysound
    [x,y] = ginput(1);
    [left,top] = mydsxy2figxy(x,y);
    
    dx = ud.boxLeft - left;
    dy = ud.boxTop - top;

    for j=1:numel(h)
        p = get(h(j),'Position');
        p(1) = p(1) - dx;
        p(2) = p(2) - dy;
        set(h(j),'Position',p);
    end
    ud.boxLeft = left;
    ud.boxTop = top;
    
    set(h,'Visible','on','UserData',ud)
end
function EditCell(~,~)
    myhandle = gco;
    ud = get(myhandle,'UserData');
    [nrows,ncols] = size(ud.handles);
    d = cell(nrows,ncols);
    for j=1:nrows
        for k=1:ncols
            d{j,k} = get(ud.handles(j,k),'String');
        end
    end
    [j,k] = find(myhandle == ud.handles);
    d = OverlayTableEdit(d,[j,k]);
    if isempty(d)
        return
    end
    for j=1:nrows
        for k=1:ncols
            set(ud.handles(j,k),'String',d{j,k});
        end
    end
    if ud.autosize == 1
        AutoFitTable(ud.handles,ud.hbox)
    end    
end
function FormatTable(hmnu,~)
    myhandle = gco;
    ud = get(myhandle,'UserData');    
    caller =  get(get(hmnu,'Parent'),'Tag');
    switch caller
        case 'Table'
            inspect(ud.handles(:))
        case 'Cell'
            inspect(myhandle)
        case 'Row'
            [j,~] = find(ud.handles == myhandle);
            inspect(ud.handles(j,:))
        case 'Col'            
            [~,k] = find(ud.handles == myhandle);
            inspect(ud.handles(:,k))
    end
end
function FormatBox(~,~)
    ud = get(gco,'UserData');
    inspect(ud.hbox)
end
function AutoFitTableNow(~,~)
    ud = get(gco,'UserData');
    AutoFitTable(ud.handles,ud.hbox)
end
function AutoFitTableListener(hbox,~,~)
    ud = get(hbox,'UserData');
    AutoFitTable(ud.handles,hbox);
end
function AutoFitTable(h,hbox)
    set([h(:);hbox],'Visible','off')
    [rowHeight,colWidth] = deal(zeros(size(h)));
    [nrows,ncols] = size(h);
    for j=1:nrows
        for k=1:ncols
            set(h(j,k),'FitBoxtoText','on')
            p = get(h(j,k),'Position');
            set(h(j,k),'FitBoxtoText','off')
            rowHeight(j,k) = p(4);
            colWidth(j,k) = p(3);
        end
    end
    
    width = nan(1,ncols);
    for k=1:ncols
        width(k) = max(colWidth(:,k));
    end
    
    height = nan(1,nrows);
    for j=1:nrows
        height(j) = max(rowHeight(j,:));
    end
    
    ud = get(h(1),'UserData');
    left = ud.boxLeft;
    top = ud.boxTop;
    
    %bounding box
    set(hbox,'Position',[left, top-sum(height(:)), sum(width(:)),sum(height(:))]);
    set(hbox,'units','pixels')
    p = get(hbox,'position');
%     boxweight = 1;
    p = p + [-1,-1,2,2] * ud.boxweight;%add pixel margin to box size
    set(hbox,'position',p)
    set(hbox,'units','normalized')
    
    T = top;
    L = left;
    for j = 1:nrows
        T = T - height(j);
        for k = 1:ncols
            set(h(j,k),'position',[L,T,width(k),height(j)])
            L = L + width(k);
        end
        L = left;
    end

    set([h(:);hbox],'Visible','on')
end
function AutoSizeEnable(hdl,~)
    if strcmp(get(hdl,'Checked'),'on')
        set(hdl,'Checked','off')
    else
        set(hdl,'Checked','on')
    end
    if strcmp(get(hdl,'Checked'),'on')
        %enable
        ud = get(gco,'UserData');
        AutoFitTable(ud.handles,ud.hbox)    
        ud.listener = addlistener(gcf,'Position',...
            'PostSet',@(src,evt)AutoFitTableListener(ud.hbox,src,evt));
        ud.autosize = 1;
    else
        %disable
        ud = get(gco,'UserData');
        ud = get(ud.hbox,'UserData');
        delete(ud.listener)
        ud.listener = [];
        ud.autosize = 0;
    end
    set(ud.hbox,'UserData',ud)
    set(ud.handles,'UserData',ud)
end
function deleteRow(~,~)
    myhandle = gco;
    ud = get(myhandle,'UserData');
    h = ud.handles;
    [j,~] = find(h == myhandle);
    delete(h(j,:));
    h(j,:) = [];
    ud.handles = h;
    set(ud.handles,'UserData',ud)
    set(ud.hbox,'UserData',ud)
    AutoFitTable(ud.handles,ud.hbox);
end
function deleteColumn(~,~)
    myhandle = gco;
    ud = get(myhandle,'UserData');
    h = ud.handles;
    [~,k] = find(h == myhandle);
    delete(h(:,k));
    h(:,k) = [];
    ud.handles = h;
    set(ud.handles,'UserData',ud)
    set(ud.hbox,'UserData',ud)
    AutoFitTable(ud.handles,ud.hbox);
end
function insertColumn(~,~)
    myhandle = gco;
    ud = get(myhandle,'UserData');
    h = ud.handles;
    [~,k] = find(h == myhandle);
    h(:,end+1) = nan;
    for m=size(h,2):-1:k
        if m == 1
            break
        end
        h(:,m) = h(:,m-1);
    end
    h(:,k) = nan;
    for m=1:size(h,1)
        s = get(h(m,k+1));
        s = rmfield(s,'Annotation');
        s = rmfield(s,'BeingDeleted');
        s = rmfield(s,'Type');
        f = fieldnames(s);
        h(m,k) = annotation('textbox','Position',s.Position);
        for p=1:numel(f)
            try
                set(h(m,k),f{p},s.(f{p}))
            catch
            end
        end
        set(h(m,k),'string','-')
    end  
    ud.handles = h;
    set(ud.handles,'UserData',ud)
    AutoFitTable(ud.handles,ud.hbox)
end
function insertRow(~,~)
    myhandle = gco;
    ud = get(myhandle,'UserData');
    h = ud.handles;
    [j,~] = find(h == myhandle);
    h(end+1,:) = nan;
    for m=size(h,1):-1:j
        if m == 1
            break
        end
        h(m,:) = h(m-1,:);
    end
    h(j,:) = nan;
    for m=1:size(h,2)
        s = get(h(j+1,m));
        s = rmfield(s,'Annotation');
        s = rmfield(s,'BeingDeleted');
        s = rmfield(s,'Type');
        f = fieldnames(s);
        h(j,m) = annotation('textbox','Position',s.Position);
        for p=1:numel(f)
            try
                set(h(j,m),f{p},s.(f{p}))
            catch
            end
        end
        set(h(j,m),'string','-')
    end  
    ud.handles = h;
    set(ud.handles,'UserData',ud)
    AutoFitTable(ud.handles,ud.hbox)
end
function appendRow(~,~)
    myhandle = gco;
    ud = get(myhandle,'UserData');
    h = ud.handles;
    [j,~] = find(h == myhandle);
    h(end+1,:) = nan;
    j = j + 1;
    for m=size(h,1):-1:j
        if m == 1
            break
        end
        h(m,:) = h(m-1,:);
    end
    h(j,:) = nan;
    for m=1:size(h,2)
        s = get(h(j-1,m));
        s = rmfield(s,'Annotation');
        s = rmfield(s,'BeingDeleted');
        s = rmfield(s,'Type');
        f = fieldnames(s);
        h(j,m) = annotation('textbox','Position',s.Position);
        for p=1:numel(f)
            try
                set(h(j,m),f{p},s.(f{p}))
            catch
            end
        end
        set(h(j,m),'string','-')
    end  
    ud.handles = h;
    set(ud.handles,'UserData',ud)
    AutoFitTable(ud.handles,ud.hbox)
end
function appendColumn(~,~)
    myhandle = gco;
    ud = get(myhandle,'UserData');
    h = ud.handles;
    [~,k] = find(h == myhandle);
    h(:,end+1) = nan;
    k = k + 1;
    for m=size(h,2):-1:k
        if m == 1
            break
        end
        h(:,m) = h(:,m-1);
    end
    h(:,k) = nan;
    for m=1:size(h,1)
        s = get(h(m,k-1));
        s = rmfield(s,'Annotation');
        s = rmfield(s,'BeingDeleted');
        s = rmfield(s,'Type');
        f = fieldnames(s);
        h(m,k) = annotation('textbox','Position',s.Position);
        for p=1:numel(f)
            try
                set(h(m,k),f{p},s.(f{p}))
            catch
            end
        end
        set(h(m,k),'string','-')
    end  
    ud.handles = h;
    set(ud.handles,'UserData',ud)
    AutoFitTable(ud.handles,ud.hbox)

end
function FontColor(hmnu,~,c)
    myhandle = gco;
    ud = get(myhandle,'UserData');
    caller = get(get(get(hmnu,'Parent'),'Parent'),'Tag');
    switch caller
        case 'Table'
            set(ud.handles,'Color',c)
        case 'Cell'
            set(myhandle,'Color',c)
        case 'Row'
            [j,~] = find(ud.handles == myhandle);
            set(ud.handles(j,:),'Color',c)            
        case 'Col'            
            [~,k] = find(ud.handles == myhandle);
            set(ud.handles(:,k),'Color',c)            
    end
    if ud.autosize == 1
        AutoFitTable(ud.handles,ud.hbox)
    end    
end
function FontWeight(hmnu,~,wt)
    myhandle = gco;
    ud = get(myhandle,'UserData');
    caller = get(get(get(hmnu,'Parent'),'Parent'),'Tag');
    switch caller
        case 'Table'
            set(ud.handles,'FontWeight',wt)
        case 'Cell'
            set(myhandle,'FontWeight',wt)
        case 'Row'
            [j,~] = find(ud.handles == myhandle);
            set(ud.handles(j,:),'FontWeight',wt)            
        case 'Col'            
            [~,k] = find(ud.handles == myhandle);
            set(ud.handles(:,k),'FontWeight',wt)            
    end
    if ud.autosize == 1
        AutoFitTable(ud.handles,ud.hbox)
    end    
end
function BoxBorder(~,~,W)
    ud = get(gco,'UserData');
    ud.boxweight = W;
    set(ud.handles,'UserData',ud)
    AutoFitTable(ud.handles,ud.hbox)
end
function myhappysound(n,Fs)
%happysound     A calmer alternative to system beep
    if ~ismember('n',who)
        n = 1500;
    end
    if ~ismember('Fs',who)
        Fs = 1500;
    end

    x = 1:2:n;
    y = sin(x).*exp(-x/(0.3*n));

    soundsc(y,Fs);
end
function TableCleanup(h,~)
    ud = get(h,'UserData');
    if ud.autosize == 1
        %disable listener
        delete(ud.listener)
    end
end
function VertAlign(hmnu,~,align)
    myhandle = gco;
    ud = get(myhandle,'UserData');
    caller =  get(get(get(get(hmnu,'Parent'),'parent'),'parent'),'tag');
    switch caller
        case 'Table'
            set(ud.handles,'VerticalAlignment',align)
        case 'Cell'
            set(myhandle,'VerticalAlignment',align)
        case 'Row'
            [j,~] = find(ud.handles == myhandle);
            set(ud.handles(j,:),'VerticalAlignment',align)            
        case 'Col'            
            [~,k] = find(ud.handles == myhandle);
            set(ud.handles(:,k),'VerticalAlignment',align)            
    end
    if ud.autosize == 1
        AutoFitTable(ud.handles,ud.hbox)
    end    

end
function HorizAlign(hmnu,~,align)
    myhandle = gco;
    ud = get(myhandle,'UserData');
    caller =  get(get(get(get(hmnu,'Parent'),'parent'),'parent'),'tag');
    switch caller
        case 'Table'
            set(ud.handles,'HorizontalAlignment',align)
        case 'Cell'
            set(myhandle,'HorizontalAlignment',align)
        case 'Row'
            [j,~] = find(ud.handles == myhandle);
            set(ud.handles(j,:),'HorizontalAlignment',align)            
        case 'Col'            
            [~,k] = find(ud.handles == myhandle);
            set(ud.handles(:,k),'HorizontalAlignment',align)            
    end
    if ud.autosize == 1
        AutoFitTable(ud.handles,ud.hbox)
    end    
end
function varargout = mydsxy2figxy(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)) ')'])
    %modified by JVC to correctly treat logarithmic axis scales
    % 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

Contact us