/* * 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. * */ #ifndef STRAIGHTENER_H #define STRAIGHTENER_H #include #include #include #include #include "libvpsc/rectangle.h" #include "libcola/commondefs.h" namespace cola { class Cluster; class ConvexCluster; class SeparationConstraint; } namespace straightener { struct Route { Route(unsigned n) : n(n), xs(new double[n]), ys(new double[n]) {} ~Route() { delete [] xs; delete [] ys; } void print() { std::cout << "double xs[]={"; std::copy(xs,xs+n-1,std::ostream_iterator(std::cout,",")); std::cout << xs[n-1] << "};" << std::endl << "double ys[]={"; std::copy(ys,ys+n-1,std::ostream_iterator(std::cout,",")); std::cout << ys[n-1] << "};" << std::endl; } void boundingBox(double &xmin,double &ymin,double &xmax,double &ymax) { xmin=ymin=DBL_MAX; xmax=ymax=-DBL_MAX; for(unsigned i=0;i dummyNodes; std::vector path; std::vector activePath; std::vector debugPoints; std::vector debugLines; void print() { printf("Edge[%d]=(%d,%d)\n",id,startNode,endNode); route->print(); } // if the edge route intersects with any of the rectangles in rects then reroute // those parts of the route around the rectangle boundaries void rerouteAround(std::vector const &rects) { unsigned rid=0; for(std::vector::const_iterator r=rects.begin();r!=rects.end();r++,rid++) { if(rid!=startNode && rid!=endNode) { route->rerouteAround(*r); } } updateBoundingBox(); } // Edge with a non-trivial route Edge(unsigned id, unsigned start, unsigned end, Route* route) : ScanObject(id), startNode(start), endNode(end), route(route) { updateBoundingBox(); } // Edge with a trivial route Edge(unsigned id, unsigned start, unsigned end, double x1, double y1, double x2, double y2) : ScanObject(id), startNode(start), endNode(end) { route = new Route(2); route->xs[0]=x1; route->ys[0]=y1; route->xs[1]=x2; route->ys[1]=y2; updateBoundingBox(); } ~Edge() { delete route; } bool isEnd(unsigned n) const { if(startNode==n||endNode==n) return true; return false; } void nodePath(std::vector& nodes, bool allActive); void createRouteFromPath(std::vector const & nodes); void xpos(double y, std::vector& xs) const { // search line segments for intersection points with y pos for(unsigned i=1;in;i++) { double ax=route->xs[i-1], bx=route->xs[i], ay=route->ys[i-1], by=route->ys[i]; double r=(y-ay)/(by-ay); // as long as y is between ay and by then r>0 if(r>=0&&r<=1) { xs.push_back(ax+(bx-ax)*r); } } } void ypos(double x, std::vector& ys) const { // search line segments for intersection points with x pos for(unsigned i=1;in;i++) { double ax=route->xs[i-1], bx=route->xs[i], ay=route->ys[i-1], by=route->ys[i]; double r=(x-ax)/(bx-ax); // as long as y is between ax and bx then r>0 if(r>0&&r<=1) { ys.push_back(ay+(by-ay)*r); } } } Route const * getRoute() const { return route; } void setRoute(Route * r) { delete route; route=r; updateBoundingBox(); } private: void updateBoundingBox() { route->boundingBox(min[0],min[1],max[0],max[1]); } Route* route; }; class Straightener { public: Straightener( const double strength, const vpsc::Dim dim, std::vector const & rs, cola::FixedList const & fixed, std::vector const & edges, vpsc::Variables const & vs, vpsc::Variables & lvs, vpsc::Constraints & lcs, std::valarray & oldCoords, std::valarray & oldG); ~Straightener(); void updateNodePositions(); void finalizeRoutes(); void computeForces(cola::SparseMap &H); void computeForces2(cola::SparseMap &H); double computeStress(std::valarray const &coords); double computeStress2(std::valarray const &coords); std::valarray dummyNodesX; std::valarray dummyNodesY; std::valarray g; std::valarray coords; unsigned N; private: double strength; const vpsc::Dim dim; cola::FixedList const & fixed; std::vector const & edges; vpsc::Variables const & vs; vpsc::Variables & lvs; std::vector nodes; double len(const unsigned u, const unsigned v, double& dx, double& dy, double& dx2, double& dy2); double gRule1(const unsigned a, const unsigned b); double gRule2(const unsigned a, const unsigned b, const unsigned c); double hRuleD1(const unsigned u, const unsigned v, const double sqrtf); double hRuleD2(const unsigned u, const unsigned v, const unsigned w, const double sqrtf); double hRule2(const unsigned u, const unsigned v, const unsigned w, const double sqrtf); double hRule3(const unsigned u, const unsigned v, const unsigned w, const double sqrtf); double hRule4(const unsigned a, const unsigned b, const unsigned c, const unsigned d); double hRule56(const unsigned u, const unsigned v, const unsigned a, const unsigned b, const unsigned c); double hRule7(const unsigned a, const unsigned b, const unsigned c, const unsigned d, const double sqrtf); double hRule8(const unsigned u, const unsigned v, const unsigned w, const unsigned a, const unsigned b, const unsigned c); }; class Cluster { public: cola::ConvexCluster* colaCluster; Cluster(cola::ConvexCluster* c) : colaCluster(c) {} double scanpos; std::vector boundary; void updateActualBoundary(); }; class Node : public ScanObject { public: Cluster* cluster; // Nodes may optionally belong to a cluster. // Neg cluster_id means no cluster - i.e. top-level membership. double pos[2]; double scanpos; double length[2]; Edge* edge; bool dummy; // nodes on edge paths (but not ends) are dummy bool scan; // triggers scan events bool active; // node is active if it is not dummy or is dummy and involved in // a violated constraint bool open; // a node is opened (if scan is true) when the scanline first reaches // its boundary and closed when the scanline leaves it. Node(unsigned id, vpsc::Rectangle const * r) : ScanObject(id),cluster(nullptr), edge(nullptr),dummy(false),scan(true),active(true),open(false) { for(unsigned i=0;i<2;i++) { pos[i]=r->getCentreD(i); min[i]=r->getMinD(i); max[i]=r->getMaxD(i); length[i]=r->length(i); } } Node(unsigned id, const double x, const double y) : ScanObject(id),cluster(nullptr), edge(nullptr),dummy(false),scan(false),active(true),open(false) { pos[vpsc::HORIZONTAL]=x; pos[vpsc::VERTICAL]=y; for(unsigned i=0;i<2;i++) { length[i]=4; min[i]=pos[i]-length[i]/2.0; max[i]=pos[i]+length[i]/2.0; } } double euclidean_distance(Node const * v) const { double dx=pos[0]-v->pos[0]; double dy=pos[1]-v->pos[1]; return sqrt(dx*dx+dy*dy); } private: friend void sortNeighbours(const vpsc::Dim dim, Node * v, Node * l, Node * r, const double conjpos, std::vector const & openEdges, std::vector& L, std::vector& nodes); Node(const unsigned id, const double x, const double y, Edge* e) : ScanObject(id),cluster(nullptr), edge(e),dummy(true),scan(false),active(false) { pos[vpsc::HORIZONTAL]=x; pos[vpsc::VERTICAL]=y; for(unsigned i=0;i<2;i++) { length[i]=4; min[i]=pos[i]-length[i]/2.0; max[i]=pos[i]+length[i]/2.0; } e->dummyNodes.push_back(id); } }; struct CmpNodePos { bool operator() (const Node* u, const Node* v) const { double upos = u->scanpos; double vpos = v->scanpos; bool tiebreaker = u < v; if (u->cluster != v->cluster) { if(u->cluster!=nullptr) { upos = u->cluster->scanpos; } if(v->cluster!=nullptr) { vpos = v->cluster->scanpos; } tiebreaker = u->cluster < v->cluster; } if (upos < vpos) { return true; } if (vpos < upos) { return false; } return tiebreaker; } }; typedef std::set NodeSet; // defines references to three variables for which the goal function // will be altered to prefer points u-b-v are in a linear arrangement // such that b is placed at u+t(v-u). struct LinearConstraint { LinearConstraint( Node const & u, Node const & v, Node const & b, double w) : u(u.id),v(v.id),b(b.id),w(w) { // from cosine rule: ub.uv/|uv|=|ub|cos(theta) double uvx = v.pos[0] - u.pos[0], uvy = v.pos[1] - u.pos[1], ubx = b.pos[0] - u.pos[0], uby = b.pos[1] - u.pos[1], duv2 = uvx * uvx + uvy * uvy; if(duv2 < 0.0001) { t=0; } else { t = (uvx * ubx + uvy * uby)/duv2; } duu=(1-t)*(1-t); duv=t*(1-t); dub=t-1; dvv=t*t; dvb=-t; dbb=1; //printf("New LC: t=%f\n",t); } unsigned u; unsigned v; unsigned b; double w; // weight double t; // 2nd partial derivatives of the goal function // (X[b] - (1-t) X[u] - t X[v])^2 double duu; double duv; double dub; double dvv; double dvb; double dbb; }; void setEdgeLengths(double **D, std::vector & edges); double pathLength(Edge const * e, std::vector const & nodes); double computeStressFromRoutes(double strength, std::vector & edges); typedef std::vector LinearConstraints; void generateConstraints( const vpsc::Dim dim, std::vector & nodes, std::vector const & edges, std::vector& cs, bool xSkipping); void nodePath(Edge& e, std::vector& nodes, std::vector& path); void generateClusterBoundaries( const vpsc::Dim dim, std::vector & nodes, std::vector & edges, std::vector const & rs, cola::Cluster const & clusterHierarchy, std::vector& sclusters); } // namespace straightener #endif