- Undocumented Matlab - https://undocumentedmatlab.com -

Setting axes tick labels format

Posted By Yair Altman On April 18, 2012 | 18 Comments

Have you ever tried to customize the way in which tick labels appear in Matlab plot axes?
For example, setting the numerical precision of the labels, or adding some short descriptive text (for example, the units)? If you have, then I bet that you have encountered the following dilemma: Once we modify the tick labels (for discussion sake, let’s assume the Y axis, so this is done by updating the YTickLabel property), then the corresponding YTickLabelMode property changes from ‘auto’ to ‘manual’ and loses its relationship to the tick values (YTick). So, if we now zoom or pan the plot, our new labels remain unchanged although the tick values have changed, causing much panic and frustration… If we also set the tick values manually, this solves that problem but leaves us with another: now, when we zoom or pan, we no longer see any ticks or tick labels at all!

Original plot Manual labels, auto ticks Manual labels, manual ticks
Original plot (left)
Manual labels, auto ticks (center)
Manual labels, manual ticks (right)

Of course, we can always trap the zoom and pan callback functions to update the tick labels dynamically while keeping the tick values automatically. This will work for these cases, but we need to do it separately for zoom and pan. Also, if we modify the axes limits explicitly (via the corresponding YLim property) or indirectly (by modifying the displayed plot data), then the callbacks are not called and the labels are not updated.

The solution – using a property change listener

A better way to solve this problem is to simply trap changes to the displayed tick values, and whenever these occur to call our dedicated function to update the labels according to the new tick values. This can be done by using UDD, or more precisely the ability to trap update events on any property [1] (in our case, YTick). Such a mechanism was already demonstrated here [2] in 2010, as one way to achieve continuous slider feedback. The idea is to use the built-in handle.listener function with the PropertyPostSet event, as follows:

hhAxes = handle(hAxes);  % hAxes is the Matlab handle of our axes
hProp = findprop(hhAxes,'YTick');  % a schema.prop object
hListener = handle.listener(hhAxes, hProp, 'PropertyPostSet', @myCallbackFunction);
setappdata(hAxes, 'YTickListener', hListener);

Note that we have used setappdata to store the hListener handle in the axes. This ensures that the listener exists for exactly as long as the axes does. If we had not stored this listener handle somewhere, then Matlab would have immediately deleted the listener hook and our callback function would not have been called upon tick value updates. Forgetting to store listener handles is a common pitfall when using them. If you take a look at the addlistener function’s code, you will see that it also uses setappdata after creating the listener, for exactly this reason. Unfortunately, addlistsner cannot always be used, and I keep forgetting under which circumstances, so I generally use handle.listener directly as above: It’s simple enough to use that I find I never really need to use the simple addlistener wrapper, but you are welcome to try it yourself.
That’s all there is to it: Whenever YTick changes its value(s), our callback function (myCallbackFunction) will automatically be called. It is quite simple to set up. While we cannot use TeX in tick labels yet (this will change in the upcoming HG2 [3]), using sprintf formatting does enable quite a bit of flexibility in formatting the labels. For example, let’s say I want my tick labels to have the format ‘%.1fV’ (i.e., always one decimal, plus the Volts units):

function myCallbackFunction(hProp,eventData)    %#ok - hProp is unused
   hAxes = eventData.AffectedObject;
   tickValues = get(hAxes,'YTick');
   newLabels = arrayfun(@(value)(sprintf('%.1fV',value)), tickValues, 'UniformOutput',false);
   set(hAxes, 'YTickLabel', newLabels);
end  % myCallbackFunction

Manual labels, automatically updated Manual labels, automatically updated
Manual labels, automatically updated

Handling duplicate tick labels

Of course, ‘%.1fV’ may not be a good format when we zoom in to such a degree that the values differ by less than 0.1 – in this case all the labels will be the same. So let’s modify our callback function to add extra decimals until the labels become distinct:

function myCallbackFunction(hProp,eventData)    %#ok - hProp is unused
   hAxes = eventData.AffectedObject;
   tickValues = get(hAxes,'YTick');
   %newLabels = arrayfun(@(value)(sprintf('%.1fV',value)), tickValues, 'UniformOutput',false);
   digits = 0;
   labelsOverlap = true;
   while labelsOverlap
      % Add another decimal digit to the format until the labels become distinct
      digits = digits + 1;
      format = sprintf('%%.%dfV',digits);
      newLabels = arrayfun(@(value)(sprintf(format,value)), tickValues, 'UniformOutput',false);
      labelsOverlap = (length(newLabels) > length(unique(newLabels)));
      % prevent endless loop if the tick values themselves are non-unique
      if labelsOverlap && max(diff(tickValues))< 16*eps
         break;
      end
   end
   set(hAxes, 'YTickLabel', newLabels);
end  % myCallbackFunction

non-distinct labels distinct labels
Non-distinct labels                   distinct labels

ticklabelformat

Based on a file that I received from an anonymous reader a few years ago, I have prepared a utility called ticklabelformat that automates much of the set-up above. Feel free to download [4] this utility and modify it for your needs – it’s quite simple to read and follow. The usage syntax is as follows:

ticklabelformat(gca,'y','%.6g V')  % sets y axis on current axes to display 6 significant digits
ticklabelformat(gca,'xy','%.2f')   % sets x & y axes on current axes to display 2 decimal digits
ticklabelformat(gca,'z',@myCbFcn)  % sets a function to update the Z tick labels on current axes
ticklabelformat(gca,'z',{@myCbFcn,extraData})  % sets an update function as above, with extra data

Addendum for HG2 (R2014b or newer releases)

As I’ve indicated in my comment response [5] below, in HG2 (R2014b or newer) we need to listen to the HG object’s MarkedClean event [6], rather than to a property change event.
I’ve updated my ticklabelformat utility accordingly, so that it works with both HG1 (pre-R2014a) and HG2 (R2014b+). Use this utility as-is, and/or look within its source code for the implementation details.

Categories: Handle graphics, Listeners, Medium risk of breaking in future versions, Undocumented function


18 Comments (Open | Close)

18 Comments To "Setting axes tick labels format"

#1 Comment By Ed On April 25, 2012 @ 06:56

This is fantastic! I had long since given up hope of finding a solution to this problem. Thanks very much.

#2 Comment By Lubos Smolik On January 9, 2013 @ 02:40

Brilliant!

Very useful in 3D rotordynamics graphs when

axis normal;

gives ugly results. Thank you very much 😉

#3 Comment By Yuri On April 30, 2013 @ 12:12

Yair,
Have you looked at how MATLAB works with scientific notation format of tick labels putting the exponent only into one place on the axes? The XTickLabels are only what one can see there without exponent. Where MATLAB hides the exponent? Is there a way to change it? to enforce this format? I know only way with manually changing the ticks and adding exponent as a text object. As in this question, for example:
[13]
Any undocumented staff?

#4 Comment By Yair Altman On April 30, 2013 @ 12:52

@Yuri – I do not know of a way to do this with the current HG system. HG axes have some related hidden properties (ExpFontAngle, ExpFontName, ExpFontSize, ExpFontStrikeThrough, ExpFontUnderline, ExpFontUnits, ExpFontWeight) but they don’t appear to have any effect as far as I can tell (maybe someone could find out).

I believe that I saw some configurable exponent properties in the [14]. I haven’t tested HG2 on the latest releases lately, so try it (using the -hgVersion2 startup switch as mentioned in the article) and let us know what you discover.

p.s. – wow, has it been 2 full years already? – it’s about time HG2 got released…

#5 Comment By Yair Altman On May 28, 2013 @ 16:55

@Yuri – as followup, customizing the exponent is indeed possible in HG2, as I have [15].

#6 Comment By Nico On May 13, 2013 @ 05:09

Just found this post yesterday and really liked it. Using this method, you can avoid the stupid identical labels that appear when you zoom in real close in a large plot. Also, the ticklabelformat function from matlabcentral can be modified quite easily to scale the tick values, which can be extremely helpful. Thanks, Yair.

#7 Comment By damayi On December 2, 2014 @ 23:38

I always use this method for MATLAB 2013B, it works fine.
However, it does not work for 2014b, the latest released version.

#8 Comment By Yair Altman On December 4, 2014 @ 14:35

@Damayi – This is a known issue that is due to the changes in property listeners that were done for the new HG2 graphics engine in R2014b.

#9 Comment By damayi On December 27, 2014 @ 01:23

Do you have a resolution for this problem?

#10 Comment By Yair Altman On January 5, 2015 @ 07:28

@Damayi – Yes, in HG2 we need to listen to the HG object’s MarkedClean event, rather than to a property change event.

I’ve updated my [4] accordingly, so that it works with both HG1 (pre-R2014a) and HG2 (R2014b+). Use this utility as-is, and/or look within its source code for the implementation details.

#11 Comment By Robert On January 25, 2015 @ 08:41

Yair,

thanks for providing this kind of input and the ticklabelformat. I’m also thankful for the comments as I had no idea why the callbacks for axis properties do not work anymore with HG2. Already in HG1 some things are confusing, since somehow the event argument is of different type, depending on…well I don’t really know. But in HG2 addlistener does somehow not work on those properties.

Would it be possible to provide a fully working example callback function for HG2 (maybe in the docstring of the ticklabelformat function)?. The one given above apparently does not work (only tested with HG2). The second argument has only two properties: .Source and something else. I tried to access the ticks via – I think it was – .Source.Tick, but it did not work properly and even crashed MATLAB. All my old callbacks do not work anymore with the new HG2. Sad, that TWM does not supply easy instructions on updating customized tick labels as this is quite a substantial thing, when examining data using a plot and the pan/zoom functionality. Thanks.

#12 Comment By Sandor Toth On August 3, 2015 @ 07:01

Dear Yair,

do you know a way to change the XTickLabel vertical position, a.k.a its distance from the x-axis line? The generally suggested solution is to recreate the labels using the text() command, but there should be a simpler way to do it!
Do you have any suggestion?

Thanks,
Sandor

#13 Comment By fulvio setti On April 28, 2016 @ 15:23

YEEEESSSS !!! It’s many time that i searched this way to have MORE digit in the graphs !!!! THAAANK YOU !!!!

#14 Comment By Jim Hokanson On November 29, 2017 @ 21:18

Yair,

Would it be possible to add an “update” section by the blurb on the listener to describe the change with HG2? I’m specifically thinking something that references MarkedClean and YRuler. I’m fine but it might be nice for others …

Thanks,
Jim

#15 Comment By Yair Altman On December 6, 2017 @ 16:29

@Jim – done 🙂

#16 Comment By Ron Dekel On June 20, 2018 @ 15:43

I wonder – is it possible to use images as axis tick labels?
Ron

#17 Comment By Yair Altman On June 21, 2018 @ 16:14

@Ron – Tick labels are strings. If you want to use images, then set the ticklabels to empty (i.e., do not show any ticks) and programmatically add images at the relevant [negative] axes locations based on the computed tick positions. You will need to update the images and their location whenever the axes resizes or zooms or pans or changes its ticks in whichever way ( [16]). To be honest, I’m not sure that it’s worth all this effort…

#18 Comment By Antonius Armanious On August 7, 2019 @ 12:51

Do you know of a way to change the position of one of the tick labels so that it does not overlap with the tick labels from the neighbouring axes?
Thanks


Article printed from Undocumented Matlab: https://undocumentedmatlab.com

URL to article: https://undocumentedmatlab.com/articles/setting-axes-tick-labels-format

URLs in this post:

[1] ability to trap update events on any property: http://undocumentedmatlab.com/blog/udd-events-and-listeners/

[2] demonstrated here: http://undocumentedmatlab.com/blog/continuous-slider-callback/#Property_Listener

[3] HG2: http://undocumentedmatlab.com/blog/matlab-hg2/

[4] download: http://www.mathworks.com/matlabcentral/fileexchange/36254-ticklabelformat

[5] my comment response: http://undocumentedmatlab.com/blog/setting-axes-tick-labels-format#comment-339729

[6] HG object’s MarkedClean event: http://undocumentedmatlab.com/blog/undocumented-hg2-graphics-events

[7] Customizing axes tick labels : https://undocumentedmatlab.com/articles/customizing-axes-tick-labels

[8] Customizing axes part 5 – origin crossover and labels : https://undocumentedmatlab.com/articles/customizing-axes-part-5-origin-crossover-and-labels

[9] Axes LooseInset property : https://undocumentedmatlab.com/articles/axes-looseinset-property

[10] Customizing axes rulers : https://undocumentedmatlab.com/articles/customizing-axes-rulers

[11] Undocumented HG2 graphics events : https://undocumentedmatlab.com/articles/undocumented-hg2-graphics-events

[12] FIG files format : https://undocumentedmatlab.com/articles/fig-files-format

[13] : http://stackoverflow.com/questions/13326894/force-exponential-format-of-ticks-like-matlab-does-it-automatically

[14] : https://undocumentedmatlab.com/blog/matlab-hg2/

[15] : https://undocumentedmatlab.com/blog/hg2-update/

[16] : https://undocumentedmatlab.com/blog/undocumented-hg2-graphics-events

Copyright © Yair Altman - Undocumented Matlab. All rights reserved.