Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

plot

matlab

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:

Issue Example

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.

like image 681
Werner Avatar asked Jul 11 '13 02:07

Werner


2 Answers

How about explicitly setting the z-position of the y-label via it's handle? If I put this after your loop it seems to work in R2012b:

ylh = get(gca,'Ylabel')
ylpos = get(ylh,'Position');
set(ylh,'Position',[ylpos(1:2) 0]);

If I adjust the z-position I can get the y-label to pop up and even interleave between the datatips. I'm not completely sure if this is a bug or a feature, but sometimes there are workarounds to rendering issues that involve tweaking the position of an element slightly to get Matlab to recalculate and redraw the figure.

like image 121
horchler Avatar answered Nov 15 '22 04:11

horchler


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

  1. create an axes (hax_1) with the function to be plotted, without the datatips
  2. create an axes (hax_2) with the function to be plotted AND the datatips but without the axes labels
  3. set hax_2 visibility to 'off' (this will plot the datatips above the first axes labels)
  4. 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.

like image 34
marsei Avatar answered Nov 15 '22 04:11

marsei