11

I want to state from the beginning that this is not supposed to be a “please draw for me” post. I just would like to know if it is possible to produce a data cube similar to this

enter image description here

with reasonable effort in Latex. I would preferable start off with a template if any is available. Reasonable effort shall be defined as two hours work max. My starting point is no knowledge of drawing in Latex. I basically only know that tikz can be used for drawing.

If anyone has done something similar I would appreciate it very much if he or she could post the code here and preferable state what he or she read in order to do it. If anyone knows where a template can be found, I would very much appreciate a link to the template. If it is not possible to produce a data cube with what was defined as reasonable effort but anyone knows some freeware program to produce this kind of data cube, I would like to invite the person to post the link to the program.

  • This could help but a lot of tweaking would be needed: tex.stackexchange.com/questions/29877/… – Augustin May 11 '15 at 15:40
  • I would say: No. But it's hard to say because, of course, it depends how quickly you read and learn. If you're unfamiliar with TikZ but familiar with some similar syntax (perhaps from programming), it might be easier for you than it was for me ;). Or I may just be an unusually slow learner :(. That is, it is perfectly possible to do this in TikZ (or, I assume, pstricks etc.) but not if you know nothing about the system and have a 2 hour limit. If this is a one-off, use a graphical programme. If you need to draw other cubes or things, it might be worth more than 2 hours to learn a system. – cfr May 11 '15 at 15:41
  • "Reasonable effort shall be defined as two hours work max." Uhm... :D – Alenanno May 11 '15 at 15:45
  • I will only need one of these data cubes and will most likely use another graphic program if nothing else will come up here. @Alenanno As "reasonable effort" is a rather wide term I have chosen to define it in terms of hours. – Thomas May 11 '15 at 17:23
  • 1
    @Thomas Please post the edit here as a follow-up question (you can link this question in the new one). It's better that way. This way you're changing this question which has been already been answered. – Alenanno May 13 '15 at 15:24
23
+50

This answer uses

  • the matrix library for the main part, including some auxiliary macros \rd and \rn for some random content in the matrix,
  • the calc library for some easy vector calculation,
  • my paths.ortho library (code: [1] and [2]) for the connections outside of the matrix,
  • my positioning-plus library for the use of left=of |(n1)(n2) which positions a node to the left of the vertical center of (n1) and (n2).

The local cs key (packaged later in the my cs style) is used to create a custom coordinate system that slants the vertical axis for the three-dimension part of the cube. (There are a lot of different ways to achieve this.)

There are also various different ways to achieve specific things in this code:

We could have drawn the lines inside the matrix as part of the inner nodes that lie inside it. We could have used the workarounds in the linked answer to replace my library.

A few explanations:

tight matrix

I have used a style tight matrix defined as

  tight matrix/.style={
    matrix of nodes, inner sep=+0pt, outer sep=+0pt,
    every cell/.append style={
      every node/.append style={
        outer sep=+0pt,
        inner xsep=+.1em,
        inner ysep=+.3333em, % default (overwritten by matrix inner sep)
        align=center,
        text depth=+0pt, % depth("y"),
        text height=height("M"),
        text width=width("MMM-00-00"), % possibly other approaches
      }}},

that sets up a tight matrix where the matrix’ nodes are set border to border. The text … keys are used so that all nodes have same height, depth and width.

The matrix is then filled just as you would.

A custom coordinate system simulating the third dimension

With my cs/.style={local cs={m.north west}{m.north east}{1}{2}} I'm setting up a coordinate system which origins lie at (m.north west), the x vector points to (m.north east) and the y coordinate is “x-slanted” by the factor of 1 (For every y you go an additionally 1 to the right.) Also, the y is doubled.

I have used the xslanting as a way to use orthogonal parts (like |-, -| or my own -|-) without the need to worry about the third dimension.


However, there is actually a third dimension in TikZ. Its vector can be set with the z key and its default is (-3.85mm, -3.85mm). (So it actually points “out” of the paper.) We cannot use this third dimension in path operators. (It’s just a coordinate specification which maps three values to 2d-vectors.) However the direction of the y vector can be calculated from the xslanting: <direction> = atan(1/<xslant>).

The “third” dimension

With the coordinate system set up, we can draw the cube’s borderuse a usual rectangle path operator and it will draw a slanted rectangle.

The coordinate system also allows us to use the relative coordinate + (up:1) if we want to get into the third dimension anyway.

Lines

With the custom coordinate system set up we can draw the box’s border:

\draw (0,0) |- (1,1) -- (right:1)
  (m.south east) -- ++ (up:1) -- (1,1);

In a few loops, we draw the lines:

\foreach \c in {1,2,3}
  \draw (m-12-\c.south east) -- (m-1-\c.north east) -- ++ (up:1);
\foreach \c in {1,...,12}
  \draw[if={isodd(\c)}{densely dashed}{}]
    (m-\c-1.south west) -- (m-\c-4.south east) -- ++(up:1);
\foreach \c in {1,...,7}
  \draw[if={isodd(\c)}{densely dashed}{}]
    (up:\c/8) coordinate (tl-\c) -- ++ (right:1) -- ([shift=(up:\c/8)]m.south east);

Here, I also define seven coordinates we later use to place the nodes at the top left of the cube.

Even though, in all three paths, the custom coordinate system is active, named nodes/coordinates are not affected as they are referenced absolutely. The ++(up:1) is used to get into the third dimension. The same applies to the shift coordinate at the end.

Nodes

For the placing of the descriptive nodes we either use the matrix’ cells’ nodes’ anchors as in

\foreach \st/\lt[count=\c from 0, evaluate={\d=int(2*\c+1)}]
  in {af/Africa, as/Asia, au/Australia, eu/Europe, na/North\\America, sa/South\\America}
    \node[continent] (\st) at (m-\d-1.south west) {\lt};

or the previous defined coordinates:

\foreach \st[count=\c from 0, evaluate={\d=int(2*\c+1)}] in {air, sea, road, rail}
  \node[continent, left=of (tl-\d)](\st){\st};

The positioning-plus library allows us to do left=of |(<n1>)(<n2>) which place the new node to the left of a coordinate that lies in the middle of the most left vertical line that spans the nodes (<n1>) and (<n1>). We could also write left=of ($(<n1>.north west)$!.5!(<n2>.south west)$) with just basic positioning.

The lines are drawn with the -|- to path and active my cs (see the options of \begin{scope}). The xslanting also slants the orthogonal connections:

\node[hemi, left=of |(af)(eu)] (eh) {Eastern\\Hemisphere}
  (eh) \foreach \co in {af, as, au, eu}{ edge[-|-, hvvh/distance=.3cm] (\co)};

Without paths.ortho you can write:

\node[hemi, left=of |(af)(eu)] (eh) {Eastern\\Hemisphere}
  (eh.east) \foreach \co in {af, as, au, eu}{
    edge[to path=++(right:.3cm) -| (\tikztotarget)] (\co)};

Code

\documentclass[tikz]{standalone}
\usetikzlibrary{matrix,paths.ortho,calc,positioning-plus}
\newcommand*\rd{{% random date
  \pgfmathrandominteger\m{0}{11}\ifcase\m Jan\or Feb\or Mar\or Apr\or May\or
    Jun\or Jul\or Aug\or Sep\or Oct\or Nov\or Dec\fi
    -\pgfmathprint{random(31)}-\pgfmathprint{random(100)}}}
\newcommand*\rn{\pgfmathprint{random(100,9999)}}
\tikzset{if/.code n args=3{\pgfmathparse{#1}\ifnum\pgfmathresult=0 
  \pgfkeysalso{#3}\else\pgfkeysalso{#2}\fi}}
\tikzset{local cs/.style n args=4{shift={(#1)}, x=($(#2)-(#1)$), xslant={#3}, yscale={#4}}}

\begin{document}\sffamily
\begin{tikzpicture}[
  tight matrix/.style={
    matrix of nodes, inner sep=+0pt, outer sep=+0pt,
    every cell/.append style={
      every node/.append style={
        outer sep=+0pt,
        inner xsep=+.1em,
        inner ysep=+.3333em, % default (overwritten by matrix inner sep)
        align=center,
        text depth=+0pt, % depth("y"),
        text height=height("M"),
        text width=width("MMM-00-00"), % possibly other approaches
      }}},
  desc/.style={/utils/exec=\scriptsize}, % font key is not perfect
  continent/.style={
    desc, align=center, anchor=east},
  hemi/.style={desc, align=center, text width=width("Hemisphere")},
  route/.style={desc},
  Route/.style={font=\bfseries},
]

% The matrix
\matrix[tight matrix, draw] (m) {
  190       & 215       & 160       & 240 \\
  Feb-17-99 & Apr-22-99 & Sep-07-99 & Dec-01-99 \\
  \rn & \rn & \rn & \rn \\
  \rd & \rd & \rd & \rd \\
  \rn & \rn & \rn & \rn \\
  \rd & \rd & \rd & \rd \\
  \rn & \rn & \rn & \rn \\
  \rd & \rd & \rd & \rd \\
  \rn & \rn & \rn & \rn \\
  \rd & \rd & \rd & \rd \\
  \rn & \rn & \rn & \rn \\
  \rd & \rd & \rd & \rd \\
};

% The 3D
\tikzset{my cs/.style={local cs={m.north west}{m.north east}{1}{2}}}
\begin{scope}[my cs]
  \draw (0,0) |- (1,1) -- (right:1)
    (m.south east) -- ++ (up:1) -- (1,1);

% The lines
\foreach \c in {1,2,3}
  \draw (m-12-\c.south east) -- (m-1-\c.north east) -- ++ (up:1);
\foreach \c in {1,...,12}
  \draw[if={isodd(\c)}{densely dashed}{}]
    (m-\c-1.south west) -- (m-\c-4.south east) -- ++(up:1);
\foreach \c in {1,...,7}
  \draw[if={isodd(\c)}{densely dashed}{}]
    (up:\c/8) coordinate (tl-\c) -- ++ (right:1) -- ([shift=(up:\c/8)]m.south east);
\end{scope}

% The Descriptions
\foreach \st/\lt[count=\c from 0, evaluate={\d=int(2*\c+1)}]
  in {af/Africa, as/Asia, au/Australia, eu/Europe, na/North\\America, sa/South\\America}
    \node[continent] (\st) at (m-\d-1.south west) {\lt};
\node[hemi, left=of |(af)(eu)] (eh) {Eastern\\Hemisphere}
  (eh) \foreach \co in {af, as, au, eu}{ edge[-|-, hvvh/distance=.3cm] (\co)};
\node[hemi] at (eh|-m-11-1.north) (wh) {Western\\Hemisphere}
  (wh) \foreach \co in {na, sa}{         edge[-|-, hvvh/distance=.3cm] (\co)};

\begin{scope}[node distance=.25cm, hvvh/distance=.2cm, my cs]
\foreach \st[count=\c from 0, evaluate={\d=int(2*\c+1)}] in {air, sea, road, rail}
  \node[continent, left=of tl-\d](\st){\st};

\node[route, left=of |(sea)(air)] (ng) {non-ground}
  (ng) \foreach \co in {sea, air}{  edge[-|-] (\co)};
\node[route, left=of |(rail)(road)] (g) {ground}
  (g) \foreach \co in {rail, road}{ edge[-|-] (\co)};
\node[Route, left=of |(g)(ng)] (R) {Route}
  (R) \foreach \co in {g, ng}{      edge[-|-] (\co)};
\end{scope}
\end{tikzpicture}
\end{document}

Output

enter image description here

  • And to answer the question "Is it reasonable?" Yes, if you know how to approach the problems wit TikZ. Even more so, if you have multiple of these cubes. – Qrrbrbirlbel May 11 '15 at 20:20
  • Yes, but the question explicitly stated the conditions and, given those conditions, the answer is surely 'No. It is not reasonable.' If you change the conditions, of course the answer will change... ;). – cfr May 11 '15 at 20:22
  • Wow this is very helpful and much more then i expected. The picture was basically just an example of what I was thinking about. If I had known that you are going to do it I would have drawn it on paper and uploaded it. Now I just have to find out how to get it to compile. – Thomas May 11 '15 at 20:37
  • @Thomas Start with the basic stuff (e.g. the matrix) and build upon that. (And if you get stuck, ask.) — I realize now that the 3d stuff is probably better drawn differently to understand it better as I use calculations and transformations at places you may not expect it. But I wanted to use the xslant factor for the "orthogonal" connections between the "Route" nodes. The cube is probably the most complicated part of the drawing. – Qrrbrbirlbel May 11 '15 at 20:44
  • Yes but that's way over my head. I'm googling right now how to add libraries to a package. I think you start to get the idea where I am at. – Thomas May 11 '15 at 20:48
20

Qrrbrbirlbel already gave a very nice answer, but since I've been working on this for hours (I'm less expert), I thought it'd be a waste not to post it. It certainly can be improved, but it's late here so I'll try to do it tomorrow.

There are some differences with Qrrbrbirlbel's answer, the main one being the fact that the cube's depth has been done using perspective and then clipping the excess lines.

Here is the cube without clipping (the red line is the clipping path):

figure 1

While here is the clipped image:

figure 2

I'll explain better in the next edit.

Output

figure 3

Code

\documentclass[margin=20pt]{standalone}
\usepackage{tikz}

\usetikzlibrary{shapes,calc,positioning}

\tikzset{
    mynode/.style={
        draw,
        thick,
        anchor=south west,
        minimum width=2cm,
        minimum height=1.3cm,
        align=center, 
        inner sep=0.2cm,
        outer sep=0,
        rectangle split, 
        rectangle split parts=2,
        rectangle split draw splits=false},
    reverseclip/.style={
        insert path={(current page.north east) --
            (current page.south east) --
            (current page.south west) --
            (current page.north west) --
            (current page.north east)}
    }
}

\newcommand{\MaxNumberX}{3}
\newcommand{\MaxNumberY}{5}

\begin{document}
\begin{tikzpicture}[x=2cm,y=1.39cm,node distance=0]

    % external nodes ...
        % ...below
    \node[anchor=north west] (1st) at (0,0) {1st quarter};
    \node[anchor=north west] (2nd) at (1,0) {2nd quarter};
    \node[anchor=north west] (3rd) at (2,0) {3rd quarter};
    \node[anchor=north west] (4th) at (3,0) {4th quarter};
    \node (1h) at ($(1st)!.5!(2nd)+(0,-1)$) {1st half};
    \node (2h) at ($(3rd)!.5!(4th)+(0,-1)$) {2nd half};
    \node (time) at ($(1h)!.5!(2h)+(0,-1)$) {\textbf{Time}};

        % ...side
    \node[anchor=east, align=right] (af) at (0,5.5) {Africa};
    \node[anchor=east, align=right] (as) at (0,4.5) {Asia};
    \node[anchor=east, align=right] (au) at (0,3.5) {Australia};
    \node[anchor=east, align=right] (eu) at (0,2.5) {Europe};
    \node[text width=1.5cm,anchor=east, align=right] (na) at (0,1.5) {North America};
    \node[text width=1.5cm,anchor=east, align=right] (sa) at (0,0.5) {South America};
    \node[text width=2cm, align=center] (eh) at ($(as)!.5!(au)+(-2,0)$) {Eastern Hemisphere};
    \node[text width=2cm, align=center] (wh) at ($(na)!.5!(sa)+(-2,0)$) {Western Hemisphere};
    \node (source) at ($(eh)!.5!(wh)+(-1.5,0)$) {\textbf{Source}};

        % ...above
    \node[anchor=east] (air) at (0.5,6.3) {air};
    \node[anchor=east] (sea) at (1.3,6.6) {sea};
    \node[anchor=east] (road) at (2,6.9) {road};
    \node[anchor=east] (rail) at (2.6,7.15) {rail};
    \node[anchor=east] (ng) at ($(air)!.5!(sea)+(-1,0)$) {non-ground};
    \node[anchor=east] (g) at ($(road)!.5!(rail)+(-1,0)$) {ground};
    \node[anchor=east] (route) at ($(ng)!.5!(g)+(-1.5,0)$) {\textbf{Route}};

% edges... 
    % ...below
\draw (time.north) |- ($(time)+(0,.5)$) -| (1h.south);
\draw ($(time)+(0,.5)$) -| (2h.south);
\draw (1h.north) |- ($(1h)+(0,.5)$) -| (1st.south);
\draw ($(1h)+(0,.5)$) -| (2nd.south);
\draw (2h.north) |- ($(2h)+(0,.5)$) -| (3rd.south);
\draw ($(2h)+(0,.5)$) -| (4th.south);

    % ...side
\draw (eh.east) -| ($(eh)+(1,0)$) |- (af.west);
\draw ($(eh)+(1,.5)$) -- (as.west);
\draw ($(eh)+(1,-.5)$) -- (au.west);
\draw ($(eh)+(1,0)$) |- (eu.west);
\draw (wh.east) -| ($(eh)+(1,-3)$) |- (na.west);
\draw ($(eh)+(1,-3)$) |- (sa.west);
\draw (source.east) -| ($(source)+(.6,0)$) |- (eh.west);
\draw ($(source)+(.6,0)$) |- (wh.west);

    % ...above
\draw (air.west) -- ($(air)+(-.5,0)$) -- ($(sea)+(-.5,0)$) -- (sea.west);
\draw ($(air)!.5!(sea)+(-.5,0)$) -- (ng.east);
\draw (road.west) -- ($(road)+(-.5,0)$) -- ($(rail)+(-.5,0)$) -- (rail.west);
\draw ($(road)!.5!(rail)+(-.5,0)$) -- (g.east);
\draw (ng.west) -- ($(ng)+(-1,0)$) -- ($(g)+(-1,0)$) -- (g.west);
\draw ($(ng)!.5!(g)+(-1,0)$) -- (route.east);


% LEGEND
\node[mynode, anchor=center, label={90:Measure}] (mn) at (source|-time) {Packages \nodepart{two} Last};
\draw[thin, dashed] ($(mn.north west)!0.5!(mn.south west)$) -- ($(mn.north east)!0.5!(mn.south east)$);

\draw[thick] (0,0) -- (4,0);    

% DRAW FACE CUBE
\foreach \x/\y/\Number/\Text  in {%
        0/0/600/\nodepart{two} Feb-27-99,
        1/0/490/\nodepart{two} Jun-03-99,
        2/0/315/\nodepart{two} Aug-21-99,
        3/0/580/\nodepart{two} Nov-30-99,
        0/1/3056/\nodepart{two} Mar-30-99,
        1/1/4050/\nodepart{two} Jun-28-99,
        2/1/4360/\nodepart{two} Sep-30-99,
        3/1/5112/\nodepart{two} Dec-29-99,
        0/2/500/\nodepart{two} Mar-07-99,
        1/2/470/\nodepart{two} Jun-20-99,
        2/2/464/\nodepart{two} Sep-11-99,
        3/2/696/\nodepart{two} Dec-15-99,
        0/3/212/\nodepart{two} Mar-05-99,
        1/3/240/\nodepart{two} May-19-99,
        2/3/300/\nodepart{two} Aug-09-99,
        3/3/410/\nodepart{two} Nov-27-99,
        0/4/550/\nodepart{two} Mar-19-99,
        1/4/600/\nodepart{two} May-31-99,
        2/4/520/\nodepart{two} Sep-18-99,
        3/4/780/\nodepart{two} Dec-22-99,
        0/5/190/\nodepart{two} Feb-17-99,
        1/5/215/\nodepart{two} Apr-22-99,
        2/5/160/\nodepart{two} Sep-07-99,
        3/5/260/\nodepart{two} Dec-01-99}{
\node[mynode] (mynode) at (\x,\y) {\Number\nodepart{two}\Text\vphantom{pg}};
\draw[thin, dashed] ($(mynode.north west)!0.5!(mynode.south west)$) -- ($(mynode.north east)!0.5!(mynode.south east)$);

% diagonal lines
\begin{pgfinterruptboundingbox}
\path [clip] (0,6.01) -- (3.05,7.237) -- (4.85,7.237) -- (4.85,4.45) -- (4.01,0) -- (5,3) -- (5.6,8.3) -- (1,7) -- cycle [reverseclip];
%\path[draw=red] (0,6.01) -- (3.05,7.237) -- (4.85,7.237) -- (4.85,4.45) -- (4.01,0) -- (5,3) -- (5.6,8.3) -- (1,7) -- cycle;
\end{pgfinterruptboundingbox}
\draw[thick] (4,\y,0) -- (4,6,-8);
\draw[thick] (\x,6,0) -- (4,6,-8);
\draw[thick] (4,6,0) -- (4,6,-8);
% midsection
\draw[very thin, loosely dashed] (4,\y+.5,0) -- (4,6,-8);
}

% Sections
\draw[thick] (1.49,5.01,-8) -- (3.3,5.01,-8) -- (3.3,2.26,-8);
\draw[thick] (0,4.5,-8) -- (2.95,4.5,-8) -- (2.95,0,-8);
\draw[thick] (-1,4.18,-8) -- (2.75,4.18,-8) -- (2.75,-1,-8);
\draw[thick] (0,4.75,-8) -- (3.13,4.75,-8) -- (3.13,0,-8);

%midsections
\draw[very thin, loosely dashed] (-1,4,-8) -- (2.6,4,-8) -- (2.6,-1.5,-8);
\draw[very thin, loosely dashed] (0,4.63,-8) -- (3.05,4.63,-8) -- (3.05,0,-8);
\draw[very thin, loosely dashed] (-.7,4.35,-8) -- (2.85,4.35,-8) -- (2.85,-1,-8);
\draw[very thin, loosely dashed] (0,4.87,-8) -- (3.2,4.87,-8) -- (3.2,0,-8);

\end{tikzpicture}
\end{document}
  • 4
    With something like \tikzset{partway to vanishing point/.style={to path={-- ($(\tikztostart)!#1!(\tikztotarget)$)\tikztonodes}}, partway from vanishing point/.style={to path={($(\tikztostart)!1-#1!(\tikztotarget)$) -- (\tikztotarget)\tikztonodes}}} you can do \path[thick, partway from vanishing point=.6] (4,6,-8) edge (4,\y,0) edge (\x,6,0) edge (4,6,0); and you wouldn't need to use clip. If you do not want to guess the parameter .6 you can also use any horizontal/vertical line in the xy plane and find where it intersects with lines to the vanishing point. – Qrrbrbirlbel May 12 '15 at 21:19
  • 1
    @Qrrbrbirlbel That's brilliant, where do you learn these things?! :D – Alenanno May 12 '15 at 21:25

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.