10

I've just seen this representation of pi and wanted to create something similar.

Minimal example

Create data.csv:

#!/usr/bin/env python

"""Create a data.csv file."""

import csv

try:
    # import version included with old SymPy
    from sympy.mpmath import mp
except ImportError:
    # import newer version
    from mpmath import mp

mp.dps = 1000  # set number of digits
pi = mp.pi
print(pi)

# Split pi in groups of two digits
pi = str(pi)[2:]
split_pi = []
for i in range(0, len(pi), 2):
    part = pi[i:i + 2]
    if len(part) != 2:
        continue
    split_pi.append(part)

# Representation of pi
data = [("x", "y", "color")]  # header
for e1, e2 in zip(split_pi, split_pi[1:]):
    tuple_date = (int(e1), int(e2), "c{}".format(int(int(e1) / 10)))
    data.append(tuple_date)

# Write data to csv
with open('data.csv', 'w') as fp:
    writer = csv.writer(fp, delimiter=',')
    writer.writerows(data)

Create graph:

\documentclass{standalone}
\usepackage{amssymb}
\usepackage{tikz}
\usepackage[utf8]{inputenc}
\usepackage{csvsimple}
\usepackage{xcolor}

\definecolor{c0}{HTML}{5A311D}
\definecolor{c1}{HTML}{E18B4E}
\definecolor{c2}{HTML}{4A1776}
\definecolor{c3}{HTML}{C966DA}
\definecolor{c4}{HTML}{04676C}
\definecolor{c5}{HTML}{0CE7E1}
\definecolor{c6}{HTML}{004692}
\definecolor{c7}{HTML}{0082FF}
\definecolor{c8}{HTML}{355128}
\definecolor{c9}{HTML}{DF1C24}

\begin{document}
\newcommand{\distance}{6}

\begin{tikzpicture}
    \foreach \a in {0,1,...,100}{
        \node[draw=none](\a) at (\a/100*360: \distance) {} ;
    }
    \csvreader[ head to column names]%
                {data.csv}{}{%
    \path (\x) edge [bend right, \color] (\y);
    }
\end{tikzpicture}
\end{document}

Image:

enter image description here

Question

Why are all edges black and how can I fix it? (It's ok for me to adjust the Python code)

Edit: The CSV file starts like

x,y,color
14,15,c1
15,92,c1
92,65,c9
65,35,c6
35,89,c3
89,79,c8
79,32,c7
32,38,c3
38,46,c3
46,26,c4
  • BTW I don't know about the original image, but looking at your Python code, I wonder about the choice of for i in range(0, len(pi), 2): part = pi[i:i + 2] rather than for i in range(0, len(pi)): part = pi[i:i + 2] — if you're considering transitions between digits (as I guess you are), there's no good reason to consider only transitions from digits at odd positions to digits at even positions. (Of course it won't substantially change your image except to double the number of transitions.) – ShreevatsaR May 3 '17 at 7:33
  • The digits of the original image do not come from a single point, but from a block. I thought this might be how it is done. But probably they look at single digits and order them (that might explain some of the patterns) – Martin Thoma May 3 '17 at 7:40
  • 1
    Also, I suspect you might want \coordinate (\a) at (\a/100*360: \distance); instead of a \node (which has a certain size, so lines won't meet at the same point). – Torbjørn T. May 3 '17 at 7:54
  • 1
    @Dr.ManuelKuehner I've added the first few lines of the csv file to the question (to make this answerable by those who know TeX but don't have immediate access to Python); hope the OP (Martin) doesn't mind. – ShreevatsaR May 3 '17 at 8:04
  • 1
    The method for the original image is given here, here and especially here (also here but it's hard to find on the page). To quote: Cristian Ilies Vasile had the idea of representing the digits of π as a path traced by links between successive digits. Each digit is assigned a segment around the circle…. – ShreevatsaR May 3 '17 at 11:17
11

I know basically nothing about TikZ, but your main stuff happens in this line:

\path (\x) edge [bend right, \color] (\y)

where \x, \y and \color are defined by the csvsimple package, based on the headers in the CSV file. Well, think about it: if a particular line has 14,15,c1 (as the first line does), then your above expression expands to

\path (14) edge [bend right, c1] (15)

in which there is nothing letting TikZ know that it should interpret c1 as the colour c1. So simply replacing that line with the following works:

\path (\x) edge [bend right, color=\color] (\y);

and gives this image:

colourful transitions

which solves your colour problem.

An interesting challenge might be to:

  • implement all of this in Lua code, without needing a Python script

  • more closely match the original image, by not having everything go to the same point.


Edit: Pursuing the above: first I found out how the original image mentioned in the question was generated. The method is given here, here and especially here (also here but it's hard to find on the page):

Cristian Ilies Vasile had the idea of representing the digits of π as a path traced by links between successive digits. Each digit is assigned a segment around the circle…

It turns out we can do all this in code that we can compile with just lualatex, without needing an external CSV data file generated with Python. (I incorporated the suggestion by Torbjørn T. of using \coordinate rather than \node, and changed the positions.)

\documentclass{standalone}
\usepackage{tikz}
\usepackage{xcolor}
\usepackage{luacode}

\definecolor{c0}{HTML}{5A311D}
\definecolor{c1}{HTML}{E18B4E}
\definecolor{c2}{HTML}{4A1776}
\definecolor{c3}{HTML}{C966DA}
\definecolor{c4}{HTML}{04676C}
\definecolor{c5}{HTML}{0CE7E1}
\definecolor{c6}{HTML}{004692}
\definecolor{c7}{HTML}{0082FF}
\definecolor{c8}{HTML}{355128}
\definecolor{c9}{HTML}{DF1C24}

\begin{document}
\newcommand{\distance}{6}

\begin{tikzpicture}
  \foreach \digit in {0,1,...,9}{
    \foreach \position in {0,1,...,1000}{
      % Want to allocate range [36d .. 36(d+1)) to digit, but use only say 90% of the range.
      \pgfmathsetlengthmacro{\angle}{\digit/10*360 + 0.9*\position/1000/10*360}
      \coordinate (\digit-\position) at (\angle: \distance);
    }
  }
  \begin{luacode}
    dofile("pidigits.lua")
    function print_edges()
        local position = -1   -- Starting at -1 because pi_digits yields 0314159265...
        local previous = nil
        for digit in coroutine.wrap(pi_digits) do
            if position >= 1 then
                tex.print(string.format("\\path (%d-%d) edge [bend right, color=c%d] (%d-%d);",
                                        previous, position-1, previous, digit, position))
            end
            previous = digit
            position = position + 1
        end
    end
    print_edges()
  \end{luacode}
\end{tikzpicture}
\end{document}

where the file pidigits.lua (which I could have inlined into the same file, but prefer to keep separate) is the following that simply returns the digits of π:

function pi_digits()
    -- Spigot algorithm by Rabinowitz and Wagon:
    -- http://www.cecm.sfu.ca/~jborwein/Expbook/Manuscript/Related%20files/spigot.pdf
    -- The idea of the algorithm: we can say that
    -- pi = 3 + (1, 4, 1, 5, 9, ...) in base (1/10, 1/10, 1/10, 1/10, 1/10, ...)
    -- Similarly, from a well-known formula,
    -- pi = 2 + (2, 2, 2, 2, 2, 2, ...) in base (1/3, 2/5, 3/7, 4/9, 5/11, 6/13,...)
    -- So to get the digits of pi, we just have to convert to the familiar base.

    local n = 1000  -- The number of digits we want.
    local len = math.floor(10 * n / 3) + 1  -- A value high enough (see Gibbons)
    local a = {}  -- Holds the number pi in base C. (Later: pi * 10^k after k steps.)
    for j = 1, len do
        a[j] = 2
    end
    local nines = 0
    local predigit = 0
    for k = 1, n do
        local carry = 0  -- We're about to multiply by 10
        for i = len, 1, -1 do
            local x = 10 * a[i] + carry * i
            a[i] = math.fmod(x, 2 * i - 1)
            carry = math.modf(x / (2 * i - 1))
        end
        a[1] = math.fmod(carry, 10)
        carry = math.modf(carry / 10)
        if carry < 9 then
            coroutine.yield(predigit)
            for k = 1, nines do
                coroutine.yield(9)
            end
            nines = 0
            predigit = carry
        elseif carry == 9 then
            nines = nines + 1  -- Too early to know what digits to print.
        else  -- If we got here, it means carry = 10
            coroutine.yield(predigit + 1)
            for k = 1, nines do
                coroutine.yield(0)
            end
            nines = 0
            predigit = 0
        end
    end
    coroutine.yield(predigit)  -- The remaining digit, let's not throw it away.
end

With this, we can get something like the following image: flow of 1000 digits of π at widths 0.9

(Or we could use a random stream of digits instead of the digits of π, and get an essentially similar image.)

  • 2
    Very nice picture :). – Dr. Manuel Kuehner May 3 '17 at 8:00
  • 1
    @Dr.ManuelKuehner Credit goes to Cristian Vasile! (And the OP for asking the question here :-) – ShreevatsaR May 3 '17 at 19:53

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.