问题
I need to plot horizontal bars using the grouped
style such that all the bars belonging to each group have the same color but different from other groups (i.e. all bars of the top group are red, the group below it - green, and so on...).
Also, how can I put the values on the top of each bar horizontally? How can I control the position of such values?
This is my code:
y = [91.9 8.1 94.4 5.6; 84.9 15.1 90.12 9.88; 89.4 10.6 91.2 8.8; 72 28 50.9 49.1];
h = barh(y,'grouped');
job = {'group1','group2 ','group 3','group4'};
legend(job,'location','northeast');
This is my current figure:
回答1:
Here's a hack:
Plot bar graph for 1 row at a time and fill the space with NaN
s while specifying a single color for all rows. Plotting NaN
s will plot nothing.
[ry, cy] = size(y); %Number of rows and columns of y
hold on;
for k = 1:ry
tmp = [NaN(k-1,cy); y(k,:); NaN(4-k,cy)]; %Filling with NaNs
h{k} = barh(tmp, 'FaceColor', rand(1,3)); %Plotting bar graph in random color
h{k} = h{k}(1); %Store handle of any of the rows (Required for legend)
end
job = {'group1', 'group2', 'group3', 'group4'}; %Required legend entries
legend([h{:}], job); %Default location is already 'northeast' (so can be skipped)
Output:
回答2:
This is another type of hack; tested on R2017b.
The reason why what you're asking is difficult to do is because each group in a bar plot is actually a Quadrilateral
object (i.e. polygon). For example, let's take the 1stBar
object:
>> h(1).Face.VertexData
ans =
3×16 single matrix
Columns 1 through 9
0 91.9000 91.9000 0 0 84.9000 84.9000 0 0
0.6545 0.6545 0.8000 0.8000 1.6545 1.6545 1.8000 1.8000 2.6545
0 0 0 0 0 0 0 0 0
Columns 10 through 16
89.4000 89.4000 0 0 72.0000 72.0000 0
2.6545 2.8000 2.8000 3.6545 3.6545 3.8000 3.8000
0 0 0 0 0 0 0
And if we add this line to the code:
drawnow; hold on; scatter(h(1).Face.VertexData(1,:), h(1).Face.VertexData(2,:));
we get (notice the green circles):
What's interesting is that the points in VertexData
are not read-only, which means we can just intelligently "rearrange" the VertexData
matrices of the N
bar groups to get the desired result. Here's one way to do it (notice I renamed h
to hBar
):
% Obtain the old VertexData:
vd_old = cell2mat(reshape(get([hBar.Face],'VertexData'),1,1,[]));
% Rearrange VertexData:
nB = numel(hBar);
vd_new = cell2mat(permute(mat2cell(vd_old,3,repelem(4,nB),repelem(1,nB)),[1,3,2]));
% Assign the new VertexData back into the Bar objects' Edge and Face fields:
for indB = 1:nB
hBar(indB).Edge.VertexData = vd_new(:,:,indB);
hBar(indB).Face.VertexData = vd_new(:,:,indB);
end
Finally, we manually add labels using the text
command:
xOffset = 1;
for indB = 1:nB
text(double(vd_new(1,2:4:end,indB)) + xOffset, double(tmpY(2:4:end)), ...
string(vd_new(1,2:4:end,indB)),'VerticalAlignment','middle');
end
The end result:
The final code:
function q47978293
close all force;
y = [91.9 8.1 94.4 5.6; 84.9 15.1 90.12 9.88; 89.4 10.6 91.2 8.8; 72 28 50.9 49.1];
figure(47978293); set(gcf,'Position',[786,556,887,420]); hBar = barh(y,'grouped');
legend("group" + (1:4),'location','northeast');
drawnow;
% hold on; scatter(h(1).Face.VertexData(1,:), h(1).Face.VertexData(2,:));
% Obtain the old VertexData:
vd_old = cell2mat(reshape(get([hBar.Face],'VertexData'),1,1,[]));
% Rearrange VertexData:
nB = numel(hBar);
vd_new = cell2mat(permute(mat2cell(vd_old,3,repelem(4,nB),repelem(1,nB)),[1,3,2]));
% Assign the new VertexData back into the Bar objects' Edge and Face fields:
xOffset = 1; % < Adjust as you see fit
for indB = 1:nB
hBar(indB).Edge.VertexData = vd_new(:,:,indB);
hBar(indB).Face.VertexData = vd_new(:,:,indB);
try
text( y(indB,:) + xOffset, mean(reshape(vd_new(2,1:2:end,indB),2,[]),1,'double'), ...
string(vd_new(1,2:4:end,indB)),'VerticalAlignment','middle');
catch % Compatibility fix for R2016b & R2017a. Credit @SardarUsama
text( y(indB,:) + xOffset, mean(reshape(vd_new(2,1:2:end,indB),2,[]),1,'double'), ...
split(num2str(vd_new(1,2:4:end,indB))),'VerticalAlignment','middle');
end
end
end
Note: due to the hacky nature of this solution, if the figure is redrawn (due to e.g. resizing), the bar colors will return to their original state - so make sure that the part that changes colors is the very last thing you do.
P.S.
The R2017b release notes state that this customization should be officially supported now (through the CData
property of Bar
objects), but I couldn't get the legend to display the correct colors. If you want to try it, just plot using the following command (R2017b+):
barh(y,'grouped','FaceColor','flat','CData',lines(size(y,1)));
回答3:
The two requests overlap making everything kinda complex, since using uniform colors for each group implies using a workaround that needs another workaround to male bar text work. Here is the code:
y = [
91.90 8.10 94.40 5.60;
84.90 15.10 90.12 9.88;
89.40 10.60 91.20 8.80;
72.00 28.00 50.90 49.10
];
[rows,cols] = size(y);
n = NaN(rows,cols);
c = {'r' 'g' 'b' 'y'};
bh = cell(rows,1);
lh = cell(rows,1);
hold on;
for k = 1:rows
curr = n;
curr(k,:) = y(k,:);
bar = barh(curr,'FaceColor',c{k});
bh{k} = bar;
lh{k} = bar(1);
end
hold off;
legend([lh{:}],{'G1','G2','G3','G4'},'Location','southoutside');
yl = get(gca,'XLim');
yl_up = yl(2);
xoff = yl_up * 0.01;
set(gca,'XLim',[yl(1) (yl_up + (yl_up * 0.1))]);
set(gcf,'Units','normalized','Position',[0.1 0.1 0.8 0.8]);
for i = 1:numel(bh)
bh_i = bh{i};
for j = 1:numel(bh_i)
bh_ij = bh_i(j);
hx = bh_ij.YData + xoff;
hy = bh_ij.XData + bh_ij.XOffset;
text(hx,hy,num2str(hx.','%.2f'), ...
'FontSize',10, ...
'HorizontalAlignment','left', ...
'VerticalAlignment','middle');
end
end
And here is the output:
来源:https://stackoverflow.com/questions/47978293/how-to-make-groups-of-horizontal-bars-have-the-same-color