Source code for crc_diagram.renders.dot
# coding: utf-8
from graphviz import Digraph
from crc_diagram.utils import split_by_extension
from crc_diagram.log import logger
[docs]class DotRender(object):
"""
Render CRC cards using DOT language.
:param list crc_cards: A list of :py:data:`CRC` objects.
:param str format: The format used to save the diagram. Default is png.
:param dict graph_data: graph attributes, see documentation `here`_.
.. _here: http://www.graphviz.org/doc/info/attrs.html
Example::
from crc_diagram import py_to_crc
# getting crcs from a file
crcs = py_to_crc('html_to_markdown.py')
DotRender(crcs).render('html_to_markdown.png')
This will save to html_to_markdown.png the diagram
You can open it passing :py:data:`view=True` to :py:data:`render` method::
DotRender(crcs).render('html_to_markdown.png', view=True)
By default :py:class:`DotRender` uses a directed graph.
You can customize the graph passing a dictionary to :py:data:`graph_data=graph_data`::
# create a blue graph
graph_data = {
'fillcolor': 'blue',
'style': 'filled'
}
DotRender(crcs, graph_data=graph_data).render(
'html_to_markdown.png',
view=True
)
Rendered diagram:
.. image:: _images/blue_card.png
|
"""
def __init__(self, crc_cards, format='png', graph_data=None):
self.format = format
self.crc_cards = crc_cards
self.graph_data = {'shape': 'record'}
self._edges = [] # keep track of the edges
if graph_data is not None:
self.graph_data.update(graph_data)
self.graph = Digraph(
comment='CRC Diagram',
node_attr=self.graph_data,
format=self.format
)
self._init()
@property
def source(self):
"""
Returns the DOT source of the :py:data:`self.graph`
"""
return self.graph.source
@staticmethod
def format(texts, format_spec='\l'):
return format_spec.join(texts)
[docs] def get_edge_direction(self, collaborator, crc_name):
"""
Check if a collaborator has bidirectional reference::
A -> B
B -> A
:param str collaborator: The collaborator to check against.
:param str crc_name: The name of the CRC to check against.
:return direction: both if the collaborator has bidirectional reference
or single otherwise.
"""
direction = 'single'
for crc_card in self.crc_cards:
if collaborator == crc_card.name and crc_name in crc_card.collaborators:
direction = 'both'
return direction
[docs] def edge_already_added(self, crc_name, collaborator):
"""
Return True if a edge have already been added for that collaborator
return False otherwise.
:param str crc_name: The CRC name.
:param str collaborator: The CRC collaborator.
"""
return (
(crc_name, collaborator) in self._edges or
(collaborator, crc_name) in self._edges
)
[docs] def create_edges(self, crc):
"""
Iterate over :py:data:`crc.collaborators` and create a edge
for each one if the edge hasn't already been created.
:param CRC crc: A CRC instance.
"""
for collaborator in crc.collaborators:
direction = self.get_edge_direction(collaborator, crc.name)
if not self.edge_already_added(crc.name, collaborator):
self.graph.edge(crc.name, collaborator, dir=direction)
self._edges.append((crc.name, collaborator))
def _init(self):
"""
Iterate over :py:data:`self.crc_cards` and create a graph node
for each one,then calls :py:data:`create_edges` to create the edges
for each node.
"""
for index, crc in enumerate(self.crc_cards):
crc_name = crc.name
if crc.collaborators:
collaborators = DotRender.format(crc.collaborators)
else:
collaborators = "NO-COLLABORATORS"
if crc.responsibilities:
responsibilities = DotRender.format(crc.responsibilities)
else:
responsibilities = "NO-RESPONSIBILITIES"
self.graph.node(crc_name, "{%s|{%s|%s}}" % (
crc_name,
collaborators,
responsibilities
))
self.create_edges(crc)
[docs] def render(self, filename, view=False):
"""
Render :py:data:`self.graph`.
:param str filename: The filename where the diagram will be saved.
:param bool view: Set to True to open the rendered diagram.
"""
filename, extension = split_by_extension(filename)
if extension is not None and extension != self.format:
logger.warn(
'File extension is different from --format. '
'The file name is {filename}.{extension} '
'but it will be saved as {format}'.format(
filename=filename,
extension=extension,
format=self.format
)
)
self.graph.render(filename, view=view)