Stacked bar graph with negative BaseValue but "positive" height

I want to plot a waterfall-chart. Simplified, I want to show costs, revenue, and lastly profit/loss. The respective components of "costs"-bar and "revenues"-bar should also be shown, thus i need stacked bars. The "revenue" bar should have its base value, where the "costs"-bar ends. For this I work with multiple overlapping axes, and it works, when not working with stacked bars:
But when i try to stack the components of costs & revenue, it won't work for the revenue bar. I know that there are few problems regarding the use of negative values, but I tried everything and I really can't come up with a solution. In the following you'll find my code sofar:
%Data
costs_tot = -1200;
rev_tot = 900;
costs_distr = [-400 -800];
rev_distr = [300 600];
fig0 = figure('Color', 'w');
ax = gca;
hold(ax(1),"on");
%Axes Appearence
ax(1).XTick = [1:1:3];
ax(1).XAxis.TickLength = [0 0];
ax(1).XTickLabelRotation = 90;
ax(1).TickLabelInterpreter = 'none';
ax(1).Box = 'off';
ax(1).YTick = [-1500 : 500 : 500];
ax(1).YGrid = 'on';
set(ax(1), 'XLim', [0.0, 4.0],'YLim', [-1500.0, 500.0],'XAxisLocation', 'bottom',...
'XTickLabels', ["Costs" "Revenue" "Profit/Loss"]);
for i = 2:3
ax(i) = copyobj(ax(1), ax(1).Parent);
end
set(ax(2:end), 'Color', 'none', 'XColor', 'none', 'YColor', 'none');
linkprop(ax, {'XLim', 'YLim', 'DataAspectRatio'});
%plot bars
b = bar(ax(1), 1, costs_distr, 'stacked', 'BaseValue', 0, 'FaceColor', 'flat', 'EdgeColor', 'flat');
b(1).CData = [0.8500 0.3250 0.0980]; %orange
b(2).CData = [0.9290 0.6940 0.1250]; %yellow
c = bar(ax(2), 2, [costs_tot+rev_distr(1), costs_tot+rev_distr(1)+rev_distr(2)], ...
'stacked', 'BaseValue', costs_tot, 'FaceColor', 'flat', 'EdgeColor', 'flat');
c(1).CData = [0 0.4470 0.7410]; %dark blue
c(2).CData = [0.3010 0.7450 0.9330]; %light blue
d = bar(ax(3), 3, (costs_tot + rev_tot), 'BaseValue', 0, 'FaceColor', 'flat', 'EdgeColor', 'flat');
d.CData = [0.6350 0.0780 0.1840]; %dark red
I use Version R2019b

 Accepted Answer

The BaseValue is not necessarily the bottom of the bar; for bars representing negative value, the BaseValue is the top of the bar. Therefore, BaseValue for the revenue bar should not be costs_tot but rather costs_tot+rev_tot.
%Data
costs_tot = -1200;
rev_tot = 900;
costs_distr = [-400 -800];
rev_distr = [300 600];
fig0 = figure('Color', 'w');
ax = gca;
hold(ax(1),"on");
%Axes Appearance
ax(1).XTick = [1:1:3];
ax(1).XAxis.TickLength = [0 0];
ax(1).XTickLabelRotation = 90;
ax(1).TickLabelInterpreter = 'none';
ax(1).Box = 'off';
ax(1).YTick = [-1500 : 500 : 500];
ax(1).YGrid = 'on';
set(ax(1), 'XLim', [0.0, 4.0],'YLim', [-1500.0, 500.0],'XAxisLocation', 'bottom',...
'XTickLabels', ["Costs" "Revenue" "Profit/Loss"]);
for i = 2:3
ax(i) = copyobj(ax(1), ax(1).Parent);
end
set(ax(2:end), 'Color', 'none', 'XColor', 'none', 'YColor', 'none');
linkprop(ax, {'XLim', 'YLim', 'DataAspectRatio'});
%plot bars
b = bar(ax(1), 1, costs_distr, 'stacked', 'BaseValue', 0, 'FaceColor', 'flat', 'EdgeColor', 'flat');
b(1).CData = [0.8500 0.3250 0.0980]; %orange
b(2).CData = [0.9290 0.6940 0.1250]; %yellow
c = bar(ax(2), 2, [costs_tot+rev_distr(1), costs_tot+rev_distr(1)+rev_distr(2)], ...
'stacked', 'BaseValue', costs_tot+rev_tot, 'FaceColor', 'flat', 'EdgeColor', 'flat');
c(1).CData = [0 0.4470 0.7410]; %dark blue
c(2).CData = [0.3010 0.7450 0.9330]; %light blue
d = bar(ax(3), 3, (costs_tot + rev_tot), 'BaseValue', 0, 'FaceColor', 'flat', 'EdgeColor', 'flat');
d.CData = [0.6350 0.0780 0.1840]; %dark red

5 Comments

Alright, that's really good to know. But somehow i can get behind what I should write as the third argument in der revenue-bar-graph, instead of this:
[costs_tot+rev_distr(1), costs_tot+rev_distr(1)+rev_distr(2)]
. Could you also help with this? Thank you very much
If I understood the original problem correctly, the third argument is fine; only the BaseValue needed to change. When I change BaseValue, I get the plot shown in my answer. Is that plot not how it should look?
Yeah indeed the plot should look like the solution you proposed. But if i change the rev_distr or any other parameter, then it does not work anymore.
You're right, there's something weird (at least, unintuitive to me) going on with what changing the BaseValue of a stacked bar actually does. So, rather than spend time trying to make sense of it, I would use patch instead of bar. This approach gives you complete control. (And it also only requires one axes, which the one-bar-per-axes thing was a workaround, I gather, due to another bizarre aspect of the behavior of stacked bars.)
You should be able to change costs_distr and/or rev_distr, and it should still work, now.
%Data
costs_distr = [-400 -800];
rev_distr = [300 600];
costs_tot = sum(costs_distr);
rev_tot = sum(rev_distr);
fig0 = figure('Color', 'w');
ax = gca;
hold(ax,"on");
%Axes Appearance
ax.XTick = [1:1:3];
ax.XAxis.TickLength = [0 0];
ax.XTickLabelRotation = 90;
ax.TickLabelInterpreter = 'none';
ax.Box = 'off';
ax.YTick = [-1500 : 500 : 500];
ax.YGrid = 'on';
set(ax, 'XLim', [0.0, 4.0],'YLim', [-1500.0, 500.0],'XAxisLocation', 'bottom',...
'XTickLabels', ["Costs" "Revenue" "Profit/Loss"]);
%plot bars
bar_width = 0.8;
np = numel(costs_distr);
xd = 1+0.5*bar_width*[-1;1];
yd = [0 cumsum(costs_distr)];
colors = [0.8500 0.3250 0.0980; 0.9290 0.6940 0.1250];
b = patch(ax, ...
'XData',xd(zeros(1,np)+[1;1;2;2;1]), ...
'YData',yd((1:end-1)+[0;1;1;0;0]), ...
'FaceColor','flat', ...
'FaceVertexCData',colors, ...
'EdgeColor','none', ...
'FaceAlpha',0.8);
np = numel(rev_distr);
xd = 2+0.5*bar_width*[-1;1];
yd = costs_tot+[0 cumsum(rev_distr)];
colors = [0 0.4470 0.7410; 0.3010 0.7450 0.9330];
c = patch(ax, ...
'XData',xd(zeros(1,np)+[1;1;2;2;1]), ...
'YData',yd((1:end-1)+[0;1;1;0;0]), ...
'FaceColor','flat', ...
'FaceVertexCData',colors, ...
'EdgeColor','none', ...
'FaceAlpha',0.8);
np = 1;
xd = 3+0.5*bar_width*[-1;1];
yd = [0 costs_tot+rev_tot];
colors = [0.6350 0.0780 0.1840];
d = patch(ax, ...
'XData',xd(zeros(1,np)+[1;1;2;2;1]), ...
'YData',yd((1:end-1)+[0;1;1;0;0]), ...
'FaceColor','flat', ...
'FaceVertexCData',colors, ...
'EdgeColor','none', ...
'FaceAlpha',0.8);
Thank you very much. This was a little bit difficult to implement for my original project, but it works wonderfully. very helpful...

Sign in to comment.

More Answers (0)

Categories

Find more on Graphics Object Properties in Help Center and File Exchange

Products

Release

R2019b

Asked:

on 21 Aug 2023

Commented:

on 6 Sep 2023

Community Treasure Hunt

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

Start Hunting!