function varargout = ita_plottools_cursors(state,options,axh)
%ITA_PLOTTOOLS_CURSORS Add cursors to the plot and trigger functions
%
%Syntax
% ita_plottools_cursors('state',opt,axh);
%
%
% This file is part of the ITA-Toolbox. Some rights reserved.
% You can find the license for this m-file in the license.txt file in the ITA-Toolbox folder.
%
if ~ita_preferences('plotcursors') && ((islogical(state) && ~state) || ~ismember(lower(state),{'on','yfocus'})) % no cursors wanted, skip all of this
varargout{1} = [];
return;
end
%% IN: If output argument, return the cursor values
if nargout %See if the user specified a formatting function for the datalabel
if nargin==0 %Use current axis
h = gca;
else
h = state;
if strcmp(get(h,'Type'),'line');
h = get(h,'Parent');
end;
end;
ActiveHandle = getappdata(h,'ActiveCursor');
PassiveHandle = getappdata(h,'PassiveCursor');
if (isempty(ActiveHandle) || isempty(PassiveHandle)) && ita_preferences('plotcursors')
warning('I could not find any cursors'); %#ok
return
end
act_pos = get(ActiveHandle, 'XData'); % pdi: change to get instead getappdata(ActiveHandle, 'Coordinates')
pas_pos = get(PassiveHandle,'XData');
if act_pos(1) < pas_pos(1)
val = [act_pos(1) pas_pos(1)];
else
val = [pas_pos(1) act_pos(1)];
end
varargout{1} = val;
return
end
%% Parse input arguments
if nargin == 1 %pdi added
options = [];
axh = gca;
end
global cursorUtils %important for cursors
%% pdi
% Define constants for zoom
%pdi: @ are all cursorUtils entries really neccessary?
cursorUtils.y_jump = 10; % dB's to move y axis
cursorUtils.zoom = 4/3; % Ratio the x axis should be zoomed
cursorUtils.VICINITYDELTA = 440; % Number of Samples to be showed in each side of the cursors vicinity
cursorUtils.longjump = 16; % Number of bins to divide the axis for a long jump
cursorUtils.SHORTJUMP = 512; % Number of bins to divide the axis for a short jump
%% decide what to do
switch state
case {1,'on'} % ON: Set the WindowButtonDownFcn and add the cursors.
%Marker and color specification
%if ita_preferences('blackbackground')
% ActiveColor = 'g';
% PassiveColor = 'y';
%else
ActiveColor = 'g';
PassiveColor = [0.46 0.53 0.6]; % grey color works great for both backgroundcolors
%end
marker = 'none'; %'+';
LineWidth = get(axh(1),'LineWidth')*3;
for idx = 1:length(axh); % used for multiple axis in one plot (subplots)
current_axh = axh(idx);
%If there are already some data cursors on this plot, delete them!
ita_plottools_cursors('off',state,current_axh);
% Get the handle for the data
current_figure_handle = getappdata(current_axh,'FigureHandle');
if isempty(current_figure_handle)
break
end
FigureData = getappdata(current_figure_handle);
AxisData = getappdata(current_axh);
% Set mouse callback
lnh = AxisData.ChannelHandles(AxisData.ActiveChannel);
%pdi: @ do we need the following lines? what do they do?
setappdata(current_axh,'ActiveChannelHandle',lnh); %The currently selected line.
set(AxisData.ChannelHandles,'ButtonDownFcn', ...
['setappdata(getappdata(gca,''FigureHandle''),''ActiveChannelHandle'',gco);',...
'ita_plottools_cursors(''selectline'',[],getappdata(gca,''FigureHandle''))']);
xl = get(current_axh,'Xlim');
xdata = get(lnh,'XData');
x_init = local_data2samples(xl,xdata);
%pdi: @ do we need the following lines? what do they do?
while xdata(x_init(1)) < xl(1) && xl(1) < xdata(1)
x_init(1) = x_init(1)+1;
end
while xdata(x_init(2)) > xl(2) && xl(2) > xdata(end)
x_init(2) = x_init(2)-1;
end
xv1 = xl(1);
xv2 = xl(2);
% Save data of current active axis
if current_axh == FigureData.ActiveAxis
% Initalization of important global variables
cursorUtils.xdata = xdata;
cursorUtils.XLim = xl;
cursorUtils.sampleXLim = x_init;
cursorUtils.OriginalXLim = xl;
cursorUtils.sampleOriginalXLim = x_init;
cursorUtils.sampleSize = length(xdata);
end
setappdata(current_axh,'OriginalYLim',AxisData.Limits(3:4));
% �andrey:
% yl = get(current_axh,'YLim');
%Add the cursors
ph1 = line([xv1 xv1],[-1e7 1e7], ... % andrey: it's faster define a big cursor then move it all the time.
'Color',ActiveColor, ...
'Marker',marker, ...
'MarkerEdgeColor','k',...
'Tag','Cursor', ...
'LineStyle','-', ...
'LineWidth',LineWidth, ...
'Parent',current_axh,...
'UserData',lnh,...
'Visible','off'); %do not display cursor initially
ph2 = line([xv2 xv2],[-1e7 1e7], ...
'Color',PassiveColor, ...
'Marker',marker, ...
'MarkerEdgeColor','k',...
'Tag','Cursor', ...
'LineStyle','-', ...
'LineWidth',LineWidth, ...
'Parent',current_axh,...
'UserData',lnh,...
'Visible','off'); %do not display cursor initially
% hide the cursors from the plot legend
set(get(get(ph1,'Annotation'),'LegendInformation'),'IconDisplayStyle','off');
set(get(get(ph2,'Annotation'),'LegendInformation'),'IconDisplayStyle','off');
setappdata(ph1,'sample_CurrentPos',x_init(1));
setappdata(ph2,'sample_CurrentPos',x_init(2));
%Set Application Data.
setappdata(current_axh,'ActiveCursor',ph1);
setappdata(current_axh,'PassiveCursor',ph2);
setappdata(current_axh,'ActiveColor',ActiveColor);
setappdata(current_axh,'PassiveColor',PassiveColor);
setappdata(current_axh,'LineWidth',LineWidth);
setappdata(current_axh,'CursorLock',0);
setappdata(current_axh,'DeltaLock',0);
end
fgh = getappdata(current_axh,'FigureHandle');
set(fgh,'WindowButtonDownFcn','ita_plottools_cursors(''down'',[],getappdata(gca,''FigureHandle''))')
% set(fgh,'WindowButtonDownFcn','ita_plottools_cursors(''down'',[],gcf)')
set(fgh,'DoubleBuffer','on'); %eliminate flicker
case 'activate'
%% ACTIVATE: Exchange the current active and passive cursors
% Update cursors in every axis
AllAxis = getappdata(getappdata(axh,'FigureHandle'),'AxisHandles');
for idx = 1:length(AllAxis)
axh = AllAxis(idx);
act_h = getappdata(axh,'ActiveCursor');
pas_h = getappdata(axh,'PassiveCursor');
act_coord = getappdata(act_h,'sample_CurrentPos'); %get cursorposition
pas_coord = getappdata(pas_h,'sample_CurrentPos');
if (strcmp(options,'left') && (pas_coord(1) < act_coord(1))) ||...
(strcmp(options,'right') && (act_coord(1) < pas_coord(1))) ||...
strcmp(options,'exchange')
%change colors
set(act_h,'Color',getappdata(axh,'PassiveColor'));
set(pas_h,'Color',getappdata(axh,'ActiveColor'));
%update tag
setappdata(axh,'ActiveCursor',pas_h);
setappdata(axh,'PassiveCursor',act_h);
if getappdata(axh,'CursorLock');
setappdata(axh,'DeltaLock',-getappdata(axh,'DeltaLock'));
end
end
end
case 'update'
%% UPDATE: Update the cursor value
try
ActiveHandle = getappdata(axh,'ActiveCursor');
active = getappdata(ActiveHandle,'sample_CurrentPos');
update(active(1),axh); %update location of active cursor
catch
ita_verbose_info('no valid cursor handle found.');
end
case 'yfocus'
%% YFOCUS: Zoom in the y axis
YLim = get (axh,'YLim'); %ylim(); %getappdata(axh,'YLim'); pdi changed!!!
if strcmp(getappdata(axh,'YAxisType'),'freq') %spectrogram
switch options
case 'up'
set(axh,'Clim',get(axh,'Clim')+1);
case 'down'
set(axh,'Clim',get(axh,'Clim')-1);
case 'increase'
set(axh,'Clim',get(axh,'Clim')+[1 0]);
case 'decrease'
set(axh,'Clim',get(axh,'Clim')-[1 0]);
otherwise
end
else %normal plots
if strcmp(getappdata(axh,'YAxisType'),'linear')
if strcmp(options,'increase')
y_lim = max(abs(YLim-mean(YLim))) * cursorUtils.zoom;
aux = floor(log10(y_lim));
y_lim = 10^aux * ceil(y_lim / 10^aux);
ylim_new = [-y_lim y_lim] + mean(YLim);
elseif strcmp(options,'decrease')
y_lim = max(abs(YLim-mean(YLim))) / cursorUtils.zoom;
aux = floor(log10(y_lim));
y_lim = 10^aux * floor(y_lim / 10^aux);
ylim_new = [-y_lim y_lim] + mean(YLim);
elseif strcmp(options,'up')
step = abs(diff(YLim))/10;
ylim_new = YLim + step;
elseif strcmp(options,'down')
step = abs(diff(YLim))/10;
ylim_new = YLim - step;
end
elseif strcmp(getappdata(axh,'YAxisType'),'db')
if strcmp(options,'increase')
jump = min(cursorUtils.y_jump/2,diff(YLim)/10);
ylim_new = [(YLim(1)-jump) YLim(2)]; %pdi:bugfix 0dB cursor stuck
elseif strcmp(options,'down')
if diff(ylim) / 2 <= cursorUtils.y_jump
yjump = diff(ylim)/10;
else
yjump = cursorUtils.y_jump;
end
ylim_new = YLim + yjump;
elseif strcmp(options,'decrease')
jump = min(cursorUtils.y_jump/2,diff(YLim)/10);
ylim_new = [(YLim(1)+jump) YLim(2)];%pdi:bugfix 0dB cursor stuck
elseif strcmp(options,'up')
if diff(ylim) / 2 <= cursorUtils.y_jump
yjump = diff(ylim)/10;
else
yjump = cursorUtils.y_jump;
end
ylim_new = YLim - yjump;
else
disp(options)
end
else
return
end
set(axh,'YLim',ylim_new);
end
case 'xfocus'
%% XFOCUS: Zoom around the active cursor
ActiveHandle = getappdata(axh,'ActiveCursor');
active = getappdata(ActiveHandle,'sample_CurrentPos');
if strcmp(options,'cursor') %show vicinity of active cursor
delta = cursorUtils.VICINITYDELTA;
new_lim = [active-delta active+delta];
elseif strcmp(options,'entire')
new_lim = cursorUtils.sampleOriginalXLim;
elseif strcmp(options,'between')
PassiveHandle = getappdata(axh,'PassiveCursor');
passive = getappdata(PassiveHandle,'sample_CurrentPos');
if passive < active
new_lim = [passive active];
elseif active < passive
new_lim = [active passive];
else % if they are the same, then don�t do anything
return
end
else
if strcmp(getappdata(axh,'XAxisType'),'time')
delta = abs(cursorUtils.sampleXLim - active);
if strcmp(options,'in')
delta = max(delta)/cursorUtils.zoom;
elseif strcmp(options,'out')
delta = max(delta)*cursorUtils.zoom;
elseif strcmp(options,'center') %center around active cursor
delta = min(delta);
if delta < 1
return
end
end
new_lim = round([active-delta active+delta]);
elseif strcmp(getappdata(axh,'XAxisType'),'freq')
active_db = log10(active);
delta = abs(log10(cursorUtils.sampleXLim) - active_db);
if strcmp(options,'in')
delta = max(delta) / cursorUtils.zoom;
elseif strcmp(options,'out')
delta = max(delta) * cursorUtils.zoom;
elseif strcmp(options,'center') %center around active cursor
delta = min(delta);
if delta == 0
return
end
end
new_lim = round(10.^[active_db-delta active_db+delta]);
else
error('Such x axis is not defined!')
end
end
if new_lim(1) < 1
new_lim(1) = 1;
end
if new_lim(2) > cursorUtils.sampleSize
new_lim(2) = cursorUtils.sampleSize;
end
cursorUtils.sampleXLim = new_lim;
new_lim = cursorUtils.xdata(new_lim);
cursorUtils.XLim = new_lim;
set(axh,'Xlim',new_lim);
update(active,axh);
case 'exp_move'
%% EXP_MOVE: Repositon the active cursor based on the passive cursor position
if getappdata(axh,'CursorLock')
return
else
ActiveHandle = getappdata(axh,'ActiveCursor');
active = getappdata(ActiveHandle,'sample_CurrentPos');
PassiveHandle = getappdata(axh,'PassiveCursor');
passive = getappdata(PassiveHandle,'sample_CurrentPos');
width = active - passive;
if strcmp(options,'increase')
step = ceil(log2(abs(width)+1));
act_new = passive + sign(width)*(2^step);
if (act_new < 1) || (act_new > cursorUtils.sampleSize)
return
end
else
step = floor(log2(abs(width)-1));
if step < 0
return
end
act_new = passive + sign(width)*(2^step);
end
end
update(act_new,axh);
case 'jump'
%% JUMP: Move active cursor to defined position
if strcmp(options,'home')
act_new = cursorUtils.sampleXLim(1);
elseif strcmp(options,'end')
act_new = cursorUtils.sampleXLim(2);
elseif strcmp(options,'together')
% Move active cursor to position from passie cursor
if getappdata(axh,'CursorLock')
return
else
act_new = getappdata(getappdata(axh,'PassiveCursor'),'sample_CurrentPos');
end
elseif strcmp(options,'max_act')
lnh = getappdata(axh,'ActiveChannelHandle');
ydata = get(lnh,'YData');
if strcmp(getappdata(axh,'XAxisType'),'time')
ydata = abs(ydata);
end
[junk,ind] = max(ydata); %#ok
act_new = ind(1);
elseif strcmp(options,'max_all')
ChannelHandles = getappdata(axh,'ChannelHandles');
aux = zeros(size(ChannelHandles,1),1);
ind = zeros(size(ChannelHandles,1),1);
for idx = 1:size(ChannelHandles,1)
ydata = get(ChannelHandles(idx),'YData');
if strcmp(getappdata(axh,'XAxisType'),'time')
ydata = abs(ydata);
end
[aux(idx),ind(idx)] = max(ydata);
end
[junk,idx] = max(aux); %#ok
act_new = ind(idx);
elseif strcmp(options,'begin')
%TO DO: check if signal has IR characteristics
if strcmp(getappdata(axh,'XAxisType'),'time')
lnh = getappdata(axh,'ActiveChannelHandle');
ydata = get(lnh,'YData');
act_new = ita_start_IR(ydata);
else
return
end
end
update(act_new,axh);
%% CURSOR MOVE: Move the active cursor with keyboard
case {'long_move','short_move'}
try %pdi
ActiveHandle = getappdata(axh,'ActiveCursor');
active = getappdata(ActiveHandle,'sample_CurrentPos');
delta = cursorUtils.sampleXLim(2) - cursorUtils.sampleXLim(1);
catch
return
end
% Define the new cursor position,
% based on the user choice of short or long
if strcmp(getappdata(axh,'XAxisType'),'time')
if strcmp(state,'long_move')
step = ceil(delta/cursorUtils.longjump);
else
step = ceil(delta/cursorUtils.SHORTJUMP);
end
if strcmp(options,'left')
act_new = active - step;
if act_new < 1
act_new = 1;
end
else
act_new = active + step;
if act_new > cursorUtils.sampleSize
act_new = cursorUtils.sampleSize;
end
end
elseif strcmp(getappdata(axh,'XAxisType'),'freq')
if strcmp(state,'long_move')
step = delta^(1/cursorUtils.longjump);
else
step = delta^(1/cursorUtils.SHORTJUMP);
end
if strcmp(options,'left')
act_new = floor(active / step);
if act_new < 1
act_new = 1;
end
else
act_new = ceil(active * step);
if act_new > cursorUtils.sampleSize
act_new = cursorUtils.sampleSize;
end
end
else
error('Such x axis is not defined!')
end
% Give new value of active cursor position to be updated
update(act_new,axh)
case 'newlimits' %get cursors to current figure limits
%% LOCK: Lock the distance between cursors
%pdi: new
try
cursorUtils.XLim = xlim;
% cursorUtils.YLim = ylim;
cursorUtils.sampleXLim = local_data2samples(cursorUtils.XLim,cursorUtils.xdata);
update(local_data2samples(cursorUtils.XLim(1),cursorUtils.xdata),axh);
ita_plottools_cursors('activate','exchange',axh);
update(local_data2samples(cursorUtils.XLim(2),cursorUtils.xdata),axh);
catch
ita_verbose_info('ITA_PLOTTOOLS_CURSORS: Could not adjust cursors to new limits');
end
case 'lock'
lock = getappdata(axh,'CursorLock');
if lock
setappdata(axh,'CursorLock',0);
else
setappdata(axh,'CursorLock',1);
active = getappdata(getappdata(axh,'ActiveCursor'),'sample_CurrentPos');
passive = getappdata(getappdata(axh,'PassiveCursor'),'sample_CurrentPos');
setappdata(axh,'DeltaLock',passive - active);
end
case {'off',0}
axh_backup = axh;
for jdx = 1:length(axh_backup)
axh = axh_backup(jdx);
fgh = getappdata(axh,'FigureHandle');
if isfield(get(fgh),'WindowButtonDownFcn')
set(fgh,'WindowButtonDownFcn','','WindowButtonUpFcn','')
end
if strcmp(options,'on')
h1 = findobj(axh,'Tag','InfoText'); %All text
h2 = findobj(axh,'Tag','Cursor'); %The cursors
lnh = local_findlines(axh);
set(lnh,'ButtonDownFcn','');
delete([h1;h2]);
else
try
AllAxis = getappdata(getappdata(axh,'FigureHandle'),'AxisHandles');
for idx = 1:length(AllAxis)
axh = AllAxis(idx);
h1 = findobj(axh,'Tag','InfoText'); %All text
h2 = findobj(axh,'Tag','Cursor'); %The cursors
lnh = local_findlines(axh);
set(lnh,'ButtonDownFcn','');
delete([h1;h2]);
end
clear global cursorUtils
catch
ita_verbose_info('ita_plottools_cursors::no cursors found',1)
end
end
end
end %switch/case end
function samples = local_data2samples(points,data_vector)
%% Converts a value in time or frequency in the x axis into its equivalent
%% sample values
samples = zeros(length(points),1);
for idx = 1:length(points)
[junk,samples(idx)] = min(abs(data_vector(1,:) - points(idx))); %#ok
end
function update(act_new,axh)
%% Update the cursors and axis limits for a new active cursor position
% Changed by Andrey and pdi - 02.04.2012
% getappdata(ActiveHandle, 'Coordinates') out
% ylim takes a lot of time, use only when necessary
global cursorUtils
%Case cursors are locked, update also passive cursor
if getappdata(axh,'CursorLock')
delta = getappdata(axh,'DeltaLock');
pas_new = act_new + delta;
if pas_new < 1
pas_new = 1;
elseif pas_new > cursorUtils.sampleSize
pas_new = cursorUtils.sampleSize;
end
%Don't do anything
else
PassiveHandle = getappdata(axh,'PassiveCursor');
pas_new = getappdata(PassiveHandle,'sample_CurrentPos');
end
xva = cursorUtils.xdata(min(act_new,length(cursorUtils.xdata)));
xvp = cursorUtils.xdata(min(pas_new,length(cursorUtils.xdata)));
% Update cursors in every axis
AllAxis = getappdata(getappdata(axh,'FigureHandle'),'AxisHandles');
for idx = 1:length(AllAxis)
axh = AllAxis(idx);
ActiveHandle = getappdata(axh,'ActiveCursor');
PassiveHandle = getappdata(axh,'PassiveCursor');
%finally set x-DATA of cursor - it is also plotted directly
set(PassiveHandle,'XData',[xvp xvp],'Visible','on'); %display cursor if updated (see "on", it's turned off there initially)
set(ActiveHandle, 'XData',[xva xva],'Visible','on'); %display cursor if updated
setappdata(PassiveHandle,'sample_CurrentPos',pas_new);
setappdata(ActiveHandle, 'sample_CurrentPos',act_new);
end
% drawnow
function lnh = local_findlines(axh)
%% Find a line to add cursor to
lnh = findobj(axh,'Type','line');
dots = findobj(axh,'Type','line','Tag','Cursor'); %Ignore existing cursors
lnh = setdiff(lnh,dots);
%Ignore lines with only one or two values - these are annotations
xdtemp = get(lnh,'XData');
lnhtemp = lnh;
lnh=[];
if ~iscell(xdtemp)
xdtemp = {xdtemp};
end;
for idx=1:length(xdtemp);
if length(xdtemp{idx})>2
lnh = [lnh; lnhtemp(idx)]; %#ok
end
end