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');
set(gca,'xtick',1)

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.

Related posts:

  1. Undocumented scatter plot behavior The scatter plot function has an undocumented behavior when plotting more than 100 points: it returns a single unified patch object handle, rather than a patch handle for each specific point as it returns with 100 or less points....
  2. Undocumented scatter plot jitter Matlab's scatter plot can automatically jitter data to enable better visualization of distribution density. ...
  3. Bug and workaround in timeseries plot Matlab's internal hgconvertunits function has a bug that affects timeseries plots. Luckily there is a simple workaround....
  4. Plot LimInclude properties The plot objects' XLimInclude, YLimInclude, ZLimInclude, ALimInclude and CLimInclude properties are an important feature, that has both functional and performance implications....
  5. Accessing plot brushed data Plot data brushing can be accessed programmatically using very simple pure-Matlab code...
  6. Draggable plot data-tips Matlab's standard plot data-tips can be customized to enable dragging, without being limitted to be adjacent to their data-point. ...

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

Tags: , ,

Bookmark and SharePrint Print

8 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

Leave a Reply

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

*

<pre lang="matlab">
a = magic(3);
sum(a)
</pre>