Stacked bar graph with negative BaseValue but "positive" height

6 次查看(过去 30 天)
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

采纳的回答

Voss
Voss 2023-8-21
编辑:Voss 2023-8-21
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 个评论
Voss
Voss 2023-8-22
编辑:Voss 2023-8-22
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);
Leon Berger
Leon Berger 2023-9-6
Thank you very much. This was a little bit difficult to implement for my original project, but it works wonderfully. very helpful...

请先登录,再进行评论。

更多回答(0 个)

类别

Help CenterFile Exchange 中查找有关 Annotations 的更多信息

产品


版本

R2019b

Community Treasure Hunt

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

Start Hunting!

Translated by