blob: 7eb2432e1d8c298fa64762b7e0ad73e1c1ec9f54 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.java.graph;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import javax.swing.JScrollPane;
import org.netbeans.api.visual.layout.SceneLayout;
import org.netbeans.api.visual.model.ObjectSceneEvent;
import org.netbeans.api.visual.model.ObjectSceneListener;
import org.netbeans.api.visual.model.ObjectState;
import org.netbeans.api.visual.widget.Scene;
import org.netbeans.api.visual.widget.Widget;
/**
* Layout instance implementing the FruchtermanReingold algorithm
* http://mtc.epfl.ch/~beyer/CCVisu/manual/main005.html
*
* Inspired by implementations at JUNG and Prefuse.
*/
class FruchtermanReingoldLayout<I extends GraphNodeImplementation> extends SceneLayout {
private double forceConstant;
private double temp;
private int iterations = 700;
private final int magicSizeMultiplier = 10;
private final int magicSizeConstant = 200;
private Rectangle bounds;
protected int m_fidx;
private static final double MIN = 0.000001D;
private static final double ALPHA = 0.1;
private DependencyGraphScene<I> scene;
private JScrollPane panel;
FruchtermanReingoldLayout(DependencyGraphScene<I> scene, JScrollPane panel) {
super(scene);
iterations = 700;
this.scene = scene;
init();
this.panel = panel;
}
public @Override void performLayout() {
performLayout(true);
// scene.validate();
// Rectangle rectangle = new Rectangle (0, 0, 1, 1);
// for (Widget widget : scene.getChildren()) {
// Rectangle childBounds = widget.getBounds();
// if (childBounds == null) {
// continue;
// }
// rectangle = rectangle.union(widget.convertLocalToScene(childBounds));
// }
// Dimension dim = rectangle.getSize ();
// Dimension viewDim = panel.getViewportBorderBounds ().getSize ();
// double zoom = Math.min ((float) viewDim.width / dim.width, (float) viewDim.height / dim.height);
// scene.setZoomFactor (Math.min(zoom, 1));
// scene.validate();
}
private void performLayout(boolean finish) {
for (int i=0; i < iterations; i++ ) {
int repeats = 0;
while (true) {
for (GraphNode n : scene.getNodes()) {
NodeWidget w = getWidget(n);
if (w.isFixed()) {
continue;
}
// if (i < iterations / 5) {
// if (scene.findNodeEdges(n, false, true).size() == 1
// && scene.findNodeEdges(n, true, false).size() == 0) {
// //for leaves, ignore repulsion first, to cause closer location to parent...
// System.out.println("continue.." + n.getArtifact().getId());
// continue;
// }
// }
calcRepulsion(w);
}
for (GraphEdge e : scene.getEdges()) {
calcAttraction(e);
}
for (GraphNode n : scene.getNodes()) {
NodeWidget w = getWidget(n);
if (w.isFixed()) {
continue;
}
calcPositions(w);
}
if (areAllFixed() || repeats > 2) {
doRelayoutNonFixed();
resetFixed();
cool(i);
break;
}
repeats = repeats + 1;
}
}
if (finish) {
finish();
}
}
public void rePerformLayout(int iters) {
int nds = scene.getNodes().size();
iterations = iters;
bounds = scene.getBounds();
if (bounds == null) {
return;
}
// System.out.println("scene bounds are =" + bounds);
temp = bounds.getWidth() / 1000;
// forceConstant = 0.75 * Math.sqrt(bounds.getHeight() * bounds.getWidth() / nds);
forceConstant = 0.25 * Math.sqrt(bounds.getHeight() * bounds.getWidth() / nds);
// System.out.println("force constant2=" + forceConstant);
performLayout(false);
}
private void init() {
int nds = scene.getNodes().size();
bounds = new Rectangle(magicSizeConstant + (magicSizeMultiplier * nds),
magicSizeConstant + (magicSizeMultiplier * nds)); //g.getMaximumBounds();
temp = bounds.getWidth() / 10;
forceConstant = 0.75 * Math.sqrt(bounds.getHeight() * bounds.getWidth() / nds);
GraphNode<I> rn = scene.getRootGraphNode();
NodeWidget rw = getWidget(rn);
rw.locX = bounds.getCenterX();
rw.locY = bounds.getCenterY();
rw.setFixed(true);
layoutCirculary(scene.getNodes(), rn);
}
private void finish() {
for (GraphNode n : scene.getNodes()) {
NodeWidget w = getWidget(n);
Widget wid = scene.findWidget(n);
Point point = new Point();
point.setLocation(w.locX, w.locY);
if (scene.isAnimated()) {
scene.getSceneAnimator().animatePreferredLocation(wid, point);
} else {
wid.setPreferredLocation(point);
}
}
}
private void calcPositions(NodeWidget w) {
double deltaLength = Math.max(MIN,
Math.sqrt(w.dispX * w.dispX + w.dispY * w.dispY));
double xDisp = w.dispX/deltaLength * Math.min(deltaLength, temp);
double yDisp = w.dispY/deltaLength * Math.min(deltaLength, temp);
w.locX += xDisp;
w.locY += yDisp;
if (isThereFreeSpaceNonFixedSpace(w)) {
w.setFixed(true);
}
// double x = n.locX;
// double y = n.locY;
// // don't let nodes leave the display
// double borderWidth = bounds.getWidth() / 50.0;
// if (x < bounds.getMinX() + borderWidth) {
// x = bounds.getMinX() + borderWidth + Math.random() * borderWidth * 2.0;
// } else if (x > (bounds.getMaxX() - borderWidth)) {
// x = bounds.getMaxX() - borderWidth - Math.random() * borderWidth * 2.0;
// }
//
// if (y < bounds.getMinY() + borderWidth) {
// y = bounds.getMinY() + borderWidth + Math.random() * borderWidth * 2.0;
// } else if (y > (bounds.getMaxY() - borderWidth)) {
// y = bounds.getMaxY() - borderWidth - Math.random() * borderWidth * 2.0;
// }
// n.locX = x;
// n.locY = y;
}
public void calcAttraction(GraphEdge e) {
NodeWidget w1 = getWidget(scene.getEdgeSource(e));
NodeWidget w2 = getWidget(scene.getEdgeTarget(e));
assert (w1 != null && w2 != null) : "wrong edge=" + e;
// Widget wid1 = scene.findWidget(n1);
// Rectangle rect1 = wid1.getBounds();
// Widget wid2 = scene.findWidget(n2);
// Rectangle rect2 = wid2.getBounds();
double xDelta = w1.locX - w2.locX;
double yDelta = w1.locX - w2.locY;
double deltaLength = Math.max(MIN, Math.sqrt(xDelta*xDelta + yDelta*yDelta));
double force = (deltaLength * deltaLength) / forceConstant;
double xDisp = (xDelta / deltaLength) * force;
double yDisp = (yDelta / deltaLength) * force;
w1.dispX -= xDisp;
w1.dispY -= yDisp;
w2.dispX += xDisp;
w2.dispY += yDisp;
}
public void calcRepulsion(NodeWidget w1) {
w1.dispX = 0.0;
w1.dispY = 0.0;
// Widget wid1 = scene.findWidget(n1);
// Rectangle rect1 = wid1.getBounds();
for (GraphNode n2 : scene.getNodes()) {
NodeWidget w2 = getWidget(n2);
// Widget wid2 = scene.findWidget(n2);
// Rectangle rect2 = wid1.getBounds();
//TODO..
// if (n2.isFixed()) continue;
if (w1 != w2) {
double xDelta = w1.locX - w2.locX;
double yDelta = w1.locY - w2.locY;
double deltaLength = Math.max(MIN, Math.sqrt(xDelta*xDelta + yDelta*yDelta));
double force = (forceConstant * forceConstant) / deltaLength;
w1.dispX += (xDelta / deltaLength) * force;
w1.dispY += (yDelta / deltaLength) * force;
}
}
}
/**
* this "cools" down the forces causing smaller movements..
*/
private void cool(int iter) {
temp *= (1.0 - iter / (double) iterations);
}
private void layoutCirculary(Collection<GraphNode<I>> nodes, GraphNode<I> masterNode) {
Point masterPoint = new Point();
NodeWidget master = getWidget(masterNode);
masterPoint.setLocation(master.locX, master.locY);
double r;
double theta;
double thetaStep = Math.PI / 5;
r = 150;
theta = 0;
Iterator<GraphNode<I>> it = nodes.iterator();
NodeWidget nw = getWidget(it.next());
while (true) {
AffineTransform tr = AffineTransform.getRotateInstance(theta);
Point2D d2point = tr.transform(new Point2D.Double(0, r), null);
Point point = new Point((int)d2point.getX() + masterPoint.x, (int)d2point.getY() + masterPoint.y);
if (isThereFreeSpace(point, nw)) {
nw.locX = point.getX();
nw.locY = point.getY();
nw.dispX = 0;
nw.dispY = 0;
if (it.hasNext()) {
nw = getWidget(it.next());
} else {
return;
}
}
theta = theta + thetaStep;
if (theta > (Math.PI * 2 - Math.PI / 10)) {
r = r + 90;
theta = theta - Math.PI * 2;
thetaStep = thetaStep * 3 / 4;
}
}
}
private boolean isThereFreeSpace(Point pnt, NodeWidget widget) {
if(scene != null) {
if(widget != null) {
Rectangle bnds = widget.getBounds();
if (bnds == null) {
return true;
}
bnds = new Rectangle(pnt.x, pnt.y, bnds.width, bnds.height);
for (GraphNode nd : scene.getNodes()) {
NodeWidget nw = getWidget(nd);
Rectangle bnds2 = nw.getBounds();
if (bnds2 == null) {
return true;
}
Point point = new Point();
point.setLocation(nw.locX, nw.locY);
bnds2 = new Rectangle(point, bnds2.getSize());
if (bnds.intersects((bnds2))) {
return false;
}
}
}
}
return true;
}
private boolean areAllFixed() {
for (GraphNode nd : scene.getNodes()) {
if (!getWidget(nd).isFixed()) {
return false;
}
}
return true;
}
private void resetFixed() {
for (GraphNode nd : scene.getNodes()) {
getWidget(nd).setFixed(false); // XXX suboptimal, the same loop & findWidget here and at many other places
}
getWidget(scene.getRootGraphNode()).setFixed(true);
}
private boolean isThereFreeSpaceNonFixedSpace(NodeWidget w) {
Rectangle bnds = w.getBounds();
if (bnds == null) {
return true;
}
Point pnt = new Point();
pnt.setLocation(w.locX, w.locY);
bnds = new Rectangle(pnt, bnds.getSize());
for (GraphNode nd : scene.getNodes()) {
NodeWidget nw = getWidget(nd);
Rectangle bnds2 = scene.findWidget(nd).getBounds();
if (bnds2 == null) {
return true;
}
Point point = new Point();
point.setLocation(nw.locX, nw.locY);
bnds2 = new Rectangle(point, bnds2.getSize());
if (nw.isFixed() && bnds.intersects((bnds2))) {
return false;
}
}
return true;
}
private void doRelayoutNonFixed() {
for (GraphNode node : scene.getNodes()) {
NodeWidget w = getWidget(node);
if (!w.isFixed()) {
relayoutNonFixed(w);
}
}
}
private void relayoutNonFixed(NodeWidget w) {
Point masterPoint = new Point();
masterPoint.setLocation(w.locX, w.locY);
double r;
double theta;
double thetaStep = Math.PI / 5;
r = 30;
theta = 0;
w.setFixed(false);
while (true) {
AffineTransform tr = AffineTransform.getRotateInstance(theta);
Point2D d2point = tr.transform(new Point2D.Double(0, r), null);
Point point = new Point((int)d2point.getX() + masterPoint.x, (int)d2point.getY() + masterPoint.y);
w.locX = point.getX();
w.locY = point.getY();
if (isThereFreeSpaceNonFixedSpace(w)) {
w.setFixed(true);
return;
}
theta = theta + thetaStep;
if (theta > (Math.PI * 2 - Math.PI / 10)) {
r = r + 30;
theta = theta - Math.PI * 2;
thetaStep = thetaStep * 3 / 4;
}
}
}
private NodeWidget getWidget(GraphNode n) {
return (NodeWidget) scene.findWidget(n);
}
private EdgeWidget getWidget(GraphEdge e) {
return (EdgeWidget) scene.findWidget(e);
}
}