2

I have found the answer from @marmot in the following post, which looks very nice and doesn't need any external programs. But I have some problem to adjust the z axis to my data.

3-dimensional histogram in pgfplots

In this example the z values of my data are not between 0 and 100, but between 0 and 1000. I don't get the part where it says "here one has to cheat". What do I need to adjust for z values not between 0 and 100?

\documentclass[tikz,border=3.14pt]{standalone}
\usepackage{pgfplots}
\usepackage{pgfplotstable}
\pgfplotsset{compat=1.15}
\begin{document}
\pgfplotstableread[col sep=comma,header=true]{%
x,y,color,myvalue
2,3,1,1000
4,3,2,30
2,7,3,0.75
7,7,4,450
8,5,2,300
2,5,1,100
4,-4,2,1
4,1,3,750
5,-1,4,4
5,2,2,300
1,-2,1,100
2,5,2,5
3,-8,3,750
4,5,4,420
7,-2,2,200
}{\datatable}
%
\pgfplotstablesort[col sep=comma,header=true]\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=8,
        ymin=-10,ymax=8,
        zmin=0,zmax=1000,
        enlargelimits=upper,
        ztick={0,500,1000},
        zticklabels={0,500,1000}, % here one has to "cheat"
        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},
        ]
\addplot3 [visualization depends on={z \as \myz},
scatter/@pre marker code/.append style={/pgfplots/cube/size z=\myz},%
scatter,only marks,
mark=cube*,mark size=5]
 table[x expr={\thisrow{x}},y expr={\thisrow{y}},z
 expr={\thisrow{myvalue}},
 meta expr={\thisrow{color}}
        ] \resulttable;
    \end{axis}
\end{tikzpicture}
\end{document}

Result:

enter image description here

EDIT: Now after Marmot presented a really nice solution for plotting a 3d histogram, I am asking if it's possible to make it even more automatic, by also computing the depth and width of the bars?

ADDITION: I have some data sets with a very high range in x and y. When I change the cube size like suggested in x and y direction I get very small bars (see first image below). My data is a classification, where the range is split up in 64 or 100 classes. So one bar needs to be as big as the range divided by 64 or 100 in x and y direction. The solution marmot suggested works fine, but in this specific case the cube sizes are to small. It says 0,00192pt in x and 0,00272pt in y direction. Right now I am calculating the cube size by hand. I take the values given by marmot and multiply them with my range and then divide them with my classes. For x direction: 0,00192pt*65000/64=1,95pt and for y: 0,00272pt*65000/64=2,7625pt. (Result in second image) How can I adapt this to the code?

enter image description here

enter image description here

5

You also need to adjust the height in that case. Note that you also draw some of the bars at the same x and y values. The current version of this trick doesn't support this. Before fixing this I would like to know if you really mean to draw them at the same x-y positions. UPDATE: I added an improved sort. Have a guess from whom I stole it. (Hint: his last name starts with "F" and ends with "euersänger";-) ANOTHER UPDATE: I made the thing more automatic. Due to the usual expansion issues, I could not make the conversion fully automatic, but now at least it will tell you what to put.

\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,1000
4,3,2,30
2,7,3,0.75
7,7,4,450
8,5,2,300
2,5,1,100
4,-4,2,1
4,1,3,750
5,-1,4,4
5,2,2,300
1,-2,1,100
2,5,2,5
3,-8,3,750
4,5,4,420
7,-2,2,200
}{\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=2000,
        enlargelimits=upper,
        ztick={0,1000,2000},
        zticklabels={0,500,1000}, % 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.09952*z \as \myz}, % you'll get told how to adjust the prefactor
scatter/@pre marker code/.append style={/pgfplots/cube/size z=\myz},%
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}

enter image description here

ADDENDUM: In this version, the dimension for "full size" bars are computed, i.e. the x and y dimensions to make the bar fill out one unit in each direction. It also checks if the user already has put the correct z scale. It computes the correct scale factor, and asks you to recompile if necessary.

\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}
\ifdefined\gconv
\else
\pgfmathsetmacro{\gconv}{0.1}
\fi
\pgfplotstableread[col sep=comma,header=true]{%
x,y,color,myvalue
2,3,1,1000
4,3,2,30
2,7,3,0.75
7,7,4,450
8,5,2,300
2,5,1,100
4,-4,2,1
4,1,3,750
5,-1,4,4
5,2,2,300
1,-2,1,100
2,5,2,5
3,-8,3,750
4,5,4,420
7,-2,2,200
}{\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=2000,
        enlargelimits=upper,
        ztick={0,1000,2000},
        zticklabels={0,500,1000}, % 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}
\ifx\gconv\conv
\else
\xdef\gconv{\conv}
\typeout{Please\space recompile\space the\space file!}
\fi     
        };  
\path let \p1=($(axis cs:1,0,0)-(axis cs:0,0,0)$) in 
\pgfextra{\pgfmathsetmacro{\convx}{veclen(\x1,\y1)}
\typeout{One\space unit\space in\space x\space 
        direction\space is\space\convx pt}
        };                  
\path let \p1=($(axis cs:0,1,0)-(axis cs:0,0,0)$) in 
\pgfextra{\pgfmathsetmacro{\convy}{veclen(\x1,\y1)}
\typeout{One\space unit\space in\space y\space 
        direction\space is\space\convy pt}
        };                  
\addplot3 [visualization depends on={
\gconv*z \as \myz}, % you may have to recompile to get the prefactor right
scatter/@pre marker code/.append style={/pgfplots/cube/size z=\myz},%
scatter/@pre marker code/.append style={/pgfplots/cube/size x=11.66135pt},%
scatter/@pre marker code/.append style={/pgfplots/cube/size y=9.10493pt},%
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}
\makeatletter
\immediate\write\@mainaux{\xdef\string\gconv{\gconv}\relax}
\makeatother

\end{tikzpicture}
\end{document}

enter image description here

  • I really appreciate your efforts. Thank you! When you change the angle of the view, for example to view={50}{70} something get's messed up with the sorting, the bars are overlapping, the same happens when you set x dir=reversed. Maybe there is a solution for it by changing the sorting algorithm. One little addition: You could add "pt" at the end of the typeout for the depth and width. – sporc Jun 12 '18 at 7:35
  • @sporc Yes, of course, if you change the view, it won't work any more. And I am doing the sort independently of the view. At this point, I don't see any easy way to fix that. But thanks for the remark on pt, I'll change this right away. – marmot Jun 12 '18 at 14:45
  • Could you please explain how you calculate the x and y dimensions. As far as I understand you take the length from (1,0,0) to (0,0,0) for x and divide it by xmax? I am asking beacuase I want to adjust the code so that it takes the length (1,0,0) to (0,0,0) and divides it by a fix value, like 100. So that 100 bars would fit along the axis – sporc Jun 28 '18 at 11:22
  • @sporc Yes, that's precisely how I compute it. – marmot Jun 28 '18 at 14:18
  • I can't see where you divide. Isn't this just the length?\pgfextra{\pgfmathsetmacro{\convy}{veclen(\x1,\y1)} – sporc Jun 29 '18 at 6:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.