1 | /** |
2 | * The SpringLayout package represents a visualization of a set of nodes. The |
3 | * SpringLayout, which is initialized with a Graph, assigns X/Y locations to |
4 | * each node. When called <code>relax()</code>, the SpringLayout moves the |
5 | * visualization forward one step. |
6 | * |
7 | * @author Danyel Fisher |
8 | * @author Joshua O'Madadhain |
9 | */ |
10 | sclass CALSpringLayout { |
11 | double repulsion_range_sq = sqr(0.4/*0.5*//*100*/); |
12 | double repulsionFactor = 0.02/*0.01*/; |
13 | |
14 | double desiredLength = defaultDesiredLength; |
15 | static double defaultDesiredLength = 0.3; |
16 | bool center; |
17 | |
18 | AutoMap<Circle, SpringVertexData> springVertexData = new(SpringVertexData.class); |
19 | |
20 | CirclesAndLines cal; |
21 | |
22 | *() {} |
23 | *(CirclesAndLines *cal) {} |
24 | |
25 | /** |
26 | * <p>Sets the stretch parameter for this instance. This value |
27 | * specifies how much the degrees of an edge's incident vertices |
28 | * should influence how easily the endpoints of that edge |
29 | * can move (that is, that edge's tendency to change its length).</p> |
30 | * |
31 | * <p>The default value is 0.70. Positive values less than 1 cause |
32 | * high-degree vertices to move less than low-degree vertices, and |
33 | * values > 1 cause high-degree vertices to move more than |
34 | * low-degree vertices. Negative values will have unpredictable |
35 | * and inconsistent results.</p> |
36 | * @param stretch |
37 | */ |
38 | double stretch = 0.70; |
39 | |
40 | /** |
41 | * Returns the current value for the node repulsion range. |
42 | * @see #setRepulsionRange(int) |
43 | */ |
44 | public int getRepulsionRange() { |
45 | return (int)(Math.sqrt(repulsion_range_sq)); |
46 | } |
47 | |
48 | /** |
49 | * Sets the node repulsion range (in drawing area units) for this instance. |
50 | * Outside this range, nodes do not repel each other. The default value |
51 | * is 100. Negative values are treated as their positive equivalents. |
52 | * @param range |
53 | */ |
54 | public void setRepulsionRange(int range) { |
55 | this.repulsion_range_sq = range * range; |
56 | } |
57 | |
58 | /** |
59 | * Sets the force multiplier for this instance. This value is used to |
60 | * specify how strongly an edge "wants" to be its default length |
61 | * (higher values indicate a greater attraction for the default length), |
62 | * which affects how much its endpoints move at each timestep. |
63 | * The default value is 1/3. A value of 0 turns off any attempt by the |
64 | * layout to cause edges to conform to the default length. Negative |
65 | * values cause long edges to get longer and short edges to get shorter; use |
66 | * at your own risk. |
67 | */ |
68 | double force_multiplier = 1.0 / 3.0; |
69 | |
70 | /** |
71 | * Relaxation step. Moves all nodes a smidge. |
72 | */ |
73 | bool step() { |
74 | deleteKeysNotIn(springVertexData, new HashSet(cal.circles)); |
75 | |
76 | for (Circle v : cal.circles) { |
77 | SpringVertexData svd = springVertexData.get(v); |
78 | if (svd == null) continue; |
79 | svd.dx /= 4; |
80 | svd.dy /= 4; |
81 | svd.edgedx = svd.edgedy = 0; |
82 | svd.repulsiondx = svd.repulsiondy = 0; |
83 | } |
84 | |
85 | relaxEdges(); |
86 | calculateRepulsion(); |
87 | ret moveNodes(); |
88 | } |
89 | |
90 | protected void relaxEdges() { |
91 | try { |
92 | for (Line e : cal.lines) { |
93 | Circle v1 = e.a, v2 = e.b; |
94 | |
95 | Point2D p1 = transform(v1); |
96 | Point2D p2 = transform(v2); |
97 | if(p1 == null || p2 == null) continue; |
98 | double vx = p1.getX() - p2.getX(); |
99 | double vy = p1.getY() - p2.getY(); |
100 | double len = Math.sqrt(vx * vx + vy * vy); |
101 | |
102 | // round from zero, if needed [zero would be Bad.]. |
103 | len = (len == 0) ? .0001 : len; |
104 | |
105 | double f = force_multiplier * (desiredLength - len) / len; |
106 | |
107 | // XXX? f = f * Math.pow(stretch, (getGraph().degree(v1) + getGraph().degree(v2) - 2)); |
108 | |
109 | // the actual movement distance 'dx' is the force multiplied by the |
110 | // distance to go. |
111 | double dx = f * vx; |
112 | double dy = f * vy; |
113 | ifdef CALSpringLayout_debug |
114 | print("desiredLen=" + desiredLen + ", len=" + len + ", f=" + f + ", dx=" + dx + ", dy=" + dy); |
115 | endifdef |
116 | |
117 | SpringVertexData v1D, v2D; |
118 | v1D = springVertexData.get(v1); |
119 | v2D = springVertexData.get(v2); |
120 | |
121 | v1D.edgedx += dx; |
122 | v1D.edgedy += dy; |
123 | v2D.edgedx += -dx; |
124 | v2D.edgedy += -dy; |
125 | } |
126 | } catch(ConcurrentModificationException cme) { |
127 | relaxEdges(); |
128 | } |
129 | } |
130 | |
131 | void calculateRepulsion() { |
132 | for (Circle v : cal.circles) { |
133 | SpringVertexData svd = springVertexData.get(v); |
134 | if(svd == null) continue; |
135 | double dx = 0, dy = 0; |
136 | |
137 | for (Circle v2 : cal.circles) { |
138 | if (v == v2) continue; |
139 | Point2D p = transform(v); |
140 | Point2D p2 = transform(v2); |
141 | if(p == null || p2 == null) continue; |
142 | double vx = p.getX() - p2.getX(); |
143 | double vy = p.getY() - p2.getY(); |
144 | double distanceSq = p.distanceSq(p2); |
145 | if (distanceSq == 0) { |
146 | dx += Math.random()*0.01; |
147 | dy += Math.random()*0.01; |
148 | } else if (distanceSq < repulsion_range_sq) { |
149 | double factor = 1; |
150 | dx += factor * vx / distanceSq; |
151 | dy += factor * vy / distanceSq; |
152 | } |
153 | |
154 | ifdef CALSpringLayout_debug |
155 | print("repulsion: distanceSq=" + distanceSq + ", dx=" + dx + ", dy=" + dy); |
156 | endifdef |
157 | } |
158 | double dlen = dx * dx + dy * dy; |
159 | if (dlen > 0) { |
160 | //dlen = Math.sqrt(dlen) / 2 / repulsionFactor; |
161 | // XXX |
162 | dlen = 1000; |
163 | |
164 | svd.repulsiondx += dx / dlen; |
165 | svd.repulsiondy += dy / dlen; |
166 | ifdef CALSpringLayout_debug |
167 | print("total repulsion: " + dx/dlen + ", " + dy/dlen); |
168 | endifdef |
169 | } |
170 | } |
171 | } |
172 | |
173 | // returns true if anything was moved |
174 | bool moveNodes() { |
175 | for (Circle v : cal.circles) { |
176 | SpringVertexData vd = springVertexData.get(v); |
177 | if (vd == null) continue; |
178 | Point2D xyd = transform(v); |
179 | |
180 | vd.dx += vd.repulsiondx + vd.edgedx; |
181 | vd.dy += vd.repulsiondy + vd.edgedy; |
182 | |
183 | // keeps nodes from moving any faster than 5 per time unit |
184 | v.x += Math.max(-5, Math.min(5, vd.dx)); |
185 | v.y += Math.max(-5, Math.min(5, vd.dy)); |
186 | pcallF(cal.onLayoutChange, v); |
187 | } |
188 | |
189 | if (center) |
190 | calCenterStepwise(cal, 0.1); |
191 | |
192 | true; // for now |
193 | } |
194 | |
195 | Point2D transform(Circle v) { |
196 | ret new Point2D.Double(v.x, v.y); |
197 | } |
198 | |
199 | void clear() { |
200 | springVertexData.clear(); |
201 | } |
202 | |
203 | static class SpringVertexData { |
204 | double edgedx; |
205 | double edgedy; |
206 | double repulsiondx; |
207 | double repulsiondy; |
208 | |
209 | /** movement speed, x */ |
210 | protected double dx; |
211 | |
212 | /** movement speed, y */ |
213 | protected double dy; |
214 | } |
215 | } |
download show line numbers debug dex old transpilations
Travelled to 14 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1009847 |
Snippet name: | CALSpringLayout |
Eternal ID of this version: | #1009847/38 |
Text MD5: | 9f545163380c37cb43a6ecd3d620605b |
Author: | stefan |
Category: | javax / a.i. / gui |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2017-09-14 04:18:08 |
Source code size: | 6584 bytes / 215 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 535 / 1933 |
Version history: | 37 change(s) |
Referenced in: | [show references] |