Data labels above bars on grouped bar plot

I'm making a grouped bar plot (i.e., a bar plot with multiple bars in each category). I would like to add labels at the top of each bar to indicate its height. I was planning to just use the text function. However, bars within a given group all have the same x location (i.e get(h,'XData') is the same for all bar series). I'm not sure how to find the proper x location for each bar within a given group. Any ideas?
Thanks, Justin

3 Comments

Matlab should offer bar labels as part of the bar function since bar plots are often presented this way. Really appreciate the workaround answers below though!
Submit the enhancement request to TMW @ www.mathworks.com
At long last, bar labels are now built-in to MATLAB as of R2024b.
Check out the new example on the bar doc page: Create Bar Labels and the properties on the Bar object's property page.

Sign in to comment.

 Accepted Answer

dpb
dpb on 16 May 2014
Edited: dpb on 16 May 2014
You're on the right track, the center of each group is at the axis tick value. For each bar in the group, use that value plus/minus a delta to locate the x position for your text.
I did an example of this for another poster within the last few weeks at most altho I don't have the link at hand. Perhaps a search will uncover it.
ADDENDUM:
OK, I looked at past answers--this one is pretty close altho I thought I did another. Maybe it was in the newsgroup instead...

6 Comments

Thanks for your response. Do you know of a way to figure out what delta to use, other than just trial and error? i.e., is do you know how, given the xlimits and number of bars/group, to determine the exact position of each bar? The reason I ask is because I need my code to work for plots with varying sizes, aspect ratios, xlimits, etc so I don't want to have to trial and error for each condition.
dpb
dpb on 16 May 2014
Edited: dpb on 17 May 2014
The center is at the ticks and the width parameter controls what percentage of the difference is used. I've not tried to find a completely general formula, no.
I suspect if one were to read the bar source one could probably (at least eventually) decipher the algorithm used, but I've not done so.
ADDENDUM:
Well, I take that back -- the guts of the plotting is buried completely in the bowels of the HG object hierarchy and nothing is directly visible by the input X,Y values.
Think the only hope is to do some experiments and build an empirical adjustment correlation.
So I figured out a way to do this. I basically use the Xdata and Ydata properties of the underlying patch objects that are created when bar() is called. Thanks again for your comments. I appreciate it. Here's an example of some code:
Y=random('unif',0,100,[4 3]);
h=bar(Y);
ybuff=2;
for i=1:length(h)
XDATA=get(get(h(i),'Children'),'XData');
YDATA=get(get(h(i),'Children'),'YData');
for j=1:size(XDATA,2)
x=XDATA(1,j)+(XDATA(3,j)-XDATA(1,j))/2;
y=YDATA(2,j)+ybuff;
t=[num2str(YDATA(2,j),3) ,'%'];
text(x,y,t,'Color','k','HorizontalAlignment','left','Rotation',90)
end
end
ylim([0 100])
Good...I had thought would be able to find the computation of those in the code to save "handle diving" instead. Your way should work consistently; I'll stick a copy in my "bits 'n pieces" file (and then hopefully remember it's there next time :) ).
Hello, I just tumbled into this post and I found it very interesting in my application. in this tripple bar plot, you used the array of [4 3] that means 4 intervals with 3 bars in each interval. But the function that you have used is a random number generator. My question is that how should I change the code in the case that I have 3 arrays as functions. For example I have three following arrays and I want to plot them in triple plot.
Y1 = [1 3 6 7 9 12 11]; Y2 = [12 5 8 13 11 1 4]; Y3 = [11 31 16 17 7 113 15];
Just past 'em together...
Y=[Y1;Y2;Y3].'; % create nx3 column array for *bar*
NB: The latest release of bar uses HG2 and returns a handle to a barobject, rather than the former bar series objects. This new object is essentially opaque to the the details and the data for the patches used to draw the bars isn't available from which to compute the individual bar positions so labelling bars as this example does won't work. I've not got a more recent version so not sure what the workaround is, if there is one or if one must resort back to the earlier "trick" I illustrated before.

Sign in to comment.

More Answers (6)

With the release of MATLAB R2024b, this has gotten much easier.
Starting in MATLAB R2024b, Bar objects now have a Labels property that can be used to add labels to the tops of your bars. You can read about the new properties on the Bar objects property page.
For example:
b = bar(magic(3));
b(1).Labels = ["a","b","c"];
b(2).Labels = ["d","e","f"];
b(3).Labels = ["g","h","i"];
Solution for MATLAB R2019b through R2024a
If you can't upgrade to MATLAB R2024b just yet, you can still do this with a bit more work by using XEndPoints and YEndPoints properties that were added in R2019b.
b = bar(magic(3));
text([b.XEndPoints], [b.YEndPoints], ["a","b","c","d","e","f","g","h","i"], ...
VerticalAlignment='bottom', HorizontalAlignment='center')

9 Comments

dpb
dpb on 1 Nov 2024
Edited: dpb on 1 Nov 2024
It's a start, certainly, I submitted the first enhancement request about 20 years ago, I believe. :)
Why, however, only give the labels the two possible location values at the top and not at least, 'middle' and 'bottom'? Better yet, would be an alternate position by data height. And, it still appears the penchant of not exposing the handles to the labels doesn't let the user do anything further than what is already canned, so one will still have to do them manually for any special effects TMW didn't think about. At least having exposed the 'X|YEnd' properties avoids all the prior machinations that were required during the dark interval when couldn't get to much of anything; it is far less of an opaque object than was for a while which is at least progress in the right direction even if not fully transparent.
But, it will cover the routine, and a welcome addition at that...
@dpb Thanks for the feedback. I recognize that this feature is limited in the ways you describe (no access to the handles and limited layout locations) but I think it provides value as a quick solution that satisfies many users. I had hoped to add the ability to put the labels in the center or bottom of the bars (and we haven't ruled that out) but some technical issues prevented doing that in R2024b in a way that we felt would meet our quality standards.
As for exposing the handles to the labels: This is a double-edged sword. Exposing the handles makes it substantially more difficult to release a feature that we can confidently say will support everything you can do with direct access to the handles, while maintaining the quality standards our users value. This would make it harder to ship features like this. We are trying to satisfy both the casual users who just want a way to quickly add labels to their bars as well as the advanced users who are looking for more control. For now, if you want that extra level of control, you can still use the XEndPoints and YEndPoints properties with manually positioned text objects, and we have no intentions of taking that away.
I've noted your feedback on the enhancement request to add "center" and "bottom" to the options for LabelLocation. Out of curiosity, we currently offer "end-inside" and "end-outside", do you see value in allowing "bottom-outside" or do you think that "bottom-inside" would satisfy most people?
There are always ways we can improve and we appreciate the feedback. Keep it coming!
Certainly I agree it is a step forward ane will, undoubtedly, meet expectations (or at least minimum requirements) for a large fraction of users; I was just somewhat surprised to not see more regarding the available positions when the feature was introduced. From the outside not having access to the code; it wouldn't seem computing the middle or any fraction of the bar segment heights would be any particular implementation challenge; if it is as you say, an issue, then so be it. As noted, since we've now gotten past that very difficult period when almost nothing useful was exposed and the EndData values are provided that do return the needed locations, one can still do it manually if must have something else for whatever reason; either purely aesthetics or the need to do something else on top of the basic bar.
As to your Q? regarding the two 'bottom' options, I would agree that 'bottom-inside' would undoubtedly be by far the most common use; the 'bottom-outside' would generally conflict with the axes and could be confusing for stacked plots although again, my preference would always trend towards giving the user the flexibility; if they make a confusing graph as a result of misdesign that's their responsibility, not MATLAB (nor TMW).
bar has been one of my whipping boys for as long as I've used MATLAB, which goes back to the 3.1 days; I felt it was poorly designed/implemented from the git-go and that has, I conject, hampered much in its development ever since. But again, that's purely from the outside having no real knowledge of the internals, but many things over the years have been the subject of countless posts in the old newsgroup and now in the forum.
For various reasons, I've stayed at R2022b so haven't gotten the opportunity to really explore yet; just read the doc on new properties..
As for other feedback, has the thing about a one-bar 'stacked' been addressed? Well, let's see---the example for stacked is
subplot(2,1,1)
y = [2 2 3; 2 5 6; 2 8 9; 2 11 12];
bar(y,'stacked')
subplot(2,1,2)
y = [2 5 6];
bar(y,'stacked')
Nope; it still ignores the 'stacked' directive and creates three bars instead...that, too, has existed from the start. Granted it's probably an edge case, but... :)
Another of my pet peeves that is particularly apropos to bar graphs is the lack of any builtin hatching patterns; distinguishing bars by color alone can become extremely difficult; particularly if/when one is in a black-white presentation mode. That also isn't as much of a limitation now as it used to be, but still can be for publication.
ADDENDUM:
"......add "center" and "bottom" to the options for LabelLocation. ... we currently offer "end-inside" and "end-outside", ..."
Out of curiosity from this end (so to speak :) ); I suppose 'End' was chosen to match the 'X|YEnd' properties, but it one introduces 'bottom' for the label position, the orthogonal name would have been 'top' for the labels. It seems that natural name to have chosen instead for it given the dfifference in functionality between the two properties. But, once introduced, while not what I would have chosen, it's probably not wise to change or add an aliased name (thinking of the confusion with 'NumHeaderLines' with the readXXX functions as example).
And, on exposing the handles to internal objects and the imposed condition on ensuring reliability of "support[ing] everything you can do with direct access to the handles", my POV is that that again reverts to the user although I grok that it can/could/would(?) probably create more service requests. My solution in that case would be to make them hidden, but discoverable so they're not documented but the advanced user can still get to them, but it's "not supported" if they break something.
The issue in the prior time with bar was that not only were the endpoints hidden, they weren't even discoverable so one was forced to calculate the now-EndPoint values from the patch data coordinates. That was beyond the capabilities of any casual user and many fairly experienced ones. Now, I agree it's less of an issue than then because the EndPoint properties are exposed and as long as one can add to the existing axes(*), then even if somewhat less convenient, one can create and have access to the text handles for further customization.
(*) One of my real pet peeves now is the penchant towards adding these new custom graphics containers that are self-contained objects and that don't allow much in the way of customization because any time you try to access the underlying axes object, the internal code either throws an error or just completely deletes the existing and leaves you with nothing. Invevitably, the casual user gets the base figure that looks a lot like what they're looking for, but then they need/want some customization but are totally locked out from being able to do anything with that as a base--and there's no way to create the same presentation with base HG2 other than start from scratch.
Regarding bar(y,'stacked'):
This behavior was a concious decision and we are unlikely to change it. It behaves that way for consistency with how other commands (like plot) behave.
  • When you pass a vector into a command like bar or plot (that create graphics objects that display vector based data), we always create one object (one Bar or one Line object).
  • When you omit x, we always (in every one of our commands that support optional x), auto-populate x with either 1:numel(y) (for vectors) or 1:height(y) (for matrices).
  • stacked bar plots are only meaningful when you have multiple objects. Technically, the bar created with bar(y, 'stacked') above is a stacked bar, but it is stack with only one item.
We used to have an issue with bar that it didn't consistently handle scalar x, but that has since been fixed (in MATLAB R2019b). If your goal is a single stacked bar, you can do that (and we have a documentation example showing how):
subplot(2,1,1)
y = [2 2 3; 2 5 6; 2 8 9; 2 11 12];
bar(1:3, y,'stacked')
subplot(2,1,2)
y = [2 5 6];
bar(1, y,'stacked')
Out of curiosity from this end (so to speak :) ); I suppose 'End' was chosen to match the 'X|YEnd' properties, but it one introduces 'bottom' for the label position, the orthogonal name would have been 'top' for the labels.
If you could specify that you wanted the labels to be on the 'bottom', which of the four letters would you expect to be the position of the label in this diagram? B would seem to be the "obvious" choice, but if you think of Horizontal as merely tipping the bar on its side then A would be the obvious choice.
bar(6, Horizontal="on", FaceAlpha = 0.3)
text(0.5, 1, 'A')
text(3, 0.75, 'B')
text(5.5, 1, 'C')
text(3, 1.25, 'D')
I think it would be difficult for the documentation staff to convincingly explain that A is the "bottom" of this bar chart. Technically they could probably do so by saying that it's the bottom relative to the base of the bar, but it's starting to get more complicated, and it would mess with users' mental models even more if we had to state which is the base of this bar.
figure
bar(-6, Horizontal = 'on')
Would the label at the "bottom" of this bar be next to the 1 or on the opposite side of the axes from the 1? In fact, what's the answer to that question for a similar but vertically oriented bar?
figure
bar(-6)
Lots of things are easier before you introduce rotations / transposes and/or reflections / negative numbers :)
dpb
dpb on 2 Nov 2024
Edited: dpb on 2 Nov 2024
Good points, @Steven Lord!; indeed I hadn't really thought about horizontal bars; don't believe I've ever had call for one in almost 40 years of consulting, but I'm sure there are occasions.
I suppose trying having 'left', 'right' when is horizontal instead of 'top','bottom' when vertical would also add excessive cumbersomness to the UI, too, granted...although that would be the way to express.
I wondered about the negative and stacked as well...
Ah well, a long journey begins with a single, small step.
I suppose trying having 'left', 'right' when is horizontal instead of 'top','bottom' when vertical would also add excessive cumbersomness to the UI, too, granted...although that would be the way to express.
Funny that you ordered them that way. I caught myself doing that as well when typing a first draft of that comment. Most of the time (when the data was positive) the 'left' label position for a horizontal bar would be the equivalent of the 'bottom' label position for a vertical bar rather than 'top'.
Naming things in a software design often can be easy in one (admittedly probably the most common) case for the designed functionality, but one of the challenges of designing/naming things (functions/methods, classes, properties, etc.) is to make the designs/names understandable, discoverable, and more-or-less accurate (or at least not misleading) in the general case. If I asked you to guess what percentage of the time I've spent in design reviews in have been spent arguing over and/or brainstorming names to generalize them, make them discoverable, and to help users be able to understand their purpose successfully, I'm almost certain you'd guess low.
And sometimes we can't really agree: we spent a lot of time brainstorming other names for Binary Single eXpansion FUNction aka bsxfun.
dpb
dpb on 2 Nov 2024
Edited: dpb on 2 Nov 2024
I really didn't intend that left,right match top,bottom in the orderwhen posting that, I was just listing the alternative names that were the directional equivalents when horizontal instead of vertical. But, you are correct in that with "normal" positive values and increasing axis values to the right, the baseline would be 'left', and the magnigude to the 'right', instead. As you note, when one gets away from the plain vanilla, it does get complicated to define all the cases unambiguously in a manner that is consistent across all possibilities.
While it's not important to go into reasons why here, I was surprised by the comment that finding the center or other fractional height position so that an arbitrary (percentage say) position along the bar length turned out to be difficult to implement. Then again, I haven't thought in serious depth, so there's probably something obvious I'm overlooking there.
Oh, I can imagine indeed, that is is a very high fraction, indeed, as compared to the base functionality.
As for things like bsxfun, at least it didn't end up with a camel-cased 20-character long moniker! :)
ADDENDUM: Would 'base' and 'head' be orientation agnostic names, maybe???

Sign in to comment.

This doesn't work in R2014b any more, due to this.
The available bar series properties no longer have the info for the location of the bar. Any idea how to recreate this in grouped bar plots?

8 Comments

There's a hidden property called XOffset that can be used to find the bar positions:
Y=random('unif',0,100,[4 3]);
h=bar(Y);
yb = cat(1, h.YData);
xb = bsxfun(@plus, h(1).XData, [h.XOffset]');
hold on;
text(xb(:),yb(:), cellstr(num2str(Y(:))), 'rotation', 90);
Thank you very much for your insight, it helped me solve the problem I had. This is not entirely correct, however, as it gets positions correctly but displays wrong values. The complete way to do it is:
Y=random('unif',0,100,[4 3]);
h=bar(Y);
yb = cat(1, h.YData);
xb = bsxfun(@plus, h(1).XData, [h.XOffset]');
hold on;
for i = 1:size(yb,2)
for j = length(yb(:,1))
text(xb(j, i),yb(j, i), cellstr(num2str(Y(i, j))), 'rotation', 90);
end
end
This is the best solution so far. Only problem is if you don't want to rotate your numbers 90 deg. They appear at the middle of each bar, so if you want them centered you have to put in a small offset the size of half of the text. I added an offset like this:
xb = xb + (h(1).BarWidth*h(1).XOffset./((Y<100)+2))'
but that's not generalized, just works because my numbers were two or three digits.
Why doesn't 'horizontalalignment','center' work automagically? If they're centered correctly for the vertical orientation with the default horizontal position of 'left', seems like should be centered where you want them if don't rotate but use the alignment property.
I don't have recent release so can't test new HG...
I believe the line should read
j = 1:length(yb(:,1))
and if you want horizontal not vertical alignment,
text(xb(j, i),yb(j, i), ...
sprintf('%.0f', Y(i,j)), ...
'clipping', 'off', ...
'horiz', 'center', 'vert', 'bot');
Thanks so much for this solution!
Query posted as Answer by Artem Smirnov moved here as comment by dpb--
"Is there any chance of putting labels inside the bars? That is a huge problem..."
What's the issue, specifically? The x-position for the text object to write is given by the above; simply modify the y-position to place where wanted in the bar instead above it--the y distances are computable directly from the data; don't need similar machinations as do for x.
I was thinking I'd seen additional properties mentioned that were useful having been introduced in HG2 but a search the other day didn't seem to find such, unfortunately, so guess I had mixed up with something else.
Moving the labels inside the bars is a simple matter of changing the horizontal alignment so the right edge, rather than left, aligns with the bar height (and in most cases, adding a small offset to the y-position so the text doesn't sit flush to the bar edge).
This example also assumes that your bars are all large enough to fit the full text string.
Y=random('unif',30,100,[4 3]); % sample data
h=bar(Y);
yb = cat(1, h.YData);
xb = bsxfun(@plus, h(1).XData, [h.XOffset]');
hold on;
padval = 1;
htxt = text(xb(:),yb(:)-padval, cellstr(num2str(yb(:))), ...
'rotation', 90, 'horiz', 'right');
set(htxt(1:3:end), 'color', 'w'); % for legibility

Sign in to comment.

For what it's worth, see attached demo. Adapt as needed.

1 Comment

Thanks for your comments. The difference in the case you show here and my case is that your bars are all centered on the ticks. If you have bar groups, then you have multiple bars grouped about the same xtick (see the image link in my original post). The only way I was able to figure out the exact center location of the each bar within a group was to get info from the underlying patch objects that are created when bar() is called. See my code above for an example.

Sign in to comment.

for 2 bars one one y value:
for i2=1:numel(y)
tx(i2,1)=text(x(i2),y(i2,1),num2str(y(i2,1),'%0.2f'),...
'HorizontalAlignment','right',...
'VerticalAlignment','bottom');
tx(i2,2)=text(x(i2),y(i2,2),num2str(y(i2,2),'%0.2f'),...
'HorizontalAlignment','left',...
'VerticalAlignment','bottom');
end
I've written a function to do just this. It's called barvalues, and is very easy yo use.
simply:
bar(x);
barvalues;

3 Comments

Nice start; unfortunately it still won't handle OP's problem with the grouped bar...consider
>> y = [2 2 3; 2 5 6; 2 8 9; 2 11 12]; % example from bar doc's...
>> bar(y)
>> barvalues
Error using axes
Invalid axes handle
Error in barvalues (line 49)
axes(ancestor(h,'axes')); % make intended axes curent.
>>
Not yet general enough by default to handle the multiple handles...let's try using the figure handle...
>> barvalues(1,2)
Ah! Doesn't error, but returns the following figure--
where all the label are at the x-value of the centers of the groups instead on their respective bar centers.
In HG2(), there's a (hidden for heaven's sakes!!! :( ) property that can be queried to find where the bar centers are with respect to the tick values but you've got to know it exists before you'll ever be able to use it (a _*major_ foopah on TMW's part, this!)
>> hBar=bar(y); % redraw, save handles this time...
>> get(hBar,'xoffset')
ans =
[-0.2222]
[ 0]
[ 0.2222]
>> barvalues(hBar)
Error using axes
Invalid axes handle
Error in barvalues (line 49)
axes(ancestor(h,'axes')); % make intended axes curent.
>>
Unfortunately, still doesn't know how to cope with the multiple handles.
It's not hard to make use of the above offset property to fixup the position; I posted an Answer/Comment not terribly long ago but can't find the link to it at the moment, unfortunately. It (at least comments that include it) was just in the last month or two at the latest.
ADDENDUM Found it; see following link...
It still should be part and parcel of the basic bar object as I commented therein.
(*) Prior to HG2, one could query the patch objects making up the bars properties and compute the mean of the x-positions, but TMW has made the bar object nearly opaque in HG2 so can't find those coordinates. At least there is the hidden property altho if it weren't for the likes of Yair it's unlikely anybody at all outside TMW would be aware of it.
dpb
dpb on 6 Nov 2017
Edited: dpb on 6 Nov 2017
I munged on your function some...haven't tested it thoroughly but it now works for the above case...haven't tried for more exotic examples. I know it won't work correctly for 'stacked' as the YData property isn't correct y location for it--not sure if it is retrievable or must be recomputed.
Modifications include
  1. fixing up isaType to look at all objects in handle array rather than just one (this may be too simplistic in some cases, I don't know; didn't study the search logic enough to know if could possibly pass a group that might be other than bar handles)
  2. using only one of the array handles in axes call to get axes handle (this could also possibly(?) be too simplistic if there are multiple bar objects but in multiple axes on a given figure), and
  3. looping over the handle array and adding the .XOffSet property differential to the text call.
Doesn't seem an easy way to add updates at the Exchange so I'll just paste the modified routine here for your convenience...
Thanks for the barvalues function Elimelech, very easy to use and solid
Very appreciated
G

Sign in to comment.

Hi , I would like to know how to code to get the total value of each bar in this grouped bar graph ie , total bincounts for the yellow bar, total bincounts for blue bar , and total bincounts for green bar. I am interested to know which of these 3 bars give the most information

1 Comment

Since you plotted the 3 sets of data, you already have the counts. If you want the total number of counts, that's simply the number of elements in the data that you took the histogram of.

Sign in to comment.

Categories

Asked:

on 16 May 2014

Edited:

dpb
on 2 Nov 2024

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!