I want to create a stacked bar chart from a table. Here there is a MWE of the type of table I am looking at:
clear all;
country1=rand(5,1);
country2=rand(5,1);
country3=rand(5,1);
country4=rand(5,1);
country5=rand(5,1);
date=1990:1994;
T=table(date',country1,country2,country3,country4,country5);
T.Properties.VariableNames{1}='date';
T.Total=sum(T{:,2:end},2);
T{:,2:end} = T{:,2:end}./T.Total;
A = table2array(T);
A(:,[1,end])=[];
A=sort(A,2);
TT=array2table(A,'VariableNames',{'country1','country2','country3','country4','country5'});
TT.Date=T.date;
TT.Total=T.Total;
T_new=table(TT.Date, TT.country1,TT.country2,TT.country3,TT.country4,TT.country5,TT.Total);
T_new.Properties.VariableNames=T.Properties.VariableNames;
T_new.World=sum(T{:,2:4},2);
T_new.World=1-(T_new.country4+T_new.country5);
T_new(:,[2:4,end-1])=[];
T_new
date country4 country5 World
____ ________ ________ _______
1990 0.2933 0.29471 0.41199
1991 0.31453 0.34511 0.34035
1992 0.22595 0.29099 0.48307
1993 0.26357 0.33336 0.40306
1994 0.28401 0.28922 0.42677
Type of Stacked BAR
====================
Based on the T_new table I want to create a stacked bar graph. In the 'x' axis the chart should show the dates (1990,1991 etc) and for each date should be one stacked bar. So, for example, for 1990 there is should be one bar stacking the values 0.2933 0.29471 0.41199
Ideally, in the stack bar I want also to include the labels of (country1, country2, world) for the correspending values.
How I can do that in matlab ?
You can do the following:
bar(T_new{:,1},T_new{:,2:end},'stacked')
legend(T_new.Properties.VariableNames(2:end))
The code you've provided contains an erron at line:
T{:,2:end} = T{:,2:end}./T.Total
Error using ./
Matrix dimensions must agree.
Error in stacked_bars (line 14)
T{:,2:end} = T{:,2:end}./T.Total;
since T{:,2:end} is a (5 x 6) matrix and T.Total is a (5 x 1) array
You can fix it replacing that line with, for example:
T{:,2:end}=bsxfun(#rdivide,T{:,2:end},T.Total)
Once fixed the error, an alternative way (with respect to the already posted answer) to plot the labels could be to use the text function to draw a string in each of the stackedbars.
You can identify the x and y coordinate of the point in which to draw the string this way:
x: for each set of bars, is the corresponding date (you need to shift that value a little on the left in order to centre the text with respect to the bar since text uses the x coordinate as starting point
y: for the first label (the lower) could be simply the half of the height of the bar; from the second bar on, you need to add the height of the previous ones
A possible implementation of this approach could be the following:
% Get the T_new data
x=table2array(T_new)
x=x(:,2:end)
% Ientify the number of bars
n_s_bars=size(x,2)
% Open a Figure for the plot
figure(123)
% Plot the stacked bars
bar(T_new{:,1},T_new{:,2:end},'stacked')
% Get the names of the table variables
v_names=T_new.Properties.VariableNames
% Loop over the dates
for i=1:length(date)
% Create the label string:
% country_x (or world)
% percentage
str=sprintf('%s\n%f',v_names{2},x(i,1))
% Print the label in the center of the first bar
tx=text(date(i)-.3,x(i,1)/2,str,'Color',[1 1 1])
% Loop over the bars, starting from the second bar
for j=2:n_s_bars
% Create the label string:
% country_x (or world)
% percentage
str=sprintf('%s\n%f',v_names{j+1},x(i,j))
% Print the label in the center of the first bar
tx=text(date(i)-.3,sum(x(i,1:j-1))+x(i,j)/2,str)
end
end
Over the loops, the following image is generated:
Hope this helps,
Qapla'
Related
I made in Matlab a horizontal bar plot, with a bar coming from left to right:
Horizontal Single Bar Plot
I would like to add an additional horizontal bar, on the same horizontal plan of the previous bar, this time coming from right to left. The bar is supposed to stop at the white dot (with its error bars). Both horizontal bars should be present at the same time.
Does anybody have any idea on how to do that? If I use the reverse function everything is reversed, but I need to reverse only the new specific bar without changing anything else.
Here is my script:
figure
Data = [19 26; 1.7 2; 1.8 2]; % 1 Means 2 Error Bars1 3 Errors Bars2
%% Horizontal Bar Graph
barh(Data(1,1),'FaceColor',[0,0.5,0.5],'EdgeColor',[0,0,0],'LineWidth',2);
hold on
x = Data(1,1); %mean Bar 1
e = Data(2,1); %err Bar 1
f = Data(3,1); %err Bar 2
h = herrorbar(x,1,e,f);
set(h(1),'linewidth',6,'Color',[0 0 0]);
%% Point for other bar graph coming from other side
hold on;
plot(Data(1,2),1,'o','LineWidth',2,'MarkerSize',20,'MarkerEdgeColor','k','MarkerFaceColor',[1 1 1])
%% Axis limits etc etc
set(gca,'fontsize',20)
set(gca,'linewidth',3)
axis([10 30 0.5 1.5])
A possible solution could be to add a new axes to the figure, in the same position and of the same size of the firt one.
You can then plot the second horizontal bar in the new axes.
In order to have the second bar aligned to the right, you have to set the XDir property of the second axes to reverse
Setting the color property of the second axes to `none" allows you not to mask the first bar.
In order to plot the second bar from right to left up to the circle, you can evalaute the distance from the right limit of the first axex (i. e. the max of its XLim) from the position of the circle.
I'm not sure how you want to evalaute the errorbar, so, in the following code I've used the same settings of the first one, you can easily adapt it.
In the following you can find a possible implementation of the proposed solution.
figure
Data = [19 26; 1.7 2; 1.8 2]; % 1 Means 2 Error Bars1 3 Errors Bars2
% Horizontal Bar Graph
barh(Data(1,1),'FaceColor',[0,0.5,0.5],'EdgeColor',[0,0,0],'LineWidth',2);
hold on
x = Data(1,1); %mean Bar 1
e = Data(2,1); %err Bar 1
f = Data(3,1); %err Bar 2
h = herrorbar(x,1,e,f);
set(h(1),'linewidth',6,'Color',[0 0 0]);
% Point for other bar graph coming from other side
hold on;
plot(Data(1,2),1,'o','LineWidth',2,'MarkerSize',20,'MarkerEdgeColor','k','MarkerFaceColor',[1 1 1])
% Axis limits etc etc
set(gca,'fontsize',20)
set(gca,'linewidth',3)
axis([10 30 0.5 1.5])
%%%%%%%%%%%%%%%%%%%%%%%%%%%
% ADD THE SECOND AXES AND %
% PLOT THE SECOND BAR %
%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Get first axes position
xp=get(gca,'position')
% Get first axes X Limits
first_ax_lim=get(gca,'xlim')
% Get first axes X span
first_ax_span=first_ax_lim(2)-first_ax_lim(1)
% Create the second axes in the same position of the first one
new_ax=axes('position',xp)
% Get the data for the second bar
new_bar_data=first_ax_lim(2)-Data(1,2)
% Plot the second bar in the second axes
barh(new_bar_data,'FaceColor',[0,0.5,0.5],'EdgeColor',[0,0,0],'LineWidth',2);
% Set the properties of the second axes
set(new_ax,'xdir','reverse','color','none')
% Set the limits of the second axes
axis([0 first_ax_span 0.5 1.5])
% Remove the XTick from the second axes
new_ax.XTick=[]
new_ax.YTick=[]
% Add the error bar on the second axes
hold on
new_h = herrorbar(new_bar_data,1,e,f);
% Set the properties of the second error bar
set(new_h,'linewidth',6,'Color',[0 0 0]);
I have a matrix, A, that contains 50 rows and 4 columns, and the entries are filled with integers. My interest is to construct a stacked 3D bar plot from this data. However, using bar3(A,'stacked') creates a row of 50 bars, whereas I want the bars to be plotted at the coordinates of a grid of size 5 (vertical) x 10 (horizontal). So the first bar in the row would be at location (1,1), second bar at (1,2), 11th bar at (2,1) and so on until the 50th bar which would be at (5,10). I can't seem to find a way to do this in Matlab, is this possible at all?
Thank you in advance!
I agree with #cris, there are better ways to represent your data. However, something like this would work if you still want to do use a 3D bar plot:
figure
hold on
for i = 1:5
Ai = A(10*(i-1)+1:10*i,:);
h = bar3(1:10,Ai,'stacked');
for ih = 1 :length(h)
x = get(h(ih), 'Xdata');
set(h(ih), 'Xdata', x+i-1);
end
end
view(3)
Say that I have a matrix Z with some values, and I want to illustrate it by a plotting the values in Z by height. The first solution comes to mind is a surface, but using surf and similar functions with small matrices doesn't look good.
So I thought about using something like a 3D bar plot with bar3. But the problem is that this function always sets the color by the group and not by height, and I can't get it to do so.
Here is an example:
Z = peaks(5);
subplot 121
surf(Z)
title('Surface look bad')
subplot 122
bar3(Z)
title('The color is not by height')
I tried to look for the color properties in the handles returned by bar3 (like CData and FaceColor) but got lost with all the values and how they relate to the bars themselves.
Ultimately, I would like to have a general solution that for 2 matrices Z and C I can create a 3D bar plot with bars in height given by Z and color given by C.
How can I do so?
The function bar3 returns a surface object, one for each group (i.e. one for each color), so all the bars in one group are essentially plotted as one 'broken' surface. This is explained very good in this answer, so I won't repeat it here.
Instead, I'll get to the solution for this specific problem. The relevant property of the surface is CData. When we create the bar plot, each surface's CData is assigned with a matrix in some size (we'll get to this) that is all equal one value. A different value for each surface. This is how the figure as a whole translates its color map to the color of the groups.
As written above (and elaborated in the linked answer), each group represented by a surface, so it takes a whole matrix to define the color at each point of the surface. The first thing we want to do is to get this matrix size:
Z = peaks(5);
bar_h = bar3(Z);
% we take only the first one, but they are all the same size:
cdata_sz = size(bar_h(1).CData)
cdata_sz =
30 4
CData has always 4 columns (see here why), and the number of rows is always 6*number of groups. This is because it takes 5 vertices to create one closed rectangle with an area object (the last vertex is like the first one) and one line is for spacing between the bars with NaNs, so they will look separated.
Next, we need to enlarge our original colormap (which is the same size of Z) to fit CData in the right way. Essentially, we just want to repeat the same value for all vertices that belong to the same bar. Assuming Z is also our color data (i.e. we color by height) we do:
z_color = repelem(Z,6,4)
Now we need to split our z_color to different cells in the number of our groups. Each cell will contain the coloring data for one surface object:
z_color = mat2cell(z_color,cdata_sz(1),ones(1,size(Z,2))*cdata_sz(2));
And finally, we apply the new color data to the bar plot:
set(bar_h,{'CData'},z_color.')
As a bonus, if we want to remove all zero values from our bar, it can be done easily by setting them to NaN:
Z(abs(Z)<eps) = nan;
C(isnan(Z)) = nan; % if we use a colormap C different from Z
All the above could be boiled down to this handy function:
function bar_h = Cbar3(Z,C,b,y)
% Z - The data
% C - CData (if other then Z values)
% b - Minimum absolute value to keep colored
% y - y-axis values to order the data by
if nargin<2, C = Z; end
if nargin<3 || isempty(b), b = 0; end
Z(abs(Z)<b) = nan;
C(isnan(Z)) = nan;
if nargin<4
bar_h = bar3(Z);
else
bar_h = bar3(y,Z);
end
cdata_sz = size(bar_h(1).CData);
z_color = repelem(C,6,4);
z_color = mat2cell(z_color,...
cdata_sz(1),ones(1,size(Z,2))*cdata_sz(2));
set(bar_h,{'CData'},z_color.')
end
Example of usage:
subplot 121
Z = peaks(30);
Cbar3(Z,Z,0.5);
pbaspect auto
shading flat % just to get a cleaner look
title('Cbar3 using height as color')
subplot 122
Cbar3(Z,rand(size(Z)),0.5);
pbaspect auto
shading flat % just to get a cleaner look
title('Cbar3 using random as color')
Result:
This is a partial answer.
The case of using the bar height as color is covered by the official MATLAB documentation. Essentially the example code boils down to:
function q45423394
hB = bar3(peaks(25)); colorbar;
for indE = 1:numel(hB)
hB(indE).CData = hB(indE).ZData;
end
All you need to do afterwards is make sure that the colormap is the one you want.
While I find EBH's solution aesthetically more pleasing, here there is a simpler solution: interpolation
z = peaks(5);
[x,y]=meshgrid(1:0.1:size(z,1),1:0.1:size(z,2));
zn=interp2(z,x,y,'nearest');
% plot it
surf(zn,'edgecolor','none','facecolor','interp')
I'd like to add data to a grouped bar chart in matlab. However, I can't place each data in the top of each bar. Using this question for usual bar and this one, I tried the following code for grouped chart, but xpos and ypos is not correct. Any help is appreciated.
a=[0.92,0.48,0.49];
b=[0.74,0.60,0.30];
c=[0.70,0.30,0.10];
X=[a;b;c];
hbar = bar(X, 'grouped');
for i=1:length(hbar)
XDATA=get(hbar(i),'XData')';
YDATA=get(hbar(i),'YData')';
labels = cellstr(num2str(YDATA));
ygap=0.01;
for j=1:size(XDATA,2)
xpos=XDATA(i,1);
ypos=YDATA(i,1)+ygap;
t=[num2str(YDATA(1,j),3)];text(xpos,ypos,t,'Color','k','HorizontalAlignment','left','Rotation',90)
end
end
There are two main errors in your code:
the definition of the inner loop: XDATA is a (N x 1) array therefore the inner loop makes only one iteration since size(XDATA,2) is 1. This makes your labels added on the center bar of each group
in the innser loop you first set the variable t as the label (t=[num2str(YDATA(1,j),3)];; then you use the same variable as output of the text function (t = text(xpos,ypos,labels{i});; then you use that variable in another call to text but now it contains the handle to the label and no longer the label string. This generate an error.
To proper add the labels you have to modify your code in order to identify the X position of the label.
You have to retreive the position of each bar within the groups: the X position of each bar is given by its XDATA value, plus its offset with respect to the center of the group. The offset value is stored in the XOffset property of the bar (notice: this is an hidden / undocumented property).
This is a possible implementation:
% Generate some data
bar_data=rand(4,4)
% Get the max value of data (used ot det the YLIM)
mx=max(bar_data(:))
% Draw the grouped bars
hbar=bar(bar_data)
% Set the axes YLIM (increaed wrt the max data value to have room for the
% label
ylim([0 mx*1.2])
grid minor
% Get the XDATA
XDATA=get(hbar(1),'XData')';
% Define the vertical offset of the labels
ygap=mx*0.1;
% Loop over the bar's group
for i=1:length(hbar)
% Get the YDATA of the i-th bar in each group
YDATA=get(hbar(i),'YData')';
% Loop over the groups
for j=1:length(XDATA)
% Get the XPOS of the j-th bar
xpos=XDATA(j);
% Get the height of the bar and increment it with the offset
ypos=YDATA(j)+ygap;
% Define the labels
labels=[num2str(YDATA(j),3)];
% Add the labels
t = text(xpos+hbar(i).XOffset,ypos,labels,'Color','k','HorizontalAlignment','center','Rotation',90)
end
end
Hope this helps,
Qapla'
I'm supposed to show the plot of data (curve) and a plot of the average of the data (flat horizontal line) in the same graph. My code computes everything fine and displays the curve in the graph, but I can't get the horizontal line to show up.
plot(1:24,hours3,'b-.',1:24, avg3,'r--');
So the plot of the column "hours3" shows up just fine, but the plot of the average "avg3" does not come up at all. It should be a flat line that extends from points 1 to 24 on the x-axis. Does anyone know why this is?
It's because plot(1:24,avg3,rx--) is interpreted as 24 single points. Using rx-- as linestyle shows you that the points are plotted right but not connected.
plot(1:24,hours3,'b-.',1:24, repmat(avg3,1,24),'r--');
Also with hold on you can keep plotting in an existing figure.
Try below code:
StartPoint=1;
EndPoint=24;
Resolution=1; % you can set 0.1 if you want more precision
Ave=Ave3;% set a value for Ave3
x=[StartPoint:Resolution:EndPoint];
NumberofPoints=((EndPoint-StartPoint)/Resolution)+1;
HrzLine=Ave*ones(1,NumberofPoints);
Curve=0.2*x.^3-4.*x.^2+8;
plot(x,HrzLine);
hold on;
plot(x,Curve);
The 2018b release has made adding horizontal lines (& vertical lines) much easier with yline() (xline() for verticle lines).
Xrng = 1:.01:24;
fh =#(x) 0.2*x.^3 - 4.*x.^2 + 50*sin(x)+ 8; % curve
plot(Xrng,fh(Xrng))
yline(0) % horizontal line at y = 0
Subsequent lines can be added just as easily.
yline(50) % horizontal line at y = 0
xline(12,'b--') % vertical line at x = 12 (blue, dashed)
Requires MATLAB R2018b or later.