blob: 332b52f7407760ae820788e9e9374fd3dccd80b6 [file] [log] [blame]
Marc Kupietzd4ad8e72017-07-04 14:07:31 +02001(function() {
2
3d3.labeler = function() {
4 var lab = [],
5 anc = [],
6 w = 1, // box width
7 h = 1, // box width
8 update,
9 labeler = {};
10
11 var max_move = 5.0/20,
12 max_angle = 0.5,
13 acc = 0;
14 rej = 0;
15
16 // weights
17 var w_len = 0.2, // leader line length
18 w_inter = 1.0, // leader line intersection
19 w_lab2 = 30.0, // label-label overlap
20 w_lab_anc = 30.0; // label-anchor overlap
21 w_orient = 3.0; // orientation bias
22
23 // booleans for user defined functions
24 var user_energy = false,
25 user_schedule = false;
26
27 var user_defined_energy,
28 user_defined_schedule;
29
30 energy = function(index) {
31 // energy function, tailored for label placement
32
33 var m = lab.length,
34 ener = 0,
35 dx = lab[index].x - anc[index].x,
36 dy = anc[index].y - lab[index].y,
37 dist = Math.sqrt(dx * dx + dy * dy),
38 overlap = true,
39 amount = 0
40 theta = 0;
41
42 // penalty for length of leader line
43 if (dist > 0) ener += dist * w_len;
44
45 // label orientation bias
46 dx /= dist;
47 dy /= dist;
48 if (dx > 0 && dy > 0) { ener += 0 * w_orient; }
49 else if (dx < 0 && dy > 0) { ener += 1 * w_orient; }
50 else if (dx < 0 && dy < 0) { ener += 2 * w_orient; }
51 else { ener += 3 * w_orient; }
52
53 var x21 = lab[index].x,
54 y21 = lab[index].y - lab[index].height + 2.0,
55 x22 = lab[index].x + lab[index].width,
56 y22 = lab[index].y + 2.0;
57 var x11, x12, y11, y12, x_overlap, y_overlap, overlap_area;
58
59 for (var i = 0; i < m; i++) {
60 if (i != index) {
61
62 // penalty for intersection of leader lines
63 overlap = intersect(anc[index].x, lab[index].x, anc[i].x, lab[i].x,
64 anc[index].y, lab[index].y, anc[i].y, lab[i].y);
65 if (overlap) ener += w_inter;
66
67 // penalty for label-label overlap
68 x11 = lab[i].x;
69 y11 = lab[i].y - lab[i].height + 2.0;
70 x12 = lab[i].x + lab[i].width;
71 y12 = lab[i].y + 2.0;
72 x_overlap = Math.max(0, Math.min(x12,x22) - Math.max(x11,x21));
73 y_overlap = Math.max(0, Math.min(y12,y22) - Math.max(y11,y21));
74 overlap_area = x_overlap * y_overlap;
75 ener += (overlap_area * w_lab2);
76 }
77
78 // penalty for label-anchor overlap
79 x11 = anc[i].x - anc[i].r;
80 y11 = anc[i].y - anc[i].r;
81 x12 = anc[i].x + anc[i].r;
82 y12 = anc[i].y + anc[i].r;
83 x_overlap = Math.max(0, Math.min(x12,x22) - Math.max(x11,x21));
84 y_overlap = Math.max(0, Math.min(y12,y22) - Math.max(y11,y21));
85 overlap_area = x_overlap * y_overlap;
86 ener += (overlap_area * w_lab_anc);
87
88 }
89 return ener;
90 };
91
92 mcmove = function(currT) {
93 // Monte Carlo translation move
94
95 // select a random label
96 var i = Math.floor(Math.random() * lab.length);
97
98 // save old coordinates
99 var x_old = lab[i].x;
100 var y_old = lab[i].y;
101
102 // old energy
103 var old_energy;
104 if (user_energy) {old_energy = user_defined_energy(i, lab, anc)}
105 else {old_energy = energy(i)}
106
107 // random translation
108 lab[i].x += (Math.random() - 0.5) * max_move;
109 lab[i].y += (Math.random() - 0.5) * max_move;
110
111 // hard wall boundaries
112 if (lab[i].x > w) lab[i].x = x_old;
113 if (lab[i].x < 0) lab[i].x = x_old;
114 if (lab[i].y > h) lab[i].y = y_old;
115 if (lab[i].y < 0) lab[i].y = y_old;
116
117 // new energy
118 var new_energy;
119 if (user_energy) {new_energy = user_defined_energy(i, lab, anc)}
120 else {new_energy = energy(i)}
121
122 // delta E
123 var delta_energy = new_energy - old_energy;
124
125 if (Math.random() < Math.exp(-delta_energy / currT)) {
126 acc += 1;
127 } else {
128 // move back to old coordinates
129 lab[i].x = x_old;
130 lab[i].y = y_old;
131 rej += 1;
132 }
133
134 };
135
136 mcrotate = function(currT) {
137 // Monte Carlo rotation move
138
139 // select a random label
140 var i = Math.floor(Math.random() * lab.length);
141
142 // save old coordinates
143 var x_old = lab[i].x;
144 var y_old = lab[i].y;
145
146 // old energy
147 var old_energy;
148 if (user_energy) {old_energy = user_defined_energy(i, lab, anc)}
149 else {old_energy = energy(i)}
150
151 // random angle
152 var angle = (Math.random() - 0.5) * max_angle;
153
154 var s = Math.sin(angle);
155 var c = Math.cos(angle);
156
157 // translate label (relative to anchor at origin):
158 lab[i].x -= anc[i].x
159 lab[i].y -= anc[i].y
160
161 // rotate label
162 var x_new = lab[i].x * c - lab[i].y * s,
163 y_new = lab[i].x * s + lab[i].y * c;
164
165 // translate label back
166 lab[i].x = x_new + anc[i].x
167 lab[i].y = y_new + anc[i].y
168
169 // hard wall boundaries
170 if (lab[i].x > w) lab[i].x = x_old;
171 if (lab[i].x < 0) lab[i].x = x_old;
172 if (lab[i].y > h) lab[i].y = y_old;
173 if (lab[i].y < 0) lab[i].y = y_old;
174
175 // new energy
176 var new_energy;
177 if (user_energy) {new_energy = user_defined_energy(i, lab, anc)}
178 else {new_energy = energy(i)}
179
180 // delta E
181 var delta_energy = new_energy - old_energy;
182
183 if (Math.random() < Math.exp(-delta_energy / currT)) {
184 acc += 1;
185 } else {
186 // move back to old coordinates
187 lab[i].x = x_old;
188 lab[i].y = y_old;
189 rej += 1;
190 }
191
192 };
193
194 intersect = function(x1, x2, x3, x4, y1, y2, y3, y4) {
195 // returns true if two lines intersect, else false
196 // from http://paulbourke.net/geometry/lineline2d/
197
198 var mua, mub;
199 var denom, numera, numerb;
200
201 denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
202 numera = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
203 numerb = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
204
205 /* Is the intersection along the the segments */
206 mua = numera / denom;
207 mub = numerb / denom;
208 if (!(mua < 0 || mua > 1 || mub < 0 || mub > 1)) {
209 return true;
210 }
211 return false;
212 }
213
214 cooling_schedule = function(currT, initialT, nsweeps) {
215 // linear cooling
216 return (currT - (initialT / nsweeps));
217 }
218
219 labeler.start2 = function(nsweeps) {
220 // main simulated annealing function
221 var m = lab.length,
222 currT = 1.0,
223 initialT = 1.0;
224
225 for (var i = 0; i < nsweeps; i++) {
226 for (var j = 0; j < m; j++) {
227 if (Math.random() < 0.5) { mcmove(currT); }
228 else { mcrotate(currT); }
229 }
230 currT = cooling_schedule(currT, initialT, nsweeps);
231 }
232 };
233
234 labeler.start = function(nsweeps) {
235 // main simulated annealing function
236 if(nsweeps <= 0)
237 return;
238
239 var m = lab.length,
240 currT = 1.0,
241 initialT = 1.0;
242
243 for (var j = 0; j < m; j++) {
244 if (Math.random() < 0.5) { mcmove(currT); }
245 else { mcrotate(currT); }
246 }
247 update();
248 currT = cooling_schedule(currT, initialT, nsweeps);
249 setTimeout(labeler.start(nsweeps-1), 2);
250 };
251
252 labeler.width = function(x) {
253 // users insert graph width
254 if (!arguments.length) return w;
255 w = x;
256 return labeler;
257 };
258
259 labeler.height = function(x) {
260 // users insert graph height
261 if (!arguments.length) return h;
262 h = x;
263 return labeler;
264 };
265
266 labeler.label = function(x) {
267 // users insert label positions
268 if (!arguments.length) return lab;
269 lab = x;
270 return labeler;
271 };
272
273 labeler.update = function(x) {
274 // users insert label positions
275 if (!arguments.length) return null;
276 update = x;
277 return labeler;
278 };
279
280 labeler.anchor = function(x) {
281 // users insert anchor positions
282 if (!arguments.length) return anc;
283 anc = x;
284 return labeler;
285 };
286
287 labeler.alt_energy = function(x) {
288 // user defined energy
289 if (!arguments.length) return energy;
290 user_defined_energy = x;
291 user_energy = true;
292 return labeler;
293 };
294
295 labeler.alt_schedule = function(x) {
296 // user defined cooling_schedule
297 if (!arguments.length) return cooling_schedule;
298 user_defined_schedule = x;
299 user_schedule = true;
300 return labeler;
301 };
302
303 return labeler;
304};
305
306})();
307