I have a simple 3d histogram
which I want to import into pgfplots, e.g. using matlab2tikz or by hand.
PGFplots does not offer 3d histograms.
Is there an easy way to do this?
You can use \addplot3 graphics
to include an image in pgfplots
. This allows you to use pgfplots
for drawing the axes and to add annotations using the data coordinate system.
If you have saved a plot from Matlab as an image called 3dcolumnchart.png
, for example, the following code
\addplot3 graphics[points={%
(-4.4449,4.6547,0) => (110.814,167.827)
(-4.633,-4.5186,0) => (264.187,74.679)
(4.5829,-4.5216,0) => (470.558,145.343)
(-0.45821,-0.43355,1157) => (287.474,379.016)
}] {3dcolumnchart.png};
\node at (axis cs:-1.5,0.5,490) [inner sep=0pt, pin={[pin edge={thick,black},align=left]145:Interesting\\Data Point}] {};
will generate
For this, you need to provide the mapping from the data coordinate system to the figure coordinate system for four points. You can do this by finding the data coordinates for the points using the "Data Cursor" in Matlab, and finding the figure coordinates (in pt
) for the same points in an image editor like GIMP. However, this can quickly become a bit tedious.
I've written a Matlab script called pgfplotscsconversion.m
that allows you to click on four points in the Matlab figure, and the mapping will be written to the Matlab command prompt.
Here's an example of how I arrived at the above figure.
Create the Matlab plot
hist3(randn(10000,2)) % some random data
set(get(gca,'child'),'FaceColor','interp','CDataMode','auto'); % colors
set(gcf,'PaperPositionMode','auto') % make sure the "print" paper format is the same as the screen paper format
Save the following code as pgfplotscsconversion.m
function pgfplotscsconversion
% Hook into the Data Cursor "click" event
h = datacursormode(gcf);
set(h,'UpdateFcn',@myupdatefcn,'SnapToDataVertex','off');
datacursormode on
% select four points in plot using mouse
% The function that gets called on each Data Cursor click
function [txt] = myupdatefcn(obj,event_obj)
% Get the screen resolution, in dots per inch
dpi = get(0,'ScreenPixelsPerInch');
% Get the click position in pixels, relative to the lower left of the
% screen
screen_location=get(0,'PointerLocation');
% Get the position of the plot window, relative to the lower left of
% the screen
figurePos = get(gcf,'Position');
% Get the data coordinates of the cursor
pos = get(event_obj,'Position');
% Format the data and figure coordinates. The factor "72.27/dpi" is
% necessary to convert from pixels to TeX points (72.27 poins per inch)
display(['(',num2str(pos(1)),',',num2str(pos(2)),',',num2str(pos(3)),') => (',num2str((screen_location(1)-figurePos(1))*72.27/dpi),',',num2str((screen_location(2)-figurePos(2))*72.27/dpi),')'])
% Format the tooltip display
txt = {['X: ',num2str(pos(1))],['Y: ',num2str(pos(2))],['Z: ',num2str(pos(3))]};
Run pgfplotscsconversion
, click on four points in your plot. Preferably select non-colinear points near the edges of the plot. Copy and paste the four lines that were written to the Matlab command window.
Export the plot as an image
axis off
print -dpng matlabout -r400 % PNG called "matlabout.png" with 400 dpi resolution
If you want to use vector PDF output, you'll have to set the paper size to match the figure size yourself, since the PDF driver doesn't automatically adjust the size:
currentScreenUnits=get(gcf,'Units') % Get current screen units
currentPaperUnits=get(gcf,'PaperUnits') % Get current paper units
set(gcf,'Units',currentPaperUnits) % Set screen units to paper units
plotPosition=get(gcf,'Position') % Get the figure position and size
set(gcf,'PaperSize',plotPosition(3:4)) % Set the paper size to the figure size
set(gcf,'Units',currentScreenUnits) % Restore the screen units
print -dpdf matlabout % PDF called "matlabout.pdf"
Remove the white background of the image, for example using the ImageMagick command
convert matlabout.png -transparent white 3dcolumnchart.png
Include the image in your pgfplots
axis. If you selected points on the plot corners, your xmin
, xmax
, ymin
and ymax
should be set automatically, otherwise you'll have to provide those yourself. Also, you'll need to adjust the width
and height
of the plot to get the right vertical placement of the plot.
\documentclass[border=5mm]{standalone}
\usepackage{pgfplots}
\begin{document}
\begin{tikzpicture}
\begin{axis}[3d box,xmin=-5,xmax=5,ymin=-5,ymax=5,width=9cm,height=9.25cm,grid=both, minor z tick num=1]
\addplot3 graphics[points={%
(-4.4449,4.6547,0) => (110.814,167.827)
(-4.633,-4.5186,0) => (264.187,74.679)
(4.5829,-4.5216,0) => (470.558,145.343)
(-0.45821,-0.43355,1157) => (287.474,379.016)
}]
{3dcolumnchart.png};
\node at (axis cs:-1.5,0.5,490) [inner sep=0pt, pin={[pin edge={thick,black},align=left]145:Interesting\\Data Point}] {};
\end{axis}
\end{tikzpicture}
\end{document}
width
and height
values of your plot manually to make the axes start at zero. Also, something seems to be wrong with your grid lines. What axis
options do you use? - Jake
I managed to achieve a 3-dimensional histogram effect by repeating the coordinates. I just repeat each x,y combination 4 times, once for each of the 4 possible bar tops it could appear in.
For example, the code
\documentclass{minimal}
\usepackage{pgfplots}
\begin{document}
\begin{tikzpicture}
\begin{axis}[
view = {120}{35},% important to draw x,y in increasing order
xmin = 0,
ymin = 0,
xmax = 3,
ymax = 3,
zmin = 0,
unbounded coords = jump,
colormap={pos}{color(0cm)=(white); color(6cm)=(blue)}
]
\addplot3[surf,mark=none] coordinates {
(0,0,0) (0,0,0) (0,1,0) (0,1,0) (0,2,nan) (0,2,nan) (0,3,nan) (0,3,nan)
(0,0,0) (0,0,2) (0,1,2) (0,1,3) (0,2,3) (0,2,1) (0,3,1) (0,3,0)
(1,0,0) (1,0,2) (1,1,2) (1,1,3) (1,2,3) (1,2,1) (1,3,1) (1,3,0)
(1,0,0) (1,0,0) (1,1,0) (1,1,6) (1,2,6) (1,2,0) (1,3,0) (1,3,0)
(2,0,nan) (2,0,nan) (2,1,0) (2,1,6) (2,2,6) (2,2,0) (2,3,nan) (2,3,nan)
(2,0,0) (2,0,1) (2,1,1) (2,1,0) (2,2,0) (2,2,0) (2,3,nan) (2,3,nan)
(3,0,0) (3,0,1) (3,1,1) (3,1,0) (3,2,nan) (3,2,nan) (3,3,nan) (3,3,nan)
(3,0,0) (3,0,0) (3,1,0) (3,1,0) (3,2,nan) (3,2,nan) (3,3,nan) (3,3,nan)
};
\end{axis}
\end{tikzpicture}
\end{document}
produces
The 0 z-coordinate values above are meant to have the same value as zmin
and the view has to be set such that the points with lower x and y coordinates are drawn first.
I don't know how to make the colour of the sides of the bars the same as the top, hence the monochrome colormap.
A larger example can be seen here
This is from some data I produced in python. To save it to a file, I had to add a few for loops that make sure all the points at the border are set to have z value equal to zmin. The function below takes the x, y mesh stored in x
and y
. The respective z values are in z
, a list of len(x) - 1
lists of length len(y) - 1
. It writes to a file output
that can be included with \addplot3 file {}
. It assumes that there are no NaN values and sets the z values along the border to zmin
.
import csv
def make3dhistogram(x, y, z, zmin, output):
writer = csv.writer(open(output, 'wb'), delimiter=' ')
i = 0
for j in range(len(y)):
writer.writerow((x[i], y[j], zmin))
writer.writerow((x[i], y[j], zmin))
for i in range(len(x)-1):
writer.writerow((x[i], y[0], zmin))
for j in range(len(y)-1):
writer.writerow((x[i], y[j], z[i][j]))
writer.writerow((x[i], y[j+1], z[i][j]))
writer.writerow((x[i], y[len(y)-1], zmin))
writer.writerow([])
writer.writerow((x[i+1], y[0], zmin))
for j in range(len(y)-1):
writer.writerow((x[i+1], y[j], z[i][j]))
writer.writerow((x[i+1], y[j+1], z[i][j]))
writer.writerow((x[i+1], y[len(y)-1], zmin))
writer.writerow([])
i = len(x)-1
for j in range(len(y)):
writer.writerow((x[i], y[j], zmin))
writer.writerow((x[i], y[j], zmin))
So for example
x = [0,1,2,3]
y = [0,1,2,3]
z = [[2,3,1], [0, 6, 0], [1, 0, 0]]
make3dhistogram(x, y, z, 0.0, 'data')
produces the simple plot above, this time with a grid on the z plane as none of the points are skipped.
shader=flat corner
. To get the connecting lines in a darker shade of the fill color, you can use draw=mapped color!80!black
. - Jake
matlab2tikz [1] now fully supports 3D histograms. This
load seamount
dat = [-y,x]; % Grid corrected for negative y-values
hist3(dat) % Draw histogram in 2D
n = hist3(dat); % Extract histogram data;
% default to 10x10 bins
view([-37.5, 30]);
gives
[1] http://www.mathworks.com/matlabcentral/fileexchange/22022view
parameters. The color can be improved a bit by setting shader=flat corner,draw=mapped color!70!black
in the \addplot3
options. - Jake
pdflatex
due to memory limitations, and compiling with lualatex
(which works) reveals that there are z-buffering errors due to the reversed y axis. - Jake
y dir=reverse
will work well with the next pgfplots stable (i.e. not with 1.5.1). - Christian Feuersänger
surf()
s logic. - Nico Schlömer
rfdemo2.m
from the rainflow
package: mathworks.de/matlabcentral/fileexchange/…. There's a bit of a discussion in the comments to my answer, and the chat that is linked there. - Jake
I was not satisfied with the previous answers and therefore created my own solution.
Jake's solution is present in the documentation but first I couldn't make it work, second it does not generate a image from data, which is what I want from Tikz.
I worked on Anton's answer. It took me some time to figure out the way coordinates were organized to put it in a automatic code reading data from a file. But even if I managed to do so I couldn't overcome the impossibility to create a personalized coloring. I was then left with the choice between a monochrome plot like Anton shows, and a figure where the top face of a bar has a different color than its sides.
I don't think that, as Nico said, matlab2tikz now fully supports 3D histograms, or I wouldn't have come here in the first place. I tried matlab2tikz on my picture given by matlab using 'bar3', and the result was bugged.
=> I offer a solution where I got rid of \begin{axis}
and the constraints like \edef
, \temp
and \noexpand
, that I had to use to make Anton's way work automatically.
Here is the minimal code :
\documentclass{minimal}
\usepackage{pgfplots}
\begin{document}
\pgfplotstableread{DataTest.dat}{\firsttable}
\pgfplotstablegetrowsof{DataTest.dat}
\pgfmathtruncatemacro{\rows}{\pgfplotsretval-1}
\pgfplotstablegetelem{0}{[index] 2}\of{\firsttable}
\let\maxZ\pgfplotsretval
\pgfmathsetmacro{\Zscale}{4/\maxZ}
\begin{tikzpicture}[x={(0.866cm,-0.5cm)},y={(0.866cm,0.5cm)},z={(0cm,4 cm)}]
\colorlet{redhsb}[hsb]{red} %
\colorlet{bluehsb}[hsb]{blue} %
\foreach \p in {1,...,\rows}{
\pgfplotstablegetelem{\p}{[index] 0}\of{\firsttable}
\let\x\pgfplotsretval
\pgfplotstablegetelem{\p}{[index] 1}\of{\firsttable}
\let\y\pgfplotsretval
\pgfplotstablegetelem{\p}{[index] 2}\of{\firsttable}
\let\z\pgfplotsretval
\pgfmathtruncatemacro{\teinte}{100-((\z/\maxZ)*100)}
\colorlet{col}[rgb]{bluehsb!\teinte!redhsb}
% Visible faces from original view
\fill[col] (\x+0.5,\y+0.5,\z) -- (\x+0.5,\y-0.5,\z) -- (\x+0.5,\y-0.5,0) -- (\x+0.5,\y+0.5,0) -- (\x+0.5,\y+0.5,\z);
\draw[black](\x+0.5,\y+0.5,\z) -- (\x+0.5,\y-0.5,\z) -- (\x+0.5,\y-0.5,0) -- (\x+0.5,\y+0.5,0) -- (\x+0.5,\y+0.5,\z);
\fill[col] (\x+0.5,\y-0.5,\z) -- (\x-0.5,\y-0.5,\z) -- (\x-0.5,\y-0.5,0) -- (\x+0.5,\y-0.5,0) -- (\x+0.5,\y-0.5,\z);
\draw[black](\x+0.5,\y-0.5,\z) -- (\x-0.5,\y-0.5,\z) -- (\x-0.5,\y-0.5,0) -- (\x+0.5,\y-0.5,0) -- (\x+0.5,\y-0.5,\z);
% Top face
\fill[col] (\x-0.5,\y-0.5,\z) -- (\x-0.5,\y+0.5,\z) -- (\x+0.5,\y+0.5,\z) -- (\x+0.5,\y-0.5,\z) -- (\x-0.5,\y-0.5,\z) ;
\draw[black] (\x-0.5,\y-0.5,\z) -- (\x-0.5,\y+0.5,\z) -- (\x+0.5,\y+0.5,\z) -- (\x+0.5,\y-0.5,\z) -- (\x-0.5,\y-0.5,\z);
}
\end{tikzpicture}
\end{document}
Using the file DataTest.dat containing:
X Y Z
3 3 1 %max
1 3 1
1 2 0.8
1 1 0.7
2 3 0.75
2 2 0.5
2 1 0.25
3 3 0.3
3 2 0.1
3 1 0
Which gives the following picture :
To improve the picture and to have more comments on the code, here is a more detailed solution :
\documentclass{minimal}
\usepackage{pgfplots}
\begin{document}
\pgfplotstableread{DataTest.dat}{\firsttable} % Read
\pgfplotstablegetrowsof{DataTest.dat}
\pgfmathtruncatemacro{\rows}{\pgfplotsretval-1} % Put the number of row minus one in \rows,
% to use : \foreach \p in {0,...,\rows}
% Assign variables max
\pgfplotstablegetelem{0}{[index] 0}\of{\firsttable}
\let\maxX\pgfplotsretval
\pgfplotstablegetelem{0}{[index] 1}\of{\firsttable}
\let\maxY\pgfplotsretval
\pgfplotstablegetelem{0}{[index] 2}\of{\firsttable}
\let\maxZ\pgfplotsretval
\pgfmathsetmacro{\Zscale}{4/\maxZ} % contain the values of z between 0 and 4 cm
% (provided that they are positiv)
% Defining by hand the axis
\begin{tikzpicture}[x={(0.866cm,-0.5cm)},y={(0.866cm,0.5cm)},z={(0cm,\Zscale cm)}]
% Defining hsb color to have a color scale
\colorlet{redhsb}[hsb]{red} %
\colorlet{bluehsb}[hsb]{blue} %
% Drawing the system of axes
\draw[->] (0,0,0) -- (1,0,0) node [black,left] {x};
\draw[->] (0,0,0) -- (0,1,0) node [black,left] {y};
\draw[->] (0,0,0) -- (0,0,1/\Zscale) node [black,left] {z};
% Write unit on x and y
\foreach \p in {1,...,\maxX}{
\draw {(\p,0,0)} node[right] {\p};
% Draw the grid
\foreach \q in {1,...,\maxY}{
\draw[black] (\p-0.5,\q-0.5,0) -- (\p+0.5,\q-0.5,0) -- (\p+0.5,\q+0.5,0) -- (\p-0.5,\q+0.5,0) -- (\p-0.5,\q-0.5,0);
}
}
\foreach \p in {1,...,\maxY}{
\draw {(\maxX+1,\p,0)} node[left] {\p};
}
\foreach \p in {1,...,\rows}{
\pgfplotstablegetelem{\p}{[index] 0}\of{\firsttable} % The order in which the bars are drawn is determined by
\let\x\pgfplotsretval % the order of the lines in the data file.
\pgfplotstablegetelem{\p}{[index] 1}\of{\firsttable} % And as the drawings just pile up, the last one just goes
\let\y\pgfplotsretval % on top of the previous drawings.
\pgfplotstablegetelem{\p}{[index] 2}\of{\firsttable} % The order here works with chosen view angle, if you
\let\z\pgfplotsretval % change the angle, you might have to change it.
\pgfmathsetmacro{\w}{0.8/2} % half the width of the bars
\pgfmathtruncatemacro{\teinte}{100-((\z/\maxZ)*100)}
\colorlet{col}[rgb]{bluehsb!\teinte!redhsb}
% Unseen faces from orginal view, but if you change the angle ....
%\fill[col] (\x-\w,\y-\w,\z) -- (\x-\w,\y+\w,\z) -- (\x-\w,\y+\w,0) -- (\x-\w,\y-\w,0) -- (\x-\w,\y-\w,\z);
% \draw[black] (\x-\w,\y-\w,\z) -- (\x-\w,\y+\w,\z) -- (\x-\w,\y+\w,0) -- (\x-\w,\y-\w,0) -- (\x-\w,\y-\w,\z);
%\fill[col] (\x-\w,\y+\w,\z) -- (\x+\w,\y+\w,\z) -- (\x+\w,\y+\w,0) -- (\x-\w,\y+\w,0) -- (\x-\w,\y+\w,\z);
% \draw[black](\x-\w,\y+\w,\z) -- (\x+\w,\y+\w,\z) -- (\x+\w,\y+\w,0) -- (\x-\w,\y+\w,0) -- (\x-\w,\y+\w,\z);
% Visible faces from original view
\fill[col] (\x+\w,\y+\w,\z) -- (\x+\w,\y-\w,\z) -- (\x+\w,\y-\w,0) -- (\x+\w,\y+\w,0) -- (\x+\w,\y+\w,\z);
\draw[black](\x+\w,\y+\w,\z) -- (\x+\w,\y-\w,\z) -- (\x+\w,\y-\w,0) -- (\x+\w,\y+\w,0) -- (\x+\w,\y+\w,\z);
\fill[col!60!gray] (\x+\w,\y-\w,\z) -- (\x-\w,\y-\w,\z) -- (\x-\w,\y-\w,0) -- (\x+\w,\y-\w,0) -- (\x+\w,\y-\w,\z);
\draw[black](\x+\w,\y-\w,\z) -- (\x-\w,\y-\w,\z) -- (\x-\w,\y-\w,0) -- (\x+\w,\y-\w,0) -- (\x+\w,\y-\w,\z);
% Top face
\fill[top color=col!40!gray, bottom color=col!80!gray] (\x-\w,\y-\w,\z) -- (\x-\w,\y+\w,\z) -- (\x+\w,\y+\w,\z) -- (\x+\w,\y-\w,\z) -- (\x-\w,\y-\w,\z) ;
\draw[black] (\x-\w,\y-\w,\z) -- (\x-\w,\y+\w,\z) -- (\x+\w,\y+\w,\z) -- (\x+\w,\y-\w,\z) -- (\x-\w,\y-\w,\z);
}
\end{tikzpicture}
\end{document}
Which gives the image :
As you can see I added some features which are detailed in the comments of the code.
I hope it can help someone, thanks for reading.
To complete this, here is the Matlab code I used to generate the data file from the data in the matrix MAC
.
fileID = fopen('DataOutMac.dat','w');
MaxMAC = max(MAC(:));
%Header
fprintf(fileID,'X \t\t Y \t\t Z \n');
%Maximums
fprintf(fileID,['' num2str(size(MAC,1)) ' \t\t ' num2str(size(MAC,2)) ' \t\t ' num2str(MaxMAC) ' \t\t %% max \n \n']);
%All the values
for i=1:size(MAC,1)
for j=size(MAC,2):-1:1
if MAC(i,j)>(MaxMAC/10000) % To remove the smallest bars
fprintf(fileID,['' num2str(i) ' \t\t ' num2str(j) ' \t\t%12.8f \n'], MAC(i,j));%'%12.8f\n'], MAC(i,j));
end
end
end
fclose(fileID);
Edit to answer the comment:
-First, the reason why I am here in the first place : on the left is matlab's output and on the right matlab2tikz's result.
-Second, here is the result of one of my tries of jake's solution.
\usepackage{pgfplotstable} \pgfplotsset{compat=newest}
(I was getting Lua errors, even with lualatex
otherwise), then it worked without any problem. - Suzanne Soy
WITHOUT EXTERNAL PROGRAMS: Just produce a scatter plot of cubes with the height of the code depending on the data. (I stole the code from here [1], but the author of the code is fine with me sharing it here ;-)
UPDATE: @sporc found a bug and helped me to improve the code. Thanks! See here [2] for further additions to the code.
If you run this, LaTeX will tell you which prefactor to insert. I could not do this in a simple way because of the expansion issues, essentially because pgfplots is setting the scales only after it is done with the plots. In this example, it would be possible to avoid this by doing an empty plot first. But then the code would blow up and the user would modify things twice, or do some other crazy things, such that I feel adjusting one single number is the lesser evil.
\documentclass[tikz,border=3.14pt]{standalone}
\usetikzlibrary{calc}
\usepackage{pgfplots}
\usepackage{pgfplotstable}
\pgfplotsset{compat=1.16}
% from https://tex.stackexchange.com/a/102770/121799
\def\pgfplotsinvokeiflessthan#1#2#3#4{%
\pgfkeysvalueof{/pgfplots/iflessthan/.@cmd}{#1}{#2}{#3}{#4}\pgfeov
}%
\def\pgfplotsmulticmpthree#1#2#3#4#5#6\do#7#8{%
\pgfplotsset{float <}%
\pgfplotsinvokeiflessthan{#1}{#4}{%
% first key <:
#7%
}{%
\pgfplotsinvokeiflessthan{#4}{#1}{%
% first key >:
#8%
}{%
% first key ==:
\pgfplotsset{float <}%
\pgfplotsinvokeiflessthan{#2}{#5}{%
% second key <
#7%
}{%
\pgfplotsinvokeiflessthan{#5}{#2}{%
% second key >
#8%
}{%
% second key ==
\pgfplotsset{float <}%
\pgfplotsinvokeiflessthan{#3}{#6}{%
% third key <
#7%
}{%
% third key >=
#8%
}%
}%
}%
}%
}%
}%
\begin{document}
\pgfplotstableread[col sep=comma,header=true]{%
x,y,color,myvalue
2,3,1,100
4,3,2,3
2,7,3,0.75
7,7,4,45
8,5,2,3
2,5,1,10
4,-4,2,1
4,1,3,75
5,-1,4,4
5,2,2,3
1,-2,1,10
2,5,2,5
3,-8,3,75
4,5,4,42
7,-2,2,2
}{\datatable}
%
%\pgfplotstablesort[col sep=comma,header=true]\resulttable{\datatable}
\pgfplotstablesort[create on use/sortkey/.style={
create col/assign/.code={%
\edef\entry{{\thisrow{x}}{\thisrow{y}}{\thisrow{myvalue}}}%
\pgfkeyslet{/pgfplots/table/create col/next content}\entry
}
},
sort key=sortkey,
sort cmp={%
iflessthan/.code args={#1#2#3#4}{%
\edef\temp{#1#2}%
\expandafter\pgfplotsmulticmpthree\temp\do{#3}{#4}%
},
},
sort,
columns/Mtx/.style={string type},
columns/Kind/.style={string type},]\resulttable{\datatable}
\begin{tikzpicture}%[x={(0.866cm,-0.5cm)},y={(0.866cm,0.5cm)},z={(0cm,1 cm)}]
\pgfplotsset{set layers}
\begin{axis}[% from section 4.6.4 of the pgfplotsmanual
view={120}{40},
width=320pt,
height=280pt,
z buffer=none,
xmin=-1,xmax=9,
ymin=-10,ymax=8,
zmin=0,zmax=200,
enlargelimits=upper,
ztick={0,100,200},
zticklabels={0,50,100}, % here one has to "cheat"
% meaning that one has to put labels which are the actual value
% divided by 2. This is because the bars will be centered at these
% values
xtick=data,
extra tick style={grid=major},
ytick=data,
grid=minor,
xlabel={$x$},
ylabel={$y$},
zlabel={$z$},
minor tick num=1,
point meta=explicit,
colormap name=viridis,
scatter/use mapped color={
draw=mapped color,fill=mapped color!70},
execute at begin plot={}
]
\path let \p1=($(axis cs:0,0,1)-(axis cs:0,0,0)$) in
\pgfextra{\pgfmathsetmacro{\conv}{2*\y1}
\typeout{Kindly\space\space consider\space setting\space the\space
prefactor\space of\space z\space to\space \conv}};
\addplot3 [visualization depends on={
0.9952*z \as \myz}, % you'll get told how to adjust the prefactor
scatter/@pre marker code/.append style={/pgfplots/cube/size z=\myz pt},%
scatter,only marks,
mark=cube*,mark size=5,opacity=1]
table[x expr={\thisrow{x}},y expr={\thisrow{y}},z
expr={1*\thisrow{myvalue}},
meta expr={\thisrow{color}}
] \resulttable;
\end{axis}
\end{tikzpicture}
\end{document}
[1] https://texwelt.de/wissen/antwort_link/21902/zmax
to match a smaller max z value, the bars seem to float, 2)In your example the yellow bar is behind the purple bar, which doesn't look very nice - sporc
zmax
too small. The whole post is in a sense a big cheat: I just put cubes of different length at the various positions, and you'll always be able to find a scenario where that fails. - user121799
Despite the many solutions and this being an old post, the initial problem/question, i.e. I have a MATLAB 3D histogram and want to put it in a nice tikz environment, is not solved. This is maybe because in the newer MATLAB versions the function histogram2 was introduced, but who knows.
Being myself in this situation, and considering I tend to dislike pgfplots and prefer plain TikZ (easier to make changes later, more low level and understandable) I propose the following MATLAB function that creates a nice TikZ figure from an histogram2 object.
function histogram2tikz( h, file )
%HISTOGRAM2TIKZ
if nargin < 2
file = 'figure.tex';
end
% Open file for writing / re-writing
fid = fopen( file, 'w' );
% Collect relevant variables from histogram
hvals = h.Values;
xed = h.XBinEdges;
yed = h.YBinEdges;
colorBars = 'blue';
clear h
% Begin 3D tikz picture
fprintf( fid, ...
'%% Begin 3D tikz picture \n \\tdplotsetmaincoords{20}{100} \n \\begin{tikzpicture}[tdplot_main_coords, yscale = %f] \n', ...
5/max( hvals(:) ) );
% Plot axis and grid
% Axis
maxx = max( xed ); maxy = max( yed ); maxval = max( hvals(:) );
minx = min( xed ); miny = min( yed );
fprintf( fid, ...
'%% Plot axis and grid \n\t%% Axis \n \\draw[->] (%f,%f,0) -- (%f,%f,0) node[anchor=west]{$y$} ;\n', ...
minx, miny, minx, maxy );
fprintf( fid, ...
'\\draw[->] (%f,%f,0) -- (%f,%f,0) node[anchor=north]{$x$};\n', ...
minx, miny, maxx, miny );
fprintf( fid, ...
'\\draw[->] (%f,%f,0) -- (%f,%f,%f);\n', ...
minx, miny, minx, miny, maxval );
% Grid
fprintf( fid, '\t%%Grid and ticks\n');
for ii = 1:length( xed )
fprintf( fid, ...
'\\draw[gray,thin] (%f,%f,0) -- (%f,%f,0);\n', ...
xed(ii), miny, xed(ii), maxy );
end
for ii = 1:length( yed )
fprintf( fid, ...
'\\draw[gray,thin] (%f,%f,0) -- (%f,%f,0);\n', ...
minx, yed(ii), maxx, yed(ii) );
end
% Plot bars
for ii = 1:length( xed )-1
for jj = 1:length( yed )-1
bar( xed(ii:ii+1), yed(jj:jj+1), hvals(ii,jj), fid, colorBars );
end
end
% Finish tikzfigure and close file
fprintf( fid, ...
'%% Finish tikz picture \n \\end{tikzpicture}' );
fclose( fid );
end
function bar( xbin, ybin, h, fid, cB )
fprintf( fid, '%%Bar with edges x: %.2f,%.2f; edges y: %.2f,%.2f ; height: %.2f\n', ...
xbin(1), xbin(2), ybin(1), ybin(2), h );
if h>0
% Draw bottom square
fprintf( fid, ...
'\\draw[thin] (%f,%f,0) -- (%f,%f,0) -- (%f,%f,0) -- (%f,%f,0) -- cycle;\n', ...
xbin(1), ybin(1), ...
xbin(1), ybin(2), ...
xbin(2), ybin(2), ...
xbin(2), ybin(1) );
% Draw back faces
fprintf( fid, ...
'\\draw[thin,fill=%s!70!black] (%f,%f,%f) -- (%f,%f,%f) -- (%f,%f,%f) -- (%f,%f,%f) -- cycle;\n', ...
cB, ...
xbin(1), ybin(1), 0, ...
xbin(2), ybin(1), 0, ...
xbin(2), ybin(1), h, ...
xbin(1), ybin(1), h );
fprintf( fid, ...
'\\draw[thin,fill=%s!70!black] (%f,%f,%f) -- (%f,%f,%f) -- (%f,%f,%f) -- (%f,%f,%f) -- cycle;\n', ...
cB, ...
xbin(1), ybin(1), 0, ...
xbin(1), ybin(2), 0, ...
xbin(1), ybin(2), h, ...
xbin(1), ybin(1), h );
% Draw front faces
fprintf( fid, ...
'\\draw[thin,fill=%s!70!black] (%f,%f,%f) -- (%f,%f,%f) -- (%f,%f,%f) -- (%f,%f,%f) -- cycle;\n', ...
cB, ...
xbin(1), ybin(2), 0, ...
xbin(2), ybin(2), 0, ...
xbin(2), ybin(2), h, ...
xbin(1), ybin(2), h );
fprintf( fid, ...
'\\draw[thin,fill=%s!70!black] (%f,%f,%f) -- (%f,%f,%f) -- (%f,%f,%f) -- (%f,%f,%f) -- cycle;\n', ...
cB, ...
xbin(2), ybin(1), 0, ...
xbin(2), ybin(2), 0, ...
xbin(2), ybin(2), h, ...
xbin(2), ybin(1), h );
% Draw top square
fprintf( fid, ...
'\\path[fill=%s] (%f,%f,%f) -- (%f,%f,%f) -- (%f,%f,%f) -- (%f,%f,%f) -- cycle;\n', ...
cB, ...
xbin(1), ybin(1), h, ...
xbin(1), ybin(2), h, ...
xbin(2), ybin(2), h, ...
xbin(2), ybin(1), h );
end
end
A minimal working example would be running in MATLAB
% Generate Multivariate normal data
d = mvnrnd( [0,0], eye(2), 200 );
% Get Histogram
h = histogram2( d(:,1), d(:,2) );
% Generate TikZ figure
histogram2tikz(h)
and having in the same folder the minimal LaTeX document
\documentclass{article}
% Graphics in tikz format
\usepackage{tikz}
% 3D tikz, simple library
\usepackage{tikz-3dplot}
%% Document begins
\begin{document}
\input{figure}
\end{document}
The great thing about this is that then you can get into the generated file and change colors for every bar, as well as anything other you might want, in a pretty straightforward manner if you know your basic TikZ. For example, replacing all fill=blue!70!black
by fill=blue!70!black,fill opacity=0.6
and change the yscale
for it to look nicer, you get
Regardless of the fact that this is a confusing perspective, you can create a 3D bar diagram with pgfplots like this:
The idea is to use mark=cube*
and manipulate the cube/size z
in such a way that the desired 3D bars are created.
Similar to the answer from @user121799, with the difference that reading out and using the unit length in the z-direction should be automated.
If one put e.g. cube/size z=4mm
, so the cube dimensions grow from its room position by 2mm
and by -2mm
in the z-direction.
So they have to be positioned at
x=X, y=Y, z expr={0.5*\thisrow{Z}}
.
The correct height of the cubes can be made with
visualization depends on={\thisrow{Z} \as \zvalue},
scatter/@pre marker code/.append style={
/utils/exec=\pgfmathsetmacro{\barheight}{\zunitlength*\zvalue},
/pgfplots/cube/size z=\barheight },
where the zunitlength
is found with
\path let \p1=($(axis cs:0,0,1)-(axis cs:0,0,0)$) in
\pgfextra{ \pgfmathsetglobalmacro{\zunitlength}{\y1} } };
Note: It turns out that it makes sense to set zmax=\zMax
, where this maximum z-value \zMax
can be determined in the form
\pgfplotstablegetrowsof{\datatable}
\pgfmathtruncatemacro{\RowsNo}{\pgfplotsretval-1}
%Number of Rows: \RowsNo
\pgfmathsetmacro\zMax{0}
\foreach \n in {0,...,\RowsNo}{
\pgfplotstablegetelem{\n}{Z}\of{\datatable}
\pgfmathparse{\pgfplotsretval > \zMax ? \pgfplotsretval : \zMax}
\xdef\zMax{\pgfmathresult}
}
%Maximum z-Axis: \zMax
\documentclass[border=5pt, tikz]{standalone}
\usepackage{pgfplots}
\usepackage{pgfplotstable}
\pgfplotsset{compat=1.17}
\usetikzlibrary{calc}
\def\pgfmathsetglobalmacro#1#2{\pgfmathparse{#2}%
\global\let#1\pgfmathresult}
\pgfplotsset{
colormap = {mycolormap}{
color(0) = (blue!50!black);
color(1) = (purple);
color(2) = (green!55!black);
color(3) = (brown);
color(4) = (blue!66)
color(5) = (violet)
},
colormap name=mycolormap,
%colormap name=viridis
}
\begin{document}
\pgfplotstableread[col sep=comma,header=true]{
X, Y, Z
2, 0, 4
1, 0, 5
0, 1, 10
3, 1, 1
1, 1, 3
2, 1, 0
1, 2, 0
2, 2, 5
2, 3, 6
1, 4, 7
1, 5, 11
}{\datatable}
% z-Maximum determination ==================
\pgfplotstablegetrowsof{\datatable}
\pgfmathtruncatemacro{\RowsNo}{\pgfplotsretval-1}
%Number of rows: \RowsNo
\pgfmathsetmacro\zMax{0}
\foreach \n in {0,...,\RowsNo}{
\pgfplotstablegetelem{\n}{Z}\of{\datatable}
\pgfmathparse{\pgfplotsretval > \zMax ? \pgfplotsretval : \zMax}
\xdef\zMax{\pgfmathresult}
}
%Maximum z-Axis: \zMax
% ============================
\begin{tikzpicture}[]
\begin{axis}[
%height=2cm, width=7cm,
% view={120}{40}, x dir=reverse,
xmin=0,
ymin=0,
zmin=0, zmax=\zMax,
enlarge z limits={rel=0.25,upper},
xtick={1,...,10},
ytick={1,...,10},
%ytick={0,25,...,100},
grid=both,
xlabel={$x$}, ylabel={$y$}, zlabel={$z$},
minor z tick num=1,
point meta=explicit,
scatter/use mapped color={draw=mapped color!50!black,
fill=mapped color!70},
]
% unitlenghth z-Axis determination
\path let \p1=($(axis cs:0,0,1)-(axis cs:0,0,0)$) in
\pgfextra{ \pgfmathsetglobalmacro{\zunitlength}{\y1} } node[xshift=2cm, yshift=2cm]{%\zunitlength % show value
};
\addplot3[
scatter, only marks,
mark=cube*, mark size=5, %opacity=0.8,
nodes near coords*=, % will er...
%%% barheight determination
visualization depends on={\thisrow{Z} \as \zvalue},
scatter/@pre marker code/.append style={
/utils/exec=\pgfmathsetmacro{\barheight}{\zunitlength*\zvalue},
/pgfplots/cube/size z=\barheight
},
] table[x=X, y=Y,
z expr={0.5*\thisrow{Z}},
meta expr={\thisrow{Y}}
]{\datatable};
\end{axis}
\end{tikzpicture}
\end{document}
pgfplots
offer. - percusseNaN
values. - percusseview={0}{90}
, they'd be a great way to visualise scalar fields. Currently there is no correct way to do this with pgfplots, hence (+1). I want this as well. - DennisH