1 /** 2 * Constructs a new spring force with the specified constant. The links 3 * associated with this spring force must be specified before the spring force 4 * can be applied. 5 * 6 * @class Implements a spring force, per Hooke's law. The spring force can be 7 * configured with a tension constant, rest length, and damping factor. The 8 * tension and damping will automatically be normalized using the inverse square 9 * root of the maximum link degree of attached nodes; this makes springs weaker 10 * between nodes of high link degree. 11 * 12 * <p>Unlike other forces (such as charge and drag forces) which may be applied 13 * globally, spring forces are only applied between linked particles. Therefore, 14 * an array of links must be specified before this force can be applied; the 15 * links should be an array of {@link pv.Layout.Network.Link}s. See also 16 * {@link pv.Layout.Force} for an example of using spring and charge forces for 17 * network layout. 18 * 19 * @extends pv.Force 20 * @param {number} k the spring constant. 21 * @see #constant 22 * @see #links 23 */ 24 pv.Force.spring = function(k) { 25 var d = .1, // default damping factor 26 l = 20, // default rest length 27 links, // links on which to apply spring forces 28 kl, // per-spring normalization 29 force = {}; 30 31 if (!arguments.length) k = .1; // default spring constant (tension) 32 33 /** 34 * Sets or gets the links associated with this spring force. Unlike other 35 * forces (such as charge and drag forces) which may be applied globally, 36 * spring forces are only applied between linked particles. Therefore, an 37 * array of links must be specified before this force can be applied; the 38 * links should be an array of {@link pv.Layout.Network.Link}s. 39 * 40 * @function 41 * @name pv.Force.spring.prototype.links 42 * @param {array} x the new array of links. 43 * @returns {pv.Force.spring} this, or the current array of links. 44 */ 45 force.links = function(x) { 46 if (arguments.length) { 47 links = x; 48 kl = x.map(function(l) { 49 return 1 / Math.sqrt(Math.max( 50 l.sourceNode.linkDegree, 51 l.targetNode.linkDegree)); 52 }); 53 return force; 54 } 55 return links; 56 }; 57 58 /** 59 * Sets or gets the spring constant. The default value is 0.1; greater values 60 * will result in stronger tension. The spring tension is automatically 61 * normalized using the inverse square root of the maximum link degree of 62 * attached nodes. 63 * 64 * @function 65 * @name pv.Force.spring.prototype.constant 66 * @param {number} x the new spring constant. 67 * @returns {pv.Force.spring} this, or the current spring constant. 68 */ 69 force.constant = function(x) { 70 if (arguments.length) { 71 k = Number(x); 72 return force; 73 } 74 return k; 75 }; 76 77 /** 78 * The spring damping factor, in the range [0,1]. Damping functions 79 * identically to drag forces, damping spring bounciness by applying a force 80 * in the opposite direction of attached nodes' velocities. The default value 81 * is 0.1. The spring damping is automatically normalized using the inverse 82 * square root of the maximum link degree of attached nodes. 83 * 84 * @function 85 * @name pv.Force.spring.prototype.damping 86 * @param {number} x the new spring damping factor. 87 * @returns {pv.Force.spring} this, or the current spring damping factor. 88 */ 89 force.damping = function(x) { 90 if (arguments.length) { 91 d = Number(x); 92 return force; 93 } 94 return d; 95 }; 96 97 /** 98 * The spring rest length. The default value is 20 pixels. 99 * 100 * @function 101 * @name pv.Force.spring.prototype.length 102 * @param {number} x the new spring rest length. 103 * @returns {pv.Force.spring} this, or the current spring rest length. 104 */ 105 force.length = function(x) { 106 if (arguments.length) { 107 l = Number(x); 108 return force; 109 } 110 return l; 111 }; 112 113 /** 114 * Applies this force to the specified particles. 115 * 116 * @function 117 * @name pv.Force.spring.prototype.apply 118 * @param {pv.Particle} particles particles to which to apply this force. 119 */ 120 force.apply = function(particles) { 121 for (var i = 0; i < links.length; i++) { 122 var a = links[i].sourceNode, 123 b = links[i].targetNode, 124 dx = a.x - b.x, 125 dy = a.y - b.y, 126 dn = Math.sqrt(dx * dx + dy * dy), 127 dd = dn ? (1 / dn) : 1, 128 ks = k * kl[i], // normalized tension 129 kd = d * kl[i], // normalized damping 130 kk = (ks * (dn - l) + kd * (dx * (a.vx - b.vx) + dy * (a.vy - b.vy)) * dd) * dd, 131 fx = -kk * (dn ? dx : (.01 * (.5 - Math.random()))), 132 fy = -kk * (dn ? dy : (.01 * (.5 - Math.random()))); 133 a.fx += fx; 134 a.fy += fy; 135 b.fx -= fx; 136 b.fy -= fy; 137 } 138 }; 139 140 return force; 141 }; 142