-
Notifications
You must be signed in to change notification settings - Fork 188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature visualisation #17
base: master
Are you sure you want to change the base?
Changes from all commits
7e8f25d
6b44f2c
9430114
b884449
a11c2a9
66397a6
2021bde
273f829
d008550
de32394
2741b13
67625f5
31d3fc2
542540c
56b12ac
ccdb199
7a61af4
1a472a3
18ce6e2
09dde6a
5b5a2cc
a268b10
6aa2471
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<?php | ||
|
||
require_once __DIR__ . '/../vendor/autoload.php'; | ||
|
||
use Finite\Loader\ArrayLoader; | ||
use Finite\State\StateInterface; | ||
use Finite\StatefulInterface; | ||
use Finite\StateMachine\StateMachine; | ||
use Finite\Visualisation\Configuration; | ||
use Finite\Visualisation\Graphviz; | ||
|
||
/** | ||
* Example implementation | ||
*/ | ||
class Document implements StatefulInterface | ||
{ | ||
private $state; | ||
|
||
public function getFiniteState() | ||
{ | ||
return $this->state; | ||
} | ||
|
||
public function setFiniteState($state) | ||
{ | ||
$this->state = $state; | ||
} | ||
} | ||
|
||
// Configure your graph | ||
$loader = new ArrayLoader([ | ||
'class' => 'Document', | ||
'states' => [ | ||
'draft' => [ | ||
'type' => StateInterface::TYPE_INITIAL, | ||
'properties' => ['deletable' => true, 'editable' => true], | ||
], | ||
'proposed' => [ | ||
'type' => StateInterface::TYPE_NORMAL, | ||
'properties' => [], | ||
], | ||
'accepted' => [ | ||
'type' => StateInterface::TYPE_NORMAL, | ||
'properties' => ['printable' => true], | ||
], | ||
'published' => [ | ||
'type' => StateInterface::TYPE_FINAL, | ||
'properties' => ['printable' => true], | ||
] | ||
], | ||
'transitions' => [ | ||
'propose' => ['from' => ['draft'], 'to' => 'proposed'], | ||
'accept' => ['from' => ['proposed'], 'to' => 'accepted'], | ||
'reject' => ['from' => ['proposed'], 'to' => 'draft'], | ||
'publish' => ['from' => ['accepted'], 'to' => 'published'], | ||
'cheat' => ['from' => ['draft'], 'to' => 'published'], | ||
], | ||
]); | ||
|
||
$document = new Document; | ||
$stateMachine = new StateMachine($document); | ||
$loader->load($stateMachine); | ||
$stateMachine->initialize(); | ||
|
||
$config = new Configuration(__DIR__ . '/rendered-graph.png', true, 'red'); | ||
$renderer = new Graphviz($config); | ||
$renderer->render($stateMachine); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<?php | ||
|
||
namespace Finite\Visualisation; | ||
|
||
/** | ||
* Configuration value object. | ||
* | ||
* @author Daniel Pozzi <bonndan76@googlemail.com> | ||
*/ | ||
class Configuration | ||
{ | ||
private $renderProperties; | ||
private $markCurrentState; | ||
|
||
/** | ||
* Constructor. | ||
* | ||
* @param boolean $renderProperties flag | ||
* @param string|null $markCurrentState fillcolor | ||
*/ | ||
public function __construct($renderProperties = false, $markCurrentState = null) | ||
{ | ||
$this->renderProperties = (bool) $renderProperties; | ||
$this->markCurrentState = $markCurrentState; | ||
} | ||
|
||
/** | ||
* Returns whether state properties shall be rendered or not. | ||
* | ||
* @return boolean | ||
*/ | ||
public function renderProperties() | ||
{ | ||
return $this->renderProperties; | ||
} | ||
|
||
/** | ||
* Returns in which color the current state shall be rendered (fillcolor) or null. | ||
* | ||
* @return string|null | ||
*/ | ||
public function markCurrentState() | ||
{ | ||
return $this->markCurrentState; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?php | ||
namespace Finite\Visualisation; | ||
|
||
/** | ||
* Visualisation exception. | ||
* | ||
* @author Daniel Pozzi <bonndan76@googlemail.com> | ||
*/ | ||
class Exception extends \Finite\Exception\Exception | ||
{ | ||
const CODE_DOT_ERROR = 1; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
<?php | ||
|
||
namespace Finite\Visualisation; | ||
|
||
use Finite\StateMachine\StateMachineInterface; | ||
use Finite\State\StateInterface; | ||
use Alom\Graphviz\Digraph; | ||
|
||
/** | ||
* Visualisation of a State machine using Graphviz | ||
* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove line break |
||
* This class geneates dot source code which can be rendered | ||
* by graphviz. Pass a configuration object to control how | ||
* the nodes are rendered. | ||
* | ||
* @link http://www.graphviz.org/Gallery/directed/fsm.gv.txt | ||
* @author Daniel Pozzi <bonndan76@googlemail.com> | ||
*/ | ||
class Graphviz implements VisualisationInterface | ||
{ | ||
/** | ||
* the graphviz graph representation | ||
* | ||
* @var \Alom\Graphviz\Digraph | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove first \ |
||
*/ | ||
private $graph; | ||
|
||
/** | ||
* visualisation options | ||
* | ||
* @var \Finite\Visualisation\Configuration | ||
*/ | ||
private $configuration; | ||
|
||
/** | ||
* Constructor. | ||
* | ||
* @param \Finite\Visualisation\Configuration $config | ||
*/ | ||
public function __construct(Configuration $config = null) | ||
{ | ||
if (null === $config) { | ||
$config = new Configuration(); | ||
} | ||
$this->configuration = $config; | ||
} | ||
|
||
/** | ||
* Renders the state machine. | ||
* | ||
* @param \Finite\StateMachine\StateMachineInterface $stateMachine | ||
* @param string $target | ||
* @throws Exception | ||
*/ | ||
public function render(StateMachineInterface $stateMachine) | ||
{ | ||
$this->graph = new Digraph('state_machine'); | ||
$this->addNodes($stateMachine); | ||
$this->addEdges($stateMachine); | ||
$this->graph->end(); | ||
|
||
return $this->graph->render(); | ||
} | ||
|
||
/** | ||
* Adds the states as nodes. | ||
* | ||
* @param \Finite\StateMachine\StateMachineInterface $stateMachine | ||
*/ | ||
private function addNodes(StateMachineInterface $stateMachine) | ||
{ | ||
$states = $stateMachine->getStates(); | ||
foreach ($states as $name) { | ||
$this->graph->beginNode($name, $this->getNodeAttributes($stateMachine, $name))->end(); | ||
} | ||
} | ||
|
||
/** | ||
* Returns the node attributes. | ||
* | ||
* @param \Finite\StateMachine\StateMachineInterface $stateMachine | ||
* @param string $name | ||
* @return array | ||
*/ | ||
private function getNodeAttributes(StateMachineInterface $stateMachine, $name) | ||
{ | ||
$state = $stateMachine->getState($name); /* @var $state \Finite\State\StateInterface */ | ||
$data = array( | ||
'shape' => $state->getType() != StateInterface::TYPE_NORMAL ? 'doublecircle' : 'circle', | ||
'label' => $this->getNodeLabel($state), | ||
); | ||
if ($stateMachine->getCurrentState() == $state && $this->configuration->markCurrentState()) { | ||
$data['fillcolor'] = $this->configuration->markCurrentState(); | ||
$data['style'] = 'filled'; | ||
} | ||
|
||
return $data; | ||
} | ||
|
||
/** | ||
* Returns the node label. | ||
* | ||
* @param \Finite\State\StateInterface $state | ||
* @return string | ||
*/ | ||
private function getNodeLabel(StateInterface $state) | ||
{ | ||
$id = $state->getName(); | ||
$props = $state->getProperties(); | ||
if (count($props) > 0 && $this->configuration->renderProperties()) { | ||
foreach (array_keys($props) as $prop) { | ||
$id .= "\\n* " . $prop; | ||
} | ||
} | ||
|
||
return $id; | ||
} | ||
|
||
/** | ||
* Adds all transitions as edges. | ||
* | ||
* @param \Finite\StateMachine\StateMachineInterface $stateMachine | ||
*/ | ||
private function addEdges(StateMachineInterface $stateMachine) | ||
{ | ||
$states = $stateMachine->getStates(); | ||
foreach ($states as $name) { | ||
$state = $stateMachine->getState($name); | ||
/* @var $state \Finite\State\StateInterface */ | ||
$transitions = $state->getTransitions(); | ||
foreach ($transitions as $name) { | ||
$trans = $stateMachine->getTransition($name); | ||
/* @var $trans Finite\Transition\TransitionInterface */ | ||
$this->graph->beginEdge( | ||
array($state->getName(), $trans->getState()), array('label' => $trans->getName())) | ||
->end(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Weird indent |
||
} | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
namespace Finite\Visualisation; | ||
|
||
use Finite\StateMachine\StateMachineInterface; | ||
|
||
/** | ||
* Interface for state machine visualisation strategies. | ||
* | ||
* @author Daniel Pozzi <bonndan76@googlemail.com> | ||
*/ | ||
interface VisualisationInterface | ||
{ | ||
public function render(StateMachineInterface $stateMachine); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should require dot if that is an executable no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've thought about that, too, Imho "suggesting" it is better, since you're not forced to install dot if you don't need it. alom/graphviz doesn't require it, too.