No BSD License  

Highlights from
DataMatrix

image thumbnail
from DataMatrix by Yair Altman
Display a color-coded image of a data matrix with programmable data-tips & click callbacks

datamatrix(varargin)
function [hFig, hMatrix] = datamatrix(varargin)
%DATAMATRIX display a color-coded image of a data matrix with programmable data-tips & click callbacks
%   [hFig, hMatrix] = datamatrix(data, xlabels,ylabels,dataTips,callbacks)
%   [hFig, hMatrix] = datamatrix(data, propName,propValue, ...)
%   [hFig, hMatrix] = datamatrix(data, xlabels,ylabels,dataTips,callbacks, propName,propValue,...)
%
%   DATAMATRIX(data) accepts a numeric or logical data matrix (0, 1 or 2D)
%   and presents it in a color-coded image. Complex data is supported by
%   color-coding the data's complex modulus (magnitude). NaN elements are
%   assigned a specific shade of gray to distinguish them.
%
%   The user can specify optional labels, as well as data-tips and/or click
%   callbacks for any or all matrix cells. These optional parameters can be
%   specified either directly (format #1 above) or via order-indifferent
%   case-insensitive property-value pairs (format #2). The latter format
%   enables some extra parameterized control (see details below).
%   The formats may also be mixed by specifying valid propName/Value pairs
%   following the direct args (format #3). Note: all direct args must always
%   precede any supplied propName.
%
%   [hFig,hMatrix] = DATAMATRIX(...) returns a handle to the generated
%   figure as well as to the color-coded data matrix (an image object).
%
%   Inputs:
%       data      - mandatory matrix of logical or numeric values
%       xlabels   - optional cell array of X labels (strings)
%                   empty cell array (=default) means {'A','B',...}
%       ylabels   - optional cell array of Y labels (strings)
%                   empty cell array (=default) means {'1','2',...}
%       dataTips  - optional cell matrix of data-tips (strings)
%                   empty cell array (=default) means using standard tips
%       callbacks - optional cell matrix of callbacks (strings)
%                   empty cell array (=default) means no callbacks
%       propName  - one or more of the following property name/value pairs:
%         'xlabels'   - optional cell array of X labels (strings)
%         'ylabels'   - optional cell array of Y labels (strings)
%         'xrotation' - optional numeric value of xlabels rotation (degrees)
%                       default: 0 for 1-letter labels, 90 for others
%                       Note: 45 is space-saving but looks a bit fuzzy
%         'yrotation' - optional numeric value of xlabels rotation (default=0)
%         'dataTips'  - optional cell matrix of data-tips (strings)
%                       Note: Matlab 7+ only (Matlab 6 does not support data tips)
%         'callbacks' - optional cell matrix of callbacks (strings, func handles or callback cell arrays)
%                       Note: Matlab 7+ only
%         'minData'   - optional minimal value of color-coding (same type as data)
%         'maxData'   - optional maximal value of color-coding (same type as data)
%         'xtitle'    - optional x-axis title (string)
%         'ytitle'    - optional y-axis title (string)
%         'color'     - optional color: control shade colors (between white
%                       and this color) - string or [r,g,b] numeric array
%                       default = 'red' = [255,0,0] = [1,0,0]
%
%   Outputs:
%     hFig    - handle to generated figure
%     hMatrix - handle to generated data matrix (an image object)
%
%   Examples:
%     [hFig, hMatrix] = datamatrix(magic(3));
%     datamatrix(magic(3),{'alpha','beta','gamma'},{'row 1','row 2','row3'});
%     datamatrix(magic(3),'xlabels',{'alpha','beta','gamma'},'xrotation',30,'xtitle','My data');
%     datamatrix([i,2-i,nan;1,2*i,3],'ylabels',{'ert','tyu'});        % test complex data
%     datamatrix([true,true,false;false,true,false],'color','m');     % test logical data
%     datamatrix([1,2,3;4,nan,5;7,6,8],'color',[.5,.73,.85]);         % 2nd color format; NaN
%     datamatrix(magic(3),'datatips',{'row#1/col#1','row#1/col#2'});  % datatips (2 elements only)
%     datamatrix(magic(3),'mindata',6,'callbacks',{'12',{@disp,34}}); % callbacks; data clamp
%
%   Class support:
%     int*, single, double, complex*, logical
%     (on applicable Matlab versions: old versions don't support some classes)
%
%   Warning:
%      This code relies in part on undocumented and unsupported Matlab
%      functionality. It works on Matlab 6+, but use at your own risk!
%      Some features (e.g., data-tips) are unavailable on old Matlab versions.
%
%   Bugs and suggestions:
%      Please send to Yair Altman (altmany at gmail dot com)
%
%   Change log:
%     2007-Aug-30: First version posted on <a href="http://www.mathworks.com/matlabcentral/fileexchange/loadAuthor.do?objectType=author&mfx=1&objectId=1096533#">MathWorks File Exchange</a>

% Programmed by Yair M. Altman: altmany(at)gmail.com
% $Revision: 1.1 $  $Date: 2007/08/31 01:23:35 $

  try
      %dbstop if error
      hFig = [];
      hMatrix = [];

      % Process input arguments
      paramsStruct = processArgs(varargin{:});

      % Present the data
      [hFig, hMatrix] = presentData(paramsStruct);

  % Error handling
  catch
      %handleError;
      v = version;
      if v(1)<='6'
          err.message = lasterr;  % no lasterror function...
      else
          err = lasterror;
      end
      try
          err.message = regexprep(err.message,'Error using ==> [^\n]+\n','');
      catch
          try
              % Another approach, used in Matlab 6 (where regexprep is unavailable)
              startIdx = findstr(err.message,'Error using ==> ');
              stopIdx = findstr(err.message,char(10));
              for idx = length(startIdx) : -1 : 1
                  idx2 = min(find(stopIdx > startIdx(idx)));  %#ok ML6
                  err.message(startIdx(idx):stopIdx(idx2)) = [];
              end
          catch
              % never mind...
          end
      end
      if isempty(findstr(mfilename,err.message))
          % Indicate error origin, if not already stated within the error message
          err.message = [mfilename ': ' err.message];
      end
      if v(1)<='6'
          while err.message(end)==char(10)
              err.message(end) = [];  % strip excessive Matlab 6 newlines
          end
          error(err.message);
      else
          rethrow(err);
      end
  end

%% Internal error processing
function myError(id,msg)
    v = version;
    if (v(1) >= '7')
        error(id,msg);
    else
        % Old Matlab versions do not have the error(id,msg) syntax...
        error(msg);
    end
%end  % myError  %#ok for Matlab 6 compatibility


%% Process optional arguments
function paramsStruct = processArgs(varargin)

    % Ensure we got a 2D numeric/logical data matrix
    if ~nargin
        myError('YMA:datamatrix:missingData','Must supply data matrix as first argument');
    end
    paramsStruct.data = varargin{1};
    if ~isnumeric(paramsStruct.data) & ~islogical(paramsStruct.data)  %#ok Matlab 6
        myError('YMA:datamatrix:invalidData','Data must be numeric or logical');
    elseif numel(size(paramsStruct.data)) > 2
        myError('YMA:datamatrix:multiDimData','Data dimensions may not exceed 2');
    end

    % Get the properties in either direct or P-V format
    [directArgs, pvPairs] = parseparams(varargin(2:end));

    % Process direct parameters
    if length(directArgs) > 0,  paramsStruct.xlabels   = directArgs{1};  end  %#ok
    if length(directArgs) > 1,  paramsStruct.ylabels   = directArgs{2};  end
    if length(directArgs) > 2,  paramsStruct.datatips  = directArgs{3};  end
    if length(directArgs) > 3,  paramsStruct.callbacks = directArgs{4};  end

    % Now process the optional P-V params
    try
        % Initialize
        paramName = [];

        supportedArgs = {'xlabels','ylabels','datatips','callbacks',...
                         'xrotation','yrotation','mindata','maxdata',...
                         'xtitle','ytitle','color'};
        while ~isempty(pvPairs)

            % Ensure basic format is valid
            paramName = '';
            if ~ischar(pvPairs{1})
                myError('YMA:datamatrix:invalidProperty','Invalid property passed to datamatrix');
            elseif length(pvPairs) == 1
                myError('YMA:datamatrix:noPropertyValue',['No value specified for property ''' pvPairs{1} '''']);
            end

            % Process parameter values
            paramName  = pvPairs{1};
            paramValue = pvPairs{2};
            %paramsStruct.(lower(paramName)) = paramValue;  % no good on ML6...
            paramsStruct = setfield(paramsStruct, lower(paramName), paramValue);  %#ok ML6
            pvPairs(1:2) = [];
            if ~any(strcmpi(paramName,supportedArgs))
                url = 'matlab:help datamatrix';
                urlStr = getHtmlText(['<a href="' url '">' strrep(url,'matlab:','') '</a>']);
                myError('YMA:datamatrix:invalidProperty',...
                        ['Unsupported property - type "' urlStr ...
                         '" for a list of supported properties']);
            end
        end  % loop pvPairs

        % Process color arg
        if isfield(paramsStruct,'color')
            paramsStruct = processColor(paramsStruct);
        else
            paramsStruct.color = [1,0,0];  % default = red
        end

        % Update & check min/max data
        if ~isfield(paramsStruct,'mindata'),  paramsStruct.mindata = min(paramsStruct.data(:));  end
        if ~isfield(paramsStruct,'maxdata'),  paramsStruct.maxdata = max(paramsStruct.data(:));  end
        if ~strcmp(class(paramsStruct.mindata),class(paramsStruct.data))
            myError('YMA:datamatrix:invalidProperty','mindata must be the same type as data');
        elseif ~strcmp(class(paramsStruct.maxdata),class(paramsStruct.data))
            myError('YMA:datamatrix:invalidProperty','maxdata must be the same type as data');
        elseif numel(paramsStruct.mindata) ~= 1
            myError('YMA:datamatrix:invalidProperty','mindata must be a single value the same type as data');
        elseif numel(paramsStruct.maxdata) ~= 1
            myError('YMA:datamatrix:invalidProperty','maxdata must be a single value the same type as data');
        elseif paramsStruct.maxdata <= paramsStruct.mindata
            myError('YMA:datamatrix:invalidProperty','maxdata must be greater than mindata');
        end

        % Check args
        if isfield(paramsStruct,'xlabels') & ~iscellstr(paramsStruct.xlabels)  %#ok ML6
            myError('YMA:datamatrix:invalidProperty','xlabels must be a cell array of strings');
        elseif isfield(paramsStruct,'xlabels') & length(paramsStruct.xlabels) ~= size(paramsStruct.data,2)  %#ok ML6
            myError('YMA:datamatrix:invalidProperty','mismatch between # data cols & # xlabels');
        elseif isfield(paramsStruct,'ylabels') & ~iscellstr(paramsStruct.ylabels)  %#ok ML6
            myError('YMA:datamatrix:invalidProperty','ylabels must be a cell array of strings');
        elseif isfield(paramsStruct,'ylabels') & length(paramsStruct.ylabels) ~= size(paramsStruct.data,1)  %#ok ML6
            myError('YMA:datamatrix:invalidProperty','mismatch between # data rows & # ylabels');
        elseif isfield(paramsStruct,'ylabels') & length(paramsStruct.ylabels) ~= size(paramsStruct.data,1)  %#ok ML6
            myError('YMA:datamatrix:invalidProperty','mismatch between # data rows & # ylabels');
        elseif isfield(paramsStruct,'xrotation') & (~isnumeric(paramsStruct.xrotation) | numel(paramsStruct.xrotation)~=1)  %#ok ML6
            myError('YMA:datamatrix:invalidProperty','xrotation must be a scalar numeric value (degrees)');
        elseif isfield(paramsStruct,'yrotation') & (~isnumeric(paramsStruct.yrotation) | numel(paramsStruct.yrotation)~=1)  %#ok ML6
            myError('YMA:datamatrix:invalidProperty','yrotation must be a scalar numeric value (degrees)');
        elseif isfield(paramsStruct,'callbacks') & ~iscell(paramsStruct.callbacks)  %#ok ML6
            myError('YMA:datamatrix:invalidProperty','callbacks must be a cell array of strings, func handles or callback cell arrays');
        elseif isfield(paramsStruct,'xtitle') & ~ischar(paramsStruct.xtitle)  %#ok ML6
            myError('YMA:datamatrix:invalidProperty','xtitle must be a cell array of strings');
        elseif isfield(paramsStruct,'ytitle') & ~ischar(paramsStruct.ytitle)  %#ok ML6
            myError('YMA:datamatrix:invalidProperty','ytitle must be a cell array of strings');
        end
    catch
        if ~isempty(paramName),  paramName = [' ''' paramName ''''];  end
        myError('YMA:datamatrix:invalidProperty',['Error setting datamatrix property' paramName ':' char(10) lasterr]);
    end
%end  % processArgs  %#ok for Matlab 6 compatibility

%% Strip HTML tags for Matlab 6
function txt = getHtmlText(txt)
    v = version;
    if v(1)<='6'
        leftIdx  = findstr(txt,'<');
        rightIdx = findstr(txt,'>');
        if length(leftIdx) ~= length(rightIdx)
            newLength = min(length(leftIdx),length(rightIdx));
            leftIdx  = leftIdx(1:newLength);
            rightIdx = leftIdx(1:newLength);
        end
        for idx = length(leftIdx) : -1 : 1
            txt(leftIdx(idx) : rightIdx(idx)) = [];
        end
    end
%end  % getHtmlText  %#ok ML6

%% Process color argument
function paramsStruct = processColor(paramsStruct)
    try
        % Convert color names to RBG triple (0-1) if not already in that format
        if isempty(paramsStruct.color)
            paramsStruct.color = [1,0,0];  % =red
        elseif ischar(paramsStruct.color)
            switch lower(paramsStruct.color)
                case {'y','yellow'},   paramsStruct.color = [1,1,0];
                case {'m','magenta'},  paramsStruct.color = [1,0,1];
                case {'c','cyan'},     paramsStruct.color = [0,1,1];
                case {'r','red',''},   paramsStruct.color = [1,0,0];  % empty '' also sets red color
                case {'g','green'},    paramsStruct.color = [0,1,0];
                case {'b','blue'},     paramsStruct.color = [0,0,1];
                case {'w','white'},    paramsStruct.color = [1,1,1];
                case {'k','black'},    paramsStruct.color = [0,0,0];
                otherwise,  myError('YMA:datamatrix:invalidColor', ['Invalid color specified: ' color]);
            end
        elseif ~isnumeric(paramsStruct.color) | length(paramsStruct.color)~=3  %#ok ML6
            myError('YMA:datamatrix:invalidColor', ['Invalid color specified: ' paramsStruct.color]);
        end

        % Convert decimal RGB format (0-255) to fractional format (0-1)
        if max(paramsStruct.color) > 1
            paramsStruct.color = paramsStruct.color / 255;
        end
    catch
        myError('YMA:datamatrix:invalidColor',['Invalid color specified: ' lasterr]);
    end
%end  % processColor  %#ok ML6

%% Present data in a color-coded matrix
function [hFig, hMatrix] = presentData(paramsStruct)
    hMatrix = [];  %#ok in case of error

    % Start a new figure
    hFig = figure;
    set(hFig, 'name','Data matrix', 'number','off', 'visible','on', 'pointer','fleur');

    % Prepare colormap
    cm = colormap('gray');   % = shades of gray: black=0, white=1
    clen = length(cm);
    cm2 = ((1-cm)+repmat(paramsStruct.color,clen,1)).^0.2;  % color shades: white=0, color=1
    cm2(cm2>1)=1;

    % Special cell color for NaNs (see doc colormap, colormapeditor)
    cm2(1,:) = 0.8*[1,1,1];  % special color = light gray
    cm2(2,:) = cm2(1,:);

    % Prepare colored matrix data
    span = paramsStruct.maxdata - paramsStruct.mindata;
    cdata = double(paramsStruct.data - paramsStruct.mindata) / double(span);
    if ~isreal(paramsStruct.data)
        cdata = abs(cdata);
    end
    cdata(cdata <= 0) = 0;       % clamp low  values to min color intensity = white
    cdata = 3 + floor(cdata*(clen-2));
    cdata(cdata > clen) = clen;  % clamp high values to max color intensity
    cdata(isnan(cdata)) = 1;     % NaNs = special gray color

    % Prepare colorbar tick labels
    if isnumeric(paramsStruct.data)
        numGaps = 10;
        delta = span / numGaps;
        try
            if isinteger(paramsStruct.data)  %#ok ML6
                delta = max(1,delta);
                %numGaps = min(span,numGaps);
            end
        catch
            % never mind - probably an old matlab version without int* support
        end
        ticklabels = paramsStruct.mindata : delta : paramsStruct.maxdata;
        if ticklabels(end) ~= paramsStruct.maxdata  % might happen for int data
            ticklabels(end+1) = paramsStruct.maxdata;
        end
        numGaps = length(ticklabels) - 1;
        %tickvalues = [1,0.5+(2:(clen-2)/numGaps:clen)];
        valuesFrac = double(ticklabels - paramsStruct.mindata) / double(span);
        tickvalues = [1,2.5+floor(valuesFrac*(clen-2))];
        ticklabels = mat2cell(ticklabels,1,ones(1,numGaps+1));
        ticklabels = cellfun(@num2str,ticklabels,'un',0);
        ticklabels = {'NaN', ticklabels{:}};
    else
        ticklabels = {'false','true'};  % no NaNs in a logical matrix, or else it is converted to double...
        tickvalues = [1,2];
        cdata(cdata == 3) = 1;
        cdata(cdata == clen) = 2;
        cm2([1:2,4:end-1],:) = [];
    end

    % Display data in figure window
    axData = axes('parent',hFig, 'tag','axData');
    hMatrix = image(cdata,'CDataMapping','direct', 'ButtonDownFcn','', 'tag','dataMatrix', 'parent',axData);
    caxis([paramsStruct.mindata-eps, paramsStruct.maxdata]);

    % Colormap is finally ready - display in a colorbar
    colormap(cm2);
    hc = colorbar;  %#ok
    set(hc,'ytick',tickvalues, 'ytickLabel',ticklabels, 'FontSize',8);
    %zoom on;

    % Display the labels (if specified)
    if isfield(paramsStruct,'xtitle')
        xlabel(paramsStruct.xtitle);
    end
    if isfield(paramsStruct,'ytitle')
        hYlabel = ylabel(paramsStruct.ytitle);
        ylabelPos = get(hYlabel,'pos');
        set(hYlabel,'VerticalAlignment','top','pos',[size(cdata,2)+0.5 ylabelPos(2:3)]);
    end
    [numRows,numCols] = size(paramsStruct.data);

    % Set default labels
    if ~isfield(paramsStruct,'ylabels')
        ylabels = mat2cell(1:numRows,1,ones(1,numRows));
        paramsStruct.ylabels = cellfun(@num2str,ylabels,'un',0);
    end
    if ~isfield(paramsStruct,'xlabels')
        paramsStruct.xlabels = cellfun(@n2a,mat2cell(1:numCols,1,ones(1,numCols)),'un',0);
    end
    if ~isfield(paramsStruct,'xrotation')
        paramsStruct.xrotation = 0;
        if max(cellfun(@length,paramsStruct.xlabels)) > 1  % multi-char labels: vertical orientation
            paramsStruct.xrotation = 90;
        end
    end
    if ~isfield(paramsStruct,'yrotation')
        paramsStruct.yrotation = 0;
    end

    %set(hi,'cdata',rand(13,24))
    %set(axData,'pos',get(axData,'pos')-[0,0.03,0,0]);
    set(axData, 'xtick',[], 'ytick',[], 'tag','axData')
    try
        set(axData, 'outerpos',[.05,0,1,.95]);

        % Decrease axis width, for all colorbar tick labels to fit in window
        %axPos = get(axData,'position');
        %axPos(3) = axPos(3) - 0.02;
        %set(axData, 'position',axPos);
        set(axData, 'Position',[0.15,0.06,0.70,0.85]);
    catch
        % unsupported on old Matlab versions - never mind
    end
    commonProps = {'Interpreter','none', 'FontSize',8};
    htx = text(1:numCols,repmat(0.5-0.01*numRows,1,numCols), paramsStruct.xlabels, commonProps{:}, 'tag','xlabels', 'rotation',paramsStruct.xrotation);  %#ok
    hty = text(repmat(0.5-0.01*numCols,1,numRows),1:numRows, paramsStruct.ylabels, commonProps{:}, 'tag','ylabels', 'rotation',paramsStruct.yrotation, 'horizontal','right');  %#ok
    set(axData,'UserData',paramsStruct);

    % Add the <Data> button
%    uicontrol('String','View data','tag','btData','callback',@btData_Callback,'units','norm','position',[.15,.005,.12,.05],'tooltip','View results data in table format');

    % Data cursor setup
    try
        cursorObj = datacursormode(hFig);
        set(cursorObj, 'enable','on', 'UpdateFcn',@dataTipsTxt);

        % Mouse movements callbacks
        set(hFig, 'ButtonDownFcn',get(hFig,'WindowButtonDownFcn'));
        set(hFig, 'WindowButtonMotionFcn',@onMouseMove_Callback);
        %set(hFig,'WindowButtonDownFcn',  @onMouseDown_Callback);
        set(hFig, 'visible','on');
        pause(0.2);

        % The following hack only works in Matlab 7.2+
        if str2double(regexprep(version,'^(\d+\.\d+).*','$1')) >= 7.2
            modeMgr = get(hFig,'ModeManager');
            modeMgr.CurrentMode.WindowButtonDownFcn{1} = @onMouseDown_Callback;
            modeMgr.CurrentMode.ButtonDownFilter = @true;  % Pass mouse-clicks to *MY* callback
            warning off MATLAB:uitools:uimode:callbackerror %because ButtonDownFilter sends extra params...
        end
    catch
        % unsupported on old Matlab versions - never mind
    end
%end  % presentData  %#ok ML6

%% --- Convert col # format to 'A','B','C' format
% Thanks Brett Shoelson, via CSSM
function colStr = n2a(c)
      t = [floor((c-1)/26)+64, rem(c-1,26)+65];
      if (t(1)<65), t(1) = []; end
      colStr = char(t);

%% --- Executes on mouse click within the matrix image
function onMouseDown_Callback(hObject, varargin)  %#ok
      hFig = ancestor(hObject,'figure');  % should already be the figure handle, but just in case...
      if ~strcmpi(get(hFig,'selectiontype'),'normal')
          hgfeval(get(hFig,'ButtonDownFcn'),hFig,[]);
      else
          st = dbstack;
          %disp({st.name})
          if isempty(strmatch('onMouseMove_Callback',{st.name}))
              % Button clicked, not mouse movement
              hImage = findall(hFig, 'tag','dataMatrix');
              image_Callback(hImage);
          end
      end

%% --- Called on mouse cursor movement
function onMouseMove_Callback(hFig,varargin)   %#ok
% hFig    handle to figure
  %try
      %return;

      % Get the figure's main axes
      hAxes = get(hFig,'currentAxes');
      if isempty(hAxes)
          return;  % should never happen
      end
      
      % Get the current cursor point
      cp = get(hAxes,'CurrentPoint');
      cx = round(cp(1,1));
      cy = round(cp(1,2));
      
      % Check if the cursor is within the axes limits
      inMatrixFlag = 1;
      xlim = get(hAxes,'xlim');
      ylim = get(hAxes,'ylim');
      if (xlim(1)<cx & cx<xlim(2))  %#ok ML6
          dy = min(0.5, 0.05 * (ylim(2)-ylim(1)));
          updateGuideline(hAxes,[1,1,1,1]*cx,[ylim(1),cy-dy,cy+dy,ylim(2)],'x-guide');
      else
          inMatrixFlag = 0;
          set(findall(hAxes,'tag','x-guide'), 'visible','off');
      end
      if (ylim(1)<cy & cy<ylim(2))  %#ok ML6
          %updateGuideline(hAxes,xlim,[1,1]*cy,'y-guide');
          dx = min(0.5, 0.05 * (xlim(2)-xlim(1)));
          updateGuideline(hAxes,[xlim(1),cx-dx,cx+dx,xlim(2)],[1,1,1,1]*cy,'y-guide');
      else
          inMatrixFlag = 0;
          set(findall(hAxes,'tag','y-guide'), 'visible','off');
          if cy>ylim(2)
              set(findall(hAxes,'tag','x-guide'), 'visible','off');
          end
      end
      if cx>xlim(2)
          set(findall(hAxes,'tag','y-guide'), 'visible','off');
      end

      % If cursor is within the matrix
      cursorObj = datacursormode(hFig);
      if inMatrixFlag
          % Update the data tip (if shown) by simulating a MouseButtonDown event
          hImage = findall(hAxes, 'tag','dataMatrix');
          %set(hImage, 'ButtonDownFcn',@image_Callback);
          if strcmp(cursorObj.Enable,'on')
              % This doesn't work... :(((
              %cursorObj.sendMouseEvent('ButtonDown',hImage);
              % ...so try something else:
              hgfeval(get(hFig,'ButtonDownFcn'),hFig,[]);

              % ...Not enough: in Matlab 7.4 we also need the following
              modeMgr = get(hFig,'ModeManager');
              modeMgr.CurrentMode.WindowButtonDownFcn{1} = @onMouseDown_Callback;
              hgfeval(modeMgr.CurrentMode.WindowButtonDownFcn,hFig,[]);
          else
              set(hImage, 'ButtonDownFcn',@image_Callback);
          end
      else
          % Otherwise, remove all data tips
          cursorObj.removeAllDataCursors;
      end
  %catch
  %    handleError;
  %    return
  %end

%% --- Prepare/update axes guideline
function updateGuideline(hAxes,x,y,tag)
  hGuideline = findall(hAxes,'tag',tag);
  if ~isempty(hGuideline)
      set(hGuideline(1), 'xdata',x(1:2), 'ydata',y(1:2), 'visible','on');
      set(hGuideline(2), 'xdata',x(3:4), 'ydata',y(3:4), 'visible','on');
  else
      line(x(1:2), y(1:2), 'tag',tag, 'linestyle',':', 'color','k', 'HandleVisibility','off');
      line(x(3:4), y(3:4), 'tag',tag, 'linestyle',':', 'color','k', 'HandleVisibility','off');
  end

%% --- Generate Data Tip text
% Note: datacursor/datatip functions are located at: %MATLABROOT%\toolbox\matlab\graphics\@graphics\@datacursor\*.m
% ^^^^  and at:                                      %MATLABROOT%\toolbox\matlab\graphics\@graphics\@datatip\*.m
function txt = dataTipsTxt(empt,event_obj)  %#ok
  try
      if ~ishandle(event_obj),  return;  end
      pos = floor(get(event_obj,'Position'));

      % Clamp to axis limits (needed because onMouseMove_Callback() below messes the default datatip callback)
      hFig = ancestor(get(event_obj,'target'),'figure');
      hAxes = findall(hFig, 'tag','axData');
      xlim = get(hAxes,'xlim');
      ylim = get(hAxes,'ylim');
      if pos(1)<xlim(1),  pos(1)=xlim(1)+0.5;  end
      if pos(1)>xlim(2),  pos(1)=xlim(2)-0.5;  end
      if pos(2)<ylim(1),  pos(2)=ylim(1)+0.5;  end
      if pos(2)>ylim(2),  pos(2)=ylim(2)-0.5;  end
      pos = floor(pos);
      %cp = get(hAxes, 'CurrentPoint');
      %disp(floor([pos cp(1,1:2)]))

      %containerObj = event_obj.Target;
      %while ~isempty(containerObj) && ~strcmp(get(containerObj,'Type'),'figure')
      %    containerObj = get(containerObj,'Parent');
      %end
      %if ~isempty(containerObj)
      %    handles = guidata(containerObj);
      %    dataIdx = find(handles.data(:,1)==pos(1));
      %else
      %    dataIdx = [];
      %end
      %if isempty(dataIdx)
      
      % Get the Image object (note: can't use 'target' directly because of the guide lines)
      %hImage = get(event_obj,'target');
      hImage = findall(hFig, 'tag','dataMatrix');

      % Get the current object & analysis names
      hAx = get(hImage,'parent');
      paramsStruct = get(hAx,'userdata');

      % Set the tooltip text based on the currrent position and image value
      %cdata = get(hImage,'cdata');
      %curData = cdata(pos(2),pos(1));
      curData = paramsStruct.data(pos(2),pos(1));
      if isnumeric(curData)
          dataStr = num2str(curData,3);
      elseif islogical(curData)
          dataStr = char(java.lang.Boolean(curData));
      end
      txt = {['Row: ',  paramsStruct.ylabels{pos(2)}], ...  % ['obj' num2str(pos(1))]], ...
             ['Col: ',  paramsStruct.xlabels{pos(1)}], ...  % ['an'  num2str(pos(2))]], ...
             ['Data: ', dataStr]};

      % Display the tooltip message, if available (wrap long lines)
      if isfield(paramsStruct,'datatips')
          datatip = paramsStruct.datatips{pos(2),pos(1)};
          if ~isempty(datatip)
              newline = double(char(java.lang.System.getProperty('line.separator')));
              newline = newline(end);
              msg = strrep(datatip,'; ',[';' newline]);
              if iscell(msg)
                  msg = regexprep(msg,'(.)$',['$1' sprintf('\n\n')]);
                  msg = [msg{:}];
              end
              msg = regexprep(msg, {'^(.)','\n+$'},{'\n$1',''});  % separator newline before msg, but none at the end
              newlineIdx = find(msg==newline);
              spans = [0, newlineIdx, length(msg)];
              MAX_WIDTH = 50;
              for spanIdx = 1 : length(spans)-1
                  wrapIdx = spans(spanIdx);
                  spanEndIdx = spans(spanIdx+1);
                  rem = msg(wrapIdx+1:spanEndIdx);
                  spaceIdx = wrapIdx + find(rem==' ');
                  while length(rem) > MAX_WIDTH
                      wrapIdx = spaceIdx(find(spaceIdx>wrapIdx & spaceIdx<=wrapIdx+MAX_WIDTH, 1, 'last'));
                      if ~isempty(wrapIdx)
                          msg(wrapIdx) = newline;
                          rem = msg(wrapIdx+1:spanEndIdx);
                      else
                          rem = '';
                      end
                  end
              end
              txt{end+1} = '';  % seperator
              txt{end+1} = msg;
          end
      end
  catch
      %handleError;
      return;
  end

%% --- Executes on mouse click within the matrix image
function image_Callback(hImage, varargin)  %#ok
  try
      % Get the clicked location point
      hAx = get(hImage,'parent');
      cp = get(hAx, 'CurrentPoint');
      cx = round(cp(1,1));
      cy = round(cp(1,2));

      % Ensure the clicked location is within the results matrix...
      xlim = get(hAx,'xlim');
      ylim = get(hAx,'ylim');
      if ~(xlim(1)<cx & cx<xlim(2) & ylim(1)<cy & cy<ylim(2))  %#ok ML6
          return;
      end

      % Open all reports saved for this analysis/object combination
      paramsStruct = get(hAx,'UserData');
      callback = paramsStruct.callbacks{cy,cx};
      hgfeval(callback);
  catch
      %handleError;
      return;
  end

%% --- mat2cell support for Matlab 6 (simplified for performance)
function cellData = mat2cell(matData,varargin)
      [numRows,numCols] = size(matData);
      cellData{numRows,numCols} = matData(1);  % pre-allocate
      for rowIdx = 1 : numRows
          for colIdx = 1 : numCols
              cellData{rowIdx,colIdx} = matData(rowIdx,colIdx);
          end
      end

%% --- cellfun replacement for Matlab 6
function cellData = cellfun(func,cell,varargin)
  try
      % Call builtin cellfun function if possible (Matlab 7.1+)
      % Note: use hgfeval not feval to filter out partial support on Matlab 6
      % ^^^^  that may cause a SEGV crash...
      cellData = hgfeval(@cellfun,func,cell,varargin{:});
  catch
      % Probably an old Matlab version with limited cellfun support
      if nargin > 2  % 'un'...
          cellData = {};
          for cellIdx = 1 : length(cell)
              cellData{cellIdx} = feval(func,cell{cellIdx});
          end
      else
          cellData = [];
          for cellIdx = 1 : length(cell)
              cellData(cellIdx) = feval(func,cell{cellIdx});
          end
      end
  end

Contact us at files@mathworks.com