6 * $Date: 2012-07-16 15:34:43 +0200 (Mo, 16. Jul 2012) $
7 ***************************************************************/
10 * \brief implementation of class PlanarizationLayout.
12 * applies planarization approach for drawing UML diagrams
13 * by calling a planar layouter for every planarized connected
15 * Static and incremental calls available
16 * Replaces cliques (if m_processCliques is set) to speed up
17 * the computation, this does only work in non-UML mode
19 * \author Carsten Gutwenger
22 * This file is part of the Open Graph Drawing Framework (OGDF).
26 * See README.txt in the root directory of the OGDF installation for details.
29 * This program is free software; you can redistribute it and/or
30 * modify it under the terms of the GNU General Public License
31 * Version 2 or 3 as published by the Free Software Foundation;
32 * see the file LICENSE.txt included in the packaging of this file
36 * This program is distributed in the hope that it will be useful,
37 * but WITHOUT ANY WARRANTY; without even the implied warranty of
38 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 * GNU General Public License for more details.
42 * You should have received a copy of the GNU General Public
43 * License along with this program; if not, write to the Free
44 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
45 * Boston, MA 02110-1301, USA.
47 * \see http://www.gnu.org/copyleft/gpl.html
48 ***************************************************************/
51 #include <ogdf/planarity/PlanarizationLayout.h>
52 #include <ogdf/planarity/FastPlanarSubgraph.h>
53 #include <ogdf/planarity/FixedEmbeddingInserter.h>
54 #include <ogdf/planarity/SimpleEmbedder.h>
55 #include <ogdf/orthogonal/OrthoLayout.h>
56 #include <ogdf/packing/TileToRowsCCPacker.h>
57 #include <ogdf/basic/TopologyModule.h>
58 #include <ogdf/basic/precondition.h>
59 #include <ogdf/basic/simple_graph_alg.h>
60 #include <ogdf/graphalg/CliqueFinder.h>
66 //set default values for parameters and set planar layouter,
67 //planarization modules
68 PlanarizationLayout::PlanarizationLayout()
71 m_subgraph
.set(new FastPlanarSubgraph
);
72 m_inserter
.set(new FixedEmbeddingInserter
);
73 m_planarLayouter
.set(new OrthoLayout
);
74 m_packer
.set(new TileToRowsCCPacker
);
75 m_embedder
.set(new SimpleEmbedder
);
79 m_processCliques
= false; //do not search for cliques, only if not uml
81 m_fakeTree
= true; //change edge type from generalization to association
85 void PlanarizationLayout::reembed(PlanRepUML
&PG
, int ccNumber
, bool l_align
,
88 //TODO: update by reinitialization?
90 //first we remove all inserted crossings
95 if (PG
.isCrossingType(v
))
97 crossings
.pushBack(v
);
100 ListIterator
<node
> it
= crossings
.begin();
103 PG
.removeCrossing((*it
));
106 //***************************************
107 // first phase: Compute a planar subgraph
108 //***************************************
110 // The planar subgraph should contain as many generalizations
111 // as possible, hence we put all generalizations into the list
113 List
<edge
> preferedEdges
;
115 EdgeArray
<int> costOrig(PG
.original(), 1);
118 if (PG
.typeOf(e
) == Graph::generalization
)
120 if (l_align
) l_gensExist
= true;
121 preferedEdges
.pushBack(e
);
122 edge ori
= PG
.original(e
);
123 //high cost to allow alignment without crossings
125 ((ori
&& (PG
.typeOf(e
->target()) == Graph::generalizationMerger
))
126 || (PG
.alignUpward(e
->adjSource()))
134 List
<edge
> deletedEdges
;
135 m_subgraph
.get().callAndDelete(PG
, preferedEdges
, deletedEdges
);
138 //**************************************
139 // second phase: Re-insert deleted edges
140 //**************************************
142 m_inserter
.get().callForbidCrossingGens(PG
, costOrig
, deletedEdges
);
144 OGDF_ASSERT(isPlanar(PG
));
148 // determine embedding of PG
151 // We currently compute any embedding and choose the maximal face
154 // if we use FixedEmbeddingInserter, we have to re-use the computed
155 // embedding, otherwise crossing nodes can turn into "touching points"
156 // of edges (alternatively, we could compute a new embedding and
157 // finally "remove" such unnecessary crossings).
158 if(!PG
.representsCombEmbedding())
162 // CG: This code does not do anything...
163 //adjEntry adjExternal = 0;
165 //if(PG.numberOfEdges() > 0)
167 // CombinatorialEmbedding E(PG);
168 // //face fExternal = E.maximalFace();
169 // face fExternal = findBestExternalFace(PG,E);
170 // adjExternal = fExternal->firstAdj();
171 // //while (PG.sinkConnect(adjExternal->theEdge()))
172 // // adjExternal = adjExternal->faceCycleSucc();
177 //---------------------------------------------------------
178 //compute a layout with the given embedding, take special
179 //care of crossings (assumes that all crossings are non-ambiguous
180 //and can be derived from node/bend positions)
181 //---------------------------------------------------------
182 void PlanarizationLayout::callFixEmbed(UMLGraph
¨Graph
)
186 if(((const Graph
&)umlGraph
).empty())
189 //check necessary preconditions
190 preProcess(umlGraph
);
192 int l_layoutOptions
= m_planarLayouter
.get().getOptions();
193 bool l_align
= ((l_layoutOptions
& umlOpAlign
)>0);
195 //--------------------------------------------------------
196 //first, we sort all edges around each node corresponding
197 //to the given layout in umlGraph
198 //then we insert the mergers
199 //try to rebuild this: insert mergers only in copy
200 bool umlMerge
= false; //Standard: false
203 //*********************************************************
204 // first phase: Compute planarized representation from input
205 //*********************************************************
207 //now we derive a planar representation of the input
208 //graph using its layout information
209 PlanRepUML
PG(umlGraph
);
211 const int numCC
= PG
.numberOfCCs();
213 // (width,height) of the layout of each connected component
214 Array
<DPoint
> boundingBox(numCC
);
217 //******************************************
218 //now planarize CCs and apply drawing module
219 for(i
= 0; i
< numCC
; ++i
)
221 // we treat connected component i
222 // PG is set to a copy of this CC
226 int nOrigVerticesPG
= PG
.numberOfNodes();
228 //alignment: check wether gens exist, special treatment is necessary
229 bool l_gensExist
= false; //set this for all CC's, start with first gen,
230 //this setting can be mixed among CC's without problems
232 //*********************************************************
233 // we don't need to compute a planar subgraph, because we
234 // use the given embedding
235 //*********************************************************
237 adjEntry adjExternal
= 0;
239 //here lies the main difference to the other call
240 //we first planarize by using the given layout and then
241 //set the embedding corresponding to the input
245 embedded
= TM
.setEmbeddingFromGraph(PG
, umlGraph
, adjExternal
, umlMerge
);
249 //TODO: check for graph changes that are not undone
254 //-------------------------------------------------
255 //if not embedded correctly
258 reembed(PG
, i
, l_align
, l_gensExist
);
261 //-------------------------------------------------
263 CombinatorialEmbedding
E(PG
);
266 PG
.setupIncremental(i
, E
);
269 // determine embedding of PG
272 // We currently compute any embedding and choose the maximal face
275 // if we use FixedEmbeddingInserter, we have to re-use the computed
276 // embedding, otherwise crossing nodes can turn into "touching points"
277 // of edges (alternatively, we could compute a new embedding and
278 // finally "remove" such unnecessary crossings).
280 if((adjExternal
== 0) && PG
.numberOfEdges() > 0)
282 //face fExternal = E.maximalFace();
283 face fExternal
= findBestExternalFace(PG
,E
);
284 adjExternal
= fExternal
->firstAdj();
285 //while (PG.sinkConnect(adjExternal->theEdge()))
286 // adjExternal = adjExternal->faceCycleSucc();
289 m_nCrossings
+= PG
.numberOfNodes() - nOrigVerticesPG
;
292 //*********************************************************
293 // third phase: Compute layout of planarized representation
294 //*********************************************************
298 //distinguish between CC's with/without generalizations
299 //this changes the input layout modules options!
301 m_planarLayouter
.get().setOptions(l_layoutOptions
);
302 else m_planarLayouter
.get().setOptions((l_layoutOptions
& ~umlOpAlign
));
304 //***************************************
305 //call the Layouter for the CC's UMLGraph
306 m_planarLayouter
.get().call(PG
,adjExternal
,drawing
);
308 // copy layout into umlGraph
309 // Later, we move nodes and edges in each connected component, such
310 // that no two overlap.
311 const List
<node
> &origInCC
= PG
.nodesInCC(i
);
312 ListConstIterator
<node
> itV
;
314 //set position for original nodes and set bends for
316 for(itV
= origInCC
.begin(); itV
.valid(); ++itV
)
320 umlGraph
.x(vG
) = drawing
.x(PG
.copy(vG
));
321 umlGraph
.y(vG
) = drawing
.y(PG
.copy(vG
));
326 if ((adj
->index() & 1) == 0) continue;
327 edge eG
= adj
->theEdge();
329 drawing
.computePolylineClear(PG
,eG
,umlGraph
.bends(eG
));
335 //insert bend point for incremental mergers
336 const SList
<node
>& mergers
= PG
.incrementalMergers(i
);
337 SListConstIterator
<node
> itMerger
= mergers
.begin();
338 while (itMerger
.valid())
340 node vMerger
= (*itMerger
);
341 //due to the fact that the merger may be expanded, we are
342 //forced to run through the face
343 adjEntry adjMerger
= PG
.expandAdj(vMerger
);
344 //check if there is an expansion face
347 //we run through the expansion face to the connected edges
348 //this edge is to dummy corner
349 adjEntry runAdj
= adjMerger
->faceCycleSucc();
350 while (runAdj
!= adjMerger
)
352 node vConnect
= runAdj
->theNode();
353 //because of the node collapse using the original
354 //edges instead of the merger copy edges (should be
355 //fixed for incremental mode) the degree is 4
356 //if (vConnect->degree() != 3)
357 if (vConnect
->degree() != 4)
359 runAdj
= runAdj
->faceCycleSucc();
362 edge eCopy
= runAdj
->cyclicPred()->theEdge();
363 OGDF_ASSERT(eCopy
->target() == runAdj
->theNode())
364 OGDF_ASSERT(PG
.isGeneralization(eCopy
))
365 OGDF_ASSERT(PG
.original(eCopy
))
366 umlGraph
.bends(PG
.original(eCopy
)).pushBack(
367 DPoint(drawing
.x(vMerger
), drawing
.y(vMerger
)));
368 runAdj
= runAdj
->faceCycleSucc();
373 else //currently all nodes are expanded, but this is not guaranteed
375 forall_adj(adjMerger
, vMerger
)
377 if (adjMerger
->theEdge()->target() == vMerger
)
379 edge eOrig
= PG
.original(adjMerger
->theEdge());
381 //incoming merger edges always have an original here!
382 umlGraph
.bends(eOrig
).pushBack(DPoint(drawing
.x(vMerger
),
383 drawing
.y(vMerger
)));
389 }//while merger nodes
392 // the width/height of the layout has been computed by the planar
393 // layout algorithm; required as input to packing algorithm
394 boundingBox
[i
] = m_planarLayouter
.get().getBoundingBox();
397 postProcess(umlGraph
);
399 //----------------------------------------
400 // Arrange layouts of connected components
401 //----------------------------------------
403 arrangeCCs(PG
, umlGraph
, boundingBox
);
405 umlGraph
.undoGenMergers();
406 umlGraph
.removeUnnecessaryBendsHV();
411 //-----------------------------------------------------------------------------
412 //call function: compute an UML layout for graph umlGraph
413 //-----------------------------------------------------------------------------
414 void PlanarizationLayout::call(UMLGraph
¨Graph
)
418 if(((const Graph
&)umlGraph
).empty())
421 //check necessary preconditions
422 preProcess(umlGraph
);
424 //---------------------------------------------------
425 // preprocessing: insert a merger for generalizations
426 umlGraph
.insertGenMergers();
428 PlanRepUML
PG(umlGraph
);
429 const int numCC
= PG
.numberOfCCs();
431 // (width,height) of the layout of each connected component
432 Array
<DPoint
> boundingBox(numCC
);
435 //alignment section (should not be here, because planarlayout should
436 //not know about the meaning of layouter options and should not cope
437 //with them), move later
438 //we have to distinguish between cc's with and without generalizations
439 //if the alignment option is set
440 int l_layoutOptions
= m_planarLayouter
.get().getOptions();
441 bool l_align
= ((l_layoutOptions
& umlOpAlign
)>0);
442 //end alignment section
444 //------------------------------------------
445 //now planarize CCs and apply drawing module
447 for(i
= 0; i
< numCC
; ++i
)
449 // we treat connected component i
450 // PG is set to a copy of this CC
453 int nOrigVerticesPG
= PG
.numberOfNodes();
455 //alignment: check wether gens exist, special treatment is necessary
456 bool l_gensExist
= false; //set this for all CC's, start with first gen,
457 //this setting can be mixed among CC's without problems
459 //---------------------------------------
460 // first phase: Compute a planar subgraph
461 //---------------------------------------
463 // The planar subgraph should contain as many generalizations
464 // as possible, hence we put all generalizations into the list
466 // In the case of clique processing we have to prevent the
467 // clique replacement (star) edges from being crossed.
468 // We do not allow that clique replacement is done in the case
469 // of UML diagrams, therefore we can temporarily set the type of
470 // all deleted and replacement edges to generalization to
472 EdgeArray
<Graph::EdgeType
> savedType(PG
);
473 EdgeArray
<Graph::EdgeType
> savedOrigType(PG
.original()); //for deleted copies
475 List
<edge
> preferedEdges
;
477 EdgeArray
<int> costOrig(PG
.original(), 1);
478 //edgearray for reinserter call: which edge may never be crossed?
479 EdgeArray
<bool> noCrossingEdge(PG
.original(), false);
482 edge ori
= PG
.original(e
);
483 if (m_processCliques
)
485 savedType
[e
] = PG
.typeOf(e
);
488 if (umlGraph
.isReplacement(ori
))
490 preferedEdges
.pushBack(e
);
492 PG
.setGeneralization(e
);
493 noCrossingEdge
[ori
] = true;
495 }//if clique replacement
500 if (PG
.typeOf(e
) == Graph::generalization
)
502 if (l_align
) l_gensExist
= true;
503 OGDF_ASSERT(!ori
|| !(noCrossingEdge
[ori
]));
504 preferedEdges
.pushBack(e
);
506 //high cost to allow alignment without crossings
508 (ori
&& (PG
.typeOf(e
->target()) == Graph::generalizationMerger
))
509 || PG
.alignUpward(e
->adjSource())
516 List
<edge
> deletedEdges
;
517 m_subgraph
.get().callAndDelete(PG
, preferedEdges
, deletedEdges
);
519 if (m_processCliques
)
521 ListIterator
<edge
> itEdge
= deletedEdges
.begin();
522 while (itEdge
.valid())
524 savedOrigType
[*itEdge
] = PG
.typeOrig(*itEdge
);
525 umlGraph
.type(*itEdge
) = Graph::generalization
;
527 OGDF_ASSERT(!(umlGraph
.isReplacement(*itEdge
)))
533 //--------------------------------------
534 // second phase: Re-insert deleted edges
535 //--------------------------------------
537 if (m_processCliques
)
538 m_inserter
.get().call(PG
, costOrig
, noCrossingEdge
, deletedEdges
);
540 m_inserter
.get().callForbidCrossingGens(PG
, costOrig
, deletedEdges
);
542 //reset the changed edge types
543 if (m_processCliques
)
548 edge ori
= PG
.original(e
);
550 if (umlGraph
.isReplacement(ori
))
552 OGDF_ASSERT(PG
.chain(ori
).size() == 1)
553 PG
.setType(e
, savedType
[e
]);
554 umlGraph
.type(ori
) = Graph::association
;
557 //set the type of the reinserted edges in original and copy
558 ListIterator
<edge
> itEdge
= deletedEdges
.begin();
559 while (itEdge
.valid())
561 umlGraph
.type(*itEdge
) = savedOrigType
[*itEdge
];
562 const List
<edge
> &le
= PG
.chain(*itEdge
);
563 ListConstIterator
<edge
> it2
= le
.begin();
566 PG
.setType(*it2
, savedOrigType
[*itEdge
]);
573 //-----------------------------------------------------------------
574 //additional check for clique processing
575 //guarantee that there is no crossing in replacement, otherwise
576 //we can not cluster a star
577 //fileName.sprintf("planar2.gml");
579 //if (m_processCliques)
581 // SListConstIterator<node> itDV = umlGraph.centerNodes().begin();
582 // while (itDV.valid())
584 // node cent = PG.copy(*itDV);
585 // OGDF_ASSERT(cent->degree() == (*itDV)->degree())
587 // forall_adj(adj, cent)
589 // OGDF_ASSERT(PG.original(adj->twinNode()))
590 // OGDF_ASSERT(!(PG.isGeneralization(adj->theEdge())))
591 // PG.setBrother(adj->theEdge());//only output coloring
596 // Layout fakeDrawing(PG);
597 // PG.writeGML(fileName, fakeDrawing);
598 //}//if clique replacement
599 //----------------------------------------------------------
603 // determine embedding of PG
606 // We currently compute any embedding and choose the maximal face
609 // if we use FixedEmbeddingInserter, we have to re-use the computed
610 // embedding, otherwise crossing nodes can turn into "touching points"
611 // of edges (alternatively, we could compute a new embedding and
612 // finally "remove" such unnecessary crossings).
613 if(!PG
.representsCombEmbedding())
615 adjEntry adjExternal
= 0;
617 if(PG
.numberOfEdges() > 0)
619 CombinatorialEmbedding
E(PG
);
620 face fExternal
= findBestExternalFace(PG
,E
);
621 adjExternal
= fExternal
->firstAdj();
624 m_nCrossings
+= PG
.numberOfNodes() - nOrigVerticesPG
;
627 //---------------------------------------------------------
628 // third phase: Compute layout of planarized representation
629 //---------------------------------------------------------
631 if (m_processCliques
)
633 //insert boundaries around clique representation node
634 //and compute a representation layout for the cliques
635 //(is needed to guarantee the size of the replacement
637 //conserve external face information
638 SListPure
<node
> centerNodes
= umlGraph
.centerNodes();
639 SListIterator
<node
> itNode
= centerNodes
.begin();
640 while (itNode
.valid())
642 PG
.insertBoundary(*itNode
, adjExternal
);
650 //distinguish between CC's with/without generalizations
651 //this changes the input layout modules options!
653 m_planarLayouter
.get().setOptions(l_layoutOptions
);
654 else m_planarLayouter
.get().setOptions((l_layoutOptions
& ~umlOpAlign
));
656 //***************************************
657 //call the Layouter for the CC's UMLGraph
658 m_planarLayouter
.get().call(PG
,adjExternal
,drawing
);
660 //--------------------------------------
661 //we now have to reposition clique nodes
662 if (m_processCliques
)
664 //--------------------------------------------------------
665 //first, we derive the current size of the boundary around
666 //the center nodes, then we position the clique nodes
667 //in a circular fashion in the boundary
669 //the node array is only used for the simple anchor move strategy at the
670 //end of this if and can later be removed
671 NodeArray
<bool> isClique(PG
, false);
673 SListPure
<node
> centerNodes
= umlGraph
.centerNodes();
674 SListIterator
<node
> itNode
= centerNodes
.begin();
675 while (itNode
.valid())
677 node centerNode
= (*itNode
);
678 adjEntry adjBoundary
= PG
.boundaryAdj(centerNode
);
679 //-----------------------------------------------------
680 //derive the boundary size
681 //if the boundary does not exist (connected component is clique), we
682 //only run over the nodes adjacent to centerNode
683 double minx
= DBL_MAX
, maxx
= -DBL_MAX
, miny
= DBL_MAX
, maxy
= -DBL_MAX
;
686 adjEntry adjRunner
= adjBoundary
;
687 //explore the dimension and position of the boundary rectangle
688 //TODO: guarantee (edge types?) that we run around a boundary
690 double vx
= drawing
.x(adjRunner
->theNode());
691 double vy
= drawing
.y(adjRunner
->theNode());
692 if (vx
< minx
) minx
= vx
;
693 if (vx
> maxx
) maxx
= vx
;
694 if (vy
< miny
) miny
= vy
;
695 if (vy
> maxy
) maxy
= vy
;
697 //are we at a bend or a crossing?
698 OGDF_ASSERT((adjRunner
->twinNode()->degree() == 2) ||
699 (adjRunner
->twinNode()->degree() == 4))
701 if (adjRunner
->twinNode()->degree() < 4)
702 adjRunner
= adjRunner
->faceCycleSucc();
703 else adjRunner
= adjRunner
->faceCycleSucc()->cyclicPred();
704 } while (adjRunner
!= adjBoundary
);
705 }//if boundary exists
708 forall_adj(adjBoundary
, centerNode
)
710 node w
= adjBoundary
->twinNode();
711 double vx
= drawing
.x(PG
.copy(w
));
712 double vy
= drawing
.y(PG
.copy(w
));
713 if (vx
< minx
) minx
= vx
;
714 if (vx
> maxx
) maxx
= vx
;
715 if (vy
< miny
) miny
= vy
;
716 if (vy
> maxy
) maxy
= vy
;
719 //-----------------------------------------------------------
721 //we now have to arrange the nodes on a circle with positions
722 //that respect the position within the given rectangle, i.e.
723 //the node with highest position should be on top etc. . This
724 //helps in avoiding unnecessary crossings of outgoing edges
725 //with the clique circle and unnecessary long edges to the anchors
726 //Note that the ordering around centerNode in umlGraph is different
727 //to the ordering in the drawing (defined on PG)
728 //recompute size of clique and the node positions
730 //---------------------------------------------------------
731 //derive the ordering of the nodes around centerNode in the
734 fillAdjNodes(adjNodes
, PG
, centerNode
, isClique
, drawing
);
736 //-----------------------------
737 //compute clique node positions
738 umlGraph
.computeCliquePosition(adjNodes
, centerNode
,
739 min(maxx
-minx
,maxy
-miny
));
742 double centralX
= (maxx
-minx
)/2.0+minx
;
743 double centralY
= (maxy
-miny
)/2.0+miny
;
744 double circleX
= umlGraph
.cliqueRect(centerNode
).width()/2.0;
745 double circleY
= umlGraph
.cliqueRect(centerNode
).height()/2.0;
747 //now we have the position and size of the rectangle around
750 //assign shifted coordinates to drawing
751 forall_adj(adjBoundary
, centerNode
)
753 node w
= adjBoundary
->twinNode();
754 drawing
.x(PG
.copy(w
)) = centralX
-circleX
+umlGraph
.cliquePos(w
).m_x
;
755 drawing
.y(PG
.copy(w
)) = centralY
-circleY
+umlGraph
.cliquePos(w
).m_y
;
761 //simple strategy to move anchor positions too (they are not needed:
762 // move to same position)
766 //forall clique nodes shift the anchor points
769 adjEntry adRun
= w
->firstAdj();
772 node wOpp
= adRun
->twinNode();
773 drawing
.x(wOpp
) = drawing
.x(w
);
774 drawing
.y(wOpp
) = drawing
.y(w
);
775 adRun
= adRun
->cyclicSucc();
776 } while (adRun
!= w
->firstAdj());
783 // copy layout into umlGraph
784 // Later, we move nodes and edges in each connected component, such
785 // that no two overlap.
786 const List
<node
> &origInCC
= PG
.nodesInCC(i
);
787 ListConstIterator
<node
> itV
;
789 for(itV
= origInCC
.begin(); itV
.valid(); ++itV
) {
792 umlGraph
.x(vG
) = drawing
.x(PG
.copy(vG
));
793 umlGraph
.y(vG
) = drawing
.y(PG
.copy(vG
));
797 if ((adj
->index() & 1) == 0) continue;
798 edge eG
= adj
->theEdge();
800 drawing
.computePolylineClear(PG
,eG
,umlGraph
.bends(eG
));
804 // the width/height of the layout has been computed by the planar
805 // layout algorithm; required as input to packing algorithm
806 boundingBox
[i
] = m_planarLayouter
.get().getBoundingBox();
809 //----------------------------------------
810 // Arrange layouts of connected components
811 //----------------------------------------
813 arrangeCCs(PG
, umlGraph
, boundingBox
);
815 umlGraph
.undoGenMergers();
816 umlGraph
.removeUnnecessaryBendsHV();
818 //new position after adding of cliqueprocess, check if correct
819 postProcess(umlGraph
);
824 //-----------------------------------------------------------------------------
825 //static call function: compute a layout for graph umlGraph without
826 //special UML or interactive features, clique processing etc.
827 //-----------------------------------------------------------------------------
829 void PlanarizationLayout::doSimpleCall(GraphAttributes
*pGA
)
833 if(pGA
->constGraph().empty())
836 PlanRepUML
*pPG
= new PlanRepUML(*pGA
);
837 const int numCC
= pPG
->numberOfCCs();
839 // (width,height) of the layout of each connected component
840 Array
<DPoint
> boundingBox(numCC
);
842 //------------------------------------------
843 //now planarize CCs and apply drawing module
845 for(i
= 0; i
< numCC
; ++i
)
847 // we treat connected component i
848 // PG is set to a copy of this CC
851 int nOrigVerticesPG
= pPG
->numberOfNodes();
853 //---------------------------------------
854 // first phase: Compute a planar subgraph
855 //---------------------------------------
857 // The planar subgraph should contain as many generalizations
858 // as possible, hence we put all generalizations into the list
861 List
<edge
> preferedEdges
;
863 EdgeArray
<int> costOrig(pPG
->original(), 1);
864 //edgearray for reinserter call: which edge may never be crossed?
865 EdgeArray
<bool> noCrossingEdge(pPG
->original(), false);
866 forall_edges(e
,*pPG
) {
867 if (pPG
->typeOf(e
) == Graph::generalization
)
868 preferedEdges
.pushBack(e
);
871 List
<edge
> deletedEdges
;
872 m_subgraph
.get().callAndDelete(*pPG
, preferedEdges
, deletedEdges
);
874 //--------------------------------------
875 // second phase: Re-insert deleted edges
876 //--------------------------------------
878 m_inserter
.get().callForbidCrossingGens(*pPG
, costOrig
, deletedEdges
);
880 // ... and embed resulting planar graph
881 adjEntry adjExternal
= 0;
882 m_embedder
.get().call(*pPG
, adjExternal
);
884 m_nCrossings
+= pPG
->numberOfNodes() - nOrigVerticesPG
;
887 //---------------------------------------------------------
888 // third phase: Compute layout of planarized representation
889 //---------------------------------------------------------
891 Layout
drawing(*pPG
);
893 //---------------------------------------
894 //call the Layouter for the CC's UMLGraph
895 m_planarLayouter
.get().call(*pPG
,adjExternal
,drawing
);
897 // copy layout into umlGraph
898 // Later, we move nodes and edges in each connected component, such
899 // that no two overlap.
900 const List
<node
> &origInCC
= pPG
->nodesInCC(i
);
901 ListConstIterator
<node
> itV
;
903 for(itV
= origInCC
.begin(); itV
.valid(); ++itV
) {
906 pGA
->x(vG
) = drawing
.x(pPG
->copy(vG
));
907 pGA
->y(vG
) = drawing
.y(pPG
->copy(vG
));
911 if ((adj
->index() & 1) == 0)
913 edge eG
= adj
->theEdge();
914 drawing
.computePolylineClear(*pPG
,eG
,pGA
->bends(eG
));
918 // the width/height of the layout has been computed by the planar
919 // layout algorithm; required as input to packing algorithm
920 boundingBox
[i
] = m_planarLayouter
.get().getBoundingBox();
923 //----------------------------------------
924 // Arrange layouts of connected components
925 //----------------------------------------
927 arrangeCCs(*pPG
, *pGA
, boundingBox
);
932 //-----------------------------------------------------------------------------
933 //call function for simultaneous drawing: compute a layout for graph umlGraph
934 // without special UML or interactive features, clique processing etc.
935 //-----------------------------------------------------------------------------
936 void PlanarizationLayout::callSimDraw(UMLGraph
¨Graph
)
938 //this simple call method does not care about any special treatments
939 //of subgraphs, layout informations etc., therefore we save the
940 //option status and set them back later on
941 bool l_saveCliqueHandling
= m_processCliques
;
942 m_processCliques
= false;
946 const Graph
&G
= umlGraph
.constGraph();
950 PlanRepUML
PG(umlGraph
);
952 const int numCC
= PG
.numberOfCCs();
954 // (width,height) of the layout of each connected component
955 Array
<DPoint
> boundingBox(numCC
);
959 //------------------------------------------
960 //now planarize CCs and apply drawing module
961 for(i
= 0; i
< numCC
; ++i
)
963 // we treat connected component i
964 // PG is set to a copy of this CC
968 int nOrigVerticesPG
= PG
.numberOfNodes();
971 EdgeArray
<int> costOrig(PG
.original(), 1);
972 EdgeArray
<unsigned int> esgOrig(PG
.original(), 0);
974 esgOrig
[e
] = umlGraph
.subGraphBits(e
);
976 //---------------------------------------
977 // first phase: Compute a planar subgraph
978 //---------------------------------------
980 List
<edge
> deletedEdges
;
981 m_subgraph
.get().callAndDelete(PG
, deletedEdges
);
983 //**************************************
984 // second phase: Re-insert deleted edges
985 //**************************************
987 m_inserter
.get().call(PG
, costOrig
, deletedEdges
, esgOrig
);
989 //calls embedder module:
990 adjEntry adjExternal
= 0;
991 m_embedder
.get().call(PG
, adjExternal
);
993 m_nCrossings
+= PG
.numberOfNodes() - nOrigVerticesPG
;
996 //*********************************************************
997 // third phase: Compute layout of planarized representation
998 //*********************************************************
1002 //***************************************
1003 //call the Layouter for the CC's UMLGraph
1004 m_planarLayouter
.get().call(PG
,adjExternal
,drawing
);
1006 // copy layout into umlGraph
1007 // Later, we move nodes and edges in each connected component, such
1008 // that no two overlap.
1009 const List
<node
> &origInCC
= PG
.nodesInCC(i
);
1010 ListConstIterator
<node
> itV
;
1012 for(itV
= origInCC
.begin(); itV
.valid(); ++itV
) {
1015 umlGraph
.x(vG
) = drawing
.x(PG
.copy(vG
));
1016 umlGraph
.y(vG
) = drawing
.y(PG
.copy(vG
));
1019 forall_adj(adj
,vG
) {
1020 if ((adj
->index() & 1) == 0) continue;
1021 edge eG
= adj
->theEdge();
1023 drawing
.computePolylineClear(PG
,eG
,umlGraph
.bends(eG
));
1028 // the width/height of the layout has been computed by the planar
1029 // layout algorithm; required as input to packing algorithm
1030 boundingBox
[i
] = m_planarLayouter
.get().getBoundingBox();
1033 //----------------------------------------
1034 // Arrange layouts of connected components
1035 //----------------------------------------
1037 arrangeCCs(PG
, umlGraph
, boundingBox
);
1039 umlGraph
.removeUnnecessaryBendsHV();
1040 m_processCliques
= l_saveCliqueHandling
;
1044 //#################################################################
1046 //additional help functions
1048 void PlanarizationLayout::assureDrawability(UMLGraph
&UG
)
1051 //self loops are killed by the caller (e.g., the plugin interface)
1052 //should be done here later
1054 const Graph
& G
= UG
;
1056 //check for selfloops and handle them
1058 forall_edges(e
, G
) {
1059 if (e
->isSelfLoop())
1060 OGDF_THROW_PARAM(PreconditionViolatedException
, pvcSelfLoop
);
1063 // check for generalization - nontrees
1064 // if m_fakeTree is set, change type of "back" edges to association
1065 m_fakedGens
.clear();//?
1066 if (!dfsGenTree(UG
, m_fakedGens
, m_fakeTree
))
1067 OGDF_THROW_PARAM(PreconditionViolatedException
, pvcTreeHierarchies
);
1070 ListConstIterator
<edge
> itE
= m_fakedGens
.begin();
1071 while (itE
.valid()) {
1072 UG
.type(*itE
) = Graph::association
;
1076 }//assureDrawability
1080 void PlanarizationLayout::preProcess(UMLGraph
&UG
)
1082 assureDrawability(UG
);
1084 if (m_processCliques
)
1086 UG
.setDefaultCliqueCenterSize(m_planarLayouter
.get().separation());
1087 const Graph
& G
= (const Graph
&)UG
;
1089 cf
.setMinSize(m_cliqueSize
);
1091 List
< List
<node
> > cliques
;
1093 //now replace all found cliques by stars
1094 UG
.replaceByStar(cliques
);
1096 //TODO: sollte kein else sein, aber man muss abfangen,
1097 //ob eine Kante geloescht wurde (und die Info nachher wieder bereitstellen)
1100 const SListPure
<UMLGraph::AssociationClass
*> &acList
= UG
.assClassList();
1101 SListConstIterator
<UMLGraph::AssociationClass
*> it
= acList
.begin();
1104 UG
.modelAssociationClass((*it
));
1112 void PlanarizationLayout::postProcess(UMLGraph
& UG
)
1114 //reset the type of faked associations to generalization
1117 ListIterator
<edge
> itE
= m_fakedGens
.begin();
1120 UG
.type(*itE
) = Graph::generalization
;
1125 UG
.undoAssociationClasses();
1126 if (m_processCliques
)
1131 // find best suited external face according to certain criteria
1132 face
PlanarizationLayout::findBestExternalFace(
1134 const CombinatorialEmbedding
&E
)
1136 FaceArray
<int> weight(E
);
1140 weight
[f
] = f
->size();
1145 if(PG
.typeOf(v
) != Graph::generalizationMerger
)
1150 if(adj
->theEdge()->source() == v
)
1154 OGDF_ASSERT(adj
->theEdge()->source() == v
);
1156 node w
= adj
->theEdge()->target();
1160 forall_adj(adj2
, w
) {
1161 edge e
= adj2
->theEdge();
1162 if(e
->target() != w
&& PG
.typeOf(e
) == Graph::generalization
) {
1171 face f1
= E
.leftFace(adj
);
1172 face f2
= E
.rightFace(adj
);
1174 weight
[f1
] += v
->indeg();
1176 weight
[f2
] += v
->indeg();
1179 face fBest
= E
.firstFace();
1181 if(weight
[f
] > weight
[fBest
])
1188 void PlanarizationLayout::arrangeCCs(PlanRep
&PG
, GraphAttributes
&GA
, Array
<DPoint
> &boundingBox
)
1190 int numCC
= PG
.numberOfCCs();
1191 Array
<DPoint
> offset(numCC
);
1192 m_packer
.get().call(boundingBox
,offset
,m_pageRatio
);
1194 // The arrangement is given by offset to the origin of the coordinate
1195 // system. We still have to shift each node and edge by the offset
1196 // of its connected component.
1198 for(int i
= 0; i
< numCC
; ++i
) {
1199 const List
<node
> &nodes
= PG
.nodesInCC(i
);
1201 const double dx
= offset
[i
].m_x
;
1202 const double dy
= offset
[i
].m_y
;
1204 // iterate over all nodes in ith CC
1205 ListConstIterator
<node
> it
;
1206 for(it
= nodes
.begin(); it
.valid(); ++it
)
1215 if ((adj
->index() & 1) == 0) continue;
1216 edge e
= adj
->theEdge();
1218 DPolyline
&dpl
= GA
.bends(e
);
1219 ListIterator
<DPoint
> it
;
1220 for(it
= dpl
.begin(); it
.valid(); ++it
) {
1230 //collects and stores nodes adjacent to centerNode in adjNodes
1231 void PlanarizationLayout::fillAdjNodes(List
<node
>& adjNodes
,
1234 NodeArray
<bool>& isClique
,
1237 //at this point, cages are collapsed, i.e. we have a center node
1238 //in the planrep representing the copy of the original node
1239 node cCopy
= PG
.copy(centerNode
);
1240 OGDF_ASSERT(cCopy
!= 0)
1241 OGDF_ASSERT(cCopy
->degree() == centerNode
->degree())
1242 OGDF_ASSERT(cCopy
->degree() > 1)
1243 //store the node with mostright position TODO: consider cage
1246 //due to the implementation in PlanRepUML::collapseVertices, the
1247 //ordering of the nodes in the copy does not correspond to the
1248 //ordering in the used embedding, but to the original graph
1249 //we therefore need to run around the cage to search for the
1252 adjEntry adjRun
= cCopy
->firstAdj();
1255 //we search for the edge outside the node cage
1257 OGDF_ASSERT(adjRun
->twinNode()->degree() == 4)
1258 //should be cs->cs, but doesnt work, comb. embedding?
1259 //adjEntry outerEdgeAdj = adjRun->twin()->cyclicSucc()->cyclicSucc();
1260 //TODO: braucht man glaube ich gar nicht, da bereits erste Kante Original hat
1261 adjEntry outerEdgeAdj
= adjRun
->twin()->cyclicSucc();
1262 //this may fail in case of bends if there are no orig edges, but there
1264 while (!PG
.original(outerEdgeAdj
->theEdge()))
1265 outerEdgeAdj
= outerEdgeAdj
->cyclicSucc();
1266 OGDF_ASSERT(outerEdgeAdj
!= adjRun
)
1268 //hier besser: if... und alle anderen ignorieren
1269 edge umlEdge
= PG
.original(outerEdgeAdj
->theEdge());
1270 OGDF_ASSERT(umlEdge
!= 0)
1271 node u
= umlEdge
->opposite(centerNode
);
1272 adjNodes
.pushBack(u
);
1273 isClique
[PG
.copy(u
)] = true;
1275 //----------------------------------------------
1276 //part to delete all bends that lie within the clique rectangle
1277 //first we identify the copy node of the clique node we currently
1279 node uCopy
= PG
.copy(u
);
1281 adjEntry adjURun
= uCopy
->firstAdj();
1284 //we search for the edge outside the node cage
1285 OGDF_ASSERT(adjURun
->twinNode()->degree() == 4)
1286 adjEntry outerEdgeUAdj
= adjURun
->twin()->cyclicSucc();
1287 while (!PG
.original(outerEdgeUAdj
->theEdge()))
1288 outerEdgeUAdj
= outerEdgeUAdj
->cyclicSucc();
1289 OGDF_ASSERT(outerEdgeUAdj
!= adjRun
)
1290 //outerEdgeUAdj points outwards, edge does too per implementation,
1291 //but we don't want to rely on that fact
1293 edge potKill
= outerEdgeUAdj
->theEdge();
1295 if (potKill
->source() == outerEdgeUAdj
->theNode()) //Could use opposite
1297 splitter
= potKill
->target();
1302 splitter
= potKill
->source();
1305 //we erase bends and should check the node type here, but the only
1306 //case that can happen is bend
1307 while (splitter
->degree() == 2)
1311 PG
.unsplit(potKill
, potKill
->adjTarget()->cyclicSucc()->theEdge());
1312 splitter
= potKill
->target();
1316 edge ek
= potKill
->adjSource()->cyclicSucc()->theEdge();
1317 PG
.unsplit(ek
, potKill
);
1319 splitter
= potKill
->source();
1323 adjURun
= adjURun
->cyclicPred(); //counterclockwise, Succ clockwise
1324 } while (adjURun
!= uCopy
->firstAdj());
1326 //----------------------------------------------
1328 //check if node is better suited to lie at the right position
1331 if (drawing
.x(PG
.copy(u
)) > drawing
.x(PG
.copy(rightNode
)))
1341 adjRun
= adjRun
->cyclicPred(); //counterclockwise, Succ clockwise
1342 } while (adjRun
!= cCopy
->firstAdj());
1344 //---------------------------------------
1345 //adjust ordering to start with rightNode
1346 //if (true) //zum debuggen ausschalten koennen
1347 while (adjNodes
.front() != rightNode
)
1349 node tempV
= adjNodes
.popFrontRet();
1350 adjNodes
.pushBack(tempV
);
1354 } // end namespace ogdf