Using Python to Make a TikZ Timeline

TikZ is a drawing language for LaTeX that can produce all sorts of diagrams, including commutative diagrams with the tikz-cd package, which is possibly the best package for commutative diagrams, at least with regards to typesetting quality and usability. Sometimes when you draw a diagram, though, you might want a little more programming language thrown in to make drawing easier. A classic example is a timeline. Though probably not useful for research math papers, a timeline could be helpful for a survey paper or history text.

Suppose you have a tab-delimited text file of dates of birthdays:

1809-02-12	Charles Darwin
1822-12-27	Louis Pasteur
1826-09-17	Bernhard Riemann
1877-02-07	Godfrey Harold Hardy
1845-03-03	Georg Cantor

You could draw them on a timeline in TikZ directly, but the problem is you’d have to worry about manually calculating how far down the line these dates are. This is a good case where writing a program in Python 3 that outputs the TikZ picture drawing commands is much easier and painless, especially if you need several timelines:

#!/usr/bin/python3
import datetime
import re
from sys import argv

#Constants
monthNames = ["January", "Feburary", "March", "April", "May", "June",
        "July", "August", "September", "October", "November", "December"]

#Data
dates = []
description = []
lengthOfLine = 10

#Input of the form string "yyyy-mm-dd"
def dashedToDate(dashed):
    dateArray = dashed.split('-')
    year = int(dateArray[0])
    month = int(dateArray[1])
    day = int(dateArray[2])
    return datetime.date(year,month,day)

if len(argv) > 1:
    data = open(argv[1],'r')
else:
    data = open('dates.txt', 'r')

while True:
    line = data.readline()
    if not line: break
    entry = line.split('\t')
    dates.append(dashedToDate(entry[0]))
    description.append(re.sub("\n","",entry[1]))
data.close()

# create file to put the TeX/Tikz code

texfile = open('picture.tex','w')
texfile.writelines("\\begin{tikzpicture}")

# Styles used
texfile.writelines("[datemarker/.style={circle,draw=black,fill=white,radius=4pt},\n")
texfile.writelines("textlabel/.style={anchor=west,text height=1.7ex,text depth=.25ex}]\n")

# Draw the background
texfile.writelines("\\draw (0,0) -- (0,"+ str(lengthOfLine)+ ");\n")

startDate = dates[0]
endDate = dates[0]
for x in range(len(dates)):
    if dates[x] < startDate:
        startDate = dates[x]
    if dates[x] > endDate:
        endDate = dates[x]
span = (endDate - startDate).days

# Draw the dates
for x in range(len(dates)):
    diff = (dates[x] - startDate).days
    month = monthNames[dates[x].month-1]
    dateText = month+" "+str(dates[x].day)+", "+str(dates[x].year)
    yCoordOnLine = lengthOfLine - (diff/span)*lengthOfLine#!/usr/bin/python3
import datetime
import re
from sys import argv

#Constants
monthNames = ["January", "Feburary", "March", "April", "May", "June",
        "July", "August", "September", "October", "November", "December"]

#Data
dates = []
description = []
lengthOfLine = 10

#Input of the form string "yyyy-mm-dd"
def dashedToDate(dashed):
    dateArray = dashed.split('-')
    year = int(dateArray[0])
    month = int(dateArray[1])
    day = int(dateArray[2])
    return datetime.date(year,month,day)

if len(argv) > 1:
    data = open(argv[1],'r')
else:
    data = open('dates.txt', 'r')

while True:
    line = data.readline()
    if not line: break
    entry = line.split('\t')
    dates.append(dashedToDate(entry[0]))
    description.append(re.sub("\n","",entry[1]))
data.close()

# create file to put the TeX/Tikz code

texfile = open('picture.tex','w')
texfile.writelines("\\begin{tikzpicture}")

# Styles used
texfile.writelines("[datemarker/.style={circle,draw=black,fill=white,radius=4pt},\n")
texfile.writelines("textlabel/.style={anchor=west,text height=1.7ex,text depth=.25ex}]\n")

# Draw the background
texfile.writelines("\\draw (0,0) -- (0,"+ str(lengthOfLine)+ ");\n")

startDate = dates[0]
endDate = dates[0]
for x in range(len(dates)):
    if dates[x] < startDate:
        startDate = dates[x]
    if dates[x] > endDate:
        endDate = dates[x]
span = (endDate - startDate).days

# Draw the dates
for x in range(len(dates)):
    diff = (dates[x] - startDate).days
    month = monthNames[dates[x].month-1]
    dateText = month+" "+str(dates[x].day)+", "+str(dates[x].year)
    yCoordOnLine = lengthOfLine - (diff/span)*lengthOfLine
    textToPrint = dateText +": "+ description[x]
    texfile.writelines("\\node at (0, "+ str(yCoordOnLine)+ ") [datemarker] {};\n")
    texfile.writelines("\\draw (0.1, "+ str(yCoordOnLine)+ ") node [textlabel] {"+textToPrint+"};\n")


texfile.writelines("\\end{tikzpicture}")
texfile.close()
    textToPrint = dateText +": "+ description[x]
    texfile.writelines("\\node at (0, "+ str(yCoordOnLine)+ ") [datemarker] {};\n")
    texfile.writelines("\\draw (0.1, "+ str(yCoordOnLine)+ ") node [textlabel] {"+textToPrint+"};\n")


texfile.writelines("\\end{tikzpicture}")
texfile.close()

What it does is read the file in, do all the calculations, and ouputs the following TikZ code into a file called “picture.tex”:

\begin{tikzpicture}[datemarker/.style={circle,draw=black,fill=white,radius=4pt},
textlabel/.style={anchor=west,text height=1.7ex,text depth=.25ex}]
\draw (0,0) — (0,10);
\node at (0, 10.0) [datemarker] {};
\draw (0.1, 10.0) node [textlabel] {Feburary 12, 1809: Charles Darwin};
\node at (0, 7.959890463917526) [datemarker] {};
\draw (0.1, 7.959890463917526) node [textlabel] {December 27, 1822: Louis Pasteur};
\node at (0, 7.412210051546392) [datemarker] {};
\draw (0.1, 7.412210051546392) node [textlabel] {September 17, 1826: Bernhard Riemann};
\node at (0, 0.0) [datemarker] {};
\draw (0.1, 0.0) node [textlabel] {Feburary 7, 1877: Godfrey Harold Hardy};
\node at (0, 4.697164948453608) [datemarker] {};
\draw (0.1, 4.697164948453608) node [textlabel] {March 3, 1845: Georg Cantor};
\end{tikzpicture}

You can then include it into a LaTeX document using the tikz package by using the comamnd “\input{picture.tex}” to get:

timeline

1 Comment

Leave a comment

Fields marked with * are required. LaTeX snippets may be entered by surrounding them with single dollar signs. Use double dollar signs for display equations.