// recursively write clusters and nodes static void write_ogml_graph(const ClusterGraphAttributes &A, cluster c, int level, ostream &os) { if(level > 0) { GraphIO::indent(os,2+level) << "<node id=\"c" << c->index() << "\">\n"; if (A.has(GraphAttributes::nodeLabel)) { GraphIO::indent(os,4) << "<label id=\"lc" << c->index() << "\">\n"; GraphIO::indent(os,5) << "<content>" << formatLabel(A.label(c)) << "</content>\n"; GraphIO::indent(os,4) << "</label>\n"; } } ListConstIterator<node> itn; for (itn = c->nBegin(); itn.valid(); ++itn) { node v = *itn; GraphIO::indent(os,3+level) << "<node id=\"n" << v->index() << "\">\n"; if (A.has(GraphAttributes::nodeLabel)) { GraphIO::indent(os,4) << "<label id=\"ln" << v->index() << "\">\n"; GraphIO::indent(os,5) << "<content>" << formatLabel(A.label(v)) << "</content>\n"; GraphIO::indent(os,4) << "</label>\n"; } GraphIO::indent(os,3+level) << "</node>\n"; } for (cluster child : c->children) { write_ogml_graph(child, level+1, os); } if(level > 0) { GraphIO::indent(os,2+level) << "</node>\n"; } }
static inline void writeAttributes( std::ostream &out, const int &depth, const ClusterGraphAttributes &CA, const cluster &c) { GraphIO::indent(out, depth) << "color=\"" << CA.strokeColor(c) << "\"\n"; GraphIO::indent(out, depth) << "bgcolor=\"" << CA.fillColor(c) << "\"\n"; GraphIO::indent(out, depth) << "label=\"" << CA.label(c) << "\"\n"; // There is no point in exporting rest of the cluster attributes, so to // maintain high readability they are omitted. }
void FMMMLayout::call(ClusterGraphAttributes &GA) { const Graph &G = GA.constGraph(); //compute depth of cluster tree, also sets cluster depth values const ClusterGraph &CG = GA.constClusterGraph(); int cdepth = CG.treeDepth(); EdgeArray<double> edgeLength(G); //compute lca of end vertices for each edge edge e; forall_edges(e, G) { edgeLength[e] = cdepth - CG.clusterDepth(CG.commonCluster(e->source(),e->target())) + 1; OGDF_ASSERT(edgeLength[e] > 0) }
// write cluster layout with attributes static void write_ogml_layout(const ClusterGraphAttributes &A, ostream &os) { const ClusterGraph &C = A.constClusterGraph(); GraphIO::indent(os,2) << "<layout>\n"; GraphIO::indent(os,3) << "<styles>\n"; for(cluster c : C.clusters) { if(c != C.rootCluster()) { GraphIO::indent(os,4) << "<nodeStyle idRef=\"c" << c->index() << "\">\n"; GraphIO::indent(os,5) << "<location x=\"" << A.x(c) << "\" y=\"" << A.y(c) << "\" />\n"; GraphIO::indent(os,5) << "<shape type=\"rect\" width=\"" << A.width(c) << "\" height=\"" << A.height(c) << "\" />\n"; GraphIO::indent(os,5) << "<fill color=\"" << A.fillColor(c) << "\"" << " pattern=\"" << fillPatternToOGML(A.fillPattern(c)) << "\" patternColor=\"" << A.fillBgColor(c) << "\" />\n"; GraphIO::indent(os,5) << "<line type=\"" << edgeStyleToOGML(A.strokeType(c)) << "\" width=\"" << A.strokeWidth(c) << "\" color=\"" << A.strokeColor(c) << "\" />\n"; GraphIO::indent(os,4) << "</nodeStyle>\n"; } } write_ogml_layout_nodes_edges(A,os); GraphIO::indent(os,3) << "</styles>\n"; GraphIO::indent(os,2) << "</layout>\n"; }
bool GraphIO::writeDOT(const ClusterGraphAttributes &CA, std::ostream &out) { const Graph &G = CA.constGraph(); const ClusterGraph &C = CA.constClusterGraph(); 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, &CA, C.rootCluster(), id); }
// write cluster structure with attributes static void write_ogml_graph(const ClusterGraphAttributes &A, ostream &os) { GraphIO::indent(os,2) << "<structure>\n"; write_ogml_graph(A, A.constClusterGraph().rootCluster(), 0, os); write_ogml_graph_edges(A, os); GraphIO::indent(os,2) << "</structure>\n"; }
bool GraphIO::writeGEXF(const ClusterGraphAttributes &CA, std::ostream &out) { const ClusterGraph &C = CA.constClusterGraph(); gexf::writeHeader(out, true); gexf::writeCluster(out, 1, C, &CA, C.rootCluster()); gexf::writeFooter(out); return true; }
bool GraphMLParser::readData( ClusterGraphAttributes &CA, const cluster &c, const pugi::xml_node clusterData) { auto keyId = clusterData.attribute("key"); if (!keyId) { GraphIO::logger.lout() << "Cluster data does not have a key." << endl; return false; } pugi::xml_text text = clusterData.text(); using namespace graphml; switch (toAttribute(m_attrName[keyId.value()])) { case a_nodeLabel: CA.label(c) = text.get(); break; case a_x: CA.x(c) = text.as_double(); break; case a_y: CA.y(c) = text.as_double(); break; case a_width: CA.width(c) = text.as_double(); break; case a_height: CA.height(c) = text.as_double(); break; case a_size: // We want to set a new size only if width and height was not set. if (CA.width(c) == CA.height(c)) { CA.width(c) = CA.height(c) = text.as_double(); } case a_r: if (!GraphIO::setColorValue(text.as_int(), [&](uint8_t val) { CA.fillColor(c).red(val); })) { return false; } break; case a_g: if (!GraphIO::setColorValue(text.as_int(), [&](uint8_t val) { CA.fillColor(c).green(val); })) { return false; } break; case a_b: if (!GraphIO::setColorValue(text.as_int(), [&](uint8_t val) { CA.fillColor(c).blue(val); })) { return false; } break; case a_clusterStroke: CA.strokeColor(c) = text.get(); break; default: GraphIO::logger.lout(Logger::LL_MINOR) << "Unknown cluster attribute with \"" << keyId.value() << "--enum: " << m_attrName[keyId.value()] << "--" << "\"." << endl; } return true; }
//recursively read cluster subtree information bool GmlParser::recursiveAttributedClusterRead(GmlObject* clusterObject, ClusterGraph& CG, ClusterGraphAttributes& ACG, cluster c) { //for direct root cluster sons, this is checked twice... if (clusterObject->m_valueType != gmlListBegin) return false; GmlObject *clusterSon = clusterObject->m_pFirstSon; for(; clusterSon; clusterSon = clusterSon->m_pBrother) { //we dont read the attributes, therefore look only for //id and sons switch(id(clusterSon)) { case clusterPredefKey: { if (clusterSon->m_valueType != gmlListBegin) return false; cluster cson = CG.newCluster(c); //recursively read child cluster recursiveAttributedClusterRead(clusterSon, CG, ACG, cson); } break; case labelPredefKey: { if (clusterSon->m_valueType != gmlStringValue) return false; ACG.label(c) = clusterSon->m_stringValue; } break; case templatePredefKey: { if (clusterSon->m_valueType != gmlStringValue) return false; ACG.templateCluster(c) = clusterSon->m_stringValue; break; } case graphicsPredefKey: //read the info for cluster c { if (clusterSon->m_valueType != gmlListBegin) return false; readClusterAttributes(clusterSon, c , ACG); }//graphics break; case vertexPredefKey: //direct cluster vertex entries { if (clusterSon->m_valueType != gmlStringValue) return false; string vIDString = clusterSon->m_stringValue; if ((vIDString[0] != 'v') && (!isdigit((int)vIDString[0])))return false; //do not allow labels //if old style entry "v"i if (!isdigit((int)vIDString[0])) //should check prefix? vIDString[0] = '0'; //leading zero to allow conversion int vID = stoi(vIDString); OGDF_ASSERT(m_mapToNode[vID] != 0) //we assume that no node is already assigned //changed: all nodes are already assigned to root CG.reassignNode(m_mapToNode[vID], c); }//case vertex }//switch }//for clustersons return true; }//recursiveAttributedClusterRead
bool GmlParser::readClusterAttributes( GmlObject* cGraphics, cluster c, ClusterGraphAttributes& ACG) { string label; string fill; // the fill color attribute string line; // the line color attribute float lineWidth = 1.0f; //node line width int pattern = 1; //node brush pattern int stipple = 1; //line style pattern // read all relevant attributes GmlObject *graphicsObject = cGraphics->m_pFirstSon; for(; graphicsObject; graphicsObject = graphicsObject->m_pBrother) { switch(id(graphicsObject)) { case xPredefKey: if(graphicsObject->m_valueType != gmlDoubleValue) return false; ACG.x(c) = graphicsObject->m_doubleValue; break; case yPredefKey: if(graphicsObject->m_valueType != gmlDoubleValue) return false; ACG.y(c) = graphicsObject->m_doubleValue; break; case widthPredefKey: if(graphicsObject->m_valueType != gmlDoubleValue) return false; ACG.width(c) = graphicsObject->m_doubleValue; break; case heightPredefKey: if(graphicsObject->m_valueType != gmlDoubleValue) return false; ACG.height(c) = graphicsObject->m_doubleValue; break; case fillPredefKey: if(graphicsObject->m_valueType != gmlStringValue) return false; ACG.fillColor(c) = graphicsObject->m_stringValue; break; case patternPredefKey: if(graphicsObject->m_valueType != gmlIntValue) return false; pattern = graphicsObject->m_intValue; break; //line style case colorPredefKey: // line color if(graphicsObject->m_valueType != gmlStringValue) return false; ACG.strokeColor(c) = graphicsObject->m_stringValue; break; case stipplePredefKey: if(graphicsObject->m_valueType != gmlIntValue) return false; stipple = graphicsObject->m_intValue; break; case lineWidthPredefKey: if(graphicsObject->m_valueType != gmlDoubleValue) return false; lineWidth = (float)graphicsObject->m_doubleValue; break; //TODO: backgroundcolor //case stylePredefKey: //case boderwidthPredefKey: }//switch }//for //Hier eigentlich erst abfragen, ob clusterattributes setzbar in ACG, //dann setzen ACG.setStrokeType(c, intToStrokeType(stipple)); //defaulting 1 ACG.strokeWidth(c) = lineWidth; ACG.setFillPattern(c, intToFillPattern(pattern)); return true; }//readclusterattributes
//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