jsmetric4java 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +7 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +8 -0
  4. data/README +16 -0
  5. data/Rakefile +37 -0
  6. data/bin/jsmetric4java +20 -0
  7. data/boot.rb +5 -0
  8. data/build +1 -0
  9. data/features/cyclometric_complexity/boolean_complexity_counting.feature +46 -0
  10. data/features/cyclometric_complexity/case_complexity_counting.feature +117 -0
  11. data/features/cyclometric_complexity/exception_complexity_counting.feature +81 -0
  12. data/features/cyclometric_complexity/function_detection.feature +128 -0
  13. data/features/cyclometric_complexity/if_else_complexity_counting.feature +178 -0
  14. data/features/cyclometric_complexity/loop_complexity_counting.feature +81 -0
  15. data/features/graphing/draw_basic_graph.feature +14 -0
  16. data/features/reporting/report.feature +13 -0
  17. data/features/sample_js_files_for_test/foobar.js +30 -0
  18. data/features/step_definitions/cyclometric_complexity_steps.rb +31 -0
  19. data/features/step_definitions/graph_steps.rb +10 -0
  20. data/features/step_definitions/reporting_steps.rb +14 -0
  21. data/features/support/env.rb +1 -0
  22. data/jsgraphlib/Curry-1.0.1.js +29 -0
  23. data/jsgraphlib/dracula_algorithms.js +599 -0
  24. data/jsgraphlib/dracula_graffle.js +106 -0
  25. data/jsgraphlib/dracula_graph.js +534 -0
  26. data/jsgraphlib/graphtest.html +57 -0
  27. data/jsgraphlib/jquery-1.4.2.min.js +154 -0
  28. data/jsgraphlib/jsgraphsource.js +12 -0
  29. data/jsgraphlib/raphael-min.js +7 -0
  30. data/jsgraphlib/seedrandom.js +266 -0
  31. data/jsmetric.gemspec +23 -0
  32. data/lib/cc_report.rb +24 -0
  33. data/lib/complexity_analyser.rb +90 -0
  34. data/lib/fulljslint.js +6100 -0
  35. data/lib/graphing/graph_analyser.rb +19 -0
  36. data/lib/js_lint.rb +23 -0
  37. data/lib/json2.js +480 -0
  38. data/lib/options.js +24 -0
  39. data/lib/report.rb +26 -0
  40. data/lib/utils.rb +18 -0
  41. data/lib/version.rb +3 -0
  42. data/spec/spec_helper.rb +1 -0
  43. data/tasks/dev.rb +4 -0
  44. data/tasks/run.rb +55 -0
  45. metadata +129 -0
@@ -0,0 +1,599 @@
1
+ /*
2
+ * Various algorithms and data structures, licensed under the MIT-license.
3
+ * (c) 2010 by Johann Philipp Strathausen <strathausen@gmail.com>
4
+ * http://strathausen.eu
5
+ *
6
+ */
7
+
8
+
9
+
10
+ /*
11
+ Bellman-Ford
12
+
13
+ Path-finding algorithm, finds the shortest paths from one node to all nodes.
14
+
15
+
16
+ Complexity
17
+
18
+ O( |E| · |V| ), where E = edges and V = vertices (nodes)
19
+
20
+
21
+ Constraints
22
+
23
+ Can run on graphs with negative edge weights as long as they do not have
24
+ any negative weight cycles.
25
+
26
+ */
27
+ function bellman_ford(g, source) {
28
+
29
+ /* STEP 1: initialisation */
30
+ for(var n in g.nodes)
31
+ g.nodes[n].distance = Infinity;
32
+ /* predecessors are implicitly null */
33
+ source.distance = 0;
34
+
35
+ step("Initially, all distances are infinite and all predecessors are null.");
36
+
37
+ /* STEP 2: relax each edge (this is at the heart of Bellman-Ford) */
38
+ /* repeat this for the number of nodes minus one */
39
+ for(var i = 1; i < g.nodes.length; i++)
40
+ /* for each edge */
41
+ for(var e in g.edges) {
42
+ var edge = g.edges[e];
43
+ if(edge.source.distance + edge.weight < edge.target.distance) {
44
+ step("Relax edge between " + edge.source.id + " and " + edge.target.id + ".");
45
+ edge.target.distance = edge.source.distance + edge.weight;
46
+ edge.target.predecessor = edge.source;
47
+ }
48
+ //Added by Jake Stothard (Needs to be tested)
49
+ if(!edge.style.directed) {
50
+ if(edge.target.distance + edge.weight < edge.source.distance) {
51
+ g.snapShot("Relax edge between "+edge.target.id+" and "+edge.source.id+".");
52
+ edge.source.distance = edge.target.distance + edge.weight;
53
+ edge.source.predecessor = edge.target;
54
+ }
55
+ }
56
+ }
57
+ step("Ready.");
58
+
59
+ /* STEP 3: TODO Check for negative cycles */
60
+ /* For now we assume here that the graph does not contain any negative
61
+ weights cycles. (this is left as an excercise to the reader[tm]) */
62
+ }
63
+
64
+
65
+
66
+ /*
67
+ Path-finding algorithm Dijkstra
68
+
69
+ - worst-case running time is O((|E| + |V|) · log |V| ) thus better than
70
+ Bellman-Ford for sparse graphs (with less edges), but cannot handle
71
+ negative edge weights
72
+ */
73
+ function dijkstra(g, source) {
74
+
75
+ /* initially, all distances are infinite and all predecessors are null */
76
+ for(var n in g.nodes)
77
+ g.nodes[n].distance = Infinity;
78
+ /* predecessors are implicitly null */
79
+
80
+ g.snapShot("Initially, all distances are infinite and all predecessors are null.");
81
+
82
+ source.distance = 0;
83
+ /* set of unoptimized nodes, sorted by their distance (but a Fibonacci heap
84
+ would be better) */
85
+ var q = new BinaryMinHeap(g.nodes, "distance");
86
+
87
+ /* pointer to the node in focus */
88
+ var node;
89
+
90
+ /* get the node with the smallest distance
91
+ as long as we have unoptimized nodes. q.min() can have O(log n). */
92
+ while(q.min() != undefined) {
93
+ /* remove the latest */
94
+ node = q.extractMin();
95
+ node.optimized = true;
96
+
97
+ /* no nodes accessible from this one, should not happen */
98
+ if(node.distance == Infinity)
99
+ throw "Orphaned node!";
100
+
101
+ /* for each neighbour of node */
102
+ for(e in node.edges) {
103
+ var other = (node == node.edges[e].target) ? node.edges[e].source : node.edges[e].target;
104
+
105
+ if(other.optimized)
106
+ continue;
107
+
108
+ /* look for an alternative route */
109
+ var alt = node.distance + node.edges[e].weight;
110
+
111
+ /* update distance and route if a better one has been found */
112
+ if (alt < other.distance) {
113
+
114
+ /* update distance of neighbour */
115
+ other.distance = alt;
116
+
117
+ /* update priority queue */
118
+ q.heapify();
119
+
120
+ /* update path */
121
+ other.predecessor = node;
122
+ g.snapShot("Enhancing node.")
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+
129
+ /* All-Pairs-Shortest-Paths */
130
+ /* Runs at worst in O(|V|³) and at best in Omega(|V|³) :-)
131
+ complexity Sigma(|V|²) */
132
+ /* This implementation is not yet ready for general use, but works with the
133
+ Dracula graph library. */
134
+ function floyd_warshall(g, source) {
135
+
136
+ /* Step 1: initialising empty path matrix (second dimension is implicit) */
137
+ var path = [];
138
+ var next = [];
139
+ var n = g.nodes.length;
140
+
141
+ /* construct path matrix, initialize with Infinity */
142
+ for(j in g.nodes) {
143
+ path[j] = [];
144
+ next[j] = [];
145
+ for(i in g.nodes)
146
+ path[j][i] = j == i ? 0 : Infinity;
147
+ }
148
+
149
+ /* initialize path with edge weights */
150
+ for(e in g.edges)
151
+ path[g.edges[e].source.id][g.edges[e].target.id] = g.edges[e].weight;
152
+
153
+ /* Note: Usually, the initialisation is done by getting the edge weights
154
+ from a node matrix representation of the graph, not by iterating through
155
+ a list of edges as done here. */
156
+
157
+ /* Step 2: find best distances (the heart of Floyd-Warshall) */
158
+ for(k in g.nodes){
159
+ for(i in g.nodes) {
160
+ for(j in g.nodes)
161
+ if(path[i][j] > path[i][k] + path[k][j]) {
162
+ path[i][j] = path[i][k] + path[k][j];
163
+ /* Step 2.b: remember the path */
164
+ next[i][j] = k;
165
+ }
166
+ }
167
+ }
168
+
169
+ /* Step 3: Path reconstruction, get shortest path */
170
+ function getPath(i, j) {
171
+ if(path[i][j] == Infinity)
172
+ throw "There is no path.";
173
+ var intermediate = next[i][j];
174
+ if(intermediate == undefined)
175
+ return null;
176
+ else
177
+ return getPath(i, intermediate)
178
+ .concat([intermediate])
179
+ .concat(getPath(intermediate, j));
180
+ }
181
+
182
+ /* TODO use the knowledge, e.g. mark path in graph */
183
+ }
184
+
185
+ /*
186
+ Ford-Fulkerson
187
+
188
+ Max-Flow-Min-Cut Algorithm finding the maximum flow through a directed
189
+ graph from source to sink.
190
+
191
+
192
+ Complexity
193
+
194
+ O(E * max(f)), max(f) being the maximum flow
195
+
196
+
197
+ Description
198
+
199
+ As long as there is an open path through the residual graph, send the
200
+ minimum of the residual capacities on the path.
201
+
202
+
203
+ Constraints
204
+
205
+ The algorithm works only if all weights are integers. Otherwise it is
206
+ possible that the Ford–Fulkerson algorithm will not converge to the maximum
207
+ value.
208
+
209
+
210
+ Input
211
+
212
+ g - Graph object
213
+ s - Source ID
214
+ t - Target (sink) ID
215
+
216
+
217
+ Output
218
+
219
+ Maximum flow from Source s to Target t
220
+
221
+ */
222
+ /*
223
+ Edmonds-Karp
224
+
225
+ Max-Flow-Min-Cut Algorithm finding the maximum flow through a directed
226
+ graph from source to sink. An implementation of the Ford-Fulkerson
227
+ algorithm.
228
+
229
+
230
+ Complexity
231
+
232
+ O(|V|*|E|²)
233
+
234
+
235
+ Input
236
+
237
+ g - Graph object (with node and edge lists, capacity is a property of edge)
238
+ s - source ID
239
+ t - sink ID
240
+
241
+ */
242
+ function edmonds_karp(g, s, t) {
243
+
244
+ }
245
+
246
+ /*
247
+ A simple binary min-heap serving as a priority queue
248
+ - takes an array as the input, with elements having a key property
249
+ - elements will look like this:
250
+ {
251
+ key: "... key property ...",
252
+ value: "... element content ..."
253
+ }
254
+ - provides insert(), min(), extractMin() and heapify()
255
+ - example usage (e.g. via the Firebug or Chromium console):
256
+ var x = {foo: 20, hui: "bla"};
257
+ var a = new BinaryMinHeap([x,{foo:3},{foo:10},{foo:20},{foo:30},{foo:6},{foo:1},{foo:3}],"foo");
258
+ console.log(a.extractMin());
259
+ console.log(a.extractMin());
260
+ x.foo = 0; // update key
261
+ a.heapify(); // call this always after having a key updated
262
+ console.log(a.extractMin());
263
+ console.log(a.extractMin());
264
+ - can also be used on a simple array, like [9,7,8,5]
265
+ */
266
+ function BinaryMinHeap(array, key) {
267
+
268
+ /* Binary tree stored in an array, no need for a complicated data structure */
269
+ var tree = [];
270
+
271
+ var key = key || 'key';
272
+
273
+ /* Calculate the index of the parent or a child */
274
+ var parent = function(index) { return Math.floor((index - 1)/2); };
275
+ var right = function(index) { return 2 * index + 2; };
276
+ var left = function(index) { return 2 * index + 1; };
277
+
278
+ /* Helper function to swap elements with their parent
279
+ as long as the parent is bigger */
280
+ function bubble_up(i) {
281
+ var p = parent(i);
282
+ while((p >= 0) && (tree[i][key] < tree[p][key])) {
283
+ /* swap with parent */
284
+ tree[i] = tree.splice(p, 1, tree[i])[0];
285
+ /* go up one level */
286
+ i = p;
287
+ p = parent(i);
288
+ }
289
+ }
290
+
291
+ /* Helper function to swap elements with the smaller of their children
292
+ as long as there is one */
293
+ function bubble_down(i) {
294
+ var l = left(i);
295
+ var r = right(i);
296
+
297
+ /* as long as there are smaller children */
298
+ while(tree[l] && (tree[i][key] > tree[l][key]) || tree[r] && (tree[i][key] > tree[r][key])) {
299
+
300
+ /* find smaller child */
301
+ var child = tree[l] ? tree[r] ? tree[l][key] > tree[r][key] ? r : l : l : l;
302
+
303
+ /* swap with smaller child with current element */
304
+ tree[i] = tree.splice(child, 1, tree[i])[0];
305
+
306
+ /* go up one level */
307
+ i = child;
308
+ l = left(i);
309
+ r = right(i);
310
+ }
311
+ }
312
+
313
+ /* Insert a new element with respect to the heap property
314
+ 1. Insert the element at the end
315
+ 2. Bubble it up until it is smaller than its parent */
316
+ this.insert = function(element) {
317
+
318
+ /* make sure there's a key property */
319
+ (element[key] == undefined) && (element = {key:element});
320
+
321
+ /* insert element at the end */
322
+ tree.push(element);
323
+
324
+ /* bubble up the element */
325
+ bubble_up(tree.length - 1);
326
+ }
327
+
328
+ /* Only show us the minimum */
329
+ this.min = function() {
330
+ return tree.length == 1 ? undefined : tree[0];
331
+ }
332
+
333
+ /* Return and remove the minimum
334
+ 1. Take the root as the minimum that we are looking for
335
+ 2. Move the last element to the root (thereby deleting the root)
336
+ 3. Compare the new root with both of its children, swap it with the
337
+ smaller child and then check again from there (bubble down)
338
+ */
339
+ this.extractMin = function() {
340
+ var result = this.min();
341
+
342
+ /* move the last element to the root or empty the tree completely */
343
+ /* bubble down the new root if necessary */
344
+ (tree.length == 1) && (tree = []) || (tree[0] = tree.pop()) && bubble_down(0);
345
+
346
+ return result;
347
+ }
348
+
349
+ /* currently unused, TODO implement */
350
+ this.changeKey = function(index, key) {
351
+ throw "function not implemented";
352
+ }
353
+
354
+ this.heapify = function() {
355
+ for(var start = Math.floor((tree.length - 2) / 2); start >= 0; start--) {
356
+ bubble_down(start);
357
+ }
358
+ }
359
+
360
+ /* insert the input elements one by one only when we don't have a key property (TODO can be done more elegant) */
361
+ for(i in (array || []))
362
+ this.insert(array[i]);
363
+ }
364
+
365
+
366
+
367
+ /*
368
+ Quick Sort:
369
+ 1. Select some random value from the array, the median.
370
+ 2. Divide the array in three smaller arrays according to the elements
371
+ being less, equal or greater than the median.
372
+ 3. Recursively sort the array containg the elements less than the
373
+ median and the one containing elements greater than the median.
374
+ 4. Concatenate the three arrays (less, equal and greater).
375
+ 5. One or no element is always sorted.
376
+ TODO: This could be implemented more efficiently by using only one array object and several pointers.
377
+ */
378
+ function quickSort(arr) {
379
+ /* recursion anchor: one element is always sorted */
380
+ if(arr.length <= 1) return arr;
381
+ /* randomly selecting some value */
382
+ var median = arr[Math.floor(Math.random() * arr.length)];
383
+ var arr1 = [], arr2 = [], arr3 = [];
384
+ for(var i in arr) {
385
+ arr[i] < median && arr1.push(arr[i]);
386
+ arr[i] == median && arr2.push(arr[i]);
387
+ arr[i] > median && arr3.push(arr[i]);
388
+ }
389
+ /* recursive sorting and assembling final result */
390
+ return quickSort(arr1).concat(arr2).concat(quickSort(arr3));
391
+ }
392
+
393
+ /*
394
+ Selection Sort:
395
+ 1. Select the minimum and remove it from the array
396
+ 2. Sort the rest recursively
397
+ 3. Return the minimum plus the sorted rest
398
+ 4. An array with only one element is already sorted
399
+ */
400
+ function selectionSort(arr) {
401
+ /* recursion anchor: one element is always sorted */
402
+ if(arr.length == 1) return arr;
403
+ var minimum = Infinity;
404
+ var index;
405
+ for(var i in arr) {
406
+ if(arr[i] < minimum) {
407
+ minimum = arr[i];
408
+ index = i; /* remember the minimum index for later removal */
409
+ }
410
+ }
411
+ /* remove the minimum */
412
+ arr.splice(index, 1);
413
+ /* assemble result and sort recursively (could be easily done iteratively as well)*/
414
+ return [minimum].concat(selectionSort(arr));
415
+ }
416
+
417
+ /*
418
+ Merge Sort:
419
+ 1. Cut the array in half
420
+ 2. Sort each of them recursively
421
+ 3. Merge the two sorted arrays
422
+ 4. An array with only one element is considered sorted
423
+
424
+ */
425
+ function mergeSort(arr) {
426
+ /* merges two sorted arrays into one sorted array */
427
+ function merge(a, b) {
428
+ /* result set */
429
+ var c = [];
430
+ /* as long as there are elements in the arrays to be merged */
431
+ while(a.length > 0 || b.length > 0){
432
+ /* are there elements to be merged, if yes, compare them and merge */
433
+ var n = a.length > 0 && b.length > 0 ? a[0] < b[0] ? a.shift() : b.shift() : b.length > 0 ? b.shift() : a.length > 0 ? a.shift() : null;
434
+ /* always push the smaller one onto the result set */
435
+ n != null && c.push(n);
436
+ }
437
+ return c;
438
+ }
439
+ /* this mergeSort implementation cuts the array in half, wich should be fine with randomized arrays, but introduces the risk of a worst-case scenario */
440
+ median = Math.floor(arr.length / 2);
441
+ var part1 = arr.slice(0, median); /* for some reason it doesn't work if inserted directly in the return statement (tried so with firefox) */
442
+ var part2 = arr.slice(median - arr.length);
443
+ return arr.length <= 1 ? arr : merge(
444
+ mergeSort(part1), /* first half */
445
+ mergeSort(part2) /* second half */
446
+ );
447
+ }
448
+
449
+ /* Balanced Red-Black-Tree */
450
+ function RedBlackTree(arr) {
451
+
452
+ }
453
+
454
+ function BTree(arr) {
455
+
456
+ }
457
+
458
+ function NaryTree(n, arr) {
459
+
460
+ }
461
+
462
+ /**
463
+ * Knuth-Morris-Pratt string matching algorithm - finds a pattern in a text.
464
+ * FIXME: Doesn't work correctly yet.
465
+ */
466
+ function kmp(p, t) {
467
+
468
+ /**
469
+ * PREFIX, OVERLAP or FALIURE function for KMP. Computes how many iterations
470
+ * the algorithm can skip after a mismatch.
471
+ *
472
+ * @input p - pattern (string)
473
+ * @result array of skippable iterations
474
+ */
475
+ function prefix(p) {
476
+ /* pi contains the computed skip marks */
477
+ var pi = [0], k = 0;
478
+ for(q = 1; q < p.length; q++) {
479
+ while(k > 0 && (p.charAt(k) != p.charAt(q)))
480
+ k = pi[k-1];
481
+
482
+ (p.charAt(k) == p.charAt(q)) && k++;
483
+
484
+ pi[q] = k;
485
+ }
486
+ return pi;
487
+ }
488
+
489
+ /* The actual KMP algorithm starts here. */
490
+
491
+ var pi = prefix(p), q = 0, result = [];
492
+
493
+ for(var i = 0; i < t.length; i++) {
494
+ /* jump forward as long as the character doesn't match */
495
+ while((q > 0) && (p.charAt(q) != t.charAt(i)))
496
+ q = pi[q];
497
+
498
+ (p.charAt(q) == t.charAt(i)) && q++;
499
+
500
+ (q == p.length) && result.push(i - p.length) && (q = pi[q]);
501
+ }
502
+
503
+ return result;
504
+ }
505
+
506
+ /* step for algorithm visualisation */
507
+ function step(comment, funct) {
508
+ //wait for input
509
+ //display comment (before or after waiting)
510
+ // next.wait();
511
+ /* execute callback function */
512
+ funct();
513
+ }
514
+
515
+ /**
516
+ * Curry - Function currying
517
+ * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
518
+ * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
519
+ * Date: 10/4/2008
520
+ *
521
+ * @author Ariel Flesler
522
+ * @version 1.0.1
523
+ */
524
+ function curry( fn ){
525
+ return function(){
526
+ var args = curry.args(arguments),
527
+ master = arguments.callee,
528
+ self = this;
529
+
530
+ return args.length >= fn.length ? fn.apply(self,args) : function(){
531
+ return master.apply( self, args.concat(curry.args(arguments)) );
532
+ };
533
+ };
534
+ };
535
+
536
+ curry.args = function( args ){
537
+ return Array.prototype.slice.call(args);
538
+ };
539
+
540
+ Function.prototype.curry = function(){
541
+ return curry(this);
542
+ };
543
+
544
+ /**
545
+ * Topological Sort
546
+ *
547
+ * Sort a directed graph based on incoming edges
548
+ *
549
+ * Coded by Jake Stothard
550
+ */
551
+ function topological_sort(g) {
552
+ //Mark nodes as "deleted" instead of actually deleting them
553
+ //That way we don't have to copy g
554
+
555
+ for(i in g.nodes)
556
+ g.nodes[i].deleted = false;
557
+
558
+ var ret = topological_sort_helper(g);
559
+
560
+ //Cleanup: Remove the deleted property
561
+ for(i in g.nodes)
562
+ delete g.nodes[i].deleted
563
+
564
+ return ret;
565
+ }
566
+ function topological_sort_helper(g) {
567
+ //Find node with no incoming edges
568
+ var node;
569
+ for(i in g.nodes) {
570
+ if(g.nodes[i].deleted)
571
+ continue; //Bad style, meh
572
+
573
+ var incoming = false;
574
+ for(j in g.nodes[i].edges) {
575
+ if(g.nodes[i].edges[j].target == g.nodes[i]
576
+ && g.nodes[i].edges[j].source.deleted == false) {
577
+ incoming = true;
578
+ break;
579
+ }
580
+ }
581
+ if(!incoming) {
582
+ node = g.nodes[i];
583
+ break;
584
+ }
585
+ }
586
+
587
+ // Either unsortable or done. Either way, GTFO
588
+ if(node == undefined)
589
+ return [];
590
+
591
+ //"Delete" node from g
592
+ node.deleted = true;
593
+
594
+ var tail = topological_sort_helper(g);
595
+
596
+ tail.unshift(node);
597
+
598
+ return tail;
599
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Originally grabbed from the official RaphaelJS Documentation
3
+ * http://raphaeljs.com/graffle.html
4
+ * Adopted (arrows) and commented by Philipp Strathausen http://blog.ameisenbar.de
5
+ * Licenced under the MIT licence.
6
+ */
7
+
8
+ /**
9
+ * Usage:
10
+ * connect two shapes
11
+ * parameters:
12
+ * source shape [or connection for redrawing],
13
+ * target shape,
14
+ * style with { fg : linecolor, bg : background color, directed: boolean }
15
+ * returns:
16
+ * connection { draw = function() }
17
+ */
18
+ Raphael.fn.connection = function (obj1, obj2, style) {
19
+ var selfRef = this;
20
+ /* create and return new connection */
21
+ var edge = {/*
22
+ from : obj1,
23
+ to : obj2,
24
+ style : style,*/
25
+ draw : function() {
26
+ /* get bounding boxes of target and source */
27
+ var bb1 = obj1.getBBox();
28
+ var bb2 = obj2.getBBox();
29
+ var off1 = 0;
30
+ var off2 = 0;
31
+ /* coordinates for potential connection coordinates from/to the objects */
32
+ var p = [
33
+ {x: bb1.x + bb1.width / 2, y: bb1.y - off1}, /* NORTH 1 */
34
+ {x: bb1.x + bb1.width / 2, y: bb1.y + bb1.height + off1}, /* SOUTH 1 */
35
+ {x: bb1.x - off1, y: bb1.y + bb1.height / 2}, /* WEST 1 */
36
+ {x: bb1.x + bb1.width + off1, y: bb1.y + bb1.height / 2}, /* EAST 1 */
37
+ {x: bb2.x + bb2.width / 2, y: bb2.y - off2}, /* NORTH 2 */
38
+ {x: bb2.x + bb2.width / 2, y: bb2.y + bb2.height + off2}, /* SOUTH 2 */
39
+ {x: bb2.x - off2, y: bb2.y + bb2.height / 2}, /* WEST 2 */
40
+ {x: bb2.x + bb2.width + off2, y: bb2.y + bb2.height / 2} /* EAST 2 */
41
+ ];
42
+
43
+ /* distances between objects and according coordinates connection */
44
+ var d = {}, dis = [];
45
+
46
+ /*
47
+ * find out the best connection coordinates by trying all possible ways
48
+ */
49
+ /* loop the first object's connection coordinates */
50
+ for (var i = 0; i < 4; i++) {
51
+ /* loop the seond object's connection coordinates */
52
+ for (var j = 4; j < 8; j++) {
53
+ var dx = Math.abs(p[i].x - p[j].x),
54
+ dy = Math.abs(p[i].y - p[j].y);
55
+ if ((i == j - 4) || (((i != 3 && j != 6) || p[i].x < p[j].x) && ((i != 2 && j != 7) || p[i].x > p[j].x) && ((i != 0 && j != 5) || p[i].y > p[j].y) && ((i != 1 && j != 4) || p[i].y < p[j].y))) {
56
+ dis.push(dx + dy);
57
+ d[dis[dis.length - 1].toFixed(3)] = [i, j];
58
+ }
59
+ }
60
+ }
61
+ var res = dis.length == 0 ? [0, 4] : d[Math.min.apply(Math, dis).toFixed(3)];
62
+ /* bezier path */
63
+ var x1 = p[res[0]].x,
64
+ y1 = p[res[0]].y,
65
+ x4 = p[res[1]].x,
66
+ y4 = p[res[1]].y,
67
+ dx = Math.max(Math.abs(x1 - x4) / 2, 10),
68
+ dy = Math.max(Math.abs(y1 - y4) / 2, 10),
69
+ x2 = [x1, x1, x1 - dx, x1 + dx][res[0]].toFixed(3),
70
+ y2 = [y1 - dy, y1 + dy, y1, y1][res[0]].toFixed(3),
71
+ x3 = [0, 0, 0, 0, x4, x4, x4 - dx, x4 + dx][res[1]].toFixed(3),
72
+ y3 = [0, 0, 0, 0, y1 + dy, y1 - dy, y4, y4][res[1]].toFixed(3);
73
+ /* assemble path and arrow */
74
+ var path = ["M", x1.toFixed(3), y1.toFixed(3), "C", x2, y2, x3, y3, x4.toFixed(3), y4.toFixed(3)].join(",");
75
+ /* arrow */
76
+ if(style && style.directed) {
77
+ /* magnitude, length of the last path vector */
78
+ var mag = Math.sqrt((y4 - y3) * (y4 - y3) + (x4 - x3) * (x4 - x3));
79
+ /* vector normalisation to specified length */
80
+ var norm = function(x,l){return (-x*(l||5)/mag);};
81
+ /* calculate array coordinates (two lines orthogonal to the path vector) */
82
+ var arr = [
83
+ {x:(norm(x4-x3)+norm(y4-y3)+x4).toFixed(3), y:(norm(y4-y3)+norm(x4-x3)+y4).toFixed(3)},
84
+ {x:(norm(x4-x3)-norm(y4-y3)+x4).toFixed(3), y:(norm(y4-y3)-norm(x4-x3)+y4).toFixed(3)}
85
+ ];
86
+ path = path + ",M"+arr[0].x+","+arr[0].y+",L"+x4+","+y4+",L"+arr[1].x+","+arr[1].y;
87
+ }
88
+ /* function to be used for moving existent path(s), e.g. animate() or attr() */
89
+ var move = "attr";
90
+ /* applying path(s) */
91
+ edge.fg && edge.fg[move]({path:path})
92
+ || (edge.fg = selfRef.path(path).attr({stroke: style && style.stroke || "#000", fill: "none"}).toBack());
93
+ edge.bg && edge.bg[move]({path:path})
94
+ || style && style.fill && (edge.bg = style.fill.split && selfRef.path(path).attr({stroke: style.fill.split("|")[0], fill: "none", "stroke-width": style.fill.split("|")[1] || 3}).toBack());
95
+ /* setting label */
96
+ style && style.label
97
+ && (edge.label && edge.label.attr({x:(x1+x4)/2, y:(y1+y4)/2})
98
+ || (edge.label = selfRef.text((x1+x4)/2, (y1+y4)/2, style.label).attr({fill: "#000", "font-size": style["font-size"] || "12px"})));
99
+ // && selfRef.text(x4, y4, style.label).attr({stroke: style && style.stroke || "#fff", "font-weight":"bold", "font-size":"20px"})
100
+ // style && style.callback && style.callback(edge);
101
+ }
102
+ }
103
+ edge.draw();
104
+ return edge;
105
+ };
106
+ //Raphael.prototype.set.prototype.dodo=function(){console.log("works");};