7

How can I simplify some positioning with some foreach loops or some own styles? Here is the original picture:

Screenshot

And here is my MWE:

\documentclass[border=5pt,tikz]{standalone}
\usetikzlibrary{calc}
\begin{document}
    \begin{tikzpicture}[every node/.style={font=\tiny},scale=1.5,xscale=1.2]
        \coordinate (a) at (0:1.5);
        \coordinate (b) at (60:1);
        \coordinate (c) at (120:1);
        \coordinate (d) at (180:1.5);
        \coordinate (e) at (240:1);
        \coordinate (f) at (300:1);
            \draw[dashed] (a) -- (b) -- (c) -- (d);
            \draw (d) -- (e) -- (f) -- (a);
                \draw (a) -- (0,3);
                \draw (0,3) -- ($(b)!.5!(0,3)$);
                \draw[dashed] (c) -- ($(c)!.5!(0,3)$);
                    \draw ($(c)!.5!(0,3)$) -- (0,3);
                \draw[dashed] (b) -- ($(b)!.5!(0,3)$);
                \draw (0,3) -- (d);
                \draw (0,3) -- (e);
                \draw (0,3) -- (f);
            \draw ($(a)!.5!(0,3)$) -- ($(b)!.5!(0,3)$) -- ($(c)!.5!(0,3)$) -- ($(d)!.5!(0,3)$) -- ($(e)!.5!(0,3)$) -- ($(f)!.5!(0,3)$) -- cycle;
                    \fill[black,opacity=.3] ($(a)!.5!(0,3)$) -- ($(b)!.5!(0,3)$) -- ($(c)!.5!(0,3)$) -- ($(d)!.5!(0,3)$) -- ($(e)!.5!(0,3)$) -- ($(f)!.5!(0,3)$) -- cycle;
                \fill[gray,opacity=.3] ($(a)!.5!(0,3)$) -- ($(b)!.5!(0,3)$) -- (b) -- (a) -- cycle;
                \fill[gray,opacity=.3] ($(b)!.5!(0,3)$) -- ($(c)!.5!(0,3)$) -- (c) -- (b) -- cycle;
                \fill[gray,opacity=.3] ($(d)!.5!(0,3)$) -- ($(e)!.5!(0,3)$) -- (e) -- (d) -- cycle;
                \fill[gray,opacity=.3] ($(c)!.5!(0,3)$) -- ($(d)!.5!(0,3)$) -- (d) -- (c) -- cycle;
                \fill[gray,opacity=.3] ($(e)!.5!(0,3)$) -- ($(f)!.5!(0,3)$) -- (f) -- (e) -- cycle;
                \fill[black,opacity=.3] ($(f)!.5!(0,3)$) -- ($(a)!.5!(0,3)$) -- (a) -- (f) -- cycle;
            \draw[dashed] ([xshift=-.75cm]$(a)!.5!(0,3)$) -- (0,0);
                \draw[fill=white,radius=.1em] (0,0) circle node[right] {$O_1$};
                    \draw ([xshift=-.75cm]$(a)!.5!(0,3)$) -- (0,3);
            \draw[fill=white,radius=.1em] ([xshift=-.75cm]$(a)!.5!(0,3)$) circle node[above right=-3.5] {$O_1'$};
                \node[above right=-2] at (b) {$A_i$};
                    \node[above left=-2] at (b) {$A_2'$};
                    \node[above right=-2] at (c) {$A_1'$};
                        \draw[fill=white,radius=.1em] (b) circle;
                        \draw[fill=white,radius=.1em] (c) circle;
                \node[left] at (d) {$A_n$};
                \node[below] at (e) {$A_1$};
                \node[below] at (f) {$A_2$};
            \draw[fill=white,radius=.1em] (0,3) circle node[above] {$O$};
                \node[above left=-2] at ($(d)!.5!(0,3)$) {$A_n'$};
                \node[above right=-4] at ($(b)!.5!(0,3)$) {$A_i'$};
            \draw[fill=white,radius=.1em] ($(a)!.5!(0,3)$) circle;
            \draw[fill=white,radius=.1em] ($(b)!.5!(0,3)$) circle;
            \draw[fill=white,radius=.1em] ($(c)!.5!(0,3)$) circle;
            \draw[fill=white,radius=.1em] ($(d)!.5!(0,3)$) circle;
            \draw[fill=white,radius=.1em] ($(e)!.5!(0,3)$) circle;
            \draw[fill=white,radius=.1em] ($(f)!.5!(0,3)$) circle;
    \end{tikzpicture}
\end{document}

Here is the ouput, too:

Screenshot

BONUS: How can in generally be the code cleaned up?

3
  • 2
    +1. TikZ is very literate, even understands the alphabet. So you could simplify the last lines of your code to \foreach \X in {a,...,f} {\draw[fill=white,radius=.1em] ($(\X)!.5!(0,3)$) circle;} and so on...
    – user121799
    Aug 4, 2018 at 22:38
  • @marmot: Oh, I didn't know about that, thank you! Are there any more possibilities to clean the “mess“ a little bit up? Aug 4, 2018 at 22:39
  • 1
    +1: Great example of a professional looking LaTeX drawing! – Aug 6, 2018 at 20:19

2 Answers 2

9

Since this is IMHO a very nice question, I'd like to collect some tricks I learned from others or the pgfmanual.

  1. TikZ is very literate, it understands the alphabet, so you can do loops over {a,...,f}, say.

  2. And you can also do double loops.

  3. One can convert number to letters. I don't know if my way is the most elegant one, but one can define a counter, set it, and use \alph to convert it to a letter. (I do not claim any priority on this trick and would not be surprised if that has been done more elegantly somewhere else. I just did not search long enough.)

  4. A trick that I learned (if I remember correctly) from @percusse is to do something like

    \draw plot[samples at={a,...,f},variable=\x] (\x);

    to connect a to b ... to f.

  5. An arguably even more powerful trick, which I learned when writing this answer, is the foreach style. An example is in the code below. There is also Loop Space's answer, which I however did not dare to try out. Note that this does not at all imply that there is something wrong, I just hesitate to use it. EDIT: As pointed out by @Max Snippe, an even more elegant way is to simply say

    \fill[black,opacity=0.3] (a') foreach \p in {b,...,f}{ -- (\p')} -- cycle;

I also added three front faces with low opacity to make the 3D feel even more pronounced.

\documentclass[border=5pt,tikz]{standalone}
\usetikzlibrary{calc}
\newcounter{cheat}
\begin{document}
    \begin{tikzpicture}[every node/.style={font=\tiny},scale=1.5,xscale=1.2]
        \foreach \X [count=\Y,evaluate=\Y as \Z using {int(60*(\Y-1))}] 
        in {a,...,f}
        {\pgfmathtruncatemacro{\Cheat}{int(mod(\Y-1,3))}
        \ifnum\Cheat=0
            \coordinate (\X) at (\Z:1.5);
        \else
            \coordinate (\X) at (\Z:1);
        \fi
        }
        \draw[dashed] (a) -- (b) -- (c) -- (d);
        \draw (d) -- (e) -- (f) -- (a);
        \draw (a) -- (0,3);
        % define primed coordinates for later use
        \foreach \X in {a,...,f}
        {\coordinate (\X') at ($(\X)!.5!(0,3)$);}
        \draw (0,3) -- (b');
        \draw[dashed] (c) -- (c');
        \draw (c') -- (0,3);
        \draw[dashed] (b) -- (b');
        \foreach \X in {d,e,f}
            {\draw (0,3) -- (\X);}
        % foreach style from https://tex.stackexchange.com/a/124105/121799  
        \fill[black,opacity=.3,foreach/.style={insert path=--(#1')}] (a') [foreach/.list={b,...,f}]
        -- cycle;       
        \foreach \X [count=\Y,evaluate=\Y as \Z using {ifthenelse(\Y==5,6,int(mod(\Y+1,6)))}] 
            in {a,...,f} {\setcounter{cheat}{\Z}
            \def\NextX{\alph{cheat}}
            \draw (\X') -- (\NextX');
        \fill[gray,opacity=.3] (\NextX') -- (\X') --
            (\X) -- (\NextX) -- cycle;
        }
        \draw[dashed] ([xshift=-.75cm]$(a)!.5!(0,3)$) -- (0,0);
        \draw[fill=white,radius=.1em] (0,0) circle node[right] {$O_1$};
        \draw ([xshift=-.75cm]$(a)!.5!(0,3)$) -- (0,3);
        \draw[fill=white,radius=.1em] ([xshift=-.75cm]$(a)!.5!(0,3)$) circle node[above right=-3.5] {$O_1'$};
        \node[above right=-2] at (b) {$A_i$};
        \node[above left=-2] at (b) {$A_2'$};
        \node[above right=-2] at (c) {$A_1'$};
        \draw[fill=white,radius=.1em] (b) circle;
        \draw[fill=white,radius=.1em] (c) circle;
        \node[left] at (d) {$A_n$};
        \node[below] at (e) {$A_1$};
        \node[below] at (f) {$A_2$};
        \draw[fill=white,radius=.1em] (0,3) circle node[above] {$O$};
        \node[above left=-2] at ($(d)!.5!(0,3)$) {$A_n'$};
        \node[above right=-4] at ($(b)!.5!(0,3)$) {$A_i'$};
        \foreach \X in {a,...,f}
            {\draw[fill=white,radius=.1em] ($(\X)!.5!(0,3)$) circle;}
        \foreach \X/\Y in {d/e,e/f,f/a} 
        {\fill[opacity=0.15,gray] (\X) -- (\X') -- (\Y') -- (\Y) -- cycle;}
    \end{tikzpicture}
\end{document}

enter image description here

12
  • 1
    @current_user I think this is the first time we disagree: you are not a fool! ;-)
    – user121799
    Aug 4, 2018 at 23:04
  • 1
    @current_user You made me discover discover a very useful trick, thanks! (Marmots are beings, please see the tikzmarmots package documentation for a proof ;-)
    – user121799
    Aug 4, 2018 at 23:25
  • 1
    Just trowing this out there: it's not so much the foreach style, it's more calling it with the /.list handler. You can define a key with arbitrary code (it should do something with a single argument, otherwise it wouldn't make sense) and when calling it with the /.list handler it will execute that code sequentially for all values in the list argument.
    – Max
    Aug 5, 2018 at 7:58
  • 1
    Also I just noticed, in this case you don't even need the list handler. You can replace the corresponding line with \fill[black,opacity=0.3] (a') foreach \p in {b,...,f}{ -- (\p')} -- cycle;.
    – Max
    Aug 5, 2018 at 10:30
  • 1
    @Max_Snippe: Could you post an answer with all the corrections/simplifications that you've found? Aug 5, 2018 at 10:53
3

Improving on marmot's nice answer adding a few more loops to append the nodes and simplifying a bit more (added Max Snippe's hint, for example):

\documentclass[border=5pt,tikz]{standalone}
\usetikzlibrary{calc}
\newcounter{cheat}
\begin{document}
    \begin{tikzpicture}[every node/.style={font=\tiny},scale=1.5,xscale=1.2]
        \foreach \X [count=\Y,evaluate=\Y as \Z using {int(60*(\Y-1))}] 
          in {a,...,f}
          {\pgfmathtruncatemacro{\Cheat}{int(mod(\Y-1,3))}
          \ifnum\Cheat=0
            \coordinate (\X) at (\Z:1.5);
          \else
            \coordinate (\X) at (\Z:1);
          \fi}
        \coordinate (h) at (0,3);
        \coordinate (g) at (0,0);
        \draw[dashed] (a) -- (b) -- (c) -- (d);
        \draw (d) -- (e) -- (f) -- (a);
        \draw (a) -- (h);
        \foreach \X in {a,...,g}
          {\coordinate (\X') at ($(\X)!.5!(0,3)$);}
        \foreach \X in {b,c,g}
          {\draw[dashed] (\X) -- (\X');
          \draw (h) -- (\X');}
        \foreach \X in {d,e,f}
          {\draw (h) -- (\X);}
        \fill[black,opacity=.3] (a') foreach \X in {b,...,f} { -- (\X') } -- cycle;
        \fill[gray,opacity=.3] (a) foreach \X in {f,f',a'} { -- (\X) } -- cycle;
        \foreach \X [count=\Y,evaluate=\Y as \Z using {ifthenelse(\Y==5,6,int(mod(\Y+1,6)))}] 
          in {a,...,f} 
          {\setcounter{cheat}{\Z}
            \def\NextX{\alph{cheat}}
            \draw (\X') -- (\NextX');
          \fill[gray,opacity=.3] (\NextX') -- (\X') -- (\X) -- (\NextX) -- cycle;}
        \foreach \X in {b,c,g,h}
          {\draw[fill=white,radius=.1em] (\X) circle;}
        \foreach \X in {a,...,g}
          {\draw[fill=white,radius=.1em] (\X') circle;}
        \foreach \X/\Y/\Z 
          in {e/below/A_1,c/below/A_1',
              f/below/A_2,b/below/A_2',
              d/left/A_n, d'/left/A_n',
              b/right/A_i,b'/right/A_i',
              g/right/O_1,h/right/O}
          {\node[\Y] at (\X) {$\Z$};}
        \node[below right=-2.5] at (g') {$O_1'$};
    \end{tikzpicture}
\end{document}
1
  • 2
    One could easily simplify further. There is no reason why in your last \foreach below and so on are doubled for the first 5 entries, i.e. you could treat primed and unprimed quantities the same way.
    – user121799
    Aug 5, 2018 at 23:54

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.