使用gnuplot或octave创建3D直方图。

11
我想要绘制一个三维柱状图(使用gnuplot或者octave)来展示我的数据。假设我的数据文件如下所示:
2 3 4    
8 4 10    
5 6 7

我想绘制九个彩色条形图(矩阵的大小),在集合[1,3]x[1,3]中,使得条形图的颜色与其高度成比例。我该如何实现?

使用interp2和最近邻插值来将数据从3x3重采样到300x300,然后只需使用surf,您觉得如何? - Dan
@Dan,为什么你加了Matlab标签? - Tom Fenech
@TomFenech Matlab和Octave具有相同的语法和函数,而Matlab拥有更大的SO(Stack Overflow)社区,因此可以增加获得良好解决方案的机会。 - Dan
@Dan 我没有意识到它们是如此相似。我想如果一个有效的MATLAB答案在Octave上也能工作,那就可以了。 - Tom Fenech
@TomFenech 不是确定的,但极有可能。 - Dan
显示剩余2条评论
6个回答

19
以下是我实现的函数,它部分地充当了bar3的替代品。
在我的版本中,条形图是通过创建patch graphics object来渲染的:我们构建一个包含顶点坐标和连接这些顶点的面列表的矩阵。
思路是先建立一个单独的“三维立方体”作为模板,然后根据需要复制多个相同的立方体。每个条形图根据其位置和高度进行平移和缩放。
顶点/面矩阵以向量化的方式构建(看,没有循环!),结果是单个patch对象绘制所有条形图,而不是每个条形图一个补丁(这在图形性能方面更有效)。
该函数可以通过指定连接顶点形成多边形的坐标,使用XDataYDataZDataCData属性来实现,而不是VerticesFaces属性。事实上,这就是bar3内部所做的。这种方法通常需要更大的数据来定义补丁(因为我们不能在补丁面之间共享点,尽管我在我的实现中并不太关心这一点)。在这里,我试图解释由bar3构建的数据结构的相关帖子

my_bar3.m

function pp = my_bar3(M, width)
    % MY_BAR3  3D bar graph.
    %
    % M     - 2D matrix
    % width - bar width (1 means no separation between bars)
    %
    % See also: bar3, hist3

    %% construct patch
    if nargin < 2, width = 0.8; end
    assert(ismatrix(M), 'Matrix expected.')
    
    % size of matrix
    [ny,nx] = size(M);

    % first we build a "template" column-bar (8 vertices and 6 faces)
    % (bar is initially centered at position (1,1) with width=? and height=1)
    hw = width / 2;    % half width
    [X,Y,Z] = ndgrid([1-hw 1+hw], [1-hw 1+hw], [0 1]);
    v = [X(:) Y(:) Z(:)];
    f = [
        1 2 4 3 ; % bottom
        5 6 8 7 ; % top
        1 2 6 5 ; % front
        3 4 8 7 ; % back
        1 5 7 3 ; % left
        2 6 8 4   % right
    ];

    % replicate vertices of "template" to form nx*ny bars
    [offsetX,offsetY] = meshgrid(0:nx-1,0:ny-1);
    offset = [offsetX(:) offsetY(:)]; offset(:,3) = 0;
    v = bsxfun(@plus, v, permute(offset,[3 2 1]));
    v = reshape(permute(v,[2 1 3]), 3,[]).';

    % adjust bar heights to be equal to matrix values
    v(:,3) = v(:,3) .* kron(M(:), ones(8,1));

    % replicate faces of "template" to form nx*ny bars
    increments = 0:8:8*(nx*ny-1);
    f = bsxfun(@plus, f, permute(increments,[1 3 2]));
    f = reshape(permute(f,[2 1 3]), 4,[]).';

    %% plot
    % prepare plot
    if exist('OCTAVE_VERSION','builtin') > 0
        % If running Octave, select OpenGL backend, gnuplot wont work
        graphics_toolkit('fltk');
        hax = gca;
    else
        hax = newplot();
        set(ancestor(hax,'figure'), 'Renderer','opengl')
    end


    % draw patch specified by faces/vertices
    % (we use a solid color for all faces)
    p = patch('Faces',f, 'Vertices',v, ...
        'FaceColor',[0.75 0.85 0.95], 'EdgeColor','k', 'Parent',hax);
    view(hax,3); grid(hax,'on');
    set(hax, 'XTick',1:nx, 'YTick',1:ny, 'Box','off', 'YDir','reverse', ...
        'PlotBoxAspectRatio',[1 1 (sqrt(5)-1)/2]) % 1/GR (GR: golden ratio)

    % return handle to patch object if requested
    if nargout > 0
        pp = p;
    end
end

这是一个示例,用于与MATLAB中内置的bar3函数进行比较:

subplot(121), bar3(magic(7)), axis tight
subplot(122), my_bar3(magic(7)), axis tight

comparison_bar3

请注意,我选择将所有条形图都涂上单一的颜色(类似于hist3函数的输出),而MATLAB则强调使用匹配颜色的矩阵列。
不过很容易自定义这个补丁;以下是一个示例,使用索引颜色映射(缩放)来匹配bar3 着色模式:
M = membrane(1); M = M(1:3:end,1:3:end);
h = my_bar3(M, 1.0);

% 6 faces per bar
fvcd = kron((1:numel(M))', ones(6,1));
set(h, 'FaceVertexCData',fvcd, 'FaceColor','flat', 'CDataMapping','scaled')

colormap hsv; axis tight; view(50,25)
set(h, 'FaceAlpha',0.85)   % semi-transparent bars

bar3_coloring

假设你想要根据柱子的高度使用渐变来着色:

M = 9^2 - spiral(9);
h = my_bar3(M, 0.8);

% use Z-coordinates as vertex colors (indexed color mapping)
v = get(h, 'Vertices');
fvcd = v(:,3);
set(h, 'FaceVertexCData',fvcd, 'FaceColor','interp')

axis tight vis3d; daspect([1 1 10]); view(-40,20)
set(h, 'EdgeColor','k', 'EdgeAlpha',0.1)

gradient_bars_animation

请注意,在最后一个示例中,"图形的“渲染器”属性会影响梯度的外观。在MATLAB中,“OpenGL”渲染器会沿着RGB颜色空间插值颜色,而其他两个渲染器(“Painters”和“ZBuffer”)将插值当前使用的颜色映射表的颜色(因此直方图条看起来像小的colorbar穿过jet调色板,而不是从底部蓝色到定义高度处的任何颜色的渐变,如上所示)。有关更多详细信息,请参见此帖子
我已经在Windows上测试了Octave 3.6.43.8.1中的函数,并且运行良好。如果您运行我上面展示的示例,您会发现一些高级的3D功能在Octave中尚未正确实现(包括透明度、照明等)。此外,我使用了Octave中不可用的函数,如membranespiral来构建样本矩阵,但这些对代码不是必需的,只需用您自己的数据替换它们即可 :)

octave_my_bar3


太棒了。但为什么不将彩色条作为 C 数据的额外参数添加到函数中呢? - Dan
@Dan:你的意思是想以 my_bar3(Z,C) 的形式调用函数,其中 C(与 Z 大小相同的矩阵)用于着色条形图?我想这是可以做到的,但是 bar3(我试图复制它)没有提供这样的语法。此外,我不想处理所有可能的调用选项,因为这会使代码变得太长(我们必须检查是否传递了 C,如果缺少,我们是否假定 C=Z,还是应该为所有条形图使用单一颜色?此外,决定是否应将 C 用于指定渐变或用于具有纯色的条形图)。至少在示例中,我展示了如何完成其中的一些内容 :) - Amro
我想更像是 my_bar3(Z, width, C) 这样,这样它仍然可以像 bar3 一样使用。我认为默认的 C 可以是一个纯色,就像你现在创建的函数一样。我没有真正考虑渐变与纯色问题... - Dan

7

本解决方案仅使用OCTAVE中可用的功能,在octave-online上经过测试。

此解决方案以类似于Matlab hist3d函数内部的方式生成表面。

简而言之:

  • 创建一个具有每个值“高度”的4个点的表面,这些值在每个bin边缘处绘制。
  • 每个点周围都是零,这些零也在每个bin边缘处绘制。
  • 颜色设置为基于bin值,并应用于4个点和周围的零。(因此'条形'的边缘和顶部被涂成与“高度”相匹配的颜色。)

对于包含bin高度(代码中的bin_values)的矩阵给出的数据:

代码:

bin_values=rand(5,4); %some random data

bin_edges_x=[0:size(bin_values,2)]; 
x=kron(bin_edges_x,ones(1,5));
x=x(4:end-2);

bin_edges_y=[0:size(bin_values,1)]; 
y=kron(bin_edges_y,ones(1,5));
y=y(4:end-2);

mask_z=[0,0,0,0,0;0,1,1,0,0;0,1,1,0,0;0,0,0,0,0;0,0,0,0,0];
mask_c=ones(5);
z=kron(bin_values,mask_z);
c=kron(bin_values,mask_c);

surf(x,y,z,c)

输出

3dhist


太棒了。有没有可能在条之间添加空格/填充?(不过这不是什么大问题) - Steven Lu

3

我没有使用Octave的权限,但是我相信以下代码可以解决问题:

Z = [2 3 4
     8 4 10
     5 6 7];

[H W] = size(Z);

h = zeros( 1, numel(Z) ); 
ih = 1;
for ix = 1:W
    fx = ix-.45;
    tx = ix+.45;
    for iy = 1:W
        fy = iy-.45;
        ty = iy+.45;      

        vert = [ fx fy 0;...
            fx ty 0;...
            tx fy 0;...
            tx ty 0;...
            fx fy Z(iy,ix);...
            fx ty Z(iy,ix);...
            tx fy Z(iy,ix);...
            tx ty Z(iy,ix)];
        faces = [ 1 3 5;...
            5 3 7;...
            7 3 4;...
            7 8 4;...
            5 6 7;...
            6 7 8;...
            1 2 5;...
            5 6 2;...
            2 4 8;...
            2 6 8];

        h(ih) = patch( 'faces', faces, 'vertices', vert, 'FaceVertexCData', Z(iy,ix),...
            'FaceColor', 'flat', 'EdgeColor','none' );
        ih = ih+1;
    end
end
view( 60, 45 );
colorbar;

@Dan - 你能在Octave上检查一下吗? - Shai
我也没有安装Octave。我通常在这里检查:http://www.compileonline.com/execute_matlab_online.php,但它并不总是绘制图形。不过它没有错误,这是个好兆头。 - Dan
你能解释一下你的“faces”矩阵吗? - Dan
@Dan,一个面是由一系列顶点组成的列表。我怀疑Octave不支持非平面面,因此这里的所有面都是三角形(3个顶点)。每个三角形面(faces的一行)在vert中索引了三行。因此,面[1 3 5]是连接第一个、第三个和第五个顶点的三角形。 - Shai
我没有Octave来检查。好的,我明白了,每个条形图有10个三角形 - 谢谢。 - Dan
1
在Octave中运行正常;在Ubuntu 22.04上测试了脚本在6.4.0版本中。 - undefined

3
我认为以下代码可以解决问题。我没有使用比colormapsurfpatch更复杂的内容,据我所知这些内容在Octave中应该都可以直接使用。
代码如下:
%# Your data
Z = [2 3 4
    8 4 10
    5 6 7];


%# the "nominal" bar (adjusted from cylinder())
n = 4;
r = [0.5; 0.5];
m = length(r);
theta = (0:n)/n*2*pi + pi/4;

sintheta = sin(theta); sintheta(end) = sqrt(2)/2;

x0 = r * cos(theta);
y0 = r * sintheta;
z0 = (0:m-1)'/(m-1) * ones(1,n+1);

%# get data for current colormap
map = colormap;
Mz = max(Z(:));
mz = min(Z(:));

% Each "bar" is 1 surf and 1 patch
for ii = 1:size(Z,1)
    for jj = 1:size(Z,2)

        % Get color (linear interpolation through current colormap)
        cI = (Z(ii,jj)-mz)*(size(map,1)-1)/(Mz-mz) + 1;
        fC = floor(cI);
        cC = ceil(cI);
        color = map(fC,:) + (map(cC,:) - map(fC,:)) * (cI-fC);

        % Translate and rescale the nominal bar
        x = x0+ii;
        y = y0+jj;
        z = z0*Z(ii,jj);

        % Draw the bar
        surf(x,y,z, 'Facecolor', color)
        patch(x(end,:), y(end,:), z(end,:), color)

    end
end

结果:

enter image description here

我如何生成“名义条形图”是基于MATLAB的cylinder()代码。这个方法很酷的一点是你可以非常容易地制作出更加炫酷的条形图:

enter image description here

通过改变参数,可以生成上述效果。

n = 4;
r = [0.5; 0.5];

转换为

n = 8;
r = [0.5; 0.45; 0.2; 0.1; 0.2; 0.45; 0.5];

在线版本没有出现错误,但是不幸的是我无法检查它是否生成了正确的图形。如果没有出错,那么我会认为它是有效的,但是我担心无法真正检查。不过看起来是一个很好的解决方案! - Dan
就此代码而言,我得到了一个单独的大黄色框。 - Steven Lu
@StevenLu 你用的是哪个版本的MATLAB? - Rody Oldenhuis
@StevenLu:啊...你认为是什么原因导致了这个问题?我不明白为什么surfpatch或者colormap会以你所描述的方式失败... - Rody Oldenhuis
这在 Octave 上失败,因为您没有在对 surface 的调用之间使用 hold on。最终结果是只绘制了最后一个条形图。 - carandraug
显示剩余2条评论

2

您看过这篇关于bar3的教程了吗?

稍作修改:

Z=[2 3 4
   8 4 10
   5 6 7]; % input data
figure; 
h = bar3(Z); % get handle to graphics
for k=1:numel(h), 
    z=get(h(k),'ZData'); % old data - need for its NaN pattern
    nn = isnan(z); 
    nz = kron( Z(:,k),ones(6,4) ); % map color to height 6 faces per data point 
    nz(nn) = NaN; % used saved NaN pattern for transparent faces 
    set(h(k),'CData', nz); % set the new colors
end
colorbar;

最终你将会得到以下内容: 在此输入图片描述


1
Matlab的绝佳解决方案 - 但是看起来Octave中不存在bar3 :( - Dan

0

我想提出一个灵感来自ScilabMatlab的解决方案。对于颜色,我使用了Oldenhuis在这个页面上展示的代码。

clear -a;
data=[2,3,4;8,4,10;5,6,7];
[nr,nc]=size(data);
m=min(data(:));
M=max(data(:));
w=0.8;
cmap=colormap(jet);

figure(1);
clf;
hold on;
for i=1:nr
    for j=1:nc
        x=[zeros(2,1)+j+(1-w),ones(2,2)*w+j,zeros(2,2)+j+(1-w)];
        y=[zeros(2,2)+i+(1-w),ones(2,2)*w+i,zeros(2,1)+i+(1-w)];
        z=[zeros(1,5);ones(1,5)*data(i,j)];
        index=(data(i,j)-m)*(size(cmap,1)-1)/(M-m)+1;
        f=floor(index);
        c=ceil(index);
        color=cmap(f,:)+(cmap(c,:)-cmap(f,:))*(index-f);
        surf(x,y,z,"facecolor",color,"facealpha",1,"linewidth",0.1);
        patch(x',y',z',color,"facealpha",1,"linewidth",0.1);
    end
end

view(160,30);
xlabel("X label");
ylabel("Y label");
zlabel("Z label");
title("Title");
set(gca,"xtick",1.5:1:3.5,"xticklabel",{"3","2","1"});
set(gca,"ytick",1.5:1:3.5,"yticklabel",{"C","B","A"});
grid on;
hold off;

print -dpng plot_bar3d_test.png

结果是最终的图表


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接