Antoine Labour | d0b0503 | 2010-07-01 13:41:25 -0700 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
| 6 | """Helper functions for building graphs with dot.""" |
| 7 | |
| 8 | import subprocess |
| 9 | |
| 10 | class Subgraph(object): |
| 11 | """A subgraph in dot. Contains nodes, arcs, and other subgraphs.""" |
| 12 | |
| 13 | _valid_ranks = set(['source', 'sink', 'same', 'min', 'max', None]) |
| 14 | |
| 15 | def __init__(self, rank=None): |
| 16 | self.SetRank(rank) |
| 17 | self._nodes = [] |
| 18 | self._subgraphs = [] |
| 19 | self._arcs = set() |
| 20 | |
| 21 | def SetRank(self, rank): |
| 22 | """Sets the rank for the nodes in the graph. |
| 23 | |
| 24 | Can be one of 'source', 'sink', 'same', 'min', 'max' or None. See dot |
| 25 | documentation at http://www.graphviz.org/Documentation.php for exact |
| 26 | semantics.""" |
| 27 | assert(rank in self._valid_ranks) |
| 28 | self._rank = rank |
| 29 | |
| 30 | def AddNode(self, id, name=None, color=None, href=None): |
| 31 | """Adds a node to the subgraph.""" |
| 32 | tags = {} |
| 33 | if name: |
| 34 | tags['label'] = name |
| 35 | if color: |
| 36 | tags['color'] = color |
| 37 | tags['fontcolor'] = color |
| 38 | if href: |
| 39 | tags['href'] = href |
| 40 | self._nodes.append({'id': id, 'tags': tags}) |
| 41 | |
| 42 | def AddSubgraph(self, subgraph): |
| 43 | """Adds a subgraph to the subgraph.""" |
| 44 | self._subgraphs.append(subgraph) |
| 45 | |
| 46 | def AddNewSubgraph(self, rank=None): |
| 47 | """Adds a new subgraph to the subgraph. The new subgraph is returned.""" |
| 48 | subgraph = Subgraph(rank) |
| 49 | self.AddSubgraph(subgraph) |
| 50 | return subgraph |
| 51 | |
| 52 | def AddArc(self, node_from, node_to): |
| 53 | """Adds an arc between two nodes.""" |
| 54 | self._arcs.add((node_from, node_to)) |
| 55 | |
| 56 | def _GenNodes(self): |
| 57 | """Generates the code for all the nodes.""" |
| 58 | lines = [] |
| 59 | for node in self._nodes: |
| 60 | tags = ['%s="%s"' % (k, v) for (k, v) in node['tags'].iteritems()] |
| 61 | lines.append('"%s" [%s];' % (node['id'], ', '.join(tags))) |
| 62 | return lines |
| 63 | |
| 64 | def _GenSubgraphs(self): |
| 65 | """Generates the code for all the subgraphs contained in this subgraph.""" |
| 66 | lines = [] |
| 67 | for subgraph in self._subgraphs: |
| 68 | lines += subgraph.Gen(); |
| 69 | return lines |
| 70 | |
| 71 | def _GenArcs(self): |
| 72 | """Generates the code for all the arcs.""" |
| 73 | lines = [] |
| 74 | for node_from, node_to in self._arcs: |
| 75 | lines.append('"%s" -> "%s";' % (node_from, node_to)) |
| 76 | return lines |
| 77 | |
| 78 | def _GenInner(self): |
| 79 | """Generates the code for the inner contents of the subgraph.""" |
| 80 | lines = [] |
| 81 | if self._rank: |
| 82 | lines.append('rank=%s;' % self._rank) |
| 83 | lines += self._GenSubgraphs() |
| 84 | lines += self._GenNodes() |
| 85 | lines += self._GenArcs() |
| 86 | return lines |
| 87 | |
| 88 | def Gen(self): |
| 89 | """Generates the code for the subgraph.""" |
| 90 | return ['subgraph {'] + self._GenInner() + ['}'] |
| 91 | |
| 92 | |
| 93 | class Graph(Subgraph): |
| 94 | """A top-level graph in dot. It's basically a subgraph with a name.""" |
| 95 | |
| 96 | def __init__(self, name): |
| 97 | Subgraph.__init__(self) |
| 98 | self._name = name |
| 99 | |
| 100 | def Gen(self): |
| 101 | """Generates the code for the graph.""" |
| 102 | return ['digraph "%s" {' % self._name, |
| 103 | 'graph [name="%s"];' % self._name] + self._GenInner() + ['}'] |
| 104 | |
| 105 | |
| 106 | def GenerateImage(lines, filename, format='svg', save_dot_filename=None): |
| 107 | """Generates the image by calling dot on the input lines.""" |
| 108 | data = '\n'.join(lines) |
| 109 | proc = subprocess.Popen(['dot', '-T' + format, '-o' + filename], |
| 110 | stdin=subprocess.PIPE) |
| 111 | proc.communicate(data) |
| 112 | |
| 113 | if save_dot_filename: |
| 114 | file = open(save_dot_filename, 'w') |
| 115 | file.write(data) |
| 116 | file.close() |