3D histogram with gnuplot or octave

后端 未结 5 504
余生分开走
余生分开走 2020-12-09 13:17

I would like to draw a 3D histogram (with gnuplot or octave) in order to represent my data. lets say that I have a data file in the following form:

2 3 4             


        
相关标签:
5条回答
  • 2020-12-09 13:25

    Have you looked at this tutorial on bar3?

    Adapting it slightly:

    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;
    

    And here's what you get at the end: enter image description here

    0 讨论(0)
  • 2020-12-09 13:30

    Below is a function I implemented that acts as a bar3 replacement (partially).

    In my version, the bars are rendered by creating a patch graphics object: we build a matrix of vertex coordinates and a list of faces connecting those vertices.

    The idea is to first build a single "3d cube" as a template, then replicate it for as many bars as we have. Each bar is shifted and scaled according to its position and height.

    The vertices/faces matrices are constructed in a vectorized manner (look ma, no loops!), and the result is a single patch object drawn for all bars, as opposed to multiple patches one per bar (this is more efficient in terms of graphics performance).

    The function could have been implemented by specifying coordinates of connected vertices that form polygons, by using the XData, YData, ZData and CData properties instead of the Vertices and Faces properties. In fact this is what bar3 internally does. Such approach usually requires larger data to define the patches (because we cant have shared points across patch faces, although I didn't care much about that in my implementation). Here is a related post where I tried to explain the structure of the data constructed by 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
    

    Here is an example to compare it against the builtin bar3 function in MATLAB:

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

    comparison_bar3

    Note that I chose to color all the bars in a single solid color (similar to the output of the hist3 function), while MATLAB emphasizes the columns of the matrix with matching colors.

    It is easy to customize the patch though; Here is an example to match bar3 coloring mode by using indexed color mapping (scaled):

    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

    Or say you wanted to color the bars using gradient according to their heights:

    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

    Note that in the last example, the "Renderer" property of the figure will affect the appearance of the gradients. In MATLAB, the 'OpenGL' renderer will interpolate colors along the RGB colorspace, whereas the other two renderers ('Painters' and 'ZBuffer') will interpolate across the colors of the current colormap used (so the histogram bars would look like mini colorbars going through the jet palette, as opposed to a gradient from blue at the base to whatever the color is at the defined height as shown above). See this post for more details.


    I've tested the function in Octave 3.6.4 and 3.8.1 both running on Windows, and it worked fine. If you run the examples I showed above, you'll find that some of the advanced 3D features are not yet implemented correctly in Octave (this includes transparency, lighting, and such..). Also I've used functions not available in Octave like membrane and spiral to build sample matrices, but those are not essential to the code, just replace them with your own data :)

    octave_my_bar3

    0 讨论(0)
  • 2020-12-09 13:37

    I don't have access to Octave, butI believe this should do the trick:

    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;
    
    0 讨论(0)
  • 2020-12-09 13:37

    I think the following should do the trick. I didn't use anything more sophisticated than colormap, surf and patch, which to my knowledge should all work as-is in Octave.

    The code:

    %# 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
    

    Result:

    enter image description here

    How I generate the "nominal bar" is based on code from MATLAB's cylinder(). One cool thing about that is you can very easily make much more funky-looking bars:

    enter image description here

    This was generated by changing

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

    into

    n = 8;
    r = [0.5; 0.45; 0.2; 0.1; 0.2; 0.45; 0.5];
    
    0 讨论(0)
  • 2020-12-09 13:43

    Solution using only functions available in OCTAVE, tested with octave-online

    This solution generates a surface in a similar way to the internals of Matlabs hist3d function.

    In brief:

    • creates a surface with 4 points with the "height" of each value, which are plotted at each bin edge.
    • Each is surrounded by zeros, which are also plotted at each bin edge.
    • The colour is set to be based on the bin values and is applied to the 4 points and the surrounding zeros. (so that the edges and tops of the 'bars' are coloured to match the "height".)

    For data given as a matrix containing bin heights (bin_values in the code):

    Code

    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)
    

    Output

    3dhist

    0 讨论(0)
提交回复
热议问题