Don't import ogdf namespace
[TortoiseGit.git] / ext / OGDF / src / planarity / PlanarizationLayout.cpp
blobca4a9bdea67be8f0cefaa3df83d746b5099d4740
1 /*
2 * $Revision: 2616 $
4 * last checkin:
5 * $Author: gutwenger $
6 * $Date: 2012-07-16 15:34:43 +0200 (Mo, 16. Jul 2012) $
7 ***************************************************************/
9 /** \file
10 * \brief implementation of class PlanarizationLayout.
12 * applies planarization approach for drawing UML diagrams
13 * by calling a planar layouter for every planarized connected
14 * component
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
21 * \par License:
22 * This file is part of the Open Graph Drawing Framework (OGDF).
24 * \par
25 * Copyright (C)<br>
26 * See README.txt in the root directory of the OGDF installation for details.
28 * \par
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
33 * for details.
35 * \par
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.
41 * \par
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>
63 namespace ogdf {
65 //Constructor:
66 //set default values for parameters and set planar layouter,
67 //planarization modules
68 PlanarizationLayout::PlanarizationLayout()
70 //modules
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);
77 //parameters
78 m_pageRatio = 1.0;
79 m_processCliques = false; //do not search for cliques, only if not uml
80 m_cliqueSize = 10;
81 m_fakeTree = true; //change edge type from generalization to association
85 void PlanarizationLayout::reembed(PlanRepUML &PG, int ccNumber, bool l_align,
86 bool l_gensExist)
88 //TODO: update by reinitialization?
89 //PG.initActiveCC(i);
90 //first we remove all inserted crossings
91 node v;
92 List<node> crossings;
93 forall_nodes(v, PG)
95 if (PG.isCrossingType(v))
97 crossings.pushBack(v);
99 }//forallnodes
100 ListIterator<node> it = crossings.begin();
101 while (it.valid())
103 PG.removeCrossing((*it));
104 it++;
105 }//while
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
112 // preferedEdges.
113 List<edge> preferedEdges;
114 edge e;
115 EdgeArray<int> costOrig(PG.original(), 1);
116 forall_edges(e,PG)
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
124 if ( (l_align) &&
125 ((ori && (PG.typeOf(e->target()) == Graph::generalizationMerger))
126 || (PG.alignUpward(e->adjSource()))
129 costOrig[ori] = 10;
131 }//generalization
132 }//foralledges
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
152 // as external 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())
159 planarEmbed(PG);
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();
174 }//reembed
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 &umlGraph)
184 m_nCrossings = 0;
186 if(((const Graph &)umlGraph).empty())
187 return;
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
201 int i;
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
224 PG.initCC(i);
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
242 bool embedded;
243 TopologyModule TM;
244 try {
245 embedded = TM.setEmbeddingFromGraph(PG, umlGraph, adjExternal, umlMerge);
246 }//try
247 catch (...)
249 //TODO: check for graph changes that are not undone
250 embedded = false;
254 //-------------------------------------------------
255 //if not embedded correctly
256 if (!embedded)
258 reembed(PG, i, l_align, l_gensExist);
259 }//if !embedded
261 //-------------------------------------------------
263 CombinatorialEmbedding E(PG);
265 if (!umlMerge)
266 PG.setupIncremental(i, E);
269 // determine embedding of PG
272 // We currently compute any embedding and choose the maximal face
273 // as external 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 //*********************************************************
296 Layout drawing(PG);
298 //distinguish between CC's with/without generalizations
299 //this changes the input layout modules options!
300 if (l_gensExist)
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
315 //all edges
316 for(itV = origInCC.begin(); itV.valid(); ++itV)
318 node vG = *itV;
320 umlGraph.x(vG) = drawing.x(PG.copy(vG));
321 umlGraph.y(vG) = drawing.y(PG.copy(vG));
323 adjEntry adj;
324 forall_adj(adj,vG)
326 if ((adj->index() & 1) == 0) continue;
327 edge eG = adj->theEdge();
329 drawing.computePolylineClear(PG,eG,umlGraph.bends(eG));
330 }//foralladj
331 }//for orig nodes
333 if (!umlMerge)
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
345 if (adjMerger != 0)
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();
360 continue;
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());
380 if (eOrig)
381 //incoming merger edges always have an original here!
382 umlGraph.bends(eOrig).pushBack(DPoint(drawing.x(vMerger),
383 drawing.y(vMerger)));
386 }//forall adj
388 itMerger++;
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();
395 }//for cc's
397 postProcess(umlGraph);
399 //----------------------------------------
400 // Arrange layouts of connected components
401 //----------------------------------------
403 arrangeCCs(PG, umlGraph, boundingBox);
405 umlGraph.undoGenMergers();
406 umlGraph.removeUnnecessaryBendsHV();
407 }//callfixembed
411 //-----------------------------------------------------------------------------
412 //call function: compute an UML layout for graph umlGraph
413 //-----------------------------------------------------------------------------
414 void PlanarizationLayout::call(UMLGraph &umlGraph)
416 m_nCrossings = 0;
418 if(((const Graph &)umlGraph).empty())
419 return;
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
446 int i;
447 for(i = 0; i < numCC; ++i)
449 // we treat connected component i
450 // PG is set to a copy of this CC
451 PG.initCC(i);
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
465 // preferredEdges.
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
471 // avoid crossings
472 EdgeArray<Graph::EdgeType> savedType(PG);
473 EdgeArray<Graph::EdgeType> savedOrigType(PG.original()); //for deleted copies
475 List<edge> preferedEdges;
476 edge e;
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);
480 forall_edges(e,PG)
482 edge ori = PG.original(e);
483 if (m_processCliques)
485 savedType[e] = PG.typeOf(e);
486 if (ori)
488 if (umlGraph.isReplacement(ori))
490 preferedEdges.pushBack(e);
491 costOrig[ori] = 10;
492 PG.setGeneralization(e);
493 noCrossingEdge[ori] = true;
494 continue;
495 }//if clique replacement
497 }//if
498 }//if cliques
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
507 if (l_align && (
508 (ori && (PG.typeOf(e->target()) == Graph::generalizationMerger))
509 || PG.alignUpward(e->adjSource())
511 costOrig[ori] = 10;
513 }//generalization
514 }//foralledges
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)))
529 itEdge++;
530 }//while
531 }//if cliques
533 //--------------------------------------
534 // second phase: Re-insert deleted edges
535 //--------------------------------------
537 if (m_processCliques)
538 m_inserter.get().call(PG, costOrig, noCrossingEdge, deletedEdges);
539 else
540 m_inserter.get().callForbidCrossingGens(PG, costOrig, deletedEdges);
542 //reset the changed edge types
543 if (m_processCliques)
545 forall_edges(e, PG)
547 //if replacement
548 edge ori = PG.original(e);
549 if (ori)
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;
556 }//foralledges
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();
564 while (it2.valid())
566 PG.setType(*it2, savedOrigType[*itEdge]);
567 it2++;
568 }//while
569 itEdge++;
570 }//while
571 }//if cliques
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())
583 // {
584 // node cent = PG.copy(*itDV);
585 // OGDF_ASSERT(cent->degree() == (*itDV)->degree())
586 // adjEntry adj;
587 // forall_adj(adj, cent)
588 // {
589 // OGDF_ASSERT(PG.original(adj->twinNode()))
590 // OGDF_ASSERT(!(PG.isGeneralization(adj->theEdge())))
591 // PG.setBrother(adj->theEdge());//only output coloring
593 // }//foralladj
594 // itDV++;
595 // }//while
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
607 // as external 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())
614 planarEmbed(PG);
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
636 //boundary)
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);
643 itNode++;
646 }//if cliques
648 Layout drawing(PG);
650 //distinguish between CC's with/without generalizations
651 //this changes the input layout modules options!
652 if (l_gensExist)
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;
684 if (adjBoundary)
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
689 do {
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))
700 //bend
701 if (adjRunner->twinNode()->degree() < 4)
702 adjRunner = adjRunner->faceCycleSucc();
703 else adjRunner = adjRunner->faceCycleSucc()->cyclicPred();
704 } while (adjRunner != adjBoundary);
705 }//if boundary exists
706 else
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;
718 }//else
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
729 //test
730 //---------------------------------------------------------
731 //derive the ordering of the nodes around centerNode in the
732 //planarized copy
733 List<node> adjNodes;
734 fillAdjNodes(adjNodes, PG, centerNode, isClique, drawing);
736 //-----------------------------
737 //compute clique node positions
738 umlGraph.computeCliquePosition(adjNodes, centerNode,
739 min(maxx-minx,maxy-miny));
740 //testend
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
748 //the clique
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;
758 itNode++;
759 }//while
761 //simple strategy to move anchor positions too (they are not needed:
762 // move to same position)
763 node w;
764 forall_nodes(w, PG)
766 //forall clique nodes shift the anchor points
767 if (isClique[w])
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());
781 }//if cliques
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) {
790 node vG = *itV;
792 umlGraph.x(vG) = drawing.x(PG.copy(vG));
793 umlGraph.y(vG) = drawing.y(PG.copy(vG));
795 adjEntry adj;
796 forall_adj(adj,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();
807 }//for cc's
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);
820 }//call
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)
831 m_nCrossings = 0;
833 if(pGA->constGraph().empty())
834 return;
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
844 int i;
845 for(i = 0; i < numCC; ++i)
847 // we treat connected component i
848 // PG is set to a copy of this CC
849 pPG->initCC(i);
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
859 // preferredEdges.
861 List<edge> preferedEdges;
862 edge e;
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) {
904 node vG = *itV;
906 pGA->x(vG) = drawing.x(pPG->copy(vG));
907 pGA->y(vG) = drawing.y(pPG->copy(vG));
909 adjEntry adj;
910 forall_adj(adj,vG) {
911 if ((adj->index() & 1) == 0)
912 continue;
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();
921 }//for cc's
923 //----------------------------------------
924 // Arrange layouts of connected components
925 //----------------------------------------
927 arrangeCCs(*pPG, *pGA, boundingBox);
928 delete pPG;
929 }//simplecall
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 &umlGraph)
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;
944 m_nCrossings = 0;
946 const Graph &G = umlGraph.constGraph();
947 if(G.empty())
948 return;
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);
957 int i;
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
966 PG.initCC(i);
968 int nOrigVerticesPG = PG.numberOfNodes();
970 edge e;
971 EdgeArray<int> costOrig(PG.original(), 1);
972 EdgeArray<unsigned int> esgOrig(PG.original(), 0);
973 forall_edges(e, G)
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 //*********************************************************
1000 Layout drawing(PG);
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) {
1013 node vG = *itV;
1015 umlGraph.x(vG) = drawing.x(PG.copy(vG));
1016 umlGraph.y(vG) = drawing.y(PG.copy(vG));
1018 adjEntry adj;
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();
1031 }//for cc's
1033 //----------------------------------------
1034 // Arrange layouts of connected components
1035 //----------------------------------------
1037 arrangeCCs(PG, umlGraph, boundingBox);
1039 umlGraph.removeUnnecessaryBendsHV();
1040 m_processCliques = l_saveCliqueHandling;
1041 }//callSimDraw
1044 //#################################################################
1046 //additional help functions
1048 void PlanarizationLayout::assureDrawability(UMLGraph &UG)
1050 //preliminary
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
1057 edge e;
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);
1069 else {
1070 ListConstIterator<edge> itE = m_fakedGens.begin();
1071 while (itE.valid()) {
1072 UG.type(*itE) = Graph::association;
1073 itE++;
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;
1088 CliqueFinder cf(G);
1089 cf.setMinSize(m_cliqueSize);
1091 List< List<node> > cliques;
1092 cf.call(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)
1098 else
1100 const SListPure<UMLGraph::AssociationClass*> &acList = UG.assClassList();
1101 SListConstIterator<UMLGraph::AssociationClass*> it = acList.begin();
1102 while (it.valid())
1104 UG.modelAssociationClass((*it));
1105 it++;
1109 }//preprocess
1112 void PlanarizationLayout::postProcess(UMLGraph& UG)
1114 //reset the type of faked associations to generalization
1115 if (m_fakeTree)
1117 ListIterator<edge> itE = m_fakedGens.begin();
1118 while (itE.valid())
1120 UG.type(*itE) = Graph::generalization;
1121 itE++;
1125 UG.undoAssociationClasses();
1126 if (m_processCliques)
1127 UG.undoStars();
1128 }//postProcess
1131 // find best suited external face according to certain criteria
1132 face PlanarizationLayout::findBestExternalFace(
1133 const PlanRep &PG,
1134 const CombinatorialEmbedding &E)
1136 FaceArray<int> weight(E);
1138 face f;
1139 forall_faces(f,E)
1140 weight[f] = f->size();
1142 node v;
1143 forall_nodes(v,PG)
1145 if(PG.typeOf(v) != Graph::generalizationMerger)
1146 continue;
1148 adjEntry adj;
1149 forall_adj(adj,v) {
1150 if(adj->theEdge()->source() == v)
1151 break;
1154 OGDF_ASSERT(adj->theEdge()->source() == v);
1156 node w = adj->theEdge()->target();
1157 bool isBase = true;
1159 adjEntry adj2;
1160 forall_adj(adj2, w) {
1161 edge e = adj2->theEdge();
1162 if(e->target() != w && PG.typeOf(e) == Graph::generalization) {
1163 isBase = false;
1164 break;
1168 if(isBase == false)
1169 continue;
1171 face f1 = E.leftFace(adj);
1172 face f2 = E.rightFace(adj);
1174 weight[f1] += v->indeg();
1175 if(f2 != f1)
1176 weight[f2] += v->indeg();
1179 face fBest = E.firstFace();
1180 forall_faces(f,E)
1181 if(weight[f] > weight[fBest])
1182 fBest = f;
1184 return 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)
1208 node v = *it;
1210 GA.x(v) += dx;
1211 GA.y(v) += dy;
1213 adjEntry adj;
1214 forall_adj(adj,v) {
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) {
1221 (*it).m_x += dx;
1222 (*it).m_y += dy;
1230 //collects and stores nodes adjacent to centerNode in adjNodes
1231 void PlanarizationLayout::fillAdjNodes(List<node>& adjNodes,
1232 PlanRepUML& PG,
1233 node centerNode,
1234 NodeArray<bool>& isClique,
1235 Layout& drawing)
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
1244 node rightNode = 0;
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
1250 //attached edges
1252 adjEntry adjRun = cCopy->firstAdj();
1255 //we search for the edge outside the node cage
1256 //anchor node
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
1263 //always is one!?
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
1278 //look at
1279 node uCopy = PG.copy(u);
1280 OGDF_ASSERT(uCopy)
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
1292 bool outwards;
1293 edge potKill = outerEdgeUAdj->theEdge();
1294 node splitter;
1295 if (potKill->source() == outerEdgeUAdj->theNode()) //Could use opposite
1297 splitter = potKill->target();
1298 outwards = true;
1300 else
1302 splitter = potKill->source();
1303 outwards = false;
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)
1309 if (outwards)
1311 PG.unsplit(potKill, potKill->adjTarget()->cyclicSucc()->theEdge());
1312 splitter = potKill->target();
1314 else
1316 edge ek = potKill->adjSource()->cyclicSucc()->theEdge();
1317 PG.unsplit(ek, potKill);
1318 potKill = ek;
1319 splitter = potKill->source();
1321 }//while
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
1329 if (rightNode != 0)
1331 if (drawing.x(PG.copy(u)) > drawing.x(PG.copy(rightNode)))
1333 rightNode = u;
1336 else
1338 rightNode = u;
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);
1352 }//filladjNodes
1354 } // end namespace ogdf