Put datatip stack on top of axis label and update axes label after a change was done on axes position

matlab, plot

Solution

A workaround that uses both linkaxes, so useful when zooming/panning multiple plots, and the visibilty of plots.

- create an axes (hax_1) with the function to be plotted, without the datatips

- create an axes (hax_2) with the function to be plotted AND the datatips but without the axes labels

- set hax_2 visibility to 'off' (this will plot the datatips above the first axes labels)

- link the 2 axes with linkaxes([hax_1 hax_2],'xy'); (zooming and panning on one of the axes will modify on the fly the second axes)

This gives with your first code (not the edited one):

gradientStep = 1e-1;   
x=-100:gradientStep:100; xSize=numel(x);
y=x.^3-x.^2;   
figH=figure(42);
plot(x,y);   
ylabel('YLabel (YUnits)','FontSize',16)
xlabel('XLabel (XUnits)','FontSize',16)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% modification starts

hax_1 = gca;
hax_2 = axes('Position', get(hax_1,'Position'));
lineH = plot(x,y);
linkaxes([hax_1 hax_2],'xy');
set(hax_2,'Visible', 'off');

% modification ends
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

dcH=datacursormode(figH); 
nTips = 20; % May change the loop for a datatip at x=-78.
for pos = round(linspace(2,xSize,nTips))
  datatipH=dcH.createDatatip(lineH,struct('Position',[x(pos) y(pos)]));
  orientation = 'top-left';
  if pos>1
    tipText{1} = 'The grandient here is: ';
    tipText{2} = ['\Deltax:',sprintf('%d',x(pos)-x(pos-1)),' XUnits'];
    tipText{3} = ['\Deltay:',sprintf('%d',y(pos)-y(pos-1)),' YUnits'];
  else
    tipText = 'Cannot calculate gradient here.';
  end
  bkgColor = [1 1 .5]; % May be removed.
  fontSize = 12; % May be removed.
  set(datatipH,'StringFcn',(@(~,~) tipText),'Orientation',orientation,'backGroundColor',bkgColor,'FontSize',fontSize,'Draggable','on');  % Only set text and orientation needed.    
  datatipTextBoxH=get(datatipH,'TextBoxHandle');
  set(datatipTextBoxH,'HorizontalAlignment','left','VerticalAlignment','top','Margin',0.02,'Interpreter','tex','FontName','Courier','FontSize',fontSize); % May be removed.   
end

I am on OSX 10.8.4, R2012b, and had the same issue as yours. Here, the proposed solution plots datatips above the axis labels and allow zooming/panning without making use of undocumented features of matlab.

Problem

This issue is only for unix matlabs, windows users won't be able to reproduce it. I am having trouble while trying to create datatips in which are on top of the y axis label. The following picture illustrate the issue: As you can see, the datatips created close to the ylabel will get bottom to the ylabel text, while the desire effect is the opposite: the datatip to be on top of the axis label. I generated the plot with the following (not so minimal) code, which is available bellow. You may remove the lines commented with `% may be removed`, or even just put a datatip on −78 instead of a loop in order to achieve a faster testing script, but I leave this code if someone one day wants it to create custom datatips (in this case, consider seeing also http://undocumentedmatlab.com/blog/controlling-plot-data-tips/): ``` gradientStep = 1e-1; x=-100:gradientStep:100; xSize=numel(x); y=x.^3-x.^2; figH=figure(42); lineH=plot(x,y); ylabel('YLabel (YUnits)','FontSize',16) xlabel('XLabel (XUnits)','FontSize',16) dcH=datacursormode(figH); nTips = 20; % May change the loop for a datatip at x=-78. for pos = round(linspace(2,xSize,nTips)) datatipH=dcH.createDatatip(lineH,... struct('Position',[x(pos) y(pos)])); orientation = 'top-left'; if pos>1 tipText{1} = 'The grandient here is: '; tipText{2} = ['\Deltax:',sprintf('%d',x(pos)-x(pos-1)),' XUnits']; tipText{3} = ['\Deltay:',sprintf('%d',y(pos)-y(pos-1)),' YUnits']; else tipText = 'Cannot calculate gradient here.'; end bkgColor = [1 1 .5]; % May be removed. fontSize = 12; % May be removed. set(datatipH,'StringFcn',(@(~,~) tipText),'Orientation',... orientation,'backGroundColor',bkgColor,'FontSize',... fontSize,'Draggable','on'); % Only set text and orientation needed. datatipTextBoxH=get(datatipH,'TextBoxHandle'); % May be removed. uistack(datatipH,'top'); % Unfortunately makes no effect, since the ylabel handles is not at the axes children list datatipTextBoxH=get(datatipH,'TextBoxHandle'); set(datatipTextBoxH,'HorizontalAlignment','left',... 'VerticalAlignment','top','Margin',0.02,'Interpreter',... 'tex','FontName','Courier','FontSize',fontSize); % May be removed. end uistack(get(gca,'YLabel'),'bottom') % Also makes no effect, for the same reason. ``` I have tried: - uistack all datatips to top, - uistack the label to bottom (both of them don't work because the ylabel handle is not in the axes children handles). Update: After implementing the @horchler' solution, a new issue appeared: when zooming and panning the axes, the axes label would also move. I've found a small fix for that, I changed the following aspects: - Set datatip z-value to 1, so that it will always be higher than ylabel axis z. - Recreating the ylabel afterwards a pan or zoom movement occurs. For this, I implemented `localAxisUpdate` function that get the old ylabel properties, replace it by a new one, and them reset all settable properties but the ylabel position. For this I used this reference The resulting code is as follows: ``` function test gradientStep = 1e-1; x=-100:gradientStep:100; xSize=numel(x); y=x.^3-x.^2; figH=figure(42); lineH=plot(x,y); ylabel('YLabel (YUnits)','FontSize',16) xlabel('XLabel (XUnits)','FontSize',16) dcH=datacursormode(figH); %nTips = 20; %for pos = round(linspace(2,xSize,nTips)) pos = find(x>-78,1); datatipH=dcH.createDatatip(lineH,... struct('Position',[x(pos) y(pos) 1])); orientation = 'top-left'; if pos>1 tipText{1} = 'The grandient here is: '; tipText{2} = ['\Deltax:',sprintf('%d',x(pos)-x(pos-1)),' XUnits']; tipText{3} = ['\Deltay:',sprintf('%d',y(pos)-y(pos-1)),' YUnits']; else tipText = 'Cannot calculate gradient here.'; end bkgColor = [1 1 .5]; % Light Yellow fontSize = 12; set(datatipH,'StringFcn',(@(~,~) tipText),'Orientation',... orientation,'backGroundColor',bkgColor,'FontSize',... fontSize,'Draggable','on'); datatipTextBoxH=get(datatipH,'TextBoxHandle'); datatipTextBoxH=get(datatipH,'TextBoxHandle'); set(datatipTextBoxH,'HorizontalAlignment','left',... 'VerticalAlignment','top','Margin',0.02,'Interpreter',... %end % Set changes due to zoom and pan to also use adaptativeDateTicks: set(zoom(figH),'ActionPostCallback',... @(~,~) localAxisUpdate(gca)); set(pan(figH),'ActionPostCallback',... @(~,~) localAxisUpdate(gca)); end function localAxisUpdate(aH) % Fix axis label on top of datatip: ylh = get(aH,'YLabel'); % Get original YLabel properties ylstruct = get(ylh); % Get settable fields: yfieldnames=fieldnames(rmfield(set(ylh),'Position'))'; % Remove old label: delete(ylh) % Create new one: ylh = ylabel(aH,'Dummy'); % Send it bottom: ylpos = get(ylh,'Position'); set(ylh, 'Position', [ylpos(1:2) 0]); % Reset new ylabel to old values: for field=yfieldnames field = field{1}; set(ylh,field,ylstruct.(field)); end end ``` This approach creates an unwanted effect, which is the ylabel will move across the figure until the mouse button is released. How can I remove this unwanted effect ? I think the solution may be more or less as it was done in undocummented matlab solution for updating axes ticks, but now I would need the listener to the ylabel postset property. Does anyone knows how to do that? If you are a windows user, you can also try to help, all I need is to reset the position of the ylabel after a change (pan, zoom or whatever) is made on the figure.

Original source