Bar plot customizations

Bar charts are a great way to visualize data. Matlab includes the bar function that enables displaying 2D bars in several different manners, stacked or grouped (there’s also bar3 for 3D bar-charts, and barh, bar3h for the corresponding horizontal bar charts).

Displaying stacked 1D data

bar is basically a high-level m-file wrapper for the low-level specgraph.barseries object. Such wrappers are generally a good thing, assigning default property values, checking the input data for illegal values etc. But in some cases, they are simply too smart. One such case happened to me a few days ago, when I needed to display a stacked chart of some data values. This works great when the data is a 2D matrix, but when the data happens to be 1D (a single row), bar decides to display the values in separate bars (left screenshot), rather than in a single stacked bar (right screenshot), even when I specifically ask for a ‘stacked’ graph:

bar([6,4,1], 'Stacked');

Default bar plot of 1D data

Default bar plot of 1D data

Bar plot of 1D data - stacked

Bar plot of 1D data - stacked

I could of course revert to the lower-level specgraph.barseries. But in this case the simple workaround was to add a dummy data row of Nan values, tricking bar into thinking it’s a 2D data. Since Nan values are never plotted in Matlab, we get the requested stacked bar, alongside an empty bar next to it. All we need to do now is to delete the extra tick-label for the dummy (empty) bar and we’re done:

bar([6,4,1; nan(1,3)], 'Stacked');

or, more generally (taking care of both 2D and 1D data):

bar([data; nan(size(data))],'Stacked');
set(gca, 'xtick',1:size(data,1));

Displaying data labels

One of the features that I miss most in Matlab bar-charts is the ability to easily add data labels next to the colored patches. This can be added programmatically as I’ll just show, but it would be much better if there were properties that controlled this. Here’s my implementation:

First, let’s display a simple stacked bar chart:

data = [6,4,1; 5,2,2; 4,0,0; 2,0,1; 0,2,0; 0,1,1];
hDataSeries = bar(data,'stacked');
set(gca, 'xlim',[0,7], 'box','off');

Default bar plot of 2D data

Default bar plot of 2D data

Now let’s add the bar totals on top of the bars:

sumTotals = sum(data,2);
sumTotals = sumTotals(~isnan(sumTotals));
labels = arrayfun(@num2str,sumTotals,'uniform',false);
hText = text(1:size(data,1), sumTotals, labels);
set(hText, 'VerticalAlignment','bottom', 'FontSize',8, 'Color','b');

Bar plot with totals

Bar plot with totals

Now let’s add the individual data-series labels, but only next to bars that have 2 or more components (if a bar only has a single patch component, then the top label is enough and there is no need for it to also appear on the side):

hPatches = get(hDataSeries,'Children');
try hPatches = cell2mat(hPatches); catch, end  % no need in case of single patch
xData = get(hPatches(1),'XData');
xPos = xData(end,:) + 0.01;
yData = get(hPatches,'YData');
try yData = cell2mat(yData); catch, end
barYs = yData(2:4:end,:);
barValues = diff([zeros(1,size(barYs,2)); barYs]);
barValues(bsxfun(@minus,barValues,sum(barValues))==0) = 0;  % no sub-total for bars having only a single sub-total
yPos = yData(1:4:end,:) + barValues/2;
xPos = xPos(ones(1,size(yPos,1)),:);
xPos(barValues==0)      = [];  % remove entries for empty bars patches
yPos(barValues==0)      = [];  % remove entries for empty bars patches
barValues(barValues==0) = [];  % remove entries for empty bars patches
labels = strcat(' ', arrayfun(@num2str,barValues(:),'uniform',false));
hText = text(xPos(:), yPos(:), labels);
set(hText, 'FontSize',8);

Finally, add the legend and we’re done:

hLegend = legend(hDataSeries, {'Blue','Green','Red'});
set(hLegend, 'FontSize',8);

Bar plot with totals & legend

Bar plot with sub-totals & legend

Additional customization of the bar patches can be made using the patch handles, which are included as children of the returned hDataSeries handles:

hDataSeries = bar(...);
hPatches = get(hDataSeries, 'Children');
try hPatches = cell2mat(hPatches); catch, end  % no need in case of single patch

Note: hDataSeries is an array of handles, one per data column. Each handle is an hggroup of patches. As with all patches, we can customize their face and edge line-style, width, color, transparency, shading/lighting etc.

Categories: Handle graphics, Low risk of breaking in future versions, Stock Matlab function, Undocumented feature

Tags: , ,

Bookmark and SharePrint Print

15 Responses to Bar plot customizations

  1. Yaroslav says:

    This is a known issue in Matlab’s high-level functions (plot, stem, mesh, etc.). For example,

    plot(1:3, 2:2:6, '*');
    axis([0 4 0 8])

    will always plot a single line; but if we insist on three distinct lines, we must resort to the aforementioned NaN trick:

    plot([1:3; 2:4], [2:2:6; nan(1,3)], '*'); 
    axis([0 4 0 8])

    However, using NaN-s has a clear disadvantage in code readability and the requirement of many additional check-ups, which are better to be avoided in the first place. @Yair, is there any (undocumented) way to force Matlab to behave in a discrete level?

  2. Julian says:

    Thanks for raising this.

    In 2012 I sent the following report to the MathWorks (with respect to R2011a that I was running at the time):

    The commands

    bar(1, 1:5, 'grouped')
    bar(1, 1:5, 'stacked')

    report an error “X must be same length as Y”.

    But according to documentation for the grouped or stacked options, each row of Y is an observation group and we require only numel(X)==size(Y,1) for this case. I expect to see a single stack or group plotted for such a call. On the other hand

    bar(1, (1:5)', 'grouped')

    would be expected to generate the error given.

    My point was not accepted, but they keep changing the documentation in this area. In R2013a (that I use today) the documentation is not consistent between Grouped and Stacked. Looking at R2014a on MathWorks web-site there is the getout statement,

    “If Y is a vector of length n, then MATLAB treats Y as a column vector and displays n bars.”

    In my opinion this is a kludge. The doc is now mutually contradictory while the behaviour sucks! Clearly if you explicitly choose a grouped or stacked bar chart, by passing the flag, then orientation of matrix Y matters, and each row of Y is a group. It is quite legitimate to plot a single stack, and this is what we should expect bar() to do in that case.

  3. Sergey says:

    Thanks a lot for the very useful resource, Yair. Indeed in HG1 bar series can be treated as a set of patches and one can change a transparency using, e.g.

    hb = bar(...);
    hp = arrayfun(@(x) allchild(x), hb);
    set(hp, 'FaceAlpha', 0.5)

    However in HG2 this code won’t work since bar object has no children. Do you know how to tackle this problem? Thanks a lot in advance!

    • Yair Altman says:

      @Sergey –
      To set all bar faces to the same color and alpha:

      hBar = bar(...);
      hBar.Face.ColorType = 'truecoloralpha';  % default is 'truecolor' without alpha
      hBar.Face.ColorData = uint8([53;142;134;128]);   % 128=50% alpha

      We can see the possible ColorType values by typing hBar.Face.ColorType='<TAB>. We get the following list: colormapped, texturemapped, truecolor (default), truecoloralpha.

      We can see the possible ColorData values by trying some obviously invalid value:

      >> hBar.Face.ColorData = 1
      While setting the 'ColorData' property of Quadrilateral:
      Value must be one of the following:
      Truecolor  - a 4xN array of type uint8
      Colormapped - a 1xN vector of type single
      Texturemapped - a 2xN array of type single

      This seems to indicate that there’s a way to set a 4xN color array, presumably for the separate bar faces (patches). Unfortunately, setting such an array directly is not enough:

      >> hBar.Face.ColorData = uint8([53,40,100; 42,0,100; 134,50,100; 120,50,100]);
      Warning: Error creating or updating Quadrilateral
       Error in value of property  ColorData
       Array is wrong shape or size
      (Type "warning off MATLAB:gui:array:InvalidArrayShape" to suppress this warning.)

      So apparently we need to set some other property before being able to do this. I have still not found out exactly how to do this. If anyone finds out, please post a follow-on comment here. For reference, here’s what the Face object looks like:

      >> hBar.Face.get
                   AmbientStrength: 0.3
                   BackFaceCulling: 'none'
                      ColorBinding: 'object'
                         ColorData: [4x1 uint8]
                         ColorType: 'truecoloralpha'
                   DiffuseStrength: 0.6
                  HandleVisibility: 'on'
                           HitTest: 'off'
                             Layer: 'middle'
                     NormalBinding: 'none'
                        NormalData: []
                            Parent: [1x1 Bar]
          SpecularColorReflectance: 1
                  SpecularExponent: 10
                  SpecularStrength: 0.9
                         StripData: [1 5 9 13]
                           Texture: []
                  TwoSidedLighting: 'off'
                        VertexData: [3x12 single]
                     VertexIndices: []
                           Visible: 'on'

      Anyway, this is not a major limitation since we normally want all our bar faces to have consistent color and alpha. We can always use multiple bars, each having different color/alpha:

      data = [6,4,1; 5,2,2; 4,0,0; 2,0,1; 0,2,0; 0,1,1];
      hBar = bar(data,'stacked');
      % Set the middle bar's color to translucent red
      hBar(2).Face.ColorType = 'truecoloralpha';
      hBar(2).Face.ColorData = uint8([255; 0; 0; 100]);

      HG2 bar alpha

    • Sergey says:

      Thank you very much, Yair. Apparently I need to read more about the HG2 and use “if-s” in my code for compatibility.

  4. Jared says:

    Here’s how to get multiple colors in a single Bar series. The ColorBinding can also be ‘interpolated’.

    hBar = bar(...);
    hBar.Face.StripData = [];
    vi = [1:4:N*4; 2:4:N*4; 4:4:N*4; 3:4:N*4];
    hBar.Face.VertexIndices = uint32(vi(:)');
    hBar.Face.ColorBinding = 'discrete';
    hBar.Face.ColorData = ... % 4xN uint8
  5. Michael Cappello says:

    Yair (or anyone),

    I have an application where I need to color the bars individually, and have tried to figure out how to get the handles of the underlying patches or graphical objects. I am doing this in HG2. Any ideas?

    Thank you,

    • Yaroslav says:

      So far, a smart solution is elusive; you can inspect Yair’s previous comment for a detailed analysis. In the meanwhile, you can try the old-school simple approach:

      N = 15;
      hAxes = axes('NextPlot','add');
      facecolors = jet(N);
      for i = N : -1 : 1
          hBar(i) = bar(i, 2*i, 'FaceColor',facecolors(i,:));
  6. afaq says:

    I used the following code.
    But I wanted to display the ranges of each stacked portion along its bar.
    Please share if anyone knows how to display the ranges (limits of the each portion).

    Y = [5 1 2; 8 3 7; 9 6 8; 5 5 5; 4 2 3];  % sample data from bar example...
    Z=cumsum(Y,2);                            % bar cumulative heights for positioning
    X=repmat([1:5].',1,3);                    % x locations for each Y
    bar(Y,'stack')                            % base stacked bar
    colormap(copper);                         % change color map
    arrayfun(@(x,y,z) text(x,y,num2str(z), 'horizontalalign','center', 'verticalalign','top', 'color','w'), X,Z,Y);
    % write the labels for each subset
    sumTotals = sum(Y,2);
    sumTotals = sumTotals(~isnan(sumTotals));
    labels = arrayfun(@num2str,sumTotals,'uniform',false);
    hText = text(1:size(Y,1), sumTotals, labels);
    set(hText, 'VerticalAlignment','bottom', 'FontSize',8, 'Color','k');
  7. NG Keerthy says:

    how to display the stack values for horizontal bar graph

    • @NG – you will need to adapt the code in my post. This should not be too difficult: all the components are there, it’s basically just switching between X-Y etc., so I am not going to spoon-feed the answer. If you cannot do this (or don’t have time), then contact me by email for a short consulting.

  8. dgm says:

    Thanks for this solution. In addition to needing to label the individual bars, I have floating point numbers as the labels at the tops of the bars. Is there a way to center those labels over the bars instead of starting the text at the mid-point of each bar?

  9. Cyril says:

    Hi. I have a problem with making scatered bar graph in matlab.

    I need to create graph where on X axis will be years from 2005 to 2016 and on Y axis will be another numbers.
    If I use just simple number for example from 1 to 10 for X axis is working, but I need put years on X axis.

    I use this example from web:

    measles = [38556 24472 14556 18060 19549 8122 28541 7880 3283 4135 7953 1884]';
    mumps = [20178 23536 34561 37395 36072 32237 18597 9408 6005 6268 8963 13882]';
    chickenPox = [37140 32169 37533 39103 33244 23269 16737 5411 3435 6052 12825 23332]';
    % Create a stacked bar chart using the bar function
    bar(1:12, [measles mumps chickenPox], 0.5, 'stack')
    % Adjust the axis limits
    axis([0 13 0 100000])
    set(gca, 'XTick', 1:12)
    % Add title and axis labels
    title('Childhood diseases by month')
    ylabel('Cases (in thousands)')
    % Add a legend
    legend('Measles', 'Mumps', 'Chicken pox')

    But then I repleaced nummbers for X axis (how you can see below), nad now is not working.
    Some advice in this issue for me?

    % Create data for application of fertilizer in arable land
    dusikate = [70147.6	84165.8	92217.6	73166.4	85068.2	90982.2	98787.2	110714.4 116029.5 111892.1]';
    fosforecne = [15757.9 19187.6 19828.2 13866.1 12797.9 14982.2 18898 20306.7 22497.4 21286.5]';
    draselne = [13698.8	15764.6	16232.1	9798.1 8002.1 10108.4 13038.3 14534.1 16366.4 15868.7]';
    x = 2005:1:2016;
    % Create a stacked bar chart using the bar function   
    bar(x,[dusikate fosforecne draselne], 0.8, 'stack')
    % Adjust the axis limits
    axis([50000 180000])
    set(gca, 'XTick', x)
    ax = gca; 
    ax.YAxis.Exponent = 0;
    % Add title and axis labels
    title({'Celková spotreba draselných, fosforečných a dusíkatých hnojív (v tonách) na';'sledovanej výmere ornej pôdy na Slovensku v období rokov 2005/2006 - 2014/2015'})
    xlabel('sezóna (hospodársky rok')
    ylabel('spotreba hnojív v tonách [t]')
    % Add a legend
    legend('dusikaté', 'fosforečné', 'draselné')

Leave a Reply

Your email address will not be published. Required fields are marked *