Метод 1
Вот одно из возможных решений с использованием функции bar
MATLAB.
Допущения:
- Столбцы всего всегда первый и последний столбцы.
Основная идея c заключается в использовании свойства 'Baseline'
объекта Bar, которое позволяет конкретному бару начинаться с указанного значения c. Например, bar([1,3,5], 'BaseValue', 2)
производит 3 бара, которые начинаются со значения 2: первый понижается на 1 единицу, второй повышается на 1 единицу, а последний повышается на 3 единицы.
Из тестирования на R2019b, к сожалению, выяснилось, что все объекты Bar на осях должны иметь один и тот же BaseValue
. Таким образом, чтобы каждый объект Bar имел свое базовое значение, каждый из них должен находиться в отдельном объекте Axes. Мы можем обойти это, накладывая несколько осей (по одному на каждый столбец) друг на друга, делая прозрачными все, кроме одного. Таким образом, все столбцы будут видны.
В любом случае, вот функция. Входные данные:
- ax (необязательно): дескриптор существующего объекта Axes. Возможно, вы захотите сделать это, если у вас уже есть другие объекты, или если вы хотите вручную установить различные свойства осей.
- y : вектор всех добавочных значений. Примечание : конечное значение НЕ требуется, т. Е. Чтобы воспроизвести сюжет в вопросе, используйте
y=[5, 2, -5, 8, 2];
Функция выводит дескрипторы для каждого созданного объекта Bar. Вы можете захотеть, чтобы это еще больше изменило EdgeColor Bars.
function h = wfall(ax, y)
if nargin == 1
y = ax;
ax = gca;
end
if ~strcmp(ax.NextPlot, 'add')
fprintf('hold on not set for current axes. Overriding.\n');
hold(ax, 'on');
end
y = y(:); % column vector
n = length(y);
cumy = cumsum(y);
set(ax, 'XLim', [0, n+1]+0.5, 'YLim', [min(min(cumy), 0), max(max(cumy), 0)]);
% colors:
% decrease - red - code as -1
% total - black - code as 0
% increase - blue - code as 1
set(ax, 'CLim', [-1, 1], 'ColorMap', [1 0 0; 0 0 0; 0 0 1]);
% copy a bunch of axes
for i = 1:n
ax(i+1) = copyobj(ax(1), ax(1).Parent);
end
% Make all subsequent axes invisible
% Make sure all axes will always be the same size by linking properties
set(ax(2:end), 'Color', 'none', 'XColor', 'none', 'YColor', 'none');
linkprop(ax, {'XLim', 'YLim', 'Position', 'DataAspectRatio'});
% define from/to of each bar (except 1st and last)
from = cumy(1:n-1);
to = cumy(2:n);
% color of each bar (except 1st and last)
c = double(y>0) - double(y<0);
c(1) = [];
% first total bar
h = bar(ax(1), 1, from(1), 'CData', 0, 'BaseValue', 0);
% 2nd to 2nd last bars
for i = 1:n-1
h(end+1) = bar(ax(i+1), i+1, to(i), 'CData', c(i), 'BaseValue', from(i), 'ShowBaseLine', 'off');
end
% last total bar
h(end+1) = bar(ax(1), n+1, cumy(n), 'CData', 0);
% setting FaceColor flat makes the Bars use the CData property
set(h, 'FaceColor', 'flat')
Выполните код следующим образом для получения следующего графика.
close all;
ax = gca;
h = wfall(ax, y(1:end-1));
Метод 2
Вот еще одно решение, если вы предпочитаете не размещать объекты Axes друг над другом. В этом случае мы делаем дополнительное предположение:
- Совокупное значение никогда не бывает отрицательным (это применимо, например, к sh в моем кармане)
Проще говоря, каждый нарисованный нами столбец можно рассматривать как один цветной (синий или красный), который частично покрыт более короткой белой полосой.
function h = wfall2(ax, y)
if nargin == 1
y = ax;
ax = gca;
end
if ~strcmp(ax.NextPlot, 'add')
fprintf('hold on not set for current axes. Overriding.\n');
hold(ax, 'on');
end
y = y(:); % column vector
n = length(y);
cumy = cumsum(y);
from = cumy(1:n-1);
to = cumy(2:n);
% color values:
% 1 - blue (increase)
% 0 - white
% -1 - red (decrease)
c = double(y>0) - double(y<0);
c(1) = [];
upper = max(cumy(1:n-1), cumy(2:n));
lower = min(cumy(1:n-1), cumy(2:n));
h(1) = bar(ax, 2:n, upper, 'FaceColor', 'flat', 'CData', c);
h(2) = bar(ax, 2:n, lower, 'FaceColor', 'w');
h(3) = bar(ax, 1, cumy(1), 'FaceColor', 'k');
h(4) = bar(ax, n+1, cumy(n), 'FaceColor', 'k');
set(h, 'EdgeColor', 'none')
set(ax, 'CLim', [-1, 1], 'ColorMap', [1 0 0; 0 0 0; 0 0 1]);
Запустите функцию следующим образом:
close all;
ax = gca;
h = wfall2(ax, y(1:end-1));
Полученный график: Результат, однако, немного уродлив по моим личным стандартам , поскольку белая полоса будет частично покрывать ось X. Однако это можно исправить, установив для нижнего YLim отрицательное значение, например set(ax, 'YLim', [-0.5 inf])