Code covered by the BSD License  

Highlights from
Axis Context Menu

image thumbnail

Axis Context Menu

by

 

Adds a uicontextmenu to x and/or y axis of an axes. The regular axes uicontextmenu stays intact.

uiaxiscontextmenu(varargin)
function uiaxiscontextmenu(varargin)
%uiaxiscontextmenu adds special contextmenu to X & Y axis of an axes.
%
%uiaxiscontextmenu() adds two properties to an axes (gca in default):
%"XUIContextMenu" and "YUIContextMenu".
%After this, these properties can be set to a regular uicontextmenu that
%will be shown when the mouse is on one of the axis and right clicked.
%
%uiaxiscontextmenu('remove') removes the axis contextmenu from gca.
%uiaxiscontextmenu(hAxes, ...)  is the same but for the explicitly axes
%"hAxes". hAxes can be scalar or vector of axes numeric handles.
%
%Note: Although these properties can be get/set regularly, they will not
%appear in the get(hAxes) or set(hAxes). In addition it can not be get/set
%with other "regular stock" properties.
%so set(hAxes,'YUIContextMenu',@myfun) is good but
%set(hAxes,'YUIContextMenu',@myfun, 'visible', 'on') is not good.
%In order to get all the properties on ecan use get(handle(hAxes))
%
%Written by Lior Cohen Jan 2013
%
%Example:
%
%fig = figure;
% ax=gca;
% plot(ax, rand(20,2));
%
% cm = uicontextmenu;
% uimenu(cm, 'label', 'This is regular axes contextmenu','callback', 'disp(''This is regular axes contextmenu'')');
% set(ax,'uicontextmenu', cm);
%
% uiaxiscontextmenu(ax); %activating the feature
%
% Xcm = uicontextmenu; %X axis contextmenu
% uimenu(Xcm, 'label', ' This is X axis special contextmenu', 'callback', 'disp(''This is X axis special contextmenu'')' );
% uimenu(Xcm, 'label', ' Set Linear Scale', 'callback', @(h,e)set(ax,'XScale','linear'), 'separator', 'on'  );
% uimenu(Xcm, 'label', 'Set Log Scale', 'callback', @(h,e)set(ax,'XScale','log') );
% set(ax,'XUIContextMenu', Xcm); %assigning Xcm to XUIContextMenu
%
% Ycm = uicontextmenu; %Y axis contextmenu
% uimenu(Ycm, 'label', ' This is Y axis special contextmenu', 'callback', 'disp(''This is Y axis special contextmenu'')' );
% uimenu(Ycm, 'label', ' Set Linear Scale', 'callback', @(h,e)set(ax,'YScale','linear'), 'separator', 'on' );
% uimenu(Ycm, 'label', 'Set Log Scale', 'callback', @(h,e)set(ax,'YScale','log') );
% set(ax,'YUIContextMenu', Ycm); %assigning Ycm to YUIContextMenu

%% Inputs validation
mode = '';
if nargin==0  %uiaxiscontextmenu signature
    hAxes = gca;
elseif nargin==1 && ~ischar(varargin{1}) %uiaxiscontextmenu(hAxes) signature
    hAxes = varargin{1};
elseif nargin==1 && ischar(varargin{1}) %uiaxiscontextmenu('remove') signature
    hAxes = gca;
    mode = varargin{1};
    assert( strcmpi(mode, 'remove'), 'LC:ArgCheck', 'mode can be only ''remove'' ');
elseif nargin==2 %uiaxiscontextmenu(ax, 'remove') signature
    hAxes = varargin{1};
    mode = varargin{2};
    assert( strcmpi(mode, 'remove'), 'LC:ArgCheck', 'mode can be only ''remove'' ');
else
    error('LC:ArgCheck', 'Invalid inputs');
end

assert( all(ishghandle(hAxes,'axes')), 'LC:ArgCheck', 'hAxes must be a handle to axes object');

%% Non-Scalar hAxes
if numel(hAxes)>1
    for k=1:numel(hAxes)
        thisAx = hAxes(k);
        if strcmpi(mode, 'remove')
            uiaxiscontextmenu(thisAx, 'remove');
        else
            uiaxiscontextmenu(thisAx);
        end
    end
    return;
end

%% Scalar hAxes Code

%% "remove" mode -> remove the feature
if strcmpi(mode, 'remove')
    removeUIAxisContextMenu(hAxes);
    return;
end

%% "on" mode
if ~isprop(hAxes, 'YUIContextMenu')
    schema.prop(hAxes, 'YUIContextMenu', 'mxArray');
end
if ~isprop(hAxes, 'XUIContextMenu')
    schema.prop(hAxes, 'XUIContextMenu', 'mxArray');
end

%listenr to axes is being destroyed
lh = handle.listener(hAxes, 'ObjectBeingDestroyed', @removeUIAxisContextMenu);
setappdata(hAxes,'AxesBeingDestroyedListener', lh);

%Timer is needed in order to restore the original uicontextmenu of the
%axes. Must restore with a little delay in order to let "our" axis
%contextmenu to react.
hFig = ancestor(hAxes,'figure');
UIAxisCMTimer = getappdata(hFig,'UIAxisCMTimer');
if isempty(UIAxisCMTimer)
    UIAxisCMTimer = timer('StartDelay', 0.2 , 'ExecutionMode' , 'singleShot', 'tag', 'UIAxisCMTimer' );
    setappdata(hFig,'UIAxisCMTimer', UIAxisCMTimer);
end

UIAxisCMButtonUpListener = getappdata(hFig, 'UIAxisCMButtonUpListener');
if isempty(UIAxisCMButtonUpListener) %need to create the listener
    UIAxisCMButtonUpListener = handle.listener(hFig, 'WindowButtonUpEvent', @onButtonUp);
    setappdata(hFig,'UIAxisCMButtonUpListener', UIAxisCMButtonUpListener); %to keep alive until the figure is destroyed or no other registered axes
end

%Register the axes in the figure. this way we can delete the
%WindowButtonUpEvent lisnter if no registered axes were left.
RegisteredAxes = getappdata(hFig, 'UIAxisCMRegisteredAxes');
if ~any(RegisteredAxes == hAxes) %not registered
    RegisteredAxes = [RegisteredAxes; hAxes];
end
setappdata(hFig, 'UIAxisCMRegisteredAxes', RegisteredAxes);


function removeUIAxisContextMenu(hAxes, eventdata) %#ok<INUSD>
hAxes = double(hAxes); %may be handle
hFig = ancestor(hAxes, 'figure');

%delete properties
delete( findprop(handle(hAxes),'XUIContextMenu') );
delete( findprop(handle(hAxes),'YUIContextMenu') );

%remove from figure registry
RegisteredAxes = getappdata(hFig, 'UIAxisCMRegisteredAxes');
RegisteredAxes(RegisteredAxes==hAxes) = [];
setappdata(hFig, 'UIAxisCMRegisteredAxes', RegisteredAxes);

%if no more registered axes we can delete "UIAxisCMTimer" and
%"UIAxisCMButtonUpListener". The tries are to overcome case in which
%already deleted / remove externally.
if isempty(RegisteredAxes)
    try  rmappdata(hFig, 'UIAxisCMRegisteredAxes') ; catch, end  %probably already removed
    try  delete( getappdata(hFig, 'UIAxisCMTimer') ); catch, end  %#ok<*CTCH> %if failes probably already deleted
    try  rmappdata(hFig, 'UIAxisCMTimer') ; catch, end  %probably already removed
    try  delete( getappdata(hFig, 'UIAxisCMButtonUpListener') );    catch, end %probably already deleted
    try  rmappdata(hFig, 'UIAxisCMButtonUpListener') ;  catch, end %probably already removed
end

function onButtonUp(hFig, eventdata)
%if ButtonUp isnot on axes return
hAxes = eventdata.CurrentObject;
if ~isa( hAxes, 'axes')
    return;
end

%if not 2d (view(2)), return
[az, el]=view(hAxes);
if az~=0 || el~=90 %not 2D
    return;
end

%if not right click, return
ButtonType = get(hFig, 'SelectionType');
if ~strcmp(ButtonType, 'alt') %not right click
    return;
end

if isOnXAxis(hAxes)    %y axis
    setAxisUIContextMenu(hAxes, 'X');
elseif isOnYAxis(hAxes) %x axis
    setAxisUIContextMenu(hAxes, 'Y');
end

function setAxisUIContextMenu(hAxes, axis)
if ~isprop(hAxes, [axis, 'UIContextMenu'])
    return;
end

axisContextMenu = get(hAxes,[axis, 'UIContextMenu']);
if  isempty(axisContextMenu)
    return;
end

OriginalCMenu = get(hAxes, 'uicontextmenu');
Timer = getappdata(ancestor(hAxes, 'figure'), 'UIAxisCMTimer');
set(Timer,  'TimerFcn', @(h,e)set(hAxes, 'uicontextmenu', OriginalCMenu) );
%
set(hAxes, 'uicontextmenu',  axisContextMenu );
start(Timer); %original context menu will return in a sec ....

function tf = isOnYAxis(hAxes)

if isnumeric(hAxes)
    hAxes = handle(hAxes);
end

ActivePositionProperty = get(hAxes, 'ActivePositionProperty');
set(hAxes, 'ActivePositionProperty', 'position');
Xlim_in_pixels = getpixelposition(hAxes); Xlim_in_pixels = Xlim_in_pixels(3);
set(hAxes, 'ActivePositionProperty', ActivePositionProperty);

x = hAxes.currentPoint(1,1);

if strcmp(hAxes.XDir,'normal') && strcmp(hAxes.YAxisLocation, 'left')
    XofYAxis = hAxes.Xlim(1);
elseif strcmp(hAxes.XDir,'reverse') && strcmp(hAxes.YAxisLocation, 'left')
    XofYAxis = hAxes.Xlim(2);
elseif strcmp(hAxes.XDir,'normal') && strcmp(hAxes.YAxisLocation, 'right')
    XofYAxis = hAxes.Xlim(2);
elseif strcmp(hAxes.XDir,'reverse') && strcmp(hAxes.YAxisLocation, 'right')
    XofYAxis = hAxes.Xlim(1);
end

th_pix = 5;
if strcmp( hAxes.XScale, 'linear')
    tf = abs(x-XofYAxis) / diff(hAxes.XLim) <= th_pix / Xlim_in_pixels;
else
    tf = abs(log10(x/XofYAxis)) / log10(hAxes.XLim(2)/hAxes.XLim(1)) <=  th_pix / Xlim_in_pixels;
end

function tf = isOnXAxis(hAxes)

if isnumeric(hAxes)
    hAxes = handle(hAxes);
end

ActivePositionProperty = get(hAxes, 'ActivePositionProperty');
set(hAxes, 'ActivePositionProperty', 'position');
Ylim_in_pixels = getpixelposition(hAxes); Ylim_in_pixels = Ylim_in_pixels(4);
set(hAxes, 'ActivePositionProperty', ActivePositionProperty);

y = hAxes.currentPoint(1,2);

if strcmp(hAxes.YDir,'normal') && strcmp(hAxes.XAxisLocation, 'bottom')
    YofXAxis = hAxes.Ylim(1);
elseif strcmp(hAxes.YDir,'reverse') && strcmp(hAxes.XAxisLocation, 'bottom')
    YofXAxis = hAxes.Ylim(2);
elseif strcmp(hAxes.YDir,'normal') && strcmp(hAxes.XAxisLocation, 'top')
    YofXAxis = hAxes.Ylim(2);
elseif strcmp(hAxes.YDir,'reverse') && strcmp(hAxes.XAxisLocation, 'top')
    YofXAxis = hAxes.Ylim(1);
end

th_pix = 5;
if strcmp( hAxes.YScale, 'linear')
    tf = abs(y-YofXAxis) / diff(hAxes.YLim) <= th_pix / Ylim_in_pixels;
else
    tf = abs(log10(y/YofXAxis)) / log10(hAxes.YLim(2)/hAxes.YLim(1)) <=  th_pix / Ylim_in_pixels;
end



Contact us