TransWikia.com

Seeking help in simplifying a TikZ Tonnetz

TeX - LaTeX Asked on November 1, 2021

I’ve created a Tonnetz, a type of musical network in TikZ:

%LuaLaTeX
documentclass{article}
usepackage{tikz}

begin{document}

begin{tikzpicture}[xscale=1.5,yscale=2.5]
footnotesize

begin{scope}
newcommand*columns{7}
newcommand*rows{2}
clip(0,0.5) rectangle (columns,rows);
foreach x in {0,0.5,1,...,columns}
foreach y in {0,0.5,1,...,rows}
foreach z in {-1.5,-0.5,...,columns} 
{
    draw (0,y) -- (columns,y);
    draw (z,rows) -- (z+2,0);
    draw (z,0) -- (z+2,rows);
}
end{scope}

draw[fill=gray!50] (3.5,1) -- (4.5,1) -- (4,1.5) -- cycle;
foreach toprow/topx in {E/0.5,B/1.5,F$sharp$/2.5,C$sharp$/3.5,G$sharp$/4.5,D$sharp$/5.5,A$sharp$/6.5}
foreach secondrow/secondx in {C/0,G/1,D/2,A/3,E/4,B/5,F$sharp$/6,C$sharp$/7}
foreach thirdrow/thirdx in {E$flat$/0.5,B$flat$/1.5,F/2.5,C/3.5,G/4.5,D/5.5,A/6.5}
foreach fourthrow/fourthx in {C$flat$/0,G$flat$/1,D$flat$/2,A$flat$/3,E$flat$/4,B$flat$/5,F/6,C/7}
%foreach bottomrow/bottomx in {E$flatflat$/0.5,B$flatflat$/1.5,F$flat$/2.5,C$flat$/3.5,G$flat$/4.5,D$flat$/5.5,A$flat$/6.5}
{
node[draw,circle,minimum size=0.75cm,fill=white] at (topx,2) {toprow};
node[draw,circle,minimum size=0.75cm,fill=white] at (secondx,1.5) {secondrow};
node[draw,circle,minimum size=0.75cm,fill=white] at (thirdx,1) {strut thirdrow};
node[draw,circle,minimum size=0.75cm,fill=white] at (fourthx,0.5) {strut fourthrow};
%node[draw,circle,minimum size=0.75cm,fill=white] at (bottomx,0) {strut bottomrow};
}

end{tikzpicture}

end{document}

enter image description here

As written, with the two lines commented out, the output is correct. However, it is very slow to compile. (Even Inkscape and PDF readers are slow to open the file!) And when I uncomment those two lines to create the bottom row, nothing ever results; sometimes I get the "TeX capacity exceeded" error, but other times it goes on compiling so long that I just give up.

Ideally I will have four or five diagrams like this in this chapter (itself part of a much larger book), so these compile times aren’t realistic. Is there any way to simplify this diagram to help it compile faster?

2 Answers

As mentioned previously, your problem is that you're doing the same thing many times, because you're nesting the loops. Take the loops drawing the grid. The drawing instructions in the innermost loop (over z) is repeated for each iteration of that loop. But the entire loop is again repeated for each iteration of the second loop (over y). And that loop is repeated for each iteration of the outermost loop (over x). As a result, because there are 10 steps in the x-loop and 14 steps in the z-loop, you're drawing every horizontal line 140 times.

The same thing happens for the nodes. Each consecutive loop is repeated for every iteration of the loop it's placed within, so when you get five levels deep, with 7 or 8 iterations in each level, you end up drawing each node over 3000 times.

For the grid, you could do this:

begin{scope}
newcommand*columns{7}
newcommand*rows{2}
clip(0,-pgflinewidth) rectangle (columns,rows);
foreach y in {0,0.5,1,...,rows} 
  draw (0,y) -- (columns,y);

%the previous loop has ended, start a new:
foreach z in {-1.5,-0.5,...,columns} 
{
    draw (z,rows) -- (z+2,0);
    draw (z,0) -- (z+2,rows);
}
end{scope}

The horizontal lines are drawn in one loop, the diagonal lines in a separate loop.

For the nodes you can do the same thing, have one loop for each line:

foreach toprow [count=topx] in {E,B,F$sharp$,C$sharp$,G$sharp$,D$sharp$,A$sharp$}
   node[note] at (topx-0.5,2) {toprow};

foreach secondrow [count=secondx from 0] in {C,G,D,A,E,B,F$sharp$,C$sharp$}
   node[note] at (secondx,1.5) {secondrow};

foreach thirdrow[count=thirdx] in {E$flat$,B$flat$,F,C,G,D,A}
   node[note] at (thirdx-0.5,1) {strut thirdrow};
   
foreach fourthrow[count=fourthx from 0] in {C$flat$,G$flat$,D$flat$,A$flat$,E$flat$,B$flat$,F,C}
  node[note] at (fourthx,0.5) {strut fourthrow};

foreach bottomrow[count=bottomx] in {E$flatflat$,B$flatflat$,F$flat$,C$flat$,G$flat$,D$flat$,A$flat$}
  node[note] at (bottomx-0.5,0) {strut bottomrow};

I.e. instead of having foreach x (foreach y (foreach z <do stuff x y and z> ) ), you have foreach x <do stuff x>, then foreach y <do stuff y> etc.

Above I made use of the count feature of foreach, that way you don't have to fiddle around with the a/b syntax. You could also iterate over a list of lists:

foreach  notelist [count=row from 0] in {
  {E$flatflat$,B$flatflat$,F$flat$,C$flat$,G$flat$,D$flat$,A$flat$},
  {C$flat$,G$flat$,D$flat$,A$flat$,E$flat$,B$flat$,F,C},
  {E$flat$,B$flat$,F,C,G,D,A},
  {C,G,D,A,E,B,F$sharp$,C$sharp$},
  {E,B,F$sharp$,C$sharp$,G$sharp$,D$sharp$,A$sharp$}}
  foreach note [count=column from 0,evaluate={colX=column+0.5-mod(row,2)/2;}] in notelist
     node [note] at (colX,row*0.5) {strut note};

There are some simple calculations to get the coordinates correct.

Complete code, containing both variants for the nodes:

enter image description here

documentclass{article}
usepackage{tikz}
begin{document}

begin{tikzpicture}[
  xscale=1.5,yscale=2.5981,
  note/.style={draw,circle,minimum size=0.75cm,fill=white},
  every node/.append style={font=footnotesize}
  ]
%footnotesize

begin{scope}
newcommand*columns{7}
newcommand*rows{2}
clip(0,-pgflinewidth) rectangle (columns,rows);
foreach y in {0,0.5,1,...,rows} 
  draw (0,y) -- (columns,y);
foreach z in {-1.5,-0.5,...,columns} 
{
    draw (z,rows) -- (z+2,0);
    draw (z,0) -- (z+2,rows);
}
end{scope}

draw[fill=gray!50] (3.5,1) -- (4.5,1) -- (4,1.5) -- cycle;
%foreach toprow [count=topx] in {E,B,F$sharp$,C$sharp$,G$sharp$,D$sharp$,A$sharp$}
%   node[note] at (topx-0.5,2) {toprow};
%
%foreach secondrow [count=secondx from 0] in {C,G,D,A,E,B,F$sharp$,C$sharp$}
%   node[note] at (secondx,1.5) {secondrow};
%
%foreach thirdrow[count=thirdx] in {E$flat$,B$flat$,F,C,G,D,A}
%   node[note] at (thirdx-0.5,1) {strut thirdrow};
%   
%foreach fourthrow[count=fourthx from 0] in {C$flat$,G$flat$,D$flat$,A$flat$,E$flat$,B$flat$,F,C}
%  node[note] at (fourthx,0.5) {strut fourthrow};
%
%foreach bottomrow[count=bottomx] in {E$flatflat$,B$flatflat$,F$flat$,C$flat$,G$flat$,D$flat$,A$flat$}
%  node[note] at (bottomx-0.5,0) {strut bottomrow};


foreach [count=row from 0] notelist in {
  {E$flatflat$,B$flatflat$,F$flat$,C$flat$,G$flat$,D$flat$,A$flat$},
  {C$flat$,G$flat$,D$flat$,A$flat$,E$flat$,B$flat$,F,C},
  {E$flat$,B$flat$,F,C,G,D,A},
  {C,G,D,A,E,B,F$sharp$,C$sharp$},
  {E,B,F$sharp$,C$sharp$,G$sharp$,D$sharp$,A$sharp$}}
  foreach note [count=column from 0,evaluate={colX=column+0.5-mod(row,2)/2;}] in notelist
     node [note] at (colX,row*0.5) {strut note};

end{tikzpicture}

end{document}

Answered by Torbjørn T. on November 1, 2021

Instead of nesting loops unnecessarily, I make a test in a single loop on the number of nodes to display (7; 8; 7 and 8 per line).

screenshot

%LuaLaTeX
documentclass[tikz,border=5mm]{standalone}
%usepackage{tikz}

begin{document}

begin{tikzpicture}[xscale=1.5,yscale=2.5981]
footnotesize

begin{scope}
newcommand*columns{7}
newcommand*rows{2}
clip(0,0.5) rectangle (columns,rows);
 %foreach x in {0,0.5,1,...,columns}
foreach y in {0,0.5,1,...,rows}
foreach z in {-1.5,-0.5,...,columns} 
{
    draw (0,y) -- (columns,y);
    draw (z,rows) -- (z+2,0);
    draw (z,0) -- (z+2,rows);
}
end{scope}

draw[fill=gray!50] (3.5,1) -- (4.5,1) -- (4,1.5) -- cycle;
foreach row/x [count=n]in {E/0.5,B/1.5,F$sharp$/2.5,C$sharp$/3.5,G$sharp$/4.5,D$sharp$/5.5,A$sharp$/6.5,C/0,G/1,D/2,A/3,E/4,B/5,F$sharp$/6,C$sharp$/7,E$flat$/0.5,B$flat$/1.5,F/2.5,C/3.5,G/4.5,D/5.5,A/6.5,C$flat$/0,G$flat$/1,D$flat$/2,A$flat$/3,E$flat$/4,B$flat$/5,F/6,C/7}
%foreach bottomrow/bottomx in {E$flatflat$/0.5,B$flatflat$/1.5,F$flat$/2.5,C$flat$/3.5,G$flat$/4.5,D$flat$/5.5,A$flat$/6.5}
{
ifnum n < 8
node[draw,circle,minimum size=0.75cm,fill=white] at (x,2) {row};
else 
    ifnum n <16
    node[draw,circle,minimum size=0.75cm,fill=white] at (x,1.5) {row};
    else   
        ifnum n <23    
        node[draw,circle,minimum size=0.75cm,fill=white] at (x,1) {strut row};
        else 
        node[draw,circle,minimum size=0.75cm,fill=white] at (x,0.5) {strut row};
        fi
    fi
fi
%node[draw,circle,minimum size=0.75cm,fill=white] at (bottomx,0) {strut bottomrow};
}

end{tikzpicture}

Answered by AndréC on November 1, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP