001: package hero.client.grapheditor;
002:
003: import java.awt.Rectangle;
004: import java.awt.geom.Point2D;
005: import java.util.Hashtable;
006:
007: import com.jgraph.graph.CellView;
008: import com.jgraph.graph.Edge;
009: import com.jgraph.graph.GraphConstants;
010:
011: public class Touch implements Runnable {
012:
013: private WFGraph graph;
014: private Hashtable deltas = new Hashtable();
015: private Hashtable positions = new Hashtable();
016: private Thread relaxer;
017: private boolean allowedToRun = false;
018: private boolean repaintNeeded = false;
019: private double damper = 1.0; // A low damper value causes the graph to move slowly
020: private double maxMotion = 0; // Keep an eye on the fastest moving node to see if the graph is stabilizing
021: private double lastMaxMotion = 0;
022: private double motionRatio = 0; // It's sort of a ratio, equal to lastMaxMotion/maxMotion-1
023: private boolean damping = true; // When damping is true, the damper value decreases
024:
025: private double rigidity = 1; // Rigidity has the same effect as the damper, except that it's a constant
026: // a low rigidity value causes things to go slowly.
027: // a value that's too high will cause oscillation
028: private double newRigidity = 1;
029:
030: // ............
031:
032: /** Constructor with a supplied TGPanel <tt>tgp</tt>.
033: */
034: public Touch(WFGraph graph) {
035: this .graph = graph;
036: relaxer = null;
037: }
038:
039: void setRigidity(double r) {
040: newRigidity = r; //update rigidity at the end of the relax() thread
041: }
042:
043: //relaxEdges is more like tense edges up. All edges pull nodes closer together;
044: private synchronized void relaxEdges() {
045: try {
046: Object[] edges = graph.getEdges(graph.getAll());
047: //System.out.println("edges="+edges.length);
048: for (int i = 0; i < edges.length; i++) {
049: CellView from = graph.getSourceView(edges[i]);
050: CellView to = graph.getTargetView(edges[i]);
051: CellView fromV = graph.getView().getMapping(
052: ((Edge) edges[i]).getSource(), false);
053: CellView toV = graph.getView().getMapping(
054: ((Edge) edges[i]).getTarget(), false);
055:
056: if (from != null && to != null) {
057: Rectangle bf = fromV.getBounds();// GraphConstants.getBounds(fromV.getAttributes());
058: Rectangle bt = toV.getBounds();//GraphConstants.getBounds(toV.getAttributes());
059: //Point2D.Double bf = getPosition(from);
060: //Point2D.Double bt = getPosition(to);
061: double vx = bt.x - bf.x;
062: double vy = bt.y - bf.y;
063: double len = Math.sqrt(vx * vx + vy * vy);
064: double dx = vx * rigidity; //rigidity makes edges tighter
065: double dy = vy * rigidity;
066: double length = getLength(edges[i]) * 100;
067: dx /= length;
068: dy /= length;
069: moveView(to, -dx * len, -dy * len);
070: moveView(from, dx * len, dy * len);
071:
072: }
073: }
074: } catch (Exception e) {
075: run();
076: }
077: }
078:
079: public double getLength(Object edge) {
080: CellView view = graph.getView().getMapping(edge, false);
081: return GPGraphTools.getLength(view);
082: }
083:
084: private synchronized void avoidLabels() {
085: Object[] vertices = graph.getVertices(graph.getAll());
086: for (int i = 0; i < vertices.length; i++) {
087: for (int j = i + 1; j < vertices.length; j++) {
088: CellView from = graph.getView().getMapping(vertices[i],
089: false);
090: CellView to = graph.getView().getMapping(vertices[j],
091: false);
092: if (from != null && to != null) {
093: Point2D.Double bf = getPosition(from);
094: Point2D.Double bt = getPosition(to);
095:
096: double dx = 0;
097: double dy = 0;
098: double vx = bf.x - bt.x;
099: double vy = bf.y - bt.y;
100: double len = vx * vx + vy * vy; //so it's length squared
101: if (len == 0) {
102: dx = Math.random();
103: dy = Math.random();
104: } else if (len < 200 * 200) {
105: dx = vx / len;
106: dy = vy / len;
107: }
108: int repSum = 400; // 200; // n1.repulsion * n2.repulsion/100
109: moveView(from, dx * repSum, dy * repSum);
110: moveView(to, -dx * repSum, -dy * repSum);
111: }
112: }
113: }
114: }
115:
116: public void startDamper() {
117: damping = true;
118: }
119:
120: public void stopDamper() {
121: damping = false;
122: damper = 1.0; //A value of 1.0 means no damping
123: }
124:
125: public void resetDamper() { //reset the damper, but don't keep damping.
126: damping = true;
127: damper = 1.0;
128: }
129:
130: public void setDamper(double newValue) {
131: damper = newValue;
132: }
133:
134: public void damp() {
135: if (damping) {
136: if (motionRatio <= 0.001) { //This is important. Only damp when the graph starts to move faster
137: //When there is noise, you damp roughly half the time. (Which is a lot)
138: //
139: //If things are slowing down, then you can let them do so on their own,
140: //without damping.
141:
142: //If max motion<0.2, damp away
143: //If by the time the damper has ticked down to 0.9, maxMotion is still>1, damp away
144: //We never want the damper to be negative though
145: if ((maxMotion < 0.2 || (maxMotion > 1 && damper < 0.9))
146: && damper > 0.01)
147: damper -= 0.01;
148: //If we've slowed down significanly, damp more aggresively (then the line two below)
149: else if (maxMotion < 0.4 && damper > 0.003)
150: damper -= 0.003;
151: //If max motion is pretty high, and we just started damping, then only damp slightly
152: else if (damper > 0.0001)
153: damper -= 0.0001;
154: }
155: }
156: if (maxMotion < 0.001 && damping)
157: damper = 0;
158: //System.out.println("damper="+damper);
159: }
160:
161: private synchronized void moveNodes() {
162: lastMaxMotion = maxMotion;
163: double maxMotionA = 0;
164: Object[] vertices = graph.getVertices(graph.getAll());
165: for (int i = 0; i < vertices.length; i++) {
166: CellView view = graph.getView().getMapping(vertices[i],
167: false);
168: if (view != null) {
169: Rectangle bounds = GraphConstants.getBounds(view
170: .getAttributes());
171: Point2D.Double delta = getDelta(view);
172: Point2D.Double position = getPosition(view);
173: //deltas.remove(view);
174: double dx = delta.getX();
175: double dy = delta.getY();
176: dx *= damper;
177: dy *= damper;
178: delta.setLocation(dx / 2, dy / 2);
179: double distMoved = Math.sqrt(dx * dx + dy * dy);
180: if (GraphConstants.isMoveable(view.getAttributes())
181: && !graph.isCellSelected(vertices[i])
182: && (dx != 0 || dy != 0)) {
183: //System.out.println("dx: " + dx + " dy: "+dy);
184: position.x += Math.max(-5, Math.min(5, dx));
185: position.y += Math.max(-5, Math.min(5, dy));
186: bounds.x = Math.max(0, (int) position.x
187: - bounds.width / 2);
188: bounds.y = Math.max(0, (int) position.y
189: - bounds.height / 2);
190: repaintNeeded = true;
191: }
192: maxMotionA = Math.max(distMoved, maxMotionA);
193:
194: }
195: }
196: ;
197: maxMotion = maxMotionA;
198: if (maxMotion > 0)
199: motionRatio = lastMaxMotion / maxMotion - 1;
200: else
201: motionRatio = 0;
202: damp();
203: }
204:
205: private synchronized void relax() {
206: for (int i = 0; i < 10; i++) {
207: relaxEdges();
208: avoidLabels();
209: moveNodes();
210: }
211: if (rigidity != newRigidity)
212: rigidity = newRigidity; //update rigidity
213: if (repaintNeeded) {
214: graph.repaint();
215: repaintNeeded = false;
216: }
217: }
218:
219: public void run() {
220: Thread me = Thread.currentThread();
221: while (relaxer == me && allowedToRun) {
222: relax();
223: //System.out.println("D "+damper);
224: try {
225: Thread.sleep(20);
226: } catch (InterruptedException e) {
227: break;
228: }
229: }
230: }
231:
232: public void start() {
233: relaxer = new Thread(this );
234: allowedToRun = true;
235: relaxer.start();
236: }
237:
238: public boolean isRunning() {
239: if (relaxer != null)
240: return relaxer.isAlive();
241: return false;
242: }
243:
244: public void stop() {
245: allowedToRun = false;
246: relaxer = null;
247: }
248:
249: public Point2D.Double getPosition(CellView view) {
250: Point2D.Double p1 = (Point2D.Double) positions.get(view);
251:
252: Rectangle rect = GraphConstants.getBounds(view.getAttributes());
253: Point2D.Double p2 = new Point2D.Double(rect.x + rect.width / 2,
254: rect.y + rect.height / 2);
255:
256: if (p1 != null) {
257: if (Math.abs(p1.x - p2.x) > 5 || Math.abs(p1.y - p2.y) > 5) {
258: //System.out.println("p1.x " +p1.x +" p2.x " +p2.x);
259: p1.setLocation(p2.x, p2.y);
260: }
261: return p1;
262: } else {
263: positions.put(view, p2);
264: return p2;
265: }
266: }
267:
268: public Point2D.Double getDelta(CellView view) {
269: Point2D.Double p = (Point2D.Double) deltas.get(view);
270: if (p == null) {
271: p = new Point2D.Double(0, 0);
272: deltas.put(view, p);
273: }
274: return p;
275: }
276:
277: public void moveView(CellView view, double dx, double dy) {
278: //System.out.println("set Delta for "+view+" to "+dx+", "+dy);
279: Point2D.Double p = getDelta(view);
280: p.setLocation(p.getX() + dx, p.getY() + dy);
281: }
282:
283: }
|