1

I've been experimenting with the HierarchicalGraphMachine class to help visualise the machine structures as I edit them.

from transitions.extensions import HierarchicalGraphMachine as Machine

count_states = ['1', '2', '3', 'done']
count_trans = [
    ['increase', '1', '2'],
    ['increase', '2', '3'],
    ['decrease', '3', '2'],
    ['decrease', '2', '1'],
    ['done', '3', 'done'],
    ['reset', '*', '1']
]
counter = Machine(states=count_states, transitions=count_trans, initial='1')

states = ['waiting', 'collecting', {'name': 'counting', 'children': counter, 'initial': '1'}]

transitions = [
    ['collect', '*', 'collecting'],
    ['wait', '*', 'waiting'],
    ['count', 'collecting', 'counting']
]

collector = Machine(states=states, transitions=transitions, initial='waiting')
collector.get_graph(show_roi=False).draw('count1.png', prog='dot')

This generates the expected graphic showing both the parent and nested states in full (I'm not yet authorised to upload the graphics). Is there a way to generate a the full parent state machine graphic without expanding the nested states? For example reducing the nested states to an empty box.

I've tried "show_roi=True", but this only shows the current transition event, and removes all other states.

Bilby42
  • 11
  • 1

1 Answers1

0

Depending on whether you use the pygraphviz (default in 0.8.8 and prior) or graphviz backend, get_graph may return a pygraphiv.AGraph object or a custom transitions.Graph. An AGraph is easier to manipulate while the second is basically the pure graph notation in dot. However, you can manipulate both according to your needs. For instance, you could filter edges and nodes from an AGraph and rebuild a 'flat' version of it:

# your code here ...

collector.collect()

graph = collector.get_graph()

# iterate over all edges; We know that parent and child states are connected
# with an underscore. We just collect the root element of each source
# and target element of each edge. Furthermore, we collect the edge color,
# and the label which is stored either in 'label', 'taillabel' or 'headlabel' 
new_edges = [(edge[0].split('_')[0],
              edge[1].split('_')[0],
              edge.attr['color'],
              edge.attr['label']
              or edge.attr['taillabel']
              or edge.attr['headlabel']) for edge in graph.edges()]

# States with children are noted as subgraphs. We collect their name and their
# current color.
new_nodes = [(sgraph.graph_attr['label'], sgraph.graph_attr['color'])
             for sgraph in graph.subgraphs()]
# We add all states that have no children and also do not contain an
# underscore in their name. An underscore would suggest that this node/state
# is a child/substate.
new_nodes += [(node.name, node.attr['color'])
              for node in graph.nodes() if '_' not in node.name]

# remove everything from the graph obeject
graph.clear()

# and add nodes and edges again
for name, color in new_nodes:
    graph.add_node(name, color=color)

for start, target, color, label in new_edges:
    if label:
        graph.add_edge(start, target, color=color, label=label)

graph.draw('agraph.png', prog='dot')

This results in the following graph:

HSM Graph

You see that I also collected the edge and node color to visualize the last transition but graph.clear() removed all the 'default' styling attributes. They could be copied and restored as well or we could only remove nodes, edges and subgraphs. This depends on how much you are willing to mess with (py)graphviz.

aleneum
  • 2,083
  • 12
  • 29