Stacked bar graph with negative BaseValue but "positive" height
6 views (last 30 days)
Show older comments
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
0 Comments
Accepted Answer
Voss
on 21 Aug 2023
Edited: Voss
on 21 Aug 2023
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
Voss
on 22 Aug 2023
Edited: Voss
on 22 Aug 2023
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);
More Answers (0)
See Also
Categories
Find more on Annotations in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!