Histograma 3D con gnuplot u octava
Frecuentes
Visto 10,890 equipos
11
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
8 4 10
5 6 7
I'd like to draw nine colored bars (the size of the matrix), in the set [1,3]x[1,3], such that the bar's color is proportional to the bar's height. How can I do this?
6 Respuestas
19
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
objeto drawn for all bars, as opposed to multiple patches one per bar (this is more efficient in terms of rendimiento de gráficos).
The function could have been implemented by specifying coordinates of connected vertices that form polygons, by using the XData
, YData
, ZData
e CData
propiedades en lugar de las Vertices
e 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 Publicación relacionada 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
función en MATLAB:
subplot(121), bar3(magic(7)), axis tight
subplot(122), my_bar3(magic(7)), axis tight
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
modo de colorear mediante el uso 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
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)
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 colorbar
s 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 esta publicación para más información.
I've tested the function in Octave 3.6.4 e 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
e spiral
to build sample matrices, but those are not essential to the code, just replace them with your own data :)
Respondido 23 Abr '22, 10:04
Awesome. But why not incorporate the colouring bars by height as a C-data extra parameter to the function? - Dan
@Dan: Do you mean you want to call the function as my_bar3(Z,C)
donde C
(a matrix of same size as Z
) is used to color the bars? I guess it could done, but bar3
(which I tried to replicate) doesn't offer such syntax. Plus I didn't want to handle all possible calling options as it would make the code too long (we have to check if C
is passed or not, do we assume C=Z
if its missing or should we use a single color for all bars? Plus decide whether C
should be used to specify gradients, or for bars with flat colors).. At least I sorta showed how to do some of it in the examples :) - Amro
Estaba pensando más como my_bar3(Z, width, C)
so that it can still be used just like bar3
. I think the default C
can be one solid colour as your function creates now. I didn't really think about the gradient vs solid issue... - Dan
@Dan: ok here is one possible way to incorporate a color parameter: pastebin.com/HL9UtY2N (diff/patch file). Now you can call the function in of the following ways: my_bar3(M,w)
(default solid color), my_bar3(M,w,rand(1,3))
e my_bar3(M,w,'red')
(user-supplied solid color), my_bar3(M,w,rand(size(M)))
(one color per bar as gradients using indexed mapping into the current colormap), or my_bar3(M,w,rand(numel(M),3))
(flat colors, one row per bar, denoted by a matrix of true-colors). - Amro
7
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
función.
En breve:
- 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):
Código
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)
Salida
Respondido 03 Jul 14, 16:07
Amazing. Any chance spaces/padding could get added in between the bars? (not a big deal though) - steven lu
3
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;
Respondido 03 Jul 14, 12:07
@Dan - can you check this on Octave? - Shai
I don't have Octave installed either. I normally check here: compileonline.com/execute_matlab_online.php but it doesn't always plot graphs. it doesn't error though so that's promising. - Dan
¿Puedes explicar tu faces
¿matriz? - Dan
@Dan a face is a list of vertices, I suspect that octave does not support non-planar faces and therefore all faces here are triangles (3 vertices). Each traiangle face (row of faces
) index three rows in vert
. Hence the face [1 3 5]
is a triangle connecting the first, third and fifth vertices. - Shai
I don't have Octave to check. OK I see so it's 10 triangles per bar - thanks. - Dan
3
I think the following should do the trick. I didn't use anything more sophisticated than colormap
, surf
e patch
, which to my knowledge should all work as-is in Octave.
El código:
%# 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
Resultado:
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:
This was generated by changing
n = 4;
r = [0.5; 0.5];
dentro
n = 8;
r = [0.5; 0.45; 0.2; 0.1; 0.2; 0.45; 0.5];
Respondido 03 Jul 14, 14:07
@Dan: can you verify whether this works correctly on Octave (or the online version thereof)? - Rody Oldenhuis
It doesn't error on the online version, but I can't check if it makes the right plot unfortunately. I would suspect that if it doesn't error then it does work though but I'm afraid I can't really check. Looks like a great solution though! - Dan
fwiw, I got a single big yellow box with this code. - steven lu
@StevenLu What MATLAB version do you have? - Rody Oldenhuis
I don't. Using Octave. I would hope it works perfectly on MATLAB. - steven lu
2
Has mirado 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:
Respondido el 03 de junio de 16 a las 09:06
Great solution for Matlab - but it looks like bar3
doesn't exist for Octave :( - Dan
0
I would like to propose a solution inspired by two references about Scilab e Matlab. For the colors, I have used the code shown in this page by 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
y el resultado es the final plot.
contestado el 09 de mayo de 23 a las 06:05
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas gnuplot histogram octave or haz tu propia pregunta.
¿Qué pasa con el uso
interp2
and nearest neighbours interpolation to resample your data from 3-by-3 to say 300-by-300 and then just usesurf
? - Dan@Dan why have you added the matlab tag? - Tom Fenech
@TomFenech Matlab and Octave share the same syntax and functions and Matlab has a larger SO community so it should increase the chances of getting a good solution - Dan
@Dan I didn't realise they were so similar. I guess if it's certain that a valid MATLAB answer will also work on Octave, then that's fair enough. - Tom Fenech
@TomFenech It's not certain, but it is extremely likely - Dan