3

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

1 Answer 1

6

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

13
  • 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, 2018 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.
    – user121799
    Jun 12, 2018 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, 2018 at 11:22
  • @sporc Yes, that's precisely how I compute it.
    – user121799
    Jun 28, 2018 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, 2018 at 6:44

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .