tdot


Name

tdot - Thingy DOT writer - package to create dot files and image files for the Graphviz tools with a syntax close to Tcl and to the dot language.

Description

DDG 2021-09-06: The package provides one command tdot which can hold currently just a single dot code collection. All commands will be evaluated within the tdot namespace. In comparison to the Graphviz tcldot and the gvtcl packages this package uses directly the Graphviz executables and has a syntax very close to the dot language. So there is no need to consult special API pages for the interface. It is therefore enough to consult the few methods in the documentation and the standard dot or neato documentation. There are a few restrictions because of this, for instance you can’t delete nodes and edges, you can only use shape=invis to hide a node or edge at a later point for instance.

Links

Example

Below a few examples from the Manual :

# demo: synopsis
package require tdot
tdot set type "strict digraph G"
tdot graph margin=0.2 
tdot node width=1 height=0.7 style=filled fillcolor=salmon shape=box
tdot block rank=same A B C
tdot addEdge A -> B
tdot node A label="Hello"
tdot node B label="World" fillcolor=skyblue
tdot edge dir=back
tdot addEdge B -> C
tdot node C label="Powered by\n tdot"
tdot write tdot-synopsis.png

tdot-synopsis2

Using the Tk canvas as output:

# demo: write
package require Tk
pack [canvas .can -background white] -side top -fill both -expand true
package require tdot
tdot set code ""
tdot graph margin=0.4
tdot node style=filled fillcolor=salmon shape=hexagon
tdot addEdge A -> B
tdot node A label="tdot"
tdot node B label="Hello World!"  
tdot write .can
.can create rect 10 10 290 250 -outline red ;# standard canvas commands still work

tdot-canvas

Switch to neato layout:

tdot set code ""
tdot set type "graph N" ;# switch to neato as layout engine
tdot addEdge n0 -- n1 -- n2 -- n3 -- n0;
tdot node n0 color=red style=filled fillcolor=salmon
tdot write dot-neato.png

tdot-neato

Switch to circo layout:

tdot set code ""
tdot set type "strict digraph G" ; # back to dot
tdot graph layout=circo ;# switch to circo (circular layout engine)
tdot addEdge A -> B -> C -> D -> A
tdot write dot-circle2.png

tdot-circo

Now a longer example which is possible with version 0.3.0 showing the history of Tcl/Tk ... and tdot ;)

tdot set code ""
tdot set type "digraph Tk"
tdot graph margin=0.3 
tdot graph size="8\;7" ;# semikolon must be backslashed due to thingy
tdot node shape=box style=filled fillcolor=grey width=1
tdot addEdge 1988  -> 1993 -> 1995 -> 1997 -> 1999 \
      -> 2000 -> 2002 -> 2007 -> 2012 -> future
tdot node fillcolor="#ff9999"
tdot edge style=invis
tdot addEdge  Tk -> Bytecode -> Unicode -> TEA -> vfs -> \
      Tile -> TclOO -> zipvfs
tdot edge style=filled
tdot node fillcolor="salmon"
tdot addEdge "Tcl/Tk" -> 7.3 -> 7.4 -> 8.0  ->  8.1 ->  8.3 \
      -> 8.4  -> 8.5  ->  8.6 -> 8.7;
tdot node fillcolor=cornsilk
tdot addEdge  7.3 -> Itcl -> 8.6
tdot addEdge  Tk -> 7.4 -> Otcl -> XOTcl -> NX 
tdot addEdge  Otcl -> Thingy -> tdot
tdot addEdge  Bytecode -> 8.0 
tdot addEdge  8.0 -> Namespace dir=back
tdot addEdge  Unicode -> 8.1
tdot addEdge  8.1 -> Wiki
tdot addEdge  TEA -> 8.3 
tdot addEdge  8.3 -> Tcllib -> Tklib
tdot addEdge  8.4 -> Starkit -> Snit -> Dict -> 8.5 
tdot addEdge  vfs -> 8.4
tdot addEdge  Tile -> 8.5
tdot addEdge  TclOO -> 8.6  -> TDBC
tdot addEdge  zipvfs -> 8.7  ;# Null is just a placeholder for the history
tdot block    rank=same 1988 "Tcl/Tk"  group=g1
tdot block    rank=same 1993  7.3      group=g1  Itcl
tdot block    rank=same 1995  Tk       group=g0  7.4 group=g1 Otcl group=g2
tdot block    rank=same 1997  Bytecode group=g0  8.0 group=g1 Namespace
tdot block    rank=same 1999  Unicode  group=g0  8.1 group=g1 Wiki 
tdot block    rank=same 2000  TEA      group=g0  8.3 group=g1 Tcllib \
                              Tklib Thingy group=g3 XOTcl group=g2 
tdot block    rank=same 2002  vfs      group=g0  8.4 group=g1 Starkit Dict Snit
tdot block    rank=same 2007  Tile     group=g0  8.5 group=g1 
tdot block    rank=same 2012  TclOO    group=g0  8.6 group=g1 TDBC NX group=g2
tdot block    rank=same future zipvfs  group=g0  8.7 group=g1 Null group=g2 tdot group=g3

# specific node settings 
tdot node     History label="History of Tcl/Tk\nand  tdot" shape=doubleoctagon color="salmon" penwidth=5 \
      fillcolor="white" fontsize=26 fontname="Monaco"
tdot node     Namespace fillcolor="#ff9999"
tdot node     future label=2021
tdot node     8.7    label="\[ 8.7a5 \]"
# arranging the History in the middle
tdot addEdge  8.7 -> Null style=invis
tdot addEdge  Null -> History style=invis
tdot node Null style=invis
tdot write tdot-history.png

tdot-history

For more examples see the manual page .

You might think why not writing dot code directly? Think about Tcl as a scripting engine for dot, here an example illustrating the concept. Imagine that you would have to write the pure dot code ...

package require tdot
tdot set code ""
tdot set type "strict graph G"

tdot graph margin=0.2 
tdot node width=0.5 height=0.5 \
  style=filled fillcolor=salmon shape=circle
tdot node Hello fillcolor=salmon
tdot node fillcolor=skyblue
foreach a [list A B C D E F] {
   tdot addEdge Hello -- $a
   foreach b [list a b c] {
       tdot node $a$b fillcolor=cornsilk
       tdot addEdge $a -- $a$b
   }
}

tdot-flower-example


Changelog

  • Version 0.2.0 (2021-09-14) - adding dotstring function to directly load graphs from standard dot texts
  • Version 0.3.0 (2021-09-26) - subgraphs, header method, space-issue fix, quoted node names, blocks with attributes, extended history example
  • Version 0.3.1 (2021-09-30) - removal of temporary files added

See also


Discussion

Please discuss here ...

JMN 2021-10-01 - This is a great little package. A minor issue I have is that when writing to a canvas I'm accumulating temporary dot files in the folder I run my app from. Handy for debugging - but I'd like to know a way to clear these as soon as the dot executable has finished.

It would also be very handy to be able to store some metadata in the nodes (and edges) in the canvas - but I think that's partly a limitation of the dot format(?) The nodes are tagged with things like 0node0x1ac099e8ca0. If there was a way of knowing/setting the tag before the dot exe is run, I could associate data - but as it stands you don't know where an element will end up on the canvas nor how it'll be tagged so the only data that seems to be usable for things like mouse click events is the visible label data. Again I'm guessing this is a limitation of graphviz because most output formats aren't potentially interactive like the Tk canvas. Any ideas on how I could store something like a hidden ID that could be retrieved upon clicking?

(update) I found one rather tortuous workaround using HTML labels containing a table. The second line of a label seems to end up as a separate text element in the canvas tagged the same as the node. By making that small enough and/or the right color it can be made invisible and the text can act as an ID.

DDG 2021-30-09 - I added the removal of temporary files to version 0.3.1. Unfortunately the canvas mode ignores the id argument for nodes and edges. The comment argument is still kept, but commented (sic) in the Tcl code ... so can't be not really used as data holder: So the dot command A[comment="node data A",id="nodeA"] ignores and removes the id argument, but shows the comment after the node name:

# A
# node data A
$c create oval ...
$c create text ...

It would be possible to place a little parser between tdot and the canvas which connects node names and tag names and possible comments and keep this as a dictionary in the background. The comments could be then "misused" as simple data holder: so the key is the node name and then two other keys for the comment and for the auto-tag, alternatively the tag can be changed to the node name, stripping away possible spaces and then just keeping a dict with node names and comments ... But thinking longer I would rather prefer to not wrangling the tdot canvas code, better seems to me the dictionary approach, you can then walk after loading the dictionary and replace the tags with the node names. So the dictionary would look like:

set d [dict create A [dict create comment "node data A" tags node0000xab2]]

After writing to the canvas tdot getDict should return the dictionary