6 * $Date: 2012-07-07 17:14:54 +0200 (Sa, 07. Jul 2012) $
7 ***************************************************************/
10 * \brief Implementation of the optimal third phase of the
11 * sugiyama algorithm for cluster graphs
13 * \author Carsten Gutwenger
16 * This file is part of the Open Graph Drawing Framework (OGDF).
20 * See README.txt in the root directory of the OGDF installation for details.
23 * This program is free software; you can redistribute it and/or
24 * modify it under the terms of the GNU General Public License
25 * Version 2 or 3 as published by the Free Software Foundation;
26 * see the file LICENSE.txt included in the packaging of this file
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
36 * You should have received a copy of the GNU General Public
37 * License along with this program; if not, write to the Free
38 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
39 * Boston, MA 02110-1301, USA.
41 * \see http://www.gnu.org/copyleft/gpl.html
42 ***************************************************************/
45 #include <ogdf/layered/OptimalHierarchyClusterLayout.h>
46 #include <ogdf/lpsolver/LPSolver.h>
47 #include <ogdf/basic/Array2D.h>
55 Array
<int> &matrixBegin
, // matrixBegin[i] = begin of column i
56 Array
<int> &matrixCount
, // matrixCount[i] = number of nonzeroes in column i
57 Array
<int> &matrixIndex
, // matrixIndex[n] = index of matrixValue[n] in its column
58 Array
<double> &matrixValue
, // matrixValue[n] = non-zero value in matrix
59 Array
<double> &rightHandSide
, // right-hand side of LP constraints
60 Array
<char> &equationSense
, // 'E' == 'G' >= 'L' <=
61 Array
<double> &lowerBound
, // lower bound of x[i]
62 Array
<double> &upperBound
, // upper bound of x[i]
63 Array
<double> &x
) // x-vector of optimal solution (if result is lpOptimal)
65 const double zeroeps
= 1.0E-7;
67 const int nCols
= matrixBegin
.size();
68 const int nRows
= rightHandSide
.size();
70 Array2D
<double> M(0,nCols
-1, 0,nRows
-1, 0.0);
73 for(i
= 0; i
< nCols
; ++i
)
75 for(int j
= 0; j
< matrixCount
[i
]; ++j
)
77 int k
= matrixBegin
[i
] + j
;
78 M(i
,matrixIndex
[k
]) = matrixValue
[k
];
83 for(i
= 0; i
< nRows
; ++i
)
86 for(int j
= 0; j
< nCols
; ++j
)
89 switch(equationSense
[i
]) {
91 if(fabs(val
- rightHandSide
[i
]) > zeroeps
)
95 if(!(val
+zeroeps
>= rightHandSide
[i
]))
99 if(!(val
-zeroeps
<= rightHandSide
[i
]))
111 //---------------------------------------------------------
113 //---------------------------------------------------------
114 OptimalHierarchyClusterLayout::OptimalHierarchyClusterLayout()
118 m_fixedLayerDistance
= false;
119 m_weightSegments
= 2.0;
120 m_weightBalancing
= 0.1;
121 m_weightClusters
= 0.05;
125 //---------------------------------------------------------
127 //---------------------------------------------------------
128 OptimalHierarchyClusterLayout::OptimalHierarchyClusterLayout(
129 const OptimalHierarchyClusterLayout
&ohl
)
131 m_nodeDistance
= ohl
.nodeDistance();
132 m_layerDistance
= ohl
.layerDistance();
133 m_fixedLayerDistance
= ohl
.fixedLayerDistance();
134 m_weightSegments
= ohl
.weightSegments();
135 m_weightBalancing
= ohl
.weightBalancing();
136 m_weightClusters
= ohl
.weightClusters();
140 //---------------------------------------------------------
141 // Assignment Operator
142 //---------------------------------------------------------
143 OptimalHierarchyClusterLayout
&OptimalHierarchyClusterLayout::operator=(
144 const OptimalHierarchyClusterLayout
&ohl
)
146 m_nodeDistance
= ohl
.nodeDistance();
147 m_layerDistance
= ohl
.layerDistance();
148 m_fixedLayerDistance
= ohl
.fixedLayerDistance();
149 m_weightSegments
= ohl
.weightSegments();
150 m_weightBalancing
= ohl
.weightBalancing();
151 m_weightClusters
= ohl
.weightClusters();
157 //---------------------------------------------------------
158 // Call for Cluster Graphs
159 //---------------------------------------------------------
160 void OptimalHierarchyClusterLayout::doCall(
161 const ExtendedNestingGraph
&H
,
162 ClusterGraphCopyAttributes
&ACGC
)
165 const int n
= H
.numberOfNodes();
168 return; // nothing to do
171 node v
= H
.firstNode();
180 // actual computation
181 computeXCoordinates(H
,ACGC
);
182 computeYCoordinates(H
,ACGC
);
186 //---------------------------------------------------------
187 // Compute x-coordinates (LP-based approach) (for cluster graphs)
188 //---------------------------------------------------------
189 void OptimalHierarchyClusterLayout::computeXCoordinates(
190 const ExtendedNestingGraph
& H
,
191 ClusterGraphCopyAttributes
&AGC
)
193 const ClusterGraphCopy
&CG
= H
.getClusterGraph();
195 const int k
= H
.numberOfLayers();
198 // preprocessing: determine nodes that are considered as virtual
203 for(i
= 0; i
< k
; ++i
)
207 // Process nodes on layer i from left to right
208 Stack
<const LHTreeNode
*> S
;
209 S
.push(H
.layerHierarchyTree(i
));
212 const LHTreeNode
*vNode
= S
.pop();
214 if(vNode
->isCompound()) {
215 for(int j
= vNode
->numberOfChildren()-1; j
>= 0; --j
)
216 S
.push(vNode
->child(j
));
219 node v
= vNode
->getNode();
221 if(H
.isLongEdgeDummy(v
) == true) {
222 m_isVirtual
[v
] = true;
224 edge e
= v
->firstAdj()->theEdge();
226 e
= v
->lastAdj()->theEdge();
227 node u
= e
->target();
229 if(H
.verticalSegment(e
) == false)
232 if(H
.isLongEdgeDummy(u
) == true) {
234 if(last
!= -1 && last
> down
) {
235 m_isVirtual
[v
] = false;
241 m_isVirtual
[v
] = false;
248 // determine variables of LP
250 int nSegments
= 0; // number of vertical segments
251 int nRealVertices
= 0; // number of real vertices
252 int nEdges
= 0; // number of edges not in vertical segments
253 int nBalanced
= 0; // number of real vertices with deg > 1 for which balancing constraints may be applied
255 m_vIndex
.init(H
,-1); // for real node: index of x[v] for dummy: index of corresponding segment
256 NodeArray
<int> bIndex(H
,-1); // (relative) index of b[v]
257 EdgeArray
<int> eIndex(H
,-1); // for edge not in vertical segment: its index
258 Array
<int> count(H
.numberOfEdges()); // counts the number of dummy vertices
259 // in corresponding segment that are not at
262 int nSpacingConstraints
= 0;
263 for(i
= 0; i
< k
; ++i
)
265 Stack
<const LHTreeNode
*> S
;
266 S
.push(H
.layerHierarchyTree(i
));
269 const LHTreeNode
*vNode
= S
.pop();
271 if(vNode
->isCompound()) {
272 cluster c
= vNode
->originalCluster();
274 if(H
.isVirtual(c
) == false)
275 nSpacingConstraints
+= (c
== CG
.rootCluster()) ? 1 : 2;
277 for(int j
= vNode
->numberOfChildren()-1; j
>= 0; --j
)
278 S
.push(vNode
->child(j
));
281 node v
= vNode
->getNode();
283 // ignore dummy nodes and nodes representing cluster
284 // (top or bottom) border
285 if(H
.type(v
) == ExtendedNestingGraph::ntClusterBottom
|| H
.type(v
) == ExtendedNestingGraph::ntClusterTop
)
288 ++nSpacingConstraints
;
290 // ignore dummy nodes
291 if(m_isVirtual
[v
] == true)
294 // we've found a real vertex
295 m_vIndex
[v
] = nRealVertices
++;
297 bIndex
[v
] = nBalanced
++;
299 // consider all outgoing edges
301 forall_adj_edges(e
,v
) {
302 node w
= e
->target();
306 // we've found an edge not belonging to a vetical segment
307 eIndex
[e
] = nEdges
++;
309 if(m_isVirtual
[w
] == false)
313 // we've found a vertical segment
314 count
[nSegments
] = 0;
316 m_vIndex
[w
] = nSegments
;
317 count
[nSegments
] += 2;
319 // next edge / dummy in segment
320 e
= e
->adjTarget()->cyclicSucc()->theEdge();
322 } while(m_isVirtual
[w
] && H
.verticalSegment(e
));
326 // edge following vertical segment
327 eIndex
[e
] = nEdges
++;
329 } while(m_isVirtual
[w
]);
335 // Assign indices to clusters
337 m_cIndex
.init(CG
,-1);
340 forall_clusters(c
,CG
)
341 if(H
.isVirtual(c
) == false)
342 m_cIndex
[c
] = nClusters
++;
345 // assignment of variables to matrix columns
346 // d_e 0, ..., nEdges-1
347 // x_v vertexOffset, ..., vertexOffset + nRealVertices-1
348 // x_s segmentOffset, ..., segmentOffset + nSegments-1
349 // b_v balancedOffset, ..., balancedOffset + nBalanced-1
350 // l_c clusterLefOffset, ..., clusterLeftOffset + nClusters-1
351 // r_c clusterRightOffset, ..., clusterRightOffset+ nClusters-1
354 if(m_weightBalancing
<= 0.0)
355 nBalanced
= 0; // no balancing
357 const int nCols
= nEdges
+ nRealVertices
+ nSegments
+ nBalanced
+ 2*nClusters
;
358 const int nRows
= 2*nEdges
+ nSpacingConstraints
+ 2*nBalanced
;
360 m_vertexOffset
= nEdges
;
361 m_segmentOffset
= nEdges
+ nRealVertices
;
362 const int balancedOffset
= m_segmentOffset
+ nSegments
;
363 m_clusterLeftOffset
= balancedOffset
+ nBalanced
;
364 m_clusterRightOffset
= m_clusterLeftOffset
+ nClusters
;
366 // allocation of matrix
367 Array
<int> matrixCount(0,nCols
-1,0);
369 for(i
= 0; i
< nEdges
; ++i
) {
373 for(int jj
= 0; jj
< k
; ++jj
) {
374 const LHTreeNode
*layerRoot
= H
.layerHierarchyTree(jj
);
376 Stack
<const LHTreeNode
*> S
;
380 const LHTreeNode
*vNode
= S
.pop();
382 if(vNode
->isCompound()) {
383 i
= m_cIndex
[vNode
->originalCluster()];
386 int count
= (vNode
== layerRoot
) ? 1 : 2;
388 matrixCount
[m_clusterLeftOffset
+ i
] += count
;
389 matrixCount
[m_clusterRightOffset
+ i
] += count
;
392 for(int j
= vNode
->numberOfChildren()-1; j
>= 0; --j
)
393 S
.push(vNode
->child(j
));
396 node v
= vNode
->getNode();
398 // ignore nodes representing cluster (top or bottom) border
399 if(H
.type(v
) == ExtendedNestingGraph::ntClusterBottom
||
400 H
.type(v
) == ExtendedNestingGraph::ntClusterTop
)
403 if(m_isVirtual
[v
] == false) {
404 i
= m_vertexOffset
+ m_vIndex
[v
];
406 int count
= 2 + 2*v
->degree();
412 node w
= adj
->twinNode();
418 matrixCount
[i
] = count
;
420 } else if (nBalanced
> 0) {
424 node w
= adj
->twinNode();
433 for(i
= 0; i
< nSegments
; ++i
) {
434 matrixCount
[m_segmentOffset
+i
] = count
[i
] + 4;
437 for(i
= 0; i
< nBalanced
; ++i
) {
438 matrixCount
[balancedOffset
+i
] = 2;
442 // Computation of matrixBegin[i] from given matrixCount values
443 Array
<int> matrixBegin(nCols
);
446 for(i
= 0; i
< nCols
; ++i
) {
447 matrixBegin
[i
] = nNonZeroes
;
448 nNonZeroes
+= matrixCount
[i
];
452 int debugNonZeroCount
= 0; // for debugging only
457 Array
<int> matrixIndex(nNonZeroes
);
458 Array
<double> matrixValue(nNonZeroes
);
459 Array
<char> equationSense(nRows
);
460 Array
<double> rightHandSide(nRows
);
463 Array
<int> currentCol(nCols
);
464 for(i
= 0; i
< nCols
; ++i
)
465 currentCol
[i
] = matrixBegin
[i
];
468 // d_(u,v) - x_u + x_v >= 0
469 // d_(u,v) + x_u - x_v >= 0
473 int dCol
= eIndex
[e
];
475 node u
= e
->source();
476 int uCol
= m_vIndex
[u
];
477 uCol
+= (m_isVirtual
[u
]) ? m_segmentOffset
: m_vertexOffset
;
479 node v
= e
->target();
480 int vCol
= m_vIndex
[v
];
481 vCol
+= (m_isVirtual
[v
]) ? m_segmentOffset
: m_vertexOffset
;
483 // d_(u,v) - x_u + x_v >= 0
485 matrixValue
[currentCol
[dCol
]] = 1.0;
486 matrixIndex
[currentCol
[dCol
]] = currentRow
;
490 matrixValue
[currentCol
[uCol
]] = -1.0;
491 matrixIndex
[currentCol
[uCol
]] = currentRow
;
495 matrixValue
[currentCol
[vCol
]] = 1.0;
496 matrixIndex
[currentCol
[vCol
]] = currentRow
;
500 equationSense
[currentRow
] = 'G';
501 rightHandSide
[currentRow
] = 0.0;
505 // d_(u,v) + x_u - x_v >= 0
507 matrixValue
[currentCol
[dCol
]] = 1.0;
508 matrixIndex
[currentCol
[dCol
]] = currentRow
;
512 matrixValue
[currentCol
[uCol
]] = 1.0;
513 matrixIndex
[currentCol
[uCol
]] = currentRow
;
517 matrixValue
[currentCol
[vCol
]] = -1.0;
518 matrixIndex
[currentCol
[vCol
]] = currentRow
;
522 equationSense
[currentRow
] = 'G';
523 rightHandSide
[currentRow
] = 0.0;
531 // x[v_i] - x[v_(i-1)] >= nodeDistance + 0.5*(width(v_i)+width(v_(i-1))
532 for(i
= 0; i
< k
; ++i
)
534 List
<Tuple2
<int,double> > L
;
535 buildLayerList(H
.layerHierarchyTree(i
), L
);
540 ListConstIterator
<Tuple2
<int,double> > it1
= L
.begin();
541 for(ListConstIterator
<Tuple2
<int,double> > it2
= it1
.succ();
542 it2
.valid(); it1
= it2
, ++it2
)
544 int uCol
= (*it1
).x1();
545 double uWidth
= (*it1
).x2();
547 int vCol
= (*it2
).x1();
548 double vWidth
= (*it2
).x2();
550 // x_v - x_u >= nodeDistance + 0.5*(width(v)+width(u))
551 matrixValue
[currentCol
[uCol
]] = -1.0;
552 matrixIndex
[currentCol
[uCol
]] = currentRow
;
556 matrixValue
[currentCol
[vCol
]] = 1.0;
557 matrixIndex
[currentCol
[vCol
]] = currentRow
;
561 equationSense
[currentRow
] = 'G';
562 rightHandSide
[currentRow
] =
563 m_nodeDistance
+ 0.5*(uWidth
+ vWidth
);
570 // b[v] - x[v] + 1/deg(v) * sum_{u in Adj(v)} x[u] >= 0
571 // b[v] + x[v] - 1/deg(v) * sum_{u in Adj(v)} x[u] >= 0
576 int bCol
= bIndex
[v
];
579 bCol
+= balancedOffset
;
581 int vCol
= m_vertexOffset
+m_vIndex
[v
];
583 // b[v] - x[v] + 1/deg(v) * sum_{u in Adj(v)} x[u] >= 0
584 matrixValue
[currentCol
[bCol
]] = 1.0;
585 matrixIndex
[currentCol
[bCol
]] = currentRow
;
589 matrixValue
[currentCol
[vCol
]] = -1.0;
590 matrixIndex
[currentCol
[vCol
]] = currentRow
;
594 double f
= 1.0 / v
->degree();
597 node u
= adj
->twinNode();
598 int uCol
= m_vIndex
[u
];
599 uCol
+= (m_isVirtual
[u
]) ? m_segmentOffset
: m_vertexOffset
;
601 matrixValue
[currentCol
[uCol
]] = f
;
602 matrixIndex
[currentCol
[uCol
]] = currentRow
;
607 equationSense
[currentRow
] = 'G';
608 rightHandSide
[currentRow
] = 0.0;
612 // b[v] + x[v] - 1/deg(v) * sum_{u in Adj(v)} x[u] >= 0
613 matrixValue
[currentCol
[bCol
]] = 1.0;
614 matrixIndex
[currentCol
[bCol
]] = currentRow
;
618 matrixValue
[currentCol
[vCol
]] = 1.0;
619 matrixIndex
[currentCol
[vCol
]] = currentRow
;
623 f
= -1.0 / v
->degree();
625 node u
= adj
->twinNode();
626 int uCol
= m_vIndex
[u
];
627 uCol
+= (m_isVirtual
[u
]) ? m_segmentOffset
: m_vertexOffset
;
629 matrixValue
[currentCol
[uCol
]] = f
;
630 matrixIndex
[currentCol
[uCol
]] = currentRow
;
635 equationSense
[currentRow
] = 'G';
636 rightHandSide
[currentRow
] = 0.0;
642 OGDF_ASSERT(nNonZeroes
== debugNonZeroCount
);
644 // lower and upper bounds
645 Array
<double> lowerBound(nCols
);
646 Array
<double> upperBound(nCols
);
648 for(i
= 0; i
< nCols
; ++i
) {
650 upperBound
[i
] = solver
.infinity();
654 // objective function
655 Array
<double> obj(nCols
);
659 // edge segments connecting to a vertical segment
660 // (i.e. the original edge is represented by at least
661 // three edges in H) get a special weight; all others
663 int sz
= H
.chain(H
.origEdge(e
)).size();
665 node uOrig
= H
.origNode(e
->source());
666 node vOrig
= H
.origNode(e
->target());
667 if((uOrig
&& uOrig
->outdeg() == 1) || (vOrig
&& vOrig
->indeg() == 1))
668 obj
[i
] = 1.2*m_weightSegments
;
670 obj
[i
] = (sz
>= 3) ? m_weightSegments
: 1.0;
674 if(m_isVirtual
[e
->source()] == false && e
->source()->degree() == 1)
675 obj
[i
] += m_weightBalancing
;
676 if(m_isVirtual
[e
->target()] == false && e
->target()->degree() == 1)
677 obj
[i
] += m_weightBalancing
;
681 for(i
= nEdges
; i
< balancedOffset
; ++i
)
682 obj
[i
] = 0.0; // all x_v and x_s do not contribute
684 for(; i
< m_clusterLeftOffset
; ++i
)
685 obj
[i
] = m_weightBalancing
;
687 for(; i
< m_clusterRightOffset
; ++i
)
688 obj
[i
] = -m_weightClusters
;
690 for(; i
< nCols
; ++i
)
691 obj
[i
] = +m_weightClusters
;
695 Array
<double> x(nCols
);
697 LPSolver::Status status
=
698 solver
.optimize(LPSolver::lpMinimize
, obj
,
699 matrixBegin
, matrixCount
, matrixIndex
, matrixValue
,
700 rightHandSide
, equationSense
,
701 lowerBound
, upperBound
,
704 OGDF_ASSERT(status
== LPSolver::lpOptimal
);
705 int checkResult
= checkSolution(matrixBegin
, matrixCount
, matrixIndex
, matrixValue
,
706 rightHandSide
, equationSense
, lowerBound
, upperBound
, x
);
707 OGDF_ASSERT(checkResult
== -1);
709 // assign x coordinates
713 ExtendedNestingGraph::NodeType t
= H
.type(v
);
714 if(t
== ExtendedNestingGraph::ntNode
|| t
== ExtendedNestingGraph::ntDummy
)
717 AGC
.x(v
) = x
[m_segmentOffset
+ m_vIndex
[v
]];
719 AGC
.x(v
) = x
[m_vertexOffset
+ m_vIndex
[v
]];
723 forall_clusters(c
,CG
.getOriginalClusterGraph())
725 int i
= m_cIndex
[CG
.copy(c
)];
728 AGC
.setClusterLeftRight(c
,
729 x
[m_clusterLeftOffset
+i
], x
[m_clusterRightOffset
+i
]);
740 void OptimalHierarchyClusterLayout::buildLayerList(
741 const LHTreeNode
*vNode
,
742 List
<Tuple2
<int,double> > &L
)
744 if(vNode
->isCompound())
746 int i
= m_cIndex
[vNode
->originalCluster()];
749 L
.pushBack(Tuple2
<int,double>(m_clusterLeftOffset
+ i
, 0));
751 for(int j
= 0; j
< vNode
->numberOfChildren(); ++j
)
752 buildLayerList(vNode
->child(j
), L
);
755 L
.pushBack(Tuple2
<int,double>(m_clusterRightOffset
+ i
, 0));
758 node v
= vNode
->getNode();
759 ExtendedNestingGraph::NodeType t
= m_pH
->type(v
);
761 if(t
!= ExtendedNestingGraph::ntClusterBottom
&&
762 t
!= ExtendedNestingGraph::ntClusterTop
)
765 i
+= (m_isVirtual
[v
]) ? m_segmentOffset
: m_vertexOffset
;
767 L
.pushBack(Tuple2
<int,double>(i
, m_pACGC
->getWidth(v
)));
773 //---------------------------------------------------------
774 // Compute y-coordinates for cluster graphs
775 //---------------------------------------------------------
776 void OptimalHierarchyClusterLayout::computeYCoordinates(
777 const ExtendedNestingGraph
& H
,
778 ClusterGraphCopyAttributes
&AGC
)
780 const int k
= H
.numberOfLayers();
784 double prevHeight
= 0;
785 for(int i
= 0; i
< k
; ++i
)
787 // Compute height of layer i and dy (if required)
788 double height
= 0; // height[i]
789 double dy
= m_layerDistance
; // dy[i]
791 Stack
<const LHTreeNode
*> S
;
792 S
.push(H
.layerHierarchyTree(i
));
795 const LHTreeNode
*vNode
= S
.pop();
797 if(vNode
->isCompound()) {
798 for(int j
= vNode
->numberOfChildren()-1; j
>= 0; --j
)
799 S
.push(vNode
->child(j
));
802 node v
= vNode
->getNode();
804 if(H
.type(v
) == ExtendedNestingGraph::ntNode
) {
805 double h
= AGC
.getHeight(v
);
810 if(m_fixedLayerDistance
== false) {
812 forall_adj_edges(e
,v
) {
813 node w
= e
->source();
815 double dwv
= fabs(AGC
.x(w
) - AGC
.x(v
)) / 3.0;
824 if(dy
> 10*m_layerDistance
)
825 dy
= 10*m_layerDistance
;
827 y
[i
] = (i
> 0) ? y
[i
-1] + dy
+0.5*(height
+prevHeight
) : 0.5*height
;
832 // set y-ccordinates of nodes
835 ExtendedNestingGraph::NodeType t
= H
.type(v
);
836 if(t
== ExtendedNestingGraph::ntNode
|| t
== ExtendedNestingGraph::ntDummy
)
838 AGC
.y(v
) = y
[H
.rank(v
)];
842 // set y-coordinates of clusters
843 const ClusterGraph
&CG
= H
.getOriginalClusterGraph();
845 const double yUnit
= m_layerDistance
;
848 forall_postOrderClusters(c
,CG
)
850 if(c
== CG
.rootCluster())
853 double contentMin
= DBL_MAX
;
854 double contentMax
= DBL_MIN
;
856 ListConstIterator
<node
> itV
;
857 for(itV
= c
->nBegin(); itV
.valid(); ++itV
) {
858 node v
= H
.copy(*itV
);
859 double t
= AGC
.y(v
) - 0.5*AGC
.getHeight(v
);
860 double b
= AGC
.y(v
) + 0.5*AGC
.getHeight(v
);
863 if(t
< contentMin
) contentMin
= t
;
864 if(b
> contentMax
) contentMax
= b
;
867 ListConstIterator
<cluster
> itC
;
868 for(itC
= c
->cBegin(); itC
.valid(); ++itC
) {
869 double t
= AGC
.top(*itC
);
870 double b
= AGC
.bottom(*itC
);
873 if(t
< contentMin
) contentMin
= t
;
874 if(b
> contentMax
) contentMax
= b
;
877 double currentTop
= y
[H
.rank(H
.top(c
))];
878 double currentBottom
= y
[H
.rank(H
.bottom(c
))];
880 if(contentMin
!= DBL_MAX
)
882 int kt
= int(((contentMin
-m_layerDistance
) - currentTop
) / yUnit
);
884 currentTop
+= kt
*yUnit
;
886 int kb
= int((currentBottom
- (contentMax
+m_layerDistance
)) / yUnit
);
888 currentBottom
-= kb
*yUnit
;
891 AGC
.setClusterTopBottom(c
, currentTop
, currentBottom
);