可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
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 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
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
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 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 :)
回答2:
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;
回答3:
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:
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];
into
n = 8; r = [0.5; 0.45; 0.2; 0.1; 0.2; 0.45; 0.5];
回答4:
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
回答5:
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: