且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何在 3D 条形图中为条形设置任意颜色?

更新时间:2023-11-26 23:36:52

bar3 函数返回一个 surface 对象,每组一个(即每种颜色一个),因此一组中的所有条形基本上都绘制为一个破碎"表面.这在

以上所有都可以归结为这个方便的功能:

function bar_h = Cbar3(Z,C,b,y)% Z - 数据% C - CData(如果不是 Z 值)% b - 保持彩色的最小绝对值% y - y 轴值以对数据进行排序如果 nargin<2,C=Z;结尾如果 nargin

使用示例:

subplot 121Z = 峰值(30);Cbar3(Z,Z,0.5);pbaspect 汽车阴影平面 % 只是为了让外观更干净title('Cbar3 使用高度作为颜色')第 122 章Cbar3(Z,rand(size(Z)),0.5);pbaspect 汽车阴影平面 % 只是为了让外观更干净title('Cbar3 使用随机颜色')

结果:

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: