diff options
| author | Marc Jeanmougin <marc.jeanmougin@telecom-paristech.fr> | 2018-04-29 14:25:32 +0000 |
|---|---|---|
| committer | Marc Jeanmougin <marc.jeanmougin@telecom-paristech.fr> | 2018-04-29 14:25:32 +0000 |
| commit | ab5f8ff5869021958f4ae8b838c3d707a2e85eaa (patch) | |
| tree | 4907675828a5401d013b7587538cc8541edd2764 /src/libcola/colafd.cpp | |
| parent | moved libcroco, libuemf, libdepixelize to 3rdparty folder (diff) | |
| download | inkscape-ab5f8ff5869021958f4ae8b838c3d707a2e85eaa.tar.gz inkscape-ab5f8ff5869021958f4ae8b838c3d707a2e85eaa.zip | |
Put adaptagrams into its own folder
Diffstat (limited to 'src/libcola/colafd.cpp')
| -rw-r--r-- | src/libcola/colafd.cpp | 1490 |
1 files changed, 0 insertions, 1490 deletions
diff --git a/src/libcola/colafd.cpp b/src/libcola/colafd.cpp deleted file mode 100644 index 748f3354b..000000000 --- a/src/libcola/colafd.cpp +++ /dev/null @@ -1,1490 +0,0 @@ -/* - * 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-2015 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. - * - * Author(s): Tim Dwyer - * Michael Wybrow - * -*/ - -// cmath needs ::strcpy_s under MinGW so include cstring. -#include <cstring> - -#include <vector> -#include <cmath> -#include <limits> - -#include "libvpsc/solve_VPSC.h" -#include "libvpsc/variable.h" -#include "libvpsc/constraint.h" -#include "libvpsc/rectangle.h" - -#include "libcola/commondefs.h" -#include "libcola/cola.h" -#include "libcola/shortest_paths.h" -#include "libcola/straightener.h" -#include "libcola/cc_clustercontainmentconstraints.h" -#include "libcola/cc_nonoverlapconstraints.h" - -#ifdef MAKEFEASIBLE_DEBUG - #include "libcola/output_svg.h" -#endif - -// Needs to come last since it will include windows.h on WIN32 and -// may mess up C++ std library include on GCC 4.4 -#include "libcola/cola_log.h" - -using namespace std; -using vpsc::XDIM; -using vpsc::YDIM; - -namespace cola { - -template <class T> -void delete_vector(vector<T*> &v) { - for_each(v.begin(),v.end(),delete_object()); - v.clear(); -} -Resizes PreIteration::__resizesNotUsed; -Locks PreIteration::__locksNotUsed; -inline double dotProd(valarray<double> x, valarray<double> y) { - COLA_ASSERT(x.size()==y.size()); - double dp=0; - for(unsigned i=0;i<x.size();i++) { - dp+=x[i]*y[i]; - } - return dp; -} -template <typename T> -void dumpSquareMatrix(unsigned n, T** L) { - printf("Matrix %dX%d\n{",n,n); - for(unsigned i=0;i<n;i++) { - printf("{"); - for(unsigned j=0;j<n;j++) { - std::cout<<L[i][j]<<std::endl;; - char c=j==n-1?'}':','; - printf("%c",c); - } - char c=i==n-1?'}':','; - printf("%c\n",c); - } -} - -ConstrainedFDLayout::ConstrainedFDLayout(const vpsc::Rectangles& rs, - const std::vector< Edge >& es, const double idealLength, - const EdgeLengths& eLengths, - TestConvergence *doneTest, PreIteration* preIteration) - : n(rs.size()), - X(valarray<double>(n)), - Y(valarray<double>(n)), - done(doneTest), - using_default_done(false), - preIteration(preIteration), - topologyAddon(new TopologyAddonInterface()), - rungekutta(true), - desiredPositions(NULL), - clusterHierarchy(NULL), - rectClusterBuffer(0), - m_idealEdgeLength(idealLength), - m_generateNonOverlapConstraints(false), - m_edge_lengths(eLengths.data(), eLengths.size()), - m_nonoverlap_exemptions(new NonOverlapConstraintExemptions()) -{ - minD = DBL_MAX; - - if (done == NULL) - { - done = new TestConvergence(); - using_default_done = true; - } - - //FILELog::ReportingLevel() = logDEBUG1; - FILELog::ReportingLevel() = logERROR; - boundingBoxes = rs; - done->reset(); - unsigned i=0; - for(vpsc::Rectangles::const_iterator ri=rs.begin();ri!=rs.end();++ri,++i) { - X[i]=(*ri)->getCentreX(); - Y[i]=(*ri)->getCentreY(); - FILE_LOG(logDEBUG) << *ri; - } - D=new double*[n]; - G=new unsigned short*[n]; - for(unsigned i=0;i<n;i++) { - D[i]=new double[n]; - G[i]=new unsigned short[n]; - } - - computePathLengths(es,m_edge_lengths); -} - -void dijkstra(const unsigned s, const unsigned n, double* d, - const vector<Edge>& es, const std::valarray<double> & eLengths) -{ - shortest_paths::dijkstra(s,n,d,es,eLengths); -} - -void ConstrainedFDLayout::setConstraints(const cola::CompoundConstraints& ccs) -{ - this->ccs = ccs; -} - -void ConstrainedFDLayout::setAvoidNodeOverlaps(bool avoidOverlaps, - std::vector<std::vector<unsigned> > listOfNodeGroups) -{ - m_generateNonOverlapConstraints = avoidOverlaps; - m_nonoverlap_exemptions->addExemptGroupOfNodes(listOfNodeGroups); -} - - -void ConstrainedFDLayout::setDesiredPositions(DesiredPositions *desiredPositions) -{ - this->desiredPositions = desiredPositions; -} - - -/* - * Sets up the D and G matrices. D is the required euclidean distances - * between pairs of nodes based on the shortest paths between them (using - * m_idealEdgeLength*eLengths[edge] as the edge length, if eLengths array - * is provided otherwise just m_idealEdgeLength). G is a matrix of - * unsigned ints such that G[u][v]= - * 0 if there are no forces required between u and v - * (for example, if u and v are in unconnected components) - * 1 if attractive forces are required between u and v - * (i.e. if u and v are immediately connected by an edge and there is - * no topology route between u and v (for which an attractive force - * is computed elsewhere)) - * 2 if no attractive force is required between u and v but there is - * a connected path between them. - */ -void ConstrainedFDLayout::computePathLengths( - const vector<Edge>& es, std::valarray<double> eLengths) -{ - // Correct zero or negative entries in eLengths array. - for (size_t i = 0; i < eLengths.size(); ++i) - { - if (eLengths[i] <= 0) - { - fprintf(stderr, "Warning: ignoring non-positive length at index %d " - "in ideal edge length array.\n", (int) i); - eLengths[i] = 1; - } - } - - shortest_paths::johnsons(n,D,es,eLengths); - //dumpSquareMatrix<double>(n,D); - for(unsigned i=0;i<n;i++) { - for(unsigned j=0;j<n;j++) { - if(i==j) continue; - double& d=D[i][j]; - unsigned short& p=G[i][j]; - p=2; - if(d==DBL_MAX) { - // i and j are in disconnected subgraphs - p=0; - } else { - d*=m_idealEdgeLength; - } - - if ((d > 0) && (d < minD)) { - minD = d; - } - } - } - if (minD == DBL_MAX) minD = 1; - - for(vector<Edge>::const_iterator e=es.begin();e!=es.end();++e) { - unsigned u=e->first, v=e->second; - G[u][v]=G[v][u]=1; - } - topologyAddon->computePathLengths(G); - //dumpSquareMatrix<short>(n,G); -} - -typedef valarray<double> Position; -void getPosition(Position& X, Position& Y, Position& pos) { - unsigned n=X.size(); - COLA_ASSERT(Y.size()==n); - COLA_ASSERT(pos.size()==2*n); - for(unsigned i=0;i<n;++i) { - pos[i]=X[i]; - pos[i+n]=Y[i]; - } -} -/* - * moves all rectangles to the desired position while respecting - * constraints. - * @param pos target positions of both axes - */ -void ConstrainedFDLayout::setPosition(Position& pos) { - COLA_ASSERT(Y.size()==X.size()); - COLA_ASSERT(pos.size()==2*X.size()); - moveTo(vpsc::HORIZONTAL,pos); - moveTo(vpsc::VERTICAL,pos); -} -/* - * Layout is performed by minimizing the P-stress goal function iteratively. - * At each iteration taking a step in the steepest-descent direction. - * x0 is the current position, x1 is the x0 - descentvector. - */ -void ConstrainedFDLayout::computeDescentVectorOnBothAxes( - const bool xAxis, const bool yAxis, - double stress, Position& x0, Position& x1) { - setPosition(x0); - if(xAxis) { - applyForcesAndConstraints(vpsc::HORIZONTAL,stress); - } - if(yAxis) { - applyForcesAndConstraints(vpsc::VERTICAL,stress); - } - getPosition(X,Y,x1); -} - -/* - * run() implements the main layout loop, taking descent steps until - * stress is no-longer significantly reduced. - * done is a callback used to check stress but also to report updated - * positions. - */ -void ConstrainedFDLayout::run(const bool xAxis, const bool yAxis) -{ - // This generates constraints for non-overlap inside and outside - // of clusters. To assign correct variable indexes it requires - // that vs[] contains elements equal to the number of rectangles. - vpsc::Variables vs[2]; - vs[0].resize(n); - vs[1].resize(n); - generateNonOverlapAndClusterCompoundConstraints(vs); - - FILE_LOG(logDEBUG) << "ConstrainedFDLayout::run..."; - double stress=DBL_MAX; - do { - if(preIteration) { - if(!(*preIteration)()) { - break; - } - //printf("preIteration->changed=%d\n",preIteration->changed); - if(preIteration->changed) { - stress=DBL_MAX; - } - if(preIteration->resizes.size()>0) { - FILE_LOG(logDEBUG) << " Resize event!"; - handleResizes(preIteration->resizes); - } - } - unsigned N=2*n; - Position x0(N),x1(N); - getPosition(X,Y,x0); - if(rungekutta) { - Position a(N),b(N),c(N),d(N),ia(N),ib(N); - computeDescentVectorOnBothAxes(xAxis,yAxis,stress,x0,a); - ia=x0+(a-x0)/2.0; - computeDescentVectorOnBothAxes(xAxis,yAxis,stress,ia,b); - ib=x0+(b-x0)/2.0; - computeDescentVectorOnBothAxes(xAxis,yAxis,stress,ib,c); - computeDescentVectorOnBothAxes(xAxis,yAxis,stress,c,d); - x1=a+2.0*b+2.0*c+d; - x1/=6.0; - } else { - computeDescentVectorOnBothAxes(xAxis,yAxis,stress,x0,x1); - } - setPosition(x1); - stress=computeStress(); - FILE_LOG(logDEBUG) << "stress="<<stress; - } while(!(*done)(stress,X,Y)); - for(unsigned i=0;i<n;i++) { - vpsc::Rectangle *r=boundingBoxes[i]; - FILE_LOG(logDEBUG) << *r; - } - FILE_LOG(logDEBUG) << "ConstrainedFDLayout::run done."; - - // Clear extra constraints. - for_each(extraConstraints.begin(), extraConstraints.end(), delete_object()); - extraConstraints.clear(); - - // Free extra variables used for cluster containment. - for (size_t dim = 0; dim < 2; ++dim) - { - for (size_t i = n; i < vs[dim].size(); ++i) - { - delete vs[dim][i]; - } - } -} - -/* - * Same as run, but only applies one iteration. This may be useful - * where it's too hard to implement a call-back (e.g. in java apps) - */ -void ConstrainedFDLayout::runOnce(const bool xAxis, const bool yAxis) { - if(n==0) return; - double stress=DBL_MAX; - unsigned N=2*n; - Position x0(N),x1(N); - getPosition(X,Y,x0); - if(rungekutta) { - Position a(N),b(N),c(N),d(N),ia(N),ib(N); - computeDescentVectorOnBothAxes(xAxis,yAxis,stress,x0,a); - ia=x0+(a-x0)/2.0; - computeDescentVectorOnBothAxes(xAxis,yAxis,stress,ia,b); - ib=x0+(b-x0)/2.0; - computeDescentVectorOnBothAxes(xAxis,yAxis,stress,ib,c); - computeDescentVectorOnBothAxes(xAxis,yAxis,stress,c,d); - x1=a+2.0*b+2.0*c+d; - x1/=6.0; - } else { - computeDescentVectorOnBothAxes(xAxis,yAxis,stress,x0,x1); - } -} - - -// Used for sorting the CompoundConstraints from lowest priority to highest. -static bool cmpCompoundConstraintPriority(const cola::CompoundConstraint *lhs, - const cola::CompoundConstraint *rhs) -{ - return lhs->priority() < rhs->priority(); -} - - -void ConstrainedFDLayout::recGenerateClusterVariablesAndConstraints( - vpsc::Variables (&vars)[2], unsigned int& priority, - cola::NonOverlapConstraints *noc, Cluster *cluster, - cola::CompoundConstraints& idleConstraints) -{ - for (std::vector<Cluster*>::iterator curr = cluster->clusters.begin(); - curr != cluster->clusters.end(); ++curr) - { - // For each of the child clusters, recursively call this function. - recGenerateClusterVariablesAndConstraints(vars, priority, - noc, *curr, idleConstraints); - } - - if ( (noc == NULL) && (dynamic_cast<RootCluster *> (cluster) == NULL) ) - { - double freeWeight = 0.00000000001; - // Then create left and right variables for the boundary of this - // cluster. - vpsc::Variable *variable = NULL; - cluster->clusterVarId = vars[XDIM].size(); - COLA_ASSERT(vars[XDIM].size() == vars[YDIM].size()); - // Left: - variable = new vpsc::Variable(vars[XDIM].size(), - cluster->bounds.getMinX(), freeWeight); - vars[XDIM].push_back(variable); - // Right: - variable = new vpsc::Variable(vars[XDIM].size(), - cluster->bounds.getMaxX(), freeWeight); - vars[XDIM].push_back(variable); - // Bottom:: - variable = new vpsc::Variable(vars[YDIM].size(), - cluster->bounds.getMinY(), freeWeight); - vars[YDIM].push_back(variable); - // Top: - variable = new vpsc::Variable(vars[YDIM].size(), - cluster->bounds.getMaxY(), freeWeight); - vars[YDIM].push_back(variable); - - RectangularCluster *rc = dynamic_cast<RectangularCluster *> (cluster); - if (rc) - { - rc->generateFixedRectangleConstraints(idleConstraints, - boundingBoxes, vars); - } - - priority--; - cola::ClusterContainmentConstraints *ccc = - new cola::ClusterContainmentConstraints(cluster, priority, - boundingBoxes); - idleConstraints.push_back(ccc); - } - - if (noc) - { - // Enforce non-overlap between all the shapes and clusters at this - // level. - //printf("Cluster #%d non-overlap constraints - nodes %d clusters %d\n", - // (int) cluster->clusterVarId, (int) cluster->nodes.size(), - // (int) cluster->clusters.size()); - unsigned int group = cluster->clusterVarId; - // The set of clusters to put non-overlap constraints between is the - // child clusters of this cluster. We will also add any overlapping - // clusters (due to multiple inheritence) to this set. - std::set<Cluster *> expandedClusterSet(cluster->clusters.begin(), - cluster->clusters.end()); - for (std::set<unsigned>::iterator curr = cluster->nodes.begin(); - curr != cluster->nodes.end(); ++curr) - { - unsigned id = *curr; - - if (cluster->m_overlap_replacement_map.count(id) > 0) - { - // This shape is child of another cluster also, so replace - // this node with the other cluster for the purpose of - // non-overlap with other children of the current cluster. - expandedClusterSet.insert( - cluster->m_overlap_replacement_map[id]); - } - // Normal case: Add shape for generation of non-overlap - // constraints. - noc->addShape(id, boundingBoxes[id]->width() / 2, - boundingBoxes[id]->height() / 2, group); - } - for (std::set<Cluster*>::iterator curr = expandedClusterSet.begin(); - curr != expandedClusterSet.end(); ++curr) - { - Cluster *cluster = *curr; - RectangularCluster *rectCluster = - dynamic_cast<RectangularCluster *> (cluster); - if (rectCluster && rectCluster->clusterIsFromFixedRectangle()) - { - // Treat it like a shape for non-overlap. - unsigned id = rectCluster->rectangleIndex(); - noc->addShape(id, boundingBoxes[id]->width() / 2, - boundingBoxes[id]->height() / 2, group); - } - else - { - // Normal cluster. - noc->addCluster(cluster, group); - } - } - - // For the set of shapes that have been replaced due to multiple - // inheritance, still generate overlap constraints between them. - // (The group uses the ID of the right side variable of the cluster - // so it is not the same group as the cluster itself.) - for (std::set<unsigned>::iterator curr = - cluster->m_nodes_replaced_with_clusters.begin(); - curr != cluster->m_nodes_replaced_with_clusters.end(); ++curr) - { - unsigned id = *curr; - noc->addShape(id, boundingBoxes[id]->width() / 2, - boundingBoxes[id]->height() / 2, group + 1); - } - - } -} - -void ConstrainedFDLayout::generateNonOverlapAndClusterCompoundConstraints( - vpsc::Variables (&vs)[2]) -{ - if (clusterHierarchy && !clusterHierarchy->flat()) - { - // Add remaining nodes that aren't contained within any clusters - // as children of the root cluster. - std::vector<unsigned> nodesInClusterCounts(boundingBoxes.size(), 0); - clusterHierarchy->countContainedNodes(nodesInClusterCounts); - - for (unsigned int i = 0; i < nodesInClusterCounts.size(); ++i) - { - unsigned count = nodesInClusterCounts[i]; - if (!clusterHierarchy->allowsMultipleParents() && - count > 1) - { - fprintf(stderr, "Warning: node %u is contained in %d " - "clusters.\n", i, count); - } - - if (count == 0) - { - // Not present in hierarchy, so add to root cluster. - clusterHierarchy->nodes.insert(i); - } - } - - // Add non-overlap and containment constraints for all clusters - // and nodes. - unsigned int priority = PRIORITY_NONOVERLAP; - clusterHierarchy->computeBoundingRect(boundingBoxes); - - // Generate the containment constraints - recGenerateClusterVariablesAndConstraints(vs, priority, - NULL, clusterHierarchy, extraConstraints); - - // Compute overlapping clusters. - clusterHierarchy->calculateClusterPathsToEachNode(boundingBoxes.size()); - - // Generate non-overlap constraints between all clusters and - // all contained nodes. - if (m_generateNonOverlapConstraints) - { - priority--; - cola::NonOverlapConstraints *noc = - new cola::NonOverlapConstraints(m_nonoverlap_exemptions, - priority); - noc->setClusterClusterExemptions( - clusterHierarchy->m_cluster_cluster_overlap_exceptions); - recGenerateClusterVariablesAndConstraints(vs, priority, - noc, clusterHierarchy, extraConstraints); - extraConstraints.push_back(noc); - } - } - else if (m_generateNonOverlapConstraints) - { - // Add standard non-overlap constraints between each pair of - // nodes. - cola::NonOverlapConstraints *noc = - new cola::NonOverlapConstraints(m_nonoverlap_exemptions); - for (unsigned int i = 0; i < boundingBoxes.size(); ++i) - { - noc->addShape(i, boundingBoxes[i]->width() / 2, - boundingBoxes[i]->height() / 2); - } - extraConstraints.push_back(noc); - } -} - -void ConstrainedFDLayout::makeFeasible(void) -{ - vpsc::Variables vs[2]; - vpsc::Constraints valid[2]; - - vpsc::Rectangle::setXBorder(1); - vpsc::Rectangle::setYBorder(1); - - // Populate all the variables for shapes. - for (unsigned int dim = 0; dim < 2; ++dim) - { - vs[dim] = vpsc::Variables(boundingBoxes.size()); - for (unsigned int i = 0; i < vs[dim].size(); ++i) - { - double pos = (dim == 0) ? - boundingBoxes[i]->getCentreX() : - boundingBoxes[i]->getCentreY(); - vs[dim][i] = new vpsc::Variable(i, pos, 1); - } - } - - vector<double> priorPos(boundingBoxes.size()); - - generateNonOverlapAndClusterCompoundConstraints(vs); - - // Make a copy of the compound constraints and sort them by priority. - cola::CompoundConstraints idleConstraints = ccs; - // Append extraConstraints to idleConstraints. - idleConstraints.insert(idleConstraints.end(), - extraConstraints.begin(), extraConstraints.end()); - - std::sort(idleConstraints.begin(), idleConstraints.end(), - cmpCompoundConstraintPriority); - - // Initialise extra variables for compound constraints. - for (unsigned int dim = 0; dim < 2; ++dim) - { - generateVariables(idleConstraints, (vpsc::Dim) dim, vs[dim]); - } - -#ifdef MAKEFEASIBLE_DEBUG - char filename[200]; - int iteration = 0; - vector<string> labels(boundingBoxes.size()); - for(unsigned i=0;i<boundingBoxes.size();++i) - { - stringstream ss; - ss << i; - labels[i]=ss.str(); - } -#endif - - // We can keep adding new constraints to the existing VPSC instances so - // long as everything is satisfiable. Only when it's not do we discard - // the existing VPSC instance for that dimension and create a new one. - vpsc::IncSolver *solver[2] = { NULL }; - - // Main makeFeasible loop. - while (!idleConstraints.empty()) - { - // idleConstraints is sorted lowest to highest priority, so the - // highest priority constraint will be at the back of the vector. - cola::CompoundConstraint *cc = idleConstraints.back(); - idleConstraints.pop_back(); - -#ifdef MAKEFEASIBLE_DEBUG - // Debugging SVG time slice output. - std::vector<cola::Edge> es; - for (unsigned int i = 0; i < boundingBoxes.size(); ++i) - { - boundingBoxes[i]->moveCentreX(vs[0][i]->finalPosition); - boundingBoxes[i]->moveCentreY(vs[1][i]->finalPosition); - } - iteration++; - sprintf(filename, "out/file-%05d.pdf", iteration); - - OutputFile of(boundingBoxes,es,clusterHierarchy,filename,true,false); - of.setLabels(labels); - of.generate(); -#endif - - cc->markAllSubConstraintsAsInactive(); - bool subConstraintSatisfiable = true; - - if (cc->shouldCombineSubConstraints()) - { - // We are processing a combined set of satisfiable constraints, - // such as for containment within cluster boundary variables, so - // we just add all the required constraints and solve in both - // the X and Y dimension once to set the cluster boundaries to - // meaningful values. - while (cc->subConstraintsRemaining()) - { - cola::SubConstraintAlternatives alternatives = - cc->getCurrSubConstraintAlternatives(vs); - // There should be no alternatives, just guaranteed - // satisfiable constraints. - COLA_ASSERT(alternatives.size() == 1); - vpsc::Dim& dim = alternatives.front().dim; - vpsc::Constraint& constraint = alternatives.front().constraint; - vpsc::Constraint *newConstraint = - new vpsc::Constraint(constraint); - valid[dim].push_back(newConstraint); - if (solver[dim]) - { - // If we have an existing valid solver instance, add the - // constraint to that. - solver[dim]->addConstraint(newConstraint); - } - cc->markCurrSubConstraintAsActive(subConstraintSatisfiable); - } - // Satisfy the constraints in each dimension. - for (size_t dim = 0; dim < 2; ++dim) - { - if (solver[dim] == NULL) - { - // Create a new VPSC solver if necessary. - solver[dim] = new vpsc::IncSolver(vs[dim], valid[dim]); - } - solver[dim]->satisfy(); - } - continue; - } - - while (cc->subConstraintsRemaining()) - { - cola::SubConstraintAlternatives alternatives = - cc->getCurrSubConstraintAlternatives(vs); - alternatives.sort(); - - if (alternatives.empty()) - { - continue; - } - - while (!alternatives.empty()) - { - // Reset subConstraintSatisfiable for new solve. - subConstraintSatisfiable = true; - - vpsc::Dim& dim = alternatives.front().dim; - vpsc::Constraint& constraint = alternatives.front().constraint; - - // Store current values for variables. - for (unsigned int i = 0; i < priorPos.size(); ++i) - { - priorPos[i] = vs[dim][i]->finalPosition; - } - - // Some solving... - try - { - // Add the constraint from this alternative to the - // valid constraint set. - vpsc::Constraint *newConstraint = - new vpsc::Constraint(constraint); - valid[dim].push_back(newConstraint); - - //fprintf(stderr, ".%d %3d - ", dim, valid[dim].size()); - - // Try to satisfy this set of constraints.. - if (solver[dim] == NULL) - { - // Create a new VPSC solver if necessary. - solver[dim] = new vpsc::IncSolver(vs[dim], valid[dim]); - } - else - { - // Or just add the constraint to the existing solver. - solver[dim]->addConstraint(newConstraint); - } - solver[dim]->satisfy(); - } - catch (char *str) - { - subConstraintSatisfiable = false; - - std::cerr << "++++ IN ERROR BLOCK" << std::endl; - std::cerr << str << std::endl; - for (vpsc::Rectangles::iterator r = boundingBoxes.begin(); - r != boundingBoxes.end(); ++r) - { - std::cerr << **r <<std::endl; - } - } - for (size_t i = 0; i < valid[dim].size(); ++i) - { - if (valid[dim][i]->unsatisfiable) - { - // It might have made one of the earlier added - // constraints unsatisfiable, so we mark that one - // as okay since we will be reverting the most - // recent one. - valid[dim][i]->unsatisfiable = false; - - subConstraintSatisfiable = false; - } - } - - if (!subConstraintSatisfiable) - { - // Since we had unsatisfiable constraints we must - // discard this solver instance. - delete solver[dim]; - solver[dim] = NULL; - - // Restore previous values for variables. - for (unsigned int i = 0; i < priorPos.size(); ++i) - { - vs[dim][i]->finalPosition = priorPos[i]; - } - - // Delete the newly added (and unsatisfiable) - // constraint from the valid constraint set. - delete valid[dim].back(); - valid[dim].pop_back(); - } - else - { - // Satisfied, so don't need to consider other alternatives. - break; - } - // Move on to the next alternative. - alternatives.pop_front(); - } -#ifdef MAKEFEASIBLE_DEBUG - if (true || idleConstraints.size() == 0) - { - // Debugging SVG time slice output, but don't show this for - // constraints that promised satisfiable. - std::vector<cola::Edge> es; - for (unsigned int i = 0; i < boundingBoxes.size(); ++i) - { - boundingBoxes[i]->moveCentreX(vs[0][i]->finalPosition); - boundingBoxes[i]->moveCentreY(vs[1][i]->finalPosition); - } - iteration++; - sprintf(filename, "out/file-%05d.pdf", iteration); - - OutputFile of(boundingBoxes,es,clusterHierarchy,filename, - true,false); - of.setLabels(labels); - of.generate(); - } -#endif - cc->markCurrSubConstraintAsActive(subConstraintSatisfiable); - } - } - - // Delete the persistent VPSC solver instances. - for (size_t dim = 0; dim < 2; ++dim) - { - if (solver[dim]) - { - delete solver[dim]; - solver[dim] = NULL; - } - } - - // Write positions from solver variables back to Rectangles. - for (unsigned int i = 0; i < boundingBoxes.size(); ++i) - { - boundingBoxes[i]->moveCentreX(vs[0][i]->finalPosition); - boundingBoxes[i]->moveCentreY(vs[1][i]->finalPosition); - } - - vpsc::Rectangle::setXBorder(0); - vpsc::Rectangle::setYBorder(0); - - // Cleanup. - for (unsigned int dim = 0; dim < 2; ++dim) - { - for_each(valid[dim].begin(), valid[dim].end(), delete_object()); - for_each(vs[dim].begin(), vs[dim].end(), delete_object()); - } - - topologyAddon->makeFeasible(m_generateNonOverlapConstraints, - boundingBoxes, clusterHierarchy); - - // Update the X and Y vectors with the new shape positions. - for (unsigned int i = 0; i < boundingBoxes.size(); ++i) - { - X[i] = boundingBoxes[i]->getCentreX(); - Y[i] = boundingBoxes[i]->getCentreY(); - } - - // Clear extra constraints for cluster containment and non-overlap. - for_each(extraConstraints.begin(), extraConstraints.end(), delete_object()); - extraConstraints.clear(); -} - -ConstrainedFDLayout::~ConstrainedFDLayout() -{ - if (using_default_done) - { - delete done; - } - - for (unsigned i = 0; i < n; ++i) - { - delete [] G[i]; - delete [] D[i]; - } - delete [] G; - delete [] D; - delete topologyAddon; - delete m_nonoverlap_exemptions; -} - -void ConstrainedFDLayout::freeAssociatedObjects(void) -{ - // Free Rectangles - for_each(boundingBoxes.begin(), boundingBoxes.end(), delete_object()); - boundingBoxes.clear(); - - // Free compound constraints - std::list<CompoundConstraint *> freeList(ccs.begin(), ccs.end()); - freeList.sort(); - freeList.unique(); - if (freeList.size() != ccs.size()) - { - // The compound constraint list had repeated elements. - fprintf(stderr, "Warning: CompoundConstraints vector contained %d " - "duplicates.\n", (int) (ccs.size() - freeList.size())); - } - ccs.clear(); - for_each(freeList.begin(), freeList.end(), delete_object()); - - if (clusterHierarchy) - { - delete clusterHierarchy; - clusterHierarchy = NULL; - } - - topologyAddon->freeAssociatedObjects(); -} - -void ConstrainedFDLayout::setTopology(TopologyAddonInterface *newTopology) -{ - COLA_ASSERT(topologyAddon); - delete topologyAddon; - topologyAddon = newTopology->clone(); -} - -TopologyAddonInterface *ConstrainedFDLayout::getTopology(void) -{ - return topologyAddon->clone(); -} - - -void setupVarsAndConstraints(unsigned n, const CompoundConstraints& ccs, - const vpsc::Dim dim, vpsc::Rectangles& boundingBoxes, - RootCluster *clusterHierarchy, - vpsc::Variables& vs, vpsc::Constraints& cs, - valarray<double> &coords) -{ - vs.resize(n); - for (unsigned i = 0; i < n; ++i) - { - vs[i] = new vpsc::Variable(i, coords[i]); - } - - if (clusterHierarchy && !clusterHierarchy->flat()) - { - // Create variables for clusters - clusterHierarchy->computeBoundingRect(boundingBoxes); - clusterHierarchy->createVars(dim, boundingBoxes, vs); - } - - for (CompoundConstraints::const_iterator c = ccs.begin(); - c != ccs.end(); ++c) - { - (*c)->generateVariables(dim, vs); - } - for (CompoundConstraints::const_iterator c = ccs.begin(); - c != ccs.end(); ++c) - { - (*c)->generateSeparationConstraints(dim, vs, cs, boundingBoxes); - } -} - - -static void setupExtraConstraints(const CompoundConstraints& ccs, - const vpsc::Dim dim, vpsc::Variables& vs, vpsc::Constraints& cs, - vpsc::Rectangles& boundingBoxes) -{ - for (CompoundConstraints::const_iterator c = ccs.begin(); - c != ccs.end(); ++c) - { - (*c)->generateVariables(dim, vs); - } - for (CompoundConstraints::const_iterator c = ccs.begin(); - c != ccs.end(); ++c) - { - (*c)->generateSeparationConstraints(dim, vs, cs, boundingBoxes); - } -} - -void updateCompoundConstraints(const vpsc::Dim dim, - const CompoundConstraints& ccs) -{ - for (CompoundConstraints::const_iterator c = ccs.begin(); - c != ccs.end(); ++c) - { - (*c)->updatePosition(dim); - } -} -void project(vpsc::Variables& vs, vpsc::Constraints& cs, valarray<double>& coords) { - unsigned n=coords.size(); - vpsc::IncSolver s(vs,cs); - s.solve(); - for(unsigned i=0;i<n;++i) { - coords[i]=vs[i]->finalPosition; - } -} -void setVariableDesiredPositions(vpsc::Variables& vs, vpsc::Constraints& cs, - const DesiredPositionsInDim& des, valarray<double>& coords) -{ - COLA_UNUSED(cs); - - unsigned n=coords.size(); - COLA_ASSERT(vs.size()>=n); - for(unsigned i=0;i<n;++i) { - vpsc::Variable* v=vs[i]; - v->desiredPosition = coords[i]; - v->weight=1; - } - for (DesiredPositionsInDim::const_iterator d=des.begin(); - d!=des.end(); ++d) { - COLA_ASSERT(d->first<vs.size()); - vpsc::Variable* v=vs[d->first]; - v->desiredPosition = d->second; - v->weight=10000; - } -} -void checkUnsatisfiable(const vpsc::Constraints& cs, - UnsatisfiableConstraintInfos* unsatisfiable) { - for(vpsc::Constraints::const_iterator c=cs.begin();c!=cs.end();++c) { - if((*c)->unsatisfiable) { - UnsatisfiableConstraintInfo* i=new UnsatisfiableConstraintInfo(*c); - unsatisfiable->push_back(i); - } - } -} - -void ConstrainedFDLayout::handleResizes(const Resizes& resizeList) -{ - topologyAddon->handleResizes(resizeList, n, X, Y, ccs, boundingBoxes, - clusterHierarchy); -} -/* - * move positions of nodes in specified axis while respecting constraints - * @param dim axis - * @param target array of desired positions (for both axes) - */ -void ConstrainedFDLayout::moveTo(const vpsc::Dim dim, Position& target) { - COLA_ASSERT(target.size()==2*n); - FILE_LOG(logDEBUG) << "ConstrainedFDLayout::moveTo(): dim="<<dim; - valarray<double> &coords = (dim==vpsc::HORIZONTAL)?X:Y; - vpsc::Variables vs; - vpsc::Constraints cs; - setupVarsAndConstraints(n, ccs, dim, boundingBoxes, - clusterHierarchy, vs, cs, coords); - DesiredPositionsInDim des; - if(preIteration) { - for(vector<Lock>::iterator l=preIteration->locks.begin(); - l!=preIteration->locks.end();l++) { - des.push_back(make_pair(l->getID(),l->pos(dim))); - FILE_LOG(logDEBUG1)<<"desi: v["<<l->getID()<<"]=("<<l->pos(vpsc::HORIZONTAL) - <<","<<l->pos(vpsc::VERTICAL)<<")"; - } - } - for(unsigned i=0, j=(dim==vpsc::HORIZONTAL?0:n);i<n;++i,++j) { - vpsc::Variable* v=vs[i]; - v->desiredPosition = target[j]; - } - setVariableDesiredPositions(vs,cs,des,coords); - if (topologyAddon->useTopologySolver()) - { - topologyAddon->moveTo(dim, vs, cs, coords, clusterHierarchy); - } else { - // Add non-overlap constraints, but not variables again. - setupExtraConstraints(extraConstraints, dim, vs, cs, boundingBoxes); - // Projection. - project(vs,cs,coords); - moveBoundingBoxes(); - } - updateCompoundConstraints(dim, ccs); - for_each(vs.begin(),vs.end(),delete_object()); - for_each(cs.begin(),cs.end(),delete_object()); -} -/* - * The following computes an unconstrained solution then uses Projection to - * make this solution feasible with respect to constraints by moving things as - * little as possible. If "meta-constraints" such as avoidOverlaps or edge - * straightening are required then dummy variables will be generated. - */ -double ConstrainedFDLayout::applyForcesAndConstraints(const vpsc::Dim dim, const double oldStress) { - FILE_LOG(logDEBUG) << "ConstrainedFDLayout::applyForcesAndConstraints(): dim="<<dim; - valarray<double> g(n); - valarray<double> &coords = (dim==vpsc::HORIZONTAL)?X:Y; - DesiredPositionsInDim des; - if(preIteration) { - for(vector<Lock>::iterator l=preIteration->locks.begin(); - l!=preIteration->locks.end();l++) { - des.push_back(make_pair(l->getID(),l->pos(dim))); - FILE_LOG(logDEBUG1)<<"desi: v["<<l->getID()<<"]=("<<l->pos(vpsc::HORIZONTAL) - <<","<<l->pos(vpsc::VERTICAL)<<")"; - } - } - vpsc::Variables vs; - vpsc::Constraints cs; - double stress; - setupVarsAndConstraints(n, ccs, dim, boundingBoxes, - clusterHierarchy, vs, cs, coords); - - if (topologyAddon->useTopologySolver()) - { - stress = topologyAddon->applyForcesAndConstraints(this, dim, g, vs, cs, - coords, des, oldStress); - } else { - // Add non-overlap constraints, but not variables again. - setupExtraConstraints(extraConstraints, dim, vs, cs, boundingBoxes); - // Projection. - SparseMap HMap(n); - computeForces(dim,HMap,g); - SparseMatrix H(HMap); - valarray<double> oldCoords=coords; - applyDescentVector(g,oldCoords,coords,oldStress,computeStepSize(H,g,g)); - setVariableDesiredPositions(vs,cs,des,coords); - project(vs,cs,coords); - valarray<double> d(n); - d=oldCoords-coords; - double stepsize=computeStepSize(H,g,d); - stepsize=max(0.,min(stepsize,1.)); - //printf(" dim=%d beta: ",dim); - stress = applyDescentVector(d,oldCoords,coords,oldStress,stepsize); - moveBoundingBoxes(); - } - updateCompoundConstraints(dim, ccs); - if(unsatisfiable.size()==2) { - checkUnsatisfiable(cs,unsatisfiable[dim]); - } - FILE_LOG(logDEBUG) << "ConstrainedFDLayout::applyForcesAndConstraints... done, stress="<<stress; - if (clusterHierarchy) - { - clusterHierarchy->computeVarRect(vs, dim); - clusterHierarchy->computeBoundingRect(boundingBoxes); - } - - for_each(vs.begin(),vs.end(),delete_object()); - for_each(cs.begin(),cs.end(),delete_object()); - return stress; -} -/* - * Attempts to set coords=oldCoords-stepsize*d. If this does not reduce - * the stress from oldStress then stepsize is halved. This is repeated - * until stepsize falls below a threshhold. - * @param d is a descent vector (a movement vector intended to reduce the - * stress) - * @param oldCoords are the previous position vector - * @param coords will hold the new position after applying d - * @param stepsize is a scalar multiple of the d to apply - */ -double ConstrainedFDLayout::applyDescentVector( - valarray<double> const &d, - valarray<double> const &oldCoords, - valarray<double> &coords, - const double oldStress, - double stepsize - ) -{ - COLA_UNUSED(oldStress); - - COLA_ASSERT(d.size()==oldCoords.size()); - COLA_ASSERT(d.size()==coords.size()); - while(fabs(stepsize)>0.00000000001) { - coords=oldCoords-stepsize*d; - double stress=computeStress(); - //printf(" applyDV: oldstress=%f, stress=%f, stepsize=%f\n", oldStress,stress,stepsize); - //if(oldStress>=stress) { - return stress; - //} - coords=oldCoords; - stepsize*=0.5; - } - return computeStress(); -} - - -// Computes X and Y offsets for nodes that are at the same position. -std::vector<double> ConstrainedFDLayout::offsetDir(double minD) -{ - std::vector<double> u(2); - double l = 0; - for (size_t i = 0; i < 2; ++i) - { - double x = u[i] = random.getNextBetween(0.01, 1) - 0.5; - l += x * x; - } - l = sqrt(l); - - for (size_t i = 0; i < 2; ++i) - { - u[i] *= (minD / l); - } - - return u; -} - - -/* - * Computes: - * - the matrix of second derivatives (the Hessian) H, used in - * calculating stepsize; and - * - the vector g, the negative gradient (steepest-descent) direction. - */ -void ConstrainedFDLayout::computeForces( - const vpsc::Dim dim, - SparseMap &H, - valarray<double> &g) { - if(n==1) return; - g=0; - // for each node: - for(unsigned u=0;u<n;u++) { - // Stress model - double Huu=0; - for(unsigned v=0;v<n;v++) { - if(u==v) continue; - - // The following loop randomly displaces nodes that are at identical positions - double rx=X[u]-X[v], ry=Y[u]-Y[v]; - double sd2 = rx*rx+ry*ry; - unsigned maxDisplaces = n; // avoid infinite loop in the case of numerical issues, such as huge values - - while (maxDisplaces--) - { - if ((sd2) > 1e-3) - { - break; - } - - std::vector<double> rd = offsetDir(minD); - X[v] += rd[0]; - Y[v] += rd[1]; - rx=X[u]-X[v], ry=Y[u]-Y[v]; - sd2 = rx*rx+ry*ry; - } - - unsigned short p = G[u][v]; - // no forces between disconnected parts of the graph - if(p==0) continue; - double l=sqrt(sd2); - double d=D[u][v]; - if(l>d && p>1) continue; // attractive forces not required - double d2=d*d; - /* force apart zero distances */ - if (l < 1e-30) { - l=0.1; - } - double dx=dim==vpsc::HORIZONTAL?rx:ry; - double dy=dim==vpsc::HORIZONTAL?ry:rx; - g[u]+=dx*(l-d)/(d2*l); - Huu-=H(u,v)=(d*dy*dy/(l*l*l)-1)/d2; - } - H(u,u)=Huu; - } - if(desiredPositions) { - for(DesiredPositions::const_iterator p=desiredPositions->begin(); - p!=desiredPositions->end();++p) { - unsigned i = p->id; - double d=(dim==vpsc::HORIZONTAL) - ?p->x-X[i]:p->y-Y[i]; - d*=p->weight; - g[i]-=d; - H(i,i)+=p->weight; - } - } -} -/* - * Returns the optimal step-size in the direction d, given gradient g and - * hessian H. - */ -double ConstrainedFDLayout::computeStepSize( - SparseMatrix const &H, - valarray<double> const &g, - valarray<double> const &d) const -{ - COLA_ASSERT(g.size()==d.size()); - COLA_ASSERT(g.size()==H.rowSize()); - // stepsize = g'd / (d' H d) - double numerator = dotProd(g,d); - valarray<double> Hd(d.size()); - H.rightMultiply(d,Hd); - double denominator = dotProd(d,Hd); - //COLA_ASSERT(numerator>=0); - //COLA_ASSERT(denominator>=0); - if(denominator==0) return 0; - return numerator/denominator; -} -/* - * Just computes the cost (Stress) at the current X,Y position - * used to test termination. - * This method will call preIteration if one is set. - */ -double ConstrainedFDLayout::computeStress() const { - FILE_LOG(logDEBUG)<<"ConstrainedFDLayout::computeStress()"; - double stress=0; - for(unsigned u=0;(u + 1)<n;u++) { - for(unsigned v=u+1;v<n;v++) { - unsigned short p=G[u][v]; - // no forces between disconnected parts of the graph - if(p==0) continue; - double rx=X[u]-X[v], ry=Y[u]-Y[v]; - double l=sqrt(rx*rx+ry*ry); - double d=D[u][v]; - if(l>d && p>1) continue; // no attractive forces required - double d2=d*d; - double rl=d-l; - double s=rl*rl/d2; - stress+=s; - FILE_LOG(logDEBUG2)<<"s("<<u<<","<<v<<")="<<s; - } - } - if(preIteration) { - if ((*preIteration)()) { - for(vector<Lock>::iterator l=preIteration->locks.begin(); - l!=preIteration->locks.end();l++) { - double dx=l->pos(vpsc::HORIZONTAL)-X[l->getID()], dy=l->pos(vpsc::VERTICAL)-Y[l->getID()]; - double s=10000*(dx*dx+dy*dy); - stress+=s; - FILE_LOG(logDEBUG2)<<"d("<<l->getID()<<")="<<s; - } - } - } - stress += topologyAddon->computeStress(); - if(desiredPositions) { - for(DesiredPositions::const_iterator p = desiredPositions->begin(); - p!=desiredPositions->end();++p) { - double dx = X[p->id] - p->x, dy = Y[p->id] - p->y; - stress+=0.5*p->weight*(dx*dx+dy*dy); - } - } - return stress; -} -void ConstrainedFDLayout::moveBoundingBoxes() { - for(unsigned i=0;i<n;i++) { - boundingBoxes[i]->moveCentre(X[i],Y[i]); - } -} - -static const double LIMIT = 100000000; - -static void reduceRange(double& val) -{ - val = std::min(val, LIMIT); - val = std::max(val, -LIMIT); -} - -void ConstrainedFDLayout::outputInstanceToSVG(std::string instanceName) -{ - std::string filename; - if (!instanceName.empty()) - { - filename = instanceName; - } - else - { - filename = "libcola-debug"; - } - filename += ".svg"; - FILE *fp = fopen(filename.c_str(), "w"); - - if (fp == NULL) - { - return; - } - - - double minX = LIMIT; - double minY = LIMIT; - double maxX = -LIMIT; - double maxY = -LIMIT; - - // Find the bounds of the diagram. - for (size_t i = 0; i < boundingBoxes.size(); ++i) - { - double rMinX = boundingBoxes[i]->getMinX(); - double rMaxX = boundingBoxes[i]->getMaxX(); - double rMinY = boundingBoxes[i]->getMinY(); - double rMaxY = boundingBoxes[i]->getMaxY(); - - reduceRange(rMinX); - reduceRange(rMaxX); - reduceRange(rMinY); - reduceRange(rMaxY); - - if (rMinX > -LIMIT) - { - minX = std::min(minX, rMinX); - } - if (rMaxX < LIMIT) - { - maxX = std::max(maxX,rMaxX); - } - if (rMinY > -LIMIT) - { - minY = std::min(minY, rMinY); - } - if (rMaxY < LIMIT) - { - maxY = std::max(maxY, rMaxY); - } - } - - minX -= 50; - minY -= 50; - maxX += 50; - maxY += 50; - - fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); - fprintf(fp, "<svg xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" xmlns=\"http://www.w3.org/2000/svg\" width=\"100%%\" height=\"100%%\" viewBox=\"%g %g %g %g\">\n", minX, minY, maxX - minX, maxY - minY); - - // Output source code to generate this ConstrainedFDLayout instance. - fprintf(fp, "<!-- Source code to generate this instance:\n"); - fprintf(fp, "#include <vector>\n"); - fprintf(fp, "#include <utility>\n"); - fprintf(fp, "#include \"libcola/cola.h\"\n"); - fprintf(fp, "using namespace cola;\n"); - fprintf(fp, "int main(void) {\n"); - fprintf(fp, " CompoundConstraints ccs;\n"); - fprintf(fp, " std::vector<Edge> es;\n"); - fprintf(fp, " EdgeLengths eLengths;\n"); - fprintf(fp, " double defaultEdgeLength=%g;\n", m_idealEdgeLength); - fprintf(fp, " std::vector<vpsc::Rectangle*> rs;\n"); - fprintf(fp, " vpsc::Rectangle *rect = NULL;\n\n"); - for (size_t i = 0; i < boundingBoxes.size(); ++i) - { - fprintf(fp, " rect = new vpsc::Rectangle(%g, %g, %g, %g);\n", - boundingBoxes[i]->getMinX(), boundingBoxes[i]->getMaxX(), - boundingBoxes[i]->getMinY(), boundingBoxes[i]->getMaxY()); - fprintf(fp, " rs.push_back(rect);\n\n"); - } - - for (size_t i = 0; i < n; ++i) - { - for (size_t j = i + 1; j < n; ++j) - { - if (G[i][j] == 1) - { - fprintf(fp, " es.push_back(std::make_pair(%lu, %lu));\n", i, j); - } - } - } - fprintf(fp, "\n"); - - if (m_edge_lengths.size() > 0) - { - fprintf(fp, " eLengths.resize(%d);\n", (int) m_edge_lengths.size()); - for (size_t i = 0; i < m_edge_lengths.size(); ++i) - { - fprintf(fp, " eLengths[%d] = %g;\n", (int) i, m_edge_lengths[i]); - } - fprintf(fp, "\n"); - } - - for (cola::CompoundConstraints::iterator c = ccs.begin(); - c != ccs.end(); ++c) - { - (*c)->printCreationCode(fp); - } - - fprintf(fp, " ConstrainedFDLayout alg(rs, es, defaultEdgeLength, eLengths);\n"); - if (clusterHierarchy) - { - clusterHierarchy->printCreationCode(fp); - fprintf(fp, " alg.setClusterHierarchy(cluster%llu);\n", - (unsigned long long) clusterHierarchy); - } - fprintf(fp, " alg.setConstraints(ccs);\n"); - fprintf(fp, " alg.setAvoidNodeOverlaps(%s);\n", - (m_generateNonOverlapConstraints) ? "true" : "false"); - fprintf(fp, " alg.makeFeasible();\n"); - fprintf(fp, " alg.run();\n"); - fprintf(fp, " alg.freeAssociatedObjects();\n"); - fprintf(fp, " return 0;\n"); - fprintf(fp, "};\n"); - fprintf(fp, "-->\n"); - - if (clusterHierarchy) - { - clusterHierarchy->computeBoundingRect(boundingBoxes); - fprintf(fp, "<g inkscape:groupmode=\"layer\" " - "inkscape:label=\"Clusters\">\n"); - clusterHierarchy->outputToSVG(fp); - fprintf(fp, "</g>\n"); - } - - fprintf(fp, "<g inkscape:groupmode=\"layer\" " - "inkscape:label=\"Rects\">\n"); - for (size_t i = 0; i < boundingBoxes.size(); ++i) - { - double minX = boundingBoxes[i]->getMinX(); - double maxX = boundingBoxes[i]->getMaxX(); - double minY = boundingBoxes[i]->getMinY(); - double maxY = boundingBoxes[i]->getMaxY(); - - fprintf(fp, "<rect id=\"rect-%u\" x=\"%g\" y=\"%g\" width=\"%g\" " - "height=\"%g\" style=\"stroke-width: 1px; stroke: black; " - "fill: blue; fill-opacity: 0.3;\" />\n", - (unsigned) i, minX, minY, maxX - minX, maxY - minY); - } - fprintf(fp, "</g>\n"); - - fprintf(fp, "<g inkscape:groupmode=\"layer\" " - "inkscape:label=\"Edges\">\n"); - for (size_t i = 0; i < n; ++i) - { - for (size_t j = i + 1; j < n; ++j) - { - if (G[i][j] == 1) - { - fprintf(fp, "<path d=\"M %g %g L %g %g\" " - "style=\"stroke-width: 1px; stroke: black;\" />\n", - boundingBoxes[i]->getCentreX(), - boundingBoxes[i]->getCentreY(), - boundingBoxes[j]->getCentreX(), - boundingBoxes[j]->getCentreY()); - } - } - } - fprintf(fp, "</g>\n"); - - fprintf(fp, "</svg>\n"); - fclose(fp); -} - - -} // namespace cola |
