1 (function(global){	// BEGIN CLOSURE
  2 
  3 var
  4 math = global.Math,
  5 cos = math.cos,
  6 sin = math.sin,
  7 sqrt = math.sqrt,
  8 mmin = math.min,
  9 mmax = math.max,
 10 atan2 = math.atan2,
 11 acos = math.acos,
 12 PI = math.PI;
 13 
 14 var enqueue = function(fnc){
 15     setTimeout(fnc, 0);
 16 };
 17 
 18 var isArray = Array.isArray || function (obj) {
 19     return Object.prototype.toString.call(obj) === '[object Array]';
 20 };
 21 
 22 var isObject = function (obj) {
 23     return Object.prototype.toString.call(obj) === '[object Object]';
 24 };
 25 
 26 if (!global.console){
 27     global.console = {
 28 	log: function(){},
 29 	warn: function(){},
 30 	error: function(){},
 31 	debug: function(){}
 32     };
 33 }
 34 
 35 if (!Array.indexOf){
 36     /**
 37      * Array.indexOf is missing in IE 8.
 38      * @private
 39      */
 40     Array.prototype.indexOf = function (obj, start){
 41 	for (var i = (start || 0), len = this.length; i < len; i++){
 42 	    if (this[i] == obj){
 43 		return i;
 44 	    }
 45 	}
 46 	return -1;
 47     };
 48 }
 49 
 50 /**
 51  * Copies all the properties to the first argument from the following arguments.
 52  * All the properties will be overwritten by the properties from the following
 53  * arguments. Inherited properties are ignored.
 54  * @private
 55  */
 56 var Mixin = function() {
 57     var target = arguments[0];
 58     for (var i = 1, l = arguments.length; i < l; i++){
 59         var extension = arguments[i];
 60         for (var key in extension){
 61             if (!extension.hasOwnProperty(key)){
 62 		continue;
 63 	    }
 64             var copy = extension[key];
 65             if (copy === target[key]){
 66 		continue;
 67 	    }
 68             // copying super with the name base if it does'nt has one already
 69             if (typeof copy == "function" && typeof target[key] == "function" && !copy.base){
 70 		copy.base = target[key];
 71 	    }
 72             target[key] = copy;
 73         }
 74     }
 75     return target;
 76 };
 77 
 78 /**
 79  * Copies all properties to the first argument from the following
 80  * arguments only in case if they don't exists in the first argument.
 81  * All the function propererties in the first argument will get
 82  * additional property base pointing to the extenders same named
 83  * property function's call method.
 84  * @example
 85  * // usage of base
 86  * Bar.extend({
 87  * // function should have name
 88  * foo: function foo(digit) {
 89  * return foo.base(this, parseInt(digit))
 90  * }
 91  * });
 92  * @private
 93  */
 94 var Supplement = function() {
 95     var target = arguments[0];
 96     for (var i = 1, l = arguments.length; i < l; i++){
 97         var extension = arguments[i];
 98         for (var key in extension) {
 99             var copy = extension[key];
100             if (copy === target[key]){
101 		continue;
102 	    }
103             // copying super with the name base if it does'nt has one already
104             if (typeof copy == "function" && typeof target[key] == "function" && !target[key].base){
105 		target[key].base = copy;
106 	    }
107             // target doesn't has propery that is owned by extension copying it
108             if (!target.hasOwnProperty(key) && extension.hasOwnProperty(key)){
109 		target[key] = copy;
110 	    }
111         }
112     }
113     return target;
114 };
115 
116 /**
117  * Copies all the properties to the first argument from the following arguments.
118  * All the properties will be overwritten by the properties from the following
119  * arguments. Inherited properties are ignored.
120  * Deep version.
121  * @private
122  */
123 var DeepMixin = function() {
124     var target = arguments[0];
125     for (var i = 1, l = arguments.length; i < l; i++) {
126         var extension = arguments[i];
127         for (var key in extension) {
128             var copy = extension[key];
129             if (copy === target[key]) continue;
130             if (isObject(copy)) DeepMixin((target[key] || (target[key] = {})), copy);
131             // copying super with the name base if it does'nt has one already
132             if (typeof copy == 'function' && typeof target[key] == 'function' && !target[key].base) {
133                 target[key].base = copy;
134             }
135 	    target[key] = copy;
136         }
137     }
138     return target;
139 };
140 
141 /**
142  * Copies all properties to the first argument from the following
143  * arguments only in case if they don't exists in the first argument.
144  * All the function propererties in the first argument will get
145  * additional property base pointing to the extenders same named
146  * property function's call method.
147  * @example
148  * // usage of base
149  * Bar.extend({
150  * // function should have name
151  * foo: function foo(digit) {
152  * return foo.base(this, parseInt(digit))
153  * }
154  * });
155  * Deep version.
156  * @private
157  */
158 var DeepSupplement = function() {
159     var target = arguments[0];
160     for (var i = 1, l = arguments.length; i < l; i++) {
161         var extension = arguments[i];
162         for (var key in extension) {
163             var copy = extension[key];
164             if (copy === target[key]) continue;
165             if (isObject(copy)) DeepSupplement((target[key] || (target[key] = {})), copy);
166             // copying super with the name base if it does'nt has one already
167             if (typeof copy == 'function' && typeof target[key] == 'function' && !target[key].base) {
168                 target[key].base = copy;
169             }
170             // target doesn't has propery that is owned by extension copying it
171             if (!target.hasOwnProperty(key) && extension.hasOwnProperty(key)){
172 		target[key] = copy;
173 	    }
174         }
175     }
176     return target;
177 };
178 
179 
180 /**
181  * @name Joint
182  * @constructor
183  * @param {RaphaelObject|Shape|object} from Object/position where the connection starts.
184  * @param {RaphaelObject|Shape|object} to Object/position where the connection ends.
185  * @param {object} [opts] opt Options
186  * @param {object} [opts.interactive] Is the joint interactive? [default = true]
187  * @param {object} [opts.attrs] Connection options (see  Raphael possible parameters)
188  * @param {string} [opts.cursor] Connection CSS cursor property
189  * @param {boolean} [opts.beSmooth] Connection enable/disable smoothing
190  * @param {string|array} [opts.label] Connection label(s)
191  * @param {object|array} [opts.labelAttrs] Label(s) options (see  Raphael possible parameters)  + position attribute (<0, [0, 1], >1)
192  * @param {object|array} [opts.labelBoxAttrs] SVG Attributes of the label(s) bounding rectangle + padding attribute
193  * @param {object} [opts.startArrow] Start arrow options
194  * @param {string} [opts.startArrow.type] "none"|"basic"
195  * @param {number} [opts.startArrow.size] Start arrow size
196  * @param {object} [opts.startArrow.attrs] Start Arrow options (see  Raphael possible parameters)
197  * @param {object} [opts.endArrow] End arrow options
198  * @param {string} [opts.endArrow.type] "none"|"basic"
199  * @param {number} [opts.endArrow.size] End arrow size
200  * @param {object} [opts.endArrow.attrs] End Arrow options (see  Raphael possible parameters)
201  * @param {object} [opts.dummy] Dummy node options (shows when dragging arrows)
202  * @param {object} [opts.dummy.start] Start dummy node options
203  * @param {number} [opts.dummy.start.radius] Start dummy radius
204  * @param {object} [opts.dummy.start.attrs] Start dummy options (see  Raphael possible parameters)
205  * @param {object} [opts.dummy.end] End dummy node options
206  * @param {number} [opts.dummy.end.radius] End dummy radius
207  * @param {object} [opts.dummy.end.attrs] End dummy options (see  Raphael possible parameters)
208  * @param {object} [opts.handle] Handle options
209  * @param {number} [opts.handle.timeout] Number of milliseconds handle stays shown
210  * @param {object} [opts.handle.start] Start handle options
211  * @param {boolean} [opts.handle.start.enabled] Start handle enabled/disabled
212  * @param {number} [opts.handle.start.radius] Start handle radius
213  * @param {object} [opts.handle.start.attrs] Start handle attributes (see  Raphael possible parameters)
214  * @param {object} [opts.handle.end] End handle options
215  * @param {boolean} [opts.handle.end.enabled] End handle enabled/disabled
216  * @param {number} [opts.handle.end.radius] End handle radius
217  * @param {object} [opts.handle.end.attrs] End handle attributes (see  Raphael possible parameters)
218  * @param {object} [opts.bboxCorrection] Correction of a bounding box (useful when, e.g., the connection should start in the center of an object)
219  * @param {object} [opts.bboxCorrection.start] BBox correction of the start object.
220  * @param {string} [opts.bboxCorrection.start.type] "ellipse"|"rect"
221  * @param {number} [opts.bboxCorrection.start.x] Translation in the x-axis
222  * @param {number} [opts.bboxCorrection.start.y] Translation in the y-axis
223  * @param {number} [opts.bboxCorrection.start.width] BBox width
224  * @param {number} [opts.bboxCorrection.start.height] BBox height
225  * @param {object} [opts.bboxCorrection.end] BBox correction of the end object.
226  * @param {string} [opts.bboxCorrection.end.type] "ellipse"|"rect"
227  * @param {number} [opts.bboxCorrection.end.x] Translation in the x-axis
228  * @param {number} [opts.bboxCorrection.end.y] Translation in the y-axis
229  * @param {number} [opts.bboxCorrection.end.width] BBox width
230  * @param {number} [opts.bboxCorrection.end.height] BBox height
231  * @example
232  * Joint({x: 10, y: 10}, {x: 300, y: 100}, {
233  *  label: "my label",
234  *  beSmooth: true,
235  *  startArrow: {
236  *    type: "basic",
237  *    size: 7,
238  *    attrs: {
239  *      fill: "red",
240  *      stroke: "blue"
241  *    }
242  *  },
243  *  handle: {
244  *    timeout: 4000,
245  *    start: {
246  *      radius: 6,
247  *      attrs: {
248  *        fill: "green",
249  *        stroke: "black"
250  *      }
251  *    },
252  *    end: {
253  *      radius: 4,
254  *      attrs: {
255  *        fill: "red",
256  *        stroke: "black"
257  *      }
258  *    }
259  *  }
260  * });
261  */
262 function Joint(from, to, opt){
263     if (!(this instanceof Joint)){
264 	return new Joint(from, to, opt);
265     }
266     /**
267      * @private
268      * @type RaphaelPaper
269      */
270     var paper = this.paper = Joint.paper();
271 
272     // these objects are the ones I can connect to
273     this._registeredObjects = [];
274 
275     this._conVerticesCurrentIndex = 0;
276     this._nearbyVertexSqrDist = 500;	// sqrt(this._nearbyVertexSqrDist) is tolerable distance of vertex moving
277 
278     this.dom = {};	// holds all dom elements
279 
280     // connection from start to end
281     this._start = { // start object
282 	shape: null,		// Raphael object
283 	dummy: false		// is it a dummy object?
284     };
285     this._end = { // end object
286 	shape: null,		// Raphael object
287 	dummy: false		// is it a dummy object?
288     };
289 
290     // connection options
291     this._opt = {
292 	vertices: [],	// joint path vertices
293 	attrs: {
294 	    "stroke": "#000",
295 	    //	    "fill": "#fff",	// can not be used if connection wiring is enabled
296 	    "fill-opacity": 0.0,
297 	    "stroke-width": 1,
298 	    "stroke-dasharray": "-",
299 	    "stroke-linecap": "round", // butt/square/round/mitter
300 	    "stroke-linejoin": "round", // butt/square/round/mitter
301 	    "stroke-miterlimit": 1,
302 	    "stroke-opacity": 1.0
303 	},
304 	cursor: "move",	// CSS cursor property
305 	beSmooth: false,// be a smooth line? (bezier curve aproximation)
306 	interactive: true, // is the connection interactive?
307 	label: undefined,
308 	labelAttrsDefault: {
309             position: 1/2,
310             offset: 0,  // y-offset
311 	    "font-size": 12,
312 	    "fill": "#000"
313 	},
314         labelAttrs: [],
315 	labelBoxAttrsDefault: { stroke: "white", fill: "white" },
316         labelBoxAttrs: [],
317 	// bounding box correction
318 	// (useful when the connection should start in the center of an object, etc...)
319 	bboxCorrection: {
320 	    start: { type: null, x: 0, y: 0, width: 0, height: 0 },
321 	    end: { type: null, x: 0, y: 0, width: 0, height: 0 }
322 	},
323 	// dummy nodes radius and SVG attributes
324 	dummy: {
325 	    start: {
326 		radius: 1,
327 		attrs: {"opacity": 0.0, "fill": "red"}
328 	    },
329 	    end: {
330 		radius: 1,
331 		attrs: {"opacity": 0.0, "fill": "yellow"}
332 	    }
333 	},
334 	// handles (usefull when picking "none" type of arrow)
335 	handle: {
336 	    timeout: 2000,
337 	    start: {
338 		enabled: false,
339 		radius: 4,
340 		attrs: { opacity: 1.0, fill: "red", stroke: "black" }
341 	    },
342 	    end: {
343 		enabled: false,
344 		radius: 4,
345 		attrs: { opacity: 1.0, fill: "red", stroke: "black" }
346 	    }
347 	}
348     };
349     // used arrows (default values)
350     this._opt.arrow = {
351 	start: Joint.getArrow("none", 2, this._opt.attrs),
352 	end: Joint.getArrow("basic", 5)
353     };
354     // options
355     if (opt) this.processOptions(opt);
356 
357     JointDOMBuilder.init(this.paper, this._opt, this._start, this._end);
358 
359     var startObject = this._start,
360         endObject = this._end;
361 
362     if (from.x && from.y)	// from is point?
363 	JointDOMBuilder.dummy(startObject, from, this._opt.dummy.start);
364     else
365 	startObject.shape = from.yourself();
366 
367     if (to.x && to.y)		// to is point?
368 	JointDOMBuilder.dummy(endObject, to, this._opt.dummy.end);
369     else
370 	endObject.shape = to.yourself();
371 
372     // to be able to dispatch events in Raphael element attr method
373     // TODO: possible source of memory leaks!!!
374     this.addJoint(startObject.shape);
375     this.addJoint(endObject.shape);
376     // draw
377     this.update();
378 }
379 global.Joint = Joint;	// the only global variable
380 
381 Joint.euid = 1;	// elements/joints unique id
382 /**
383  * Assign unique id to this.
384  * @private
385  * @example Joint.generateEuid.call(obj);
386  */
387 Joint.generateEuid = function(){
388     if (this._euid === undefined) this._euid = Joint.euid++;
389     return this._euid;
390 };
391 
392 /**
393  * @private
394  */
395 Joint.prototype = {
396     // temporaries for moving objects
397     _dx: undefined,
398     _dy: undefined,
399     /*
400      * States.
401      */
402     IDLE: 0,
403     STARTCAPDRAGGING: 1,
404     ENDCAPDRAGGING: 2,
405     CONNECTIONWIRING: 3,
406     state: 0,	// IDLE
407     /*
408      * Callbacks.
409      * @name Callbacks
410      */
411     _callbacks: {
412 	// called when a joint has just connected to an object
413 	// the object is accessed using this,
414 	// the only argument is what side has been connected ("start" | "end")
415 	justConnected: function(side){},
416 	disconnected: function(side){},
417 	justBroken: function(mousePos){},
418 	wiring: function(mousePos){},
419 	objectMoving: function(obj){}
420     },
421     /**
422      * @return {String} Joint unique identifier.
423      */
424     euid: function(){
425 	return Joint.generateEuid.call(this);
426     },
427     /*
428      * Getters.
429      */
430     connection: function(){ return this.dom.connection[0]; },
431     endObject: function(){ return this._end.shape; },
432     startObject: function(){ return this._start.shape; },
433     endCap: function(){ return this.dom.endCap; },
434     endCapConnected: function(){ return !this._end.dummy; },
435     startCap: function(){ return this.dom.startCap; },
436     startCapConnected: function(){ return !this._start.dummy; },
437     isStartDummy: function(){ return this._start.dummy; },
438     isEndDummy: function(){ return this._end.dummy; },
439     /**
440      * Replaces dummy object with a new object.
441      * @private
442      * @param {object} startOrEnd start or end object (old dummy)
443      * @param {RaphaelObject} o
444      */
445     replaceDummy: function(startOrEnd, o){
446 	// assert(startOrEnd.dummy == true)
447 	startOrEnd.shape.remove();
448 	startOrEnd.dummy = false;
449 	startOrEnd.shape = o;
450     },
451     /**
452      * Calls a callback.
453      * @private
454      * @param fnc Callback 
455      * @param {object} scope Scope of the callback
456      * @param {array} args Array of arguments
457      */
458     callback: function(fnc, scope, args){
459 	this._callbacks[fnc].apply(scope, args);
460         return this;
461     },
462     /**
463      * Search the registered objects and get the one (if any)
464      * who's bounding box contains the point p.
465      * @todo check document.elementFromPoint(x, y)
466      * @private
467      * @param {Point} p
468      */
469     objectContainingPoint: function(p){
470 	var register = this._registeredObjects,
471 	    idx = (register ? register.length : 0), o;
472 	while (idx--){
473 	    o = register[idx].yourself();
474 	    if (rect(o.getBBox()).containsPoint(p))
475 		return o;
476 	}
477 	return null;
478     },
479     /**
480      * Remove reference to Joint from obj.
481      * @private
482      * @param {StartObject|EndObject} obj
483      */
484     freeJoint: function(obj){
485 	var jar = obj.yourself().joints(),
486 	    i = jar.indexOf(this);
487 	jar.splice(i, 1);
488 	return this;
489     },
490     /**
491      * Add reference to Joint to obj.
492      * @private
493      * @param {RaphaelObject} obj
494      */
495     addJoint: function(obj){
496 	var joints = obj.joints();;
497 	// push the Joint object into obj.joints array
498 	// but only if obj.joints already doesn't have that Joint object
499 	if (joints.indexOf(this) === -1) joints.push(this);
500     },
501     /**
502      * MouseDown event callback when on cap.
503      * @private
504      * @param {Event} e
505      * @param {RaphaelObject} cap
506      */
507     capMouseDown: function(e, cap){
508 	Joint.currentJoint = this;	// keep global reference to me
509 	this._dx = e.clientX;
510 	this._dy = e.clientY;
511 
512 	if (cap === this.dom.startCap){
513             this.disconnect("start");
514 	    this.state = this.STARTCAPDRAGGING;
515 	} else if (cap === this.dom.endCap){
516             this.disconnect("end");
517 	    this.state = this.ENDCAPDRAGGING;
518 	}
519     },
520     /**
521      * MouseDown event callback when on connection.
522      * @private
523      * @param {Event} e
524      */
525     connectionMouseDown: function(e){
526 	Joint.currentJoint = this;	// keep global reference to me
527 	var mousePos = Joint.getMousePosition(e, this.paper.canvas);
528 
529 	// if the mouse position is nearby a connection vertex
530 	// do not create a new one but move the selected one instead
531 	for (var i = 0, len = this._opt.vertices.length; i < len; i++){
532 	    var v = this._opt.vertices[i];
533 	    if (line(v, mousePos).squaredLength() < this._nearbyVertexSqrDist){
534 		this._conVerticesCurrentIndex = i;
535 		this.state = this.CONNECTIONWIRING;
536 		return;
537 	    }
538 	}
539 
540 	// new vertices can be added CORRECTLY only at the end
541 	// or at the start of the connection
542 	// -> @todo
543 	var sbbCenter = rect(this.startObject().getBBox()).center(),
544 	    ebbCenter = rect(this.endObject().getBBox()).center(),
545 	    // squared lengths of the lines from the center of
546 	    // start/end object bbox to the mouse position
547 	    smLineSqrLen = line(sbbCenter, mousePos).squaredLength(),
548 	    emLineSqrLen = line(ebbCenter, mousePos).squaredLength();
549 	if (smLineSqrLen < emLineSqrLen){
550 	    // new vertex is added to the beginning of the vertex array
551 	    this._conVerticesCurrentIndex = 0;
552 	    this._opt.vertices.unshift(mousePos);
553 	} else {
554 	    // new vertex is added to the end of the vertex array
555 	    this._conVerticesCurrentIndex = this._opt.vertices.push(mousePos) - 1;
556 	}
557 	this.state = this.CONNECTIONWIRING;
558 	this.callback("justBroken", this, [mousePos]);
559     },
560     capDragging: function(e){
561 	// move dummy object
562 	if (this.state === this.STARTCAPDRAGGING){
563 	    this.startObject().translate(e.clientX - this._dx, e.clientY - this._dy);
564 	} else if (this.state === this.ENDCAPDRAGGING) {
565 	    this.endObject().translate(e.clientX - this._dx, e.clientY - this._dy);
566 	} else {
567 	    return;	// should not happen
568 	}
569 	this._dx = e.clientX;
570 	this._dy = e.clientY;
571 
572 	this.update();
573     },
574     capEndDragging: function(){
575 	var dummyBB, 
576 	    STARTCAPDRAGGING = (this.state === this.STARTCAPDRAGGING),
577 	    ENDCAPDRAGGING = (this.state === this.ENDCAPDRAGGING),
578 	    capType = (STARTCAPDRAGGING) ? "start" : "end";
579 
580         
581 	if (STARTCAPDRAGGING){
582             dummyBB = this.startObject().getBBox();	    
583 	} else if (ENDCAPDRAGGING){
584 	    dummyBB = this.endObject().getBBox();
585 	}
586         
587 	var o = this.objectContainingPoint(point(dummyBB.x, dummyBB.y));
588 	if (o){
589 	    this.callback("justConnected", o, [capType]);
590 	    this.replaceDummy(this["_" + capType], o);
591 	    this.addJoint(o);
592 	}
593 
594 	this.update();
595     },
596     connectionWiring: function(e){
597 	var mousePos = Joint.getMousePosition(e, this.paper.canvas);
598 	this._opt.vertices[this._conVerticesCurrentIndex] = mousePos;
599 	this.update();
600 	this.callback("wiring", this, [mousePos]);
601     },
602     update: function(){
603 	this.redraw().listenAll();
604 	// setTimeout makes drawing much faster!
605 //	var self = this;
606 //	enqueue(function(){self.redraw().listenAll();});
607     },
608     redraw: function(){
609 	this.clean(["connection", "startCap", "endCap", "handleStart", "handleEnd", "label"]);
610 	this.draw(["connection", "startCap", "endCap", "handleStart", "handleEnd", "label"]);
611 	return this;
612     },
613     listenAll: function(){
614 	if (!this._opt.interactive){
615 	    return this;
616 	}
617 	var self = this;
618 	this.dom.startCap.mousedown(function(e){
619 		           Joint.fixEvent(e);
620 			   self.capMouseDown(e, self.dom.startCap);
621 			   e.stopPropagation();
622 			   e.preventDefault();
623         });
624 	this.dom.endCap.mousedown(function(e){
625 		           Joint.fixEvent(e);
626 			   self.capMouseDown(e, self.dom.endCap);
627 			   e.stopPropagation();
628 			   e.preventDefault();
629 	});
630         var i;
631         i = this.dom.connection.length;
632         while (i--) {
633 	    this.dom.connection[i].mousedown(function(e){
634                 Joint.fixEvent(e);
635 		self.connectionMouseDown(e);
636 		e.stopPropagation();
637 		e.preventDefault();
638             });
639         }
640 	if (this._opt.handle.start.enabled){
641 	    this.dom.handleStart.mousedown(function(e){
642 			       Joint.fixEvent(e);
643 			       self.capMouseDown(e, self.dom.startCap);
644 			       e.stopPropagation();
645 			       e.preventDefault();
646 	    });
647 	}
648 	if (this._opt.handle.end.enabled){
649 	    this.dom.handleEnd.mousedown(function(e){
650 			       Joint.fixEvent(e);
651 			       self.capMouseDown(e, self.dom.endCap);
652 			       e.stopPropagation();
653 			       e.preventDefault();
654 	    });
655 	}
656 	if (this._opt.handle.timeout !== Infinity){
657             i = this.dom.connection.length;
658             while (i--) {
659 	       this.dom.connection[i].mouseover(function(e){
660 	           Joint.fixEvent(e);
661 		   self.showHandle();
662 	           setTimeout(function(){
663 		       self.hideHandle();
664 		   }, self._opt.handle.timeout);
665 		   e.stopPropagation();
666 		   e.preventDefault();
667 	        });
668             }
669 	}
670 	return this;
671     },
672     /**
673      * @private
674      */
675     boundPoint: function(bbox, type, rotation, p){
676 	if (type === "circle" || type === "ellipse")
677 	    return ellipse(bbox.center(), bbox.width/2, bbox.height/2).intersectionWithLineFromCenterToPoint(p);
678         else if (type === 'rect' && bbox.width == bbox.height && rotation != 0) {
679             // compute new bounding box of a rotated square
680             // @todo Compute intersection properly
681             var w = bbox.width,
682                 dia = Math.sqrt(w*w + w*w),
683                 origin = bbox.center().offset(-dia/2, -dia/2);
684             bbox = rect(origin.x, origin.y, dia, dia);
685             return bbox.boundPoint(p) || bbox.center();
686         }
687 	return bbox.boundPoint(p) || bbox.center();
688     },
689     /**
690      * @private
691      * @param {object} start
692      * @param {rect} start.bbox Start object bounding box.
693      * @param {string} start.type Start object geometrical type.
694      * @param {point} start.shift Start arrow offsets.
695      * @param {object} end
696      * @param {rect} end.bbox End object bounding box.
697      * @param {string} end.type End object geometrical type.
698      * @param {point} end.shift End arrow offsets.
699      * @param {array} vertices Connection vertices.
700      * @return {object} Object containing location of start/end of the joint.
701      */
702     jointLocation: function(start, end, vertices){
703 	var verticesLength = vertices.length, theta,
704         firstVertex = (vertices.length ? vertices[0] : undefined),
705         lastVertex = (vertices.length ? vertices[verticesLength - 1] : undefined),
706         p1, p1bp, c1t, c1r, p2, p2bp, c2t, c2r;
707 
708 	// start object boundary point
709 	p1bp = this.boundPoint(start.bbox, start.type, start.rotation, firstVertex || end.bbox.center());
710 	// shift
711 	theta = start.bbox.center().theta(firstVertex || end.bbox.center());
712 	// first point of the connection
713 	p1 = point(p1bp.x + (2 * start.shift.dx * cos(theta.radians)),
714 		   p1bp.y + (-2 * start.shift.dy * sin(theta.radians)));
715 	// start arrow translation
716 	c1t = point(p1bp.x + start.shift.dx * cos(theta.radians),
717 		    p1bp.y - start.shift.dy * sin(theta.radians));
718 	// start arrow rotation
719 	c1r = 360 - theta.degrees + 180;
720 
721 	// end object boundary point
722 	p2bp = this.boundPoint(end.bbox, end.type, end.rotation, lastVertex || start.bbox.center());
723 	// shift
724 	theta = (lastVertex || start.bbox.center()).theta(end.bbox.center());
725 	// last point of the connection
726 	p2 = point(p2bp.x + (-2 * end.shift.dx * cos(theta.radians)),
727 		   p2bp.y + (2 * end.shift.dy * sin(theta.radians)));
728 	// end arrow translation
729 	c2t = point(p2bp.x - end.shift.dx * cos(theta.radians),
730 	            p2bp.y + end.shift.dy * sin(theta.radians));
731 	// end arrow rotation
732 	c2r = 360 - theta.degrees;
733 
734 	return {
735 	    start: {
736 		bound: p1bp,
737 		connection: p1,
738 		translate: c1t,
739 		rotate: c1r
740 	    },
741 	    end: {
742 		bound: p2bp,
743 		connection: p2,
744 		translate: c2t,
745 		rotate: c2r
746 	    }
747 	};
748     },
749     /**
750      * @private
751      * @param {point} start Joint start location.
752      * @param {point} end Joint end location.
753      * @param {array} vertices Connection vertices.
754      * @param {boolean} smooth Connection smooth flag.
755      * @return {array} SVG path commands.
756      */
757     connectionPathCommands: function(start, end, vertices, smooth){
758 	if (smooth)
759 	    return Bezier.curveThroughPoints([start].concat(vertices, [end]));
760 	var commands = ["M", start.x, start.y], i = 0, l = vertices.length;
761 	for (; i < l; i++)
762 	    commands.push("L", vertices[i].x, vertices[i].y);
763 	commands.push("L", end.x, end.y);
764 	return commands;
765     },
766 
767     /**
768      * @private
769      * @param {point} start Joint start location.
770      * @param {point} end Joint end location.
771      * @param {array} vertices Connection vertices.
772      * @return {array} Locations of the label (array of points).
773      */
774     labelLocation: function(connectionPathCommands){
775         var path = this.paper.path(connectionPathCommands.join(' ')),
776             length = path.getTotalLength(),
777             locations = [], attrs = this._opt.labelAttrs, len = attrs.length, i = 0,
778             position;
779         for (; i < len; i++) {
780             position = attrs[i].position;
781             position = (position > length) ? length : position; // sanity check
782             position = (position < 0) ? length + position : position;
783             position = position > 1 ? position : length * position;
784             locations.push(path.getPointAtLength(position));
785         }
786         path.remove();
787         return locations;
788     },
789 
790     /**
791      * @private
792      */
793     draw: function(components){
794 	var self = this,
795 	    paper = this.paper,
796 	    jointLocation = this.jointLocation(
797 	        {
798 	            bbox: rect(this.startObject().getBBox()).moveAndExpand(this._opt.bboxCorrection.start),
799 		    type: this.startObject().type,
800                     rotation: this.startObject().attrs.rotation,
801 		    shift: this._opt.arrow.start
802 	        },
803 	        {
804 	            bbox: rect(this.endObject().getBBox()).moveAndExpand(this._opt.bboxCorrection.end),
805 		    type: this.endObject().type,
806                     rotation: this.endObject().attrs.rotation,
807 		    shift: this._opt.arrow.end
808 	        },
809 	        this._opt.vertices
810 	    ),
811             connectionPathCommands = this.connectionPathCommands(
812 		jointLocation.start.connection,
813 		jointLocation.end.connection,
814 		this._opt.vertices,
815 		this._opt.beSmooth
816 	    ),
817 	    labelLocation = this.labelLocation(connectionPathCommands),
818 	    dom = JointDOMBuilder.init(this.paper, this._opt, this._start, this._end, jointLocation, connectionPathCommands, labelLocation),
819 	    l = components.length,
820 	    component;
821 
822 	for (var i = 0; i < l; i++){
823 	    component = components[i];
824 	    this.dom[component] = dom[component]();
825 	}
826     },
827     /**
828      * Clean operations.
829      * Remove the DOM elements of connection/startCap/endCap/label if they exist.
830      * Clean operations support chaining.
831      * @private
832      */
833     clean: function(components){
834 	var component, name, subComponents, idx = components.length;
835 	while (idx--){
836 	    name = components[idx];
837 	    component = this.dom[name];
838 	    if (component){
839 		if (component.node){
840 		    component.remove();
841 		    this.dom[name] = null;
842 		} else {  // component is a composite object
843 		    subComponents = component;
844 		    for (var key in subComponents){
845 			if (subComponents.hasOwnProperty(key))
846 			    subComponents[key].remove();
847 		    }
848 		}
849 		this.dom[name] = null;
850 	    }
851 	}
852     },
853 
854     /**
855      * Process options.
856      * @todo Please fix me! I look like spagethi.
857      * @private
858      * @param {object} opt
859      */
860     processOptions: function(opt){
861 	var key, myopt = this._opt,
862             topOptions = ['interactive', 'cursor', 'beSmooth'], i = topOptions.length;
863         
864         // top options
865         while (i--) {
866             if (opt[topOptions[i]] !== undefined)
867                 myopt[topOptions[i]] = opt[topOptions[i]];
868         }
869 
870         myopt.subConnectionAttrs = opt.subConnectionAttrs || undefined;
871 
872 	Mixin(myopt.attrs, opt.attrs);
873         Mixin(myopt.bboxCorrection.start, opt.bboxCorrection && opt.bboxCorrection.start);
874         Mixin(myopt.bboxCorrection.end, opt.bboxCorrection && opt.bboxCorrection.end);
875         if (opt.vertices) this._setVertices(opt.vertices);
876 
877         // label(s) related options
878 	if (opt.label) {
879             myopt.label = isArray(opt.label) ? opt.label : [opt.label];
880             if (!isArray(opt.labelAttrs)) opt.labelAttrs = [opt.labelAttrs];
881             for (i = 0; i < myopt.label.length; i++) {
882                 Supplement(opt.labelAttrs[i] || (opt.labelAttrs[i] = {}), myopt.labelAttrsDefault);
883             }
884 	    myopt.labelAttrs = opt.labelAttrs;      // make a copy? (parse(stringify(opt)))
885 
886             var spread = undefined;
887             if (!isArray(opt.labelBoxAttrs)) {
888                 if (typeof opt.labelBoxAttrs === 'object')
889                     spread = opt.labelBoxAttrs;
890                 opt.labelBoxAttrs = [opt.labelBoxAttrs];
891             }
892             for (i = 0; i < myopt.label.length; i++) {
893                 if (spread) opt.labelBoxAttrs[i] = spread;
894                 Supplement(opt.labelBoxAttrs[i] || (opt.labelBoxAttrs[i] = {}), this._opt.labelBoxAttrsDefault);
895             }
896 	    myopt.labelBoxAttrs = opt.labelBoxAttrs;      // make a copy? (parse(stringify(opt)))
897 	}
898 
899         // arrowheads
900 	var sa = opt.startArrow, ea = opt.endArrow;
901 	if (sa && sa.type) myopt.arrow.start = Joint.getArrow(sa.type, sa.size, sa.attrs);
902 	if (ea && ea.type) myopt.arrow.end = Joint.getArrow(ea.type, ea.size, ea.attrs);
903 	// direct arrow specification rewrites types
904 	if (opt.arrow) {
905             myopt.arrow.start = opt.arrow.start || myopt.arrow.start;
906             myopt.arrow.end = opt.arrow.end || myopt.arrow.end;
907 	}
908 
909         // dummies
910 	if (opt.dummy && opt.dummy.start) {
911 	    if (opt.dummy.start.radius) myopt.dummy.start.radius = opt.dummy.start.radius;
912             Mixin(myopt.dummy.start.attrs, opt.dummy.start.attrs);
913         }
914 	if (opt.dummy && opt.dummy.end) {
915 	    if (opt.dummy.end.radius) myopt.dummy.end.radius = opt.dummy.end.radius;
916             Mixin(myopt.dummy.end.attrs, opt.dummy.end.attrs);
917 	}
918         // handles
919 	if (opt.handle){
920 	    if (opt.handle.timeout) myopt.handle.timeout = opt.handle.timeout;
921 	    if (opt.handle.start){
922 		if (opt.handle.start.enabled) myopt.handle.start.enabled = opt.handle.start.enabled;
923 		if (opt.handle.start.radius) myopt.handle.start.radius = opt.handle.start.radius;
924                 Mixin(myopt.handle.start.attrs, opt.handle.start.attrs);
925 	    }
926 	    if (opt.handle.end){
927 		if (opt.handle.end.enabled) myopt.handle.end.enabled = opt.handle.end.enabled;
928 		if (opt.handle.end.radius) myopt.handle.end.radius = opt.handle.end.radius;
929                 Mixin(myopt.handle.end.attrs, opt.handle.end.attrs);
930 	    }
931 	}
932     },
933     // Public API
934 
935     /**
936      * Disconnects joint from objects.
937      * @param {string} cap "start|end|both" which side to disconnect
938      * @return {Joint} return this to allow chaining
939      */
940     disconnect: function(cap){
941         var disconnectedFrom, camelCap = (cap === "start")
942                                           ? "Start"
943                                           : (cap === "end"
944                                              ? "End"
945                                              : "Both");
946 
947         if (cap === "both" || cap === undefined){
948             this.freeJoint(this.startObject())
949                 .freeJoint(this.endObject());
950             
951             if (!this.isStartDummy()){
952                 disconnectedFrom = this.startObject();
953                 this.draw(["dummyStart"]);
954 	        this.callback("disconnected", disconnectedFrom, [cap]);
955             }
956             if (!this.isEndDummy()){
957                 disconnectedFrom = this.endObject();
958                 this.draw(["dummyEnd"]);
959 	        this.callback("disconnected", disconnectedFrom, [cap]);
960             }
961             
962         } else if (!this["is" + camelCap + "Dummy" ]()){
963             // do not do anything with already disconnected side
964             disconnectedFrom = this[cap + "Object"]();
965             if (this.startObject() !== this.endObject()){
966                 this.freeJoint(disconnectedFrom);                
967             }
968             this.draw(["dummy" + camelCap]);
969 	    this.callback("disconnected", disconnectedFrom, [cap]);
970         }
971 
972         return this;
973     },
974 
975     /**
976      * Register object(s) so that it can be pointed by my cap.
977      * @param {RaphaelObject|Shape|array} obj
978      * @param {string} cap "start|end|both" cap to register default: "both"
979      * @return {Joint}
980      * @example j.register(circle, "end")
981      */
982     register: function(obj, cap){
983 	if (!cap){
984 	    cap = "both";
985 	}
986 	// prepare array of objects that are to be registered
987 	var toRegister = (obj.constructor == Array) ? obj : [obj];
988 	// register all objects in toRegister array
989 	for (var i = 0, len = toRegister.length; i < len; i++){
990 	    toRegister[i].yourself()._capToStick = cap;
991 	    this._registeredObjects.push(toRegister[i]);
992 	}
993 	return this;
994     },
995     /**
996      * The difference between register and registerForever is that registerForever
997      * saves reference to an array passed as argument. It means that all objects pushed
998      * into the array before and/or after the call of this method will be registered (for both caps).
999      * This method is useful for applications that do not know to which objects the connection
1000      * can be sticked when the joint is created.
1001      * @param {array} arr An array holding objects which the joint is going to be registered to.
1002      * @return {Joint}
1003      * @example
1004      * var all = [];
1005      * j.registerForever(all);
1006      * // ... create objects and push them into all array
1007      */
1008     registerForever: function(arr){
1009         if (Object.prototype.toString.call(arr) !== "[object Array]")
1010             arr = Array.prototype.slice.call(arguments);
1011 	this._registeredObjects = arr;
1012 	return this;
1013     },
1014     /**
1015      * Cancel registration of an object.
1016      * @param {RaphaelObject|Shape} obj
1017      * @param {string} cap "start|end|both" cap to unregister default: "both"
1018      * @return {Joint}
1019      * @example j.unregister(circle, "end");
1020      */
1021     unregister: function(obj, cap){
1022 	cap = cap || "both";
1023 
1024 	var index = -1;
1025 	for (var i = 0, len = this._registeredObjects.length; i < len; i++){
1026             var capToStick = this._registeredObjects[i].yourself()._capToStick || "both";
1027 	    if (this._registeredObjects[i] === obj && capToStick === cap){
1028 		index = i;
1029 		break;
1030 	    }
1031 	}
1032 	if (index !== -1){
1033 	    this._registeredObjects.splice(index, 1);
1034 	}
1035 	return this;
1036     },
1037     /**
1038      * @return {array} Registered Objects.
1039      */
1040     registeredObjects: function(){
1041         return this._registeredObjects;
1042     },
1043     /**
1044      * Set the vertices of the connection
1045      * @param {array} vertices Array of points (vertices) - either of the form: {x: 5, y; 10} or "5 10" or "5@10"
1046      * @return {Joint}
1047      */
1048     setVertices: function(vertices){
1049         this._setVertices(vertices);
1050         this.update();
1051         return this;
1052     },
1053     /**
1054      * Set connection vertices.
1055      * @private
1056      */
1057     _setVertices: function(vertices) {
1058 	var conVertices = this._opt.vertices = [], p;
1059 	// cast vertices to points
1060 	for (var i = 0, l = vertices.length; i < l; i++){
1061             p = (vertices[i].y === undefined) ?
1062                     point(vertices[i]) : point(vertices[i].x, vertices[i].y);
1063 	    conVertices.push(p);
1064 	}
1065 	return this;
1066     },
1067     /**
1068      * Get connection vertices.
1069      * @return {array} array of connection vertices
1070      */
1071     getVertices: function(){
1072 	return this._opt.vertices;
1073     },
1074     /**
1075      * Toggle the connection smoothing (bezier/straight).
1076      * @return {Joint}
1077      */
1078     toggleSmoothing: function(){
1079 	this._opt.beSmooth = !this._opt.beSmooth;
1080 	this.update();
1081 	return this;
1082     },
1083     /**
1084      * Find out whether the connection is smooth or not.
1085      * @return {boolean} true if connection is smooth
1086      */
1087     isSmooth: function(){
1088 	return this._opt.beSmooth;
1089     },
1090     /**
1091      * Set a label of the connection.
1092      * @param {string|array} str label(s)
1093      * @return {Joint}
1094      */
1095     label: function(str){
1096         this._opt.label = isArray(str) ? str : [str];
1097         for (var i = 0; i < str.length; i++) {
1098             this._opt.labelAttrs[i] = this._opt.labelAttrsDefault;
1099             this._opt.labelBoxAttrs[i] = this._opt.labelBoxAttrsDefault;
1100         }
1101 	this.update();
1102 	return this;
1103     },
1104     /**
1105      * Register callback function on various events.
1106      * @link Callbacks
1107      * @param {string} evt "justConnected"|"disconnected"|"justBroken"|"wiring"|"objectMoving"
1108      * @param fnc Callback
1109      * @return {Joint}
1110      * @example
1111      * j.registerCallback("justConnected", function(side){ ... this points to the object the joint was just connected to ... });
1112      * j.registerCallback("disconnected", function(side){ ... this points to the object the joint was just disconnected from ... });
1113      * j.registerCallback("justBroken", function(mousePos){ ... this points to the joint object ... });
1114      * j.registerCallback("wiring", function(mousePos){ ... this points to the joint object ... });
1115      * j.registerCallback("objectMoving", function(obj){ ... this points to the joint object ... });
1116      *
1117      * j.registerCallback("justConnected", function(side){
1118      *   if (side === "start"){
1119      *     console.log("Start cap connected.");
1120      *   } else {  // side === "end"
1121      *     console.log("End cap connected");
1122      *   }
1123      * });
1124      */
1125     registerCallback: function(evt, fnc){
1126 	this._callbacks[evt] = fnc;
1127 	return this;
1128     },
1129     /**
1130      * Straighten the bent connection path.
1131      * @return {Joint}
1132      */
1133     straighten: function(){
1134 	this._opt.vertices = [];
1135 	this.update();
1136 	return this;
1137     },
1138     /**
1139      * Show/hide handle(s).
1140      * If a connection arrow is, e.g., of type none, it is difficult to grab the end of the connection.
1141      * For these cases, you can use handles, which are just simple circles showing at the end of a connection.
1142      * @param {string} cap &optional [start|end] Specifies on what side handle should be shown.
1143      * @return {Joint}
1144      */
1145     toggleHandle: function(cap){
1146 	var handle = this._opt.handle;
1147 	if (!cap){
1148 	    handle.start.enabled = !handle.start.enabled;
1149 	    handle.end.enabled = !handle.end.enabled;
1150 	} else {
1151 	    handle[cap].enabled = !handle[cap].enabled;
1152 	}
1153 	this.update();
1154 	return this;
1155     },
1156     /**
1157      * Show handle.
1158      * @return {Joint}
1159      */
1160     showHandle: function(cap){
1161 	var handle = this._opt.handle;
1162 	if (!cap){
1163 	    handle.start.enabled = true;
1164 	    handle.end.enabled = true;
1165 	} else {
1166 	    handle[cap].enabled = true;
1167 	}
1168 	this.update();
1169 	return this;
1170     },
1171     /**
1172      * Hide handle.
1173      * @return {Joint}
1174      */
1175     hideHandle: function(cap){
1176 	var handle = this._opt.handle;
1177 	if (!cap){
1178 	    handle.start.enabled = false;
1179 	    handle.end.enabled = false;
1180 	} else {
1181 	    handle[cap].enabled = false;
1182 	}
1183 	this.update();
1184 	return this;
1185     },
1186     /**
1187      * Set bounding box correction.
1188      * This advanced feature of Joint library allows you to shift a point to which a connection sticks.
1189      * You can for example modify a connection to point to the center of an object or you can set a distance
1190      * between an object and a connection arrow.
1191      * @param {object} [corr] correction Correction
1192      * @param {string} [corr.type] fake type of an object to which a cap points
1193      * @param {number} [corr.x] x-axis shift of an object bounding box
1194      * @param {number} [corr.y] y-axis shift of an object bounding box
1195      * @param {number} [corr.width] change in an object bounding box width (can be negative)
1196      * @param {number} [corr.height] change in an object bounding box height (can be negative)
1197      * @param {string} cap "start|end"|undefined cap (undefined === both caps)
1198      * @return {Joint}
1199      * @example
1200      * // 1.) both sides of the connection will point to the center of
1201      * //     a circular object with radius == 30
1202      * j.setBBoxCorrection({
1203      *   type: "ellipse",
1204      *   x: 30,
1205      *   y: 30,
1206      *   width: -60,
1207      *   height: -60
1208      * });
1209      *
1210      * // 2.) keep 20px distance between connection's arrow
1211      * //     and a circular object
1212      * j.setBBoxCorrection({
1213      *   type: "ellipse",
1214      *   x: -20,
1215      *   y: -20,
1216      *   width: 40,
1217      *   height: 40
1218      * });
1219      */
1220     setBBoxCorrection: function(corr, cap){
1221 	if (!cap){
1222 	    this._opt.bboxCorrection.start = this._opt.bboxCorrection.end = corr;
1223 	} else {
1224 	    this._opt.bboxCorrection[cap] = corr;
1225 	}
1226 	this.update();
1227 	return this;
1228     },
1229 
1230     /**
1231      * Highlight connection.
1232      * Note that highlight diseappears after the first update.
1233      * @return {Joint} Return this.
1234      */
1235     highlight: function(color){
1236         color = color || "red";
1237 	this.connection().attr("stroke", color);
1238 	return this;
1239     },
1240 
1241     /**
1242      * Unhighlight connection.
1243      * @return {Joint} Return this.
1244      */
1245     unhighlight: function(){
1246 	this.connection().attr("stroke", this._opt.attrs.stroke || "#000");
1247 	return this;
1248     }
1249 };
1250 
1251 /**
1252  * Reference to current joint when an object is dragging
1253  * can be global across all raphael 'worlds' because only one object can be dragged at a time.
1254  * @private
1255  * @type Joint
1256  */
1257 Joint.currentJoint = null;
1258 
1259 /**
1260  * Set a paper for graphics rendering.
1261  * @param {Raphael|number,number,number,number|string,number,number|HTMLElement} p
1262  * @return {Raphael} Paper.
1263  * @example
1264  * // create paper from existing HTMLElement with id "world" specifying width and height
1265  * Joint.paper("world", 640, 480);
1266  * // create paper specifying x, y position and width and height
1267  * Joint.paper(50, 50, 640, 480);
1268  * // paper is created from the HTMLElement with id "world"
1269  * Joint.paper(document.getElementById("world"));
1270  * // create paper using Raphael
1271  * Joint.paper(Raphael("world", 640, 480));
1272  */
1273 Joint.paper = function paper(){
1274     var p = arguments[0];
1275     if (p === undefined){
1276 	return this._paper;
1277     }
1278     this._paperArguments = arguments;	// save for later reset
1279     if (!(p instanceof global.Raphael)){
1280 	return (this._paper = global.Raphael.apply(global, arguments));
1281     }
1282     return (this._paper = p);
1283 };
1284 
1285 /**
1286  * Clear paper, reset again.
1287  * @example
1288  * // create paper from existing HTMLElement with id "world" specifying width and height
1289  * Joint.paper("world", 640, 480);
1290  * // ... draw objects, diagrams, etc. ...
1291  * Joint.resetPaper();
1292  * // paper is clear and ready for next usage
1293  */
1294 Joint.resetPaper = function resetPaper(){
1295     if (!this._paper){
1296 	return;
1297     }
1298     var canvas = this._paper.canvas;
1299     canvas.parentNode.removeChild(canvas);
1300     Joint.paper.apply(Joint, this._paperArguments);
1301 };
1302 
1303     // get an arrow object
1304 Joint.getArrow = function(type, size, attrs){
1305     if (!size){
1306 	size = 2; // default
1307     }
1308     var arrow = Joint.arrows[type](size);
1309     if (!arrow.attrs) arrow.attrs = {};
1310 
1311     if (attrs){
1312 	for (var key in attrs){
1313 	    arrow.attrs[key] = attrs[key];
1314 	}
1315     }
1316     return arrow;
1317 };
1318 
1319 /**
1320  * This object contains predefined arrow types. Currently, there are only two types: none and basic.
1321  * These are considered general types and are suitable for most diagrams. Nevertheless, new arrows
1322  * can be easily added. See arrows.js plugin, which provides some fancier arrows.
1323  * The names can be used as startArrow|endArrow types.
1324  * @example circle.joint(rect, { startArrow: { type: basic, size: 5, attrs: ... } });
1325  */
1326 Joint.arrows = {
1327     none: function(size){
1328 	if (!size){ size = 2; }
1329 	return {
1330 	    path: ["M",size.toString(),"0","L",(-size).toString(),"0"],
1331 	    dx: size,
1332 	    dy: size,
1333             attrs: { opacity: 0 }
1334 	};
1335     },
1336     basic: function(size){
1337 	if (!size){ size = 5; }
1338    	return {
1339 	    path: ["M",size.toString(),"0",
1340 		   "L",(-size).toString(),(-size).toString(),
1341 		   "L",(-size).toString(),size.toString(),"z"],
1342 	    dx: size,
1343 	    dy: size,
1344 	    attrs: {
1345 		stroke: "black",
1346 		fill: "black"
1347 	    }
1348 	};
1349     }
1350 };
1351 
1352 /**
1353  * Get an absolute position of an element.
1354  * @private
1355  * @return {Point}
1356  */
1357 Joint.findPos = function(el){
1358     var p = point(0, 0);
1359     if (el.offsetParent){
1360 	while (el){
1361 	    p.offset(el.offsetLeft, el.offsetTop);
1362 	    el = el.offsetParent;
1363 	}
1364     } else {
1365 	// firefox (supposing el is Raphael canvas element)
1366 	p.offset(el.parentNode.offsetLeft, el.parentNode.offsetTop);
1367     }
1368     return p;
1369 };
1370 /**
1371  * Get the mouse position relative to the raphael paper.
1372  * @private
1373  * @param {Event} e Javascript event object
1374  * @param {Element} el DOM element
1375  * @return {Point}
1376  */
1377 Joint.getMousePosition = function(e, el){
1378     var pos;
1379     if (e.pageX || e.pageY) {
1380         pos = point(e.pageX, e.pageY);
1381     } else {
1382 	var
1383 	docEl = document.documentElement,
1384 	docBody = document.body;
1385 	pos = point(e.clientX + (docEl.scrollLeft || docBody.scrollLeft) - docEl.clientLeft,
1386 		    e.clientY + (docEl.scrollTop || docBody.scrollTop) - docEl.clientTop);
1387     }
1388     var rp = Joint.findPos(el);
1389     return point(pos.x - rp.x, pos.y - rp.y);
1390 };
1391 /**
1392  * MouseMove event callback.
1393  * @private
1394  * @param {Event} e
1395  */
1396 Joint.mouseMove = function(e){
1397     if (Joint.currentJoint !== null){
1398 	var joint = Joint.currentJoint;
1399 	if (joint.state === joint.STARTCAPDRAGGING ||
1400 	    joint.state === joint.ENDCAPDRAGGING){
1401 	    joint.capDragging(e);
1402 	} else if (joint.state === joint.CONNECTIONWIRING){
1403 	    joint.connectionWiring(e);
1404 	}
1405     }
1406 };
1407 /**
1408  * MouseUp event callback.
1409  * @private
1410  * @param {Event} e
1411  */
1412 Joint.mouseUp = function(e){
1413     if (Joint.currentJoint !== null){
1414 	var joint = Joint.currentJoint;
1415 	if (joint.state === joint.STARTCAPDRAGGING ||
1416 	    joint.state === joint.ENDCAPDRAGGING){
1417 	    joint.capEndDragging();
1418 	}
1419     }
1420     Joint.currentJoint = null;
1421 };
1422 
1423 Joint.fixEvent = function(event) {
1424     // add W3C standard event methods
1425     event.preventDefault = Joint.fixEvent.preventDefault;
1426     event.stopPropagation = Joint.fixEvent.stopPropagation;
1427     return event;
1428 };
1429 Joint.fixEvent.preventDefault = function() {
1430     this.returnValue = false;
1431 };
1432 Joint.fixEvent.stopPropagation = function() {
1433     this.cancelBubble = true;
1434 };
1435 Joint.handleEvent = function(event){
1436     var returnValue = true;
1437     // grab the event object (IE uses a global event object)
1438     event = event || Joint.fixEvent(((global.ownerDocument || global.document || global).parentWindow || global).event);
1439     // get a reference to the hash table of event handlers
1440     var handlers = this.events[event.type];
1441     // execute each event handler
1442     for (var i in handlers) {
1443 	this.$$handleEvent = handlers[i];
1444 	if (this.$$handleEvent(event) === false) {
1445 	    returnValue = false;
1446 	}
1447     }
1448     return returnValue;
1449 };
1450 Joint.addEvent = function(element, type, handler){
1451     if (element.addEventListener) {
1452 	element.addEventListener(type, handler, false);
1453     } else {
1454 	// assign each event handler a unique ID
1455 	if (!handler.$$guid){ handler.$$guid = Joint.addEvent.guid++; }
1456 	// create a hash table of event types for the element
1457 	if (!element.events){ element.events = {}; }
1458 	// create a hash table of event handlers for each element/event pair
1459 	var handlers = element.events[type];
1460 	if (!handlers) {
1461 	    handlers = element.events[type] = {};
1462 	    // store the existing event handler (if there is one)
1463 	    if (element["on" + type]) {
1464 		handlers[0] = element["on" + type];
1465 	    }
1466 	}
1467 	// store the event handler in the hash table
1468 	handlers[handler.$$guid] = handler;
1469 	// assign a global event handler to do all the work
1470 	element["on" + type] = Joint.handleEvent;
1471     }
1472 };
1473 // a counter used to create unique IDs
1474 Joint.addEvent.guid = 1;
1475 
1476 Joint.removeEvent = function(element, type, handler){
1477     if (element.removeEventListener) {
1478 	element.removeEventListener(type, handler, false);
1479     } else {
1480 	// delete the event handler from the hash table
1481 	if (element.events && element.events[type]){
1482 	    delete element.events[type][handler.$$guid];
1483 	}
1484     }
1485 };
1486 
1487 /*
1488  * @todo register handlers only if draggable caps
1489  * are allowed in options. Applications may not need it.
1490  */
1491 Joint.addEvent(document, "mousemove", Joint.mouseMove);
1492 Joint.addEvent(document, "mouseup", Joint.mouseUp);
1493 
1494 var JointDOMBuilder = {
1495     init: function(paper, opt, start, end, jointLocation, connectionPathCommands, labelLocation){
1496 	this.paper = paper;
1497 	this.opt = opt;
1498 	this.start = start;
1499 	this.end = end;
1500 	this.jointLocation = jointLocation;
1501 	this.connectionPathCommands = connectionPathCommands;
1502 	this.labelLocation = labelLocation;
1503 	return this;
1504     },
1505     dummy: function(startOrEnd, pos, opt){
1506 	startOrEnd.dummy = true;
1507 	startOrEnd.shape = this.paper.circle(pos.x, pos.y, opt.radius).attr(opt.attrs);
1508 	startOrEnd.shape.show();
1509 	return startOrEnd.shape;
1510     },
1511     dummyStart: function(){
1512 	return this.dummy(this.start, this.jointLocation.start.bound, this.opt.dummy.start);
1513     },
1514     dummyEnd: function(){
1515 	return this.dummy(this.end, this.jointLocation.end.bound, this.opt.dummy.end);
1516     },
1517     handleStart: function(){
1518 	var opt = this.opt.handle.start;
1519 	if (!opt.enabled) return undefined;
1520 	var pos = this.jointLocation.start.bound;
1521 	return this.paper.circle(pos.x, pos.y, opt.radius).attr(opt.attrs);
1522     },
1523     handleEnd: function(){
1524 	var opt = this.opt.handle.end;
1525 	if (!opt.enabled) return undefined;
1526 	var pos = this.jointLocation.end.bound;
1527 	return this.paper.circle(pos.x, pos.y, opt.radius).attr(opt.attrs);
1528     },
1529     connection: function(){
1530 	var opt = this.opt, paths = [],
1531 	    con = this.paper.path(this.connectionPathCommands.join(" ")).attr(opt.attrs);
1532         if (opt.subConnectionAttrs) {
1533             var i = 0, l = opt.subConnectionAttrs.length, 
1534                 length = con.getTotalLength();
1535             for (; i < l; i++) {
1536                 var attrs = opt.subConnectionAttrs[i];
1537                 var from = attrs.from || 2, to = attrs.to || 1;
1538                 from = from > length ? length : from;
1539                 from = from < 0 ? length + from : from;
1540                 from = from > 1 ? from : length * from;
1541                 to = to > length ? length : to;
1542                 to = to < 0 ? length + to : to;
1543                 to = to > 1 ? to : length * to;                
1544                 var subPath = this.paper.path(con.getSubpath(from, to)).attr(attrs);
1545                 subPath.node.style.cursor = opt.cursor;
1546                 paths.push(subPath);
1547             }
1548         }
1549 	con.node.style.cursor = opt.cursor;
1550 	con.show();
1551 	return [con].concat(paths);
1552     },
1553     label: function(){
1554 	if (this.opt.label === undefined) return undefined;
1555 	var labels = isArray(this.opt.label) ? this.opt.label : [this.opt.label],
1556             attrs = this.opt.labelAttrs,
1557             len = labels.length, i = 0, components = [];
1558 
1559         for (; i < len; i++) {
1560             var pos = this.labelLocation[i],
1561 	        labelText = this.paper.text(pos.x, pos.y + (attrs[i].offset || 0), labels[i]).attr(attrs[i]),
1562 	        bb = labelText.getBBox(),
1563                 padding = attrs[i].padding || 0,
1564 	        labelBox = this.paper.rect(bb.x - padding, bb.y - padding + (attrs[i].offset || 0), bb.width + 2*padding, bb.height + 2*padding).attr(this.opt.labelBoxAttrs[i]);
1565 	    labelText.insertAfter(labelBox);
1566             components.push(labelText, labelBox)
1567         }
1568 	return components;
1569     },
1570     startCap: function(){
1571 	var opt = this.opt.arrow.start,
1572 	    startCap = this.paper.path(opt.path.join(" ")).attr(opt.attrs);
1573 	startCap.translate(this.jointLocation.start.translate.x,
1574 			   this.jointLocation.start.translate.y);
1575 	startCap.rotate(this.jointLocation.start.rotate);
1576 	startCap.show();
1577 	return startCap;
1578     },
1579     endCap: function(){
1580 	var opt = this.opt.arrow.end,
1581 	    endCap = this.paper.path(opt.path.join(" ")).attr(opt.attrs);
1582 	endCap.translate(this.jointLocation.end.translate.x,
1583 			 this.jointLocation.end.translate.y);
1584 	endCap.rotate(this.jointLocation.end.rotate);
1585 	endCap.show();
1586 	return endCap;
1587     }
1588 };
1589 
1590 /**
1591  * Geometry-Primitives.
1592  */
1593 
1594 /**
1595  * Point object.
1596  * @constructor
1597  */
1598 function Point(x, y){
1599     var xy;
1600     if (y === undefined){
1601         // from string
1602         xy = x.split(x.indexOf("@") === -1 ? " " : "@");
1603         this.x = parseInt(xy[0], 10);
1604         this.y = parseInt(xy[1], 10);
1605     } else {
1606         this.x = x;
1607         this.y = y;
1608     }
1609 }
1610 function point(x, y){ return new Point(x, y); }
1611 
1612 Point.prototype = {
1613     constructor: Point,
1614     _isPoint: true,
1615 
1616     toString: function(){ return this.x + "@" + this.y; },
1617 
1618     deepCopy: function(){ return point(this.x, this.y); },
1619     /**
1620      * If I lie outside rectangle r, return the nearest point on the boundary of rect r,
1621      * otherwise return me.
1622      * (see Squeak Smalltalk, Point>>adhereTo:)
1623      * @param {Rect} r
1624      * @return {Point}
1625      */
1626     adhereToRect: function(r){
1627 	if (r.containsPoint(this)){
1628 	    return this;
1629 	}
1630 	this.x = mmin(mmax(this.x, r.x), r.x + r.width);
1631 	this.y = mmin(mmax(this.y, r.y), r.y + r.height);
1632 	return this;
1633     },
1634 
1635     /**
1636      * Compute the angle between me and p and the x axis.
1637      * (cartesian-to-polar coordinates conversion)
1638      * @param {Point} p
1639      * @return {object} theta in degrees and radians
1640      */
1641     theta: function(p){
1642 	var y = -(p.y - this.y),	// invert the y-axis
1643 	x = p.x - this.x,
1644 	rad = atan2(y, x);
1645 	if (rad < 0){ // correction for III. and IV. quadrant
1646 	    rad = 2*PI + rad;
1647 	}
1648 	return {
1649 	    degrees: 180*rad / PI,
1650 	    radians: rad
1651 	};
1652     },
1653 
1654     /**
1655      * @return {number} distance between me and point p
1656      */
1657     distance: function(p){
1658 	return line(this, p).length();
1659     },
1660 
1661     /**
1662      * Offset me by the specified amount.
1663      */
1664     offset: function(dx, dy){
1665 	this.x += dx;
1666 	this.y += dy;
1667 	return this;
1668     },
1669 
1670     /**
1671      * Scale the line segment between (0,0) and me to have a length of len
1672      */
1673     normalize: function(len){
1674 	var s = len / sqrt((this.x*this.x) + (this.y*this.y));
1675 	this.x = s * this.x;
1676 	this.y = s * this.y;
1677 	return this;
1678     }
1679 };
1680 
1681 /**
1682  * Alternative constructor, from polar coordinates.
1683  */
1684 Point.fromPolar = function(r, angle){
1685     return point(r * cos(angle), r * sin(angle));
1686 };
1687 
1688 
1689 /**
1690  * Line object.
1691  */
1692 function Line(p1, p2){
1693     this.start = p1;
1694     this.end = p2;
1695 }
1696 
1697 function line(p1, p2) { return new Line(p1, p2); }
1698 
1699 Line.prototype = {
1700     constructor: Line,
1701 
1702     toString: function(){
1703 	return "start: " + this.start.toString() + " end:" + this.end.toString();
1704     },
1705 
1706     /**
1707      * @return {double} length of the line
1708      */
1709     length: function(){ return sqrt(this.squaredLength()); },
1710 
1711     /**
1712      * @return {integer} length without sqrt
1713      * @note for applications where the exact length is not necessary (e.g. compare only)
1714      */
1715     squaredLength: function(){
1716 	var
1717 	x0 = this.start.x, y0 = this.start.y,
1718 	x1 = this.end.x, y1 = this.end.y;
1719 	return (x0 -= x1)*x0 + (y0 -= y1)*y0;
1720     },
1721 
1722     /**
1723      * @return {point} my midpoint
1724      */
1725     midpoint: function(){
1726 	return point((this.start.x + this.end.x) / 2,
1727 		     (this.start.y + this.end.y) / 2);
1728     },
1729 
1730 
1731     /**
1732      * @return {point} where I intersect l.
1733      * @see Squeak Smalltalk, LineSegment>>intersectionWith:
1734      */
1735     intersection: function(l){
1736 	var pt1Dir = point(this.end.x - this.start.x, this.end.y - this.start.y),
1737 	pt2Dir = point(l.end.x - l.start.x, l.end.y - l.start.y),
1738 	det = (pt1Dir.x * pt2Dir.y) - (pt1Dir.y * pt2Dir.x),
1739 	deltaPt = point(l.start.x - this.start.x, l.start.y - this.start.y),
1740 	alpha = (deltaPt.x * pt2Dir.y) - (deltaPt.y * pt2Dir.x),
1741 	beta = (deltaPt.x * pt1Dir.y) - (deltaPt.y * pt1Dir.x);
1742 
1743 	if (det === 0 ||
1744 	    alpha * det < 0 ||
1745 	    beta * det < 0){
1746 	    return null;	// no intersection
1747 	}
1748 
1749 	if (det > 0){
1750 	    if (alpha > det || beta > det){
1751 		return null;
1752 	    }
1753 	} else {
1754 	    if (alpha < det || beta < det){
1755 		return null;
1756 	    }
1757 	}
1758 	return point(this.start.x + (alpha * pt1Dir.x / det),
1759 		     this.start.y + (alpha * pt1Dir.y / det));
1760     }
1761 };
1762 
1763 /**
1764  * Rectangle object.
1765  */
1766 function Rect(o){
1767     this.x = o.x;
1768     this.y = o.y;
1769     this.width = o.width;
1770     this.height = o.height;
1771 }
1772 
1773 function rect(o){
1774     if (typeof o.width === "undefined"){
1775 	return new Rect({x: arguments[0],
1776 			 y: arguments[1],
1777 			 width: arguments[2],
1778 			 height: arguments[3]});
1779     }
1780     return new Rect(o);
1781 }
1782 
1783 Rect.prototype = {
1784     constructor: Rect,
1785 
1786     toString: function(){
1787 	return "origin: " + this.origin().toString() + " corner: " + this.corner().toString();
1788     },
1789 
1790     origin: function(){ return point(this.x, this.y); },
1791     corner: function(){ return point(this.x + this.width, this.y + this.height); },
1792     topRight: function(){ return point(this.x + this.width, this.y); },
1793     bottomLeft: function(){ return point(this.x, this.y + this.height); },
1794     center: function(){ return point(this.x + this.width/2, this.y + this.height/2); },
1795 
1796     /**
1797      * @return {boolean} true if rectangles intersect
1798      */
1799     intersect: function(r){
1800 	var myOrigin = this.origin(),
1801 	myCorner = this.corner(),
1802 	rOrigin = r.origin(),
1803 	rCorner = r.corner();
1804 	if (rCorner.x <= myOrigin.x){ return false; }
1805 	if (rCorner.y <= myOrigin.y){ return false; }
1806 	if (rOrigin.x >= myCorner.x){ return false; }
1807 	if (rOrigin.y >= myCorner.y){ return false; }
1808 	return true;
1809     },
1810 
1811     /**
1812      * @return {string} (left|right|top|bottom) side which is nearest to point
1813      * @see Squeak Smalltalk, Rectangle>>sideNearestTo:
1814      */
1815     sideNearestToPoint: function(p){
1816 	var distToLeft = p.x - this.x,
1817 	distToRight = (this.x + this.width) - p.x,
1818 	distToTop = p.y - this.y,
1819 	distToBottom = (this.y + this.height) - p.y,
1820 	closest = distToLeft,
1821 	side = "left";
1822 	if (distToRight < closest){
1823 	    closest = distToRight;
1824 	    side = "right";
1825 	}
1826 	if (distToTop < closest){
1827 	    closest = distToTop;
1828 	    side = "top";
1829 	}
1830 	if (distToBottom < closest){
1831 	    closest = distToBottom;
1832 	    side = "bottom";
1833 	}
1834 	return side;
1835     },
1836 
1837     /**
1838      * @return {bool} true if point p is insight me
1839      */
1840     containsPoint: function(p){
1841 	if (p.x > this.x && p.x < this.x + this.width &&
1842 	    p.y > this.y && p.y < this.y + this.height){
1843 	    return true;
1844 	}
1845 	return false;
1846     },
1847 
1848     /**
1849      * @return {point} a point on my border nearest to parameter point
1850      * @see Squeak Smalltalk, Rectangle>>pointNearestTo:
1851      */
1852     pointNearestToPoint: function(p){
1853 	if (this.containsPoint(p)){
1854 	    var side = this.sideNearestToPoint(p);
1855 	    switch (side){
1856 	    case "right": return point(this.x + this.width, p.y);
1857 	    case "left": return point(this.x, p.y);
1858 	    case "bottom": return point(p.x, this.y + this.height);
1859 	    case "top": return point(p.x, this.y);
1860 	    }
1861 	} else {
1862 	    return p.adhereToRect(this);
1863 	}
1864     },
1865 
1866     /**
1867      * Find point on me where line starting
1868      * from my center ending in point p intersects my boundary.
1869      */
1870     boundPoint: function(p){
1871 	var center = point(this.x + this.width/2, this.y + this.height/2);
1872 	// (clockwise, starting from the top side)
1873 	var sides = [
1874 	    line(this.origin(), this.topRight()),
1875 	    line(this.topRight(), this.corner()),
1876 	    line(this.corner(), this.bottomLeft()),
1877 	    line(this.bottomLeft(), this.origin())
1878 	],
1879 	connector = line(center, p);
1880 	for (var i = sides.length - 1; i >= 0; --i){
1881 	    var intersection = sides[i].intersection(connector);
1882 	    if (intersection !== null){
1883 		return intersection;
1884 	    }
1885 	}
1886 	// assert(false)
1887     },
1888 
1889     /**
1890      * Move and expand me.
1891      * @param r {rectangle} representing deltas
1892      */
1893     moveAndExpand: function(r){
1894 	this.x += r.x;
1895 	this.y += r.y;
1896 	this.width += r.width;
1897 	this.height += r.height;
1898 	return this;
1899     }
1900 };
1901 
1902 /**
1903  * Ellipse object.
1904  */
1905 function Ellipse(c, a, b){
1906     this.x = c.x;
1907     this.y = c.y;
1908     this.a = a;
1909     this.b = b;
1910 }
1911 
1912 function ellipse(c, a, b){ return new Ellipse(c, a, b); }
1913 
1914 Ellipse.prototype = {
1915     constructor: Ellipse,
1916 
1917     bbox: function(){
1918 	return rect({x: this.x - this.a, y: this.y - this.b, width: 2*this.a, height: 2*this.b});
1919     },
1920 
1921     /**
1922      * Find point on me where line from my center to
1923      * point p intersects my boundary.
1924      * @see Squeak Smalltalk, EllipseMorph>>intersectionWithLineSegmentFromCenterTo:
1925      */
1926     intersectionWithLineFromCenterToPoint: function(p){
1927 	var dx = p.x - this.x,
1928 	dy = p.y - this.y;
1929 	if (dx === 0){
1930 	    return this.bbox().pointNearestToPoint(p);
1931 	}
1932 
1933 	var m = dy / dx,
1934 	mSquared = m * m,
1935 	aSquared = this.a * this.a,
1936 	bSquared = this.b * this.b,
1937 	x = sqrt(1 / ((1 / aSquared) + (mSquared / bSquared)));
1938 	if (dx < 0){
1939 	    x = -x;
1940 	}
1941 	var y = m * x;
1942 	return point(this.x + x, this.y + y);
1943     }
1944 
1945 };
1946 
1947 /**
1948  * Bezier segment object.
1949  */
1950 function BezierSegment(p0, p1, p2, p3){
1951     this.p0 = p0;
1952     this.p1 = p1;
1953     this.p2 = p2;
1954     this.p3 = p3;
1955 }
1956 
1957 function bezierSegment(p0, p1, p2, p3){
1958     return new BezierSegment(p0, p1, p2, p3);
1959 }
1960 
1961 BezierSegment.prototype = {
1962     constructor: BezierSegment,
1963 
1964     /**
1965      * Get a point on me at the specified time t.
1966      */
1967     getPoint: function(t){
1968 	var
1969 	a = 1 - t,	// (1 - t)
1970 	b = a*a,	// (1 - t)^2
1971 	c = b*a,	// (1 - t)^3
1972 	tt = t*t,	// t^2
1973 	ttt = tt*t;	// t^3
1974 
1975 	return point(c*this.p0.x + 3*b*t*this.p1.x + 3*a*tt*this.p2.x + ttt*this.p3.x,
1976 		     c*this.p0.y + 3*b*t*this.p1.y + 3*a*tt*this.p2.y + ttt*this.p3.y);
1977     }
1978 
1979 };
1980 
1981 /**
1982  * Various methods for Bezier curves manipulation.
1983  */
1984 function Bezier(){}
1985 
1986 /**
1987  * Cubic Bezier curve path through points.
1988  * Ported from ActionScript implementation by Andy Woodruff (http://cartogrammar.com/blog)
1989  */
1990 Bezier.curveThroughPoints = function(points, z, angleFactor){
1991     // default values
1992     if (typeof z === "undefined"){
1993 	z = 0.5;
1994     }
1995     if (typeof angleFactor === "undefined"){
1996 	angleFactor = 0.75;
1997     }
1998 
1999     var path = [];	// the result SVG path as an array of path commands
2000     if (points.length < 2){
2001 	throw new Error("Points array must have minimum of two points.");
2002     }
2003 
2004     var p = [points[0]];
2005     // remove duplicate neighbours
2006     for (var i = 1, len = points.length; i < len; i++){
2007 	if (points[i].x != points[i-1].x || points[i].y != points[i-1].y){
2008 	    p.push(points[i]);
2009 	}
2010     }
2011 
2012     // z is_in (0,1]
2013     if (z <= 0){
2014 	z = 0.5;
2015     } else if (z > 1){
2016 	z = 1;
2017     }
2018 
2019     // angleFactor is_in [0,1]
2020     if (angleFactor < 0){
2021 	angleFactor = 0;
2022     } else if (angleFactor > 1){
2023 	angleFactor = 1;
2024     }
2025 
2026     /**
2027      * Calculate all the curve control points.
2028      */
2029 
2030     // None of this junk will do any good if there are only two points
2031     if (p.length > 2){
2032 	// Ordinarily, curve calculations will start with the second point
2033 	// and go through the second-to-last point
2034 	var firstPt = 1;
2035 	var lastPt = p.length-1;
2036 	// Check if this is a closed line
2037 	if (p[0].x == p[lastPt].x && p[0].y == p[lastPt].y){
2038 	    // Include first and last points in curve calculations
2039 	    firstPt = 0;
2040 	    lastPt = p.length;
2041 	}
2042 
2043 	// An array to store the two control points for each point
2044 	var controlPts = [];
2045 	// Loop through all the points (except the first and last
2046 	// if not a closed line) to get curve control points for each.
2047 	for (var i = firstPt; i < lastPt; i++) {
2048 	    // The previous, current, and next points
2049 
2050 	    // If the first point (of a closed line), use the
2051 	    // second-to-last point as the previous point
2052 	    var p0 = (i-1 < 0) ? p[p.length-2] : p[i-1];
2053 	    var p1 = p[i];
2054 	    // If the last point (of a closed line), use the
2055 	    // second point as the next point
2056 	    var p2 = (i+1 == p.length) ? p[1] : p[i+1];
2057 
2058 	    // Distance from previous point to current point
2059 	    var a = p0.distance(p1);
2060 	    // Correct for near-zero distances, a cheap way to prevent
2061 	    // division by zero
2062 	    if (a < 0.001){ a = 0.001; }
2063 	    // Distance from current point to next point
2064 	    var b = p1.distance(p2);
2065 	    if (b < 0.001){ b = 0.001; }
2066 	    // Distance from previous point to next point
2067 	    var c = p0.distance(p2);
2068 	    if (c < 0.001){ c = 0.001; }
2069 	    var cos = (b*b+a*a-c*c)/(2*b*a);
2070 	    // Make sure above value is between -1 and 1 so that acos will work
2071 	    if (cos < -1){ cos = -1; }
2072 	    else if (cos > 1){ cos = 1; }
2073 	    // Angle formed by the two sides of the triangle
2074 	    // (described by the three points above) adjacent to the current point
2075 	    var C = acos(cos);
2076 	    // Duplicate set of points. Start by giving previous and next points
2077 	    // values RELATIVE to the current point.
2078 	    var aPt = point(p0.x-p1.x,p0.y-p1.y);
2079 	    var bPt = point(p1.x,p1.y);
2080 	    var cPt = point(p2.x-p1.x,p2.y-p1.y);
2081 
2082 	    /* We'll be adding adding the vectors from the previous and next points
2083 	       to the current point, but we don't want differing magnitudes (i.e.
2084 	       line segment lengths) to affect the direction of the new vector.
2085                Therefore we make sure the segments we use, based on the duplicate points
2086 	       created above, are of equal length. The angle of the new vector will
2087                thus bisect angle C (defined above) and the perpendicular to this is
2088                nice for the line tangent to the curve. The curve control points will
2089                be along that tangent line.
2090 	    */
2091 	    if (a > b){
2092 		// Scale the segment to aPt (bPt to aPt) to the size of b
2093 		// (bPt to cPt) if b is shorter.
2094 		aPt.normalize(b);
2095 	    } else if (b > a){
2096 		// Scale the segment to cPt (bPt to cPt) to the size of a (aPt to bPt)
2097 		// if a is shorter.
2098 		cPt.normalize(a);
2099 	    }
2100 	    // Offset aPt and cPt by the current point to get them back to
2101 	    // their absolute position.
2102 	    aPt.offset(p1.x,p1.y);
2103 	    cPt.offset(p1.x,p1.y);
2104 
2105 	    // Get the sum of the two vectors, which is perpendicular to the line
2106 	    // along which our curve control points will lie.
2107 
2108 	    // x component of the segment from previous to current point
2109 	    var ax = bPt.x-aPt.x;
2110 	    var ay = bPt.y-aPt.y;
2111 	    // x component of the segment from next to current point
2112 	    var bx = bPt.x-cPt.x;
2113 	    var by = bPt.y-cPt.y;
2114 	    // sum of x components
2115 	    var rx = ax + bx;
2116 	    var ry = ay + by;
2117 	    // Correct for three points in a line by finding the angle between just two of them
2118 	    if (rx === 0 && ry === 0){
2119 		// Really not sure why this seems to have to be negative
2120 		rx = -bx;
2121 		ry = by;
2122 	    }
2123 	    // Switch rx and ry when y or x difference is 0. This seems to prevent
2124 	    // the angle from being perpendicular to what it should be.
2125 	    if (ay === 0 && by === 0){
2126 		rx = 0;
2127 		ry = 1;
2128 	    } else if (ax === 0 && bx === 0){
2129 		rx = 1;
2130 		ry = 0;
2131 	    }
2132 	    // length of the summed vector - not being used, but there it is anyway
2133 	    // var r = sqrt(rx*rx+ry*ry);
2134 	    // angle of the new vector
2135 	    var theta = atan2(ry,rx);
2136 	    // Distance of curve control points from current point: a fraction
2137 	    // the length of the shorter adjacent triangle side
2138 	    var controlDist = mmin(a,b)*z;
2139 	    // Scale the distance based on the acuteness of the angle. Prevents
2140 	    // big loops around long, sharp-angled triangles.
2141 	    var controlScaleFactor = C/PI;
2142 	    // Mess with this for some fine-tuning
2143 	    controlDist *= ((1-angleFactor) + angleFactor*controlScaleFactor);
2144 	    // The angle from the current point to control points:
2145 	    // the new vector angle plus 90 degrees (tangent to the curve).
2146 	    var controlAngle = theta+PI/2;
2147 	    // Control point 2, curving to the next point.
2148 	    var controlPoint2 = Point.fromPolar(controlDist,controlAngle);
2149 	    // Control point 1, curving from the previous point
2150 	    // (180 degrees away from control point 2).
2151 	    var controlPoint1 = Point.fromPolar(controlDist,controlAngle+PI);
2152 
2153 	    // Offset control points to put them in the correct absolute position
2154 	    controlPoint1.offset(p1.x,p1.y);
2155 	    controlPoint2.offset(p1.x,p1.y);
2156 
2157 	    /* Haven't quite worked out how this happens, but some control
2158 	       points will be reversed. In this case controlPoint2 will be
2159                farther from the next point than controlPoint1 is.
2160 	       Check for that and switch them if it's true.
2161 	    */
2162 	    if (controlPoint2.distance(p2) > controlPoint1.distance(p2)){
2163 		// Add the two control points to the array in reverse order
2164 		controlPts[i] = [controlPoint2,controlPoint1];
2165 	    } else {
2166 		// Otherwise add the two control points to the array in normal order
2167 		controlPts[i] = [controlPoint1,controlPoint2];
2168 	    }
2169 	}//endfor (var i = firstPt; i < lastPt; i++) {
2170 
2171 	// DRAW THE CURVE
2172 
2173 	path.push("M", p[0].x, p[0].y);
2174 	// console.log(controlPts);
2175 
2176 	// If this isn't a closed line
2177 	if (firstPt == 1){
2178 	    // Draw a regular quadratic Bézier curve from the first to second points,
2179 	    // using the first control point of the second point
2180 	    path.push("S", controlPts[1][0].x,controlPts[1][0].y,p[1].x,p[1].y);
2181 	}
2182 
2183 	// Change to true if you want to use lineTo for straight lines of 3 or
2184 	// more points rather than curves. You'll get straight lines but possible sharp corners!
2185 	var straightLines = true;
2186 	// Loop through points to draw cubic Bézier curves through the penultimate
2187 	// point, or through the last point if the line is closed.
2188 	for (var i = firstPt; i < lastPt - 1; i++){
2189 	    // Determine if multiple points in a row are in a straight line
2190 	    var isStraight = false;
2191 	    if ( ( i > 0 && atan2(p[i].y-p[i-1].y,p[i].x-p[i-1].x) == atan2(p[i+1].y-p[i].y,p[i+1].x-p[i].x) )|| ( i < p.length - 2 && atan2(p[i+2].y-p[i+1].y,p[i+2].x-p[i+1].x) == atan2(p[i+1].y-p[i].y,p[i+1].x-p[i].x) ) ){
2192 		isStraight = true;
2193 	    }
2194 
2195 	    if (straightLines && isStraight){
2196 		path.push("L", p[i+1].x,p[i+1].y);
2197 	    } else {
2198 		// BezierSegment instance using the current point, its second control
2199 		// point, the next point's first control point, and the next point
2200 		var bezier = bezierSegment(p[i],controlPts[i][1],controlPts[i+1][0],p[i+1]);
2201 		// Construct the curve out of 100 segments (adjust number for less/more detail)
2202 		for (var t = 0.01; t < 1.01; t += 0.01){
2203 		    // x,y on the curve for a given t
2204 		    var val = bezier.getPoint(t);
2205 		    path.push("L", val.x, val.y);
2206 		}
2207 	    }
2208 	}
2209 	// If this isn't a closed line
2210 	if (lastPt == p.length-1){
2211 	    // Curve to the last point using the second control point of the penultimate point.
2212 	    path.push("S", controlPts[i][1].x,controlPts[i][1].y,p[i+1].x,p[i+1].y);
2213 	}
2214 
2215 	// just draw a line if only two points
2216     } else if (p.length == 2){
2217 	path.push("M", p[0].x,p[0].y);
2218 	path.push("L", p[1].x,p[1].y);
2219     }
2220     return path;
2221 };
2222 
2223 Joint.Point = Point;
2224 Joint.point = point;
2225 Joint.Rect = Rect;
2226 Joint.rect = rect;
2227 Joint.Line = Line;
2228 Joint.line = line;
2229 Joint.Ellipse = Ellipse;
2230 Joint.ellipse = ellipse;
2231 Joint.BezierSegment = BezierSegment;
2232 Joint.bezierSegment = bezierSegment;
2233 Joint.Bezier = Bezier;
2234 Joint.Mixin = Mixin;
2235 Joint.Supplement = Supplement;
2236 Joint.DeepMixin = DeepMixin;
2237 Joint.DeepSupplement = DeepSupplement;
2238 
2239 /**
2240  * TODO: rotation support. there is a problem because
2241  * rotation does not set any attribute in this.attrs but
2242  * instead it sets transformation directly to let the browser
2243  * SVG engine compute the position.
2244  */
2245 var _attr = global.Raphael.el.attr;
2246 global.Raphael.el.attr = function(){
2247     // is it a getter or el is not a joint object?
2248     if ((arguments.length == 1 &&
2249 	 (typeof arguments[0] === "string" || typeof arguments[0] === "array")) ||
2250 	(typeof this.joints === "undefined")){
2251 	return _attr.apply(this, arguments);	// yes
2252     }
2253 
2254     // old attributes
2255     var o = {};
2256     for (var key in this.attrs){
2257 	o[key] = this.attrs[key];
2258     }
2259 
2260     _attr.apply(this, arguments);
2261 
2262     var
2263     n = this.attrs,	// new attributes
2264     positionChanged = false,
2265     strokeChanged = false;
2266 
2267     if (o.x != n.x || o.y != n.y ||	// rect/image/text
2268 	o.cx != n.cx || o.cy != n.cy ||	// circle/ellipse
2269 	o.path != n.path ||	// path
2270 	o.r != n.r){	// radius
2271 	positionChanged = true;
2272     }
2273     if (o.stroke != n.stroke){
2274 	strokeChanged = true;
2275     }
2276 
2277     for (var i = this.joints().length - 1; i >= 0; --i){
2278 	var joint = this.joints()[i];
2279 
2280 	if (positionChanged){
2281 	    joint.update();
2282 	    joint.callback("objectMoving", joint, [this]);
2283 	}
2284 	//if (strokeChanged){}
2285     }
2286     return this;
2287 };
2288 
2289 
2290 /**
2291  * Create a joint between a Raphael object and to object.
2292  * @param {RaphaelObject} to
2293  * @param {object} [opts] opt {@link Joint}
2294  * @return {Joint}
2295  */
2296 global.Raphael.el.joint = function(to, opt){
2297     Joint.paper(this.paper);
2298     return new Joint(this, to, opt);
2299 };
2300 
2301 /**
2302  * Return element unique id.
2303  */
2304 global.Raphael.el.euid = function(){
2305     return Joint.generateEuid.call(this);
2306 };
2307 
2308 global.Raphael.el.yourself = function(){
2309     return this;
2310 };
2311 
2312 global.Raphael.el.joints = function(){
2313     return (this._joints || (this._joints = []));
2314 };
2315 
2316 global.Raphael.fn.euid = function(){
2317     return Joint.generateEuid.call(this);
2318 };
2319 
2320 })(this);	// END CLOSURE
2321