Code covered by the BSD License  

Highlights from
Data cursors for figure window

image thumbnail
from Data cursors for figure window by Malcolm Lidierth
Data cursor support for multiple axes in a fugure

NumberOfCursor=CreateCursor(fhandle, NumberOfCursor, AxesList, UserFunction)
function NumberOfCursor=CreateCursor(fhandle, NumberOfCursor, AxesList, UserFunction)
% scCreateCursor creates or replaces a vertical cursor
%
% Examples
% n=CreateCursor()
% n=CreateCursor(fighandle)
% n=CreateCursor(fighandle, CursorNumber)
% n=CreateCursor(fighandle, CursorNumber, AxesList)
% n=CreateCursor(fighandle, CursorNumber, AxesList, UserFunction)
%
% Cursors are specific to a figure. If the figure handle is not specified
% in fhandle, the current figure (returned by gcf) will be used.
% CursorNumber is any positive number. If not specified the lowest numbered
% free cursor number will be used. 
%
% AxesList, if supplied and not empty, is vector of axes handles and 
% will restrict the drawing of cursors to the specified axes. Note that
% cursor numbers must be unique to any MATLAB figure but using this feature,
% cursors 1 and 2, for example,  could be applied to one set of axes 
% while 3 and 4 are applied to a second set.
%
% UserFunction, if specified is a function handle or a cell array
% containing a function handle in the first element. The function will be
% called when there is a ButtonUpEvent following movement of the cursor.
% if UserFunction is a cell array, the 2nd and subsequent elements will be
% passed to the function as arguments.
%
% Returns n, the number of the cursor created in the relevant figure.
%
% A record is kept of the cursors in the figure's application data
% area. Cursor n occupies the nth element of a cell array. Each element is
% a structure containing the following fields:
%           Handles:    a list of objects associated with this cursor -
%                       one line for each axes and one or more text objects
%           IsActive:   true if this cursor is currently being moved. False
%                       otherwise. For manual cursors, IsActive is set by a
%                       button down on the cursor and cleared by a button
%                       up. 
% Functions that affect the position of a cursor must explicitly update all
% the objects listed in Handles. A cursor is not presently an object itself.
%
% Revisions: 27.02.07 add restoration of gca
%            30.01.08 add uicontextmenu. Speed up UpdateCursorPosition.
%            14.11.09 add UserFunction argument
%            15.11.09 update text label only on button up - improves speed
%
% -------------------------------------------------------------------------
% Author: Malcolm Lidierth 01/07
% Copyright  The Author & King's College London 2007-
% -------------------------------------------------------------------------
%

% Note: These functions presently work with 2D views only

% Keep gca to restore current axes at end
current=gca;

% Figure details
if nargin<1
    fhandle=gcf;
end

if nargin<3
    if isappdata(fhandle, 'AxesList')
        % Within sigTOOL figure
        AxesList=getappdata(fhandle, 'AxesList');
        AxesList=AxesList(AxesList>0);
    else
        % Otherwise
        AxesList=sort(findobj(fhandle, 'type', 'axes'));
    end
else
    if isempty(AxesList)
        AxesList=sort(findobj(fhandle, 'type', 'axes'));
    end
end

if nargin<4
    UserFunction=[];
end

% Get cursor info
newhandles=zeros(1, length(AxesList));
Cursors=getappdata(fhandle, 'VerticalCursors');

% Deal with this cursor number

if isempty(Cursors)
    NumberOfCursor=1;
else
    if nargin<2
        FirstEmpty=0;
        if ~isempty(Cursors)
            for i=1:length(Cursors)
                if isempty(Cursors{i})
                    FirstEmpty=i;
                    break
                end
            end
        end
        if FirstEmpty==0
            NumberOfCursor=length(Cursors)+1;
            Cursors{NumberOfCursor}=[];
        else
            NumberOfCursor=FirstEmpty;
        end
    end
end

% Add numel 19.01.08
if ~isempty(Cursors) && numel(Cursors)>NumberOfCursor && ~isempty(Cursors{NumberOfCursor}) &&...
        isfield(Cursors{NumberOfCursor},'CursorIsActive');
    if Cursors{NumberOfCursor}.CursorIsActive==true
        disp('CreateCursor: Ignored attempt to delete the currently active cursor');
        return
    else
        delete(Cursors{NumberOfCursor}.Handles);
    end
end
Cursors{NumberOfCursor}.IsActive=false;
Cursors{NumberOfCursor}.Handles=[];
Cursors{NumberOfCursor}.Type='';


XLim=get(AxesList(end),'XLim');
xhalf=XLim(1)+(0.5*(XLim(2)-XLim(1)));
for i=1:length(AxesList)
    % For each cursor there is a line object in each axes
    subplot(AxesList(i));
        YLim=get(AxesList(i),'YLim');
        newhandles(i)=line([xhalf xhalf], [YLim(1) YLim(2)]);
        Cursors{NumberOfCursor}.Type='2D';
end


% Put a label at the top and make it behave as part of the cursor
axes(AxesList(1));
YLim=get(AxesList(1),'YLim');
th=text(xhalf, YLim(2), ['C' num2str(NumberOfCursor)],...
    'Tag','Cursor',...
    'UserData', NumberOfCursor,...
    'FontSize', 7,...
    'HorizontalAlignment','center',...
    'VerticalAlignment','bottom',...
    'Color', 'k',...
    'EdgeColor',[26/255 133/255 5/255],...
    'Clipping','off',...
    'ButtonDownFcn',{@CursorButtonDownFcn});

% Set line properties en bloc
% Note UserData has the cursor number
if ispc==1
    % Windows
    EraseMode='xor';
else
    % Mac etc: 'xor' may cause problems
    EraseMode='normal';
end
set(newhandles, 'Tag', 'Cursor',...
    'Color', [26/255 133/255 5/255],...
    'UserData', NumberOfCursor,...
    'Linewidth',1,...
    'Erasemode', EraseMode,...
    'ButtonDownFcn',{@CursorButtonDownFcn, UserFunction});

Cursors{NumberOfCursor}.IsActive=false;
Cursors{NumberOfCursor}.Handles=[newhandles th];

cmenu=uicontextmenu();
uimenu(cmenu, 'Label', 'Delete', 'Callback', @Delete,...
    'UserData', NumberOfCursor);
set(Cursors{NumberOfCursor}.Handles, 'UIContextMenu', cmenu);

setappdata(fhandle, 'VerticalCursors', Cursors);
set(gcf, 'WindowButtonMotionFcn',{@CursorWindowButtonMotionFcn});
% Restore the axes on entry as the current axes
axes(current)
return
end

%--------------------------------------------------------------------------
function CursorButtonDownFcn(hObject, EventData, UserFunction) %#ok<INUSD>
%--------------------------------------------------------------------------

% Make sure we have a left button click
type=get(ancestor(hObject,'figure'), 'SelectionType');
flag=strcmp(type, 'normal');
if flag==0
    % Not so, return and let matlab call the required callback, 
    % e.g. the uicontextmenu, which will be in the queue
    return
end;

% Flag the active cursor
Cursors=getappdata(gcf, 'VerticalCursors');
for i=1:length(Cursors)
    if isempty(Cursors{i})
        continue
    end
    if any(Cursors{i}.Handles==hObject)
            % Set flag
            Cursors{i}.IsActive=true;
            break
    end
end
setappdata(gcf, 'VerticalCursors', Cursors);

% Set up
StoreWindowButtonDownFcn=get(gcf,'WindowButtonDownFcn');
StoreWindowButtonUpFcn=get(gcf,'WindowButtonUpFcn');
StoreWindowButtonMotionFcn=get(gcf,'WindowButtonMotionFcn');
% Store these values in the CursorButtonUpFcn persistent variables so they
% can be used/restored later
CursorButtonUpFcn({hObject,...
    StoreWindowButtonDownFcn,...
    StoreWindowButtonUpFcn,...
    StoreWindowButtonMotionFcn});
% Motion callback needs only the current cursor number
CursorButtonMotionFcn({hObject});

% Set up callbacks
set(gcf, 'WindowButtonUpFcn',{@CursorButtonUpFcn, UserFunction});
set(gcf, 'WindowButtonMotionFcn',{@CursorButtonMotionFcn});
return
end

%--------------------------------------------------------------------------
function CursorButtonUpFcn(hObject, EventData, UserFunction) %#ok<INUSD>
%--------------------------------------------------------------------------
% These persistent values are set by a call from CursorButtonDownFcn
persistent ActiveHandle;
persistent StoreWindowButtonDownFcn;
persistent StoreWindowButtonUpFcn;
persistent StoreWindowButtonMotionFcn;

% Called from CursorButtonDownFcn - hObject is a cell with values to seed
% the persistent variables
if iscell(hObject)
    ActiveHandle=hObject{1};
    StoreWindowButtonDownFcn=hObject{2};
    StoreWindowButtonUpFcn=hObject{3};
    StoreWindowButtonMotionFcn=hObject{4};
    return
end

% Called by button up in a figure window - use the stored CurrentCursor
% value
[Handles cpos]=UpdateCursorPosition(ActiveHandle);
% 15.11.09 Update cursor text object(s)
LabelHandle=findobj(Handles, 'Type', 'text');
tpos=get(LabelHandle,'Position');
tpos(1)=cpos(1);
set(LabelHandle,'Position',tpos);
set(LabelHandle, 'Visible', 'on');
    
% Restore the figure's original callbacks - make sure we do this in the
% same figure that we had when the mouse button-down was detected
h=ancestor(ActiveHandle,'figure');
set(h, 'WindowButtonDownFcn', StoreWindowButtonDownFcn);
set(h, 'WindowButtonUpFcn', StoreWindowButtonUpFcn);
set(h, 'WindowButtonMotionFcn',StoreWindowButtonMotionFcn);


% Remove the active cursor flag
Cursors=getappdata(gcf, 'VerticalCursors');
for i=1:length(Cursors)
    if isempty(Cursors{i})
        continue
    end
    if any(Cursors{i}.Handles==ActiveHandle)
        Cursors{i}.IsActive=false;
        break
    end
end
setappdata(gcf, 'VerticalCursors', Cursors);

% Added 14.11.09. Call user specified function on button up
if nargin==3 && ~isempty(UserFunction)
    if iscell(UserFunction)
        UserFunction{1}(UserFunction{2:end});
    else
        UserFunction();
    end
end

return
end

%--------------------------------------------------------------------------
function CursorButtonMotionFcn(hObject, EventData) %#ok<INUSD>
%--------------------------------------------------------------------------
% This replaces the CursorWindowButtonMotionFcn while a cursor is being
% moved
persistent ActiveHandle;

% Called from CursorButtonDownFcn
if iscell(hObject)
    ActiveHandle=hObject{1};
    return
end
%Called by button up
UpdateCursorPosition(ActiveHandle)
return
end

%--------------------------------------------------------------------------
function [a b]=UpdateCursorPosition(ActiveHandle)
%--------------------------------------------------------------------------
% Get the pointer position in the current axes
thisCursor= get(ActiveHandle,'UserData');
cpos=get(gca,'CurrentPoint');

% Get object handles
Cursors=getappdata(ancestor(ActiveHandle, 'figure'), 'VerticalCursors');
Handles=Cursors{thisCursor}.Handles;

% Update cursor line objects
CursorHandles=findobj(Handles, 'Type', 'line');
% ... and update them:
%if cpos(1,1)==cpos(2,1) && cpos(1,2)==cpos(2,2)
    % 2D Cursor
    % Limit to the x-axis limits
    XLim=get(gca,'XLim');
    if cpos(1)<XLim(1)
        cpos(1)=XLim(1);
    end
    if cpos(1)>XLim(2)
        cpos(1)=XLim(2);
    end
    % Update cursor line object(s)
    set(CursorHandles,'XData',[cpos(1) cpos(1)]);
    % 15.11.09
    % Turn cursor text object(s) off for now - updating them while moving
    % slows performance. Update only on button up
    LabelHandle=findobj(Handles, 'Type', 'text');
    set(LabelHandle, 'Visible', 'off');
% else
%     % TODO: Include support for 3D cursors
%     set(CursorHandles,'XData',[cpos(1) cpos(1)]);
%     return
% end

if nargout>0
    a=Handles;
    b=cpos;
end

return
end

%--------------------------------------------------------------------------
function Delete(hObject, EventData)
%--------------------------------------------------------------------------
fhandle=ancestor(hObject, 'figure');
n=get(hObject, 'UserData');
DeleteCursor(fhandle, n);
set(fhandle, 'Pointer', 'arrow');
return
end

Contact us at files@mathworks.com