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