How to set arbitrary colors for bars in a 3D bar p

2019-02-18 23:02发布

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')

enter image description here

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?

3条回答
太酷不给撩
2楼-- · 2019-02-18 23:47

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

demo2

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:

enter image description here

查看更多
姐就是有狂的资本
3楼-- · 2019-02-18 23:56

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')

enter image description here

查看更多
The star\"
4楼-- · 2019-02-18 23:57

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.

enter image description here

查看更多
登录 后发表回答