void MixedModelLayout::doCall( PlanRep &PG, adjEntry adjExternal, GridLayout &gridLayout, IPoint &boundingBox, bool fixEmbedding) { // handle graphs with less than 3 nodes node v1, v2; switch (PG.numberOfNodes()) { case 0: boundingBox = IPoint(0,0); return; case 1: v1 = PG.firstNode(); gridLayout.x(v1) = gridLayout.y(v1) = 0; boundingBox = IPoint(0,0); return; case 2: v1 = PG.firstNode(); v2 = v1->succ(); gridLayout.x(v1) = gridLayout.y(v1) = gridLayout.y(v2) = 0; gridLayout.x(v2) = 1; boundingBox = IPoint(1,0); return; } MixedModelBase mm(PG,gridLayout); if(fixEmbedding) { OGDF_ASSERT(PG.representsCombEmbedding()); PlanarAugmentationFix fixAugmenter; mm.computeOrder(fixAugmenter, 0, adjExternal, m_compOrder.get()); } else mm.computeOrder(m_augmenter.get(),&m_embedder.get(),0,m_compOrder.get()); mm.assignIopCoords(); mm.placeNodes(); mm.postprocessing1(); mm.setBends(); mm.postprocessing2(); m_crossingsBeautifier.get().call(PG,gridLayout); int xmin, ymin; gridLayout.computeBoundingBox(xmin,boundingBox.m_x,ymin,boundingBox.m_y); }
void SchnyderLayout::doCall( const Graph &G, adjEntry adjExternal, GridLayout &gridLayout, IPoint &boundingBox, bool fixEmbedding) { // check for double edges & self loops OGDF_ASSERT(isSimple(G)); // handle special case of graphs with less than 3 nodes if (G.numberOfNodes() < 3) { node v1, v2; switch (G.numberOfNodes()) { case 0: boundingBox = IPoint(0, 0); return; case 1: v1 = G.firstNode(); gridLayout.x(v1) = gridLayout.y(v1) = 0; boundingBox = IPoint(0, 0); return; case 2: v1 = G.firstNode(); v2 = G.lastNode(); gridLayout.x(v1) = gridLayout.y(v1) = gridLayout.y(v2) = 0; gridLayout.x(v2) = 1; boundingBox = IPoint(1, 0); return; } } // make a copy for triangulation GraphCopy GC(G); // embed if (!fixEmbedding) { if (planarEmbed(GC) == false) { OGDF_THROW_PARAM(PreconditionViolatedException, pvcPlanar); } } triangulate(GC); schnyderEmbedding(GC, gridLayout, adjExternal); }
void GridLayoutModule::mapGridLayout(const Graph &G, GridLayout &gridLayout, GraphAttributes &AG) { // maximum width of columns and rows double maxWidth = 0; double yMax = 0; for(node v : G.nodes) { Math::updateMax<double>(maxWidth, AG.width(v)); Math::updateMax<double>(maxWidth, AG.height(v)); Math::updateMax<double>(yMax, gridLayout.y(v)); } maxWidth += m_separation; // set position of nodes for(node v : G.nodes) { AG.x(v) = gridLayout.x(v) * maxWidth; AG.y(v) = (yMax - gridLayout.y(v)) * maxWidth; } // transform bend points of edges for(edge e : G.edges) { IPolyline ipl = gridLayout.polyline(e); // Remove superfluous bendpoints node v = e->source(); while(!ipl.empty() && ipl.front() == IPoint(gridLayout.x(v), gridLayout.y(v))) { ipl.popFront(); } v = e->target(); while(!ipl.empty() && ipl.back() == IPoint(gridLayout.x(v), gridLayout.y(v))) { ipl.popBack(); } DPolyline &dpl = AG.bends(e); dpl.clear(); for (const IPoint &ip : ipl) { dpl.pushBack(DPoint(ip.m_x*maxWidth, (yMax-ip.m_y)*maxWidth)); } dpl.normalize(); } }
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); } } } }
bool PlanarGridLayoutModule::handleTrivial(const Graph &G, GridLayout &gridLayout, IPoint &boundingBox) { // handle special case of graphs with less than 3 nodes node v1, v2; switch (G.numberOfNodes()) { case 0: boundingBox = IPoint(0, 0); return true; case 1: v1 = G.firstNode(); gridLayout.x(v1) = gridLayout.y(v1) = 0; boundingBox = IPoint(0, 0); return true; case 2: v1 = G.firstNode(); v2 = G.lastNode(); gridLayout.x(v1) = gridLayout.y(v1) = gridLayout.y(v2) = 0; gridLayout.x(v2) = 1; boundingBox = IPoint(1, 0); return true; } return false; }
void FPPLayout::computeCoordinates(const GraphCopy &G, IPoint &boundingBox, GridLayout &gridLayout, NodeArray<int> &num, NodeArray<adjEntry> &e_wp, NodeArray<adjEntry> &e_wq) { NodeArray<int> &x = gridLayout.x(); NodeArray<int> &y = gridLayout.y(); const int n = G.numberOfNodes(); NodeArray<int> x_rel(G); NodeArray<node> upper(G); NodeArray<node> next(G); Array<node, int> v(1, n); node w, vk, wp, wq; int k, xq, dx; forall_nodes(w, G) { v[num[w]] = (node) w; }
void GridLayoutModule::mapGridLayout(const Graph &G, GridLayout &gridLayout, GraphAttributes &AG) { double maxWidth = 0; // maximum width of columns and rows; double yMax = 0; node v; forall_nodes(v,G) { if (AG.width (v) > maxWidth) maxWidth = AG.width (v); if (AG.height(v) > maxWidth) maxWidth = AG.height(v); if (gridLayout.y(v) > yMax) yMax = gridLayout.y(v); } maxWidth += m_separation; // set position of nodes forall_nodes(v,G) { AG.x(v) = gridLayout.x(v) * maxWidth; AG.y(v) = (yMax - gridLayout.y(v)) * maxWidth; }
void GridLayoutPlanRepModule::doCall( const Graph &G, adjEntry adjExternal, GridLayout &gridLayout, IPoint &boundingBox, bool fixEmbedding) { // create temporary graph copy and grid layout PlanRep PG(G); PG.initCC(0); // currently only for a single component! GridLayout glPG(PG); // determine adjacency entry on external face of PG (if required) if(adjExternal != nullptr) { edge eG = adjExternal->theEdge(); edge ePG = PG.copy(eG); adjExternal = (adjExternal == eG->adjSource()) ? ePG->adjSource() : ePG->adjTarget(); } // call algorithm for copy doCall(PG,adjExternal,glPG,boundingBox,fixEmbedding); // extract layout for original graph for(node v : G.nodes) { node vPG = PG.copy(v); gridLayout.x(v) = glPG.x(vPG); gridLayout.y(v) = glPG.y(vPG); } for(edge e : G.edges) { IPolyline &ipl = gridLayout.bends(e); ipl.clear(); for(edge ec : PG.chain(e)) ipl.conc(glPG.bends(ec)); } }
void PlanarizationGridLayout::doCall( const Graph &G, GridLayout &gridLayout, IPoint &bb) { m_nCrossings = 0; if(G.empty()) return; PlanRep pr(G); const int numCC = pr.numberOfCCs(); // (width,height) of the layout of each connected component Array<IPoint> boundingBox(numCC); for(int cc = 0; cc < numCC; ++cc) { //-------------------------------------- // 1. crossing minimization //-------------------------------------- int cr; m_crossMin.get().call(pr, cc, cr); m_nCrossings += cr; OGDF_ASSERT(isPlanar(pr)); GridLayout gridLayoutPG(pr); m_planarLayouter.get().callGrid(pr,gridLayoutPG); // copy grid layout of PG into grid layout of G for(int j = pr.startNode(); j < pr.stopNode(); ++j) { node vG = pr.v(j); gridLayout.x(vG) = gridLayoutPG.x(pr.copy(vG)); gridLayout.y(vG) = gridLayoutPG.y(pr.copy(vG)); adjEntry adj; forall_adj(adj,vG) { if ((adj->index() & 1) == 0) continue; edge eG = adj->theEdge(); IPolyline &ipl = gridLayout.bends(eG); ipl.clear(); bool firstTime = true; ListConstIterator<edge> itE; for(itE = pr.chain(eG).begin(); itE.valid(); ++itE) { if(!firstTime) { node v = (*itE)->source(); ipl.pushBack(IPoint(gridLayoutPG.x(v),gridLayoutPG.y(v))); } else firstTime = false; ipl.conc(gridLayoutPG.bends(*itE)); } } } boundingBox[cc] = m_planarLayouter.get().gridBoundingBox(); boundingBox[cc].m_x += 1; // one row/column space between components boundingBox[cc].m_y += 1; } Array<IPoint> offset(numCC); m_packer.get().call(boundingBox,offset,m_pageRatio); bb.m_x = bb.m_y = 0; for(int cc = 0; cc < numCC; ++cc) { const int dx = offset[cc].m_x; const int dy = offset[cc].m_y; if(boundingBox[cc].m_x + dx > bb.m_x) bb.m_x = boundingBox[cc].m_x + dx; if(boundingBox[cc].m_y + dy > bb.m_y) bb.m_y = boundingBox[cc].m_y + dy; // iterate over all nodes in i-th cc for(int j = pr.startNode(cc); j < pr.stopNode(cc); ++j) { node vG = pr.v(j); gridLayout.x(vG) += dx; gridLayout.y(vG) += dy; adjEntry adj; forall_adj(adj,vG) { if ((adj->index() & 1) == 0) continue; edge eG = adj->theEdge(); ListIterator<IPoint> it; for(it = gridLayout.bends(eG).begin(); it.valid(); ++it) { (*it).m_x += dx; (*it).m_y += dy; } } } } bb.m_x -= 1; // remove margin of topmost/rightmost box bb.m_y -= 1; }
void PlanarDrawLayout::doCall( const Graph &G, adjEntry adjExternal, GridLayout &gridLayout, IPoint &boundingBox, bool fixEmbedding) { // require to have a planar graph without multi-edges and self-loops; // planarity is checked below OGDF_ASSERT(isSimple(G) && isLoopFree(G)); // handle special case of graphs with less than 3 nodes if(G.numberOfNodes() < 3) { node v1, v2; switch(G.numberOfNodes()) { case 0: boundingBox = IPoint(0,0); return; case 1: v1 = G.firstNode(); gridLayout.x(v1) = gridLayout.y(v1) = 0; boundingBox = IPoint(0,0); return; case 2: v1 = G.firstNode(); v2 = G.lastNode (); gridLayout.x(v1) = gridLayout.y(v1) = gridLayout.y(v2) = 0; gridLayout.x(v2) = 1; boundingBox = IPoint(1,0); return; } } // we make a copy of G since we use planar biconnected augmentation GraphCopySimple GC(G); if(fixEmbedding) { PlanarAugmentationFix augmenter; augmenter.call(GC); } else { // augment graph planar biconnected m_augmenter.get().call(GC); // embed augmented graph PlanarModule pm; bool isPlanar = pm.planarEmbed(GC); if(isPlanar == false) OGDF_THROW_PARAM(PreconditionViolatedException, pvcPlanar); } // compute shelling order m_computeOrder.get().baseRatio(m_baseRatio); ShellingOrder order; m_computeOrder.get().call(GC,order,adjExternal); // compute grid coordinates for GC NodeArray<int> x(GC), y(GC); computeCoordinates(GC,order,x,y); boundingBox.m_x = x[order(1,order.len(1))]; boundingBox.m_y = 0; node v; forall_nodes(v,GC) if(y[v] > boundingBox.m_y) boundingBox.m_y = y[v]; // copy coordinates from GC to G forall_nodes(v,G) { node vCopy = GC.copy(v); gridLayout.x(v) = x[vCopy]; gridLayout.y(v) = y[vCopy]; }
void PlanarizationGridLayout::doCall( const Graph &G, GridLayout &gridLayout, IPoint &bb) { m_nCrossings = 0; if(G.empty()) return; PlanRep PG(G); const int numCC = PG.numberOfCCs(); // (width,height) of the layout of each connected component Array<IPoint> boundingBox(numCC); int i; for(i = 0; i < numCC; ++i) { PG.initCC(i); const int nOrigVerticesPG = PG.numberOfNodes(); List<edge> deletedEdges; m_subgraph.get().callAndDelete(PG, deletedEdges); m_inserter.get().call(PG,deletedEdges); m_nCrossings += PG.numberOfNodes() - nOrigVerticesPG; GridLayout gridLayoutPG(PG); m_planarLayouter.get().callGrid(PG,gridLayoutPG); // copy grid layout of PG into grid layout of G ListConstIterator<node> itV; for(itV = PG.nodesInCC(i).begin(); itV.valid(); ++itV) { node vG = *itV; gridLayout.x(vG) = gridLayoutPG.x(PG.copy(vG)); gridLayout.y(vG) = gridLayoutPG.y(PG.copy(vG)); adjEntry adj; forall_adj(adj,vG) { if ((adj->index() & 1) == 0) continue; edge eG = adj->theEdge(); IPolyline &ipl = gridLayout.bends(eG); ipl.clear(); bool firstTime = true; ListConstIterator<edge> itE; for(itE = PG.chain(eG).begin(); itE.valid(); ++itE) { if(!firstTime) { node v = (*itE)->source(); ipl.pushBack(IPoint(gridLayoutPG.x(v),gridLayoutPG.y(v))); } else firstTime = false; ipl.conc(gridLayoutPG.bends(*itE)); } } } boundingBox[i] = m_planarLayouter.get().gridBoundingBox(); boundingBox[i].m_x += 1; // one row/column space between components boundingBox[i].m_y += 1; } Array<IPoint> offset(numCC); m_packer.get().call(boundingBox,offset,m_pageRatio); bb.m_x = bb.m_y = 0; for(i = 0; i < numCC; ++i) { const List<node> &nodes = PG.nodesInCC(i); const int dx = offset[i].m_x; const int dy = offset[i].m_y; if(boundingBox[i].m_x + dx > bb.m_x) bb.m_x = boundingBox[i].m_x + dx; if(boundingBox[i].m_y + dy > bb.m_y) bb.m_y = boundingBox[i].m_y + dy; // iterate over all nodes in i-th cc ListConstIterator<node> it; for(it = nodes.begin(); it.valid(); ++it) { node vG = *it; gridLayout.x(vG) += dx; gridLayout.y(vG) += dy; adjEntry adj; forall_adj(adj,vG) { if ((adj->index() & 1) == 0) continue; edge eG = adj->theEdge(); ListIterator<IPoint> it; for(it = gridLayout.bends(eG).begin(); it.valid(); ++it) { (*it).m_x += dx; (*it).m_y += dy; } } } } bb.m_x -= 1; // remove margin of topmost/rightmost box bb.m_y -= 1; }
void PlanarStraightLayout::doCall( const Graph &G, adjEntry adjExternal, GridLayout &gridLayout, IPoint &boundingBox, bool fixEmbedding) { // require to have a planar graph without multi-edges and self-loops; // planarity is checked below OGDF_ASSERT(isSimple(G) && isLoopFree(G)); // handle special case of graphs with less than 3 nodes if(G.numberOfNodes() < 3) { node v1, v2; switch(G.numberOfNodes()) { case 0: boundingBox = IPoint(0,0); return; case 1: v1 = G.firstNode(); gridLayout.x(v1) = gridLayout.y(v1) = 0; boundingBox = IPoint(0,0); return; case 2: v1 = G.firstNode(); v2 = G.lastNode (); gridLayout.x(v1) = gridLayout.y(v1) = gridLayout.y(v2) = 0; gridLayout.x(v2) = 1; boundingBox = IPoint(1,0); return; } } // we make a copy of G since we use planar biconnected augmentation GraphCopySimple GC(G); if(fixEmbedding) { // determine adjacency entry on external face of GC (if required) if(adjExternal != 0) { edge eG = adjExternal->theEdge(); edge eGC = GC.copy(eG); adjExternal = (adjExternal == eG->adjSource()) ? eGC->adjSource() : eGC->adjTarget(); } PlanarAugmentationFix augmenter; augmenter.call(GC); } else { adjExternal = 0; // augment graph planar biconnected m_augmenter.get().call(GC); // embed augmented graph m_embedder.get().call(GC,adjExternal); } // compute shelling order with shelling order module m_computeOrder.get().baseRatio(m_baseRatio); ShellingOrder order; m_computeOrder.get().callLeftmost(GC,order,adjExternal); // compute grid coordinates for GC NodeArray<int> x(GC), y(GC); computeCoordinates(GC,order,x,y); boundingBox.m_x = x[order(1,order.len(1))]; boundingBox.m_y = 0; node v; forall_nodes(v,GC) if(y[v] > boundingBox.m_y) boundingBox.m_y = y[v]; // copy coordinates from GC to G forall_nodes(v,G) { node vCopy = GC.copy(v); gridLayout.x(v) = x[vCopy]; gridLayout.y(v) = y[vCopy]; }
void SchnyderLayout::schnyderEmbedding( GraphCopy& GC, GridLayout &gridLayout, adjEntry adjExternal) { NodeArray<int> &xcoord = gridLayout.x(); NodeArray<int> &ycoord = gridLayout.y(); node v; List<node> L; // (un)contraction order GraphCopy T = GraphCopy(GC); // the realizer tree (reverse direction of edges!!!) EdgeArray<int> rValues(T); // the realizer values // choose outer face a,b,c adjEntry adja; if (adjExternal != 0) { edge eG = adjExternal->theEdge(); edge eGC = GC.copy(eG); adja = (adjExternal == eG->adjSource()) ? eGC->adjSource() : eGC->adjTarget(); } else { adja = GC.firstEdge()->adjSource(); } adjEntry adjb = adja->faceCyclePred(); adjEntry adjc = adjb->faceCyclePred(); node a = adja->theNode(); node b = adjb->theNode(); node c = adjc->theNode(); node a_in_T = T.copy(GC.original(a)); node b_in_T = T.copy(GC.original(b)); node c_in_T = T.copy(GC.original(c)); contract(GC, a, b, c, L); realizer(GC, L, a, b, c, rValues, T); NodeArray<int> t1(T); NodeArray<int> t2(T); NodeArray<int> val(T, 1); NodeArray<int> P1(T); NodeArray<int> P3(T); NodeArray<int> v1(T); NodeArray<int> v2(T); subtreeSizes(rValues, 1, a_in_T, t1); subtreeSizes(rValues, 2, b_in_T, t2); prefixSum(rValues, 1, a_in_T, val, P1); prefixSum(rValues, 3, c_in_T, val, P3); // now Pi = depth of all nodes in Tree T(i) (depth[root] = 1) prefixSum(rValues, 2, b_in_T, t1, v1); // special treatment for a v1[a_in_T] = t1[a_in_T]; /* * v1[v] now is the sum of the * "count of nodes in t1" minus the "subtree size for node x" * for every node x on a path from b to v in t2 */ prefixSum(rValues, 3, c_in_T, t1, val); // special treatment for a val[a_in_T] = t1[a_in_T]; /* * val[v] now is the sum of the * "count of nodes in t1" minus the "subtree size for node x" * for every node x on a path from c to v in t3 */ // r1[v]=v1[v]+val[v]-t1[v] is the number of nodes in region 1 from v forall_nodes(v, T) { // calc v1' v1[v] += val[v] - t1[v] - P3[v]; }
void FPPLayout::doCall( const Graph &G, adjEntry adjExternal, GridLayout &gridLayout, IPoint &boundingBox, bool fixEmbedding) { // check for double edges & self loops OGDF_ASSERT(isSimple(G)); // handle special case of graphs with less than 3 nodes if (G.numberOfNodes() < 3) { node v1, v2; switch (G.numberOfNodes()) { case 0: boundingBox = IPoint(0, 0); return; case 1: v1 = G.firstNode(); gridLayout.x(v1) = gridLayout.y(v1) = 0; boundingBox = IPoint(0, 0); return; case 2: v1 = G.firstNode(); v2 = G.lastNode(); gridLayout.x(v1) = gridLayout.y(v1) = gridLayout.y(v2) = 0; gridLayout.x(v2) = 1; boundingBox = IPoint(1, 0); return; } } // make a copy for triangulation GraphCopy GC(G); // embed if (!fixEmbedding) { if (planarEmbed(GC) == false) { OGDF_THROW_PARAM(PreconditionViolatedException, pvcPlanar); } } triangulate(GC); // get edges for outer face (triangle) adjEntry e_12; if (adjExternal != 0) { edge eG = adjExternal->theEdge(); edge eGC = GC.copy(eG); e_12 = (adjExternal == eG->adjSource()) ? eGC->adjSource() : eGC->adjTarget(); } else { e_12 = GC.firstEdge()->adjSource(); } adjEntry e_2n = e_12->faceCycleSucc(); NodeArray<int> num(GC); NodeArray<adjEntry> e_wp(GC); // List of predecessors on circle C_k NodeArray<adjEntry> e_wq(GC); // List of successors on circle C_k computeOrder(GC, num , e_wp, e_wq, e_12, e_2n, e_2n->faceCycleSucc()); computeCoordinates(GC, boundingBox, gridLayout, num, e_wp, e_wq); }
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 }