//read all cluster tree information bool GmlParser::clusterRead(GmlObject* rootCluster, ClusterGraph& CG) { //the root cluster is only allowed to hold child clusters and //nodes in a list if (rootCluster->m_valueType != gmlListBegin) return false; // read all clusters and nodes GmlObject *rootClusterSon = rootCluster->m_pFirstSon; for(; rootClusterSon; rootClusterSon = rootClusterSon->m_pBrother) { switch(id(rootClusterSon)) { case clusterPredefKey: { //we could delete this, but we aviod the call if (rootClusterSon->m_valueType != gmlListBegin) return false; // set attributes to default values //we currently do not set any values cluster c = CG.newCluster(CG.rootCluster()); //recursively read cluster recursiveClusterRead(rootClusterSon, CG, c); } //case cluster break; case vertexPredefKey: //direct root vertices { if (rootClusterSon->m_valueType != gmlStringValue) return false; String vIDString = rootClusterSon->m_stringValue; //we only allow a vertex id as string identification if ((vIDString[0] != 'v') && (!isdigit(vIDString[0])))return false; //do not allow labels //if old style entry "v"i if (!isdigit(vIDString[0])) //should check prefix? vIDString[0] = '0'; //leading zero to allow conversion int vID = atoi(vIDString.cstr()); OGDF_ASSERT(m_mapToNode[vID] != 0) //we assume that no node is already assigned ! Changed: //all new nodes are assigned to root //CG.reassignNode(mapToNode[vID], CG.rootCluster()); //it seems that this may be unnessecary, TODO check CG.reassignNode(m_mapToNode[vID], CG.rootCluster()); //char* vIDChar = new char[vIDString.length()+1]; //for (int ind = 1; ind < vIDString.length(); ind++) // vIDChar }//case vertex }//switch }//for all rootcluster sons return true; }//clusterread
ClusterGraphCopy::ClusterGraphCopy(const ExtendedNestingGraph &H, const ClusterGraph &CG) : ClusterGraph(H), m_pCG(&CG), m_pH(&H), m_copy(CG,0) { m_original.init(*this,0); m_copy [CG.rootCluster()] = rootCluster(); m_original[rootCluster()] = CG.rootCluster(); createClusterTree(CG.rootCluster()); }
void ClusterGraphCopy::init(const ExtendedNestingGraph &H, const ClusterGraph &CG) { ClusterGraph::init(H); m_pCG = &CG; m_pH = &H; m_copy .init(CG,0); m_original.init(*this,0); m_copy [CG.rootCluster()] = rootCluster(); m_original[rootCluster()] = CG.rootCluster(); createClusterTree(CG.rootCluster()); }
// Copy Function void ClusterGraph::shallowCopy(const ClusterGraph &C) { const Graph &G = C; m_pGraph = &G; m_nClusters = 0; //m_clusterDepth.init(*this, 0); initGraph(G); m_updateDepth = C.m_updateDepth; m_depthUpToDate = C.m_depthUpToDate; // Construct cluster tree ClusterArray<cluster> originalClusterTable(C); cluster c = 0; forall_clusters(c,C) { if (c == C.m_rootCluster) { originalClusterTable[c] = m_rootCluster; //does not really need to be assigned HERE in for m_rootCluster->depth() = 1; OGDF_ASSERT(C.rootCluster()->depth() == 1) continue; } originalClusterTable[c] = newCluster(); originalClusterTable[c]->depth() = c->depth(); }
bool CconnectClusterPlanar::preProcess(ClusterGraph &C,Graph &G) { if (!isCConnected(C)) { m_errorCode = nonCConnected; return false; } if (!isPlanar(C)) { m_errorCode = nonPlanar; return false; } cluster c; SListPure<node> selfLoops; makeLoopFree(G,selfLoops); c = C.rootCluster(); bool cPlanar = planarityTest(C,c,G); return cPlanar; }
bool CconnectClusterPlanar::preProcess(ClusterGraph &C,Graph &G) { if (!isCConnected(C)) { ogdf::sprintf(errorCode,124,"Graph is not C-connected \n"); m_errorCode = nonCConnected; return false; } PlanarModule Pm; if (!Pm.planarityTest(C)) { ogdf::sprintf(errorCode,124,"Graph is not planar\n"); m_errorCode = nonPlanar; return false; } cluster c; SListPure<node> selfLoops; makeLoopFree(G,selfLoops); c = C.rootCluster(); bool cPlanar = planarityTest(C,c,G); return cPlanar; }
bool GraphIO::writeGEXF(const ClusterGraph &C, std::ostream &out) { gexf::writeHeader(out); gexf::writeCluster(out, 1, C, nullptr, C.rootCluster()); gexf::writeFooter(out); return true; }
// write cluster graph structure with clusters static void write_ogml_graph(const ClusterGraph &C, ostream &os) { GraphIO::indent(os,2) << "<structure>\n"; write_ogml_graph(C.rootCluster(), 0, os); write_ogml_graph_edges(C.constGraph(), os); GraphIO::indent(os,2) << "</structure>\n"; }
static void writeCluster( std::ostream &out, int depth, const ClusterArray < std::vector<edge> > &edgeMap, const ClusterGraph &C, const ClusterGraphAttributes *CA, const cluster &c, int &clusterId) { if(C.rootCluster() == c) { writeHeader(out, depth++, CA); } else { GraphIO::indent(out, depth++) << "subgraph cluster" << clusterId << " {\n"; } clusterId++; bool whitespace; // True if a whitespace should printed (readability). whitespace = false; if(CA) { writeAttributes(out, depth, *CA, c); whitespace = true; } if(whitespace) { out << "\n"; } // Recursively export all subclusters. whitespace = false; for(ListConstIterator<cluster> cit = c->cBegin(); cit.valid(); ++cit) { writeCluster(out, depth, edgeMap, C, CA, *cit, clusterId); whitespace = true; } if(whitespace) { out << "\n"; } // Then, print all nodes whithout an adjacent edge. whitespace = false; for(ListConstIterator<node> nit = c->nBegin(); nit.valid(); ++nit) { whitespace |= writeNode(out, depth, CA, *nit); } if(whitespace) { out << "\n"; } // Finally, we print all edges for this cluster (ugly version for now). const std::vector<edge> &edges = edgeMap[c]; whitespace = false; for(size_t i = 0; i < edges.size(); i++) { whitespace |= writeEdge(out, depth, CA, edges[i]); } GraphIO::indent(out, --depth) << "}\n"; }
bool GraphMLParser::read(Graph &G, ClusterGraph &C, ClusterGraphAttributes &CA) { if(m_error) { return false; } G.clear(); m_nodeId.clear(); return readClusters(G, C, &CA, C.rootCluster(), m_graphTag); }
bool GraphMLParser::read(Graph &G, ClusterGraph &C) { if(m_error) { return false; } G.clear(); m_nodeId.clear(); return readClusters(G, C, nullptr, C.rootCluster(), m_graphTag); }
static void writeCluster( std::ostream &out, int depth, const ClusterGraph &C, const ClusterGraphAttributes *CA, cluster c) { if(C.rootCluster() != c) { GraphIO::indent(out, depth) << "<node " << "id=\"cluster" << c->index() << "\"" << ">\n"; } else { const std::string dir = (CA && !CA->directed()) ? "undirected" : "directed"; GraphIO::indent(out, depth) << "<graph " << "mode=\"static\"" << "defaultedgetype=\"" << dir << "\"" << ">\n"; if(CA) { defineAttributes(out, depth + 1, *CA); } } GraphIO::indent(out, depth + 1) << "<nodes>\n"; for(ListConstIterator<cluster> cit = c->cBegin(); cit.valid(); ++cit) { writeCluster(out, depth + 2, C, CA, *cit); } for(ListConstIterator<node> nit = c->nBegin(); nit.valid(); ++nit) { writeNode(out, depth + 2, CA, *nit); } GraphIO::indent(out, depth + 1) << "</nodes>\n"; if(C.rootCluster() != c) { GraphIO::indent(out, depth) << "</node>\n"; } else { writeEdges(out, C.constGraph(), CA); GraphIO::indent(out, depth) << "</graph>\n"; } }
bool GraphIO::writeDOT(const ClusterGraph &C, std::ostream &out) { const Graph &G = C.constGraph(); int id = 1; // Assign a list of edges for each cluster. Perhaps usage of std::vector // here needs reconsideration - vector is fast but usage of STL iterators // is ugly without C++11 for-each loop. ClusterArray< std::vector<edge> > edgeMap(C); for(edge e : G.edges) { const node s = e->source(), t = e->target(); edgeMap[C.commonCluster(s, t)].push_back(e); } return dot::writeCluster(out, 0, edgeMap, C, nullptr, C.rootCluster(), id); }
//in ClusterGraph?? //is not yet recursive!!! node collapseCluster(ClusterGraph& CG, cluster c, Graph& G) { OGDF_ASSERT(c->cCount() == 0) ListIterator<node> its; SListPure<node> collaps; //we should check here if not empty node robinson = (*(c->nBegin())); for (its = c->nBegin(); its.valid(); its++) collaps.pushBack(*its); CG.collaps(collaps, G); if (c != CG.rootCluster()) CG.delCluster(c); return robinson; }
bool ClusterPlanarity::isClusterPlanar(const ClusterGraph &CG, List<nodePair> &addedEdges) { m_optStatus = Master::Optimal; // We first check if there is more to do then just checking planarity on the // input graph. // Simple shortcut: With < 5 vertices, no non-planarity is possible... bool result = isPlanar(CG.constGraph()); if (!result || (CG.numberOfClusters() == 1)) { // Either non-planar or only root cluster exists, which does not restrict c-planarity. return result; } // We first create a copy of input G, and work solely on the copy // In case of the sm_new solution method, we partition the graph in // independent parts and test them separately // For all parts we test until non-c-planar or all tested. if (m_solmeth==sm_new) { // We use the ClusterAnalysis to search for independent bags // Here is the idea: We detect all bags that are minimum wrt // cluster inclusion (i.e. if a cluster contains a cluster c with // a single bag, we don't add the cluster c itself) but do // not contain an outeractive vertex wrt to smallest containing cluster // (i.e. the cluster used in the definition of bag). // The clustered subgraphs induced by these bags can be tested independently, // as we can move them freely in the drawing area of their enclosing parent cluster. ClusterAnalysis ca(CG, true); //Compute all structures, indyBags too // We can solve the c-planarity testing for all indyBags independently, // and in case all are c-planar, also our input c-graph is c-planar. const int numIndyBags = ca.numberOfIndyBags(); GraphCopy** theGraphs = new GraphCopy * [numIndyBags]; //Stores copies for the bag graphs. #ifdef OGDF_DEBUG cout << "Number of IndyBags "<<numIndyBags<<"\n"; #endif Logger::slout() << "Number of IndyBags "<<numIndyBags<<"\n"; Array<List<node> > nodesInBag(numIndyBags); //Stores vertices for the bag graphs. const Graph & G = CG.constGraph(); for(node v : G.nodes){ nodesInBag[ca.indyBagIndex(v)].pushBack(v); } for (int i = 0; i < numIndyBags && m_optStatus == Master::Optimal; i++) { // Create underlying graph theGraphs[i] = new GraphCopy(); theGraphs[i]->createEmpty(G); // Judging from the interface and the description, there are two // methods in GraphCopy that allow to construct parts based on a // set of vertices, initByNodes and initByActiveNodes, where the // latter one seems to be appropriate and can be used with an // additional 3n work to initialize the NodeArray and mark the vertices. // However, even though the former is meant to be used for connected // components, it also works for set of connected components, and // an independent bag is such a creature. EdgeArray<edge> eCopy(G); theGraphs[i]->initByNodes(nodesInBag[i], eCopy); ClusterGraph bagCG(*theGraphs[i]); //node v; ClusterArray<List<node> > cNodes(CG); ClusterArray<List<cluster> > cChildren(CG); ClusterArray<cluster> cCopy(CG); // Run through all original vertices and store // lists of copies at each cluster that is part of the bag. // Note: We should not add an enclosing parent cluster below // root, i.e., when the root does only have a single child // and no vertices, we delete the child again. for(node u : nodesInBag[i]) { cluster ct = CG.clusterOf(u); cNodes[ct].pushBack(theGraphs[i]->copy(u)); // Check if we need to store the parent relation on the path // to the root. Indicator is: We have just added the first element. while ((ct != CG.rootCluster()) && (cNodes[ct].size() + cChildren[ct].size() == 1)) { cChildren[ct->parent()].pushBack(ct); ct = ct->parent(); } } // Create cluster structure // For each vertex in the indyBag we create the cluster path // to the bag root if necessary. // Now build the cluster structure top down // Lists of root are never both empty List<cluster> queue; queue.pushBack(ca.indyBagRoot(i)); cCopy[queue.front()] = bagCG.rootCluster(); while (!queue.empty()) { cluster c = queue.popFrontRet(); //vertices are assigned to root by construction if (cCopy[c] != bagCG.rootCluster()) { for(node u : cNodes[c]) bagCG.reassignNode(u, cCopy[c]); } for(cluster ci : cChildren[c]) { cCopy[ci] = bagCG.newCluster(cCopy[c]); queue.pushBack(ci); } } #ifdef OGDF_DEBUG Logger::slout() << "Created clustered graph for indy bag with "<<theGraphs[i]->numberOfNodes()<< " nodes and "<< bagCG.numberOfClusters()<<" clusters\n"; // Make sure the cluster structure is a rooted tree cluster t = bagCG.rootCluster(); int ccnt = 0; List<cluster> cqueue; cqueue.pushBack(t); while (!cqueue.empty()) { t = cqueue.popFrontRet(); for(cluster c : t->children) { cqueue.pushBack(c); } ccnt++; } OGDF_ASSERT(ccnt == bagCG.numberOfClusters()); string filename = string("IndySubcgraph") + to_string(i) + ".gml"; ClusterGraphAttributes CGA(bagCG); GraphIO::writeGML(CGA, filename); #endif //now the actual test, similar to the one below... if (theGraphs[i]->numberOfNodes() > 2) //could even start at 4... { makeParallelFreeUndirected(*theGraphs[i]); //Could do this while creating copy Logger::slout()<< "IndyBag of size n m c"<<theGraphs[i]->numberOfNodes()<< " "<< theGraphs[i]->numberOfEdges()<< " "<< bagCG.numberOfClusters()<<"\n"; List<nodePair> ae; //Todo: Add an interface here that allows to transfer bag and // activity information to the master, otherwise we have to // compute this info twice. bool imresult = doTest(bagCG, ae); #ifdef OGDF_DEBUG Logger::slout() << "IndyBag number "<<i<<" is "<< (imresult ? "" : "non-") <<"c-planar\n"; Logger::slout() << "Number of edges added for IndyBag: "<<ae.size()<<"\n"; #endif result = result && imresult; if (!result) return result; for(const nodePair &np : ae) { addedEdges.emplaceBack(theGraphs[i]->original(np.v1), theGraphs[i]->original(np.v2)); } } #ifdef OGDF_DEBUG else { Logger::slout() << "IndyBag number "<<i<<" skipped due to size\n"; } #endif }//for indy bags for (int i = 0; i < numIndyBags; i++) { delete theGraphs[i]; } delete [] theGraphs; // We test consistency by summing up the number of vertices. } else { //todo: can be joined again, as it is a special case wo cluster analysis //otherwise we just make a copy of the whole graph Graph G; ClusterArray<cluster> clusterCopy(CG); NodeArray<node> nodeCopy(CG.constGraph()); EdgeArray<edge> edgeCopy(CG.constGraph()); ClusterGraph C(CG,G,clusterCopy, nodeCopy, edgeCopy); makeParallelFreeUndirected(G); NodeArray<node> nodeOrig(G); for(node v : CG.constGraph().nodes) { nodeOrig[nodeCopy[v]] = v; } //Could use same list here for both graphs. addedEdges.clear(); List<nodePair> ae; result = doTest(C,ae); //nodepairs are for the copy, store original nodes here for (const nodePair &np : ae) { addedEdges.emplaceBack(nodeOrig[np.v1], nodeOrig[np.v2]); } } return result; }
// Recursive call for testing c-planarity of the clustered graph // that is induced by cluster act bool CconnectClusterPlanar::planarityTest( ClusterGraph &C, cluster &act, Graph &G) { // Test children first ListConstIterator<cluster> it; for (it = act->cBegin(); it.valid();) { ListConstIterator<cluster> succ = it.succ(); cluster next = (*it); if (!planarityTest(C,next,G)) return false; it = succ; } // Get induced subgraph of cluster act and test it for planarity List<node> subGraphNodes; for (node s : act->nodes) subGraphNodes.pushBack(s); Graph subGraph; NodeArray<node> table; inducedSubGraph(G,subGraphNodes.begin(),subGraph,table); // Introduce super sink and add edges corresponding // to outgoing edges of the cluster node superSink = subGraph.newNode(); EdgeArray<node> outgoingTable(subGraph,nullptr); for (node w : act->nodes) { //adjEntry adj = w->firstAdj(); for(adjEntry adj : w->adjEntries) { edge e = adj->theEdge(); edge cor = nullptr; if (table[e->source()] == nullptr) // edge is connected to a node outside the cluster { cor = subGraph.newEdge(table[e->target()],superSink); outgoingTable[cor] = e->source(); } else if (table[e->target()] == nullptr) // dito { cor = subGraph.newEdge(table[e->source()],superSink); outgoingTable[cor] = e->target(); } // else edge connects two nodes of the cluster } } if (superSink->degree() == 0) // root cluster is not connected to outside clusters { subGraph.delNode(superSink); superSink = nullptr; } bool cPlanar = preparation(subGraph,act,superSink); if (cPlanar && act != C.rootCluster()) { // Remove induced subgraph and the cluster act. // Replace it by a wheel graph while (!subGraphNodes.empty()) { node w = subGraphNodes.popFrontRet(); // C.unassignNode(w); G.delNode(w); } cluster parent = act->parent(); if (superSink && m_clusterPQTree[act]) constructWheelGraph(C,G,parent,m_clusterPQTree[act],outgoingTable); C.delCluster(act); if (m_clusterPQTree[act] != nullptr) // if query necessary for clusters with just one child { m_clusterPQTree[act]->emptyAllPertinentNodes(); delete m_clusterPQTree[act]; } } else if (!cPlanar) { m_errorCode = nonCPlanar; }//if not cplanar return cPlanar; }
static bool writeCluster( std::ostream &out, int depth, const ClusterArray < std::vector<edge> > &edgeMap, const ClusterGraph &C, const ClusterGraphAttributes *CA, const cluster &c, int &clusterId) { std::ios_base::fmtflags currentFlags = out.flags(); out.flags(currentFlags | std::ios::fixed); bool result = out.good(); if(result) { if (C.rootCluster() == c) { writeHeader(out, depth++, CA); } else { GraphIO::indent(out, depth++) << "subgraph cluster" << clusterId << " {\n"; } clusterId++; bool whitespace; // True if a whitespace should printed (readability). whitespace = false; if (CA) { writeAttributes(out, depth, *CA, c); whitespace = true; } if (whitespace) { out << "\n"; } // Recursively export all subclusters. whitespace = false; for (cluster child : c->children) { writeCluster(out, depth, edgeMap, C, CA, child, clusterId); whitespace = true; } if (whitespace) { out << "\n"; } // Then, print all nodes whithout an adjacent edge. whitespace = false; for (node v : c->nodes) { whitespace |= writeNode(out, depth, CA, v); } if (whitespace) { out << "\n"; } // Finally, we print all edges for this cluster (ugly version for now). const std::vector<edge> &edges = edgeMap[c]; whitespace = false; for (auto &e : edges) { whitespace |= writeEdge(out, depth, CA, e); } GraphIO::indent(out, --depth) << "}\n"; } out.flags(currentFlags); return result; }
//the call function that lets ClusterPlanarizationLayout compute a layout //for the input using \a weight for the computation of the cluster planar subgraph void ClusterPlanarizationLayout::call( Graph& G, ClusterGraphAttributes& acGraph, ClusterGraph& cGraph, EdgeArray<double>& edgeWeight, bool simpleCConnect) //default true { m_nCrossings = 0; bool subGraph = false; // c-planar subgraph computed? //check some simple cases if (G.numberOfNodes() == 0) return; //------------------------------------------------------------- //we set pointers and arrays to the working graph, which can be //the original or, in the case of non-c-planar input, a copy Graph* workGraph = &G; ClusterGraph* workCG = &cGraph; ClusterGraphAttributes* workACG = &acGraph; //potential copy of original if non c-planar Graph GW; //list of non c-planarity causing edges List<edge> leftEdges; //list of nodepairs to be connected (deleted edges) List<NodePair> leftWNodes; //store some information //original to copy NodeArray<node> resultNode(G); EdgeArray<edge> resultEdge(G); ClusterArray<cluster> resultCluster(cGraph); //copy to original NodeArray<node> orNode(G); EdgeArray<edge> orEdge(G); ClusterArray<cluster> orCluster(cGraph); for(node workv : G.nodes) { resultNode[workv] = workv; //will be set to copy if non-c-planar orNode[workv] = workv; } for(edge worke : G.edges) { resultEdge[worke] = worke; //will be set to copy if non-c-planar orEdge[worke] = worke; } for (cluster workc : cGraph.clusters) { resultCluster[workc] = workc; //will be set to copy if non-c-planar orCluster[workc] = workc; } //----------------------------------------------- //check if instance is clusterplanar and embed it CconnectClusterPlanarEmbed CCPE; //cccp bool cplanar = CCPE.embed(cGraph, G); List<edge> connectEdges; //if the graph is not c-planar, we have to check the reason and to //correct the problem by planarising or inserting connection edges if (!cplanar) { bool connect = false; if ( (CCPE.errCode() == CconnectClusterPlanarEmbed::nonConnected) || (CCPE.errCode() == CconnectClusterPlanarEmbed::nonCConnected) ) { //we insert edges to make the input c-connected makeCConnected(cGraph, G, connectEdges, simpleCConnect); //save edgearray info for inserted edges for(edge e : connectEdges) { resultEdge[e] = e; orEdge[e] = e; } connect = true; CCPE.embed(cGraph, G); if ( (CCPE.errCode() == CconnectClusterPlanarEmbed::nonConnected) || (CCPE.errCode() == CconnectClusterPlanarEmbed::nonCConnected) ) { cerr << "no correct connection made\n"<<flush; OGDF_THROW(AlgorithmFailureException); } }//if not cconnected if ((CCPE.errCode() == CconnectClusterPlanarEmbed::nonPlanar) || (CCPE.errCode() == CconnectClusterPlanarEmbed::nonCPlanar)) { subGraph = true; EdgeArray<bool> inSubGraph(G, false); CPlanarSubClusteredGraph cps; if (edgeWeight.valid()) cps.call(cGraph, inSubGraph, leftEdges, edgeWeight); else cps.call(cGraph, inSubGraph, leftEdges); #ifdef OGDF_DEBUG // for(edge worke : G.edges) { // if (inSubGraph[worke]) // acGraph.strokeColor(worke) = "#FF0000"; // } #endif //--------------------------------------------------------------- //now we delete the copies of all edges not in subgraph and embed //the subgraph (use a new copy) //construct copy workGraph = &GW; workCG = new ClusterGraph(cGraph, GW, resultCluster, resultNode, resultEdge); //---------------------- //reinit original arrays orNode.init(GW, nullptr); orEdge.init(GW, nullptr); orCluster.init(*workCG, nullptr); //set array entries to the appropriate values for (node workv : G.nodes) orNode[resultNode[workv]] = workv; for (edge worke : G.edges) orEdge[resultEdge[worke]] = worke; for (cluster workc : cGraph.clusters) orCluster[resultCluster[workc]] = workc; //---------------------------------------------------- //create new ACG and copy values (width, height, type) workACG = new ClusterGraphAttributes(*workCG, workACG->attributes()); for (node workv : GW.nodes) { //should set same attributes in construction!!! if (acGraph.attributes() & GraphAttributes::nodeType) workACG->type(workv) = acGraph.type(orNode[workv]); workACG->height(workv) = acGraph.height(orNode[workv]); workACG->width(workv) = acGraph.width(orNode[workv]); } if (acGraph.attributes() & GraphAttributes::edgeType) { for (edge worke : GW.edges) { workACG->type(worke) = acGraph.type(orEdge[worke]); //all other attributes are not needed or will be set } } for(edge ei : leftEdges) { edge e = resultEdge[ei]; NodePair np; np.m_src = e->source(); np.m_tgt = e->target(); leftWNodes.pushBack(np); GW.delEdge(e); } CconnectClusterPlanarEmbed CCP; #ifdef OGDF_DEBUG bool subPlanar = #endif CCP.embed(*workCG, GW); OGDF_ASSERT(subPlanar); }//if not planar else { if (!connect) OGDF_THROW_PARAM(PreconditionViolatedException, pvcClusterPlanar); } }//if //if multiple CCs are handled, the connectedges (their copies resp.) //can be deleted here //now CCPE should give us the external face ClusterPlanRep CP(*workACG, *workCG); OGDF_ASSERT(CP.representsCombEmbedding()); const int numCC = CP.numberOfCCs(); //equal to one //preliminary OGDF_ASSERT(numCC == 1); // (width,height) of the layout of each connected component Array<DPoint> boundingBox(numCC); for (int ikl = 0; ikl < numCC; ikl++) { CP.initCC(ikl); CP.setOriginalEmbedding(); OGDF_ASSERT(CP.representsCombEmbedding()) Layout drawing(CP); //m_planarLayouter.get().setOptions(4);//progressive adjEntry ae = nullptr; //internally compute adjEntry for outer face //edges that are reinserted in workGraph (in the same //order as leftWNodes) List<edge> newEdges; m_planarLayouter.get().call(CP, ae, drawing, leftWNodes, newEdges, *workGraph); OGDF_ASSERT(leftWNodes.size()==newEdges.size()) OGDF_ASSERT(leftEdges.size()==newEdges.size()) ListConstIterator<edge> itE = newEdges.begin(); ListConstIterator<edge> itEor = leftEdges.begin(); while (itE.valid()) { orEdge[*itE] = *itEor; ++itE; ++itEor; } //hash index over cluster ids HashArray<int, ClusterPosition> CA; computeClusterPositions(CP, drawing, CA); // copy layout into acGraph // Later, we move nodes and edges in each connected component, such // that no two overlap. for(int i = CP.startNode(); i < CP.stopNode(); ++i) { node vG = CP.v(i); acGraph.x(orNode[vG]) = drawing.x(CP.copy(vG)); acGraph.y(orNode[vG]) = drawing.y(CP.copy(vG)); for(adjEntry adj : vG->adjEdges) { if ((adj->index() & 1) == 0) continue; edge eG = adj->theEdge(); edge orE = orEdge[eG]; if (orE) drawing.computePolylineClear(CP,eG,acGraph.bends(orE)); } }//for //even assignment for all nodes is not enough, we need all clusters for(cluster c : workCG->clusters) { int clNumber = c->index(); //int orNumber = originalClId[c]; cluster orCl = orCluster[c]; if (c != workCG->rootCluster()) { OGDF_ASSERT(CA.isDefined(clNumber)); acGraph.height(orCl) = CA[clNumber].m_height; acGraph.width(orCl) = CA[clNumber].m_width; acGraph.y(orCl) = CA[clNumber].m_miny; acGraph.x(orCl) = CA[clNumber].m_minx; }//if real cluster } // the width/height of the layout has been computed by the planar // layout algorithm; required as input to packing algorithm boundingBox[ikl] = m_planarLayouter.get().getBoundingBox(); }//for connected components //postProcess(acGraph); // // arrange layouts of connected components // Array<DPoint> offset(numCC); m_packer.get().call(boundingBox,offset,m_pageRatio); // The arrangement is given by offset to the origin of the coordinate // system. We still have to shift each node, edge and cluster by the offset // of its connected component. const Graph::CCsInfo &ccInfo = CP.ccInfo(); for(int i = 0; i < numCC; ++i) { const double dx = offset[i].m_x; const double dy = offset[i].m_y; HashArray<int, bool> shifted(false); // iterate over all nodes in ith CC for(int j = ccInfo.startNode(i); j < ccInfo.stopNode(i); ++j) { node v = ccInfo.v(j); acGraph.x(orNode[v]) += dx; acGraph.y(orNode[v]) += dy; // update cluster positions accordingly //int clNumber = cGraph.clusterOf(orNode[v])->index(); cluster cl = cGraph.clusterOf(orNode[v]); if ((cl->index() > 0) && !shifted[cl->index()]) { acGraph.y(cl) += dy; acGraph.x(cl) += dx; shifted[cl->index()] = true; }//if real cluster for(adjEntry adj : v->adjEdges) { if ((adj->index() & 1) == 0) continue; edge e = adj->theEdge(); //edge eOr = orEdge[e]; if (orEdge[e]) { DPolyline &dpl = acGraph.bends(orEdge[e]); for(DPoint &p : dpl) { p.m_x += dx; p.m_y += dy; } } } }//for nodes }//for numcc while (!connectEdges.empty()) { G.delEdge(connectEdges.popFrontRet()); } if (subGraph) { //originalClId.init(); orCluster.init(); orNode.init(); orEdge.init(); delete workCG; delete workACG; }//if subgraph created acGraph.removeUnnecessaryBendsHV(); }//call