From 7f6a357bc210440a965de2b4734746c3876e7708 Mon Sep 17 00:00:00 2001 From: Mofasa Date: Tue, 7 Apr 2020 22:25:20 +0800 Subject: [PATCH] Fixed subgroup and add rank `subgroup` should include `edges` and `rank` --- src/GraphViz.php | 140 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 97 insertions(+), 43 deletions(-) diff --git a/src/GraphViz.php b/src/GraphViz.php index 9d6d8a0..1d2da95 100644 --- a/src/GraphViz.php +++ b/src/GraphViz.php @@ -42,6 +42,8 @@ class GraphViz private $attributeGroup = 'group'; private $attributeBalance = 'balance'; + private $ranks = array(); + const DELAY_OPEN = 2.0; const EOL = PHP_EOL; @@ -99,6 +101,47 @@ public function setFormat($format) return $this; } + /** + * set rank for vertex + * + * @param int $rank + * @param string $vertex vertex have the same rank + * @param string $type "same", "min", "source", "max", "sink" + * @param string $group vertex in the subgraph + * @return GraphViz $this (chainable) + */ + public function setRank($rank, $vertex, $type = 'same', $group = null) + { + $id = \spl_object_hash($vertex); + if (isset($this->ranks[$group][$type][$rank])) { + $this->ranks[$group][$type][$rank][$id] = $id; + } else if (isset($this->ranks[$group][$type])) { + $this->ranks[$group][$type][$rank] = array($id => $id); + } else if (isset($this->ranks[$group])) { + $this->ranks[$group][$type] = array($rank => array($id => $id)); + } else { + $this->ranks[$group] = array($type => array($rank => array($id => $id))); + } + return $this; + } + + /** + * set rank for vertexes + * + * @param int $rank + * @param string $vertexes vertexs have the same rank + * @param string $type "same", "min", "source", "max", "sink" + * @param string $group vertex in the subgraph + * @return GraphViz $this (chainable) + */ + public function setRanks($rank, array $vertexes, $type = 'same', $group = null) + { + foreach ($vertexes as $vertex) { + $this->setRank($rank, $vertex, $type, $group); + } + return $this; + } + /** * create and display image for this graph * @@ -247,7 +290,7 @@ public function createScript(Graph $graph) $name = $this->escape($name) . ' '; } - $script = ($directed ? 'di':'') . 'graph ' . $name . '{' . self::EOL; + $script = ($directed ? 'digraph ' : 'graph ') . $name . '{' . self::EOL; // add global attributes $globals = array( @@ -266,78 +309,89 @@ public function createScript(Graph $graph) $tid = 0; $vids = array(); - $groups = array(); + $groupVertexs = array(); foreach ($graph->getVertices() as $vertex) { - assert($vertex instanceof Vertex); - $groups[$vertex->getAttribute('group', 0)][] = $vertex; + //assert($vertex instanceof Vertex); + $groupVertexs[$vertex->getAttribute('group')][] = $vertex; $id = $vertex->getAttribute('id'); if ($id === null) { $id = ++$tid; + } else { + $id = $this->escape($id); } $vids[\spl_object_hash($vertex)] = $id; } - // only cluster vertices into groups if there are at least 2 different groups - if (count($groups) > 1) { - $indent = str_repeat($this->formatIndent, 2); - $gid = 0; - // put each group of vertices in a separate subgraph cluster - foreach ($groups as $group => $vertices) { - $script .= $this->formatIndent . 'subgraph cluster_' . $gid++ . ' {' . self::EOL . - $indent . 'label = ' . $this->escape($group) . self::EOL; - foreach ($vertices as $vertex) { - $vid = $vids[\spl_object_hash($vertex)]; - $layout = $this->getLayoutVertex($vertex, $vid); - - $script .= $indent . $this->escape($vid); - if ($layout) { - $script .= ' ' . $this->escapeAttributes($layout); - } - $script .= self::EOL; - } - $script .= ' }' . self::EOL; + $groupEdges = array(); + // add all edges as directed edges + foreach ($graph->getEdges() as $edge) { + $groupEdges[$edge->getAttribute('group')][] = $edge; + } + + $gid = 0; + $edgeop = $directed ? ' -> ' : ' -- '; + // put each group of vertices in a separate subgraph cluster + foreach ($groupVertexs as $group => $vertices) { + if ($group !== '') { + $indent = str_repeat($this->formatIndent, 2); + $script .= $this->formatIndent . 'subgraph cluster_' . $gid++ . ' {' . + self::EOL . $indent . 'label = ' . $this->escape($group) . self::EOL; + } else { + $indent = $this->formatIndent; } - } else { // explicitly add all isolated vertices (vertices with no edges) and vertices with special layout set // other vertices wil be added automatically due to below edge definitions - foreach ($graph->getVertices() as $vertex){ + foreach ($vertices as $vertex){ $vid = $vids[\spl_object_hash($vertex)]; $layout = $this->getLayoutVertex($vertex, $vid); if ($layout || $vertex->getEdges()->isEmpty()) { - $script .= $this->formatIndent . $this->escape($vid); + $script .= $indent . $vid; if ($layout) { $script .= ' ' . $this->escapeAttributes($layout); } $script .= self::EOL; } } - } + if (isset($groupEdges[$group])) { + foreach ($groupEdges[$group] as $edge) { + $both = $edge->getVertices()->getVector(); + $startVertex = $both[0]; + $targetVertex = $both[1]; - $edgeop = $directed ? ' -> ' : ' -- '; - - // add all edges as directed edges - foreach ($graph->getEdges() as $currentEdge) { - $both = $currentEdge->getVertices()->getVector(); - $currentStartVertex = $both[0]; - $currentTargetVertex = $both[1]; + $script .= $indent . $vids[\spl_object_hash($startVertex)] . $edgeop . $vids[\spl_object_hash($targetVertex)]; - $script .= $this->formatIndent . $this->escape($vids[\spl_object_hash($currentStartVertex)]) . $edgeop . $this->escape($vids[\spl_object_hash($currentTargetVertex)]); + $layout = $this->getLayoutEdge($edge); - $layout = $this->getLayoutEdge($currentEdge); + // this edge is not a loop and also points to the opposite direction => this is actually an undirected edge + if ($directed && $startVertex !== $targetVertex && $edge->isConnection($targetVertex, $startVertex)) { + $layout['dir'] = 'none'; + } + if ($layout) { + $script .= ' ' . $this->escapeAttributes($layout); + } - // this edge is not a loop and also points to the opposite direction => this is actually an undirected edge - if ($directed && $currentStartVertex !== $currentTargetVertex && $currentEdge->isConnection($currentTargetVertex, $currentStartVertex)) { - $layout['dir'] = 'none'; + $script .= self::EOL; + } } - if ($layout) { - $script .= ' ' . $this->escapeAttributes($layout); + if (isset($this->ranks[$group])) { + foreach ($this->ranks[$group] as $type => $ranks) { + foreach ($ranks as $vers) { + $ids = []; + foreach ($vers as $id) { + $ids[] = $vids[$id]; + } + $script .= $indent . '{rank=' . $type . ' ' . implode(' ', $ids) . '}' . self::EOL; + } + } + } + if ($group !== '') { + $script .= ' }' . self::EOL; } - - $script .= self::EOL; } + $script .= '}' . self::EOL; return $script;