diff --git a/lib/Fhaculty/Graph/Algorithm/MaximumMatching/Flow.php b/lib/Fhaculty/Graph/Algorithm/MaximumMatching/Flow.php index 4d97a36e..d493ecf9 100644 --- a/lib/Fhaculty/Graph/Algorithm/MaximumMatching/Flow.php +++ b/lib/Fhaculty/Graph/Algorithm/MaximumMatching/Flow.php @@ -30,8 +30,8 @@ public function getEdges() // create temporary flow graph with supersource and supersink $graphFlow = $this->graph->createGraphCloneEdgeless(); - $superSource = $graphFlow->createVertex()->setLayoutAttribute('label', 's*'); - $superSink = $graphFlow->createVertex()->setLayoutAttribute('label', 't*'); + $superSource = $graphFlow->createVertex(); + $superSink = $graphFlow->createVertex(); $groups = $alg->getGroups(); $groupA = $groups[0]; diff --git a/lib/Fhaculty/Graph/Algorithm/MinimumCostFlow/CycleCanceling.php b/lib/Fhaculty/Graph/Algorithm/MinimumCostFlow/CycleCanceling.php index 116875c2..f3cdb252 100644 --- a/lib/Fhaculty/Graph/Algorithm/MinimumCostFlow/CycleCanceling.php +++ b/lib/Fhaculty/Graph/Algorithm/MinimumCostFlow/CycleCanceling.php @@ -22,8 +22,8 @@ public function createGraph() // create resulting graph with supersource and supersink $resultGraph = $this->graph->createGraphClone(); - $superSource = $resultGraph->createVertex()->setLayoutAttribute('label', 's*'); - $superSink = $resultGraph->createVertex()->setLayoutAttribute('label', 't*'); + $superSource = $resultGraph->createVertex(); + $superSink = $resultGraph->createVertex(); $sumBalance = 0; diff --git a/lib/Fhaculty/Graph/Edge/Base.php b/lib/Fhaculty/Graph/Edge/Base.php index 1d39ed75..aa5458d5 100644 --- a/lib/Fhaculty/Graph/Edge/Base.php +++ b/lib/Fhaculty/Graph/Edge/Base.php @@ -2,7 +2,6 @@ namespace Fhaculty\Graph\Edge; -use Fhaculty\Graph\Layoutable; use Fhaculty\Graph\Vertex; use Fhaculty\Graph\Set\Edges; use Fhaculty\Graph\Set\Vertices; @@ -13,8 +12,10 @@ use Fhaculty\Graph\Exception\UnderflowException; use Fhaculty\Graph\Exception\InvalidArgumentException; use Fhaculty\Graph\Exception\BadMethodCallException; +use Fhaculty\Graph\Renderer\Layout; +use Fhaculty\Graph\Renderer\LayoutAggregate; -abstract class Base extends Layoutable implements VerticesAggregate +abstract class Base implements VerticesAggregate, LayoutAggregate { /** * weight of this edge @@ -40,6 +41,8 @@ abstract class Base extends Layoutable implements VerticesAggregate */ protected $flow = NULL; + protected $layout = null; + /** * get Vertices that are a target of this edge * @@ -289,4 +292,13 @@ private function __clone() throw new BadMethodCallException(); // @codeCoverageIgnoreEnd } + + public function getLayout() + { + if ($this->layout === null) { + $this->layout = new Layout(); + } + + return $this->layout; + } } diff --git a/lib/Fhaculty/Graph/Exporter/Dot.php b/lib/Fhaculty/Graph/Exporter/Dot.php index 124754a8..8b9ca2d8 100644 --- a/lib/Fhaculty/Graph/Exporter/Dot.php +++ b/lib/Fhaculty/Graph/Exporter/Dot.php @@ -2,8 +2,8 @@ namespace Fhaculty\Graph\Exporter; -use Fhaculty\Graph\GraphViz; use Fhaculty\Graph\Graph; +use Fhaculty\Graph\Renderer\GraphViz; class Dot implements ExporterInterface { diff --git a/lib/Fhaculty/Graph/Exporter/Image.php b/lib/Fhaculty/Graph/Exporter/Image.php index c1e372d4..a6f1f177 100644 --- a/lib/Fhaculty/Graph/Exporter/Image.php +++ b/lib/Fhaculty/Graph/Exporter/Image.php @@ -2,8 +2,8 @@ namespace Fhaculty\Graph\Exporter; -use Fhaculty\Graph\GraphViz; use Fhaculty\Graph\Graph; +use Fhaculty\Graph\Renderer\GraphViz; class Image implements ExporterInterface { diff --git a/lib/Fhaculty/Graph/Graph.php b/lib/Fhaculty/Graph/Graph.php index 1434bca7..4c492d24 100644 --- a/lib/Fhaculty/Graph/Graph.php +++ b/lib/Fhaculty/Graph/Graph.php @@ -20,9 +20,11 @@ use Fhaculty\Graph\Set\Vertices; use Fhaculty\Graph\Set\VerticesMap; use Fhaculty\Graph\Set\Edges; +use Fhaculty\Graph\Renderer\LayoutAggregate; +use Fhaculty\Graph\Renderer\Layout; use Fhaculty\Graph\Set\DualAggregate; -class Graph implements DualAggregate +class Graph implements DualAggregate, LayoutAggregate { /** * @var ExporterInterface|null @@ -36,6 +38,10 @@ class Graph implements DualAggregate protected $edgesStorage = array(); protected $edges; + protected $layout = null; + protected $layoutVertex = null; + protected $layoutEdge = null; + public function __construct() { $this->vertices = VerticesMap::factoryArrayReference($this->verticesStorage); @@ -107,7 +113,7 @@ public function createVertexClone(Vertex $originalVertex) } $newVertex = new Vertex($id, $this); // TODO: properly set attributes of vertex - $newVertex->setLayout($originalVertex->getLayout()); + $newVertex->getLayout()->setLayout($originalVertex->getLayout()); $newVertex->setBalance($originalVertex->getBalance()); $newVertex->setGroup($originalVertex->getGroup()); $this->verticesStorage[$id] = $newVertex; @@ -125,7 +131,7 @@ public function createVertexClone(Vertex $originalVertex) public function createGraphCloneEdgeless() { $graph = new Graph(); -// $graph->setLayout($this->getLayout()); + $graph->getLayout()->setLayout($this->getLayout()); // TODO: set additional graph attributes foreach ($this->getVertices() as $originalVertex) { $vertex = $graph->createVertexClone($originalVertex); @@ -244,7 +250,7 @@ private function createEdgeCloneInternal(Edge $originalEdge, $ia, $ib) $newEdge = $a->createEdge($b); } // TODO: copy edge attributes - $newEdge->setLayout($originalEdge->getLayout()); + $newEdge->getLayout()->setLayout($originalEdge->getLayout()); $newEdge->setWeight($originalEdge->getWeight()); $newEdge->setFlow($originalEdge->getFlow()); $newEdge->setCapacity($originalEdge->getCapacity()); @@ -489,7 +495,40 @@ public function __toString() return $this->getExporter()->getOutput($this); } - public function getLayout(){ - return array(); + public function getLayout() + { + if ($this->layout === null) { + $this->layout = new Layout(); + } + + return $this->layout; + } + + /** + * get the global default layout for all vertices + * + * @return Layout + */ + public function getLayoutVertexDefault() + { + if ($this->layoutVertex === null) { + $this->layoutVertex = new Layout(); + } + + return $this->layoutVertex; + } + + /** + * get the global default layout for all edges + * + * @return Layout + */ + public function getLayoutEdgeDefault() + { + if ($this->layoutEdge === null) { + $this->layoutEdge = new Layout(); + } + + return $this->layoutEdge; } } diff --git a/lib/Fhaculty/Graph/GraphViz.php b/lib/Fhaculty/Graph/Renderer/GraphViz.php similarity index 88% rename from lib/Fhaculty/Graph/GraphViz.php rename to lib/Fhaculty/Graph/Renderer/GraphViz.php index a7a1b7d3..88948f88 100644 --- a/lib/Fhaculty/Graph/GraphViz.php +++ b/lib/Fhaculty/Graph/Renderer/GraphViz.php @@ -1,14 +1,16 @@ $value) { - if ($value === NULL) { - unset($old[$key]); - } else { - $old[$key] = $value; - } - } - } - } - - public function setLayout($where, $layout, $value = NULL) - { - if (!is_array($where)) { - $where = array($where); - } - if (func_num_args() > 2) { - $layout = array($layout => $value); - } - foreach ($where as $where) { - if ($where === self::LAYOUT_GRAPH) { - $this->graph->setLayout($layout, $value); - } elseif ($where === self::LAYOUT_EDGE) { - $this->mergeLayout($this->layoutEdge, $layout); - } elseif ($where === self::LAYOUT_VERTEX) { - $this->mergeLayout($this->layoutVertex, $layout); - } else { - throw new InvalidArgumentException('Invalid layout identifier'); - } - } - - return $this; - } - - // end - /** * create image file data contents for this graph * @@ -285,15 +240,19 @@ public function createScript() $script = ($directed ? 'di':'') . 'graph G {' . self::EOL; // add global attributes - $layout = $this->graph->getLayout(); + $layout = $this->graph->getLayout()->getAttributes(); if ($layout) { $script .= $this->formatIndent . 'graph ' . $this->escapeAttributes($layout) . self::EOL; } - if ($this->layoutVertex) { - $script .= $this->formatIndent . 'node ' . $this->escapeAttributes($this->layoutVertex) . self::EOL; + + $layout = $this->graph->getLayoutVertexDefault()->getAttributes(); + if ($layout) { + $script .= $this->formatIndent . 'node ' . $this->escapeAttributes($layout) . self::EOL; } - if ($this->layoutEdge) { - $script .= $this->formatIndent . 'edge ' . $this->escapeAttributes($this->layoutEdge) . self::EOL; + + $layout = $this->graph->getLayoutEdgeDefault()->getAttributes(); + if ($layout) { + $script .= $this->formatIndent . 'edge ' . $this->escapeAttributes($layout) . self::EOL; } $alg = new Groups($this->graph); @@ -428,7 +387,7 @@ public static function raw($string) protected function getLayoutVertex(Vertex $vertex) { - $layout = $vertex->getLayout(); + $layout = $vertex->getLayout()->getAttributes(); $balance = $vertex->getBalance(); if($balance !== NULL){ @@ -446,7 +405,7 @@ protected function getLayoutVertex(Vertex $vertex) protected function getLayoutEdge(Edge $edge) { - $layout = $edge->getLayout(); + $layout = $edge->getLayout()->getAttributes(); // use flow/capacity/weight as edge label $label = NULL; diff --git a/lib/Fhaculty/Graph/Layoutable.php b/lib/Fhaculty/Graph/Renderer/Layout.php similarity index 70% rename from lib/Fhaculty/Graph/Layoutable.php rename to lib/Fhaculty/Graph/Renderer/Layout.php index 4d323370..9de00d46 100644 --- a/lib/Fhaculty/Graph/Layoutable.php +++ b/lib/Fhaculty/Graph/Renderer/Layout.php @@ -1,10 +1,11 @@ layout; } @@ -28,9 +29,9 @@ public function getLayout() * * @param array $attributes * @return self $this (chainable) - * @see Layoutable::setLayoutAttribute() + * @see Layoutable::setAttribute() */ - public function setLayout(array $attributes) + public function setAttributes(array $attributes) { foreach ($attributes as $key => $value) { if ($value === NULL) { @@ -44,14 +45,14 @@ public function setLayout(array $attributes) } /** - * set a single layouto attribute + * set a single layout attribute * * @param string $name * @param string $value * @return self - * @see Layoutable::setLayout() + * @see Layoutable::setAttributes() */ - public function setLayoutAttribute($name, $value) + public function setAttribute($name, $value) { if ($value === NULL) { unset($this->layout[$name]); @@ -68,12 +69,12 @@ public function setLayoutAttribute($name, $value) * @param string $name * @return boolean */ - public function hasLayoutAttribute($name) + public function hasAttribute($name) { return isset($this->layout[$name]); } - public function getLayoutAttribute($name) + public function getAttribute($name) { if (!isset($this->layout[$name])) { throw new OutOfBoundsException('Given layout attribute is not set'); @@ -81,4 +82,11 @@ public function getLayoutAttribute($name) return $this->layout[$name]; } + + public function setLayout(self $layout) + { + $this->layout = $layout->getAttributes(); + + return $this; + } } diff --git a/lib/Fhaculty/Graph/Renderer/LayoutAggregate.php b/lib/Fhaculty/Graph/Renderer/LayoutAggregate.php new file mode 100644 index 00000000..96b861f7 --- /dev/null +++ b/lib/Fhaculty/Graph/Renderer/LayoutAggregate.php @@ -0,0 +1,14 @@ +layout === null) { + $this->layout = new Layout(); + } + + return $this->layout; + } } diff --git a/tests/Fhaculty/Graph/Algorithm/ConnectedComponentsTest.php b/tests/Fhaculty/Graph/Algorithm/ConnectedComponentsTest.php index 008059b8..1edfffc1 100644 --- a/tests/Fhaculty/Graph/Algorithm/ConnectedComponentsTest.php +++ b/tests/Fhaculty/Graph/Algorithm/ConnectedComponentsTest.php @@ -78,7 +78,7 @@ public function testComponents() $ge = new Graph(); $ge->createVertex(5); - $this->assertEquals($ge, $alg->createGraphComponentVertex($v5)); + $this->assertGraphEquals($ge, $alg->createGraphComponentVertex($v5)); } /** diff --git a/tests/Fhaculty/Graph/GraphVizTest.php b/tests/Fhaculty/Graph/Renderer/GraphVizTest.php similarity index 87% rename from tests/Fhaculty/Graph/GraphVizTest.php rename to tests/Fhaculty/Graph/Renderer/GraphVizTest.php index 181f6241..22086453 100644 --- a/tests/Fhaculty/Graph/GraphVizTest.php +++ b/tests/Fhaculty/Graph/Renderer/GraphVizTest.php @@ -6,7 +6,7 @@ use Fhaculty\Graph\Exception\OverflowException; use Fhaculty\Graph\Exception\InvalidArgumentException; use Fhaculty\Graph\Graph; -use Fhaculty\Graph\GraphViz; +use Fhaculty\Graph\Renderer\GraphViz; class GraphVizTest extends TestCase { @@ -46,8 +46,8 @@ public function testEscaping() $graph->createVertex('a'); $graph->createVertex('b¹²³ is; ok\\ay, "right"?'); $graph->createVertex(3); - $graph->createVertex(4)->setLayoutAttribute('label', 'normal'); - $graph->createVertex(5)->setLayoutAttribute('label', GraphViz::raw('')); + $graph->createVertex(4)->getLayout()->setAttribute('label', 'normal'); + $graph->createVertex(5)->getLayout()->setAttribute('label', GraphViz::raw('')); $expected = <<createVertex('a')->setBalance(1); $graph->createVertex('b')->setBalance(0); $graph->createVertex('c')->setBalance(-1); - $graph->createVertex('d')->setLayoutAttribute('label', 'test'); - $graph->createVertex('e')->setLayoutAttribute('label', 'unnamed')->setBalance(2); + $graph->createVertex('d')->getLayout()->setAttribute('label', 'test'); + $graph->createVertex('e')->setBalance(2)->getLayout()->setAttribute('label', 'unnamed'); $expected = <<createVertex('1a')->createEdge($graph->createVertex('1b')); - $graph->createVertex('2a')->createEdge($graph->createVertex('2b'))->setLayoutAttribute('numeric', 20); - $graph->createVertex('3a')->createEdge($graph->createVertex('3b'))->setLayoutAttribute('textual', "forty"); - $graph->createVertex('4a')->createEdge($graph->createVertex('4b'))->setLayoutAttribute(1, 1)->setLayoutAttribute(2, 2); - $graph->createVertex('5a')->createEdge($graph->createVertex('5b'))->setLayout(array('a' => 'b', 'c' => 'd')); + $graph->createVertex('2a')->createEdge($graph->createVertex('2b'))->getLayout()->setAttribute('numeric', 20); + $graph->createVertex('3a')->createEdge($graph->createVertex('3b'))->getLayout()->setAttribute('textual', "forty"); + $graph->createVertex('4a')->createEdge($graph->createVertex('4b'))->getLayout()->setAttribute(1, 1)->setAttribute(2, 2); + $graph->createVertex('5a')->createEdge($graph->createVertex('5b'))->getLayout()->setAttributes(array('a' => 'b', 'c' => 'd')); $expected = <<createVertex('4a')->createEdge($graph->createVertex('4b'))->setFlow(40); $graph->createVertex('5a')->createEdge($graph->createVertex('5b'))->setFlow(50)->setCapacity(60); $graph->createVertex('6a')->createEdge($graph->createVertex('6b'))->setFlow(60)->setCapacity(70)->setWeight(80); - $graph->createVertex('7a')->createEdge($graph->createVertex('7b'))->setLayoutAttribute('label', 'prefixed')->setFlow(70); + $graph->createVertex('7a')->createEdge($graph->createVertex('7b'))->setFlow(70)->getLayout()->setAttribute('label', 'prefixed'); $expected = <<assertEquals(array(), $layout->getAttributes()); + $this->assertFalse($layout->hasAttribute('non-existant')); + + return $layout; + } + + /** + * @depends testEmptyLayout + * @expectedException OutOfBoundsException + * @param Layout $layout + */ + public function testEmptyFailsInvalid(Layout $layout) + { + $layout->getAttribute('non-existant'); + } + + public function testOverwriting() + { + $layout = new Layout(); + + $layout->setAttribute('a', 'a'); + $this->assertEquals(array('a' => 'a'), $layout->getAttributes()); + + $layout->setAttribute('b', 'b'); + $this->assertEquals(array('a' => 'a', 'b' => 'b'), $layout->getAttributes()); + + $layout->setAttribute('a', 'b'); + $layout->setAttribute('b', null); + $this->assertEquals(array('a' => 'b'), $layout->getAttributes()); + $this->assertEquals('b', $layout->getAttribute('a')); + + $layout->setAttributes(array('c' => 'c', 'a' => null)); + $this->assertEquals(array('c' => 'c'), $layout->getAttributes()); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 723a8280..aeea50c5 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -74,7 +74,7 @@ private function getVertexDump(Vertex $vertex) $ret = get_class($vertex); $ret .= PHP_EOL . 'id: ' . $vertex->getId(); - $ret .= PHP_EOL . 'layout: ' . json_encode($vertex->getLayout()); + $ret .= PHP_EOL . 'layout: ' . json_encode($vertex->getLayout()->getAttributes()); $ret .= PHP_EOL . 'balance: ' . $vertex->getBalance(); $ret .= PHP_EOL . 'group: ' . $vertex->getGroup(); @@ -93,7 +93,7 @@ private function getEdgeDump(Edge $edge) $ret .= PHP_EOL . 'flow: ' . $edge->getFlow(); $ret .= PHP_EOL . 'capacity: ' . $edge->getCapacity(); $ret .= PHP_EOL . 'weight: ' . $edge->getWeight(); - $ret .= PHP_EOL . 'layout: ' . json_encode($edge->getLayout()); + $ret .= PHP_EOL . 'layout: ' . json_encode($edge->getLayout()->getAttributes()); return $ret; }