#+TITLE: GANTT charts with org-babel #+AUTHOR: Eric S Fraga #+EMAIL: address@hidden #+DATE: \url{$Revision: 1.12 $} #+LASTCHANGE: 2010.10.13 21:08:08 #+DESCRIPTION: #+KEYWORDS: #+LANGUAGE: en #+OPTIONS: H:3 num:nil toc:nil \n:nil @:t ::t |:t ^:t -:t f:t *:t <:t #+OPTIONS: TeX:t LaTeX:t skip:nil d:nil todo:t pri:nil tags:not-in-toc #+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js #+EXPORT_SELECT_TAGS: export #+EXPORT_EXCLUDE_TAGS: noexport #+LINK_UP: #+LINK_HOME: #+latex_header: \usepackage{pgf, pgfarrows, pgfnodes} #+latex_header: \usepackage{tikz} #+latex_header: \newcommand\ganttline[4]{% line, tag, start end #+latex_header: \node at (0,#1) [anchor=base east] {#2}; #+latex_header: \fill[black!20!white] (#3/20,#1-.2) rectangle (#4/20,#1+.2);} * table of tasks and milestones The following table describes the tasks and other relevant information for a project. Each line is an /entry/ and there are three types of entry allowed in this table: - task :: an actual task that has a start time, a duration and an end time. - milestone :: a specific milestone in the project that has a start time alone - date :: a point in time that will be drawn as a vertical line in the GANTT chart (e.g. start of each year). Each element of the chart will be annotated with the content of the /label/ column of each entry. The first column of the table is ignored but I use it to number the entries. The last column, titled /align/, is used to determine where to place the /activity/ text for tasks, whether to the left or right of the bar or, if nothing is specified, centred within the bar itself. #+tblname: gantttesttable | | type | label | activity | depends | start | duration | end | align | |----+-----------+-------+---------------+---------+-------+----------+-----+-------| | 1 | date | Start | | | 0 | | 0 | | | 2 | task | 1.1 | Lit survey | | 0 | 3 | 3 | right | | 3 | task | 1.2 | Develop model | 2 | 3 | 9 | 12 | right | | 4 | milestone | M1 | model | 3 | 12 | | 12 | | | 5 | task | 1.3 | Implement | 3 | 12 | 6 | 18 | left | | 6 | date | Y1 | | | 12 | | 12 | | | 7 | milestone | M2 | software | 5 | 18 | | 18 | | |----+-----------+-------+---------------+---------+-------+----------+-----+-------| | 8 | task | 2.1 | Surrogate | 3 | 15 | 6 | 21 | left | | 9 | task | 2.2 | Implement | 7 | 21 | 3 | 24 | left | | 10 | milestone | M3 | software | 8 | 24 | | 24 | | | 11 | date | End | | | 24 | | 24 | | |----+-----------+-------+---------------+---------+-------+----------+-----+-------| #+TBLFM: address@hidden::$8=$6+$7::@address@hidden::@address@hidden::@address@hidden::@address@hidden::@address@hidden * some latex code for gantt charts The following \LaTeX{} code is used to generate the actual elements of the GANTT chart. The three colours used, to indicate /tasks/, /milestones/ and /date/ lines, can be change to suit your own tastes. All the rest of the code is not meant to be changed. Ideally, this code should be in a separate /style/ file (see [[actions][list of actions below]]) and an environment should be defined. As it stands, the code can only be used once within a document unless the =gantttask= counter is reset manually. #+latex_header: \usepackage{tikz} #+begin_latex \newcounter{gantttask} \newcommand{\gantttaskcolour}{blue!50!white} \newcommand{\ganttmilestonecolour}{red!50!white} \newcommand{\ganttdatelinecolour}{black!50!white} \newcommand{\ganttprojecttime}{1} \newcommand{\ganttntasks}{1} \newcommand\gantttask[6]{% label, activity, start, end, labelpos, align \begin{pgfonlayer}{foreground} % \node at (0,\thegantttask) [anchor=base east] {#2}; \stepcounter{gantttask} \node [left] at (0,\thegantttask) {#1}; \draw[fill=\gantttaskcolour] (#3/\ganttprojecttime,\thegantttask-0.4) rectangle (#4/\ganttprojecttime,\thegantttask +0.4); \node at (#5/\ganttprojecttime,\thegantttask) #6 {#2}; \end{pgfonlayer} } \newcommand\ganttpoint[3]{% line, tag, date \node at (0,#1) [anchor=base east] {#2}; \fill[black] (#3/\ganttprojecttime,#1) circle (0.1/\ganttprojecttime); } \newcommand\ganttdateline[2]{% tag, date \begin{pgfonlayer}{background} \draw[\ganttdatelinecolour] (#2/\ganttprojecttime,0) -- (#2/\ganttprojecttime,\thegantttask+1); \node at (#2/\ganttprojecttime,0) [above] {#1}; \end{pgfonlayer} } \newcommand\ganttmilestone[2]{% tag, date \begin{pgfonlayer}{foreground} \node at (#2/\ganttprojecttime,\thegantttask+1.0) [below] {#1}; \draw[black,fill=\ganttmilestonecolour] (#2/\ganttprojecttime-0.1\ganttntasks/\ganttprojecttime,\thegantttask+0.9) rectangle (#2/\ganttprojecttime+0.1\ganttntasks/\ganttprojecttime,\thegantttask+1.1); \end{pgfonlayer} } #+END_LaTeX * lisp code for creating latex that uses above :noexport: #+source: elispgantt #+begin_src emacs-lisp :var table=gantttesttable :results output raw :exports results (defun esf/generate-gantt-chart (table) (let ((dates "") (entries (nthcdr 2 table)) (milestones "") (nmilestones 0) (ntasks 0) (projecttime 0) (tasks "") (xlength 1) ) (message "Initial: %s\n" table) (message "Entries: %s\n" entries) (while entries (let ((entry (first entries))) (if (listp entry) (let ((id (first entry)) (type (nth 1 entry)) (label (nth 2 entry)) (task (nth 3 entry)) (dependencies (nth 4 entry)) (start (nth 5 entry)) (duration (nth 6 entry)) (end (nth 7 entry)) (alignment (nth 8 entry)) ) (if (> start projecttime) (setq projecttime start)) (if (string= type "task") (let ((end (+ start duration)) (textposition (+ start (/ duration 2))) (flush "") ) (if (string= alignment "left") (progn (setq textposition start) (setq flush "[left]")) (if (string= alignment "right") (progn (setq textposition end) (setq flush "[right]")) ) ) (setq tasks (format "%s \\gantttask{%s}{%s}{%d}{%d}{%d}{%s}\n" tasks label task start end textposition flush)) (setq ntasks (+ 1 ntasks)) (if (> end projecttime) (setq projecttime end)) ) (if (string= type "milestone") (progn (setq milestones (format "%s \\ganttmilestone{$\\begin{array}{c}\\mbox{%s}\\\\ \\mbox{%s}\\end{array}$}{%d}\n" milestones label task start)) (setq nmilestones (+ 1 nmilestones))) (if (string= type "date") (setq dates (format "%s \\ganttdateline{%s}{%d}\n" dates label start)) (message "Ignoring entry with type %s\n" type) ) ) ) ) (message "Ignoring non-list entry %s\n" entry) ) ; end if list entry (setq entries (cdr entries)) ) ) ; end while entries left (format "#+begin_latex \\pgfdeclarelayer{background} \\pgfdeclarelayer{foreground} \\pgfsetlayers{background,foreground} \\renewcommand{\\ganttprojecttime}{%d} \\renewcommand{\\ganttntasks}{%d} \\noindent \\begin{tikzpicture}[y=-0.75cm,x=0.75\\textwidth] \\begin{pgfonlayer}{background} \\draw[very thin, red!10!white] (0,1+\\ganttntasks) grid [ystep=0.75cm,xstep=1/\\ganttprojecttime] (1,0); \\draw[\\ganttdatelinecolour] (0,0) -- (1,0); \\draw[\\ganttdatelinecolour] (0,1+\\ganttntasks) -- (1,1+\\ganttntasks); \\end{pgfonlayer} %s %s %s \\end{tikzpicture} ,#+end_latex\n" projecttime ntasks tasks milestones dates) ) ) (esf/generate-gantt-chart table) #+end_src #+ \\draw[help lines, very thin, red!20!white] (0,%s) grid (\\ganttprojecttime,0); * The GANTT chart The following GANTT chart is generated by the =emacs-lisp= code in the section immediately above this one in the =org= file, a section that has not been exported. #+results: elispgantt #+begin_latex \pgfdeclarelayer{background} \pgfdeclarelayer{foreground} \pgfsetlayers{background,foreground} \renewcommand{\ganttprojecttime}{24} \renewcommand{\ganttntasks}{5} \noindent \begin{tikzpicture}[y=-0.75cm,x=0.75\textwidth] \begin{pgfonlayer}{background} \draw[very thin, red!10!white] (0,1+\ganttntasks) grid [ystep=0.75cm,xstep=1/\ganttprojecttime] (1,0); \draw[\ganttdatelinecolour] (0,0) -- (1,0); \draw[\ganttdatelinecolour] (0,1+\ganttntasks) -- (1,1+\ganttntasks); \end{pgfonlayer} \gantttask{1.1}{Lit survey}{0}{3}{3}{[right]} \gantttask{1.2}{Develop model}{3}{12}{12}{[right]} \gantttask{1.3}{Implement}{12}{18}{12}{[left]} \gantttask{2.1}{Surrogate}{15}{21}{15}{[left]} \gantttask{2.2}{Implement}{21}{24}{21}{[left]} \ganttmilestone{$\begin{array}{c}\mbox{M1}\\ \mbox{model}\end{array}$}{12} \ganttmilestone{$\begin{array}{c}\mbox{M2}\\ \mbox{software}\end{array}$}{18} \ganttmilestone{$\begin{array}{c}\mbox{M3}\\ \mbox{software}\end{array}$}{24} \ganttdateline{Start}{0} \ganttdateline{Y1}{12} \ganttdateline{End}{24} \end{tikzpicture} #+end_latex # <> * TODO [0/5] outstanding issues or actions - [ ] encapsulate latex code within a package - [ ] figure out how to make indirect references in org's table - [ ] using org-store-link on a section like this one stores the special directive, in square brackets, as part of the link information but, of course, this changes as items are ticked off so the heading changes. - [ ] there seems to be some problem with indentation of description lists such as the one in the first section of this document. - [ ] although the export procedure prompts for whether the emacs lisp code should be executed, answering yes doesn't actually update the results section. The code is executed (see *Messages* buffer). The updating might not be happening because the code is within a section that is not going to be exported or because there is no export directive on the code? I should try playing with =call= invocations of babel code.