python-statemachine icon indicating copy to clipboard operation
python-statemachine copied to clipboard

Plot state machine chart

Open caiolopes opened this issue 6 years ago • 7 comments

Description

It would be nice, mainly for design docs and educational use, to have a way to plot the state machine. It can be something like using the mermaid library.

Example: image

Or something simpler like an ascii plot (http://www.algorithm.co.il/blogs/ascii-plotter/)

caiolopes avatar Aug 19 '19 19:08 caiolopes

It would be nice! But I don't like the idea of adding a required dependency for this. Maybe this should be in a contrib module.

This way, what do you think about doing an intermediate graph representation, in a way that can be consumed easily for specific parsers?

fgmacedo avatar Aug 20 '19 15:08 fgmacedo

Hello @caiolopes, @fgmacedo,

I have implemented this solution:

from graphviz import Digraph

def plot_state_machine(state_machine, name='state_machine'):
	dg = Digraph(comment=name)
	for s in state_machine.states:
		for t in s.transitions:
			dg.edge(t.source.name, t.destinations[0].name)
	dg.render('folder/{}.gv'.format(name), format='png') 

I can make a PR if you want. If not, maybe adding this example in the docs.

claverru avatar Oct 22 '19 08:10 claverru

@claverru nice snippet, thanks. graphviz is not a very good dependency to have as required to this lib, IMHO.

I think we can follow two approaches:

  1. as you suggested, we can just have an example on the README. I would suggest to do it with TrafficLightMachine to be friendly with the reader.
  2. or, having this code as a method that raises an error if graphviz is not available. This is a common approach like with pandas when you read an excel file without the required dependency.

I am really tempted to say that we can do both, since they are not mutual exclusive 🎉

What do you think @fgmacedo?

caiolopes avatar Dec 18 '19 17:12 caiolopes

Thanks for pointing out this idea @caiolopes . Thanks for your snippet @claverru !

Let me share what I'm thinking about.

I'm reading about statecharts since SCXML (Statechart XML), is a W3C standard and it defines a lot of the semantics and specifies how to deal with certain edge cases. A statechart is essentially a state machine with extra-powers, that allows any state to include more machines, in a hierarchical fashion. This is already a feature request at https://github.com/fgmacedo/python-statemachine/issues/246.

With that in mind, my goal to this library may turn to be as much as possible compatible with statecharts but using a different syntax. A pythonic interface to statecharts.

A SCXML representation may be the answer. Or even an xstate compatible object representation to leverage their nice statechart viz tool .

fgmacedo avatar Jan 23 '20 18:01 fgmacedo

Hello @caiolopes, @fgmacedo,

I have implemented this solution:

from graphviz import Digraph

def plot_state_machine(state_machine, name='state_machine'):
	dg = Digraph(comment=name)
	for s in state_machine.states:
		for t in s.transitions:
			dg.edge(t.source.name, t.destinations[0].name)
	dg.render('folder/{}.gv'.format(name), format='png') 

I can make a PR if you want. If not, maybe adding this example in the docs.

@claverru thanks for sharing. Is there a way to add the name of the transition ?

hwerbi avatar Jun 11 '20 15:06 hwerbi

Hello @hwerbi !

I think this should work (over the former snippet):

def plot_state_machine(state_machine, name='state_machine'):
	dg = Digraph(comment=name)
	for s in state_machine.states:
		for t in s.transitions:
			dg.edge(t.source.name, t.destinations[0].name, label=t.identifier)
	dg.render('folder/{}.gv'.format(name), format='png') 

I just added label=t.identifier when calling dg.edge.

Cheers!

EDIT:

Whole script:

from graphviz import Digraph
from statemachine import StateMachine, State


class TrafficLightMachine(StateMachine):
    green = State('Green', initial=True)
    yellow = State('Yellow')
    red = State('Red')
    slowdown = green.to(yellow)
    stop = yellow.to(red)
    go = red.to(green)


def plot_state_machine(state_machine, name='state_machine'):
    dg = Digraph(comment=name)
    for s in state_machine.states:
        for t in s.transitions:
            dg.edge(t.source.name, t.destinations[0].name, label=t.identifier)
    dg.render('folder/{}.gv'.format(name), format='png') 


if __name__ == '__main__':
    st = TrafficLightMachine()
    plot_state_machine(st, 'my_state_machine')

claverru avatar Jun 12 '20 08:06 claverru

I packaged the plot function into a program that plots the statemachine from a separate module, creating the graph folder if not present and writing the plots to ./graphs/modname.machinename.png and .gv

https://github.com/slowrunner/Carl/blob/master/Examples/statemachine/plotsm.py

usage: plotsm.py [-h] -m MODULE -sm STATEMACHINE
optional arguments:
  -h, --help            show this help message and exit
  -m MODULE, --module MODULE
                        module name to import from
  -sm STATEMACHINE, --statemachine STATEMACHINE
                        State Machine Type Name
./plotsm.py -m stoplight -sm TraficLightMachine
will output ./graphs/stoplight.TrafficLightMachine.gv and 
./graphs/stoplight.TrafficLightMachine.png  
for the module stoplight.py containing the class TrafficLightMachine(StateMachine)

slowrunner avatar Jun 01 '22 02:06 slowrunner

This issue seems to have stalled for while. Think about closing it.

brunolnetto avatar Dec 10 '22 21:12 brunolnetto

Hello! Nice updates! I've just merged support for diagrams at https://github.com/fgmacedo/python-statemachine/pull/300

fgmacedo avatar Jan 01 '23 17:01 fgmacedo

You can see the preview on the develop branch: https://python-statemachine.readthedocs.io/en/develop/diagram.html

I'm planning a release soon.

fgmacedo avatar Jan 01 '23 17:01 fgmacedo