Consider the "Custom styling" example in cell 11 in this link from the pytransitions github pages. (tweeked code below)
I would like to add a reset event. When Reset
is triggered from most (but not all) other states the model returns to a known state. In this example that is INITIAL
.
Doing this for a large digraph creates a lot of clutter, omitting it leads to lack of documentation.
The solution is to add a fake transition:
machine.add_transition("Reset", "* ANY_STATE *", "INITIAL")
So a 'fake' global state fixes the issue, and draws properly. It does not care the state is not defined.
However the styling code example uses the state
key of the model but that does not exist as * ANY_STATE *
is fake and not in the list of states.
How to style the dynamicaly added state?
Updated example:
class Model:
pass
model = Model()
transient_states = ['T1', 'T2', 'T3']
target_states = ['G1', 'G2']
fail_states = ['F1', 'F2']
transitions = [['eventA', 'INITIAL', 'T1'], ['eventB', 'INITIAL', 'T2'], ['eventC', 'INITIAL', 'T3'],
['success', ['T1', 'T2'], 'G1'], ['defered', 'T3', 'G2'], ['fallback', ['T1', 'T2'], 'T3'],
['error', ['T1', 'T2'], 'F1'], ['error', 'T3', 'F2']]
machine = GraphMachine(model, states=transient_states + target_states + fail_states,
transitions=transitions, initial='INITIAL', show_conditions=True,
use_pygraphviz=False, ## Jupyter does not work with pygraphviz ##
show_state_attributes=True)
machine.machine_attributes['ratio'] = '0.471'
machine.style_attributes['node']['fail'] = {'fillcolor': 'brown1'}
machine.style_attributes['node']['transient'] = {'fillcolor': 'gold'}
machine.style_attributes['node']['target'] = {'fillcolor': 'chartreuse'}
# **** EXTRA LINE ****
machine.add_transition("Reset", "* ANY_STATE *", "INITIAL")
# As before
model.eventC()
# customize node styling - NOTE: id(model) is the dict key
for s in transient_states:
machine.model_graphs[id(model)].set_node_style(s, 'transient')
for s in target_states:
machine.model_graphs[id(model)].set_node_style(s, 'target')
for s in fail_states:
machine.model_graphs[id(model)].set_node_style(s, 'fail')
# draw the whole graph ...
model.get_graph()
transitions
will just convert the machine's configuration into a graphviz diagram, either via pygraphviz
or graphviz
. As you mentioned wildcard transitions may clutter a diagram. As you also figured out, transitions
will lazily evaluate source and destination states which allows you to add 'virtual' or as you called it 'fake' states. I'd guess at some point you want to actually call 'reset' which would not work with your current configuration. But this is not your question. So let's talk about how to edit the graph. As mentioned before, graphs are just decorated (py)graphviz diagrams under the hood. You can retrieve these diagrams with get_graph
and edit them according to your liking. While graphviz
graphs are rather simplistic, pygraphviz
allows you to query nodes and edges. You need to refer to the documentation of graphviz or pygraphviz to figure out what they can provide and what suits you best.
Adding a node configuration when it is not already defined is rather straight forward. Since you want to use graphviz
this is how its done:
g = model.get_graph()
g.node("* ANY_STATE *", label="* ANY_STATE *", **machine.style_attributes['node']['transient'])
g.draw("machine.png")
This will result in a diagram like this: