This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 178705
Collapse All | Expand All

(-)a/api.visual/src/org/netbeans/modules/visual/graph/layout/TreeGraphLayout.java (-94 / +408 lines)
Lines 40-196 Link Here
40
 */
40
 */
41
package org.netbeans.modules.visual.graph.layout;
41
package org.netbeans.modules.visual.graph.layout;
42
42
43
import java.awt.Point;
44
import java.awt.Rectangle;
43
import org.netbeans.api.visual.graph.layout.GraphLayout;
45
import org.netbeans.api.visual.graph.layout.GraphLayout;
44
import org.netbeans.api.visual.graph.layout.UniversalGraph;
46
import org.netbeans.api.visual.graph.layout.UniversalGraph;
45
import org.netbeans.api.visual.widget.Widget;
47
import org.netbeans.api.visual.widget.Widget;
46
48
47
import java.awt.*;
49
import java.util.ArrayList;
48
import java.util.*;
50
import java.util.Collection;
51
import java.util.HashMap;
52
import java.util.HashSet;
53
import java.util.List;
54
import java.util.Map;
49
55
50
/**
56
/**
51
 * @author David Kaspar
57
 * @author David Kaspar
52
 */
58
 */
53
public final class TreeGraphLayout<N,E> extends GraphLayout<N,E> {
59
public final class TreeGraphLayout<N, E> extends GraphLayout<N, E> {
54
60
55
    private int originX;
61
    private int originX;
56
    private int originY;
62
    private int originY;
57
    private int verticalGap;
63
    private int verticalGap;
58
    private int horizontalGap;
64
    private int horizontalGap;
59
    private boolean vertical;
65
    private boolean vertical;
66
    private boolean minimizeGap;
67
    private N rootNode;
68
    private Alignment alignment;
60
69
61
    private N rootNode;
70
    public TreeGraphLayout(int originX, int originY, int verticalGap,
62
71
            int horizontalGap, boolean vertical, boolean minimizeGap) {
63
    public TreeGraphLayout (int originX, int originY, int verticalGap, int horizontalGap, boolean vertical) {
72
        if (vertical) {
64
        this.originX = originX;
73
            this.originX = originX;
65
        this.originY = originY;
74
            this.originY = originY;
66
        this.verticalGap = verticalGap;
75
            this.verticalGap = verticalGap;
67
        this.horizontalGap = horizontalGap;
76
            this.horizontalGap = horizontalGap;
77
        } else {
78
            this.originX = originY;
79
            this.originY = originX;
80
            this.verticalGap = horizontalGap;
81
            this.horizontalGap = verticalGap;
82
        }
68
        this.vertical = vertical;
83
        this.vertical = vertical;
84
        this.minimizeGap = minimizeGap;
85
        this.alignment = Alignment.TOP;
69
    }
86
    }
70
87
71
    public void setRootNode (N rootNode) {
88
    public TreeGraphLayout(int originX, int originY, int verticalGap,
89
            int horizontalGap, boolean vertical, boolean minimizeGap,
90
            Alignment alignment) {
91
        this(originX, originY, verticalGap, horizontalGap, vertical, minimizeGap);
92
        this.alignment = alignment;
93
    }
94
95
    public void setRootNode(N rootNode) {
72
        this.rootNode = rootNode;
96
        this.rootNode = rootNode;
73
    }
97
    }
74
98
75
    public void setProperties (int originX, int originY, int verticalGap, int horizontalGap, boolean vertical) {
99
    public void setProperties(int originX, int originY, int verticalGap,
76
        this.originX = originX;
100
            int horizontalGap, boolean vertical, boolean minimizeGap) {
77
        this.originY = originY;
101
        if (vertical) {
78
        this.verticalGap = verticalGap;
102
            this.originX = originX;
79
        this.horizontalGap = horizontalGap;
103
            this.originY = originY;
104
            this.verticalGap = verticalGap;
105
            this.horizontalGap = horizontalGap;
106
        } else {
107
            this.originX = originY;
108
            this.originY = originX;
109
            this.verticalGap = horizontalGap;
110
            this.horizontalGap = verticalGap;
111
        }
80
        this.vertical = vertical;
112
        this.vertical = vertical;
113
        this.minimizeGap = minimizeGap;
81
    }
114
    }
82
115
83
    protected void performGraphLayout (UniversalGraph<N, E> graph) {
116
    public void setProperties(int originX, int originY, int verticalGap,
84
        if (rootNode == null)
117
            int horizontalGap, boolean vertical, boolean minimizeGap,
118
            Alignment alignment) {
119
        this.setProperties(originX, originY, verticalGap, horizontalGap, vertical, minimizeGap);
120
        this.alignment = alignment;
121
    }
122
123
    protected void performGraphLayout(UniversalGraph<N, E> graph) {
124
        if (rootNode == null) {
85
            return;
125
            return;
86
        Collection<N> allNodes = graph.getNodes ();
126
        }
87
        if (! allNodes.contains (rootNode))
127
        Collection<N> allNodes = graph.getNodes();
128
        if (!allNodes.contains(rootNode)) {
88
            return;
129
            return;
89
        ArrayList<N> nodesToResolve = new ArrayList<N> (allNodes);
130
        }
131
        ArrayList<N> nodesToResolve = new ArrayList<N>(allNodes);
90
132
91
        HashSet<N> loadedSet = new HashSet<N> ();
133
        HashSet<N> loadedSet = new HashSet<N>();
92
        Node root = new Node (graph, rootNode, loadedSet);
134
        Node root = new Node(graph, rootNode, loadedSet);
93
        nodesToResolve.removeAll (loadedSet);
135
        nodesToResolve.removeAll(loadedSet);
94
        if (vertical) {
136
95
            root.allocateHorizontally (graph);
137
        Map<Integer, Integer> map = root.getMaxSpaceForEveryLevel(graph);
96
            root.resolveVertically (originX, originY);
138
97
        } else {
139
        List<Node.LeftRight> envelope = root.layout(originX, originY, map, 0);
98
            root.allocateVertically (graph);
140
99
            root.resolveHorizontally (originX, originY);
141
        // correction of originX, if needed
142
        int moveDistance = Integer.MIN_VALUE;
143
        // get the most left position and determine the distance to move
144
        for (Node.LeftRight leftRight : envelope) {
145
            if (leftRight.getLeft() < originX && originX - leftRight.getLeft() > moveDistance) {
146
                moveDistance = originX - leftRight.getLeft();
147
            }
100
        }
148
        }
101
149
102
        final HashMap<N, Point> resultPosition = new HashMap<N, Point> ();
150
        if (moveDistance == Integer.MIN_VALUE) {
103
        root.upload (resultPosition);
151
            moveDistance = 0;
152
        }
153
154
        if (!vertical) {
155
            root.invert(moveDistance);
156
        } else {
157
            root.relativeBoundsCorrectionX(moveDistance);
158
        }
159
160
        final HashMap<N, Point> resultPosition = new HashMap<N, Point>();
161
        root.upload(resultPosition);
104
162
105
        for (N node : nodesToResolve) {
163
        for (N node : nodesToResolve) {
106
            Point position = new Point ();
164
            Point position = new Point();
107
            // TODO - resolve others
165
            // TODO - resolve others
108
            resultPosition.put (node, position);
166
            resultPosition.put(node, position);
109
        }
167
        }
110
168
111
        for (Map.Entry<N, Point> entry : resultPosition.entrySet ())
169
        for (Map.Entry<N, Point> entry : resultPosition.entrySet()) {
112
            setResolvedNodeLocation (graph, entry.getKey (), entry.getValue ());
170
            setResolvedNodeLocation(graph, entry.getKey(), entry.getValue());
171
        }
113
    }
172
    }
114
173
115
    protected void performNodesLayout (UniversalGraph<N, E> universalGraph, Collection<N> nodes) {
174
    protected void performNodesLayout(UniversalGraph<N, E> universalGraph,
116
        throw new UnsupportedOperationException (); // TODO
175
            Collection<N> nodes) {
176
        throw new UnsupportedOperationException(); // TODO
177
    }
178
179
    public enum Alignment {
180
        TOP, CENTER, BOTTOM
117
    }
181
    }
118
182
119
    private class Node {
183
    private class Node {
120
184
121
        private N node;
185
        private N node;
122
        private ArrayList<Node> children;
186
        private ArrayList<Node> children;
187
        private Rectangle relativeBounds;
188
        private Point point;
189
        private int space;
123
190
124
        private Rectangle relativeBounds;
191
        private Node(UniversalGraph<N, E> graph, N node, HashSet<N> loadedSet) {
125
        private int space;
192
            this.node = node;
126
        private int totalSpace;
193
            loadedSet.add(node);
127
        private Point point;
128
194
129
        private Node (UniversalGraph<N, E> graph, N node, HashSet<N> loadedSet) {
195
            children = new ArrayList<Node>();
130
            this.node = node;
196
            for (E edge : graph.findNodeEdges(node, true, false)) {
131
            loadedSet.add (node);
197
                N child = graph.getEdgeTarget(edge);
132
198
                if (child != null && !loadedSet.contains(child)) {
133
            children = new ArrayList<Node> ();
199
                    children.add(new Node(graph, child, loadedSet));
134
            for (E edge: graph.findNodeEdges (node, true, false)) {
200
                }
135
                N child = graph.getEdgeTarget (edge);
136
                if (child != null  &&  ! loadedSet.contains (child))
137
                    children.add (new Node (graph, child, loadedSet));
138
            }
201
            }
139
        }
202
        }
140
203
141
        private int allocateHorizontally (UniversalGraph<N, E> graph) {
204
        /**
142
            Widget widget = graph.getScene ().findWidget (node);
205
         * This class represents an element of the envelope of a (sub)tree.
143
            widget.getLayout ().layout (widget);
206
         */
144
            relativeBounds = widget.getPreferredBounds ();
207
        private class LeftRight {
145
            space = 0;
208
146
            for (int i = 0; i < children.size (); i++) {
209
            /** the most left coordinate of this element of the envelope. */
147
                if (i > 0)
210
            private int left;
148
                    space += horizontalGap;
211
            /** hte most right coordinate of this element of the envelope. */
149
                space += children.get (i).allocateHorizontally (graph);
212
            private int right;
213
214
            /**
215
             * Constructor
216
             *
217
             * @param left the most left coordinate of this {@link LeftRight}.
218
             * @param right the most right coordinate of this {@link LeftRight}.
219
             */
220
            public LeftRight(int left, int right) {
221
                this.left = left;
222
                this.right = right;
150
            }
223
            }
151
            totalSpace = Math.max (space, relativeBounds.width);
152
            return totalSpace;
153
        }
154
224
155
        private void resolveVertically (int x, int y) {
225
            /**
156
            point = new Point (x + totalSpace / 2, y - relativeBounds.y);
226
             * Getter of the most left coordinate of this {@link LeftRight}.
157
            x += (totalSpace - space) / 2;
227
             * @return the most left coordinate of this {@link LeftRight}.
158
            y += relativeBounds.height + verticalGap;
228
             */
159
            for (Node child : children) {
229
            public int getLeft() {
160
                child.resolveVertically (x, y);
230
                return left;
161
                x += child.totalSpace + horizontalGap;
231
            }
232
233
            /**
234
             * Getter of the most right coordinate of this {@link LeftRight}.
235
             * @return the most right coordinate of this {@link LeftRight}.
236
             */
237
            public int getRight() {
238
                return right;
239
            }
240
241
            /**
242
             * Setter of the most left coordinate of this {@link LeftRight}.
243
             * @param left the most left coordinate to set.
244
             */
245
            public void setLeft(int left) {
246
                this.left = left;
247
            }
248
249
            /**
250
             * Setter of the most right coordinate of this {@link LeftRight}.
251
             * @param left the most right coordinate to set.
252
             */
253
            public void setRight(int right) {
254
                this.right = right;
162
            }
255
            }
163
        }
256
        }
164
257
165
        private int allocateVertically (UniversalGraph<N, E> graph) {
258
        private Map<Integer, Integer> getMaxSpaceForEveryLevel(UniversalGraph<N, E> graph) {
166
            Widget widget = graph.getScene ().findWidget (node);
259
            Map<Integer, Integer> map = new HashMap<Integer, Integer>();
167
            widget.getLayout ().layout (widget);
260
            calculateMaxSpace(graph, map, 0);
168
            relativeBounds = widget.getPreferredBounds ();
261
            return map;
169
            space = 0;
170
            for (int i = 0; i < children.size (); i++) {
171
                if (i > 0)
172
                    space += verticalGap;
173
                space += children.get (i).allocateVertically (graph);
174
            }
175
            totalSpace = Math.max (space, relativeBounds.height);
176
            return totalSpace;
177
        }
262
        }
178
263
179
        private void resolveHorizontally (int x, int y) {
264
        /**
180
            point = new Point (x - relativeBounds.x, y + totalSpace / 2);
265
         * Method to determine the maximal space (horizontaly or verticaly) of each level in the tree.
181
            x += relativeBounds.width + horizontalGap;
266
         * @param map
182
            y += (totalSpace - space) / 2;
267
         * @param lvl
183
            for (Node child : children) {
268
         * @return
184
                child.resolveHorizontally (x, y);
269
         */
185
                y += child.totalSpace + verticalGap;
270
        private Map<Integer, Integer> calculateMaxSpace(UniversalGraph<N, E> graph, Map<Integer, Integer> map, int lvl) {
271
272
            Widget widget = graph.getScene().findWidget(node);
273
            widget.getLayout().layout(widget);
274
            relativeBounds = widget.getPreferredBounds();
275
276
            if (vertical) {
277
                space = relativeBounds.height;
278
            } else {
279
                space = relativeBounds.width;
280
            }
281
282
            if (map.get(lvl) != null) {
283
                // lvl is in list, but height is greater than the old one.
284
                if (map.get(lvl) < space) {
285
                    map.put(lvl, space);
286
                }
287
            } else {
288
                // lvl isn't in the map right now.
289
                map.put(lvl, space);
290
            }
291
292
            lvl++;
293
294
            // do iteration over all children of the current node and calculate
295
            // the maxSpace
296
            for (Node n : children) {
297
                n.calculateMaxSpace(graph, map, lvl);
298
            }
299
            return map;
300
        }
301
302
        /**
303
         *
304
         * @param graph
305
         * @param x
306
         * @param y
307
         * @return
308
         */
309
        private List<LeftRight> layout(int x, int y, Map<Integer, Integer> map, int lvl) {
310
311
            List<LeftRight> leftright = null;
312
313
            // if this node is a leaf (has no childs) ==> place the node at x,y and x and x + width to the envelope.
314
            if (children.size() == 0) {
315
                leftright = new ArrayList<LeftRight>();
316
317
                // do the horizontal alignment
318
                y = doHorizontalPlacementInLevel(y, map, lvl);
319
320
                if (vertical) {
321
                    y = y - relativeBounds.y;
322
                    leftright.add(new LeftRight(x, x + relativeBounds.width));
323
                } else {
324
                    y = y - relativeBounds.x;
325
                    leftright.add(new LeftRight(x, x + relativeBounds.height));
326
                }
327
328
                point = new Point(x, y);
329
                return leftright;
330
            }
331
332
            lvl++;
333
334
            for (int i = 0; i < children.size(); i++) {
335
                if (i == 0) {
336
                    leftright = children.get(i).layout(x, (int) (y + map.get(lvl - 1) + verticalGap), map, lvl);
337
                } else {
338
                    List<LeftRight> secound = children.get(i).layout(x, (int) (y + map.get(lvl - 1) + verticalGap), map, lvl);
339
340
                    int leftlength = leftright.size();
341
                    int rightlength = secound.size();
342
343
                    int diff = rightlength - leftlength;
344
345
                    // Variable used for calculation of the distance to move the right subtree.
346
                    int moveDist = Integer.MIN_VALUE;
347
348
                    // Try to minimize the gap and move subtrees as close as possible to each other.
349
                    if (minimizeGap) {
350
                        // Caluculate the max distance to move
351
                        for (int k = leftlength - 1; k >= 0; k--) {
352
                            if (k + diff >= 0 && k >= 0) {
353
                                int tmpmaxoverlap = leftright.get(k).right - secound.get(k + diff).left;
354
                                if (tmpmaxoverlap > moveDist) {
355
                                    moveDist = tmpmaxoverlap;
356
                                }
357
                            }
358
                        }
359
                    } else {
360
                        int maxRight = Integer.MIN_VALUE;
361
362
                        for (LeftRight l : leftright) {
363
                            maxRight = Math.max(maxRight, l.right);
364
                        }
365
366
                        int minLeft = Integer.MAX_VALUE;
367
368
                        for (LeftRight s : secound) {
369
                            minLeft = Math.min(minLeft, s.left);
370
                        }
371
                        moveDist = maxRight - minLeft;
372
                    }
373
374
                    if (moveDist > Integer.MIN_VALUE) {
375
                        int dx = moveDist + horizontalGap;
376
                        children.get(i).point.x += dx;
377
                        children.get(i).moveChildrenHorizontally(dx);
378
                    }
379
380
                    // update moved tree envelope
381
                    for (LeftRight lr : secound) {
382
                        lr.setLeft(lr.getLeft() + moveDist + horizontalGap);
383
                        lr.setRight(lr.getRight() + moveDist + horizontalGap);
384
                    }
385
386
                    // store the overall envelope of leftright and secound in leftright
387
                    for (int j = rightlength - 1; j >= 0; j--) {
388
                        // leftright wasn't as long as secound, so these elements have to be added to the envelope
389
                        if (j < secound.size() - leftright.size()) {
390
                            leftright.add(0, secound.get(j));
391
                        } else if ((j - diff) >= 0 && j >= 0) {
392
                            // if the left envelope of secound is smaller than the left envelope of leftright, might never happen.
393
                            if (leftright.get(j - diff).left > secound.get(j).left) {
394
                                leftright.get(j - diff).setLeft(secound.get(j).left);
395
                            }
396
                            // if the right envelope of secound is greater than the right envelope of leftright, might happen every time when a subtree was added by a move to the right.
397
                            if (leftright.get(j - diff).right < secound.get(j).right) {
398
                                leftright.get(j - diff).setRight(secound.get(j).right);
399
                            }
400
                        }
401
                    }
402
                }
403
            }
404
405
            lvl--;
406
407
            // do the horizontal alignment
408
            int yAlignment = doHorizontalPlacementInLevel(y, map, lvl);
409
410
            if (minimizeGap) {
411
                if (vertical) {
412
                    point = new Point(((leftright.get(leftright.size() - 1).right + leftright.get(leftright.size() - 1).left) / 2) - (relativeBounds.width / 2), yAlignment - relativeBounds.y);
413
                    leftright.add(new LeftRight(point.x, point.x + relativeBounds.width));
414
                } else {
415
                    point = new Point(((leftright.get(leftright.size() - 1).right + leftright.get(leftright.size() - 1).left) / 2) - (relativeBounds.height / 2), yAlignment - relativeBounds.x);
416
                    leftright.add(new LeftRight(point.x, point.x + relativeBounds.height));
417
                }
418
            } else {
419
                int leftMin = Integer.MAX_VALUE;
420
                int rightMax = Integer.MIN_VALUE;
421
                for (LeftRight l : leftright) {
422
                    leftMin = Math.min(leftMin, l.left);
423
                    rightMax = Math.max(rightMax, l.right);
424
                }
425
426
                assert leftMin != Integer.MAX_VALUE;
427
                assert rightMax != Integer.MIN_VALUE;
428
429
                if (vertical) {
430
                    point = new Point(((leftMin + rightMax) / 2) - (relativeBounds.width / 2), yAlignment - relativeBounds.y);
431
                    leftright.add(new LeftRight(point.x, point.x + relativeBounds.width));
432
                } else {
433
                    point = new Point(((leftMin + rightMax) / 2) - (relativeBounds.height / 2), yAlignment - relativeBounds.x);
434
                    leftright.add(new LeftRight(point.x, point.x + relativeBounds.height));
435
                }
436
            }
437
438
            return leftright;
439
        }
440
441
        /**
442
         * Method to move the children of the actual {@link Node} horizontally
443
         * by the given distance dx.
444
         *
445
         * @param dx the distance to move the children horizontally.
446
         */
447
        private void moveChildrenHorizontally(int dx) {
448
            for (Node n : children) {
449
                n.point.x += dx;
450
                n.moveChildrenHorizontally(dx);
186
            }
451
            }
187
        }
452
        }
188
453
189
        private void upload (HashMap<N, Point> result) {
454
        /**
190
            result.put (node, point);
455
         *
191
            for (Node child : children)
456
         * @param y
192
                child.upload (result);
457
         * @param map
458
         * @param lvl
459
         * @return
460
         */
461
        private int doHorizontalPlacementInLevel(int y, Map<Integer, Integer> map, int lvl) {
462
            int yAlignment = 0;
463
464
            // do the alignment
465
            if (alignment == Alignment.TOP) {
466
                // nothing to do
467
                yAlignment = y;
468
            } else if (alignment == Alignment.CENTER) {
469
                // place the widget in the center of the max.
470
                yAlignment = y + ((map.get(lvl) - space) / 2);
471
            } else if (alignment == Alignment.BOTTOM) {
472
                yAlignment = y + (map.get(lvl) - space);
473
            }
474
            return yAlignment;
475
        }
476
477
        /**
478
         * Method to invert x and y. Also relativeBounds correction is done and
479
         * a moveDistance (x-axis) can be given.
480
         * @param moveDistance the distance to move the subtree (x-axis).
481
         */
482
        private void invert(int moveDistance) {
483
            int tmpx = point.x + moveDistance;
484
            point.x = point.y;
485
            point.y = tmpx - relativeBounds.y;
486
            for (Node n : children) {
487
                n.invert(moveDistance);
488
            }
489
        }
490
491
        /**
492
         * Method to do a relativeBoundsCorrection (x-axis) and also to move the
493
         * Node for a given distance (also x-axis).
494
         * @param moveDistance the distance to move the subtree (x-axis).
495
         */
496
        private void relativeBoundsCorrectionX(int moveDistance) {
497
            point.x = point.x - relativeBounds.x + moveDistance;
498
            for (Node n : children) {
499
                n.relativeBoundsCorrectionX(moveDistance);
500
            }
501
        }
502
503
        private void upload(HashMap<N, Point> result) {
504
            result.put(node, point);
505
            for (Node child : children) {
506
                child.upload(result);
507
            }
193
        }
508
        }
194
    }
509
    }
195
196
}
510
}

Return to bug 178705