[Epydoc-commits] SF.net SVN: epydoc: [1725] trunk/epydoc/src/epydoc/docwriter/dotgraph.py
Brought to you by:
edloper
|
From: <ed...@us...> - 2008-02-16 04:49:24
|
Revision: 1725
http://epydoc.svn.sourceforge.net/epydoc/?rev=1725&view=rev
Author: edloper
Date: 2008-02-15 20:49:21 -0800 (Fri, 15 Feb 2008)
Log Message:
-----------
- refactored class_tree_graph & uml_class_tree_graph -- now they share
most of their code.
- added truncation when class graphs get too big: max_subclass_depth
truncates subclasses that are too far down in the tree, and
max_subclasses limits the number of subclasses that are shown for
any given class.
- undocumented base classes are now drawn with a grey background
- uml graph options:
- Changed default values for max_attributes & max_operations
- Added options to control whether parameter defaults are shown;
and to truncate signatures that are too long
- set minimum width for uml nodes to 100
- Fixed bug in DotGraphNode -- subclasses should share the same
_next_id var, not use their own.
- If dot fails & epydoc.DEBUG is true, then print the broken dotfile
to a temp file.
Modified Paths:
--------------
trunk/epydoc/src/epydoc/docwriter/dotgraph.py
Modified: trunk/epydoc/src/epydoc/docwriter/dotgraph.py
===================================================================
--- trunk/epydoc/src/epydoc/docwriter/dotgraph.py 2008-02-16 02:02:58 UTC (rev 1724)
+++ trunk/epydoc/src/epydoc/docwriter/dotgraph.py 2008-02-16 04:49:21 UTC (rev 1725)
@@ -33,6 +33,7 @@
SELECTED_BG = '#ffd0d0'
BASECLASS_BG = '#e0b0a0'
SUBCLASS_BG = '#e0b0a0'
+UNDOCUMENTED_BG = '#c0c0c0'
ROUTINE_BG = '#e8d0b0' # maybe?
INH_LINK_COLOR = '#800000'
@@ -43,7 +44,7 @@
DOT_COMMAND = 'dot'
"""The command that should be used to spawn dot"""
-class DotGraph:
+class DotGraph(object):
"""
A ``dot`` directed graph. The contents of the graph are
constructed from the following instance variables:
@@ -248,8 +249,15 @@
self.to_dotfile())
if err: log.warning("Graphviz dot warning(s):\n%s" % err)
except OSError, e:
- log.warning("Unable to render Graphviz dot graph:\n%s" % e)
- #log.debug(self.to_dotfile())
+ log.warning("Unable to render Graphviz dot graph (%s):\n%s" %
+ (self.title, e))
+ import tempfile, epydoc
+ if epydoc.DEBUG:
+ filename = tempfile.mktemp('.dot')
+ out = open(filename, 'wb')
+ out.write(self.to_dotfile())
+ out.close()
+ log.debug('Failed dot graph written to %s' % filename)
return None
return result
@@ -277,7 +285,7 @@
# Default dot input encoding is UTF-8
return u'\n'.join(lines).encode('utf-8')
-class DotGraphNode:
+class DotGraphNode(object):
_next_id = 0
def __init__(self, label=None, html_label=None, **attribs):
if label is not None and html_label is not None:
@@ -285,8 +293,8 @@
if label is not None: attribs['label'] = label
self._html_label = html_label
self._attribs = attribs
- self.id = self.__class__._next_id
- self.__class__._next_id += 1
+ self.id = DotGraphNode._next_id
+ DotGraphNode._next_id += 1
self.port = None
def __getitem__(self, attr):
@@ -311,7 +319,7 @@
if attribs: attribs = ' [%s]' % (','.join(attribs))
return 'node%d%s' % (self.id, attribs)
-class DotGraphEdge:
+class DotGraphEdge(object):
def __init__(self, start, end, label=None, **attribs):
"""
:type start: `DotGraphNode`
@@ -386,7 +394,6 @@
- show/hide attribute types
- use qualifiers
"""
-
def __init__(self, class_doc, linker, context, collapsed=False,
bgcolor=CLASS_BG, **options):
"""
@@ -420,13 +427,20 @@
- `max_attributes`: The maximum number of attributes that
should be listed in the attribute box. If the class has
more than this number of attributes, some will be
- ellided. Ellipsis is marked with ``'...'``.
+ ellided. Ellipsis is marked with ``'...'``. (Default: 10)
- `max_operations`: The maximum number of operations that
- should be listed in the operation box.
+ should be listed in the operation box. (Default: 5)
- `add_nodes_for_linked_attributes`: If true, then
`link_attributes()` will create new a collapsed node for
the types of a linked attributes if no node yet exists for
that type.
+ - `show_signature_defaults`: If true, then show default
+ parameter values in method signatures; if false, then
+ hide them. (Default: *False*)
+ - `max_signature_width`: The maximum width (in chars) for
+ method signatures. If the signature is longer than this,
+ then it will be trunctated (with ``'...'``). (Default:
+ *60*)
"""
if not isinstance(class_doc, ClassDoc):
raise TypeError('Expected a ClassDoc as 1st argument')
@@ -463,6 +477,16 @@
These should not be added to the `DotGraph`; this node will
generate their dotfile code directly."""
+ self.same_rank = []
+ """List of nodes that should have the same rank as this one.
+ (Used for nodes that are created by _link_attributes)."""
+
+ # Keyword options:
+ self._show_signature_defaults = options.get(
+ 'show_signature_defaults', False)
+ self._max_signature_width = options.get(
+ 'max_signature_width', 60)
+
# Initialize operations & attributes lists.
show_private = options.get('show_private_vars', False)
show_magic = options.get('show_magic_vars', True)
@@ -515,7 +539,7 @@
r'^(None or|optional) ([\w\.]+)$|^([\w\.]+) or None$')
"""A regular expression that matches descriptions of optional types."""
- def link_attributes(self, nodes):
+ def link_attributes(self, graph, nodes):
"""
Convert any attributes with type descriptions corresponding to
documented classes to edges. The following type descriptions
@@ -549,9 +573,9 @@
# that var from our attribute list; otherwise, leave that var
# in our attribute list.
self.attributes = [var for var in self.attributes
- if not self._link_attribute(var, nodes)]
+ if not self._link_attribute(var, graph, nodes)]
- def _link_attribute(self, var, nodes):
+ def _link_attribute(self, var, graph, nodes):
"""
Helper for `link_attributes()`: try to convert the attribute
variable `var` into an edge, and add that edge to
@@ -563,18 +587,19 @@
# Simple type.
m = self.SIMPLE_TYPE_RE.match(type_descr)
- if m and self._add_attribute_edge(var, nodes, m.group(1)):
+ if m and self._add_attribute_edge(var, graph, nodes, m.group(1)):
return True
# Collection type.
m = self.COLLECTION_TYPE_RE.match(type_descr)
- if m and self._add_attribute_edge(var, nodes, m.group(2),
+ if m and self._add_attribute_edge(var, graph, nodes, m.group(2),
headlabel='*'):
return True
# Optional type.
m = self.OPTIONAL_TYPE_RE.match(type_descr)
- if m and self._add_attribute_edge(var, nodes, m.group(2) or m.group(3),
+ if m and self._add_attribute_edge(var, graph, nodes,
+ m.group(2) or m.group(3),
headlabel='0..1'):
return True
@@ -582,7 +607,7 @@
m = self.MAPPING_TYPE_RE.match(type_descr)
if m:
port = 'qualifier_%s' % var.name
- if self._add_attribute_edge(var, nodes, m.group(3),
+ if self._add_attribute_edge(var, graph, nodes, m.group(3),
tailport='%s:e' % port):
self.qualifiers.append( (m.group(2), port) )
return True
@@ -591,7 +616,8 @@
m = self.MAPPING_TO_COLLECTION_TYPE_RE.match(type_descr)
if m:
port = 'qualifier_%s' % var.name
- if self._add_attribute_edge(var, nodes, m.group(4), headlabel='*',
+ if self._add_attribute_edge(var, graph, nodes, m.group(4),
+ headlabel='*',
tailport='%s:e' % port):
self.qualifiers.append( (m.group(2), port) )
return True
@@ -599,7 +625,7 @@
# We were unable to link this attribute.
return False
- def _add_attribute_edge(self, var, nodes, type_str, **attribs):
+ def _add_attribute_edge(self, var, graph, nodes, type_str, **attribs):
"""
Helper for `link_attributes()`: try to add an edge for the
given attribute variable `var`. Return ``True`` if
@@ -619,7 +645,9 @@
if self.options.get('add_nodes_for_linked_attributes', True):
type_node = DotGraphUmlClassNode(type_doc, self.linker,
self.context, collapsed=True)
+ self.same_rank.append(type_node)
nodes[type_doc] = type_node
+ graph.nodes.append(type_node)
else:
return False
@@ -629,7 +657,7 @@
attribs.setdefault('tailport', 'body')
url = self.linker.url_for(var) or NOOP_URL
self.edges.append(DotGraphEdge(self, type_node, label=var.name,
- arrowhead='open', href=url,
+ arrowtail='odiamond', arrowhead='none', href=url,
tooltip=var.canonical_name, labeldistance=1.5,
**attribs))
return True
@@ -690,6 +718,8 @@
if func_doc.vararg: args.append('*'+func_doc.vararg)
if func_doc.kwarg: args.append('**'+func_doc.kwarg)
label = '%s(%s)' % (var_doc.name, ', '.join(args))
+ if len(label) > self._max_signature_width:
+ label = label[:self._max_signature_width-4]+'...)'
# Get the URL
url = self.linker.url_for(var_doc) or NOOP_URL
# Construct & return the pseudo-html code
@@ -700,7 +730,7 @@
:todo: Handle tuple args better
:todo: Optionally add type info?
"""
- if default is None:
+ if default is None or not self._show_signature_defaults:
return '%s' % name
else:
pyval_repr = default.summary_pyval_repr().to_plaintext(None)
@@ -732,12 +762,12 @@
_LABEL = '''
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" CELLPADDING="0">
<TR><TD ROWSPAN="%s">
- <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" WIDTH="100"
CELLPADDING="0" PORT="body" BGCOLOR="%s">
- <TR><TD>%s</TD></TR>
- <TR><TD><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
+ <TR><TD WIDTH="100">%s</TD></TR>
+ <TR><TD WIDTH="100"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
%s</TABLE></TD></TR>
- <TR><TD><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
+ <TR><TD WIDTH="100"><TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
%s</TABLE></TD></TR>
</TABLE>
</TD></TR>
@@ -746,7 +776,7 @@
_COLLAPSED_LABEL = '''
<TABLE CELLBORDER="0" BGCOLOR="%s" PORT="body">
- <TR><TD>%s</TD></TR>
+ <TR><TD WIDTH="50">%s</TD></TR>
</TABLE>'''
def _get_html_label(self):
@@ -760,7 +790,7 @@
# Construct the attribute list. (If it's too long, truncate)
attrib_cells = [self._attribute_cell(a) for a in self.attributes]
- max_attributes = self.options.get('max_attributes', 15)
+ max_attributes = self.options.get('max_attributes', 10)
if len(attrib_cells) == 0:
attrib_cells = ['<TR><TD></TD></TR>']
elif len(attrib_cells) > max_attributes:
@@ -769,7 +799,7 @@
# Construct the operation list. (If it's too long, truncate)
oper_cells = [self._operation_cell(a) for a in self.operations]
- max_operations = self.options.get('max_operations', 15)
+ max_operations = self.options.get('max_operations', 5)
if len(oper_cells) == 0:
oper_cells = ['<TR><TD></TD></TR>']
elif len(oper_cells) > max_operations:
@@ -797,6 +827,11 @@
if not self.collapsed:
for edge in self.edges:
s += '\n' + edge.to_dotfile()
+ if self.same_rank:
+ sr_nodes = ''.join(['node%s; ' % node.id
+ for node in self.same_rank])
+ # [xx] This can cause dot to crash! not sure why!
+ #s += '{rank=same; node%s; %s}' % (self.id, sr_nodes)
return s
class DotGraphUmlModuleNode(DotGraphNode):
@@ -953,7 +988,7 @@
log.warning('UML style package trees require dot version 2.0+')
graph = DotGraph('Package Tree for %s' % name_list(packages, context),
- body='ranksep=.3\n;nodesep=.1\n',
+ body='ranksep=.3\nnodesep=.1\n',
edge_defaults={'dir':'none'})
# Options
@@ -1003,149 +1038,226 @@
return graph
######################################################################
-def class_tree_graph(bases, linker, context=None, **options):
+def class_tree_graph(classes, linker, context=None, **options):
"""
Return a `DotGraph` that graphically displays the class
hierarchy for the given classes. Options:
- - exclude
+ - exclude: A list of classes that should be excluded
- dir: LR|RL|BT requests a left-to-right, right-to-left, or
bottom-to- top, drawing. (corresponds to the dot option
'rankdir'
+ - max_subclass_depth: The maximum depth to which subclasses
+ will be drawn.
+ - max_subclasses: A list of ints, specifying how many
+ subclasses should be drawn per class at each level of the
+ graph. E.g., [5,3,1] means draw up to 5 subclasses for the
+ specified classes; up to 3 subsubclasses for each of those (up
+ to) 5 subclasses; and up to 1 subclass for each of those.
"""
- if isinstance(bases, ClassDoc): bases = [bases]
- graph = DotGraph('Class Hierarchy for %s' % name_list(bases, context),
+ # Callbacks:
+ def mknode(cls, nodetype, linker, context, options):
+ return mk_valdoc_node(cls, linker, context)
+ def mkedge(start, end, edgetype, options):
+ return DotGraphEdge(start, end)
+
+ if isinstance(classes, ClassDoc): classes = [classes]
+ graph = DotGraph('Class Hierarchy for %s' % name_list(classes, context),
body='ranksep=0.3\n',
edge_defaults={'sametail':True, 'dir':'none'})
+ _class_tree_graph(graph, classes, mknode, mkedge, linker,
+ context, options, cls2node={})
+ return graph
- # Options
- if options.get('dir', 'TB') != 'TB': # default: top-down
- graph.body += 'rankdir=%s\n' % options.get('dir', 'TB')
+def _class_tree_graph(graph, classes, mknode, mkedge, linker,
+ context, options, cls2node):
+ """
+ A helper function that is used by both `class_tree_graph()` and
+ `uml_class_tree_graph()` to draw class trees. To abstract over
+ the differences between the two, this function takes two callback
+ functions that create graph nodes and edges:
+
+ - ``mknode(base, nodetype, linker, context, options)``: Returns
+ a `DotGraphNode`. ``nodetype`` is one of: subclass, superclass,
+ selected, undocumented.
+ - ``mkedge(begin, end, edgetype, options)``: Returns a
+ `DotGraphEdge`. ``edgetype`` is one of: subclass,
+ truncate-subclass.
+ """
+ rankdir = options.get('dir', 'TB')
+ graph.body += 'rankdir=%s\n' % rankdir
+ truncated = set() # Classes whose subclasses were truncated
+ _add_class_tree_superclasses(graph, classes, mknode, mkedge, linker,
+ context, options, cls2node)
+ _add_class_tree_subclasses(graph, classes, mknode, mkedge, linker,
+ context, options, cls2node, truncated)
+ _add_class_tree_inheritance(graph, classes, mknode, mkedge, linker,
+ context, options, cls2node, truncated)
+
+def _add_class_tree_superclasses(graph, classes, mknode, mkedge, linker,
+ context, options, cls2node):
exclude = options.get('exclude', ())
+
+ # Create nodes for all bases.
+ for cls in classes:
+ for base in cls.mro():
+ # Don't include 'object'
+ if base.canonical_name == DottedName('object'): continue
+ # Stop if we reach an excluded class.
+ if base in exclude: break
+ # Don't do the same class twice.
+ if base in cls2node: continue
+ # Make the node.
+ if linker.url_for(base) is None: typ = 'undocumented'
+ elif base in classes: typ = 'selected'
+ else: typ = 'superclass'
+ cls2node[base] = mknode(base, typ, linker, context, options)
+ graph.nodes.append(cls2node[base])
- # Find all superclasses & subclasses of the given classes.
- classes = set(bases)
- queue = list(bases)
+def _add_class_tree_subclasses(graph, classes, mknode, mkedge, linker,
+ context, options, cls2node, truncated):
+ exclude = options.get('exclude', ())
+ max_subclass_depth = options.get('max_subclass_depth', 3)
+ max_subclasses = list(options.get('max_subclasses', (5,3,2,1)))
+ max_subclasses += len(classes)*max_subclasses[-1:] # repeat last num
+
+ # Find the depth of each subclass (for truncation)
+ subclass_depth = _get_subclass_depth_map(classes)
+
+ queue = list(classes)
for cls in queue:
- if isinstance(cls, ClassDoc):
- if cls.subclasses not in (None, UNKNOWN):
- subclasses = cls.subclasses
- if exclude:
- subclasses = [d for d in subclasses if d not in exclude]
- queue.extend(subclasses)
- classes.update(subclasses)
- queue = list(bases)
- for cls in queue:
- if isinstance(cls, ClassDoc):
- if cls.bases not in (None, UNKNOWN):
- bases = cls.bases
- if exclude:
- bases = [d for d in bases if d not in exclude]
- queue.extend(bases)
- classes.update(bases)
+ # If there are no subclasses, then we're done.
+ if not isinstance(cls, ClassDoc): continue
+ if cls.subclasses in (None, UNKNOWN, (), []): continue
+ # Get the list of subclasses.
+ subclasses = [subcls for subcls in cls.subclasses
+ if subcls not in cls2node and subcls not in exclude]
+ # If the subclass list is too long, then truncate it.
+ if len(subclasses) > max_subclasses[subclass_depth[cls]]:
+ subclasses = subclasses[:max_subclasses[subclass_depth[cls]]]
+ truncated.add(cls)
+ # Truncate any classes that are too deep.
+ num_subclasses = len(subclasses)
+ subclasses = [subcls for subcls in subclasses
+ if subclass_depth[subcls] <= max_subclass_depth]
+ if len(subclasses) < num_subclasses: truncated.add(cls)
+ # Add a node for each subclass.
+ for subcls in subclasses:
+ cls2node[subcls] = mknode(subcls, 'subclass', linker,
+ context, options)
+ graph.nodes.append(cls2node[subcls])
+ # Add the subclasses to our queue.
+ queue.extend(subclasses)
- # Add a node for each cls.
- classes = [d for d in classes if isinstance(d, ClassDoc)
- if d.pyval is not object]
- nodes = add_valdoc_nodes(graph, classes, linker, context)
+def _add_class_tree_inheritance(graph, classes, mknode, mkedge, linker,
+ context, options, cls2node, truncated):
+ # Add inheritance edges.
+ for (cls, node) in cls2node.items():
+ for base in cls.bases:
+ if base in cls2node:
+ graph.edges.append(mkedge(cls2node[base], node,
+ 'subclass', options))
+ # Mark truncated classes
+ for cls in truncated:
+ ellipsis = DotGraphNode('...', shape='plaintext',
+ width='0', height='0')
+ graph.nodes.append(ellipsis)
+ graph.edges.append(mkedge(cls2node[cls], ellipsis,
+ 'truncate-subclass', options))
- # Add an edge for each package/subclass relationship.
- edges = set()
- for cls in classes:
- for subcls in cls.subclasses:
- if cls in nodes and subcls in nodes:
- edges.add((nodes[cls], nodes[subcls]))
- graph.edges = [DotGraphEdge(src,dst) for (src,dst) in edges]
+def _get_subclass_depth_map(classes):
+ subclass_depth = dict([(cls,0) for cls in classes])
+ queue = list(classes)
+ for cls in queue:
+ if (isinstance(cls, ClassDoc) and
+ cls.subclasses not in (None, UNKNOWN)):
+ for subcls in cls.subclasses:
+ subclass_depth[subcls] = max(subclass_depth.get(subcls,0),
+ subclass_depth[cls]+1)
+ queue.append(subcls)
+ return subclass_depth
- return graph
+
######################################################################
-def uml_class_tree_graph(class_doc, linker, context=None, **options):
+def uml_class_tree_graph(classes, linker, context=None, **options):
"""
Return a `DotGraph` that graphically displays the class hierarchy
for the given class, using UML notation. Options:
+ - exclude: A list of classes that should be excluded
+ - dir: LR|RL|BT requests a left-to-right, right-to-left, or
+ bottom-to- top, drawing. (corresponds to the dot option
+ 'rankdir'
+ - max_subclass_depth: The maximum depth to which subclasses
+ will be drawn.
+ - max_subclasses: A list of ints, specifying how many
+ subclasses should be drawn per class at each level of the
+ graph. E.g., [5,3,1] means draw up to 5 subclasses for the
+ specified classes; up to 3 subsubclasses for each of those (up
+ to) 5 subclasses; and up to 1 subclass for each of those.
- max_attributes
- max_operations
- show_private_vars
- show_magic_vars
- link_attributes
+ - show_signature_defaults
+ - max_signature_width
"""
- nodes = {} # ClassDoc -> DotGraphUmlClassNode
- exclude = options.get('exclude', ())
-
- # Create nodes for class_doc and all its bases.
- for cls in class_doc.mro():
- if cls.pyval is object: continue # don't include `object`.
- if cls in exclude: break # stop if we get to an excluded class.
- if cls == class_doc: color = SELECTED_BG
- else: color = BASECLASS_BG
- nodes[cls] = DotGraphUmlClassNode(cls, linker, context,
- show_inherited_vars=False,
- collapsed=False, bgcolor=color)
+ cls2node = {}
- # Create nodes for all class_doc's subclasses.
- queue = [class_doc]
- for cls in queue:
- if (isinstance(cls, ClassDoc) and
- cls.subclasses not in (None, UNKNOWN)):
- for subcls in cls.subclasses:
- subcls_name = subcls.canonical_name[-1]
- if subcls not in nodes and subcls not in exclude:
- queue.append(subcls)
- nodes[subcls] = DotGraphUmlClassNode(
- subcls, linker, context, collapsed=True,
- bgcolor=SUBCLASS_BG)
-
- # Only show variables in the class where they're defined for
- # *class_doc*.
- mro = class_doc.mro()
- for name, var in class_doc.variables.items():
- i = mro.index(var.container)
- for base in mro[i+1:]:
- if base.pyval is object: continue # don't include `object`.
- overridden_var = base.variables.get(name)
- if overridden_var and overridden_var.container == base:
- try:
- if isinstance(overridden_var.value, RoutineDoc):
- nodes[base].operations.remove(overridden_var)
- else:
- nodes[base].attributes.remove(overridden_var)
- except ValueError:
- pass # var is filtered (eg private or magic)
+ # Draw the basic graph:
+ if isinstance(classes, ClassDoc): classes = [classes]
+ graph = DotGraph('UML class diagram for %s' % name_list(classes, context),
+ body='ranksep=.2\n;nodesep=.3\n')
+ _class_tree_graph(graph, classes, _uml_mknode, _uml_mkedge,
+ linker, context, options, cls2node)
- # Keep track of which nodes are part of the inheritance graph
- # (since link_attributes might add new nodes)
- inheritance_nodes = set(nodes.values())
-
- # Turn attributes into links.
+ # Turn attributes into links (optional):
+ inheritance_nodes = set(graph.nodes)
if options.get('link_attributes', True):
- for node in nodes.values():
- node.link_attributes(nodes)
- # Make sure that none of the new attribute edges break the
- # rank ordering assigned by inheritance.
- for edge in node.edges:
- if edge.end in inheritance_nodes:
- edge['constraint'] = 'False'
-
- # Construct the graph.
- graph = DotGraph('UML class diagram for %s' % class_doc.canonical_name,
- body='ranksep=.2\n;nodesep=.3\n')
- graph.nodes = nodes.values()
-
- # Add inheritance edges.
- for node in inheritance_nodes:
- for base in node.class_doc.bases:
- if base in nodes:
- graph.edges.append(DotGraphEdge(nodes[base], node,
- dir='back', arrowtail='empty',
- headport='body', tailport='body',
- color=INH_LINK_COLOR, weight=100,
- style='bold'))
+ for cls in classes:
+ for base in cls.mro():
+ node = cls2node.get(base)
+ if node is None: continue
+ node.link_attributes(graph, cls2node)
+ # Make sure that none of the new attribute edges break
+ # the rank ordering assigned by inheritance.
+ for edge in node.edges:
+ if edge.end in inheritance_nodes:
+ edge['constraint'] = 'False'
- # And we're done!
return graph
+# A callback to make graph nodes:
+def _uml_mknode(cls, nodetype, linker, context, options):
+ if nodetype == 'subclass':
+ return DotGraphUmlClassNode(
+ cls, linker, context, collapsed=True,
+ bgcolor=SUBCLASS_BG, **options)
+ elif nodetype in ('selected', 'superclass', 'undocumented'):
+ if nodetype == 'selected': bgcolor = SELECTED_BG
+ if nodetype == 'superclass': bgcolor = BASECLASS_BG
+ if nodetype == 'undocumented': bgcolor = UNDOCUMENTED_BG
+ return DotGraphUmlClassNode(
+ cls, linker, context, show_inherited_vars=False,
+ collapsed=False, bgcolor=bgcolor, **options)
+ assert 0, 'bad nodetype'
+
+# A callback to make graph edges:
+def _uml_mkedge(start, end, edgetype, options):
+ if edgetype == 'subclass':
+ return DotGraphEdge(
+ start, end, dir='back', arrowtail='empty',
+ headport='body', tailport='body', color=INH_LINK_COLOR,
+ weight=100, style='bold')
+ if edgetype == 'truncate-subclass':
+ return DotGraphEdge(
+ start, end, dir='back', arrowtail='empty',
+ tailport='body', color=INH_LINK_COLOR,
+ weight=100, style='bold')
+ assert 0, 'bad edgetype'
+
######################################################################
def import_graph(modules, docindex, linker, context=None, **options):
graph = DotGraph('Import Graph', body='ranksep=.3\n;nodesep=.3\n')
@@ -1272,14 +1384,17 @@
:todo: Use different node styles for different subclasses of APIDoc
"""
nodes = {}
- val_docs = sorted(val_docs, key=lambda d:d.canonical_name)
- for i, val_doc in enumerate(val_docs):
- label = val_doc.canonical_name.contextualize(context.canonical_name)
- node = nodes[val_doc] = DotGraphNode(label)
- graph.nodes.append(node)
- specialize_valdoc_node(node, val_doc, context, linker.url_for(val_doc))
+ for val_doc in sorted(val_docs, key=lambda d:d.canonical_name):
+ nodes[val_doc] = mk_valdoc_node(graph, val_doc, linker, context)
+ graph.nodes.append(nodes[val_doc])
return nodes
+def mk_valdoc_node(val_doc, linker, context):
+ label = val_doc.canonical_name.contextualize(context.canonical_name)
+ node = DotGraphNode(label)
+ specialize_valdoc_node(node, val_doc, context, linker.url_for(val_doc))
+ return node
+
NOOP_URL = 'javascript:void(0);'
MODULE_NODE_HTML = '''
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0"
@@ -1307,6 +1422,10 @@
# Set the URL. (Do this even if it points to the page we're
# currently on; otherwise, the tooltip is ignored.)
node['href'] = url or NOOP_URL
+
+ if url is None:
+ node['fillcolor'] = UNDOCUMENTED_BG
+ node['style'] = 'filled'
if isinstance(val_doc, ModuleDoc) and dot_version >= [2]:
node['shape'] = 'plaintext'
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|