/* * vim: ts=4 sw=4 et tw=0 wm=0 * * libcola - A library providing force-directed network layout using the * stress-majorization method subject to separation constraints. * * Copyright (C) 2006-2008 Monash University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * See the file LICENSE.LGPL distributed with the library. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * */ #include #include #include #include #include "libcola/output_svg.h" #include "libcola/cola.h" #include "libcola/straightener.h" using namespace cola; using vpsc::Rectangle; using std::endl; using std::cout; using std::ios; using std::max; using std::min; using std::ofstream; using std::vector; using std::list; void OutputFile::generate() { unsigned E=es.size(); bool cleanupRoutes=false; if(routes==nullptr) { cleanupRoutes=true; routes = new vector(E); for(unsigned i=0;ixs[0]=rs[es[i].first]->getCentreX(); r->ys[0]=rs[es[i].first]->getCentreY(); r->xs[1]=rs[es[i].second]->getCentreX(); r->ys[1]=rs[es[i].second]->getCentreY(); (*routes)[i]=r; } } #if defined (CAIRO_HAS_SVG_SURFACE) && defined (CAIRO_HAS_PDF_SURFACE) double width,height,r=2; if(rects) r=rs[0]->width()/2; double xmin=DBL_MAX, ymin=xmin; double xmax=-DBL_MAX, ymax=xmax; for (unsigned i=0;igetCentreX(), y=rs[i]->getCentreY(); xmin=min(xmin,x); ymin=min(ymin,y); xmax=max(xmax,x); ymax=max(ymax,y); } xmax+=2*r; ymax+=2*r; xmin-=2*r; ymin-=2*r; width=xmax-xmin; height=ymax-ymin; Cairo::RefPtr cr; openCairo(cr,width,height); /* set background colour cr->save(); // save the state of the context cr->set_source_rgb(0.86, 0.85, 0.47); cr->paint(); // fill image with the color cr->restore(); // color is back to black now */ cr->set_line_width(1.); cr->set_font_size(8); cr->save(); if(rc) for(Clusters::const_iterator c=rc->clusters.begin();c!=rc->clusters.end();c++) { draw_cluster_boundary(cr,**c,xmin,ymin); } if(curvedEdges) draw_curved_edges(cr,es,xmin,ymin); else draw_edges(cr,*routes,xmin,ymin); Cairo::TextExtents te; for (unsigned i=0;igetCentreX()-xmin, y=rs[i]->getCentreY()-ymin; cr->arc(x,y,r, 0.0, 2.0 * M_PI); cr->fill(); } else { double x=rs[i]->getMinX()-xmin+0.5, y=rs[i]->getMinY()-ymin+0.5; std::string str; if(labels.size()==rs.size()) { str=labels[i]; } else { std::stringstream s; s<get_text_extents(str,te); /* double llx = x-te.width/2.-1; double lly = y-te.height/2.-1; cr->rectangle(llx,lly,te.width+2,te.height+2); */ cr->rectangle(x,y, rs[i]->width()-1,rs[i]->height()-1); cr->stroke_preserve(); cr->save(); cr->set_source_rgba(245./255., 233./255., 177./255., 0.6); cr->fill(); cr->restore(); if(labels.size()==rs.size()) { cr->move_to(x-te.x_bearing+te.width/2.,y-te.y_bearing+te.height/2.); cr->show_text(str); } cr->stroke(); } } cr->show_page(); std::cout << "Wrote file \"" << fname << "\"" << std::endl; #else std::cout << "WARNING: cola::OutputFile::generate(): No SVG file produced." << std::endl << " You must have cairomm (and cairo with SVG support) " << "this to work." << std::endl; #endif if(cleanupRoutes) { for(unsigned i=0;i const &cr, Cluster &c, const double xmin, const double ymin) { c.computeBoundary(rs); cr->save(); // background cr->set_source_rgb(0.7, 0.7, 224./255.); cr->move_to(c.hullX[0]-xmin,c.hullY[0]-ymin); for(unsigned i=1;iline_to(c.hullX[i]-xmin,c.hullY[i]-ymin); } cr->line_to(c.hullX[0]-xmin,c.hullY[0]-ymin); cr->fill(); cr->restore(); // outline cr->move_to(c.hullX[0]-xmin,c.hullY[0]-ymin); for(unsigned i=1;iline_to(c.hullX[i]-xmin,c.hullY[i]-ymin); } cr->line_to(c.hullX[0]-xmin,c.hullY[0]-ymin); cr->stroke(); } void OutputFile::draw_edges(Cairo::RefPtr &cr, vector const & es, double const xmin, double const ymin) { cr->save(); // background cr->set_source_rgba(0,0,1,0.5); for (unsigned i=0;imove_to(r->xs[0]-xmin,r->ys[0]-ymin); for (unsigned j=1;jn;j++) { cr->line_to(r->xs[j]-xmin,r->ys[j]-ymin); } cr->stroke(); } cr->restore(); } namespace bundles { struct CEdge { unsigned startID, endID; double x0,y0,x1,y1,x2,y2,x3,y3; }; struct CBundle; struct CNode { double x,y; vector edges; list bundles; }; double vangle(double ax,double ay, double bx, double by) { double ab=ax*bx+ay*by; double len=sqrt(ax*ax+ay*ay)*sqrt(bx*bx+by*by); double angle=acos(ab/len); //printf("ab=%f len=%f angle=%f\n",ab,len,angle); return angle; } struct CBundle { unsigned w; double x0, y0; double sx,sy; vector edges; CBundle(CNode const &u) : w(u.edges.size()), x0(u.x), y0(u.y), sx(w*u.x), sy(w*u.y) { } void addEdge(CEdge *e) { if(x0==e->x0 && y0==e->y0) { sx+=e->x3; sy+=e->y3; } else { sx+=e->x0; sy+=e->y0; } edges.push_back(e); } double x1() const { return sx/(w+edges.size()); } double y1() const { return sy/(w+edges.size()); } double angle(CBundle* const &b) const { double ax=x1()-x0; double ay=y1()-y0; double bx=b->x1()-b->x0; double by=b->y1()-b->y0; return vangle(ax,ay,bx,by); } double yangle() const { double x=x1()-x0; double y=y1()-y0; double o=x<0?1:-1; return vangle(0,1,x,y)*o+M_PI; } void merge(CBundle* b) { for(unsigned i=0;iedges.size();i++) { addEdge(b->edges[i]); } } void dump() { printf("Bundle: "); for(unsigned i=0;istartID,edges[i]->endID); } } }; struct clockwise { bool operator() (CBundle* const &a, CBundle* const &b) { return a->yangle()yangle(); } }; } //namespace bundles /* * draw edges bundled. That is, edges are drawn as splines, with the control points * between adjacent edges outgoing from a particular node shared if the angle between them * is less than pi/8 */ void OutputFile::draw_curved_edges(Cairo::RefPtr &cr, vector const & es, const double xmin, const double ymin) { using namespace bundles; vector nodes(rs.size()); vector edges(es.size()); for (unsigned i=0;istartID=start; e->endID=end; nodes[start].x=rs[start]->getCentreX()-xmin; nodes[start].y=rs[start]->getCentreY()-ymin; nodes[end].x=rs[end]->getCentreX()-xmin; nodes[end].y=rs[end]->getCentreY()-ymin; e->x0=nodes[start].x; e->x1=nodes[start].x; e->x2=nodes[end].x; e->x3=nodes[end].x; e->y0=nodes[start].y; e->y1=nodes[start].y; e->y2=nodes[end].y; e->y3=nodes[end].y; nodes[end].edges.push_back(e); nodes[start].edges.push_back(e); } for (unsigned i=0;iaddEdge(u.edges[j]); u.bundles.push_back(b); } u.bundles.sort(clockwise()); /* printf("Sorted: \n"); list::iterator i,j; for(list::iterator i=u.bundles.begin();i!=u.bundles.end();i++) { CBundle* a=*i; a->dump(); printf(" angle=%f\n",a->yangle()); } printf("---------\n"); */ while(true) { double minAngle=DBL_MAX; list::iterator mini,minj,i,j; for(i=u.bundles.begin();i!=u.bundles.end();i++) { j=i; if(++j==u.bundles.end()) { j=u.bundles.begin(); } CBundle* a=*i; CBundle* b=*j; double angle=b->yangle()-a->yangle(); if(angle<0) angle+=2*M_PI; //printf("between "); //a->dump(); b->dump(); //printf(" angle=%f\n",angle); if(anglecos(M_PI/8.)) break; CBundle* a=*mini; CBundle* b=*minj; //a->dump(); //b->dump(); b->merge(a); //printf("***Merged on %f***: ",minAngle); //b->dump(); //printf("\n"); u.bundles.erase(mini); if(u.bundles.size() < 2) break; } for(list::iterator i=u.bundles.begin();i!=u.bundles.end();i++) { CBundle* b=*i; for(unsigned i=0;iedges.size();i++) { CEdge* e=b->edges[i]; if(e->x0==u.x&&e->y0==u.y) { e->x1=b->x1(); e->y1=b->y1(); } else { e->x2=b->x1(); e->y2=b->y1(); } } } } cr->save(); // background cr->set_source_rgba(0,0,1,0.2); for (unsigned i=0;imove_to(e.x0,e.y0); cr->curve_to(e.x1,e.y1,e.x2,e.y2,e.x3,e.y3); cr->stroke(); } cr->restore(); } void OutputFile::openCairo(Cairo::RefPtr &cr, double width, double height) { if(fname.rfind("pdf") == (fname.length()-3) ) { printf("writing pdf file: %s\n",fname.c_str()); Cairo::RefPtr pdfsurface = Cairo::PdfSurface::create(fname, width, height); cr = Cairo::Context::create(pdfsurface); } else { printf("writing svg file: %s\n",fname.c_str()); Cairo::RefPtr svgsurface = Cairo::SvgSurface::create(fname, width, height); cr = Cairo::Context::create(svgsurface); } } #endif // HAVE_CAIROMM