// constructive heuristics for orthogonal representation OR void LongestPathCompaction::constructiveHeuristics( PlanRep &PG, OrthoRep &OR, const RoutingChannel<int> &rc, GridLayoutMapped &drawing) { OGDF_ASSERT(OR.isOrientated()); // x-coordinates of vertical segments CompactionConstraintGraph<int> Dx(OR, PG, odEast, rc.separation()); Dx.insertVertexSizeArcs(PG, drawing.width(), rc); NodeArray<int> xDx(Dx.getGraph(), 0); computeCoords(Dx, xDx); // y-coordinates of horizontal segments CompactionConstraintGraph<int> Dy(OR, PG, odNorth, rc.separation()); Dy.insertVertexSizeArcs(PG, drawing.height(), rc); NodeArray<int> yDy(Dy.getGraph(), 0); computeCoords(Dy, yDy); // final coordinates of vertices for(node v : PG.nodes) { drawing.x(v) = xDx[Dx.pathNodeOf(v)]; drawing.y(v) = yDy[Dy.pathNodeOf(v)]; } }
// improvement heuristics for orthogonal drawing void LongestPathCompaction::improvementHeuristics( PlanRep &PG, OrthoRep &OR, const RoutingChannel<int> &rc, GridLayoutMapped &drawing) { OGDF_ASSERT(OR.isOrientated()); int costs, lastCosts; int steps = 0, maxSteps = m_maxImprovementSteps; if (maxSteps == 0) maxSteps = numeric_limits<int>::max(); // OPTIMIZATION POTENTIAL: // update constraint graphs "incrementally" by only re-inserting // visibility arcs costs = 0; do { lastCosts = costs; ++steps; // x-coordinates of vertical segments CompactionConstraintGraph<int> Dx(OR, PG, odEast, rc.separation()); Dx.insertVertexSizeArcs(PG, drawing.width(), rc); Dx.insertVisibilityArcs(PG, drawing.x(),drawing.y()); NodeArray<int> xDx(Dx.getGraph(), 0); computeCoords(Dx, xDx); // final x-coordinates of vertices for(node v : PG.nodes) { drawing.x(v) = xDx[Dx.pathNodeOf(v)]; } // y-coordinates of horizontal segments CompactionConstraintGraph<int> Dy(OR, PG, odNorth, rc.separation()); Dy.insertVertexSizeArcs(PG, drawing.height(), rc); Dy.insertVisibilityArcs(PG, drawing.y(),drawing.x()); NodeArray<int> yDy(Dy.getGraph(), 0); computeCoords(Dy, yDy); // final y-coordinates of vertices for(node v : PG.nodes) { drawing.y(v) = yDy[Dy.pathNodeOf(v)]; } costs = Dx.computeTotalCosts(xDx) + Dy.computeTotalCosts(yDy); } while (steps < maxSteps && (steps == 1 || costs < lastCosts)); }
void PlanRep::collapseVertices(const OrthoRep &OR, GridLayout &drawing) { for (node v : nodes) { const OrthoRep::VertexInfoUML *vi = OR.cageInfo(v); if(vi == nullptr || (typeOf(v) != Graph::highDegreeExpander && typeOf(v) != Graph::lowDegreeExpander)) continue; node vOrig = original(v); OGDF_ASSERT(vOrig != 0); node vCenter = newNode(); m_vOrig[vCenter] = vOrig; m_vCopy[vOrig] = vCenter; m_vOrig[v] = nullptr; node lowerLeft = vi->m_corner[odNorth]->theNode(); node lowerRight = vi->m_corner[odWest ]->theNode(); node upperLeft = vi->m_corner[odEast ]->theNode(); drawing.x(vCenter) = (drawing.x(lowerLeft)+drawing.x(lowerRight)) >> 1; drawing.y(vCenter) = (drawing.y(lowerLeft)+drawing.y(upperLeft )) >> 1; edge eOrig; forall_adj_edges(eOrig,vOrig) { if(eOrig->target() == vOrig) { node connect = m_eCopy[eOrig].back()->target(); edge eNew = newEdge(connect,vCenter); m_eOrig[eNew] = eOrig; m_eIterator[eNew] = m_eCopy[eOrig].pushBack(eNew); } else { node connect = m_eCopy[eOrig].front()->source(); edge eNew = newEdge(vCenter,connect); m_eOrig[eNew] = eOrig; m_eIterator[eNew] = m_eCopy[eOrig].pushFront(eNew); } } } }
/**--------------------------------------------------- calling function taking the planar representation, the external face (adjentry), the layout to be filled, a list of non-planar edges, a list of inserted edges and the original graph as input */ void ClusterOrthoLayout::call(ClusterPlanRep &PG, adjEntry adjExternal, Layout &drawing, List<NodePair>& npEdges, List<edge>& newEdges, Graph& originalGraph) { // We don't care about UML hierarchies and therefore do not allow alignment OGDF_ASSERT(!m_align); // if we have only one vertex in PG ... if(PG.numberOfNodes() == 1) { node v1 = PG.firstNode(); node vOrig = PG.original(v1); double w = PG.widthOrig(vOrig); double h = PG.heightOrig(vOrig); drawing.x(v1) = m_margin + w/2; drawing.y(v1) = m_margin + h/2; m_boundingBox = DPoint(w + 2*m_margin, h + 2*m_margin); return; } OGDF_ASSERT(PG.representsCombEmbedding()) //------------------------- // insert cluster boundaries PG.ModelBoundaries(); OGDF_ASSERT(PG.representsCombEmbedding()) //-------------------------- // insert non-planar edges CombinatorialEmbedding* CE = new CombinatorialEmbedding(PG); if (!npEdges.empty()) { CPlanarEdgeInserter CEI; CEI.call(PG, *CE, originalGraph, npEdges, newEdges); }//if //------------------------------------------------------------ // now we set the external face, currently to the largest face adjEntry extAdj = 0; int maximum = 0; edge e, eSucc; for(e = PG.firstEdge(); e; e = eSucc) { eSucc = e->succ(); if ( PG.clusterOfEdge(e) == PG.getClusterGraph().rootCluster() ) { int asSize = CE->rightFace(e->adjSource())->size(); if ( asSize > maximum) { maximum = asSize; extAdj = e->adjSource(); } int atSize = CE->rightFace(e->adjTarget())->size(); if ( atSize > maximum) { maximum = atSize; extAdj = e->adjTarget(); } }//if root edge }//for delete CE; //returns adjEntry in rootcluster adjExternal = extAdj; OGDF_ASSERT(adjExternal != 0); //---------------------------------------------------------- //Compaction scaling: help node cages to pass by each other: //First, the layout is blown up and then shrunk again in several steps //We change the separation value and save the original value. double l_orsep = m_separation; if (m_useScalingCompaction) { double scaleFactor = double(int(1 << m_scalingSteps)); m_separation = scaleFactor*m_separation; //reduce this step by step in compaction }//if scaling //*********************************** // PHASE 1: determine orthogonal shape //------------------------------------------------------- // expand high-degree vertices and generalization mergers PG.expand(); // get combinatorial embedding CombinatorialEmbedding E(PG); E.setExternalFace(E.rightFace(adjExternal)); // orthogonal shape representation OrthoRep OR; ClusterOrthoShaper COF; //set some options COF.align(false); //cannot be used yet with clusters COF.traditional(m_orthoStyle > 0 ? false : true); //prefer 90/270 degree angles over 180/180 //bend cost depends on cluster depths avoiding unnecessary "inner" bends COF.bendCostTopDown(ClusterOrthoShaper::topDownCost); // New Call //COF.call(PG,E,OR,2); // Original call without bend bounds(still valid) COF.call(PG, E, OR); String msg; OGDF_ASSERT(OR.check(msg)) //****************************************************************** // PHASE 2: construction of a feasible drawing of the expanded graph //--------------------------- // expand low degree vertices PG.expandLowDegreeVertices(OR); OGDF_ASSERT(PG.representsCombEmbedding()); //------------------ // restore embedding E.computeFaces(); E.setExternalFace(E.rightFace(adjExternal)); OGDF_ASSERT(OR.check(msg)) //---------- //COMPACTION //-------------------------- // apply constructive compaction heuristics OR.normalize(); OR.dissect(); OR.orientate(PG,m_preferedDir); OGDF_ASSERT(OR.check(msg)) // compute cage information and routing channels OR.computeCageInfoUML(PG); //temporary grid layout GridLayoutMapped gridDrawing(PG,OR,m_separation,m_cOverhang,4); RoutingChannel<int> rcGrid(PG,gridDrawing.toGrid(m_separation),m_cOverhang); rcGrid.computeRoutingChannels(OR, m_align); node v; const OrthoRep::VertexInfoUML *pInfoExp; forall_nodes(v,PG) { pInfoExp = OR.cageInfo(v); if (pInfoExp) break; }
void OrthoLayoutUML::call(PlanRepUML &PG, adjEntry adjExternal, Layout &drawing) { // if we have only one vertex in PG ... if(PG.numberOfNodes() == 1) { node v1 = PG.firstNode(); node vOrig = PG.original(v1); double w = PG.widthOrig(vOrig); double h = PG.heightOrig(vOrig); drawing.x(v1) = m_margin + w/2; drawing.y(v1) = m_margin + h/2; m_boundingBox = DPoint(w + 2*m_margin, h + 2*m_margin); return; } //classify brother-to-brother hierarchy edges to allow alignment if (m_align) { classifyEdges(PG, adjExternal); }//if align //compaction with scaling: help node cages to pass by each other double l_orsep = m_separation; if (m_useScalingCompaction) { m_scalingSteps = 6; double scaleFactor = double(int(1 << m_scalingSteps)); m_separation = scaleFactor*m_separation; //reduce this step by step in compaction }//if scaling //*********************************** // PHASE 1: determine orthogonal shape // expand high-degree vertices and generalization mergers PG.expand(); //check preconditions, currently not necessary //assureDrawability(PG); // get combinatorial embedding CombinatorialEmbedding E(PG); E.setExternalFace(E.rightFace(adjExternal)); // determine orthogonal shape OrthoRep OR; //OrthoFormerUML OF; OrthoShaper OFG; //set some options OFG.align(m_align); //align brother objects on hierarchy levels OFG.traditional(m_orthoStyle > 0 ? false : true); //prefer 90/270 degree angles over 180/180 // New Call OFG.setBendBound(m_bendBound); OFG.call(PG,E,OR); // remove face splitter edge e, eSucc; for(e = PG.firstEdge(); e; e = eSucc) { eSucc = e->succ(); if(PG.faceSplitter(e)) { OR.angle(e->adjSource()->cyclicPred()) = 2; OR.angle(e->adjTarget()->cyclicPred()) = 2; PG.delEdge(e); } } //****************************************************************** // PHASE 2: construction of a feasible drawing of the expanded graph // expand low degree vertices PG.expandLowDegreeVertices(OR); OGDF_ASSERT(PG.representsCombEmbedding()); // restore embedding E.computeFaces(); E.setExternalFace(E.rightFace(adjExternal)); // apply constructive compaction heuristics OR.normalize(); OR.dissect2(&PG); //OR.dissect(); OR.orientate(PG,m_preferedDir); // compute cage information and routing channels OR.computeCageInfoUML(PG); // adjust value of cOverhang if(m_cOverhang < 0.05) m_cOverhang = 0.0; if(m_cOverhang > 0.5) m_cOverhang = 0.5; //temporary grid layout GridLayoutMapped gridDrawing(PG,OR,m_separation,m_cOverhang,2); RoutingChannel<int> rcGrid(PG,gridDrawing.toGrid(m_separation),m_cOverhang); rcGrid.computeRoutingChannels(OR, m_align); node v; const OrthoRep::VertexInfoUML *pInfoExp; forall_nodes(v, PG) { pInfoExp = OR.cageInfo(v); if (pInfoExp) break; }
void PlanRep::writeGML(ostream &os, const OrthoRep &OR, const GridLayout &drawing) { const Graph &G = *this; NodeArray<int> id(*this); int nextId = 0; os.setf(ios::showpoint); os.precision(10); os << "Creator \"ogdf::GraphAttributes::writeGML\"\n"; os << "graph [\n"; os << " directed 1\n"; for(node v : G.nodes) { os << " node [\n"; os << " id " << (id[v] = nextId++) << "\n"; os << " label \"" << v->index() << "\"\n"; os << " graphics [\n"; os << " x " << ((double) drawing.x(v)) << "\n"; os << " y " << ((double) drawing.y(v)) << "\n"; os << " w " << 3.0 << "\n"; os << " h " << 3.0 << "\n"; os << " type \"rectangle\"\n"; os << " width 1.0\n"; if (typeOf(v) == Graph::generalizationMerger) { os << " type \"oval\"\n"; os << " fill \"#0000A0\"\n"; } else if (typeOf(v) == Graph::generalizationExpander) { os << " type \"oval\"\n"; os << " fill \"#00FF00\"\n"; } else if (typeOf(v) == Graph::highDegreeExpander || typeOf(v) == Graph::lowDegreeExpander) os << " fill \"#FFFF00\"\n"; else if (typeOf(v) == Graph::dummy) os << " type \"oval\"\n"; else if (v->degree() > 4) os << " fill \"#FFFF00\"\n"; else os << " fill \"#000000\"\n"; os << " ]\n"; // graphics os << " ]\n"; // node } for (node v : nodes) { if (expandAdj(v) != nullptr && (typeOf(v) == Graph::highDegreeExpander || typeOf(v) == Graph::lowDegreeExpander)) { node vOrig = original(v); const OrthoRep::VertexInfoUML &vi = *OR.cageInfo(v); node ll = vi.m_corner[odNorth]->theNode(); node ur = vi.m_corner[odSouth]->theNode(); os << " node [\n"; os << " id " << nextId++ << "\n"; if (m_pGraphAttributes->attributes() & GraphAttributes::nodeLabel) { os << " label \"" << m_pGraphAttributes->label(vOrig) << "\"\n"; } os << " graphics [\n"; os << " x " << 0.5 * (drawing.x(ur) + drawing.x(ll)) << "\n"; os << " y " << 0.5 * (drawing.y(ur) + drawing.y(ll)) << "\n"; os << " w " << widthOrig(vOrig) << "\n"; os << " h " << heightOrig(vOrig) << "\n"; os << " type \"rectangle\"\n"; os << " width 1.0\n"; os << " fill \"#FFFF00\"\n"; os << " ]\n"; // graphics os << " ]\n"; // node } } for(edge e : G.edges) { os << " edge [\n"; os << " source " << id[e->source()] << "\n"; os << " target " << id[e->target()] << "\n"; os << " generalization " << typeOf(e) << "\n"; os << " graphics [\n"; os << " type \"line\"\n"; if (typeOf(e) == Graph::generalization) { if (typeOf(e->target()) == Graph::generalizationExpander) os << " arrow \"none\"\n"; else os << " arrow \"last\"\n"; os << " fill \"#FF0000\"\n"; os << " width 2.0\n"; } else { if (typeOf(e->source()) == Graph::generalizationExpander || typeOf(e->source()) == Graph::generalizationMerger || typeOf(e->target()) == Graph::generalizationExpander || typeOf(e->target()) == Graph::generalizationMerger) { os << " arrow \"none\"\n"; os << " fill \"#FF0000\"\n"; } else if (original(e) == nullptr) { os << " arrow \"none\"\n"; os << " fill \"#AFAFAF\"\n"; } else os << " arrow \"none\"\n"; if (isBrother(e)) os << " fill \"#00AF0F\"\n"; if (isHalfBrother(e)) os << " fill \"#0F00AF\"\n"; os << " width 1.0\n"; }//else generalization os << " ]\n"; // graphics os << " ]\n"; // edge } os << "]\n"; // graph }
void PlanRep::expandLowDegreeVertices(OrthoRep &OR) { for(node v : nodes) { if (!(isVertex(v)) || expandAdj(v) != nullptr) continue; SList<edge> adjEdges; SListPure<Tuple2<node,int> > expander; node u = v; bool firstTime = true; setExpandedNode(v, v); for(adjEntry adj : v->adjEdges) { adjEdges.pushBack(adj->theEdge()); if(!firstTime) u = newNode(); setExpandedNode(u, v); typeOf(u) = Graph::lowDegreeExpander; expander.pushBack(Tuple2<node,int>(u,OR.angle(adj))); firstTime = false; } SListConstIterator<Tuple2<node,int>> itn = expander.begin().succ(); for (SListConstIterator<edge> it = adjEdges.begin().succ(); it.valid(); ++it) { // Did we allocate enough dummy nodes? OGDF_ASSERT(itn.valid()); if ((*it)->source() == v) moveSource(*it,(*itn).x1()); else moveTarget(*it,(*itn).x1()); ++itn; } adjEntry adjPrev = v->firstAdj(); itn = expander.begin(); int nBends = (*itn).x2(); for (++itn; itn.valid(); ++itn) { edge e = newEdge(adjPrev,(*itn).x1()->firstAdj()); OR.bend(e->adjSource()).set(convexBend,nBends); OR.bend(e->adjTarget()).set(reflexBend,nBends); OR.angle(adjPrev) = 1; OR.angle(e->adjSource()) = 2; OR.angle(e->adjTarget()) = 1; nBends = (*itn).x2(); typeOf(e) = association; //??? setExpansionEdge(e, 2); adjPrev = (*itn).x1()->firstAdj(); } edge e = newEdge(adjPrev,v->lastAdj()); typeOf(e) = association; //??? setExpansionEdge(e, 2); expandAdj(v) = e->adjSource(); OR.bend(e->adjSource()).set(convexBend,nBends); OR.bend(e->adjTarget()).set(reflexBend,nBends); OR.angle(adjPrev) = 1; OR.angle(e->adjSource()) = 2; OR.angle(e->adjTarget()) = 1; } }//expandlowdegreevertices