2

After watching some videos on concept maps such as concept mapping for time management and long term memory, I decided that this might be a useful way to learn. When looking for apps (e.g. CmapTools) that allow for a quick creation of such maps, I realized that I will be potentially wasting a lot of time "drawing" by connecting boxes with arrows, and adjusting the map so that it looks neat. Moreover, I would like to have my maps in an easily accessible and searchable file format, i.e. a text file. Preferably in a an Org-Mode file.

As Org-Mode can contain LaTeX code, my eye fell on TiKz. However, I want the concept maps to be still as much human readable (and thus easily editable) as possible. Consider this example in a pseudo code:

(* Shares) {encompass} (common shares)
           {encompass} (preference shares)

(common shares) {carry} (voting rights)
                {may entitle} (dividends)

(preference shares) {give right to} (dividends)

(dividends) {paid out of} (distributable profits)
            {declared by} (directors)

After compiling it should look like this:

enter image description here

Notice that both 'dividends' concepts point to the concept 'directors'.

How can I achieve this with LaTeX and TiKz (or other package)?

See relating discussion on emacs.se here.

  • 1
    Perhaps you would be interested in dot and dot2tex – JLDiaz Jun 28 '16 at 10:39
  • Thanks, @JLDiaz . The dotsyntax comes close what I had in mind. However, as far as I understand the manual, one can only place a label next to a connecting line. Although you can also place it on the edge, this would diminish the maps usability as you can't read the proposition ('dividends - paid out of - distributable profits') like a sentence without moving the map. – Singulaere Entitaet Jun 28 '16 at 15:06
  • Right. Standard dot syntax does not allow you to place the label on top of the line. But dot2tex can change that using --tikzedgelabels option. See my answer. – JLDiaz Jun 28 '16 at 17:19
6

You can try with forest, I think is quite human readable, ... once you are used to it.

\documentclass[border=2mm]{standalone}

\usepackage{forest}

\begin{document}

\begin{forest}
    box/.style={rounded corners, draw, fill=gray!20}
[* Shares, box 
    [encompass 
        [common shares, box
            [carry 
                [voting rights, box 
                    [such as 
                        [vote for appointment of director, box]]]]
            [may entitle to
                [dividends, box
                    [paid out of
                        [distributable profits, box]]
                    [declared by
                        [,phantom]
                        [directors, box, name=dir]]]]]]
    [encompass
        [preference shares, box
            [give right to 
                [dividends, box
                    [declared by, name=last]]]]]]
    \draw (last)--(dir);
\end{forest}
\end{document}

enter image description here

  • By \draw (last)--(dir) you are drawing a line "manually", right? Is there a way to avoid such interference with the automatic generation of the tree? – Singulaere Entitaet Jun 28 '16 at 12:35
  • 1
    Yes, it's manually drawn. A tree cannot have a leave with two parents – Ignasi Jun 28 '16 at 13:22
  • @SingulaereEntitaet You can add it as part of the specification of the tree, though. It need not be added as a separate command. (But I'm not sure whether this would help or not.) – cfr Jun 28 '16 at 23:19
5

As suggested in a comment, a possible approach would be dot and dot2tex.

Dot's syntax is very terse. This particular example comes out a bit convoluted, due to the long text in some nodes, but it is nevertheless rather readable:

digraph G {
  node [shape=box, fixedsize=true, width=1.2];

  shares [label="*\\\\Shares"];
  shares -> "common shares" [label="encompass"];
  shares -> "preference shares" [label="encompass"];

  "common shares" -> "voting rights" [label="carry"];
  "common shares" -> {dividends1 [label="dividends"] } [label="may entitle to"];
  "preference shares" -> {dividends2 [label="dividends"]} [label="give right to"];
  dividends1 -> "distributable profits" [label="paid out of"];
  dividends1 -> directors [label="declared by", lblstyle="pos=0.1"];
  dividends2 -> directors [label="declared by"];
  "voting rights" -> "\\itshape vote for appointment of director" [label="such as"];
}

Some things to note:

  1. If the node name contains spaces, it has to be quoted
  2. If you want to insert line breaks, in standard dot syntax you can embed \n in the node name, but if you plan to process it with dot2tex you have to use \\\\ which will translate to \\ as part of the node content.
  3. You have two different nodes with the same name in your diagram. You have to give them different names, although you can use the same "label" for both.

To convert the above example.dot file into a example.tex, the tool dot2tex is used, in the following way:

dot2tex -t raw -f tikz \
--nodeoptions='every node/.style={text width=2cm, text centered, rounded corners, fill=black!10}' \
--edgeoptions="every node/.style={fill=white, inner sep=1pt}" \
--tikzedgelabels example.dot > example.tex

The meaning of the used options is the following:

  • -t raw passes the text of the labels unprocessed to TeX (so a \ is passed as \ and not transformed in $\backslash$)
  • -f tikz sets the backend to TikZ
  • --nodeoptions creates a tikz scope in which all nodes of the diagram are drawn, and the given string is passed as options of the scope. In this case I use it to give all the nodes
  • --edgeoptions creates a tikz scope in which all edges (and labels in the edges) are drawn, and the given string is passed as options to the scope. In this case, I use it to give a white background to those labels.
  • --tikzedgelabels deactivates the algorigthm wihth places the labels next to the edges, and leaves to TikZ the decision about where to put those labels. By default, TikZ puts them on top of the edge, at pos=0.5 (you can alter this with lblstyle at proper places in the .dot file).

The resulting .tex can be processed with pdflatex to produce:

Result

The tex file is readable and editable, if you need to (at least to tweak the styles, the position of the nodes is however hardcoded). See for example a fragment of it:

\begin{scope}[every node/.style={text width=2cm, text centered, rounded corners, fill=black!10}]
  \node (distributable profits) at (147.0bp,18.0bp) [draw,rectangle] {distributable profits};
  \node (voting rights) at (43.0bp,106.0bp) [draw,rectangle] {voting rights};
  \node (preference shares) at (267.0bp,194.0bp) [draw,rectangle] {preference shares};
  \node (shares) at (215.0bp,282.0bp) [draw,rectangle] {*\\Shares};
  \node (directors) at (284.0bp,18.0bp) [draw,rectangle] {directors};
  \node (--itshape vote for appointment of director) at (43.0bp,18.0bp) [draw,rectangle] {\itshape vote for appointment of director};
  \node (dividends2) at (300.0bp,106.0bp) [draw,rectangle] {dividends};
  \node (dividends1) at (163.0bp,106.0bp) [draw,rectangle] {dividends};
  \node (common shares) at (163.0bp,194.0bp) [draw,rectangle] {common shares};
\end{scope}
\begin{scope}[every node/.style={fill=white, inner sep=1pt}]
  \draw [->] (dividends1) ..controls (151.68bp,82.407bp) and (149.31bp,76.075bp)  .. (148.0bp,70.0bp) .. controls (146.36bp,62.382bp) and (145.73bp,53.975bp)  ..      → node {paid out of} (distributable profits);
  \draw [->] (voting rights) ..controls (43.0bp,75.746bp) and (43.0bp,59.817bp)  .. node {such as} (--itshape vote for appointment of director);
  \draw [->] (common shares) ..controls (120.69bp,162.68bp) and (95.277bp,144.47bp)  .. node {carry} (voting rights);
  \draw [->] (shares) ..controls (188.28bp,258.43bp) and (182.93bp,252.38bp)  .. (179.0bp,246.0bp) .. controls (174.55bp,238.78bp) and (171.24bp,230.21bp)  .. node    → {encompass} (common shares);
  \draw [->] (dividends1) ..controls (192.78bp,82.127bp) and (200.77bp,75.853bp)  .. (208.0bp,70.0bp) .. controls (216.58bp,63.054bp) and (218.05bp,60.462bp)  ..      → (227.0bp,54.0bp) .. controls (232.89bp,49.748bp) and (239.31bp,45.483bp)  .. node[pos=0.1] {declared by} (directors);
  \draw [->] (preference shares) ..controls (278.32bp,163.51bp) and (284.58bp,147.18bp)  .. node {give right to} (dividends2);
  \draw [->] (shares) ..controls (231.96bp,258.28bp) and (236.31bp,251.97bp)  .. (240.0bp,246.0bp) .. controls (244.85bp,238.16bp) and (249.7bp,229.37bp)  .. node     → {encompass} (preference shares);
  \draw [->] (common shares) ..controls (163.0bp,163.75bp) and (163.0bp,147.82bp)  .. node {may entitle to} (dividends1);
  \draw [->] (dividends2) ..controls (298.52bp,77.908bp) and (297.27bp,65.158bp)  .. (295.0bp,54.0bp) .. controls (294.46bp,51.342bp) and (293.79bp,48.599bp)  ..      → node {declared by} (directors);
\end{scope}
  • I think, thanks to your help, I am very close to my goal. With some further magic it should be possible to enter the proposition similar to my pseudo code and then convert it behind the scenes into dotcode before finally processing it via dot2tex. – Singulaere Entitaet Jun 28 '16 at 20:28
3

EDIT: NEW APPROACH, SEE OLD APPROACH BELOW

Before reading further, I'd suggest that you read the old approach below. Without further ado, here the new approach to my goal:

The pseudo code that describes the concept map is now contained in a named org-modesource code block. While this seems to look done twice, it allows us to address this block by its name and pass it to a function. First, here is an example for the pseudo code within a block:

#+name: conceptmap2
#+begin_src org :results value raw :exports results 
(Shares) {encompass} (common shares)
(Shares) {encompass} (preference shares)

(preference shares) {give right to} (dividends)
(preference shares) {carry no} (voting rights)

(common shares) {may entitle to} (dividends)
(common shares) {carry} (voting rights)

(dividends) {declared by} (directors)
(dividends) {paid out of} (distributable profits)
#+end_src

As you can see, the name is conceptmap2. :results value raw means that the content is passed on without any modification.

Secondly, I have converted my emacs-lisp function concept2doc to an emacs-lisp source code block of the same name:

#+name: concept2doc
#+begin_src emacs-lisp :results value :var str="" :exports results
(with-temp-buffer
(insert str)
    (goto-char (point-min))
    (while (re-search-forward "(\\([[:word:]].*\\)) +{\\([[:word:]].*\\)} +(\\([[:word:]].*\\))" nil t)
      (replace-match (concat "\"" (match-string 1) "\"" " -> " "\"" (match-string 3) "\"" " [label=\"" (match-string 2) "\"];") t nil))
(buffer-substring (point-min) (point-max)))
#+end_src

The only modification I made, is that a string that is passed to this function is pasted into a temporary buffer so I did not have to change my code. The modified buffer with the translated pseudo code is then returned as a result.

Thirdly, we must make sure that the translated pseudo code is wrapped into the dot2texenvironment. This effected by this wrapper, which will be called after the function concept2dochas translated the pseudo code. You will soon see, how this is done. First, the code for the wrapper:

#+name: wrapper
#+begin_src emacs-lisp :var text="" :exports none
(setq prefixwrap 
"
\\b\ egin{dot2tex}[tikz, tikzedgelabels, options={-t raw --nodeoptions='every node/.style={text width=2cm, text centered, rounded corners, fill=black!10}' --edgeoptions=\"every node/.style={fill=white, inner sep=1pt}\"}]
\n
digraph G {
\n
node [shape=box, fixedsize=true, width=1.2];
\n
"
)

(setq postfixwrap
"}
\n
\\e\ nd{dot2tex}
\n
")

(concat prefixwrap text postfixwrap)

#+end_src

The wrapper works as follows:

It expects the translated pseudo code as parameter text. It defines two hardcoded constants prefixwrap and postfixwrap. These contain the dot2texi environment our now dot code is wrapped in. The wrapper returns the dot code within these constants by (concat prefixwrap text postfixwrap).

Now we can call our function concept2doc with our concept map in conceptmap2 as follows:

#+CALL: concept2doc(str=conceptmap2) :results latex :post wrapper(text=*this*) :exports results  

:post wrapper(text=*this*) calls the wrapper with the dotcode. :results latex wraps the result into a LaTeXsource code block.

OLD APPROACH

I am a step further towards my desired outcome. I integrated @JLDiaz approach into org-mode by using a LaTeX code block that makes use of the dot2texipackage. While the forestapproach proposed by @Ignasi is very human readable, the dot2texin my view isn't. Therefore, I created a small lisp function that translates my pseudo code (see above in the question) into dotcode. The workflow is as follows:

First, the pseudo code to create the concept map:

(Shares) {encompass} (common shares)
(Shares) {encompass} (preference shares)

(preference shares) {give right to} (dividends)
(preference shares) {carry no} (voting rights)

(common shares) {may entitle to} (dividends)
(common shares) {carry} (voting rights)

(dividends) {declared by} (directors)
(dividends) {paid out of} (distributable profits)

Secondly, the integration into org-mode:

#+LATEX_HEADER: \usepackage{dot2texi}
#+LATEX_HEADER: \usepackage{tikz}
#+LATEX_HEADER: \usetikzlibrary{shapes, arrows}

#+BEGIN_LATEX
\begin{dot2tex}[tikz, tikzedgelabels, options={-t raw --nodeoptions='every node/.style={text width=2cm, text centered, rounded corners, fill=black!10}' --edgeoptions="every node/.style={fill=white, inner sep=1pt}"}]
digraph G {

  node [shape=box, fixedsize=true, width=1.2];


(Shares) {encompass} (common shares)
(Shares) {encompass} (preference shares)

(preference shares) {give right to} (dividends)
(preference shares) {carry no} (voting rights)

(common shares) {may entitle to} (dividends)
(common shares) {carry} (voting rights)

(dividends) {declared by} (directors)
(dividends) {paid out of} (distributable profits)


}
\end{dot2tex}
#+END_LATEX

Thirdly, before exporting the org-modefile, the region containing the concept map pseudo code is selected and the following eLisp function is executed:

(defun concept2dot (rStart rEnd)
  (interactive "r")
  (save-restriction
    (narrow-to-region rStart rEnd)
    (goto-char (point-min))
    (while (re-search-forward "(\\([[:word:]].*\\)) +{\\([[:word:]].*\\)} +(\\([[:word:]].*\\))" nil t)
      (replace-match (concat "\"" (match-string 1) "\"" " -> " "\"" (match-string 3) "\"" " [label=\"" (match-string 2) "\"];") t nil))
    )
  )

Afterwards the pseudo code part of the org-modefile looks like this:

"Shares" -> "common shares" [label="encompass"];
"Shares" -> "preference shares" [label="encompass"];

"preference shares" -> "dividends" [label="give right to"];
"preference shares" -> "voting rights" [label="carry no"];

"common shares" -> "dividends" [label="may entitle to"];
"common shares" -> "voting rights" [label="carry"];

"dividends" -> "directors" [label="declared by"];
"dividends" -> "distributable profits" [label="paid out of"];

The resulting concept map looks like this:

enter image description here

Things to do and to improve:

  1. Org-modesource block only contains pseudo code, all LaTeXcode is added on export.
  2. Translation from pseudo code into dotcode is made automatically on export
  3. Pseudo code allows for more complex concept maps (see first example)
  4. Create Emacs function that looks for the next occurrence of a concept under the cursor to easily follow the chain of concepts.
  • I have created a question on emacs.se to discuss some of these further improvements: here – Singulaere Entitaet Jun 30 '16 at 9:12

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.