From 6080cc2048d8484762a34641b3a307ee51d94ad8 Mon Sep 17 00:00:00 2001 From: anastasia143 Date: Thu, 23 Feb 2017 11:25:43 +0300 Subject: [PATCH 01/31] Updates jointjs and requirejs config --- .../src/main/webapp/resources/js/joint.js | 30848 ++++++---------- .../resources/types/jointjs/jointjs.d.ts | 872 +- .../main/webapp/resources/js/require/main.js | 11 +- 3 files changed, 12599 insertions(+), 19132 deletions(-) diff --git a/dashboard-service/src/main/webapp/resources/js/joint.js b/dashboard-service/src/main/webapp/resources/js/joint.js index d1e11a91..82c1b7f5 100644 --- a/dashboard-service/src/main/webapp/resources/js/joint.js +++ b/dashboard-service/src/main/webapp/resources/js/joint.js @@ -1,22465 +1,15253 @@ -/*! JointJS v0.8.1 - JavaScript diagramming library 2014-02-24 +/*! JointJS v1.0.3 (2016-11-22) - JavaScript diagramming library This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/*! - * jQuery JavaScript Library v2.0.3 - * http://jquery.com/ - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * - * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2013-07-03T13:30Z - */ -(function (window, undefined) { - -// Can't do this because several apps including ASP.NET trace -// the stack via arguments.caller.callee and Firefox dies if -// you try to trace through "use strict" call chains. (#13335) -// Support: Firefox 18+ -//"use strict"; - var - // A central reference to the root jQuery(document) - rootjQuery, - - // The deferred used on DOM ready - readyList, - - // Support: IE9 - // For `typeof xmlNode.method` instead of `xmlNode.method !== undefined` - core_strundefined = typeof undefined, +(function(root, factory) { - // Use the correct document accordingly with window argument (sandbox) - location = window.location, - document = window.document, - docElem = document.documentElement, - - // Maps over jQuery in case of overwrite - _jQuery = window.jQuery, + if (typeof define === 'function' && define.amd) { - // Maps over the $ in case of overwrite - _$ = window.$, + // For AMD. - // [[Class]] -> type pairs - class2type = {}, + define(['backbone', 'lodash', 'jquery'], function(Backbone, _, $) { - // List of deleted data cache ids, so we can reuse them - core_deletedIds = [], + Backbone.$ = $; - core_version = "2.0.3", + return factory(root, Backbone, _, $); + }); - // Save a reference to some core methods - core_concat = core_deletedIds.concat, - core_push = core_deletedIds.push, - core_slice = core_deletedIds.slice, - core_indexOf = core_deletedIds.indexOf, - core_toString = class2type.toString, - core_hasOwn = class2type.hasOwnProperty, - core_trim = core_version.trim, + } else if (typeof exports !== 'undefined') { - // Define a local copy of jQuery - jQuery = function (selector, context) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init(selector, context, rootjQuery); - }, + // For Node.js or CommonJS. - // Used for matching numbers - core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, + var Backbone = require('backbone'); + var _ = require('lodash'); + var $ = Backbone.$ = require('jquery'); - // Used for splitting on whitespace - core_rnotwhite = /\S+/g, + module.exports = factory(root, Backbone, _, $); - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + } else { - // Match a standalone tag - rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, + // As a browser global. - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([\da-z])/gi, + var Backbone = root.Backbone; + var _ = root._; + var $ = Backbone.$ = root.jQuery || root.$; - current, + root.joint = factory(root, Backbone, _, $); + root.g = root.joint.g; + root.V = root.Vectorizer = root.joint.V; + } - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function (all, letter) { - return letter.toUpperCase(); - }, +}(this, function(root, Backbone, _, $) { - // The ready event handler and self cleanup method - completed = function () { - document.removeEventListener("DOMContentLoaded", completed, false); - window.removeEventListener("load", completed, false); - jQuery.ready(); - }; + (function() { - jQuery.fn = jQuery.prototype = { - // The current version of jQuery being used - jquery: core_version, + /** + * version: 0.3.0 + * git://github.com/davidchambers/Base64.js.git + */ - constructor: jQuery, - init: function (selector, context, rootjQuery) { - var match, elem; + var object = typeof exports != 'undefined' ? exports : this; // #8: web workers + var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; - // HANDLE: $(""), $(null), $(undefined), $(false) - if (!selector) { - return this; - } + function InvalidCharacterError(message) { + this.message = message; + } - // Handle HTML strings - if (typeof selector === "string") { - if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) { - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; + InvalidCharacterError.prototype = new Error; + InvalidCharacterError.prototype.name = 'InvalidCharacterError'; - } else { - match = rquickExpr.exec(selector); + // encoder + // [https://gist.github.com/999166] by [https://github.com/nignag] + object.btoa || ( + object.btoa = function(input) { + var str = String(input); + for ( + // initialize result and counter + var block, charCode, idx = 0, map = chars, output = ''; + // if the next str index does not exist: + // change the mapping table to "=" + // check if d has no fractional digits + str.charAt(idx | 0) || (map = '=', idx % 1); + // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8 + output += map.charAt(63 & block >> 8 - idx % 1 * 8) + ) { + charCode = str.charCodeAt(idx += 3 / 4); + if (charCode > 0xFF) { + throw new InvalidCharacterError("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."); + } + block = block << 8 | charCode; } + return output; + }); - // Match html or make sure no context is specified for #id - if (match && (match[1] || !context)) { - - // HANDLE: $(html) -> $(array) - if (match[1]) { - context = context instanceof jQuery ? context[0] : context; + // decoder + // [https://gist.github.com/1020396] by [https://github.com/atk] + object.atob || ( + object.atob = function(input) { + var str = String(input).replace(/=+$/, ''); + if (str.length % 4 == 1) { + throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded."); + } + for ( + // initialize result and counters + var bc = 0, bs, buffer, idx = 0, output = ''; + // get next character + // eslint-disable-next-line no-cond-assign + buffer = str.charAt(idx++); + // character found in table? initialize bit storage and add its ascii value; + ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, + // and if not first of each 4 characters, + // convert the first 8 bits to one ascii character + bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 + ) { + // try to find character in table (0-63, not found => -1) + buffer = chars.indexOf(buffer); + } + return output; + }); - // scripts is true for back-compat - jQuery.merge(this, jQuery.parseHTML( - match[1], - context && context.nodeType ? context.ownerDocument || context : document, - true - )); + }()); - // HANDLE: $(html, props) - if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) { - for (match in context) { - // Properties of context are called as methods if possible - if (jQuery.isFunction(this[ match ])) { - this[ match ](context[ match ]); + (function() { - // ...and otherwise set as attributes - } else { - this.attr(match, context[ match ]); - } - } - } + if (typeof Uint8Array !== 'undefined' || typeof window === 'undefined') { + return; + } - return this; + function subarray(start, end) { + return this.slice(start, end); + } - // HANDLE: $(#id) - } else { - elem = document.getElementById(match[2]); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if (elem && elem.parentNode) { - // Inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } + function set_(array, offset) { - this.context = document; - this.selector = selector; - return this; - } + if (arguments.length < 2) { + offset = 0; + } + for (var i = 0, n = array.length; i < n; ++i, ++offset) { + this[offset] = array[i] & 0xFF; + } + } - // HANDLE: $(expr, $(...)) - } else if (!context || context.jquery) { - return ( context || rootjQuery ).find(selector); + // we need typed arrays + function TypedArray(arg1) { - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor(context).find(selector); + var result; + if (typeof arg1 === 'number') { + result = new Array(arg1); + for (var i = 0; i < arg1; ++i) { + result[i] = 0; } - - // HANDLE: $(DOMElement) - } else if (selector.nodeType) { - this.context = this[0] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if (jQuery.isFunction(selector)) { - return rootjQuery.ready(selector); + } else { + result = arg1.slice(0); } - - if (selector.selector !== undefined) { - this.selector = selector.selector; - this.context = selector.context; + result.subarray = subarray; + result.buffer = result; + result.byteLength = result.length; + result.set = set_; + if (typeof arg1 === 'object' && arg1.buffer) { + result.buffer = arg1.buffer; } - return jQuery.makeArray(selector, this); - }, - - // Start with an empty selector - selector: "", - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function () { - return core_slice.call(this); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function (num) { - return num == null ? - - // Return a 'clean' array - this.toArray() : - - // Return just the object - ( num < 0 ? this[ this.length + num ] : this[ num ] ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function (elems) { - - // Build a new jQuery matched element set - var ret = jQuery.merge(this.constructor(), elems); + return result; + } - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - ret.context = this.context; + window.Uint8Array = TypedArray; + window.Uint32Array = TypedArray; + window.Int32Array = TypedArray; + })(); - // Return the newly-formed element set - return ret; - }, + /** + * make xhr.response = 'arraybuffer' available for the IE9 + */ + (function() { - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function (callback, args) { - return jQuery.each(this, callback, args); - }, + if (typeof XMLHttpRequest === 'undefined') { + return; + } - ready: function (fn) { - // Add the callback - jQuery.ready.promise().done(fn); + if ('response' in XMLHttpRequest.prototype || + 'mozResponseArrayBuffer' in XMLHttpRequest.prototype || + 'mozResponse' in XMLHttpRequest.prototype || + 'responseArrayBuffer' in XMLHttpRequest.prototype) { + return; + } - return this; - }, + Object.defineProperty(XMLHttpRequest.prototype, 'response', { + get: function() { + return new Uint8Array(new VBArray(this.responseBody).toArray()); + } + }); + })(); - slice: function () { - return this.pushStack(core_slice.apply(this, arguments)); - }, - first: function () { - return this.eq(0); - }, +// Geometry library. - last: function () { - return this.eq(-1); - }, + var g = (function() { - eq: function (i) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack(j >= 0 && j < len ? [ this[j] ] : []); - }, + var g = {}; - map: function (callback) { - return this.pushStack(jQuery.map(this, function (elem, i) { - return callback.call(elem, i, elem); - })); - }, + // Declare shorthands to the most used math functions. + var math = Math; + var abs = math.abs; + var cos = math.cos; + var sin = math.sin; + var sqrt = math.sqrt; + var mmin = math.min; + var mmax = math.max; + var atan2 = math.atan2; + var round = math.round; + var floor = math.floor; + var PI = math.PI; + var random = math.random; - end: function () { - return this.prevObject || this.constructor(null); - }, + g.bezier = { - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: core_push, - sort: [].sort, - splice: [].splice - }; + // Cubic Bezier curve path through points. + // Ported from C# implementation by Oleg V. Polikarpotchkin and Peter Lee (http://www.codeproject.com/KB/graphics/BezierSpline.aspx). + // @param {array} points Array of points through which the smooth line will go. + // @return {array} SVG Path commands as an array + curveThroughPoints: function(points) { -// Give the init function the jQuery prototype for later instantiation - jQuery.fn.init.prototype = jQuery.fn; - - jQuery.extend = jQuery.fn.extend = function () { - var options, name, src, copy, copyIsArray, clone, - target = arguments[0] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if (typeof target === "boolean") { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } + var controlPoints = this.getCurveControlPoints(points); + var path = ['M', points[0].x, points[0].y]; - // Handle case when target is a string or something (possible in deep copy) - if (typeof target !== "object" && !jQuery.isFunction(target)) { - target = {}; - } + for (var i = 0; i < controlPoints[0].length; i++) { + path.push('C', controlPoints[0][i].x, controlPoints[0][i].y, controlPoints[1][i].x, controlPoints[1][i].y, points[i + 1].x, points[i + 1].y); + } - // extend jQuery itself if only one argument is passed - if (length === i) { - target = this; - --i; - } + return path; + }, - for (; i < length; i++) { - // Only deal with non-null/undefined values - if ((options = arguments[ i ]) != null) { - // Extend the base object - for (name in options) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if (target === copy) { - continue; + // Get open-ended Bezier Spline Control Points. + // @param knots Input Knot Bezier spline points (At least two points!). + // @param firstControlPoints Output First Control points. Array of knots.length - 1 length. + // @param secondControlPoints Output Second Control points. Array of knots.length - 1 length. + getCurveControlPoints: function(knots) { + + var firstControlPoints = []; + var secondControlPoints = []; + var n = knots.length - 1; + var i; + + // Special case: Bezier curve should be a straight line. + if (n == 1) { + // 3P1 = 2P0 + P3 + firstControlPoints[0] = Point((2 * knots[0].x + knots[1].x) / 3, + (2 * knots[0].y + knots[1].y) / 3); + // P2 = 2P1 – P0 + secondControlPoints[0] = Point(2 * firstControlPoints[0].x - knots[0].x, + 2 * firstControlPoints[0].y - knots[0].y); + return [firstControlPoints, secondControlPoints]; + } + + // Calculate first Bezier control points. + // Right hand side vector. + var rhs = []; + + // Set right hand side X values. + for (i = 1; i < n - 1; i++) { + rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x; + } + rhs[0] = knots[0].x + 2 * knots[1].x; + rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0; + // Get first control points X-values. + var x = this.getFirstControlPoints(rhs); + + // Set right hand side Y values. + for (i = 1; i < n - 1; ++i) { + rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y; + } + rhs[0] = knots[0].y + 2 * knots[1].y; + rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0; + // Get first control points Y-values. + var y = this.getFirstControlPoints(rhs); + + // Fill output arrays. + for (i = 0; i < n; i++) { + // First control point. + firstControlPoints.push(Point(x[i], y[i])); + // Second control point. + if (i < n - 1) { + secondControlPoints.push(Point(2 * knots [i + 1].x - x[i + 1], + 2 * knots[i + 1].y - y[i + 1])); + } else { + secondControlPoints.push(Point((knots[n].x + x[n - 1]) / 2, + (knots[n].y + y[n - 1]) / 2)); } + } + return [firstControlPoints, secondControlPoints]; + }, - // Recurse if we're merging plain objects or arrays - if (deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) )) { - if (copyIsArray) { - copyIsArray = false; - clone = src && jQuery.isArray(src) ? src : []; - - } else { - clone = src && jQuery.isPlainObject(src) ? src : {}; - } + // Divide a Bezier curve into two at point defined by value 't' <0,1>. + // Using deCasteljau algorithm. http://math.stackexchange.com/a/317867 + // @param control points (start, control start, control end, end) + // @return a function accepts t and returns 2 curves each defined by 4 control points. + getCurveDivider: function(p0, p1, p2, p3) { + + return function divideCurve(t) { + + var l = Line(p0, p1).pointAt(t); + var m = Line(p1, p2).pointAt(t); + var n = Line(p2, p3).pointAt(t); + var p = Line(l, m).pointAt(t); + var q = Line(m, n).pointAt(t); + var r = Line(p, q).pointAt(t); + return [{ p0: p0, p1: l, p2: p, p3: r }, { p0: r, p1: q, p2: n, p3: p3 }]; + }; + }, - // Never move original objects, clone them - target[ name ] = jQuery.extend(deep, clone, copy); + // Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points. + // @param rhs Right hand side vector. + // @return Solution vector. + getFirstControlPoints: function(rhs) { + + var n = rhs.length; + // `x` is a solution vector. + var x = []; + var tmp = []; + var b = 2.0; + + x[0] = rhs[0] / b; + // Decomposition and forward substitution. + for (var i = 1; i < n; i++) { + tmp[i] = 1 / b; + b = (i < n - 1 ? 4.0 : 3.5) - tmp[i]; + x[i] = (rhs[i] - x[i - 1]) / b; + } + for (i = 1; i < n; i++) { + // Backsubstitution. + x[n - i - 1] -= tmp[n - i] * x[n - i]; + } + return x; + }, - // Don't bring in undefined values - } else if (copy !== undefined) { - target[ name ] = copy; - } + // Solves an inversion problem -- Given the (x, y) coordinates of a point which lies on + // a parametric curve x = x(t)/w(t), y = y(t)/w(t), find the parameter value t + // which corresponds to that point. + // @param control points (start, control start, control end, end) + // @return a function accepts a point and returns t. + getInversionSolver: function(p0, p1, p2, p3) { + + var pts = arguments; + function l(i, j) { + // calculates a determinant 3x3 + // [p.x p.y 1] + // [pi.x pi.y 1] + // [pj.x pj.y 1] + var pi = pts[i]; + var pj = pts[j]; + return function(p) { + var w = (i % 3 ? 3 : 1) * (j % 3 ? 3 : 1); + var lij = p.x * (pi.y - pj.y) + p.y * (pj.x - pi.x) + pi.x * pj.y - pi.y * pj.x; + return w * lij; + }; } + return function solveInversion(p) { + var ct = 3 * l(2, 3)(p1); + var c1 = l(1, 3)(p0) / ct; + var c2 = -l(2, 3)(p0) / ct; + var la = c1 * l(3, 1)(p) + c2 * (l(3, 0)(p) + l(2, 1)(p)) + l(2, 0)(p); + var lb = c1 * l(3, 0)(p) + c2 * l(2, 0)(p) + l(1, 0)(p); + return lb / (lb - la); + }; } - } - - // Return the modified object - return target; - }; + }; - jQuery.extend({ - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( core_version + Math.random() ).replace(/\D/g, ""), + var Ellipse = g.Ellipse = function(c, a, b) { - noConflict: function (deep) { - if (window.$ === jQuery) { - window.$ = _$; + if (!(this instanceof Ellipse)) { + return new Ellipse(c, a, b); } - if (deep && window.jQuery === jQuery) { - window.jQuery = _jQuery; + if (c instanceof Ellipse) { + return new Ellipse(Point(c), c.a, c.b); } - return jQuery; - }, - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, + c = Point(c); + this.x = c.x; + this.y = c.y; + this.a = a; + this.b = b; + }; - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, + g.Ellipse.fromRect = function(rect) { - // Hold (or release) the ready event - holdReady: function (hold) { - if (hold) { - jQuery.readyWait++; - } else { - jQuery.ready(true); - } - }, + rect = Rect(rect); + return Ellipse(rect.center(), rect.width / 2, rect.height / 2); + }; - // Handle when the DOM is ready - ready: function (wait) { + g.Ellipse.prototype = { - // Abort if there are pending holds or we're already ready - if (wait === true ? --jQuery.readyWait : jQuery.isReady) { - return; - } + bbox: function() { - // Remember that the DOM is ready - jQuery.isReady = true; + return Rect(this.x - this.a, this.y - this.b, 2 * this.a, 2 * this.b); + }, - // If a normal DOM Ready event fired, decrement, and wait if need be - if (wait !== true && --jQuery.readyWait > 0) { - return; - } + clone: function() { - // If there are functions bound, to execute - readyList.resolveWith(document, [ jQuery ]); + return Ellipse(this); + }, - // Trigger any bound ready events - if (jQuery.fn.trigger) { - jQuery(document).trigger("ready").off("ready"); - } - }, + /** + * @param {g.Point} point + * @returns {number} result < 1 - inside ellipse, result == 1 - on ellipse boundary, result > 1 - outside + */ + normalizedDistance: function(point) { - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function (obj) { - return jQuery.type(obj) === "function"; - }, + var x0 = point.x; + var y0 = point.y; + var a = this.a; + var b = this.b; + var x = this.x; + var y = this.y; - isArray: Array.isArray, + return ((x0 - x) * (x0 - x)) / (a * a ) + ((y0 - y) * (y0 - y)) / (b * b); + }, - isWindow: function (obj) { - return obj != null && obj === obj.window; - }, + /** + * @param {g.Point} p + * @returns {boolean} + */ + containsPoint: function(p) { - isNumeric: function (obj) { - return !isNaN(parseFloat(obj)) && isFinite(obj); - }, + return this.normalizedDistance(p) <= 1; + }, - type: function (obj) { - if (obj == null) { - return String(obj); - } - // Support: Safari <= 5.1 (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ core_toString.call(obj) ] || "object" : - typeof obj; - }, + /** + * @returns {g.Point} + */ + center: function() { - isPlainObject: function (obj) { - // Not plain objects: - // - Any object or value whose internal [[Class]] property is not "[object Object]" - // - DOM nodes - // - window - if (jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow(obj)) { - return false; - } + return Point(this.x, this.y); + }, - // Support: Firefox <20 - // The try/catch suppresses exceptions thrown when attempting to access - // the "constructor" property of certain host objects, ie. |window.location| - // https://bugzilla.mozilla.org/show_bug.cgi?id=814622 - try { - if (obj.constructor && !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) { - return false; + /** Compute angle between tangent and x axis + * @param {g.Point} p Point of tangency, it has to be on ellipse boundaries. + * @returns {number} angle between tangent and x axis + */ + tangentTheta: function(p) { + + var refPointDelta = 30; + var x0 = p.x; + var y0 = p.y; + var a = this.a; + var b = this.b; + var center = this.bbox().center(); + var m = center.x; + var n = center.y; + + var q1 = x0 > center.x + a / 2; + var q3 = x0 < center.x - a / 2; + + var y, x; + if (q1 || q3) { + y = x0 > center.x ? y0 - refPointDelta : y0 + refPointDelta; + x = (a * a / (x0 - m)) - (a * a * (y0 - n) * (y - n)) / (b * b * (x0 - m)) + m; + } else { + x = y0 > center.y ? x0 + refPointDelta : x0 - refPointDelta; + y = ( b * b / (y0 - n)) - (b * b * (x0 - m) * (x - m)) / (a * a * (y0 - n)) + n; } - } catch (e) { - return false; - } - - // If the function hasn't returned already, we're confident that - // |obj| is a plain object, created by {} or constructed with new Object - return true; - }, - - isEmptyObject: function (obj) { - var name; - for (name in obj) { - return false; - } - return true; - }, - error: function (msg) { - throw new Error(msg); - }, + return g.point(x, y).theta(p); - // data: string of html - // context (optional): If specified, the fragment will be created in this context, defaults to document - // keepScripts (optional): If true, will include scripts passed in the html string - parseHTML: function (data, context, keepScripts) { - if (!data || typeof data !== "string") { - return null; - } - if (typeof context === "boolean") { - keepScripts = context; - context = false; - } - context = context || document; + }, - var parsed = rsingleTag.exec(data), - scripts = !keepScripts && []; + equals: function(ellipse) { - // Single tag - if (parsed) { - return [ context.createElement(parsed[1]) ]; - } + ellipse = Ellipse(ellipse); + return ellipse.x === this.x && + ellipse.y === this.y && + ellipse.a === this.a && + ellipse.b === this.b; + }, - parsed = jQuery.buildFragment([ data ], context, scripts); + // Find point on me where line from my center to + // point p intersects my boundary. + // @param {number} angle If angle is specified, intersection with rotated ellipse is computed. + intersectionWithLineFromCenterToPoint: function(p, angle) { - if (scripts) { - jQuery(scripts).remove(); - } + p = Point(p); + if (angle) p.rotate(Point(this.x, this.y), angle); + var dx = p.x - this.x; + var dy = p.y - this.y; + var result; + if (dx === 0) { + result = this.bbox().pointNearestToPoint(p); + if (angle) return result.rotate(Point(this.x, this.y), -angle); + return result; + } + var m = dy / dx; + var mSquared = m * m; + var aSquared = this.a * this.a; + var bSquared = this.b * this.b; + var x = sqrt(1 / ((1 / aSquared) + (mSquared / bSquared))); - return jQuery.merge([], parsed.childNodes); - }, + x = dx < 0 ? -x : x; + var y = m * x; + result = Point(this.x + x, this.y + y); + if (angle) return result.rotate(Point(this.x, this.y), -angle); + return result; + }, - parseJSON: JSON.parse, + toString: function() { - // Cross-browser xml parsing - parseXML: function (data) { - var xml, tmp; - if (!data || typeof data !== "string") { - return null; + return Point(this.x, this.y).toString() + ' ' + this.a + ' ' + this.b; } + }; - // Support: IE9 - try { - tmp = new DOMParser(); - xml = tmp.parseFromString(data, "text/xml"); - } catch (e) { - xml = undefined; - } + var Line = g.Line = function(p1, p2) { - if (!xml || xml.getElementsByTagName("parsererror").length) { - jQuery.error("Invalid XML: " + data); + if (!(this instanceof Line)) { + return new Line(p1, p2); } - return xml; - }, - noop: function () { - }, + this.start = Point(p1); + this.end = Point(p2); + }; - // Evaluates a script in a global context - globalEval: function (code) { - var script, - indirect = eval; + g.Line.prototype = { - code = jQuery.trim(code); + // @return the bearing (cardinal direction) of the line. For example N, W, or SE. + // @returns {String} One of the following bearings : NE, E, SE, S, SW, W, NW, N. + bearing: function() { - if (code) { - // If the code includes a valid, prologue position - // strict mode pragma, execute code by injecting a - // script tag into the document. - if (code.indexOf("use strict") === 1) { - script = document.createElement("script"); - script.text = code; - document.head.appendChild(script).parentNode.removeChild(script); - } else { - // Otherwise, avoid the DOM node creation, insertion - // and removal by using an indirect global eval - indirect(code); - } - } - }, + var lat1 = toRad(this.start.y); + var lat2 = toRad(this.end.y); + var lon1 = this.start.x; + var lon2 = this.end.x; + var dLon = toRad(lon2 - lon1); + var y = sin(dLon) * cos(lat2); + var x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon); + var brng = toDeg(atan2(y, x)); - // Convert dashed to camelCase; used by the css and data modules - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function (string) { - return string.replace(rmsPrefix, "ms-").replace(rdashAlpha, fcamelCase); - }, + var bearings = ['NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'N']; - nodeName: function (elem, name) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - }, + var index = brng - 22.5; + if (index < 0) + index += 360; + index = parseInt(index / 45); + + return bearings[index]; + }, - // args is for internal usage only - each: function (obj, callback, args) { - var value, - i = 0, - length = obj.length, - isArray = isArraylike(obj); + clone: function() { - if (args) { - if (isArray) { - for (; i < length; i++) { - value = callback.apply(obj[ i ], args); + return Line(this); + }, - if (value === false) { - break; - } + // @return {point} Point where I'm intersecting l. + // @see Squeak Smalltalk, LineSegment>>intersectionWith: + intersection: function(l) { + var pt1Dir = Point(this.end.x - this.start.x, this.end.y - this.start.y); + var pt2Dir = Point(l.end.x - l.start.x, l.end.y - l.start.y); + var det = (pt1Dir.x * pt2Dir.y) - (pt1Dir.y * pt2Dir.x); + var deltaPt = Point(l.start.x - this.start.x, l.start.y - this.start.y); + var alpha = (deltaPt.x * pt2Dir.y) - (deltaPt.y * pt2Dir.x); + var beta = (deltaPt.x * pt1Dir.y) - (deltaPt.y * pt1Dir.x); + + if (det === 0 || + alpha * det < 0 || + beta * det < 0) { + // No intersection found. + return null; + } + if (det > 0) { + if (alpha > det || beta > det) { + return null; } } else { - for (i in obj) { - value = callback.apply(obj[ i ], args); - - if (value === false) { - break; - } + if (alpha < det || beta < det) { + return null; } } + return Point(this.start.x + (alpha * pt1Dir.x / det), + this.start.y + (alpha * pt1Dir.y / det)); + }, - // A special, fast, case for the most common use of each - } else { - if (isArray) { - for (; i < length; i++) { - value = callback.call(obj[ i ], i, obj[ i ]); + // @return {double} length of the line + length: function() { + return sqrt(this.squaredLength()); + }, - if (value === false) { - break; - } - } - } else { - for (i in obj) { - value = callback.call(obj[ i ], i, obj[ i ]); + // @return {point} my midpoint + midpoint: function() { + return Point((this.start.x + this.end.x) / 2, + (this.start.y + this.end.y) / 2); + }, - if (value === false) { - break; - } - } - } - } + // @return {point} my point at 't' <0,1> + pointAt: function(t) { - return obj; - }, + var x = (1 - t) * this.start.x + t * this.end.x; + var y = (1 - t) * this.start.y + t * this.end.y; + return Point(x, y); + }, - trim: function (text) { - return text == null ? "" : core_trim.call(text); - }, + // @return {number} the offset of the point `p` from the line. + if the point `p` is on the right side of the line, - if on the left and 0 if on the line. + pointOffset: function(p) { - // results is for internal usage only - makeArray: function (arr, results) { - var ret = results || []; + // Find the sign of the determinant of vectors (start,end), where p is the query point. + return ((this.end.x - this.start.x) * (p.y - this.start.y) - (this.end.y - this.start.y) * (p.x - this.start.x)) / 2; + }, - if (arr != null) { - if (isArraylike(Object(arr))) { - jQuery.merge(ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - core_push.call(ret, arr); - } + // @return {integer} length without sqrt + // @note for applications where the exact length is not necessary (e.g. compare only) + squaredLength: function() { + var x0 = this.start.x; + var y0 = this.start.y; + var x1 = this.end.x; + var y1 = this.end.y; + return (x0 -= x1) * x0 + (y0 -= y1) * y0; + }, + + toString: function() { + return this.start.toString() + ' ' + this.end.toString(); } + }; - return ret; - }, + /* + Point is the most basic object consisting of x/y coordinate. - inArray: function (elem, arr, i) { - return arr == null ? -1 : core_indexOf.call(arr, elem, i); - }, + Possible instantiations are: + * `Point(10, 20)` + * `new Point(10, 20)` + * `Point('10 20')` + * `Point(Point(10, 20))` + */ + var Point = g.Point = function(x, y) { - merge: function (first, second) { - var l = second.length, - i = first.length, - j = 0; + if (!(this instanceof Point)) { + return new Point(x, y); + } - if (typeof l === "number") { - for (; j < l; j++) { - first[ i++ ] = second[ j ]; - } - } else { - while (second[j] !== undefined) { - first[ i++ ] = second[ j++ ]; - } + if (typeof x === 'string') { + var xy = x.split(x.indexOf('@') === -1 ? ' ' : '@'); + x = parseInt(xy[0], 10); + y = parseInt(xy[1], 10); + } else if (Object(x) === x) { + y = x.y; + x = x.x; } - first.length = i; + this.x = x === undefined ? 0 : x; + this.y = y === undefined ? 0 : y; + }; - return first; - }, + // Alternative constructor, from polar coordinates. + // @param {number} Distance. + // @param {number} Angle in radians. + // @param {point} [optional] Origin. + g.Point.fromPolar = function(distance, angle, origin) { - grep: function (elems, callback, inv) { - var retVal, - ret = [], - i = 0, - length = elems.length; - inv = !!inv; + origin = (origin && Point(origin)) || Point(0, 0); + var x = abs(distance * cos(angle)); + var y = abs(distance * sin(angle)); + var deg = normalizeAngle(toDeg(angle)); - // Go through the array, only saving the items - // that pass the validator function - for (; i < length; i++) { - retVal = !!callback(elems[ i ], i); - if (inv !== retVal) { - ret.push(elems[ i ]); - } + if (deg < 90) { + y = -y; + } else if (deg < 180) { + x = -x; + y = -y; + } else if (deg < 270) { + x = -x; } - return ret; - }, + return Point(origin.x + x, origin.y + y); + }; - // arg is for internal usage only - map: function (elems, callback, arg) { - var value, - i = 0, - length = elems.length, - isArray = isArraylike(elems), - ret = []; + // Create a point with random coordinates that fall into the range `[x1, x2]` and `[y1, y2]`. + g.Point.random = function(x1, x2, y1, y2) { - // Go through the array, translating each of the items to their - if (isArray) { - for (; i < length; i++) { - value = callback(elems[ i ], i, arg); + return Point(floor(random() * (x2 - x1 + 1) + x1), floor(random() * (y2 - y1 + 1) + y1)); + }; - if (value != null) { - ret[ ret.length ] = value; - } - } + g.Point.prototype = { - // Go through every key on the object, - } else { - for (i in elems) { - value = callback(elems[ i ], i, arg); + // If point lies outside rectangle `r`, return the nearest point on the boundary of rect `r`, + // otherwise return point itself. + // (see Squeak Smalltalk, Point>>adhereTo:) + adhereToRect: function(r) { - if (value != null) { - ret[ ret.length ] = value; - } + if (r.containsPoint(this)) { + return this; } - } - // Flatten any nested arrays - return core_concat.apply([], ret); - }, + this.x = mmin(mmax(this.x, r.x), r.x + r.width); + this.y = mmin(mmax(this.y, r.y), r.y + r.height); + return this; + }, - // A global GUID counter for objects - guid: 1, + // Return the bearing between me and the given point. + bearing: function(point) { - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function (fn, context) { - var tmp, args, proxy; + return Line(this, point).bearing(); + }, - if (typeof context === "string") { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } + // Returns change in angle from my previous position (-dx, -dy) to my new position + // relative to ref point. + changeInAngle: function(dx, dy, ref) { - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if (!jQuery.isFunction(fn)) { - return undefined; - } + // Revert the translation and measure the change in angle around x-axis. + return Point(this).offset(-dx, -dy).theta(ref) - this.theta(ref); + }, - // Simulated bind - args = core_slice.call(arguments, 2); - proxy = function () { - return fn.apply(context || this, args.concat(core_slice.call(arguments))); - }; + clone: function() { - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; + return Point(this); + }, - return proxy; - }, + difference: function(p) { - // Multifunctional method to get and set values of a collection - // The value/s can optionally be executed if it's a function - access: function (elems, fn, key, value, chainable, emptyGet, raw) { - var i = 0, - length = elems.length, - bulk = key == null; + return Point(this.x - p.x, this.y - p.y); + }, - // Sets many values - if (jQuery.type(key) === "object") { - chainable = true; - for (i in key) { - jQuery.access(elems, fn, i, key[i], true, emptyGet, raw); - } + // Returns distance between me and point `p`. + distance: function(p) { - // Sets one value - } else if (value !== undefined) { - chainable = true; + return Line(this, p).length(); + }, - if (!jQuery.isFunction(value)) { - raw = true; - } + equals: function(p) { - if (bulk) { - // Bulk operations run against the entire set - if (raw) { - fn.call(elems, value); - fn = null; + return this.x === p.x && this.y === p.y; + }, - // ...except when executing function values - } else { - bulk = fn; - fn = function (elem, key, value) { - return bulk.call(jQuery(elem), value); - }; - } - } + magnitude: function() { - if (fn) { - for (; i < length; i++) { - fn(elems[i], key, raw ? value : value.call(elems[i], i, fn(elems[i], key))); - } - } - } - - return chainable ? - elems : + return sqrt((this.x * this.x) + (this.y * this.y)) || 0.01; + }, - // Gets - bulk ? - fn.call(elems) : - length ? fn(elems[0], key) : emptyGet; - }, + // Returns a manhattan (taxi-cab) distance between me and point `p`. + manhattanDistance: function(p) { - now: Date.now, + return abs(p.x - this.x) + abs(p.y - this.y); + }, - // A method for quickly swapping in/out CSS properties to get correct calculations. - // Note: this method belongs to the css module but it's needed here for the support module. - // If support gets modularized, this method should be moved back to the css module. - swap: function (elem, options, callback, args) { - var ret, name, - old = {}; + // Move point on line starting from ref ending at me by + // distance distance. + move: function(ref, distance) { - // Remember the old values, and insert the new ones - for (name in options) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } + var theta = toRad(Point(ref).theta(this)); + return this.offset(cos(theta) * distance, -sin(theta) * distance); + }, - ret = callback.apply(elem, args || []); + // Scales x and y such that the distance between the point and the origin (0,0) is equal to the given length. + normalize: function(length) { - // Revert the old values - for (name in options) { - elem.style[ name ] = old[ name ]; - } + var scale = (length || 1) / this.magnitude(); + return this.scale(scale, scale); + }, - return ret; - } - }); + // Offset me by the specified amount. + offset: function(dx, dy) { - jQuery.ready.promise = function (obj) { - if (!readyList) { + this.x += dx || 0; + this.y += dy || 0; + return this; + }, - readyList = jQuery.Deferred(); + // Returns a point that is the reflection of me with + // the center of inversion in ref point. + reflection: function(ref) { - // Catch cases where $(document).ready() is called after the browser event has already occurred. - // we once tried to use readyState "interactive" here, but it caused issues like the one - // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 - if (document.readyState === "complete") { - // Handle it asynchronously to allow scripts the opportunity to delay ready - setTimeout(jQuery.ready); + return Point(ref).move(this, this.distance(ref)); + }, - } else { + // Rotate point by angle around origin. + rotate: function(origin, angle) { - // Use the handy event callback - document.addEventListener("DOMContentLoaded", completed, false); + angle = (angle + 360) % 360; + this.toPolar(origin); + this.y += toRad(angle); + var point = Point.fromPolar(this.x, this.y, origin); + this.x = point.x; + this.y = point.y; + return this; + }, - // A fallback to window.onload, that will always work - window.addEventListener("load", completed, false); - } - } - return readyList.promise(obj); - }; + round: function(precision) { -// Populate the class2type map - jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function (i, name) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); - }); + this.x = precision ? this.x.toFixed(precision) : round(this.x); + this.y = precision ? this.y.toFixed(precision) : round(this.y); + return this; + }, - function isArraylike(obj) { - var length = obj.length, - type = jQuery.type(obj); + // Scale point with origin. + scale: function(sx, sy, origin) { - if (jQuery.isWindow(obj)) { - return false; - } + origin = (origin && Point(origin)) || Point(0, 0); + this.x = origin.x + sx * (this.x - origin.x); + this.y = origin.y + sy * (this.y - origin.y); + return this; + }, - if (obj.nodeType === 1 && length) { - return true; - } + snapToGrid: function(gx, gy) { - return type === "array" || type !== "function" && - ( length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj ); - } + this.x = snapToGrid(this.x, gx); + this.y = snapToGrid(this.y, gy || gx); + return this; + }, -// All jQuery objects should point back to these - rootjQuery = jQuery(document); - /*! - * Sizzle CSS Selector Engine v1.9.4-pre - * http://sizzlejs.com/ - * - * Copyright 2013 jQuery Foundation, Inc. and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2013-06-03 - */ - (function (window, undefined) { - - var i, - support, - cachedruns, - Expr, - getText, - isXML, - compile, - outermostContext, - sortInput, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + -(new Date()), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - hasDuplicate = false, - sortOrder = function (a, b) { - if (a === b) { - hasDuplicate = true; - return 0; - } - return 0; + // Compute the angle between me and `p` and the x axis. + // (cartesian-to-polar coordinates conversion) + // Return theta angle in degrees. + theta: function(p) { + + p = Point(p); + // Invert the y-axis. + var y = -(p.y - this.y); + var x = p.x - this.x; + // Makes sure that the comparison with zero takes rounding errors into account. + var PRECISION = 10; + // Note that `atan2` is not defined for `x`, `y` both equal zero. + var rad = (y.toFixed(PRECISION) == 0 && x.toFixed(PRECISION) == 0) ? 0 : atan2(y, x); + + // Correction for III. and IV. quadrant. + if (rad < 0) { + rad = 2 * PI + rad; + } + return 180 * rad / PI; }, - // General-purpose constants - strundefined = typeof undefined, - MAX_NEGATIVE = 1 << 31, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf if we can't use a native one - indexOf = arr.indexOf || function (elem) { - var i = 0, - len = this.length; - for (; i < len; i++) { - if (this[i] === elem) { - return i; - } - } - return -1; + toJSON: function() { + + return { x: this.x, y: this.y }; }, - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - // http://www.w3.org/TR/css3-syntax/#characters - characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", - - // Loosely modeled on CSS identifier characters - // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors - // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = characterEncoding.replace("w", "w#"), - - // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + - "*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", - - // Prefer arguments quoted, - // then not containing pseudos/brackets, - // then attribute selectors/non-parenthetical expressions, - // then anything else - // These preferences are here to reduce the number of selectors - // needing tokenize in the PSEUDO preFilter - pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace(3, 8) + ")*)|.*)\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g"), - - rcomma = new RegExp("^" + whitespace + "*," + whitespace + "*"), - rcombinators = new RegExp("^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*"), - - rsibling = new RegExp(whitespace + "*[+~]"), - rattributeQuotes = new RegExp("=" + whitespace + "*([^\\]'\"]*)" + whitespace + "*\\]", "g"), - - rpseudo = new RegExp(pseudos), - ridentifier = new RegExp("^" + identifier + "$"), - - matchExpr = { - "ID": new RegExp("^#(" + characterEncoding + ")"), - "CLASS": new RegExp("^\\.(" + characterEncoding + ")"), - "TAG": new RegExp("^(" + characterEncoding.replace("w", "w*") + ")"), - "ATTR": new RegExp("^" + attributes), - "PSEUDO": new RegExp("^" + pseudos), - "CHILD": new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i"), - "bool": new RegExp("^(?:" + booleans + ")$", "i"), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp("^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i") + // Converts rectangular to polar coordinates. + // An origin can be specified, otherwise it's 0@0. + toPolar: function(o) { + + o = (o && Point(o)) || Point(0, 0); + var x = this.x; + var y = this.y; + this.x = sqrt((x - o.x) * (x - o.x) + (y - o.y) * (y - o.y)); // r + this.y = toRad(o.theta(Point(x, y))); + return this; }, - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rescape = /'|\\/g, - - // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp("\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig"), - funescape = function (_, escaped, escapedWhitespace) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - // BMP codepoint - high < 0 ? - String.fromCharCode(high + 0x10000) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode(high >> 10 | 0xD800, high & 0x3FF | 0xDC00); - }; + toString: function() { -// Optimize for push.apply( _, NodeList ) - try { - push.apply( - (arr = slice.call(preferredDoc.childNodes)), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; - } catch (e) { - push = { apply: arr.length ? - - // Leverage slice if possible - function (target, els) { - push_native.apply(target, slice.call(els)); - } : - - // Support: IE<9 - // Otherwise append directly - function (target, els) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ((target[j++] = els[i++])) { - } - target.length = j - 1; - } - }; - } + return this.x + '@' + this.y; + }, - function Sizzle(selector, context, results, seed) { - var match, elem, m, nodeType, - // QSA vars - i, groups, old, nid, newContext, newSelector; + update: function(x, y) { - if (( context ? context.ownerDocument || context : preferredDoc ) !== document) { - setDocument(context); + this.x = x || 0; + this.y = y || 0; + return this; } + }; - context = context || document; - results = results || []; + var Rect = g.Rect = function(x, y, w, h) { - if (!selector || typeof selector !== "string") { - return results; + if (!(this instanceof Rect)) { + return new Rect(x, y, w, h); } - if ((nodeType = context.nodeType) !== 1 && nodeType !== 9) { - return []; + if ((Object(x) === x)) { + y = x.y; + w = x.width; + h = x.height; + x = x.x; } - if (documentIsHTML && !seed) { + this.x = x === undefined ? 0 : x; + this.y = y === undefined ? 0 : y; + this.width = w === undefined ? 0 : w; + this.height = h === undefined ? 0 : h; + }; - // Shortcuts - if ((match = rquickExpr.exec(selector))) { - // Speed-up: Sizzle("#ID") - if ((m = match[1])) { - if (nodeType === 9) { - elem = context.getElementById(m); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if (elem && elem.parentNode) { - // Handle the case where IE, Opera, and Webkit return items - // by name instead of ID - if (elem.id === m) { - results.push(elem); - return results; - } - } else { - return results; - } - } else { - // Context is not a document - if (context.ownerDocument && (elem = context.ownerDocument.getElementById(m)) && - contains(context, elem) && elem.id === m) { - results.push(elem); - return results; - } - } + g.Rect.fromEllipse = function(e) { - // Speed-up: Sizzle("TAG") - } else if (match[2]) { - push.apply(results, context.getElementsByTagName(selector)); - return results; + e = Ellipse(e); + return Rect(e.x - e.a, e.y - e.b, 2 * e.a, 2 * e.b); + }; - // Speed-up: Sizzle(".CLASS") - } else if ((m = match[3]) && support.getElementsByClassName && context.getElementsByClassName) { - push.apply(results, context.getElementsByClassName(m)); - return results; - } - } + g.Rect.prototype = { - // QSA path - if (support.qsa && (!rbuggyQSA || !rbuggyQSA.test(selector))) { - nid = old = expando; - newContext = context; - newSelector = nodeType === 9 && selector; + // Find my bounding box when I'm rotated with the center of rotation in the center of me. + // @return r {rectangle} representing a bounding box + bbox: function(angle) { - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - if (nodeType === 1 && context.nodeName.toLowerCase() !== "object") { - groups = tokenize(selector); + var theta = toRad(angle || 0); + var st = abs(sin(theta)); + var ct = abs(cos(theta)); + var w = this.width * ct + this.height * st; + var h = this.width * st + this.height * ct; + return Rect(this.x + (this.width - w) / 2, this.y + (this.height - h) / 2, w, h); + }, - if ((old = context.getAttribute("id"))) { - nid = old.replace(rescape, "\\$&"); - } else { - context.setAttribute("id", nid); - } - nid = "[id='" + nid + "'] "; + bottomLeft: function() { - i = groups.length; - while (i--) { - groups[i] = nid + toSelector(groups[i]); - } - newContext = rsibling.test(selector) && context.parentNode || context; - newSelector = groups.join(","); - } + return Point(this.x, this.y + this.height); + }, - if (newSelector) { - try { - push.apply(results, - newContext.querySelectorAll(newSelector) - ); - return results; - } catch (qsaError) { - } finally { - if (!old) { - context.removeAttribute("id"); - } - } - } - } - } + bottomMiddle: function() { - // All others - return select(selector.replace(rtrim, "$1"), context, results, seed); - } + return Point(this.x + this.width / 2, this.y + this.height); + }, - /** - * Create key-value caches of limited size - * @returns {Function(string, Object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ - function createCache() { - var keys = []; + center: function() { - function cache(key, value) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if (keys.push(key += " ") > Expr.cacheLength) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key ] = value); - } + return Point(this.x + this.width / 2, this.y + this.height / 2); + }, - return cache; - } + clone: function() { - /** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ - function markFunction(fn) { - fn[ expando ] = true; - return fn; - } + return Rect(this); + }, - /** - * Support testing using an element - * @param {Function} fn Passed the created div and expects a boolean result - */ - function assert(fn) { - var div = document.createElement("div"); + // @return {bool} true if point p is insight me + containsPoint: function(p) { - try { - return !!fn(div); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if (div.parentNode) { - div.parentNode.removeChild(div); - } - // release memory in IE - div = null; - } - } + p = Point(p); + return p.x >= this.x && p.x <= this.x + this.width && p.y >= this.y && p.y <= this.y + this.height; + }, - /** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ - function addHandle(attrs, handler) { - var arr = attrs.split("|"), - i = attrs.length; + // @return {bool} true if rectangle `r` is inside me. + containsRect: function(r) { - while (i--) { - Expr.attrHandle[ arr[i] ] = handler; - } - } + var r0 = Rect(this).normalize(); + var r1 = Rect(r).normalize(); + var w0 = r0.width; + var h0 = r0.height; + var w1 = r1.width; + var h1 = r1.height; - /** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ - function siblingCheck(a, b) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - ( ~b.sourceIndex || MAX_NEGATIVE ) - - ( ~a.sourceIndex || MAX_NEGATIVE ); - - // Use IE sourceIndex if available on both nodes - if (diff) { - return diff; - } - - // Check if b follows a - if (cur) { - while ((cur = cur.nextSibling)) { - if (cur === b) { - return -1; - } + if (!w0 || !h0 || !w1 || !h1) { + // At least one of the dimensions is 0 + return false; } - } - return a ? 1 : -1; - } - - /** - * Returns a function to use in pseudos for input types - * @param {String} type - */ - function createInputPseudo(type) { - return function (elem) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; - } + var x0 = r0.x; + var y0 = r0.y; + var x1 = r1.x; + var y1 = r1.y; - /** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ - function createButtonPseudo(type) { - return function (elem) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; - } + w1 += x1; + w0 += x0; + h1 += y1; + h0 += y0; - /** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ - function createPositionalPseudo(fn) { - return markFunction(function (argument) { - argument = +argument; - return markFunction(function (seed, matches) { - var j, - matchIndexes = fn([], seed.length, argument), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while (i--) { - if (seed[ (j = matchIndexes[i]) ]) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); - } + return x0 <= x1 && w1 <= w0 && y0 <= y1 && h1 <= h0; + }, - /** - * Detect xml - * @param {Element|Object} elem An element or a document - */ - isXML = Sizzle.isXML = function (elem) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; - }; + corner: function() { -// Expose support vars for convenience - support = Sizzle.support = {}; + return Point(this.x + this.width, this.y + this.height); + }, - /** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ - setDocument = Sizzle.setDocument = function (node) { - var doc = node ? node.ownerDocument || node : preferredDoc, - parent = doc.defaultView; + // @return {boolean} true if rectangles are equal. + equals: function(r) { - // If no document and documentElement is available, return - if (doc === document || doc.nodeType !== 9 || !doc.documentElement) { - return document; - } + var mr = Rect(this).normalize(); + var nr = Rect(r).normalize(); + return mr.x === nr.x && mr.y === nr.y && mr.width === nr.width && mr.height === nr.height; + }, - // Set our document - document = doc; - docElem = doc.documentElement; + // @return {rect} if rectangles intersect, {null} if not. + intersect: function(r) { - // Support tests - documentIsHTML = !isXML(doc); + var myOrigin = this.origin(); + var myCorner = this.corner(); + var rOrigin = r.origin(); + var rCorner = r.corner(); - // Support: IE>8 - // If iframe document is assigned to "document" variable and if iframe has been reloaded, - // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 - // IE6-8 do not support the defaultView property so parent will be undefined - if (parent && parent.attachEvent && parent !== parent.top) { - parent.attachEvent("onbeforeunload", function () { - setDocument(); - }); - } + // No intersection found + if (rCorner.x <= myOrigin.x || + rCorner.y <= myOrigin.y || + rOrigin.x >= myCorner.x || + rOrigin.y >= myCorner.y) return null; - /* Attributes - ---------------------------------------------------------------------- */ + var x = Math.max(myOrigin.x, rOrigin.x); + var y = Math.max(myOrigin.y, rOrigin.y); - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) - support.attributes = assert(function (div) { - div.className = "i"; - return !div.getAttribute("className"); - }); + return Rect(x, y, Math.min(myCorner.x, rCorner.x) - x, Math.min(myCorner.y, rCorner.y) - y); + }, - /* getElement(s)By* - ---------------------------------------------------------------------- */ + // Find point on my boundary where line starting + // from my center ending in point p intersects me. + // @param {number} angle If angle is specified, intersection with rotated rectangle is computed. + intersectionWithLineFromCenterToPoint: function(p, angle) { - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function (div) { - div.appendChild(doc.createComment("")); - return !div.getElementsByTagName("*").length; - }); + p = Point(p); + var center = Point(this.x + this.width / 2, this.y + this.height / 2); + var result; + if (angle) p.rotate(center, angle); + + // (clockwise, starting from the top side) + var sides = [ + Line(this.origin(), this.topRight()), + Line(this.topRight(), this.corner()), + Line(this.corner(), this.bottomLeft()), + Line(this.bottomLeft(), this.origin()) + ]; + var connector = Line(center, p); + + for (var i = sides.length - 1; i >= 0; --i) { + var intersection = sides[i].intersection(connector); + if (intersection !== null) { + result = intersection; + break; + } + } + if (result && angle) result.rotate(center, -angle); + return result; + }, - // Check if getElementsByClassName can be trusted - support.getElementsByClassName = assert(function (div) { - div.innerHTML = "
"; + leftMiddle: function() { - // Support: Safari<4 - // Catch class over-caching - div.firstChild.className = "i"; - // Support: Opera<10 - // Catch gEBCN failure to find non-leading classes - return div.getElementsByClassName("i").length === 2; - }); + return Point(this.x , this.y + this.height / 2); + }, - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert(function (div) { - docElem.appendChild(div).id = expando; - return !doc.getElementsByName || !doc.getElementsByName(expando).length; - }); + // Move and expand me. + // @param r {rectangle} representing deltas + moveAndExpand: function(r) { - // ID find and filter - if (support.getById) { - Expr.find["ID"] = function (id, context) { - if (typeof context.getElementById !== strundefined && documentIsHTML) { - var m = context.getElementById(id); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [m] : []; - } - }; - Expr.filter["ID"] = function (id) { - var attrId = id.replace(runescape, funescape); - return function (elem) { - return elem.getAttribute("id") === attrId; - }; - }; - } else { - // Support: IE6/7 - // getElementById is not reliable as a find shortcut - delete Expr.find["ID"]; - - Expr.filter["ID"] = function (id) { - var attrId = id.replace(runescape, funescape); - return function (elem) { - var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - } + this.x += r.x || 0; + this.y += r.y || 0; + this.width += r.width || 0; + this.height += r.height || 0; + return this; + }, - // Tag - Expr.find["TAG"] = support.getElementsByTagName ? - function (tag, context) { - if (typeof context.getElementsByTagName !== strundefined) { - return context.getElementsByTagName(tag); - } - } : - function (tag, context) { - var elem, - tmp = [], - i = 0, - results = context.getElementsByTagName(tag); - - // Filter out possible comments - if (tag === "*") { - while ((elem = results[i++])) { - if (elem.nodeType === 1) { - tmp.push(elem); - } - } + // Normalize the rectangle; i.e., make it so that it has a non-negative width and height. + // If width < 0 the function swaps the left and right corners, + // and it swaps the top and bottom corners if height < 0 + // like in http://qt-project.org/doc/qt-4.8/qrectf.html#normalized + normalize: function() { + + var newx = this.x; + var newy = this.y; + var newwidth = this.width; + var newheight = this.height; + if (this.width < 0) { + newx = this.x + this.width; + newwidth = -this.width; + } + if (this.height < 0) { + newy = this.y + this.height; + newheight = -this.height; + } + this.x = newx; + this.y = newy; + this.width = newwidth; + this.height = newheight; + return this; + }, - return tmp; - } - return results; - }; + origin: function() { - // Class - Expr.find["CLASS"] = support.getElementsByClassName && function (className, context) { - if (typeof context.getElementsByClassName !== strundefined && documentIsHTML) { - return context.getElementsByClassName(className); - } - }; + return Point(this.x, this.y); + }, - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See http://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ((support.qsa = rnative.test(doc.querySelectorAll))) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function (div) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // http://bugs.jquery.com/ticket/12359 - div.innerHTML = ""; - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if (!div.querySelectorAll("[selected]").length) { - rbuggyQSA.push("\\[" + whitespace + "*(?:value|" + booleans + ")"); - } + // @return {point} a point on my boundary nearest to the given point. + // @see Squeak Smalltalk, Rectangle>>pointNearestTo: + pointNearestToPoint: function(point) { - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if (!div.querySelectorAll(":checked").length) { - rbuggyQSA.push(":checked"); + point = Point(point); + if (this.containsPoint(point)) { + var side = this.sideNearestToPoint(point); + switch (side){ + case 'right': return Point(this.x + this.width, point.y); + case 'left': return Point(this.x, point.y); + case 'bottom': return Point(point.x, this.y + this.height); + case 'top': return Point(point.x, this.y); } - }); + } + return point.adhereToRect(this); + }, - assert(function (div) { + rightMiddle: function() { - // Support: Opera 10-12/IE8 - // ^= $= *= and empty values - // Should not select anything - // Support: Windows 8 Native Apps - // The type attribute is restricted during .innerHTML assignment - var input = doc.createElement("input"); - input.setAttribute("type", "hidden"); - div.appendChild(input).setAttribute("t", ""); + return Point(this.x + this.width, this.y + this.height / 2); + }, - if (div.querySelectorAll("[t^='']").length) { - rbuggyQSA.push("[*^$]=" + whitespace + "*(?:''|\"\")"); - } + round: function(precision) { - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if (!div.querySelectorAll(":enabled").length) { - rbuggyQSA.push(":enabled", ":disabled"); - } + this.x = precision ? this.x.toFixed(precision) : round(this.x); + this.y = precision ? this.y.toFixed(precision) : round(this.y); + this.width = precision ? this.width.toFixed(precision) : round(this.width); + this.height = precision ? this.height.toFixed(precision) : round(this.height); + return this; + }, - // Opera 10-11 does not throw on post-comma invalid pseudos - div.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } + // Scale rectangle with origin. + scale: function(sx, sy, origin) { - if ((support.matchesSelector = rnative.test((matches = docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector)))) { + origin = this.origin().scale(sx, sy, origin); + this.x = origin.x; + this.y = origin.y; + this.width *= sx; + this.height *= sy; + return this; + }, - assert(function (div) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call(div, "div"); + // @return {string} (left|right|top|bottom) side which is nearest to point + // @see Squeak Smalltalk, Rectangle>>sideNearestTo: + sideNearestToPoint: function(point) { - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call(div, "[s!='']:x"); - rbuggyMatches.push("!=", pseudos); - }); - } + point = Point(point); + var distToLeft = point.x - this.x; + var distToRight = (this.x + this.width) - point.x; + var distToTop = point.y - this.y; + var distToBottom = (this.y + this.height) - point.y; + var closest = distToLeft; + var side = 'left'; - rbuggyQSA = rbuggyQSA.length && new RegExp(rbuggyQSA.join("|")); - rbuggyMatches = rbuggyMatches.length && new RegExp(rbuggyMatches.join("|")); - - /* Contains - ---------------------------------------------------------------------- */ - - // Element contains another - // Purposefully does not implement inclusive descendent - // As in, an element does not contain itself - contains = rnative.test(docElem.contains) || docElem.compareDocumentPosition ? - function (a, b) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains(bup) : - a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16 - )); - } : - function (a, b) { - if (b) { - while ((b = b.parentNode)) { - if (b === a) { - return true; - } - } - } - return false; - }; + if (distToRight < closest) { + closest = distToRight; + side = 'right'; + } + if (distToTop < closest) { + closest = distToTop; + side = 'top'; + } + if (distToBottom < closest) { + closest = distToBottom; + side = 'bottom'; + } + return side; + }, - /* Sorting - ---------------------------------------------------------------------- */ + snapToGrid: function(gx, gy) { - // Document order sorting - sortOrder = docElem.compareDocumentPosition ? - function (a, b) { + var origin = this.origin().snapToGrid(gx, gy); + var corner = this.corner().snapToGrid(gx, gy); + this.x = origin.x; + this.y = origin.y; + this.width = corner.x - origin.x; + this.height = corner.y - origin.y; + return this; + }, - // Flag for duplicate removal - if (a === b) { - hasDuplicate = true; - return 0; - } + topMiddle: function() { - var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition(b); + return Point(this.x + this.width / 2, this.y); + }, - if (compare) { - // Disconnected nodes - if (compare & 1 || - (!support.sortDetached && b.compareDocumentPosition(a) === compare)) { + topRight: function() { - // Choose the first element that is related to our preferred document - if (a === doc || contains(preferredDoc, a)) { - return -1; - } - if (b === doc || contains(preferredDoc, b)) { - return 1; - } + return Point(this.x + this.width, this.y); + }, - // Maintain original order - return sortInput ? - ( indexOf.call(sortInput, a) - indexOf.call(sortInput, b) ) : - 0; - } + toJSON: function() { - return compare & 4 ? -1 : 1; - } + return { x: this.x, y: this.y, width: this.width, height: this.height }; + }, - // Not directly comparable, sort on existence of method - return a.compareDocumentPosition ? -1 : 1; - } : - function (a, b) { - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Exit early if the nodes are identical - if (a === b) { - hasDuplicate = true; - return 0; - - // Parentless nodes are either documents or disconnected - } else if (!aup || !bup) { - return a === doc ? -1 : - b === doc ? 1 : - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf.call(sortInput, a) - indexOf.call(sortInput, b) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if (aup === bup) { - return siblingCheck(a, b); - } + toString: function() { - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ((cur = cur.parentNode)) { - ap.unshift(cur); - } - cur = b; - while ((cur = cur.parentNode)) { - bp.unshift(cur); - } + return this.origin().toString() + ' ' + this.corner().toString(); + }, - // Walk down the tree looking for a discrepancy - while (ap[i] === bp[i]) { - i++; - } + // @return {rect} representing the union of both rectangles. + union: function(rect) { - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck(ap[i], bp[i]) : + var myOrigin = this.origin(); + var myCorner = this.corner(); + var rOrigin = rect.origin(); + var rCorner = rect.corner(); - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; + var originX = Math.min(myOrigin.x, rOrigin.x); + var originY = Math.min(myOrigin.y, rOrigin.y); + var cornerX = Math.max(myCorner.x, rCorner.x); + var cornerY = Math.max(myCorner.y, rCorner.y); - return doc; + return Rect(originX, originY, cornerX - originX, cornerY - originY); + } }; - Sizzle.matches = function (expr, elements) { - return Sizzle(expr, null, null, elements); - }; + var normalizeAngle = g.normalizeAngle = function(angle) { - Sizzle.matchesSelector = function (elem, expr) { - // Set document vars if needed - if (( elem.ownerDocument || elem ) !== document) { - setDocument(elem); - } + return (angle % 360) + (angle < 0 ? 360 : 0); + }; - // Make sure that attribute selectors are quoted - expr = expr.replace(rattributeQuotes, "='$1']"); + g.scale = { - if (support.matchesSelector && documentIsHTML && - ( !rbuggyMatches || !rbuggyMatches.test(expr) ) && - ( !rbuggyQSA || !rbuggyQSA.test(expr) )) { + // Return the `value` from the `domain` interval scaled to the `range` interval. + linear: function(domain, range, value) { - try { - var ret = matches.call(elem, expr); - - // IE 9's matchesSelector returns false on disconnected nodes - if (ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11) { - return ret; - } - } catch (e) { - } + var domainSpan = domain[1] - domain[0]; + var rangeSpan = range[1] - range[0]; + return (((value - domain[0]) / domainSpan) * rangeSpan + range[0]) || 0; } - - return Sizzle(expr, document, null, [elem]).length > 0; }; - Sizzle.contains = function (context, elem) { - // Set document vars if needed - if (( context.ownerDocument || context ) !== document) { - setDocument(context); - } - return contains(context, elem); - }; + var snapToGrid = g.snapToGrid = function(value, gridSize) { - Sizzle.attr = function (elem, name) { - // Set document vars if needed - if (( elem.ownerDocument || elem ) !== document) { - setDocument(elem); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call(Expr.attrHandle, name.toLowerCase()) ? - fn(elem, name, !documentIsHTML) : - undefined; - - return val === undefined ? - support.attributes || !documentIsHTML ? - elem.getAttribute(name) : - (val = elem.getAttributeNode(name)) && val.specified ? - val.value : - null : - val; + return gridSize * Math.round(value / gridSize); }; - Sizzle.error = function (msg) { - throw new Error("Syntax error, unrecognized expression: " + msg); + var toDeg = g.toDeg = function(rad) { + + return (180 * rad / PI) % 360; }; - /** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ - Sizzle.uniqueSort = function (results) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice(0); - results.sort(sortOrder); - - if (hasDuplicate) { - while ((elem = results[i++])) { - if (elem === results[ i ]) { - j = duplicates.push(i); - } - } - while (j--) { - results.splice(duplicates[ j ], 1); - } - } + var toRad = g.toRad = function(deg, over360) { - return results; + over360 = over360 || false; + deg = over360 ? deg : (deg % 360); + return deg * PI / 180; }; - /** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ - getText = Sizzle.getText = function (elem) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if (!nodeType) { - // If no nodeType, this is expected to be an array - for (; (node = elem[i]); i++) { - // Do not traverse comment nodes - ret += getText(node); - } - } else if (nodeType === 1 || nodeType === 9 || nodeType === 11) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (see #11153) - if (typeof elem.textContent === "string") { - return elem.textContent; - } else { - // Traverse its children - for (elem = elem.firstChild; elem; elem = elem.nextSibling) { - ret += getText(elem); - } - } - } else if (nodeType === 3 || nodeType === 4) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes + // For backwards compatibility: + g.ellipse = g.Ellipse; + g.line = g.Line; + g.point = g.Point; + g.rect = g.Rect; - return ret; - }; + return g; - Expr = Sizzle.selectors = { + })(); - // Can be adjusted by the user - cacheLength: 50, +// Vectorizer. +// ----------- - createPseudo: markFunction, +// A tiny library for making your life easier when dealing with SVG. +// The only Vectorizer dependency is the Geometry library. - match: matchExpr, - attrHandle: {}, + var V; + var Vectorizer; - find: {}, + V = Vectorizer = (function() { - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, + 'use strict'; - preFilter: { - "ATTR": function (match) { - match[1] = match[1].replace(runescape, funescape); + var hasSvg = typeof window === 'object' && + !!( + window.SVGAngle || + document.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#BasicStructure', '1.1') + ); - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[4] || match[5] || "" ).replace(runescape, funescape); + // SVG support is required. + if (!hasSvg) { - if (match[2] === "~=") { - match[3] = " " + match[3] + " "; - } + // Return a function that throws an error when it is used. + return function() { + throw new Error('SVG is required to use Vectorizer.'); + }; + } - return match.slice(0, 4); - }, + // XML namespaces. + var ns = { + xmlns: 'http://www.w3.org/2000/svg', + xml: 'http://www.w3.org/XML/1998/namespace', + xlink: 'http://www.w3.org/1999/xlink' + }; - "CHILD": function (match) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if (match[1].slice(0, 3) === "nth") { - // nth-* requires argument - if (!match[3]) { - Sizzle.error(match[0]); - } + var SVGversion = '1.1'; - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + var V = function(el, attrs, children) { - // other types prohibit arguments - } else if (match[3]) { - Sizzle.error(match[0]); - } + // This allows using V() without the new keyword. + if (!(this instanceof V)) { + return V.apply(Object.create(V.prototype), arguments); + } - return match; - }, + if (!el) return; - "PSEUDO": function (match) { - var excess, - unquoted = !match[5] && match[2]; + if (V.isV(el)) { + el = el.node; + } - if (matchExpr["CHILD"].test(match[0])) { - return null; - } + attrs = attrs || {}; - // Accept quoted arguments as-is - if (match[3] && match[4] !== undefined) { - match[2] = match[4]; + if (V.isString(el)) { - // Strip excess characters from unquoted arguments - } else if (unquoted && rpseudo.test(unquoted) && - // Get excess from tokenize (recursively) - (excess = tokenize(unquoted, true)) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf(")", unquoted.length - excess) - unquoted.length)) { + if (el.toLowerCase() === 'svg') { - // excess is a negative index - match[0] = match[0].slice(0, excess); - match[2] = unquoted.slice(0, excess); - } + // Create a new SVG canvas. + el = V.createSvgDocument(); - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice(0, 3); - } - }, + } else if (el[0] === '<') { - filter: { + // Create element from an SVG string. + // Allows constructs of type: `document.appendChild(V('').node)`. - "TAG": function (nodeNameSelector) { - var nodeName = nodeNameSelector.replace(runescape, funescape).toLowerCase(); - return nodeNameSelector === "*" ? - function () { - return true; - } : - function (elem) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, + var svgDoc = V.createSvgDocument(el); - "CLASS": function (className) { - var pattern = classCache[ className + " " ]; + // Note that `V()` might also return an array should the SVG string passed as + // the first argument contain more than one root element. + if (svgDoc.childNodes.length > 1) { - return pattern || - (pattern = new RegExp("(^|" + whitespace + ")" + className + "(" + whitespace + "|$)")) && - classCache(className, function (elem) { - return pattern.test(typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || ""); - }); - }, + // Map child nodes to `V`s. + var arrayOfVels = []; + var i, len; - "ATTR": function (name, operator, check) { - return function (elem) { - var result = Sizzle.attr(elem, name); + for (i = 0, len = svgDoc.childNodes.length; i < len; i++) { - if (result == null) { - return operator === "!="; - } - if (!operator) { - return true; + var childNode = svgDoc.childNodes[i]; + arrayOfVels.push(new V(document.importNode(childNode, true))); } - result += ""; + return arrayOfVels; + } - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf(check) === 0 : - operator === "*=" ? check && result.indexOf(check) > -1 : - operator === "$=" ? check && result.slice(-check.length) === check : - operator === "~=" ? ( " " + result + " " ).indexOf(check) > -1 : - operator === "|=" ? result === check || result.slice(0, check.length + 1) === check + "-" : - false; - }; - }, + el = document.importNode(svgDoc.firstChild, true); - "CHILD": function (type, what, argument, first, last) { - var simple = type.slice(0, 3) !== "nth", - forward = type.slice(-4) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function (elem) { - return !!elem.parentNode; - } : - - function (elem, context, xml) { - var cache, outerCache, node, diff, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType; - - if (parent) { - - // :(first|last|only)-(child|of-type) - if (simple) { - while (dir) { - node = elem; - while ((node = node[ dir ])) { - if (ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1) { - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } + } else { - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if (forward && useCache) { - // Seek `elem` from a previously-cached index - outerCache = parent[ expando ] || (parent[ expando ] = {}); - cache = outerCache[ type ] || []; - nodeIndex = cache[0] === dirruns && cache[1]; - diff = cache[0] === dirruns && cache[2]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; + el = document.createElementNS(ns.xmlns, el); + } + } - while ((node = ++nodeIndex && node && node[ dir ] || + this.node = el; - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop())) { + if (!this.node.id) { + this.node.id = V.uniqueId(); + } - // When found, cache indexes on `parent` and break - if (node.nodeType === 1 && ++diff && node === elem) { - outerCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } + this.setAttributes(attrs); - // Use previously-cached element index if available - } else if (useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns) { - diff = cache[1]; + if (children) { + this.append(children); + } - // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) - } else { - // Use the same loop as above to seek `elem` from the start - while ((node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop())) { - - if (( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff) { - // Cache the index of each encountered element - if (useCache) { - (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; - } - - if (node === elem) { - break; - } - } - } - } + return this; + }; - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, + /** + * @param {SVGGElement} toElem + * @returns {SVGMatrix} + */ + V.prototype.getTransformToElement = function(toElem) { - "PSEUDO": function (pseudo, argument) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error("unsupported pseudo: " + pseudo); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if (fn[ expando ]) { - return fn(argument); - } + return toElem.getScreenCTM().inverse().multiply(this.node.getScreenCTM()); + }; - // But maintain support for old signatures - if (fn.length > 1) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty(pseudo.toLowerCase()) ? - markFunction(function (seed, matches) { - var idx, - matched = fn(seed, argument), - i = matched.length; - while (i--) { - idx = indexOf.call(seed, matched[i]); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function (elem) { - return fn(elem, 0, args); - }; - } + /** + * @param {SVGMatrix} matrix + * @param {Object=} opt + * @returns {Vectorizer|SVGMatrix} Setter / Getter + */ + V.prototype.transform = function(matrix, opt) { - return fn; - } - }, + if (V.isUndefined(matrix)) { + return (this.node.parentNode) + ? this.getTransformToElement(this.node.parentNode) + : this.node.getScreenCTM(); + } - pseudos: { - // Potentially complex pseudos - "not": markFunction(function (selector) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile(selector.replace(rtrim, "$1")); - - return matcher[ expando ] ? - markFunction(function (seed, matches, context, xml) { - var elem, - unmatched = matcher(seed, null, xml, []), - i = seed.length; - - // Match elements unmatched by `matcher` - while (i--) { - if ((elem = unmatched[i])) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function (elem, context, xml) { - input[0] = elem; - matcher(input, null, xml, results); - return !results.pop(); - }; - }), + var transformList = this.node.transform.baseVal; + if (opt && opt.absolute) { + transformList.clear(); + } - "has": markFunction(function (selector) { - return function (elem) { - return Sizzle(selector, elem).length > 0; - }; - }), + var svgTransform = V.createSVGTransform(matrix); + transformList.appendItem(svgTransform); + return this; + }; - "contains": markFunction(function (text) { - return function (elem) { - return ( elem.textContent || elem.innerText || getText(elem) ).indexOf(text) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction(function (lang) { - // lang value must be a valid identifier - if (!ridentifier.test(lang || "")) { - Sizzle.error("unsupported lang: " + lang); - } - lang = lang.replace(runescape, funescape).toLowerCase(); - return function (elem) { - var elemLang; - do { - if ((elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang"))) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf(lang + "-") === 0; - } - } while ((elem = elem.parentNode) && elem.nodeType === 1); - return false; - }; - }), + V.prototype.translate = function(tx, ty, opt) { - // Miscellaneous - "target": function (elem) { - var hash = window.location && window.location.hash; - return hash && hash.slice(1) === elem.id; - }, + opt = opt || {}; + ty = ty || 0; - "root": function (elem) { - return elem === docElem; - }, + var transformAttr = this.attr('transform') || ''; + var transform = V.parseTransformString(transformAttr); - "focus": function (elem) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, + // Is it a getter? + if (V.isUndefined(tx)) { + return transform.translate; + } - // Boolean properties - "enabled": function (elem) { - return elem.disabled === false; - }, + transformAttr = transformAttr.replace(/translate\([^\)]*\)/g, '').trim(); - "disabled": function (elem) { - return elem.disabled === true; - }, + var newTx = opt.absolute ? tx : transform.translate.tx + tx; + var newTy = opt.absolute ? ty : transform.translate.ty + ty; + var newTranslate = 'translate(' + newTx + ',' + newTy + ')'; - "checked": function (elem) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, + // Note that `translate()` is always the first transformation. This is + // usually the desired case. + this.attr('transform', (newTranslate + ' ' + transformAttr).trim()); + return this; + }; - "selected": function (elem) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if (elem.parentNode) { - elem.parentNode.selectedIndex; - } + V.prototype.rotate = function(angle, cx, cy, opt) { - return elem.selected === true; - }, + opt = opt || {}; - // Contents - "empty": function (elem) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), - // not comment, processing instructions, or others - // Thanks to Diego Perini for the nodeName shortcut - // Greater than "@" means alpha characters (specifically not starting with "#" or "?") - for (elem = elem.firstChild; elem; elem = elem.nextSibling) { - if (elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4) { - return false; - } - } - return true; - }, + var transformAttr = this.attr('transform') || ''; + var transform = V.parseTransformString(transformAttr); - "parent": function (elem) { - return !Expr.pseudos["empty"](elem); - }, + // Is it a getter? + if (V.isUndefined(angle)) { + return transform.rotate; + } - // Element/input types - "header": function (elem) { - return rheader.test(elem.nodeName); - }, + transformAttr = transformAttr.replace(/rotate\([^\)]*\)/g, '').trim(); - "input": function (elem) { - return rinputs.test(elem.nodeName); - }, + angle %= 360; - "button": function (elem) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, + var newAngle = opt.absolute ? angle : transform.rotate.angle + angle; + var newOrigin = (cx !== undefined && cy !== undefined) ? ',' + cx + ',' + cy : ''; + var newRotate = 'rotate(' + newAngle + newOrigin + ')'; - "text": function (elem) { - var attr; - // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) - // use getAttribute instead to test this case - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); - }, + this.attr('transform', (transformAttr + ' ' + newRotate).trim()); + return this; + }; - // Position-in-collection - "first": createPositionalPseudo(function () { - return [ 0 ]; - }), + // Note that `scale` as the only transformation does not combine with previous values. + V.prototype.scale = function(sx, sy) { - "last": createPositionalPseudo(function (matchIndexes, length) { - return [ length - 1 ]; - }), + sy = V.isUndefined(sy) ? sx : sy; - "eq": createPositionalPseudo(function (matchIndexes, length, argument) { - return [ argument < 0 ? argument + length : argument ]; - }), + var transformAttr = this.attr('transform') || ''; + var transform = V.parseTransformString(transformAttr); - "even": createPositionalPseudo(function (matchIndexes, length) { - var i = 0; - for (; i < length; i += 2) { - matchIndexes.push(i); - } - return matchIndexes; - }), + // Is it a getter? + if (V.isUndefined(sx)) { + return transform.scale; + } - "odd": createPositionalPseudo(function (matchIndexes, length) { - var i = 1; - for (; i < length; i += 2) { - matchIndexes.push(i); - } - return matchIndexes; - }), + transformAttr = transformAttr.replace(/scale\([^\)]*\)/g, '').trim(); - "lt": createPositionalPseudo(function (matchIndexes, length, argument) { - var i = argument < 0 ? argument + length : argument; - for (; --i >= 0;) { - matchIndexes.push(i); - } - return matchIndexes; - }), + var newScale = 'scale(' + sx + ',' + sy + ')'; - "gt": createPositionalPseudo(function (matchIndexes, length, argument) { - var i = argument < 0 ? argument + length : argument; - for (; ++i < length;) { - matchIndexes.push(i); - } - return matchIndexes; - }) - } + this.attr('transform', (transformAttr + ' ' + newScale).trim()); + return this; }; - Expr.pseudos["nth"] = Expr.pseudos["eq"]; + // Get SVGRect that contains coordinates and dimension of the real bounding box, + // i.e. after transformations are applied. + // If `target` is specified, bounding box will be computed relatively to `target` element. + V.prototype.bbox = function(withoutTransformations, target) { -// Add button/input type pseudos - for (i in { radio: true, checkbox: true, file: true, password: true, image: true }) { - Expr.pseudos[ i ] = createInputPseudo(i); - } - for (i in { submit: true, reset: true }) { - Expr.pseudos[ i ] = createButtonPseudo(i); - } + // If the element is not in the live DOM, it does not have a bounding box defined and + // so fall back to 'zero' dimension element. + if (!this.node.ownerSVGElement) return { x: 0, y: 0, width: 0, height: 0 }; -// Easy API for creating new setFilters - function setFilters() { - } + var box; + try { - setFilters.prototype = Expr.filters = Expr.pseudos; - Expr.setFilters = new setFilters(); + box = this.node.getBBox(); + // We are creating a new object as the standard says that you can't + // modify the attributes of a bbox. + box = { x: box.x, y: box.y, width: box.width, height: box.height }; - function tokenize(selector, parseOnly) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; + } catch (e) { - if (cached) { - return parseOnly ? 0 : cached.slice(0); + // Fallback for IE. + box = { + x: this.node.clientLeft, + y: this.node.clientTop, + width: this.node.clientWidth, + height: this.node.clientHeight + }; } - soFar = selector; - groups = []; - preFilters = Expr.preFilter; + if (withoutTransformations) { - while (soFar) { + return box; + } - // Comma and first run - if (!matched || (match = rcomma.exec(soFar))) { - if (match) { - // Don't consume trailing commas as valid - soFar = soFar.slice(match[0].length) || soFar; - } - groups.push(tokens = []); - } + var matrix = this.getTransformToElement(target || this.node.ownerSVGElement); - matched = false; + return V.transformRect(box, matrix); + }; - // Combinators - if ((match = rcombinators.exec(soFar))) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace(rtrim, " ") - }); - soFar = soFar.slice(matched.length); - } - - // Filters - for (type in Expr.filter) { - if ((match = matchExpr[ type ].exec(soFar)) && (!preFilters[ type ] || - (match = preFilters[ type ](match)))) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice(matched.length); - } - } + V.prototype.text = function(content, opt) { - if (!matched) { - break; - } + // Replace all spaces with the Unicode No-break space (http://www.fileformat.info/info/unicode/char/a0/index.htm). + // IE would otherwise collapse all spaces into one. + content = V.sanitizeText(content); + opt = opt || {}; + var lines = content.split('\n'); + var tspan; + + // `alignment-baseline` does not work in Firefox. + // Setting `dominant-baseline` on the `` element doesn't work in IE9. + // In order to have the 0,0 coordinate of the `` element (or the first ``) + // in the top left corner we translate the `` element by `0.8em`. + // See `http://www.w3.org/Graphics/SVG/WG/wiki/How_to_determine_dominant_baseline`. + // See also `http://apike.ca/prog_svg_text_style.html`. + var y = this.attr('y'); + if (!y) { + this.attr('y', '0.8em'); } - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error(selector) : - // Cache the tokens - tokenCache(selector, groups).slice(0); - } + // An empty text gets rendered into the DOM in webkit-based browsers. + // In order to unify this behaviour across all browsers + // we rather hide the text element when it's empty. + this.attr('display', content ? null : 'none'); - function toSelector(tokens) { - var i = 0, - len = tokens.length, - selector = ""; - for (; i < len; i++) { - selector += tokens[i].value; - } - return selector; - } + // Preserve spaces. In other words, we do not want consecutive spaces to get collapsed to one. + this.attr('xml:space', 'preserve'); - function addCombinator(matcher, combinator, base) { - var dir = combinator.dir, - checkNonElements = base && dir === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function (elem, context, xml) { - while ((elem = elem[ dir ])) { - if (elem.nodeType === 1 || checkNonElements) { - return matcher(elem, context, xml); - } - } - } : - - // Check against all ancestor/preceding elements - function (elem, context, xml) { - var data, cache, outerCache, - dirkey = dirruns + " " + doneName; - - // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching - if (xml) { - while ((elem = elem[ dir ])) { - if (elem.nodeType === 1 || checkNonElements) { - if (matcher(elem, context, xml)) { - return true; - } - } - } - } else { - while ((elem = elem[ dir ])) { - if (elem.nodeType === 1 || checkNonElements) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - if ((cache = outerCache[ dir ]) && cache[0] === dirkey) { - if ((data = cache[1]) === true || data === cachedruns) { - return data === true; - } - } else { - cache = outerCache[ dir ] = [ dirkey ]; - cache[1] = matcher(elem, context, xml) || cachedruns; - if (cache[1] === true) { - return true; - } - } - } - } - } - }; - } + // Easy way to erase all `` children; + this.node.textContent = ''; - function elementMatcher(matchers) { - return matchers.length > 1 ? - function (elem, context, xml) { - var i = matchers.length; - while (i--) { - if (!matchers[i](elem, context, xml)) { - return false; - } - } - return true; - } : - matchers[0]; - } + var textNode = this.node; - function condense(unmatched, map, filter, context, xml) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for (; i < len; i++) { - if ((elem = unmatched[i])) { - if (!filter || filter(elem, context, xml)) { - newUnmatched.push(elem); - if (mapped) { - map.push(i); - } - } + if (opt.textPath) { + + // Wrap the text in the SVG element that points + // to a path defined by `opt.textPath` inside the internal `` element. + var defs = this.find('defs'); + if (defs.length === 0) { + defs = V('defs'); + this.append(defs); } - } - return newUnmatched; - } + // If `opt.textPath` is a plain string, consider it to be directly the + // SVG path data for the text to go along (this is a shortcut). + // Otherwise if it is an object and contains the `d` property, then this is our path. + var d = Object(opt.textPath) === opt.textPath ? opt.textPath.d : opt.textPath; + if (d) { + var path = V('path', { d: d }); + defs.append(path); + } - function setMatcher(preFilter, selector, matcher, postFilter, postFinder, postSelector) { - if (postFilter && !postFilter[ expando ]) { - postFilter = setMatcher(postFilter); - } - if (postFinder && !postFinder[ expando ]) { - postFinder = setMatcher(postFinder, postSelector); + var textPath = V('textPath'); + // Set attributes on the ``. The most important one + // is the `xlink:href` that points to our newly created `` element in ``. + // Note that we also allow the following construct: + // `t.text('my text', { textPath: { 'xlink:href': '#my-other-path' } })`. + // In other words, one can completely skip the auto-creation of the path + // and use any other arbitrary path that is in the document. + if (!opt.textPath['xlink:href'] && path) { + textPath.attr('xlink:href', '#' + path.node.id); + } + + if (Object(opt.textPath) === opt.textPath) { + textPath.attr(opt.textPath); + } + this.append(textPath); + // Now all the ``s will be inside the ``. + textNode = textPath.node; } - return markFunction(function (seed, results, context, xml) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - // Get initial elements from seed or context - elems = seed || multipleContexts(selector || "*", context.nodeType ? [ context ] : context, []), + var offset = 0; - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense(elems, preMap, preFilter, context, xml) : - elems, + for (var i = 0; i < lines.length; i++) { - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + var line = lines[i]; + // Shift all the but first by one line (`1em`) + var lineHeight = opt.lineHeight || '1em'; + if (opt.lineHeight === 'auto') { + lineHeight = '1.5em'; + } + var vLine = V('tspan', { dy: (i == 0 ? '0em' : lineHeight), x: this.attr('x') || 0 }); + vLine.addClass('v-line'); - // ...intermediate processing is necessary - [] : + if (line) { - // ...otherwise use results directly - results : - matcherIn; + if (opt.annotations) { - // Find primary matches - if (matcher) { - matcher(matcherIn, matcherOut, context, xml); - } + // Get the line height based on the biggest font size in the annotations for this line. + var maxFontSize = 0; - // Apply postFilter - if (postFilter) { - temp = condense(matcherOut, postMap); - postFilter(temp, [], context, xml); + // Find the *compacted* annotations for this line. + var lineAnnotations = V.annotateString(lines[i], V.isArray(opt.annotations) ? opt.annotations : [opt.annotations], { offset: -offset, includeAnnotationIndices: opt.includeAnnotationIndices }); + for (var j = 0; j < lineAnnotations.length; j++) { - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while (i--) { - if ((elem = temp[i])) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } + var annotation = lineAnnotations[j]; + if (V.isObject(annotation)) { + + var fontSize = parseInt(annotation.attrs['font-size'], 10); + if (fontSize && fontSize > maxFontSize) { + maxFontSize = fontSize; + } - if (seed) { - if (postFinder || preFilter) { - if (postFinder) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while (i--) { - if ((elem = matcherOut[i])) { - // Restore matcherIn since elem is not yet a final match - temp.push((matcherIn[i] = elem)); + tspan = V('tspan', annotation.attrs); + if (opt.includeAnnotationIndices) { + // If `opt.includeAnnotationIndices` is `true`, + // set the list of indices of all the applied annotations + // in the `annotations` attribute. This list is a comma + // separated list of indices. + tspan.attr('annotations', annotation.annotations); } + if (annotation.attrs['class']) { + tspan.addClass(annotation.attrs['class']); + } + tspan.node.textContent = annotation.t; + + } else { + + tspan = document.createTextNode(annotation || ' '); + } - postFinder(null, (matcherOut = []), temp, xml); + vLine.append(tspan); } - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while (i--) { - if ((elem = matcherOut[i]) && - (temp = postFinder ? indexOf.call(seed, elem) : preMap[i]) > -1) { + if (opt.lineHeight === 'auto' && maxFontSize && i !== 0) { - seed[temp] = !(results[temp] = elem); - } + vLine.attr('dy', (maxFontSize * 1.2) + 'px'); } - } - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice(preexisting, matcherOut.length) : - matcherOut - ); - if (postFinder) { - postFinder(null, results, matcherOut, xml); } else { - push.apply(results, matcherOut); + + vLine.node.textContent = line; } - } - }); - } - function matcherFromTokens(tokens) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator(function (elem) { - return elem === checkContext; - }, implicitRelative, true), - matchAnyContext = addCombinator(function (elem) { - return indexOf.call(checkContext, elem) > -1; - }, implicitRelative, true), - matchers = [ function (elem, context, xml) { - return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext(elem, context, xml) : - matchAnyContext(elem, context, xml) ); - } ]; - - for (; i < len; i++) { - if ((matcher = Expr.relative[ tokens[i].type ])) { - matchers = [ addCombinator(elementMatcher(matchers), matcher) ]; } else { - matcher = Expr.filter[ tokens[i].type ].apply(null, tokens[i].matches); - - // Return special upon seeing a positional matcher - if (matcher[ expando ]) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for (; j < len; j++) { - if (Expr.relative[ tokens[j].type ]) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher(matchers), - i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice(0, i - 1).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) - ).replace(rtrim, "$1"), - matcher, - i < j && matcherFromTokens(tokens.slice(i, j)), - j < len && matcherFromTokens((tokens = tokens.slice(j))), - j < len && toSelector(tokens) - ); - } - matchers.push(matcher); + + // Make sure the textContent is never empty. If it is, add a dummy + // character and make it invisible, making the following lines correctly + // relatively positioned. `dy=1em` won't work with empty lines otherwise. + vLine.addClass('v-empty-line'); + // 'opacity' needs to be specified with fill, stroke. Opacity without specification + // is not applied in Firefox + vLine.node.style.fillOpacity = 0; + vLine.node.style.strokeOpacity = 0; + vLine.node.textContent = '-'; } + + V(textNode).append(vLine); + + offset += line.length + 1; // + 1 = newline character. } - return elementMatcher(matchers); - } + return this; + }; - function matcherFromGroupMatchers(elementMatchers, setMatchers) { - // A counter to specify which element is currently being matched - var matcherCachedRuns = 0, - bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function (seed, context, xml, results, expandContext) { - var elem, j, matcher, - setMatched = [], - matchedCount = 0, - i = "0", - unmatched = seed && [], - outermost = expandContext != null, - contextBackup = outermostContext, - // We must always have either seed elements or context - elems = seed || byElement && Expr.find["TAG"]("*", expandContext && context.parentNode || context), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); - - if (outermost) { - outermostContext = context !== document && context; - cachedruns = matcherCachedRuns; - } + /** + * @public + * @param {string} name + * @returns {Vectorizer} + */ + V.prototype.removeAttr = function(name) { - // Add elements passing elementMatchers directly to results - // Keep `i` a string if there are no elements so `matchedCount` will be "00" below - for (; (elem = elems[i]) != null; i++) { - if (byElement && elem) { - j = 0; - while ((matcher = elementMatchers[j++])) { - if (matcher(elem, context, xml)) { - results.push(elem); - break; - } - } - if (outermost) { - dirruns = dirrunsUnique; - cachedruns = ++matcherCachedRuns; - } - } + var qualifiedName = V.qualifyAttr(name); + var el = this.node; - // Track unmatched elements for set filters - if (bySet) { - // They will have gone through all possible matchers - if ((elem = !matcher && elem)) { - matchedCount--; - } + if (qualifiedName.ns) { + if (el.hasAttributeNS(qualifiedName.ns, qualifiedName.local)) { + el.removeAttributeNS(qualifiedName.ns, qualifiedName.local); + } + } else if (el.hasAttribute(name)) { + el.removeAttribute(name); + } + return this; + }; - // Lengthen the array for every element, matched or not - if (seed) { - unmatched.push(elem); - } - } - } + V.prototype.attr = function(name, value) { - // Apply set filters to unmatched elements - matchedCount += i; - if (bySet && i !== matchedCount) { - j = 0; - while ((matcher = setMatchers[j++])) { - matcher(unmatched, setMatched, context, xml); - } + if (V.isUndefined(name)) { - if (seed) { - // Reintegrate element matches to eliminate the need for sorting - if (matchedCount > 0) { - while (i--) { - if (!(unmatched[i] || setMatched[i])) { - setMatched[i] = pop.call(results); - } - } - } + // Return all attributes. + var attributes = this.node.attributes; + var attrs = {}; - // Discard index placeholder values to get only actual matches - setMatched = condense(setMatched); - } + for (var i = 0; i < attributes.length; i++) { + attrs[attributes[i].name] = attributes[i].value; + } - // Add matches to results - push.apply(results, setMatched); + return attrs; + } - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if (outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1) { + if (V.isString(name) && V.isUndefined(value)) { + return this.node.getAttribute(name); + } - Sizzle.uniqueSort(results); - } - } + if (typeof name === 'object') { - // Override manipulation of globals by nested matchers - if (outermost) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; + for (var attrName in name) { + if (name.hasOwnProperty(attrName)) { + this.setAttribute(attrName, name[attrName]); } + } - return unmatched; - }; + } else { - return bySet ? - markFunction(superMatcher) : - superMatcher; - } + this.setAttribute(name, value); + } - compile = Sizzle.compile = function (selector, group /* Internal Use Only */) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if (!cached) { - // Generate a function of recursive functions that can be used to check each element - if (!group) { - group = tokenize(selector); - } - i = group.length; - while (i--) { - cached = matcherFromTokens(group[i]); - if (cached[ expando ]) { - setMatchers.push(cached); - } else { - elementMatchers.push(cached); - } - } + return this; + }; - // Cache the compiled function - cached = compilerCache(selector, matcherFromGroupMatchers(elementMatchers, setMatchers)); + V.prototype.remove = function() { + + if (this.node.parentNode) { + this.node.parentNode.removeChild(this.node); } - return cached; + + return this; }; - function multipleContexts(selector, contexts, results) { - var i = 0, - len = contexts.length; - for (; i < len; i++) { - Sizzle(selector, contexts[i], results); + V.prototype.empty = function() { + + while (this.node.firstChild) { + this.node.removeChild(this.node.firstChild); } - return results; - } - function select(selector, context, results, seed) { - var i, tokens, token, type, find, - match = tokenize(selector); + return this; + }; - if (!seed) { - // Try to minimize operations if there is only one group - if (match.length === 1) { + V.prototype.setAttributes = function(attrs) { - // Take a shortcut and set the context if the root selector is an ID - tokens = match[0] = match[0].slice(0); - if (tokens.length > 2 && (token = tokens[0]).type === "ID" && - support.getById && context.nodeType === 9 && documentIsHTML && - Expr.relative[ tokens[1].type ]) { + for (var key in attrs) { + if (attrs.hasOwnProperty(key)) { + this.setAttribute(key, attrs[key]); + } + } - context = ( Expr.find["ID"](token.matches[0].replace(runescape, funescape), context) || [] )[0]; - if (!context) { - return results; - } - selector = selector.slice(tokens.shift().value.length); - } + return this; + }; - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length; - while (i--) { - token = tokens[i]; + V.prototype.append = function(els) { - // Abort if we hit a combinator - if (Expr.relative[ (type = token.type) ]) { - break; - } - if ((find = Expr.find[ type ])) { - // Search, expanding context for leading sibling combinators - if ((seed = find( - token.matches[0].replace(runescape, funescape), - rsibling.test(tokens[0].type) && context.parentNode || context - ))) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice(i, 1); - selector = seed.length && toSelector(tokens); - if (!selector) { - push.apply(results, seed); - return results; - } + if (!V.isArray(els)) { + els = [els]; + } - break; - } - } - } - } + for (var i = 0, len = els.length; i < len; i++) { + this.node.appendChild(V.toNode(els[i])); } - // Compile and execute a filtering function - // Provide `match` to avoid retokenization if we modified the selector above - compile(selector, match)( - seed, - context, - !documentIsHTML, - results, - rsibling.test(selector) - ); - return results; - } + return this; + }; -// One-time assignments + V.prototype.prepend = function(els) { -// Sort stability - support.sortStable = expando.split("").sort(sortOrder).join("") === expando; + var child = this.node.firstChild; + return child ? V(child).before(els) : this.append(els); + }; -// Support: Chrome<14 -// Always assume duplicates if they aren't passed to the comparison function - support.detectDuplicates = hasDuplicate; + V.prototype.before = function(els) { -// Initialize against the default document - setDocument(); + var node = this.node; + var parent = node.parentNode; -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* - support.sortDetached = assert(function (div1) { - // Should return 1, but returns 4 (following) - return div1.compareDocumentPosition(document.createElement("div")) & 1; - }); + if (parent) { -// Support: IE<8 -// Prevent attribute/property "interpolation" -// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx - if (!assert(function (div) { - div.innerHTML = ""; - return div.firstChild.getAttribute("href") === "#"; - })) { - addHandle("type|href|height|width", function (elem, name, isXML) { - if (!isXML) { - return elem.getAttribute(name, name.toLowerCase() === "type" ? 1 : 2); + if (!V.isArray(els)) { + els = [els]; } - }); - } -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") - if (!support.attributes || !assert(function (div) { - div.innerHTML = ""; - div.firstChild.setAttribute("value", ""); - return div.firstChild.getAttribute("value") === ""; - })) { - addHandle("value", function (elem, name, isXML) { - if (!isXML && elem.nodeName.toLowerCase() === "input") { - return elem.defaultValue; + for (var i = 0, len = els.length; i < len; i++) { + parent.insertBefore(V.toNode(els[i]), node); } - }); - } - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies - if (!assert(function (div) { - return div.getAttribute("disabled") == null; - })) { - addHandle(booleans, function (elem, name, isXML) { - var val; - if (!isXML) { - return (val = elem.getAttributeNode(name)) && val.specified ? - val.value : - elem[ name ] === true ? name.toLowerCase() : null; - } - }); - } + } - jQuery.find = Sizzle; - jQuery.expr = Sizzle.selectors; - jQuery.expr[":"] = jQuery.expr.pseudos; - jQuery.unique = Sizzle.uniqueSort; - jQuery.text = Sizzle.getText; - jQuery.isXMLDoc = Sizzle.isXML; - jQuery.contains = Sizzle.contains; + return this; + }; + V.prototype.svg = function() { - })(window); -// String to Object options format cache - var optionsCache = {}; + return this.node instanceof window.SVGSVGElement ? this : V(this.node.ownerSVGElement); + }; -// Convert String-formatted options into Object-formatted ones and store in cache - function createOptions(options) { - var object = optionsCache[ options ] = {}; - jQuery.each(options.match(core_rnotwhite) || [], function (_, flag) { - object[ flag ] = true; - }); - return object; - } + V.prototype.defs = function() { - /* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ - jQuery.Callbacks = function (options) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - ( optionsCache[ options ] || createOptions(options) ) : - jQuery.extend({}, options); - - var // Last fire value (for non-forgettable lists) - memory, - // Flag to know if list was already fired - fired, - // Flag to know if list is currently firing - firing, - // First callback to fire (used internally by add and fireWith) - firingStart, - // End of the loop when firing - firingLength, - // Index of currently firing callback (modified by remove if needed) - firingIndex, - // Actual callback list - list = [], - // Stack of fire calls for repeatable lists - stack = !options.once && [], - // Fire callbacks - fire = function (data) { - memory = options.memory && data; - fired = true; - firingIndex = firingStart || 0; - firingStart = 0; - firingLength = list.length; - firing = true; - for (; list && firingIndex < firingLength; firingIndex++) { - if (list[ firingIndex ].apply(data[ 0 ], data[ 1 ]) === false && options.stopOnFalse) { - memory = false; // To prevent further calls using add - break; - } - } - firing = false; - if (list) { - if (stack) { - if (stack.length) { - fire(stack.shift()); - } - } else if (memory) { - list = []; - } else { - self.disable(); - } - } - }, - // Actual Callbacks object - self = { - // Add a callback or a collection of callbacks to the list - add: function () { - if (list) { - // First, we save the current length - var start = list.length; - (function add(args) { - jQuery.each(args, function (_, arg) { - var type = jQuery.type(arg); - if (type === "function") { - if (!options.unique || !self.has(arg)) { - list.push(arg); - } - } else if (arg && arg.length && type !== "string") { - // Inspect recursively - add(arg); - } - }); - })(arguments); - // Do we need to add the callbacks to the - // current firing batch? - if (firing) { - firingLength = list.length; - // With memory, if we're not firing then - // we should call right away - } else if (memory) { - firingStart = start; - fire(memory); - } - } - return this; - }, - // Remove a callback from the list - remove: function () { - if (list) { - jQuery.each(arguments, function (_, arg) { - var index; - while (( index = jQuery.inArray(arg, list, index) ) > -1) { - list.splice(index, 1); - // Handle firing indexes - if (firing) { - if (index <= firingLength) { - firingLength--; - } - if (index <= firingIndex) { - firingIndex--; - } - } - } - }); - } - return this; - }, - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function (fn) { - return fn ? jQuery.inArray(fn, list) > -1 : !!( list && list.length ); - }, - // Remove all callbacks from the list - empty: function () { - list = []; - firingLength = 0; - return this; - }, - // Have the list do nothing anymore - disable: function () { - list = stack = memory = undefined; - return this; - }, - // Is it disabled? - disabled: function () { - return !list; - }, - // Lock the list in its current state - lock: function () { - stack = undefined; - if (!memory) { - self.disable(); - } - return this; - }, - // Is it locked? - locked: function () { - return !stack; - }, - // Call all callbacks with the given context and arguments - fireWith: function (context, args) { - if (list && ( !fired || stack )) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - if (firing) { - stack.push(args); - } else { - fire(args); - } - } - return this; - }, - // Call all the callbacks with the given arguments - fire: function () { - self.fireWith(this, arguments); - return this; - }, - // To know if the callbacks have already been called at least once - fired: function () { - return !!fired; - } - }; + var defs = this.svg().node.getElementsByTagName('defs'); - return self; - }; - jQuery.extend({ - - Deferred: function (func) { - var tuples = [ - // action, add listener, listener list, final state - [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], - [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], - [ "notify", "progress", jQuery.Callbacks("memory") ] - ], - state = "pending", - promise = { - state: function () { - return state; - }, - always: function () { - deferred.done(arguments).fail(arguments); - return this; - }, - then: function (/* fnDone, fnFail, fnProgress */) { - var fns = arguments; - return jQuery.Deferred(function (newDefer) { - jQuery.each(tuples, function (i, tuple) { - var action = tuple[ 0 ], - fn = jQuery.isFunction(fns[ i ]) && fns[ i ]; - // deferred[ done | fail | progress ] for forwarding actions to newDefer - deferred[ tuple[1] ](function () { - var returned = fn && fn.apply(this, arguments); - if (returned && jQuery.isFunction(returned.promise)) { - returned.promise() - .done(newDefer.resolve) - .fail(newDefer.reject) - .progress(newDefer.notify); - } else { - newDefer[ action + "With" ](this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments); - } - }); - }); - fns = null; - }).promise(); - }, - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function (obj) { - return obj != null ? jQuery.extend(obj, promise) : promise; - } - }, - deferred = {}; + return (defs && defs.length) ? V(defs[0]) : undefined; + }; - // Keep pipe for back-compat - promise.pipe = promise.then; + V.prototype.clone = function() { - // Add list-specific methods - jQuery.each(tuples, function (i, tuple) { - var list = tuple[ 2 ], - stateString = tuple[ 3 ]; + var clone = V(this.node.cloneNode(true/* deep */)); + // Note that clone inherits also ID. Therefore, we need to change it here. + clone.node.id = V.uniqueId(); + return clone; + }; - // promise[ done | fail | progress ] = list.add - promise[ tuple[1] ] = list.add; + V.prototype.findOne = function(selector) { - // Handle state - if (stateString) { - list.add(function () { - // state = [ resolved | rejected ] - state = stateString; + var found = this.node.querySelector(selector); + return found ? V(found) : undefined; + }; - // [ reject_list | resolve_list ].disable; progress_list.lock - }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock); - } + V.prototype.find = function(selector) { - // deferred[ resolve | reject | notify ] - deferred[ tuple[0] ] = function () { - deferred[ tuple[0] + "With" ](this === deferred ? promise : this, arguments); - return this; - }; - deferred[ tuple[0] + "With" ] = list.fireWith; - }); + var vels = []; + var nodes = this.node.querySelectorAll(selector); - // Make the deferred a promise - promise.promise(deferred); + if (nodes) { - // Call given func if any - if (func) { - func.call(deferred, deferred); + // Map DOM elements to `V`s. + for (var i = 0; i < nodes.length; i++) { + vels.push(V(nodes[i])); + } } - // All done! - return deferred; - }, - - // Deferred helper - when: function (subordinate /* , ..., subordinateN */) { - var i = 0, - resolveValues = core_slice.call(arguments), - length = resolveValues.length, - - // the count of uncompleted subordinates - remaining = length !== 1 || ( subordinate && jQuery.isFunction(subordinate.promise) ) ? length : 0, + return vels; + }; - // the master Deferred. If resolveValues consist of only a single Deferred, just use that. - deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + // Find an index of an element inside its container. + V.prototype.index = function() { - // Update function for both resolve and progress values - updateFunc = function (i, contexts, values) { - return function (value) { - contexts[ i ] = this; - values[ i ] = arguments.length > 1 ? core_slice.call(arguments) : value; - if (values === progressValues) { - deferred.notifyWith(contexts, values); - } else if (!( --remaining )) { - deferred.resolveWith(contexts, values); - } - }; - }, + var index = 0; + var node = this.node.previousSibling; - progressValues, progressContexts, resolveContexts; - - // add listeners to Deferred subordinates; treat others as resolved - if (length > 1) { - progressValues = new Array(length); - progressContexts = new Array(length); - resolveContexts = new Array(length); - for (; i < length; i++) { - if (resolveValues[ i ] && jQuery.isFunction(resolveValues[ i ].promise)) { - resolveValues[ i ].promise() - .done(updateFunc(i, resolveContexts, resolveValues)) - .fail(deferred.reject) - .progress(updateFunc(i, progressContexts, progressValues)); - } else { - --remaining; - } - } + while (node) { + // nodeType 1 for ELEMENT_NODE + if (node.nodeType === 1) index++; + node = node.previousSibling; } - // if we're not waiting on anything, resolve the master - if (!remaining) { - deferred.resolveWith(resolveContexts, resolveValues); - } + return index; + }; - return deferred.promise(); - } - }); - jQuery.support = (function (support) { - var input = document.createElement("input"), - fragment = document.createDocumentFragment(), - div = document.createElement("div"), - select = document.createElement("select"), - opt = select.appendChild(document.createElement("option")); - - // Finish early in limited environments - if (!input.type) { - return support; - } + V.prototype.findParentByClass = function(className, terminator) { - input.type = "checkbox"; - - // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 - // Check the default checkbox/radio value ("" on old WebKit; "on" elsewhere) - support.checkOn = input.value !== ""; - - // Must access the parent to make an option select properly - // Support: IE9, IE10 - support.optSelected = opt.selected; - - // Will be defined later - support.reliableMarginRight = true; - support.boxSizingReliable = true; - support.pixelPosition = false; - - // Make sure checked status is properly cloned - // Support: IE9, IE10 - input.checked = true; - support.noCloneChecked = input.cloneNode(true).checked; - - // Make sure that the options inside disabled selects aren't marked as disabled - // (WebKit marks them as disabled) - select.disabled = true; - support.optDisabled = !opt.disabled; - - // Check if an input maintains its value after becoming a radio - // Support: IE9, IE10 - input = document.createElement("input"); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; - - // #11217 - WebKit loses check when the name is after the checked attribute - input.setAttribute("checked", "t"); - input.setAttribute("name", "t"); - - fragment.appendChild(input); - - // Support: Safari 5.1, Android 4.x, Android 2.3 - // old WebKit doesn't clone checked state correctly in fragments - support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; - - // Support: Firefox, Chrome, Safari - // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) - support.focusinBubbles = "onfocusin" in window; - - div.style.backgroundClip = "content-box"; - div.cloneNode(true).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - // Run tests that need a body at doc ready - jQuery(function () { - var container, marginDiv, - // Support: Firefox, Android 2.3 (Prefixed box-sizing versions). - divReset = "padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box", - body = document.getElementsByTagName("body")[ 0 ]; - - if (!body) { - // Return for frameset docs that don't have a body - return; - } + var ownerSVGElement = this.node.ownerSVGElement; + var node = this.node.parentNode; - container = document.createElement("div"); - container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; + while (node && node !== terminator && node !== ownerSVGElement) { - // Check box-sizing and margin behavior. - body.appendChild(container).appendChild(div); - div.innerHTML = ""; - // Support: Firefox, Android 2.3 (Prefixed box-sizing versions). - div.style.cssText = "-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%"; + var vel = V(node); + if (vel.hasClass(className)) { + return vel; + } - // Workaround failing boxSizing test due to offsetWidth returning wrong value - // with some non-1 values of body zoom, ticket #13543 - jQuery.swap(body, body.style.zoom != null ? { zoom: 1 } : {}, function () { - support.boxSizing = div.offsetWidth === 4; - }); + node = node.parentNode; + } - // Use window.getComputedStyle because jsdom on node.js will break without it. - if (window.getComputedStyle) { - support.pixelPosition = ( window.getComputedStyle(div, null) || {} ).top !== "1%"; - support.boxSizingReliable = ( window.getComputedStyle(div, null) || { width: "4px" } ).width === "4px"; + return null; + }; - // Support: Android 2.3 - // Check if div with explicit width and no margin-right incorrectly - // gets computed margin-right based on width of container. (#3333) - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - marginDiv = div.appendChild(document.createElement("div")); - marginDiv.style.cssText = div.style.cssText = divReset; - marginDiv.style.marginRight = marginDiv.style.width = "0"; - div.style.width = "1px"; + // Convert global point into the coordinate space of this element. + V.prototype.toLocalPoint = function(x, y) { - support.reliableMarginRight = !parseFloat(( window.getComputedStyle(marginDiv, null) || {} ).marginRight); - } + var svg = this.svg().node; - body.removeChild(container); - }); + var p = svg.createSVGPoint(); + p.x = x; + p.y = y; - return support; - })({}); + try { - /* - Implementation Summary + var globalPoint = p.matrixTransform(svg.getScreenCTM().inverse()); + var globalToLocalMatrix = this.getTransformToElement(svg).inverse(); - 1. Enforce API surface and semantic compatibility with 1.9.x branch - 2. Improve the module's maintainability by reducing the storage - paths to a single mechanism. - 3. Use the same single mechanism to support "private" and "user" data. - 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) - 5. Avoid exposing implementation details on user objects (eg. expando properties) - 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - */ - var data_user, data_priv, - rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, - rmultiDash = /([A-Z])/g; - - function Data() { - // Support: Android < 4, - // Old WebKit does not have Object.preventExtensions/freeze method, - // return new empty object instead with no [[set]] accessor - Object.defineProperty(this.cache = {}, 0, { - get: function () { - return {}; + } catch (e) { + // IE9 throws an exception in odd cases. (`Unexpected call to method or property access`) + // We have to make do with the original coordianates. + return p; } - }); - - this.expando = jQuery.expando + Math.random(); - } - Data.uid = 1; - - Data.accepts = function (owner) { - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType ? - owner.nodeType === 1 || owner.nodeType === 9 : true; - }; + return globalPoint.matrixTransform(globalToLocalMatrix); + }; - Data.prototype = { - key: function (owner) { - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return the key for a frozen object. - if (!Data.accepts(owner)) { - return 0; - } + V.prototype.translateCenterToPoint = function(p) { - var descriptor = {}, - // Check if the owner object already has a cache key - unlock = owner[ this.expando ]; + var bbox = this.bbox(); + var center = g.rect(bbox).center(); - // If not, create one - if (!unlock) { - unlock = Data.uid++; + this.translate(p.x - center.x, p.y - center.y); + }; - // Secure it in a non-enumerable, non-writable property - try { - descriptor[ this.expando ] = { value: unlock }; - Object.defineProperties(owner, descriptor); + // Efficiently auto-orient an element. This basically implements the orient=auto attribute + // of markers. The easiest way of understanding on what this does is to imagine the element is an + // arrowhead. Calling this method on the arrowhead makes it point to the `position` point while + // being auto-oriented (properly rotated) towards the `reference` point. + // `target` is the element relative to which the transformations are applied. Usually a viewport. + V.prototype.translateAndAutoOrient = function(position, reference, target) { - // Support: Android < 4 - // Fallback to a less secure definition - } catch (e) { - descriptor[ this.expando ] = unlock; - jQuery.extend(owner, descriptor); - } - } + // Clean-up previously set transformations except the scale. If we didn't clean up the + // previous transformations then they'd add up with the old ones. Scale is an exception as + // it doesn't add up, consider: `this.scale(2).scale(2).scale(2)`. The result is that the + // element is scaled by the factor 2, not 8. - // Ensure the cache object - if (!this.cache[ unlock ]) { - this.cache[ unlock ] = {}; - } + var s = this.scale(); + this.attr('transform', ''); + this.scale(s.sx, s.sy); - return unlock; - }, - set: function (owner, data, value) { - var prop, - // There may be an unlock assigned to this node, - // if there is no entry for this "owner", create one inline - // and set the unlock as though an owner entry had always existed - unlock = this.key(owner), - cache = this.cache[ unlock ]; + var svg = this.svg().node; + var bbox = this.bbox(false, target); - // Handle: [ owner, key, value ] args - if (typeof data === "string") { - cache[ data ] = value; + // 1. Translate to origin. + var translateToOrigin = svg.createSVGTransform(); + translateToOrigin.setTranslate(-bbox.x - bbox.width / 2, -bbox.y - bbox.height / 2); - // Handle: [ owner, { properties } ] args - } else { - // Fresh assignments by object are shallow copied - if (jQuery.isEmptyObject(cache)) { - jQuery.extend(this.cache[ unlock ], data); - // Otherwise, copy the properties one-by-one to the cache object - } else { - for (prop in data) { - cache[ prop ] = data[ prop ]; - } - } - } - return cache; - }, - get: function (owner, key) { - // Either a valid cache is found, or will be created. - // New caches will be created and the unlock returned, - // allowing direct access to the newly created - // empty data object. A valid owner object must be provided. - var cache = this.cache[ this.key(owner) ]; - - return key === undefined ? - cache : cache[ key ]; - }, - access: function (owner, key, value) { - var stored; - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if (key === undefined || - ((key && typeof key === "string") && value === undefined)) { - - stored = this.get(owner, key); - - return stored !== undefined ? - stored : this.get(owner, jQuery.camelCase(key)); - } - - // [*]When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set(owner, key, value); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function (owner, key) { - var i, name, camel, - unlock = this.key(owner), - cache = this.cache[ unlock ]; - - if (key === undefined) { - this.cache[ unlock ] = {}; + // 2. Rotate around origin. + var rotateAroundOrigin = svg.createSVGTransform(); + var angle = g.point(position).changeInAngle(position.x - reference.x, position.y - reference.y, reference); + rotateAroundOrigin.setRotate(angle, 0, 0); - } else { - // Support array or space separated string of keys - if (jQuery.isArray(key)) { - // If "name" is an array of keys... - // When data is initially created, via ("key", "val") signature, - // keys will be converted to camelCase. - // Since there is no way to tell _how_ a key was added, remove - // both plain key and camelCase key. #12786 - // This will only penalize the array argument path. - name = key.concat(key.map(jQuery.camelCase)); - } else { - camel = jQuery.camelCase(key); - // Try the string as a key before any manipulation - if (key in cache) { - name = [ key, camel ]; - } else { - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - name = camel; - name = name in cache ? - [ name ] : ( name.match(core_rnotwhite) || [] ); - } - } + // 3. Translate to the `position` + the offset (half my width) towards the `reference` point. + var translateFinal = svg.createSVGTransform(); + var finalPosition = g.point(position).move(reference, bbox.width / 2); + translateFinal.setTranslate(position.x + (position.x - finalPosition.x), position.y + (position.y - finalPosition.y)); - i = name.length; - while (i--) { - delete cache[ name[ i ] ]; - } - } - }, - hasData: function (owner) { - return !jQuery.isEmptyObject( - this.cache[ owner[ this.expando ] ] || {} + // 4. Apply transformations. + var ctm = this.getTransformToElement(target); + var transform = svg.createSVGTransform(); + transform.setMatrix( + translateFinal.matrix.multiply( + rotateAroundOrigin.matrix.multiply( + translateToOrigin.matrix.multiply( + ctm))) ); - }, - discard: function (owner) { - if (owner[ this.expando ]) { - delete this.cache[ owner[ this.expando ] ]; - } - } - }; -// These may be used throughout the jQuery core codebase - data_user = new Data(); - data_priv = new Data(); + // Instead of directly setting the `matrix()` transform on the element, first, decompose + // the matrix into separate transforms. This allows us to use normal Vectorizer methods + // as they don't work on matrices. An example of this is to retrieve a scale of an element. + // this.node.transform.baseVal.initialize(transform); + var decomposition = V.decomposeMatrix(transform.matrix); - jQuery.extend({ - acceptData: Data.accepts, + this.translate(decomposition.translateX, decomposition.translateY); + this.rotate(decomposition.rotation); + // Note that scale has been already applied, hence the following line stays commented. (it's here just for reference). + //this.scale(decomposition.scaleX, decomposition.scaleY); - hasData: function (elem) { - return data_user.hasData(elem) || data_priv.hasData(elem); - }, + return this; + }; - data: function (elem, name, data) { - return data_user.access(elem, name, data); - }, + V.prototype.animateAlongPath = function(attrs, path) { - removeData: function (elem, name) { - data_user.remove(elem, name); - }, + var animateMotion = V('animateMotion', attrs); + var mpath = V('mpath', { 'xlink:href': '#' + V(path).node.id }); - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to data_priv methods, these can be deprecated. - _data: function (elem, name, data) { - return data_priv.access(elem, name, data); - }, + animateMotion.append(mpath); - _removeData: function (elem, name) { - data_priv.remove(elem, name); - } - }); + this.append(animateMotion); + try { + animateMotion.node.beginElement(); + } catch (e) { + // Fallback for IE 9. + // Run the animation programatically if FakeSmile (`http://leunen.me/fakesmile/`) present + if (document.documentElement.getAttribute('smiling') === 'fake') { - jQuery.fn.extend({ - data: function (key, value) { - var attrs, name, - elem = this[ 0 ], - i = 0, - data = null; - - // Gets all values - if (key === undefined) { - if (this.length) { - data = data_user.get(elem); - - if (elem.nodeType === 1 && !data_priv.get(elem, "hasDataAttrs")) { - attrs = elem.attributes; - for (; i < attrs.length; i++) { - name = attrs[ i ].name; - - if (name.indexOf("data-") === 0) { - name = jQuery.camelCase(name.slice(5)); - dataAttr(elem, name, data[ name ]); - } - } - data_priv.set(elem, "hasDataAttrs", true); - } - } + // Register the animation. (See `https://answers.launchpad.net/smil/+question/203333`) + var animation = animateMotion.node; + animation.animators = []; - return data; - } + var animationID = animation.getAttribute('id'); + if (animationID) id2anim[animationID] = animation; - // Sets multiple values - if (typeof key === "object") { - return this.each(function () { - data_user.set(this, key); - }); + var targets = getTargets(animation); + for (var i = 0, len = targets.length; i < len; i++) { + var target = targets[i]; + var animator = new Animator(animation, target, i); + animators.push(animator); + animation.animators[i] = animator; + animator.register(); + } + } } + }; - return jQuery.access(this, function (value) { - var data, - camelKey = jQuery.camelCase(key); - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if (elem && value === undefined) { - // Attempt to get data from the cache - // with the key as-is - data = data_user.get(elem, key); - if (data !== undefined) { - return data; - } + V.prototype.hasClass = function(className) { - // Attempt to get data from the cache - // with the key camelized - data = data_user.get(elem, camelKey); - if (data !== undefined) { - return data; - } + return new RegExp('(\\s|^)' + className + '(\\s|$)').test(this.node.getAttribute('class')); + }; - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr(elem, camelKey, undefined); - if (data !== undefined) { - return data; - } + V.prototype.addClass = function(className) { - // We tried really hard, but the data doesn't exist. - return; - } + if (!this.hasClass(className)) { + var prevClasses = this.node.getAttribute('class') || ''; + this.node.setAttribute('class', (prevClasses + ' ' + className).trim()); + } - // Set the data... - this.each(function () { - // First, attempt to store a copy or reference of any - // data that might've been store with a camelCased key. - var data = data_user.get(this, camelKey); + return this; + }; - // For HTML5 data-* attribute interop, we have to - // store property names with dashes in a camelCase form. - // This might not apply to all properties...* - data_user.set(this, camelKey, value); + V.prototype.removeClass = function(className) { - // *... In the case of properties that might _actually_ - // have dashes, we need to also store a copy of that - // unchanged property. - if (key.indexOf("-") !== -1 && data !== undefined) { - data_user.set(this, key, value); - } - }); - }, null, value, arguments.length > 1, null, true); - }, + if (this.hasClass(className)) { + var newClasses = this.node.getAttribute('class').replace(new RegExp('(\\s|^)' + className + '(\\s|$)', 'g'), '$2'); + this.node.setAttribute('class', newClasses); + } - removeData: function (key) { - return this.each(function () { - data_user.remove(this, key); - }); - } - }); + return this; + }; - function dataAttr(elem, key, data) { - var name; + V.prototype.toggleClass = function(className, toAdd) { - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if (data === undefined && elem.nodeType === 1) { - name = "data-" + key.replace(rmultiDash, "-$1").toLowerCase(); - data = elem.getAttribute(name); + var toRemove = V.isUndefined(toAdd) ? this.hasClass(className) : !toAdd; - if (typeof data === "string") { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - // Only convert to a number if it doesn't change the string - +data + "" === data ? +data : - rbrace.test(data) ? JSON.parse(data) : - data; - } catch (e) { - } - - // Make sure we set the data so it isn't changed later - data_user.set(elem, key, data); + if (toRemove) { + this.removeClass(className); } else { - data = undefined; + this.addClass(className); } - } - return data; - } - jQuery.extend({ - queue: function (elem, type, data) { - var queue; + return this; + }; - if (elem) { - type = ( type || "fx" ) + "queue"; - queue = data_priv.get(elem, type); + // Interpolate path by discrete points. The precision of the sampling + // is controlled by `interval`. In other words, `sample()` will generate + // a point on the path starting at the beginning of the path going to the end + // every `interval` pixels. + // The sampler can be very useful for e.g. finding intersection between two + // paths (finding the two closest points from two samples). + V.prototype.sample = function(interval) { + + interval = interval || 1; + var node = this.node; + var length = node.getTotalLength(); + var samples = []; + var distance = 0; + var sample; + while (distance < length) { + sample = node.getPointAtLength(distance); + samples.push({ x: sample.x, y: sample.y, distance: distance }); + distance += interval; + } + return samples; + }; - // Speed up dequeue by getting out quickly if this is just a lookup - if (data) { - if (!queue || jQuery.isArray(data)) { - queue = data_priv.access(elem, type, jQuery.makeArray(data)); - } else { - queue.push(data); - } - } - return queue || []; + V.prototype.convertToPath = function() { + + var path = V('path'); + path.attr(this.attr()); + var d = this.convertToPathData(); + if (d) { + path.attr('d', d); } - }, + return path; + }; - dequeue: function (elem, type) { - type = type || "fx"; + V.prototype.convertToPathData = function() { - var queue = jQuery.queue(elem, type), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks(elem, type), - next = function () { - jQuery.dequeue(elem, type); - }; + var tagName = this.node.tagName.toUpperCase(); - // If the fx queue is dequeued, always remove the progress sentinel - if (fn === "inprogress") { - fn = queue.shift(); - startLength--; + switch (tagName) { + case 'PATH': + return this.attr('d'); + case 'LINE': + return V.convertLineToPathData(this.node); + case 'POLYGON': + return V.convertPolygonToPathData(this.node); + case 'POLYLINE': + return V.convertPolylineToPathData(this.node); + case 'ELLIPSE': + return V.convertEllipseToPathData(this.node); + case 'CIRCLE': + return V.convertCircleToPathData(this.node); + case 'RECT': + return V.convertRectToPathData(this.node); } - if (fn) { + throw new Error(tagName + ' cannot be converted to PATH.'); + }; - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if (type === "fx") { - queue.unshift("inprogress"); - } + // Find the intersection of a line starting in the center + // of the SVG `node` ending in the point `ref`. + // `target` is an SVG element to which `node`s transformations are relative to. + // In JointJS, `target` is the `paper.viewport` SVG group element. + // Note that `ref` point must be in the coordinate system of the `target` for this function to work properly. + // Returns a point in the `target` coordinte system (the same system as `ref` is in) if + // an intersection is found. Returns `undefined` otherwise. + V.prototype.findIntersection = function(ref, target) { - // clear up the last queue stop function - delete hooks.stop; - fn.call(elem, next, hooks); - } + var svg = this.svg().node; + target = target || svg; + var bbox = g.rect(this.bbox(false, target)); + var center = bbox.center(); - if (!startLength && hooks) { - hooks.empty.fire(); - } - }, + if (!bbox.intersectionWithLineFromCenterToPoint(ref)) return undefined; - // not intended for public consumption - generates a queueHooks object, or returns the current one - _queueHooks: function (elem, type) { - var key = type + "queueHooks"; - return data_priv.get(elem, key) || data_priv.access(elem, key, { - empty: jQuery.Callbacks("once memory").add(function () { - data_priv.remove(elem, [ type + "queue", key ]); - }) - }); - } - }); + var spot; + var tagName = this.node.localName.toUpperCase(); - jQuery.fn.extend({ - queue: function (type, data) { - var setter = 2; + // Little speed up optimalization for `` element. We do not do conversion + // to path element and sampling but directly calculate the intersection through + // a transformed geometrical rectangle. + if (tagName === 'RECT') { - if (typeof type !== "string") { - data = type; - type = "fx"; - setter--; - } + var gRect = g.rect( + parseFloat(this.attr('x') || 0), + parseFloat(this.attr('y') || 0), + parseFloat(this.attr('width')), + parseFloat(this.attr('height')) + ); + // Get the rect transformation matrix with regards to the SVG document. + var rectMatrix = this.getTransformToElement(target); + // Decompose the matrix to find the rotation angle. + var rectMatrixComponents = V.decomposeMatrix(rectMatrix); + // Now we want to rotate the rectangle back so that we + // can use `intersectionWithLineFromCenterToPoint()` passing the angle as the second argument. + var resetRotation = svg.createSVGTransform(); + resetRotation.setRotate(-rectMatrixComponents.rotation, center.x, center.y); + var rect = V.transformRect(gRect, resetRotation.matrix.multiply(rectMatrix)); + spot = g.rect(rect).intersectionWithLineFromCenterToPoint(ref, rectMatrixComponents.rotation); + + } else if (tagName === 'PATH' || tagName === 'POLYGON' || tagName === 'POLYLINE' || tagName === 'CIRCLE' || tagName === 'ELLIPSE') { + + var pathNode = (tagName === 'PATH') ? this : this.convertToPath(); + var samples = pathNode.sample(); + var minDistance = Infinity; + var closestSamples = []; + + var i, sample, gp, centerDistance, refDistance, distance; + + for (i = 0; i < samples.length; i++) { + + sample = samples[i]; + // Convert the sample point in the local coordinate system to the global coordinate system. + gp = V.createSVGPoint(sample.x, sample.y); + gp = gp.matrixTransform(this.getTransformToElement(target)); + sample = g.point(gp); + centerDistance = sample.distance(center); + // Penalize a higher distance to the reference point by 10%. + // This gives better results. This is due to + // inaccuracies introduced by rounding errors and getPointAtLength() returns. + refDistance = sample.distance(ref) * 1.1; + distance = centerDistance + refDistance; + + if (distance < minDistance) { + minDistance = distance; + closestSamples = [{ sample: sample, refDistance: refDistance }]; + } else if (distance < minDistance + 1) { + closestSamples.push({ sample: sample, refDistance: refDistance }); + } + } + + closestSamples.sort(function(a, b) { + return a.refDistance - b.refDistance; + }); - if (arguments.length < setter) { - return jQuery.queue(this[0], type); + if (closestSamples[0]) { + spot = closestSamples[0].sample; + } } - return data === undefined ? - this : - this.each(function () { - var queue = jQuery.queue(this, type, data); - - // ensure a hooks for this queue - jQuery._queueHooks(this, type); + return spot; + }; - if (type === "fx" && queue[0] !== "inprogress") { - jQuery.dequeue(this, type); - } - }); - }, - dequeue: function (type) { - return this.each(function () { - jQuery.dequeue(this, type); - }); - }, - // Based off of the plugin by Clint Helfers, with permission. - // http://blindsignals.com/index.php/2009/07/jquery-delay/ - delay: function (time, type) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; + /** + * @private + * @param {string} name + * @param {string} value + * @returns {Vectorizer} + */ + V.prototype.setAttribute = function(name, value) { - return this.queue(type, function (next, hooks) { - var timeout = setTimeout(next, time); - hooks.stop = function () { - clearTimeout(timeout); - }; - }); - }, - clearQueue: function (type) { - return this.queue(type || "fx", []); - }, - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function (type, obj) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function () { - if (!( --count )) { - defer.resolveWith(elements, [ elements ]); - } - }; + var el = this.node; - if (typeof type !== "string") { - obj = type; - type = undefined; + if (value === null) { + this.removeAttr(name); + return this; } - type = type || "fx"; - while (i--) { - tmp = data_priv.get(elements[ i ], type + "queueHooks"); - if (tmp && tmp.empty) { - count++; - tmp.empty.add(resolve); - } - } - resolve(); - return defer.promise(obj); - } - }); - var nodeHook, boolHook, - rclass = /[\t\r\n\f]/g, - rreturn = /\r/g, - rfocusable = /^(?:input|select|textarea|button)$/i; + var qualifiedName = V.qualifyAttr(name); - jQuery.fn.extend({ - attr: function (name, value) { - return jQuery.access(this, jQuery.attr, name, value, arguments.length > 1); - }, + if (qualifiedName.ns) { + // Attribute names can be namespaced. E.g. `image` elements + // have a `xlink:href` attribute to set the source of the image. + el.setAttributeNS(qualifiedName.ns, name, value); + } else if (name === 'id') { + el.id = value; + } else { + el.setAttribute(name, value); + } - removeAttr: function (name) { - return this.each(function () { - jQuery.removeAttr(this, name); - }); - }, + return this; + }; - prop: function (name, value) { - return jQuery.access(this, jQuery.prop, name, value, arguments.length > 1); - }, + // Create an SVG document element. + // If `content` is passed, it will be used as the SVG content of the `` root element. + V.createSvgDocument = function(content) { - removeProp: function (name) { - return this.each(function () { - delete this[ jQuery.propFix[ name ] || name ]; - }); - }, + var svg = '' + (content || '') + ''; + var xml = V.parseXML(svg, { async: false }); + return xml.documentElement; + }; - addClass: function (value) { - var classes, elem, cur, clazz, j, - i = 0, - len = this.length, - proceed = typeof value === "string" && value; + V.idCounter = 0; - if (jQuery.isFunction(value)) { - return this.each(function (j) { - jQuery(this).addClass(value.call(this, j, this.className)); - }); - } + // A function returning a unique identifier for this client session with every call. + V.uniqueId = function() { - if (proceed) { - // The disjunction here is for better compressibility (see removeClass) - classes = ( value || "" ).match(core_rnotwhite) || []; + var id = ++V.idCounter + ''; + return 'v-' + id; + }; - for (; i < len; i++) { - elem = this[ i ]; - cur = elem.nodeType === 1 && ( elem.className ? - ( " " + elem.className + " " ).replace(rclass, " ") : - " " - ); + // Replace all spaces with the Unicode No-break space (http://www.fileformat.info/info/unicode/char/a0/index.htm). + // IE would otherwise collapse all spaces into one. This is used in the text() method but it is + // also exposed so that the programmer can use it in case he needs to. This is useful e.g. in tests + // when you want to compare the actual DOM text content without having to add the unicode character in + // the place of all spaces. + V.sanitizeText = function(text) { - if (cur) { - j = 0; - while ((clazz = classes[j++])) { - if (cur.indexOf(" " + clazz + " ") < 0) { - cur += clazz + " "; - } - } - elem.className = jQuery.trim(cur); + return (text || '').replace(/ /g, '\u00A0'); + }; - } - } - } + V.isUndefined = function(value) { - return this; - }, + return typeof value === 'undefined'; + }; - removeClass: function (value) { - var classes, elem, cur, clazz, j, - i = 0, - len = this.length, - proceed = arguments.length === 0 || typeof value === "string" && value; + V.isString = function(value) { - if (jQuery.isFunction(value)) { - return this.each(function (j) { - jQuery(this).removeClass(value.call(this, j, this.className)); - }); - } - if (proceed) { - classes = ( value || "" ).match(core_rnotwhite) || []; - - for (; i < len; i++) { - elem = this[ i ]; - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( elem.className ? - ( " " + elem.className + " " ).replace(rclass, " ") : - "" - ); - - if (cur) { - j = 0; - while ((clazz = classes[j++])) { - // Remove *all* instances - while (cur.indexOf(" " + clazz + " ") >= 0) { - cur = cur.replace(" " + clazz + " ", " "); - } - } - elem.className = value ? jQuery.trim(cur) : ""; - } - } - } + return typeof value === 'string'; + }; - return this; - }, + V.isObject = function(value) { - toggleClass: function (value, stateVal) { - var type = typeof value; + return value && (typeof value === 'object'); + }; - if (typeof stateVal === "boolean" && type === "string") { - return stateVal ? this.addClass(value) : this.removeClass(value); - } + V.isArray = Array.isArray; - if (jQuery.isFunction(value)) { - return this.each(function (i) { - jQuery(this).toggleClass(value.call(this, i, this.className, stateVal), stateVal); - }); - } + V.parseXML = function(data, opt) { - return this.each(function () { - if (type === "string") { - // toggle individual class names - var className, - i = 0, - self = jQuery(this), - classNames = value.match(core_rnotwhite) || []; + opt = opt || {}; - while ((className = classNames[ i++ ])) { - // check each className given, space separated list - if (self.hasClass(className)) { - self.removeClass(className); - } else { - self.addClass(className); - } - } + var xml; - // Toggle whole class name - } else if (type === core_strundefined || type === "boolean") { - if (this.className) { - // store className if set - data_priv.set(this, "__className__", this.className); - } + try { + var parser = new DOMParser(); - // If the element has a class name or if we're passed "false", - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - this.className = this.className || value === false ? "" : data_priv.get(this, "__className__") || ""; + if (!V.isUndefined(opt.async)) { + parser.async = opt.async; } - }); - }, - hasClass: function (selector) { - var className = " " + selector + " ", - i = 0, - l = this.length; - for (; i < l; i++) { - if (this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf(className) >= 0) { - return true; - } + xml = parser.parseFromString(data, 'text/xml'); + } catch (error) { + xml = undefined; } - return false; - }, - - val: function (value) { - var hooks, ret, isFunction, - elem = this[0]; - - if (!arguments.length) { - if (elem) { - hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if (hooks && "get" in hooks && (ret = hooks.get(elem, "value")) !== undefined) { - return ret; - } + if (!xml || xml.getElementsByTagName('parsererror').length) { + throw new Error('Invalid XML: ' + data); + } - ret = elem.value; + return xml; + }; - return typeof ret === "string" ? - // handle most common string cases - ret.replace(rreturn, "") : - // handle cases where value is null/undef or number - ret == null ? "" : ret; - } + /** + * @param {string} name + * @returns {{ns: string|null, local: string}} namespace and attribute name + */ + V.qualifyAttr = function(name) { - return; + if (name.indexOf(':') !== -1) { + var combinedKey = name.split(':'); + return { + ns: ns[combinedKey[0]], + local: combinedKey[1] + }; } - isFunction = jQuery.isFunction(value); - - return this.each(function (i) { - var val; + return { + ns: null, + local: name + }; + }; - if (this.nodeType !== 1) { - return; - } + V.parseTransformString = function(transform) { - if (isFunction) { - val = value.call(this, i, jQuery(this).val()); - } else { - val = value; - } + var translate, rotate, scale; - // Treat null/undefined as ""; convert numbers to string - if (val == null) { - val = ""; - } else if (typeof val === "number") { - val += ""; - } else if (jQuery.isArray(val)) { - val = jQuery.map(val, function (value) { - return value == null ? "" : value + ""; - }); - } + if (transform) { - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + var separator = /[ ,]+/; - // If set returns undefined, fall back to normal setting - if (!hooks || !("set" in hooks) || hooks.set(this, val, "value") === undefined) { - this.value = val; + var translateMatch = transform.match(/translate\((.*)\)/); + if (translateMatch) { + translate = translateMatch[1].split(separator); } - }); - } - }); - - jQuery.extend({ - valHooks: { - option: { - get: function (elem) { - // attributes.value is undefined in Blackberry 4.7 but - // uses .value. See #6932 - var val = elem.attributes.value; - return !val || val.specified ? elem.value : elem.text; + var rotateMatch = transform.match(/rotate\((.*)\)/); + if (rotateMatch) { + rotate = rotateMatch[1].split(separator); } - }, - select: { - get: function (elem) { - var value, option, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one" || index < 0, - values = one ? null : [], - max = one ? index + 1 : options.length, - i = index < 0 ? - max : - one ? index : 0; - - // Loop through all the selected options - for (; i < max; i++) { - option = options[ i ]; - - // IE6-9 doesn't update selected after form reset (#2551) - if (( option.selected || i === index ) && - // Don't return options that are disabled or in a disabled optgroup - ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && - ( !option.parentNode.disabled || !jQuery.nodeName(option.parentNode, "optgroup") )) { - - // Get the specific value for the option - value = jQuery(option).val(); - - // We don't need an array for one selects - if (one) { - return value; - } + var scaleMatch = transform.match(/scale\((.*)\)/); + if (scaleMatch) { + scale = scaleMatch[1].split(separator); + } + } - // Multi-Selects return an array - values.push(value); - } - } + var sx = (scale && scale[0]) ? parseFloat(scale[0]) : 1; - return values; + return { + translate: { + tx: (translate && translate[0]) ? parseInt(translate[0], 10) : 0, + ty: (translate && translate[1]) ? parseInt(translate[1], 10) : 0 + }, + rotate: { + angle: (rotate && rotate[0]) ? parseInt(rotate[0], 10) : 0, + cx: (rotate && rotate[1]) ? parseInt(rotate[1], 10) : undefined, + cy: (rotate && rotate[2]) ? parseInt(rotate[2], 10) : undefined }, + scale: { + sx: sx, + sy: (scale && scale[1]) ? parseFloat(scale[1]) : sx + } + }; + }; - set: function (elem, value) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray(value), - i = options.length; + V.deltaTransformPoint = function(matrix, point) { - while (i--) { - option = options[ i ]; - if ((option.selected = jQuery.inArray(jQuery(option).val(), values) >= 0)) { - optionSet = true; - } - } + var dx = point.x * matrix.a + point.y * matrix.c + 0; + var dy = point.x * matrix.b + point.y * matrix.d + 0; + return { x: dx, y: dy }; + }; - // force browsers to behave consistently when non-matching value is set - if (!optionSet) { - elem.selectedIndex = -1; - } - return values; - } - } - }, + V.decomposeMatrix = function(matrix) { - attr: function (elem, name, value) { - var hooks, ret, - nType = elem.nodeType; + // @see https://gist.github.com/2052247 - // don't get/set attributes on text, comment and attribute nodes - if (!elem || nType === 3 || nType === 8 || nType === 2) { - return; - } + // calculate delta transform point + var px = V.deltaTransformPoint(matrix, { x: 0, y: 1 }); + var py = V.deltaTransformPoint(matrix, { x: 1, y: 0 }); - // Fallback to prop when attributes are not supported - if (typeof elem.getAttribute === core_strundefined) { - return jQuery.prop(elem, name, value); - } + // calculate skew + var skewX = ((180 / Math.PI) * Math.atan2(px.y, px.x) - 90); + var skewY = ((180 / Math.PI) * Math.atan2(py.y, py.x)); - // All attributes are lowercase - // Grab necessary hook if one is defined - if (nType !== 1 || !jQuery.isXMLDoc(elem)) { - name = name.toLowerCase(); - hooks = jQuery.attrHooks[ name ] || - ( jQuery.expr.match.bool.test(name) ? boolHook : nodeHook ); - } + return { - if (value !== undefined) { + translateX: matrix.e, + translateY: matrix.f, + scaleX: Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b), + scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d), + skewX: skewX, + skewY: skewY, + rotation: skewX // rotation is the same as skew x + }; + }; - if (value === null) { - jQuery.removeAttr(elem, name); + V.isV = function(object) { - } else if (hooks && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) { - return ret; + return object instanceof V; + }; - } else { - elem.setAttribute(name, value + ""); - return value; - } + // For backwards compatibility: + V.isVElement = V.isV; - } else if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) { - return ret; + var svgDocument = V('svg').node; - } else { - ret = jQuery.find.attr(elem, name); + V.createSVGMatrix = function(matrix) { - // Non-existent attributes return null, we normalize to undefined - return ret == null ? - undefined : - ret; + var svgMatrix = svgDocument.createSVGMatrix(); + for (var component in matrix) { + svgMatrix[component] = matrix[component]; } - }, - removeAttr: function (elem, value) { - var name, propName, - i = 0, - attrNames = value && value.match(core_rnotwhite); + return svgMatrix; + }; - if (attrNames && elem.nodeType === 1) { - while ((name = attrNames[i++])) { - propName = jQuery.propFix[ name ] || name; + V.createSVGTransform = function(matrix) { - // Boolean attributes get special treatment (#10870) - if (jQuery.expr.match.bool.test(name)) { - // Set corresponding property to false - elem[ propName ] = false; - } + if (!V.isUndefined(matrix)) { - elem.removeAttribute(name); + if (!(matrix instanceof SVGMatrix)) { + matrix = V.createSVGMatrix(matrix); } - } - }, - attrHooks: { - type: { - set: function (elem, value) { - if (!jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input")) { - // Setting the type on a radio button after the value resets the value in IE6-9 - // Reset value to default in case type is set after value during creation - var val = elem.value; - elem.setAttribute("type", value); - if (val) { - elem.value = val; - } - return value; - } - } + return svgDocument.createSVGTransformFromMatrix(matrix); } - }, - propFix: { - "for": "htmlFor", - "class": "className" - }, + return svgDocument.createSVGTransform(); + }; - prop: function (elem, name, value) { - var ret, hooks, notxml, - nType = elem.nodeType; + V.createSVGPoint = function(x, y) { - // don't get/set properties on text, comment and attribute nodes - if (!elem || nType === 3 || nType === 8 || nType === 2) { - return; - } + var p = svgDocument.createSVGPoint(); + p.x = x; + p.y = y; + return p; + }; - notxml = nType !== 1 || !jQuery.isXMLDoc(elem); + V.transformRect = function(r, matrix) { - if (notxml) { - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } + var p = svgDocument.createSVGPoint(); - if (value !== undefined) { - return hooks && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined ? - ret : - ( elem[ name ] = value ); + p.x = r.x; + p.y = r.y; + var corner1 = p.matrixTransform(matrix); - } else { - return hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null ? - ret : - elem[ name ]; - } - }, + p.x = r.x + r.width; + p.y = r.y; + var corner2 = p.matrixTransform(matrix); - propHooks: { - tabIndex: { - get: function (elem) { - return elem.hasAttribute("tabindex") || rfocusable.test(elem.nodeName) || elem.href ? - elem.tabIndex : - -1; - } - } - } - }); + p.x = r.x + r.width; + p.y = r.y + r.height; + var corner3 = p.matrixTransform(matrix); -// Hooks for boolean attributes - boolHook = { - set: function (elem, value, name) { - if (value === false) { - // Remove boolean attributes when set to false - jQuery.removeAttr(elem, name); - } else { - elem.setAttribute(name, name); - } - return name; - } - }; - jQuery.each(jQuery.expr.match.bool.source.match(/\w+/g), function (i, name) { - var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr; + p.x = r.x; + p.y = r.y + r.height; + var corner4 = p.matrixTransform(matrix); - jQuery.expr.attrHandle[ name ] = function (elem, name, isXML) { - var fn = jQuery.expr.attrHandle[ name ], - ret = isXML ? - undefined : - /* jshint eqeqeq: false */ - // Temporarily disable this handler to check existence - (jQuery.expr.attrHandle[ name ] = undefined) != - getter(elem, name, isXML) ? + var minX = Math.min(corner1.x, corner2.x, corner3.x, corner4.x); + var maxX = Math.max(corner1.x, corner2.x, corner3.x, corner4.x); + var minY = Math.min(corner1.y, corner2.y, corner3.y, corner4.y); + var maxY = Math.max(corner1.y, corner2.y, corner3.y, corner4.y); - name.toLowerCase() : - null; + return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }; + }; - // Restore handler - jQuery.expr.attrHandle[ name ] = fn; + V.transformPoint = function(p, matrix) { - return ret; + return V.createSVGPoint(p.x, p.y).matrixTransform(matrix); }; - }); -// Support: IE9+ -// Selectedness for an option in an optgroup can be inaccurate - if (!jQuery.support.optSelected) { - jQuery.propHooks.selected = { - get: function (elem) { - var parent = elem.parentNode; - if (parent && parent.parentNode) { - parent.parentNode.selectedIndex; - } - return null; + // Convert a style represented as string (e.g. `'fill="blue"; stroke="red"'`) to + // an object (`{ fill: 'blue', stroke: 'red' }`). + V.styleToObject = function(styleString) { + var ret = {}; + var styles = styleString.split(';'); + for (var i = 0; i < styles.length; i++) { + var style = styles[i]; + var pair = style.split('='); + ret[pair[0].trim()] = pair[1].trim(); } + return ret; }; - } - jQuery.each([ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" - ], function () { - jQuery.propFix[ this.toLowerCase() ] = this; - }); + // Inspired by d3.js https://github.com/mbostock/d3/blob/master/src/svg/arc.js + V.createSlicePathData = function(innerRadius, outerRadius, startAngle, endAngle) { + + var svgArcMax = 2 * Math.PI - 1e-6; + var r0 = innerRadius; + var r1 = outerRadius; + var a0 = startAngle; + var a1 = endAngle; + var da = (a1 < a0 && (da = a0, a0 = a1, a1 = da), a1 - a0); + var df = da < Math.PI ? '0' : '1'; + var c0 = Math.cos(a0); + var s0 = Math.sin(a0); + var c1 = Math.cos(a1); + var s1 = Math.sin(a1); + + return (da >= svgArcMax) + ? (r0 + ? 'M0,' + r1 + + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + (-r1) + + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + r1 + + 'M0,' + r0 + + 'A' + r0 + ',' + r0 + ' 0 1,0 0,' + (-r0) + + 'A' + r0 + ',' + r0 + ' 0 1,0 0,' + r0 + + 'Z' + : 'M0,' + r1 + + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + (-r1) + + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + r1 + + 'Z') + : (r0 + ? 'M' + r1 * c0 + ',' + r1 * s0 + + 'A' + r1 + ',' + r1 + ' 0 ' + df + ',1 ' + r1 * c1 + ',' + r1 * s1 + + 'L' + r0 * c1 + ',' + r0 * s1 + + 'A' + r0 + ',' + r0 + ' 0 ' + df + ',0 ' + r0 * c0 + ',' + r0 * s0 + + 'Z' + : 'M' + r1 * c0 + ',' + r1 * s0 + + 'A' + r1 + ',' + r1 + ' 0 ' + df + ',1 ' + r1 * c1 + ',' + r1 * s1 + + 'L0,0' + + 'Z'); + }; -// Radios and checkboxes getter/setter - jQuery.each([ "radio", "checkbox" ], function () { - jQuery.valHooks[ this ] = { - set: function (elem, value) { - if (jQuery.isArray(value)) { - return ( elem.checked = jQuery.inArray(jQuery(elem).val(), value) >= 0 ); + // Merge attributes from object `b` with attributes in object `a`. + // Note that this modifies the object `a`. + // Also important to note that attributes are merged but CSS classes are concatenated. + V.mergeAttrs = function(a, b) { + + for (var attr in b) { + + if (attr === 'class') { + // Concatenate classes. + a[attr] = a[attr] ? a[attr] + ' ' + b[attr] : b[attr]; + } else if (attr === 'style') { + // `style` attribute can be an object. + if (V.isObject(a[attr]) && V.isObject(b[attr])) { + // `style` stored in `a` is an object. + a[attr] = V.mergeAttrs(a[attr], b[attr]); + } else if (V.isObject(a[attr])) { + // `style` in `a` is an object but it's a string in `b`. + // Convert the style represented as a string to an object in `b`. + a[attr] = V.mergeAttrs(a[attr], V.styleToObject(b[attr])); + } else if (V.isObject(b[attr])) { + // `style` in `a` is a string, in `b` it's an object. + a[attr] = V.mergeAttrs(V.styleToObject(a[attr]), b[attr]); + } else { + // Both styles are strings. + a[attr] = V.mergeAttrs(V.styleToObject(a[attr]), V.styleToObject(b[attr])); + } + } else { + a[attr] = b[attr]; } } - }; - if (!jQuery.support.checkOn) { - jQuery.valHooks[ this ].get = function (elem) { - // Support: Webkit - // "" is returned instead of "on" if a value isn't specified - return elem.getAttribute("value") === null ? "on" : elem.value; - }; - } - }); - var rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|contextmenu)|click/, - rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; - - function returnTrue() { - return true; - } - - function returnFalse() { - return false; - } - function safeActiveElement() { - try { - return document.activeElement; - } catch (err) { - } - } + return a; + }; - /* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ - jQuery.event = { + V.annotateString = function(t, annotations, opt) { - global: {}, + annotations = annotations || []; + opt = opt || {}; - add: function (elem, types, handler, data, selector) { + var offset = opt.offset || 0; + var compacted = []; + var batch; + var ret = []; + var item; + var prev; - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = data_priv.get(elem); + for (var i = 0; i < t.length; i++) { - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if (!elemData) { - return; - } + item = ret[i] = t[i]; - // Caller can pass in an object of custom data in lieu of the handler - if (handler.handler) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } + for (var j = 0; j < annotations.length; j++) { - // Make sure that the handler has a unique ID, used to find/remove it later - if (!handler.guid) { - handler.guid = jQuery.guid++; - } + var annotation = annotations[j]; + var start = annotation.start + offset; + var end = annotation.end + offset; - // Init the element's event structure and main handler, if this is the first - if (!(events = elemData.events)) { - events = elemData.events = {}; - } - if (!(eventHandle = elemData.handle)) { - eventHandle = elemData.handle = function (e) { - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? - jQuery.event.dispatch.apply(eventHandle.elem, arguments) : - undefined; - }; - // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events - eventHandle.elem = elem; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match(core_rnotwhite) || [""]; - t = types.length; - while (t--) { - tmp = rtypenamespace.exec(types[t]) || []; - type = origType = tmp[1]; - namespaces = ( tmp[2] || "" ).split(".").sort(); - - // There *must* be a type, no attaching namespace-only handlers - if (!type) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend({ - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test(selector), - namespace: namespaces.join(".") - }, handleObjIn); - - // Init the event handler queue if we're the first - if (!(handlers = events[ type ])) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) { - if (elem.addEventListener) { - elem.addEventListener(type, eventHandle, false); + if (i >= start && i < end) { + // Annotation applies. + if (V.isObject(item)) { + // There is more than one annotation to be applied => Merge attributes. + item.attrs = V.mergeAttrs(V.mergeAttrs({}, item.attrs), annotation.attrs); + } else { + item = ret[i] = { t: t[i], attrs: annotation.attrs }; + } + if (opt.includeAnnotationIndices) { + (item.annotations || (item.annotations = [])).push(j); } } } - if (special.add) { - special.add.call(elem, handleObj); + prev = ret[i - 1]; + + if (!prev) { - if (!handleObj.handler.guid) { - handleObj.handler.guid = handler.guid; + batch = item; + + } else if (V.isObject(item) && V.isObject(prev)) { + // Both previous item and the current one are annotations. If the attributes + // didn't change, merge the text. + if (JSON.stringify(item.attrs) === JSON.stringify(prev.attrs)) { + batch.t += item.t; + } else { + compacted.push(batch); + batch = item; } - } - // Add to the element's handler list, delegates in front - if (selector) { - handlers.splice(handlers.delegateCount++, 0, handleObj); + } else if (V.isObject(item)) { + // Previous item was a string, current item is an annotation. + compacted.push(batch); + batch = item; + + } else if (V.isObject(prev)) { + // Previous item was an annotation, current item is a string. + compacted.push(batch); + batch = item; + } else { - handlers.push(handleObj); + // Both previous and current item are strings. + batch = (batch || '') + item; } + } - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; + if (batch) { + compacted.push(batch); } - // Nullify elem to prevent memory leaks in IE - elem = null; - }, + return compacted; + }; - // Detach an event or set of events from an element - remove: function (elem, types, handler, selector, mappedTypes) { + V.findAnnotationsAtIndex = function(annotations, index) { - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = data_priv.hasData(elem) && data_priv.get(elem); + var found = []; - if (!elemData || !(events = elemData.events)) { - return; - } + if (annotations) { - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match(core_rnotwhite) || [""]; - t = types.length; - while (t--) { - tmp = rtypenamespace.exec(types[t]) || []; - type = origType = tmp[1]; - namespaces = ( tmp[2] || "" ).split(".").sort(); + annotations.forEach(function(annotation) { - // Unbind all events (on this namespace, if provided) for the element - if (!type) { - for (type in events) { - jQuery.event.remove(elem, type + types[ t ], handler, selector, true); + if (annotation.start < index && index <= annotation.end) { + found.push(annotation); } - continue; - } + }); + } - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[2] && new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)"); + return found; + }; - // Remove matching events - origCount = j = handlers.length; - while (j--) { - handleObj = handlers[ j ]; + V.findAnnotationsBetweenIndexes = function(annotations, start, end) { - if (( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test(handleObj.namespace) ) && - ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector )) { - handlers.splice(j, 1); + var found = []; - if (handleObj.selector) { - handlers.delegateCount--; - } - if (special.remove) { - special.remove.call(elem, handleObj); - } - } - } + if (annotations) { - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if (origCount && !handlers.length) { - if (!special.teardown || special.teardown.call(elem, namespaces, elemData.handle) === false) { - jQuery.removeEvent(elem, type, elemData.handle); - } + annotations.forEach(function(annotation) { - delete events[ type ]; - } + if ((start >= annotation.start && start < annotation.end) || (end > annotation.start && end <= annotation.end) || (annotation.start >= start && annotation.end < end)) { + found.push(annotation); + } + }); } - // Remove the expando if it's no longer used - if (jQuery.isEmptyObject(events)) { - delete elemData.handle; - data_priv.remove(elem, "events"); - } - }, + return found; + }; - trigger: function (event, data, elem, onlyHandlers) { + // Shift all the text annotations after character `index` by `offset` positions. + V.shiftAnnotations = function(annotations, index, offset) { - var i, cur, tmp, bubbleType, ontype, handle, special, - eventPath = [ elem || document ], - type = core_hasOwn.call(event, "type") ? event.type : event, - namespaces = core_hasOwn.call(event, "namespace") ? event.namespace.split(".") : []; + if (annotations) { - cur = tmp = elem = elem || document; + annotations.forEach(function(annotation) { - // Don't do events on text and comment nodes - if (elem.nodeType === 3 || elem.nodeType === 8) { - return; + if (annotation.start < index && annotation.end >= index) { + annotation.end += offset; + } else if (annotation.start >= index) { + annotation.start += offset; + annotation.end += offset; + } + }); } - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if (rfocusMorph.test(type + jQuery.event.triggered)) { - return; - } + return annotations; + }; - if (type.indexOf(".") >= 0) { - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split("."); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf(":") < 0 && "on" + type; + V.convertLineToPathData = function(line) { - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event(type, typeof event === "object" && event); + line = V(line); + var d = [ + 'M', line.attr('x1'), line.attr('y1'), + 'L', line.attr('x2'), line.attr('y2') + ].join(' '); + return d; + }; - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join("."); - event.namespace_re = event.namespace ? - new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : - null; + V.convertPolygonToPathData = function(polygon) { - // Clean up the event in case it is being reused - event.result = undefined; - if (!event.target) { - event.target = elem; - } + var points = V.getPointsFromSvgNode(V(polygon).node); - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray(data, [ event ]); + if (!(points.length > 0)) return null; - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if (!onlyHandlers && special.trigger && special.trigger.apply(elem, data) === false) { - return; - } + return V.svgPointsToPath(points) + ' Z'; + }; - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if (!onlyHandlers && !special.noBubble && !jQuery.isWindow(elem)) { + V.convertPolylineToPathData = function(polyline) { - bubbleType = special.delegateType || type; - if (!rfocusMorph.test(bubbleType + type)) { - cur = cur.parentNode; - } - for (; cur; cur = cur.parentNode) { - eventPath.push(cur); - tmp = cur; - } + var points = V.getPointsFromSvgNode(V(polyline).node); - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if (tmp === (elem.ownerDocument || document)) { - eventPath.push(tmp.defaultView || tmp.parentWindow || window); - } - } + if (!(points.length > 0)) return null; - // Fire handlers on the event path - i = 0; - while ((cur = eventPath[i++]) && !event.isPropagationStopped()) { + return V.svgPointsToPath(points); + }; - event.type = i > 1 ? - bubbleType : - special.bindType || type; + V.svgPointsToPath = function(points) { - // jQuery handler - handle = ( data_priv.get(cur, "events") || {} )[ event.type ] && data_priv.get(cur, "handle"); - if (handle) { - handle.apply(cur, data); - } + var i; - // Native handler - handle = ontype && cur[ ontype ]; - if (handle && jQuery.acceptData(cur) && handle.apply && handle.apply(cur, data) === false) { - event.preventDefault(); - } + for (i = 0; i < points.length; i++) { + points[i] = points[i].x + ' ' + points[i].y; } - event.type = type; - // If nobody prevented the default action, do it now - if (!onlyHandlers && !event.isDefaultPrevented()) { + return 'M ' + points.join(' L'); + }; - if ((!special._default || special._default.apply(eventPath.pop(), data) === false) && - jQuery.acceptData(elem)) { + V.getPointsFromSvgNode = function(node) { - // Call a native DOM method on the target with the same name name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if (ontype && jQuery.isFunction(elem[ type ]) && !jQuery.isWindow(elem)) { + var points = []; + var i; - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; + for (i = 0; i < node.points.numberOfItems; i++) { + points.push(node.points.getItem(i)); + } - if (tmp) { - elem[ ontype ] = null; - } + return points; + }; - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - elem[ type ](); - jQuery.event.triggered = undefined; + V.KAPPA = 0.5522847498307935; + + V.convertCircleToPathData = function(circle) { + + circle = V(circle); + var cx = parseFloat(circle.attr('cx')) || 0; + var cy = parseFloat(circle.attr('cy')) || 0; + var r = parseFloat(circle.attr('r')); + var cd = r * V.KAPPA; // Control distance. + + var d = [ + 'M', cx, cy - r, // Move to the first point. + 'C', cx + cd, cy - r, cx + r, cy - cd, cx + r, cy, // I. Quadrant. + 'C', cx + r, cy + cd, cx + cd, cy + r, cx, cy + r, // II. Quadrant. + 'C', cx - cd, cy + r, cx - r, cy + cd, cx - r, cy, // III. Quadrant. + 'C', cx - r, cy - cd, cx - cd, cy - r, cx, cy - r, // IV. Quadrant. + 'Z' + ].join(' '); + return d; + }; - if (tmp) { - elem[ ontype ] = tmp; - } - } - } - } + V.convertEllipseToPathData = function(ellipse) { + + ellipse = V(ellipse); + var cx = parseFloat(ellipse.attr('cx')) || 0; + var cy = parseFloat(ellipse.attr('cy')) || 0; + var rx = parseFloat(ellipse.attr('rx')); + var ry = parseFloat(ellipse.attr('ry')) || rx; + var cdx = rx * V.KAPPA; // Control distance x. + var cdy = ry * V.KAPPA; // Control distance y. + + var d = [ + 'M', cx, cy - ry, // Move to the first point. + 'C', cx + cdx, cy - ry, cx + rx, cy - cdy, cx + rx, cy, // I. Quadrant. + 'C', cx + rx, cy + cdy, cx + cdx, cy + ry, cx, cy + ry, // II. Quadrant. + 'C', cx - cdx, cy + ry, cx - rx, cy + cdy, cx - rx, cy, // III. Quadrant. + 'C', cx - rx, cy - cdy, cx - cdx, cy - ry, cx, cy - ry, // IV. Quadrant. + 'Z' + ].join(' '); + return d; + }; - return event.result; - }, + V.convertRectToPathData = function(rect) { - dispatch: function (event) { + rect = V(rect); + var x = parseFloat(rect.attr('x')) || 0; + var y = parseFloat(rect.attr('y')) || 0; + var width = parseFloat(rect.attr('width')) || 0; + var height = parseFloat(rect.attr('height')) || 0; + var rx = parseFloat(rect.attr('rx')) || 0; + var ry = parseFloat(rect.attr('ry')) || 0; + var bbox = g.rect(x, y, width, height); - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix(event); + var d; - var i, j, ret, matched, handleObj, - handlerQueue = [], - args = core_slice.call(arguments), - handlers = ( data_priv.get(this, "events") || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; + if (!rx && !ry) { - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[0] = event; - event.delegateTarget = this; + d = [ + 'M', bbox.origin().x, bbox.origin().y, + 'H', bbox.corner().x, + 'V', bbox.corner().y, + 'H', bbox.origin().x, + 'V', bbox.origin().y, + 'Z' + ].join(' '); - // Call the preDispatch hook for the mapped type, and let it bail if desired - if (special.preDispatch && special.preDispatch.call(this, event) === false) { - return; - } + } else { - // Determine handlers - handlerQueue = jQuery.event.handlers.call(this, event, handlers); + var r = x + width; + var b = y + height; + d = [ + 'M', x + rx, y, + 'L', r - rx, y, + 'Q', r, y, r, y + ry, + 'L', r, y + height - ry, + 'Q', r, b, r - rx, b, + 'L', x + rx, b, + 'Q', x, b, x, b - rx, + 'L', x, y + ry, + 'Q', x, y, x + rx, y, + 'Z' + ].join(' '); + } + return d; + }; - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ((matched = handlerQueue[ i++ ]) && !event.isPropagationStopped()) { - event.currentTarget = matched.elem; + // Convert a rectangle to SVG path commands. `r` is an object of the form: + // `{ x: [number], y: [number], width: [number], height: [number], top-ry: [number], top-ry: [number], bottom-rx: [number], bottom-ry: [number] }`, + // where `x, y, width, height` are the usual rectangle attributes and [top-/bottom-]rx/ry allows for + // specifying radius of the rectangle for all its sides (as opposed to the built-in SVG rectangle + // that has only `rx` and `ry` attributes). + V.rectToPath = function(r) { + + var topRx = r.rx || r['top-rx'] || 0; + var bottomRx = r.rx || r['bottom-rx'] || 0; + var topRy = r.ry || r['top-ry'] || 0; + var bottomRy = r.ry || r['bottom-ry'] || 0; + + return [ + 'M', r.x, r.y + topRy, + 'v', r.height - topRy - bottomRy, + 'a', bottomRx, bottomRy, 0, 0, 0, bottomRx, bottomRy, + 'h', r.width - 2 * bottomRx, + 'a', bottomRx, bottomRy, 0, 0, 0, bottomRx, -bottomRy, + 'v', -(r.height - bottomRy - topRy), + 'a', topRx, topRy, 0, 0, 0, -topRx, -topRy, + 'h', -(r.width - 2 * topRx), + 'a', topRx, topRy, 0, 0, 0, -topRx, topRy + ].join(' '); + }; - j = 0; - while ((handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped()) { + V.toNode = function(el) { + return V.isV(el) ? el.node : (el.nodeName && el || el[0]); + }; - // Triggered event must either 1) have no namespace, or - // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). - if (!event.namespace_re || event.namespace_re.test(handleObj.namespace)) { + return V; - event.handleObj = handleObj; - event.data = handleObj.data; + })(); - ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) - .apply(matched.elem, args); - if (ret !== undefined) { - if ((event.result = ret) === false) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } +// Global namespace. - // Call the postDispatch hook for the mapped type - if (special.postDispatch) { - special.postDispatch.call(this, event); - } + var joint = { + + version: '1.0.3', - return event.result; + config: { + // The class name prefix config is for advanced use only. + // Be aware that if you change the prefix, the JointJS CSS will no longer function properly. + classNamePrefix: 'joint-', + defaultTheme: 'default' }, - handlers: function (event, handlers) { - var i, matches, sel, handleObj, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; + // `joint.dia` namespace. + dia: {}, - // Find delegate handlers - // Black-hole SVG instance trees (#13180) - // Avoid non-left-click bubbling in Firefox (#3861) - if (delegateCount && cur.nodeType && (!event.button || event.type !== "click")) { + // `joint.ui` namespace. + ui: {}, - for (; cur !== this; cur = cur.parentNode || this) { + // `joint.layout` namespace. + layout: {}, - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if (cur.disabled !== true || event.type !== "click") { - matches = []; - for (i = 0; i < delegateCount; i++) { - handleObj = handlers[ i ]; + // `joint.shapes` namespace. + shapes: {}, - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; + // `joint.format` namespace. + format: {}, - if (matches[ sel ] === undefined) { - matches[ sel ] = handleObj.needsContext ? - jQuery(sel, this).index(cur) >= 0 : - jQuery.find(sel, this, null, [ cur ]).length; - } - if (matches[ sel ]) { - matches.push(handleObj); - } - } - if (matches.length) { - handlerQueue.push({ elem: cur, handlers: matches }); - } - } - } - } + // `joint.connectors` namespace. + connectors: {}, - // Add the remaining (directly-bound) handlers - if (delegateCount < handlers.length) { - handlerQueue.push({ elem: this, handlers: handlers.slice(delegateCount) }); - } + // `joint.highlighters` namespace. + highlighters: {}, - return handlerQueue; - }, + // `joint.routers` namespace. + routers: {}, - // Includes some event props shared by KeyEvent and MouseEvent - props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + // `joint.mvc` namespace. + mvc: { + views: {} + }, - fixHooks: {}, + setTheme: function(theme, opt) { - keyHooks: { - props: "char charCode key keyCode".split(" "), - filter: function (event, original) { + opt = opt || {}; - // Add which for key events - if (event.which == null) { - event.which = original.charCode != null ? original.charCode : original.keyCode; - } + _.invoke(joint.mvc.views, 'setTheme', theme, opt); - return event; - } + // Update the default theme on the view prototype. + joint.mvc.View.prototype.defaultTheme = theme; }, - mouseHooks: { - props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "), - filter: function (event, original) { - var eventDoc, doc, body, - button = original.button; + // `joint.env` namespace. + env: { - // Calculate pageX/Y if missing and clientX/Y available - if (event.pageX == null && original.clientX != null) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; + _results: {}, - event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); - event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); - } + _tests: { - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if (!event.which && button !== undefined) { - event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + svgforeignobject: function() { + return !!document.createElementNS && + /SVGForeignObject/.test(({}).toString.call(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'))); } + }, - return event; - } - }, + addTest: function(name, fn) { - fix: function (event) { - if (event[ jQuery.expando ]) { - return event; - } + return joint.env._tests[name] = fn; + }, - // Create a writable copy of the event object and normalize some properties - var i, prop, copy, - type = event.type, - originalEvent = event, - fixHook = this.fixHooks[ type ]; + test: function(name) { - if (!fixHook) { - this.fixHooks[ type ] = fixHook = - rmouseEvent.test(type) ? this.mouseHooks : - rkeyEvent.test(type) ? this.keyHooks : - {}; - } - copy = fixHook.props ? this.props.concat(fixHook.props) : this.props; + var fn = joint.env._tests[name]; - event = new jQuery.Event(originalEvent); + if (!fn) { + throw new Error('Test not defined ("' + name + '"). Use `joint.env.addTest(name, fn) to add a new test.`'); + } - i = copy.length; - while (i--) { - prop = copy[ i ]; - event[ prop ] = originalEvent[ prop ]; - } + var result = joint.env._results[name]; - // Support: Cordova 2.5 (WebKit) (#13255) - // All events should have a target; Cordova deviceready doesn't - if (!event.target) { - event.target = document; - } + if (typeof result !== 'undefined') { + return result; + } - // Support: Safari 6.0+, Chrome < 28 - // Target should not be a text node (#504, #13143) - if (event.target.nodeType === 3) { - event.target = event.target.parentNode; - } + try { + result = fn(); + } catch (error) { + result = false; + } + + // Cache the test result. + joint.env._results[name] = result; - return fixHook.filter ? fixHook.filter(event, originalEvent) : event; + return result; + } }, - special: { - load: { - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - focus: { - // Fire native event if possible so blur/focus sequence is correct - trigger: function () { - if (this !== safeActiveElement() && this.focus) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function () { - if (this === safeActiveElement() && this.blur) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - click: { - // For checkbox, fire native event so checked state will be right - trigger: function () { - if (this.type === "checkbox" && this.click && jQuery.nodeName(this, "input")) { - this.click(); - return false; - } - }, + util: { + + // Return a simple hash code from a string. See http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/. + hashCode: function(str) { - // For cross-browser consistency, don't fire native .click() on links - _default: function (event) { - return jQuery.nodeName(event.target, "a"); + var hash = 0; + if (str.length == 0) return hash; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + hash = ((hash << 5) - hash) + c; + hash = hash & hash; // Convert to 32bit integer } + return hash; }, - beforeunload: { - postDispatch: function (event) { + getByPath: function(obj, path, delim) { - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if (event.result !== undefined) { - event.originalEvent.returnValue = event.result; + delim = delim || '/'; + var keys = path.split(delim); + var key; + + while (keys.length) { + key = keys.shift(); + if (Object(obj) === obj && key in obj) { + obj = obj[key]; + } else { + return undefined; } } - } - }, + return obj; + }, - simulate: function (type, elem, event, bubble) { - // Piggyback on a donor event to simulate a different one. - // Fake originalEvent to avoid donor's stopPropagation, but if the - // simulated event prevents default then we do the same on the donor. - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true, - originalEvent: {} - } - ); - if (bubble) { - jQuery.event.trigger(e, null, elem); - } else { - jQuery.event.dispatch.call(elem, e); - } - if (e.isDefaultPrevented()) { - event.preventDefault(); - } - } - }; + setByPath: function(obj, path, value, delim) { - jQuery.removeEvent = function (elem, type, handle) { - if (elem.removeEventListener) { - elem.removeEventListener(type, handle, false); - } - }; + delim = delim || '/'; - jQuery.Event = function (src, props) { - // Allow instantiation without the 'new' keyword - if (!(this instanceof jQuery.Event)) { - return new jQuery.Event(src, props); - } + var keys = path.split(delim); + var diver = obj; + var i = 0; - // Event object - if (src && src.type) { - this.originalEvent = src; - this.type = src.type; + if (path.indexOf(delim) > -1) { - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = ( src.defaultPrevented || - src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + for (var len = keys.length; i < len - 1; i++) { + // diver creates an empty object if there is no nested object under such a key. + // This means that one can populate an empty nested object with setByPath(). + diver = diver[keys[i]] || (diver[keys[i]] = {}); + } + diver[keys[len - 1]] = value; + } else { + obj[path] = value; + } + return obj; + }, - // Event type - } else { - this.type = src; - } + unsetByPath: function(obj, path, delim) { - // Put explicitly provided properties onto the event object - if (props) { - jQuery.extend(this, props); - } + delim = delim || '/'; - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); + // index of the last delimiter + var i = path.lastIndexOf(delim); - // Mark it as fixed - this[ jQuery.expando ] = true; - }; + if (i > -1) { -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html - jQuery.Event.prototype = { - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, + // unsetting a nested attribute + var parent = joint.util.getByPath(obj, path.substr(0, i), delim); - preventDefault: function () { - var e = this.originalEvent; + if (parent) { + delete parent[path.slice(i + 1)]; + } - this.isDefaultPrevented = returnTrue; + } else { - if (e && e.preventDefault) { - e.preventDefault(); - } - }, - stopPropagation: function () { - var e = this.originalEvent; + // unsetting a primitive attribute + delete obj[path]; + } - this.isPropagationStopped = returnTrue; + return obj; + }, - if (e && e.stopPropagation) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function () { - this.isImmediatePropagationStopped = returnTrue; - this.stopPropagation(); - } - }; + flattenObject: function(obj, delim, stop) { -// Create mouseenter/leave events using mouseover/out and event-time checks -// Support: Chrome 15+ - jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout" - }, function (orig, fix) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function (event) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mousenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if (!related || (related !== target && !jQuery.contains(target, related))) { - event.type = handleObj.origType; - ret = handleObj.handler.apply(this, arguments); - event.type = fix; - } - return ret; - } - }; - }); + delim = delim || '/'; + var ret = {}; -// Create "bubbling" focus and blur events -// Support: Firefox, Chrome, Safari - if (!jQuery.support.focusinBubbles) { - jQuery.each({ focus: "focusin", blur: "focusout" }, function (orig, fix) { + for (var key in obj) { - // Attach a single capturing handler while someone wants focusin/focusout - var attaches = 0, - handler = function (event) { - jQuery.event.simulate(fix, event.target, jQuery.event.fix(event), true); - }; + if (!obj.hasOwnProperty(key)) continue; - jQuery.event.special[ fix ] = { - setup: function () { - if (attaches++ === 0) { - document.addEventListener(orig, handler, true); + var shouldGoDeeper = typeof obj[key] === 'object'; + if (shouldGoDeeper && stop && stop(obj[key])) { + shouldGoDeeper = false; } - }, - teardown: function () { - if (--attaches === 0) { - document.removeEventListener(orig, handler, true); - } - } - }; - }); - } - jQuery.fn.extend({ + if (shouldGoDeeper) { - on: function (types, selector, data, fn, /*INTERNAL*/ one) { - var origFn, type; + var flatObject = this.flattenObject(obj[key], delim, stop); - // Types can be a map of types/handlers - if (typeof types === "object") { - // ( types-Object, selector, data ) - if (typeof selector !== "string") { - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for (type in types) { - this.on(type, selector, data, types[ type ], one); - } - return this; - } + for (var flatKey in flatObject) { + if (!flatObject.hasOwnProperty(flatKey)) continue; + ret[key + delim + flatKey] = flatObject[flatKey]; + } - if (data == null && fn == null) { - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if (fn == null) { - if (typeof selector === "string") { - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if (fn === false) { - fn = returnFalse; - } else if (!fn) { - return this; - } + } else { - if (one === 1) { - origFn = fn; - fn = function (event) { - // Can use an empty set, since event contains the info - jQuery().off(event); - return origFn.apply(this, arguments); - }; - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return this.each(function () { - jQuery.event.add(this, types, fn, data, selector); - }); - }, - one: function (types, selector, data, fn) { - return this.on(types, selector, data, fn, 1); - }, - off: function (types, selector, fn) { - var handleObj, type; - if (types && types.preventDefault && types.handleObj) { - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery(types.delegateTarget).off( - handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if (typeof types === "object") { - // ( types-object [, selector] ) - for (type in types) { - this.off(type, selector, types[ type ]); + ret[key] = obj[key]; + } } - return this; - } - if (selector === false || typeof selector === "function") { - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if (fn === false) { - fn = returnFalse; - } - return this.each(function () { - jQuery.event.remove(this, types, fn, selector); - }); - }, - trigger: function (type, data) { - return this.each(function () { - jQuery.event.trigger(type, data, this); - }); - }, - triggerHandler: function (type, data) { - var elem = this[0]; - if (elem) { - return jQuery.event.trigger(type, data, elem, true); - } - } - }); - var isSimple = /^.[^:#\[\.,]*$/, - rparentsprev = /^(?:parents|prev(?:Until|All))/, - rneedsContext = jQuery.expr.match.needsContext, - // methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; + return ret; + }, - jQuery.fn.extend({ - find: function (selector) { - var i, - ret = [], - self = this, - len = self.length; - - if (typeof selector !== "string") { - return this.pushStack(jQuery(selector).filter(function () { - for (i = 0; i < len; i++) { - if (jQuery.contains(self[ i ], this)) { - return true; - } - } - })); - } + uuid: function() { - for (i = 0; i < len; i++) { - jQuery.find(selector, self[ i ], ret); - } + // credit: http://stackoverflow.com/posts/2117523/revisions - // Needed because $( selector, context ) becomes $( context ).find( selector ) - ret = this.pushStack(len > 1 ? jQuery.unique(ret) : ret); - ret.selector = this.selector ? this.selector + " " + selector : selector; - return ret; - }, + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random() * 16|0; + var v = c == 'x' ? r : (r&0x3|0x8); + return v.toString(16); + }); + }, - has: function (target) { - var targets = jQuery(target, this), - l = targets.length; + // Generate global unique id for obj and store it as a property of the object. + guid: function(obj) { - return this.filter(function () { - var i = 0; - for (; i < l; i++) { - if (jQuery.contains(this, targets[i])) { - return true; + this.guid.id = this.guid.id || 1; + obj.id = (obj.id === undefined ? 'j_' + this.guid.id++ : obj.id); + return obj.id; + }, + + // Copy all the properties to the first argument from the following arguments. + // All the properties will be overwritten by the properties from the following + // arguments. Inherited properties are ignored. + mixin: _.assign, + + // Copy all properties to the first argument from the following + // arguments only in case if they don't exists in the first argument. + // All the function propererties in the first argument will get + // additional property base pointing to the extenders same named + // property function's call method. + supplement: _.defaults, + + // Same as `mixin()` but deep version. + deepMixin: _.mixin, + + // Same as `supplement()` but deep version. + deepSupplement: _.defaultsDeep, + + normalizeEvent: function(evt) { + + var touchEvt = evt.originalEvent && evt.originalEvent.changedTouches && evt.originalEvent.changedTouches[0]; + if (touchEvt) { + for (var property in evt) { + // copy all the properties from the input event that are not + // defined on the touch event (functions included). + if (touchEvt[property] === undefined) { + touchEvt[property] = evt[property]; + } } + return touchEvt; } - }); - }, - not: function (selector) { - return this.pushStack(winnow(this, selector || [], true)); - }, + return evt; + }, - filter: function (selector) { - return this.pushStack(winnow(this, selector || [], false)); - }, + nextFrame: (function() { - is: function (selector) { - return !!winnow( - this, + var raf; - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test(selector) ? - jQuery(selector) : - selector || [], - false - ).length; - }, - - closest: function (selectors, context) { - var cur, - i = 0, - l = this.length, - matched = [], - pos = ( rneedsContext.test(selectors) || typeof selectors !== "string" ) ? - jQuery(selectors, context || this.context) : - 0; - - for (; i < l; i++) { - for (cur = this[i]; cur && cur !== context; cur = cur.parentNode) { - // Always skip document fragments - if (cur.nodeType < 11 && (pos ? - pos.index(cur) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector(cur, selectors))) { - - cur = matched.push(cur); - break; - } + if (typeof window !== 'undefined') { + + raf = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame; } - } - return this.pushStack(matched.length > 1 ? jQuery.unique(matched) : matched); - }, + if (!raf) { - // Determine the position of an element within - // the matched set of elements - index: function (elem) { + var lastTime = 0; - // No argument, return index in parent - if (!elem) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } + raf = function(callback) { - // index in selector - if (typeof elem === "string") { - return core_indexOf.call(jQuery(elem), this[ 0 ]); - } + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); - // Locate the position of the desired element - return core_indexOf.call(this, + lastTime = currTime + timeToCall; - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, + return id; + }; + } - add: function (selector, context) { - var set = typeof selector === "string" ? - jQuery(selector, context) : - jQuery.makeArray(selector && selector.nodeType ? [ selector ] : selector), - all = jQuery.merge(this.get(), set); + return function(callback, context) { + return context + ? raf(_.bind(callback, context)) + : raf(callback); + }; - return this.pushStack(jQuery.unique(all)); - }, + })(), - addBack: function (selector) { - return this.add(selector == null ? - this.prevObject : this.prevObject.filter(selector) - ); - } - }); + cancelFrame: (function() { - function sibling(cur, dir) { - while ((cur = cur[dir]) && cur.nodeType !== 1) { - } + var caf; + var client = typeof window != 'undefined'; - return cur; - } + if (client) { - jQuery.each({ - parent: function (elem) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function (elem) { - return jQuery.dir(elem, "parentNode"); - }, - parentsUntil: function (elem, i, until) { - return jQuery.dir(elem, "parentNode", until); - }, - next: function (elem) { - return sibling(elem, "nextSibling"); - }, - prev: function (elem) { - return sibling(elem, "previousSibling"); - }, - nextAll: function (elem) { - return jQuery.dir(elem, "nextSibling"); - }, - prevAll: function (elem) { - return jQuery.dir(elem, "previousSibling"); - }, - nextUntil: function (elem, i, until) { - return jQuery.dir(elem, "nextSibling", until); - }, - prevUntil: function (elem, i, until) { - return jQuery.dir(elem, "previousSibling", until); - }, - siblings: function (elem) { - return jQuery.sibling(( elem.parentNode || {} ).firstChild, elem); - }, - children: function (elem) { - return jQuery.sibling(elem.firstChild); - }, - contents: function (elem) { - return elem.contentDocument || jQuery.merge([], elem.childNodes); - } - }, function (name, fn) { - jQuery.fn[ name ] = function (until, selector) { - var matched = jQuery.map(this, fn, until); + caf = window.cancelAnimationFrame || + window.webkitCancelAnimationFrame || + window.webkitCancelRequestAnimationFrame || + window.msCancelAnimationFrame || + window.msCancelRequestAnimationFrame || + window.oCancelAnimationFrame || + window.oCancelRequestAnimationFrame || + window.mozCancelAnimationFrame || + window.mozCancelRequestAnimationFrame; + } - if (name.slice(-5) !== "Until") { - selector = until; - } + caf = caf || clearTimeout; - if (selector && typeof selector === "string") { - matched = jQuery.filter(selector, matched); - } + return client ? _.bind(caf, window) : caf; - if (this.length > 1) { - // Remove duplicates - if (!guaranteedUnique[ name ]) { - jQuery.unique(matched); - } + })(), - // Reverse order for parents* and prev-derivatives - if (rparentsprev.test(name)) { - matched.reverse(); - } - } + shapePerimeterConnectionPoint: function(linkView, view, magnet, reference) { - return this.pushStack(matched); - }; - }); + var bbox; + var spot; - jQuery.extend({ - filter: function (expr, elems, not) { - var elem = elems[ 0 ]; + if (!magnet) { - if (not) { - expr = ":not(" + expr + ")"; - } + // There is no magnet, try to make the best guess what is the + // wrapping SVG element. This is because we want this "smart" + // connection points to work out of the box without the + // programmer to put magnet marks to any of the subelements. + // For example, we want the functoin to work on basic.Path elements + // without any special treatment of such elements. + // The code below guesses the wrapping element based on + // one simple assumption. The wrapping elemnet is the + // first child of the scalable group if such a group exists + // or the first child of the rotatable group if not. + // This makese sense because usually the wrapping element + // is below any other sub element in the shapes. + var scalable = view.$('.scalable')[0]; + var rotatable = view.$('.rotatable')[0]; - return elems.length === 1 && elem.nodeType === 1 ? - jQuery.find.matchesSelector(elem, expr) ? [ elem ] : [] : - jQuery.find.matches(expr, jQuery.grep(elems, function (elem) { - return elem.nodeType === 1; - })); - }, + if (scalable && scalable.firstChild) { - dir: function (elem, dir, until) { - var matched = [], - truncate = until !== undefined; + magnet = scalable.firstChild; - while ((elem = elem[ dir ]) && elem.nodeType !== 9) { - if (elem.nodeType === 1) { - if (truncate && jQuery(elem).is(until)) { - break; + } else if (rotatable && rotatable.firstChild) { + + magnet = rotatable.firstChild; } - matched.push(elem); } - } - return matched; - }, - sibling: function (n, elem) { - var matched = []; + if (magnet) { - for (; n; n = n.nextSibling) { - if (n.nodeType === 1 && n !== elem) { - matched.push(n); + spot = V(magnet).findIntersection(reference, linkView.paper.viewport); + if (!spot) { + bbox = g.rect(V(magnet).bbox(false, linkView.paper.viewport)); + } + + } else { + + bbox = view.model.getBBox(); + spot = bbox.intersectionWithLineFromCenterToPoint(reference); } - } + return spot || bbox.center(); + }, - return matched; - } - }); + breakText: function(text, size, styles, opt) { -// Implement the identical functionality for filter and not - function winnow(elements, qualifier, not) { - if (jQuery.isFunction(qualifier)) { - return jQuery.grep(elements, function (elem, i) { - /* jshint -W018 */ - return !!qualifier.call(elem, i, elem) !== not; - }); + opt = opt || {}; - } + var width = size.width; + var height = size.height; - if (qualifier.nodeType) { - return jQuery.grep(elements, function (elem) { - return ( elem === qualifier ) !== not; - }); + var svgDocument = opt.svgDocument || V('svg').node; + var textElement = V('').attr(styles || {}).node; + var textSpan = textElement.firstChild; + var textNode = document.createTextNode(''); - } + // Prevent flickering + textElement.style.opacity = 0; + // Prevent FF from throwing an uncaught exception when `getBBox()` + // called on element that is not in the render tree (is not measurable). + // .getComputedTextLength() returns always 0 in this case. + // Note that the `textElement` resp. `textSpan` can become hidden + // when it's appended to the DOM and a `display: none` CSS stylesheet + // rule gets applied. + textElement.style.display = 'block'; + textSpan.style.display = 'block'; - if (typeof qualifier === "string") { - if (isSimple.test(qualifier)) { - return jQuery.filter(qualifier, elements, not); - } + textSpan.appendChild(textNode); + svgDocument.appendChild(textElement); - qualifier = jQuery.filter(qualifier, elements); - } + if (!opt.svgDocument) { - return jQuery.grep(elements, function (elem) { - return ( core_indexOf.call(qualifier, elem) >= 0 ) !== not; - }); - } + document.body.appendChild(svgDocument); + } - var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, - rtagName = /<([\w:]+)/, - rhtml = /<|&#?\w+;/, - rnoInnerhtml = /<(?:script|style|link)/i, - manipulation_rcheckableType = /^(?:checkbox|radio)$/i, - // checked="checked" or checked - rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - rscriptType = /^$|\/(?:java|ecma)script/i, - rscriptTypeMasked = /^true\/(.*)/, - rcleanScript = /^\s*\s*$/g, - - // We have to close these tags to support XHTML (#13200) - wrapMap = { - - // Support: IE 9 - option: [ 1, "" ], - - thead: [ 1, "", "
" ], - col: [ 2, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - - _default: [ 0, "", "" ] - }; + var words = text.split(' '); + var full = []; + var lines = []; + var p; -// Support: IE 9 - wrapMap.optgroup = wrapMap.option; + for (var i = 0, l = 0, len = words.length; i < len; i++) { - wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; - wrapMap.th = wrapMap.td; + var word = words[i]; - jQuery.fn.extend({ - text: function (value) { - return jQuery.access(this, function (value) { - return value === undefined ? - jQuery.text(this) : - this.empty().append(( this[ 0 ] && this[ 0 ].ownerDocument || document ).createTextNode(value)); - }, null, value, arguments.length); - }, + textNode.data = lines[l] ? lines[l] + ' ' + word : word; - append: function () { - return this.domManip(arguments, function (elem) { - if (this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9) { - var target = manipulationTarget(this, elem); - target.appendChild(elem); - } - }); - }, + if (textSpan.getComputedTextLength() <= width) { - prepend: function () { - return this.domManip(arguments, function (elem) { - if (this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9) { - var target = manipulationTarget(this, elem); - target.insertBefore(elem, target.firstChild); - } - }); - }, + // the current line fits + lines[l] = textNode.data; - before: function () { - return this.domManip(arguments, function (elem) { - if (this.parentNode) { - this.parentNode.insertBefore(elem, this); - } - }); - }, + if (p) { + // We were partitioning. Put rest of the word onto next line + full[l++] = true; - after: function () { - return this.domManip(arguments, function (elem) { - if (this.parentNode) { - this.parentNode.insertBefore(elem, this.nextSibling); - } - }); - }, + // cancel partitioning + p = 0; + } - // keepData is for internal use only--do not document - remove: function (selector, keepData) { - var elem, - elems = selector ? jQuery.filter(selector, this) : this, - i = 0; + } else { - for (; (elem = elems[i]) != null; i++) { - if (!keepData && elem.nodeType === 1) { - jQuery.cleanData(getAll(elem)); - } + if (!lines[l] || p) { - if (elem.parentNode) { - if (keepData && jQuery.contains(elem.ownerDocument, elem)) { - setGlobalEval(getAll(elem, "script")); - } - elem.parentNode.removeChild(elem); - } - } + var partition = !!p; - return this; - }, + p = word.length - 1; - empty: function () { - var elem, - i = 0; + if (partition || !p) { - for (; (elem = this[i]) != null; i++) { - if (elem.nodeType === 1) { + // word has only one character. + if (!p) { - // Prevent memory leaks - jQuery.cleanData(getAll(elem, false)); + if (!lines[l]) { - // Remove any remaining nodes - elem.textContent = ""; - } - } + // we won't fit this text within our rect + lines = []; - return this; - }, + break; + } - clone: function (dataAndEvents, deepDataAndEvents) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + // partitioning didn't help on the non-empty line + // try again, but this time start with a new line - return this.map(function () { - return jQuery.clone(this, dataAndEvents, deepDataAndEvents); - }); - }, + // cancel partitions created + words.splice(i, 2, word + words[i + 1]); - html: function (value) { - return jQuery.access(this, function (value) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; + // adjust word length + len--; - if (value === undefined && elem.nodeType === 1) { - return elem.innerHTML; - } + full[l++] = true; + i--; + + continue; + } + + // move last letter to the beginning of the next word + words[i] = word.substring(0, p); + words[i + 1] = word.substring(p) + words[i + 1]; - // See if we can take a shortcut and just use innerHTML - if (typeof value === "string" && !rnoInnerhtml.test(value) && !wrapMap[ ( rtagName.exec(value) || [ "", "" ] )[ 1 ].toLowerCase() ]) { + } else { - value = value.replace(rxhtmlTag, "<$1>"); + // We initiate partitioning + // split the long word into two words + words.splice(i, 1, word.substring(0, p), word.substring(p)); - try { - for (; i < l; i++) { - elem = this[ i ] || {}; + // adjust words length + len++; - // Remove element nodes and prevent memory leaks - if (elem.nodeType === 1) { - jQuery.cleanData(getAll(elem, false)); - elem.innerHTML = value; + if (l && !full[l - 1]) { + // if the previous line is not full, try to fit max part of + // the current word there + l--; + } } - } - elem = 0; + i--; + + continue; + } - // If using innerHTML throws an exception, use the fallback method - } catch (e) { + l++; + i--; } - } - if (elem) { - this.empty().append(value); - } - }, null, value, arguments.length); - }, + // if size.height is defined we have to check whether the height of the entire + // text exceeds the rect height + if (typeof height !== 'undefined') { - replaceWith: function () { - var - // Snapshot the DOM in case .domManip sweeps something relevant into its fragment - args = jQuery.map(this, function (elem) { - return [ elem.nextSibling, elem.parentNode ]; - }), - i = 0; + // get line height as text height / 0.8 (as text height is approx. 0.8em + // and line height is 1em. See vectorizer.text()) + var lh = lh || textElement.getBBox().height * 1.25; - // Make the changes, replacing each context element with the new content - this.domManip(arguments, function (elem) { - var next = args[ i++ ], - parent = args[ i++ ]; + if (lh * lines.length > height) { - if (parent) { - // Don't use the snapshot next if it has moved (#13810) - if (next && next.parentNode !== parent) { - next = this.nextSibling; + // remove overflowing lines + lines.splice(Math.floor(height / lh)); + + break; + } } - jQuery(this).remove(); - parent.insertBefore(elem, next); } - // Allow new content to include elements from the context set - }, true); - // Force removal if there was no new content (e.g., from empty arguments) - return i ? this : this.remove(); - }, + if (opt.svgDocument) { - detach: function (selector) { - return this.remove(selector, true); - }, + // svg document was provided, remove the text element only + svgDocument.removeChild(textElement); + + } else { - domManip: function (args, callback, allowIntersection) { + // clean svg document + document.body.removeChild(svgDocument); + } - // Flatten any nested arrays - args = core_concat.apply([], args); + return lines.join('\n'); + }, - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = this.length, - set = this, - iNoClone = l - 1, - value = args[ 0 ], - isFunction = jQuery.isFunction(value); + imageToDataUri: function(url, callback) { - // We can't cloneNode fragments that contain checked, in WebKit - if (isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test(value) )) { - return this.each(function (index) { - var self = set.eq(index); - if (isFunction) { - args[ 0 ] = value.call(this, index, self.html()); - } - self.domManip(args, callback, allowIntersection); - }); - } + if (!url || url.substr(0, 'data:'.length) === 'data:') { + // No need to convert to data uri if it is already in data uri. - if (l) { - fragment = jQuery.buildFragment(args, this[ 0 ].ownerDocument, false, !allowIntersection && this); - first = fragment.firstChild; + // This not only convenient but desired. For example, + // IE throws a security error if data:image/svg+xml is used to render + // an image to the canvas and an attempt is made to read out data uri. + // Now if our image is already in data uri, there is no need to render it to the canvas + // and so we can bypass this error. - if (fragment.childNodes.length === 1) { - fragment = first; + // Keep the async nature of the function. + return setTimeout(function() { + callback(null, url); + }, 0); } - if (first) { - scripts = jQuery.map(getAll(fragment, "script"), disableScript); - hasScripts = scripts.length; + // chrome IE10 IE11 + var modernHandler = function(xhr, callback) { - // Use the original fragment for the last item instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for (; i < l; i++) { - node = fragment; + if (xhr.status === 200) { - if (i !== iNoClone) { - node = jQuery.clone(node, true, true); + var reader = new FileReader(); - // Keep references to cloned scripts for later restoration - if (hasScripts) { - // Support: QtWebKit - // jQuery.merge because core_push.apply(_, arraylike) throws - jQuery.merge(scripts, getAll(node, "script")); - } - } + reader.onload = function(evt) { + var dataUri = evt.target.result; + callback(null, dataUri); + }; - callback.call(this[ i ], node, i); - } + reader.onerror = function() { + callback(new Error('Failed to load image ' + url)); + }; - if (hasScripts) { - doc = scripts[ scripts.length - 1 ].ownerDocument; + reader.readAsDataURL(xhr.response); + } else { + callback(new Error('Failed to load image ' + url)); + } - // Reenable scripts - jQuery.map(scripts, restoreScript); + }; - // Evaluate executable scripts on first document insertion - for (i = 0; i < hasScripts; i++) { - node = scripts[ i ]; - if (rscriptType.test(node.type || "") && !data_priv.access(node, "globalEval") && jQuery.contains(doc, node)) { + var legacyHandler = function(xhr, callback) { - if (node.src) { - // Hope ajax is available... - jQuery._evalUrl(node.src); - } else { - jQuery.globalEval(node.textContent.replace(rcleanScript, "")); - } - } + var Uint8ToString = function(u8a) { + var CHUNK_SZ = 0x8000; + var c = []; + for (var i = 0; i < u8a.length; i += CHUNK_SZ) { + c.push(String.fromCharCode.apply(null, u8a.subarray(i, i + CHUNK_SZ))); } - } - } - } + return c.join(''); + }; - return this; - } - }); - jQuery.each({ - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" - }, function (name, original) { - jQuery.fn[ name ] = function (selector) { - var elems, - ret = [], - insert = jQuery(selector), - last = insert.length - 1, - i = 0; - - for (; i <= last; i++) { - elems = i === last ? this : this.clone(true); - jQuery(insert[ i ])[ original ](elems); - - // Support: QtWebKit - // .get() because core_push.apply(_, arraylike) throws - core_push.apply(ret, elems.get()); - } - - return this.pushStack(ret); - }; - }); + if (xhr.status === 200) { - jQuery.extend({ - clone: function (elem, dataAndEvents, deepDataAndEvents) { - var i, l, srcElements, destElements, - clone = elem.cloneNode(true), - inPage = jQuery.contains(elem.ownerDocument, elem); + var bytes = new Uint8Array(xhr.response); - // Support: IE >= 9 - // Fix Cloning issues - if (!jQuery.support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc(elem)) { + var suffix = (url.split('.').pop()) || 'png'; + var map = { + 'svg': 'svg+xml' + }; + var meta = 'data:image/' + (map[suffix] || suffix) + ';base64,'; + var b64encoded = meta + btoa(Uint8ToString(bytes)); + callback(null, b64encoded); + } else { + callback(new Error('Failed to load image ' + url)); + } + }; - // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 - destElements = getAll(clone); - srcElements = getAll(elem); + var xhr = new XMLHttpRequest(); - for (i = 0, l = srcElements.length; i < l; i++) { - fixInput(srcElements[ i ], destElements[ i ]); - } - } + xhr.open('GET', url, true); + xhr.addEventListener('error', function() { + callback(new Error('Failed to load image ' + url)); + }); - // Copy the events from the original to the clone - if (dataAndEvents) { - if (deepDataAndEvents) { - srcElements = srcElements || getAll(elem); - destElements = destElements || getAll(clone); + xhr.responseType = window.FileReader ? 'blob' : 'arraybuffer'; - for (i = 0, l = srcElements.length; i < l; i++) { - cloneCopyEvent(srcElements[ i ], destElements[ i ]); + xhr.addEventListener('load', function() { + if (window.FileReader) { + modernHandler(xhr, callback); + } else { + legacyHandler(xhr, callback); } - } else { - cloneCopyEvent(elem, clone); - } - } + }); - // Preserve script evaluation history - destElements = getAll(clone, "script"); - if (destElements.length > 0) { - setGlobalEval(destElements, !inPage && getAll(elem, "script")); - } + xhr.send(); + }, - // Return the cloned set - return clone; - }, + getElementBBox: function(el) { - buildFragment: function (elems, context, scripts, selection) { - var elem, tmp, tag, wrap, contains, j, - i = 0, - l = elems.length, - fragment = context.createDocumentFragment(), - nodes = []; + var $el = $(el); + if ($el.length === 0) { + throw new Error('Element not found') + } - for (; i < l; i++) { - elem = elems[ i ]; + var element = $el[0]; + var doc = element.ownerDocument; + var clientBBox = element.getBoundingClientRect(); - if (elem || elem === 0) { + var strokeWidthX = 0; + var strokeWidthY = 0; - // Add nodes directly - if (jQuery.type(elem) === "object") { - // Support: QtWebKit - // jQuery.merge because core_push.apply(_, arraylike) throws - jQuery.merge(nodes, elem.nodeType ? [ elem ] : elem); + // Firefox correction + if (element.ownerSVGElement) { - // Convert non-html into a text node - } else if (!rhtml.test(elem)) { - nodes.push(context.createTextNode(elem)); + var bbox = V(element).bbox(); - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild(context.createElement("div")); + // if FF getBoundingClientRect includes stroke-width, getBBox doesn't. + // To unify this across all browsers we need to adjust the final bBox with `stroke-width` value. + strokeWidthX = (clientBBox.width - bbox.width); + strokeWidthY = (clientBBox.height - bbox.height); + } - // Deserialize a standard representation - tag = ( rtagName.exec(elem) || ["", ""] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + elem.replace(rxhtmlTag, "<$1>") + wrap[ 2 ]; + return { + x: clientBBox.left + window.pageXOffset - doc.documentElement.offsetLeft + strokeWidthX / 2, + y: clientBBox.top + window.pageYOffset - doc.documentElement.offsetTop + strokeWidthY / 2, + width: clientBBox.width - strokeWidthX, + height: clientBBox.height - strokeWidthY + }; + }, - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while (j--) { - tmp = tmp.lastChild; - } - // Support: QtWebKit - // jQuery.merge because core_push.apply(_, arraylike) throws - jQuery.merge(nodes, tmp.childNodes); + // Highly inspired by the jquery.sortElements plugin by Padolsey. + // See http://james.padolsey.com/javascript/sorting-elements-with-jquery/. + sortElements: function(elements, comparator) { - // Remember the top-level container - tmp = fragment.firstChild; + var $elements = $(elements); + var placements = $elements.map(function() { - // Fixes #12346 - // Support: Webkit, IE - tmp.textContent = ""; - } - } - } + var sortElement = this; + var parentNode = sortElement.parentNode; + // Since the element itself will change position, we have + // to have some way of storing it's original position in + // the DOM. The easiest way is to have a 'flag' node: + var nextSibling = parentNode.insertBefore(document.createTextNode(''), sortElement.nextSibling); - // Remove wrapper from fragment - fragment.textContent = ""; + return function() { - i = 0; - while ((elem = nodes[ i++ ])) { + if (parentNode === this) { + throw new Error('You can\'t sort elements if any one is a descendant of another.'); + } - // #4087 - If origin and destination elements are the same, and this is - // that element, do not do anything - if (selection && jQuery.inArray(elem, selection) !== -1) { - continue; - } + // Insert before flag: + parentNode.insertBefore(this, nextSibling); + // Remove flag: + parentNode.removeChild(nextSibling); + }; + }); - contains = jQuery.contains(elem.ownerDocument, elem); + return Array.prototype.sort.call($elements, comparator).each(function(i) { + placements[i].call(this); + }); + }, - // Append to fragment - tmp = getAll(fragment.appendChild(elem), "script"); + // Sets attributes on the given element and its descendants based on the selector. + // `attrs` object: { [SELECTOR1]: { attrs1 }, [SELECTOR2]: { attrs2}, ... } e.g. { 'input': { color : 'red' }} + setAttributesBySelector: function(element, attrs) { - // Preserve script evaluation history - if (contains) { - setGlobalEval(tmp); - } + var $element = $(element); - // Capture executables - if (scripts) { - j = 0; - while ((elem = tmp[ j++ ])) { - if (rscriptType.test(elem.type || "")) { - scripts.push(elem); - } + _.each(attrs, function(attrs, selector) { + var $elements = $element.find(selector).addBack().filter(selector); + // Make a special case for setting classes. + // We do not want to overwrite any existing class. + if (_.has(attrs, 'class')) { + $elements.addClass(attrs['class']); + attrs = _.omit(attrs, 'class'); } - } - } + $elements.attr(attrs); + }); + }, - return fragment; - }, + // Return a new object with all for sides (top, bottom, left and right) in it. + // Value of each side is taken from the given argument (either number or object). + // Default value for a side is 0. + // Examples: + // joint.util.normalizeSides(5) --> { top: 5, left: 5, right: 5, bottom: 5 } + // joint.util.normalizeSides({ left: 5 }) --> { top: 0, left: 5, right: 0, bottom: 0 } + normalizeSides: function(box) { - cleanData: function (elems) { - var data, elem, events, type, key, j, - special = jQuery.event.special, - i = 0; + if (Object(box) !== box) { + box = box || 0; + return { top: box, bottom: box, left: box, right: box }; + } - for (; (elem = elems[ i ]) !== undefined; i++) { - if (Data.accepts(elem)) { - key = elem[ data_priv.expando ]; + return { + top: box.top || 0, + bottom: box.bottom || 0, + left: box.left || 0, + right: box.right || 0 + }; + }, - if (key && (data = data_priv.cache[ key ])) { - events = Object.keys(data.events || {}); - if (events.length) { - for (j = 0; (type = events[j]) !== undefined; j++) { - if (special[ type ]) { - jQuery.event.remove(elem, type); + timing: { - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent(elem, type, data.handle); - } - } - } - if (data_priv.cache[ key ]) { - // Discard any remaining `private` data - delete data_priv.cache[ key ]; - } - } - } - // Discard any remaining `user` data - delete data_user.cache[ elem[ data_user.expando ] ]; - } - }, + linear: function(t) { + return t; + }, - _evalUrl: function (url) { - return jQuery.ajax({ - url: url, - type: "GET", - dataType: "script", - async: false, - global: false, - "throws": true - }); - } - }); + quad: function(t) { + return t * t; + }, -// Support: 1.x compatibility -// Manipulating tables requires a tbody - function manipulationTarget(elem, content) { - return jQuery.nodeName(elem, "table") && - jQuery.nodeName(content.nodeType === 1 ? content : content.firstChild, "tr") ? + cubic: function(t) { + return t * t * t; + }, - elem.getElementsByTagName("tbody")[0] || - elem.appendChild(elem.ownerDocument.createElement("tbody")) : - elem; - } + inout: function(t) { + if (t <= 0) return 0; + if (t >= 1) return 1; + var t2 = t * t; + var t3 = t2 * t; + return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75); + }, -// Replace/restore the type attribute of script elements for safe DOM manipulation - function disableScript(elem) { - elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; - return elem; - } + exponential: function(t) { + return Math.pow(2, 10 * (t - 1)); + }, + + bounce: function(t) { + for (var a = 0, b = 1; 1; a += b, b /= 2) { + if (t >= (7 - 4 * a) / 11) { + var q = (11 - 6 * a - 11 * t) / 4; + return -q * q + b * b; + } + } + }, - function restoreScript(elem) { - var match = rscriptTypeMasked.exec(elem.type); + reverse: function(f) { + return function(t) { + return 1 - f(1 - t); + }; + }, - if (match) { - elem.type = match[ 1 ]; - } else { - elem.removeAttribute("type"); - } + reflect: function(f) { + return function(t) { + return .5 * (t < .5 ? f(2 * t) : (2 - f(2 - 2 * t))); + }; + }, - return elem; - } + clamp: function(f, n, x) { + n = n || 0; + x = x || 1; + return function(t) { + var r = f(t); + return r < n ? n : r > x ? x : r; + }; + }, -// Mark scripts as having already been evaluated - function setGlobalEval(elems, refElements) { - var l = elems.length, - i = 0; + back: function(s) { + if (!s) s = 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; + }, - for (; i < l; i++) { - data_priv.set( - elems[ i ], "globalEval", !refElements || data_priv.get(refElements[ i ], "globalEval") - ); - } - } + elastic: function(x) { + if (!x) x = 1.5; + return function(t) { + return Math.pow(2, 10 * (t - 1)) * Math.cos(20 * Math.PI * x / 3 * t); + }; + } + }, - function cloneCopyEvent(src, dest) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + interpolate: { - if (dest.nodeType !== 1) { - return; - } + number: function(a, b) { + var d = b - a; + return function(t) { return a + d * t; }; + }, - // 1. Copy private data: events, handlers, etc. - if (data_priv.hasData(src)) { - pdataOld = data_priv.access(src); - pdataCur = data_priv.set(dest, pdataOld); - events = pdataOld.events; + object: function(a, b) { + var s = _.keys(a); + return function(t) { + var i, p; + var r = {}; + for (i = s.length - 1; i != -1; i--) { + p = s[i]; + r[p] = a[p] + (b[p] - a[p]) * t; + } + return r; + }; + }, - if (events) { - delete pdataCur.handle; - pdataCur.events = {}; + hexColor: function(a, b) { - for (type in events) { - for (i = 0, l = events[ type ].length; i < l; i++) { - jQuery.event.add(dest, type, events[ type ][ i ]); - } - } - } - } + var ca = parseInt(a.slice(1), 16); + var cb = parseInt(b.slice(1), 16); + var ra = ca & 0x0000ff; + var rd = (cb & 0x0000ff) - ra; + var ga = ca & 0x00ff00; + var gd = (cb & 0x00ff00) - ga; + var ba = ca & 0xff0000; + var bd = (cb & 0xff0000) - ba; - // 2. Copy user data - if (data_user.hasData(src)) { - udataOld = data_user.access(src); - udataCur = jQuery.extend({}, udataOld); + return function(t) { - data_user.set(dest, udataCur); - } - } + var r = (ra + rd * t) & 0x000000ff; + var g = (ga + gd * t) & 0x0000ff00; + var b = (ba + bd * t) & 0x00ff0000; + return '#' + (1 << 24 | r | g | b ).toString(16).slice(1); + }; + }, - function getAll(context, tag) { - var ret = context.getElementsByTagName ? context.getElementsByTagName(tag || "*") : - context.querySelectorAll ? context.querySelectorAll(tag || "*") : - []; + unit: function(a, b) { - return tag === undefined || tag && jQuery.nodeName(context, tag) ? - jQuery.merge([ context ], ret) : - ret; - } + var r = /(-?[0-9]*.[0-9]*)(px|em|cm|mm|in|pt|pc|%)/; + var ma = r.exec(a); + var mb = r.exec(b); + var p = mb[1].indexOf('.'); + var f = p > 0 ? mb[1].length - p - 1 : 0; + a = +ma[1]; + var d = +mb[1] - a; + var u = ma[2]; -// Support: IE >= 9 - function fixInput(src, dest) { - var nodeName = dest.nodeName.toLowerCase(); + return function(t) { + return (a + d * t).toFixed(f) + u; + }; + } + }, - // Fails to persist the checked state of a cloned checkbox or radio button. - if (nodeName === "input" && manipulation_rcheckableType.test(src.type)) { - dest.checked = src.checked; + // SVG filters. + filter: { - // Fails to return the selected option to the default selected state when cloning options - } else if (nodeName === "input" || nodeName === "textarea") { - dest.defaultValue = src.defaultValue; - } - } + // `color` ... outline color + // `width`... outline width + // `opacity` ... outline opacity + // `margin` ... gap between outline and the element + outline: function(args) { - jQuery.fn.extend({ - wrapAll: function (html) { - var wrap; + var tpl = ''; - if (jQuery.isFunction(html)) { - return this.each(function (i) { - jQuery(this).wrapAll(html.call(this, i)); - }); - } + var margin = _.isFinite(args.margin) ? args.margin : 2; + var width = _.isFinite(args.width) ? args.width : 1; - if (this[ 0 ]) { + return joint.util.template(tpl)({ + color: args.color || 'blue', + opacity: _.isFinite(args.opacity) ? args.opacity : 1, + outerRadius: margin + width, + innerRadius: margin + }); + }, - // The elements to wrap the target around - wrap = jQuery(html, this[ 0 ].ownerDocument).eq(0).clone(true); + // `color` ... color + // `width`... width + // `blur` ... blur + // `opacity` ... opacity + highlight: function(args) { - if (this[ 0 ].parentNode) { - wrap.insertBefore(this[ 0 ]); - } + var tpl = ''; - wrap.map(function () { - var elem = this; + return joint.util.template(tpl)({ + color: args.color || 'red', + width: _.isFinite(args.width) ? args.width : 1, + blur: _.isFinite(args.blur) ? args.blur : 0, + opacity: _.isFinite(args.opacity) ? args.opacity : 1 + }); + }, - while (elem.firstElementChild) { - elem = elem.firstElementChild; - } + // `x` ... horizontal blur + // `y` ... vertical blur (optional) + blur: function(args) { - return elem; - }).append(this); - } + var x = _.isFinite(args.x) ? args.x : 2; - return this; - }, + return joint.util.template('')({ + stdDeviation: _.isFinite(args.y) ? [x, args.y] : x + }); + }, - wrapInner: function (html) { - if (jQuery.isFunction(html)) { - return this.each(function (i) { - jQuery(this).wrapInner(html.call(this, i)); - }); - } + // `dx` ... horizontal shift + // `dy` ... vertical shift + // `blur` ... blur + // `color` ... color + // `opacity` ... opacity + dropShadow: function(args) { + + var tpl = 'SVGFEDropShadowElement' in window + ? '' + : ''; + + return joint.util.template(tpl)({ + dx: args.dx || 0, + dy: args.dy || 0, + opacity: _.isFinite(args.opacity) ? args.opacity : 1, + color: args.color || 'black', + blur: _.isFinite(args.blur) ? args.blur : 4 + }); + }, - return this.each(function () { - var self = jQuery(this), - contents = self.contents(); + // `amount` ... the proportion of the conversion. A value of 1 is completely grayscale. A value of 0 leaves the input unchanged. + grayscale: function(args) { - if (contents.length) { - contents.wrapAll(html); + var amount = _.isFinite(args.amount) ? args.amount : 1; - } else { - self.append(html); - } - }); - }, + return joint.util.template('')({ + a: 0.2126 + 0.7874 * (1 - amount), + b: 0.7152 - 0.7152 * (1 - amount), + c: 0.0722 - 0.0722 * (1 - amount), + d: 0.2126 - 0.2126 * (1 - amount), + e: 0.7152 + 0.2848 * (1 - amount), + f: 0.0722 - 0.0722 * (1 - amount), + g: 0.2126 - 0.2126 * (1 - amount), + h: 0.0722 + 0.9278 * (1 - amount) + }); + }, - wrap: function (html) { - var isFunction = jQuery.isFunction(html); + // `amount` ... the proportion of the conversion. A value of 1 is completely sepia. A value of 0 leaves the input unchanged. + sepia: function(args) { + + var amount = _.isFinite(args.amount) ? args.amount : 1; + + return joint.util.template('')({ + a: 0.393 + 0.607 * (1 - amount), + b: 0.769 - 0.769 * (1 - amount), + c: 0.189 - 0.189 * (1 - amount), + d: 0.349 - 0.349 * (1 - amount), + e: 0.686 + 0.314 * (1 - amount), + f: 0.168 - 0.168 * (1 - amount), + g: 0.272 - 0.272 * (1 - amount), + h: 0.534 - 0.534 * (1 - amount), + i: 0.131 + 0.869 * (1 - amount) + }); + }, - return this.each(function (i) { - jQuery(this).wrapAll(isFunction ? html.call(this, i) : html); - }); - }, + // `amount` ... the proportion of the conversion. A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. + saturate: function(args) { - unwrap: function () { - return this.parent().each(function () { - if (!jQuery.nodeName(this, "body")) { - jQuery(this).replaceWith(this.childNodes); - } - }).end(); - } - }); - var curCSS, iframe, - // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" - // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rmargin = /^margin/, - rnumsplit = new RegExp("^(" + core_pnum + ")(.*)$", "i"), - rnumnonpx = new RegExp("^(" + core_pnum + ")(?!px)[a-z%]+$", "i"), - rrelNum = new RegExp("^([+-])=(" + core_pnum + ")", "i"), - elemdisplay = { BODY: "block" }, - - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: 0, - fontWeight: 400 - }, - - cssExpand = [ "Top", "Right", "Bottom", "Left" ], - cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; - -// return a css property mapped to a potentially vendor prefixed property - function vendorPropName(style, name) { - - // shortcut for names that are not vendor prefixed - if (name in style) { - return name; - } + var amount = _.isFinite(args.amount) ? args.amount : 1; - // check for vendor prefixed names - var capName = name.charAt(0).toUpperCase() + name.slice(1), - origName = name, - i = cssPrefixes.length; + return joint.util.template('')({ + amount: 1 - amount + }); + }, - while (i--) { - name = cssPrefixes[ i ] + capName; - if (name in style) { - return name; - } - } + // `angle` ... the number of degrees around the color circle the input samples will be adjusted. + hueRotate: function(args) { - return origName; - } + return joint.util.template('')({ + angle: args.angle || 0 + }); + }, - function isHidden(elem, el) { - // isHidden might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - return jQuery.css(elem, "display") === "none" || !jQuery.contains(elem.ownerDocument, elem); - } + // `amount` ... the proportion of the conversion. A value of 1 is completely inverted. A value of 0 leaves the input unchanged. + invert: function(args) { -// NOTE: we've included the "window" in window.getComputedStyle -// because jsdom on node.js will break without it. - function getStyles(elem) { - return window.getComputedStyle(elem, null); - } + var amount = _.isFinite(args.amount) ? args.amount : 1; - function showHide(elements, show) { - var display, elem, hidden, - values = [], - index = 0, - length = elements.length; + return joint.util.template('')({ + amount: amount, + amount2: 1 - amount + }); + }, - for (; index < length; index++) { - elem = elements[ index ]; - if (!elem.style) { - continue; - } + // `amount` ... proportion of the conversion. A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + brightness: function(args) { - values[ index ] = data_priv.get(elem, "olddisplay"); - display = elem.style.display; - if (show) { - // Reset the inline display of this element to learn if it is - // being hidden by cascaded rules or not - if (!values[ index ] && display === "none") { - elem.style.display = ""; - } + return joint.util.template('')({ + amount: _.isFinite(args.amount) ? args.amount : 1 + }); + }, - // Set elements which have been overridden with display: none - // in a stylesheet to whatever the default browser style is - // for such an element - if (elem.style.display === "" && isHidden(elem)) { - values[ index ] = data_priv.access(elem, "olddisplay", css_defaultDisplay(elem.nodeName)); - } - } else { + // `amount` ... proportion of the conversion. A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + contrast: function(args) { - if (!values[ index ]) { - hidden = isHidden(elem); + var amount = _.isFinite(args.amount) ? args.amount : 1; - if (display && display !== "none" || !hidden) { - data_priv.set(elem, "olddisplay", hidden ? display : jQuery.css(elem, "display")); - } + return joint.util.template('')({ + amount: amount, + amount2: .5 - amount / 2 + }); } - } - } + }, - // Set the display of most of the elements in a second loop - // to avoid the constant reflow - for (index = 0; index < length; index++) { - elem = elements[ index ]; - if (!elem.style) { - continue; - } - if (!show || elem.style.display === "none" || elem.style.display === "") { - elem.style.display = show ? values[ index ] || "" : "none"; - } - } + format: { - return elements; - } + // Formatting numbers via the Python Format Specification Mini-language. + // See http://docs.python.org/release/3.1.3/library/string.html#format-specification-mini-language. + // Heavilly inspired by the D3.js library implementation. + number: function(specifier, value, locale) { - jQuery.fn.extend({ - css: function (name, value) { - return jQuery.access(this, function (elem, name, value) { - var styles, len, - map = {}, - i = 0; + locale = locale || { - if (jQuery.isArray(name)) { - styles = getStyles(elem); - len = name.length; + currency: ['$', ''], + decimal: '.', + thousands: ',', + grouping: [3] + }; - for (; i < len; i++) { - map[ name[ i ] ] = jQuery.css(elem, name[ i ], false, styles); + // See Python format specification mini-language: http://docs.python.org/release/3.1.3/library/string.html#format-specification-mini-language. + // [[fill]align][sign][symbol][0][width][,][.precision][type] + var re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i; + + var match = re.exec(specifier); + var fill = match[1] || ' '; + var align = match[2] || '>'; + var sign = match[3] || ''; + var symbol = match[4] || ''; + var zfill = match[5]; + var width = +match[6]; + var comma = match[7]; + var precision = match[8]; + var type = match[9]; + var scale = 1; + var prefix = ''; + var suffix = ''; + var integer = false; + + if (precision) precision = +precision.substring(1); + + if (zfill || fill === '0' && align === '=') { + zfill = fill = '0'; + align = '='; + if (comma) width -= Math.floor((width - 1) / 4); + } + + switch (type) { + case 'n': + comma = true; type = 'g'; + break; + case '%': + scale = 100; suffix = '%'; type = 'f'; + break; + case 'p': + scale = 100; suffix = '%'; type = 'r'; + break; + case 'b': + case 'o': + case 'x': + case 'X': + if (symbol === '#') prefix = '0' + type.toLowerCase(); + break; + case 'c': + case 'd': + integer = true; precision = 0; + break; + case 's': + scale = -1; type = 'r'; + break; } - return map; - } - - return value !== undefined ? - jQuery.style(elem, name, value) : - jQuery.css(elem, name); - }, name, value, arguments.length > 1); - }, - show: function () { - return showHide(this, true); - }, - hide: function () { - return showHide(this); - }, - toggle: function (state) { - if (typeof state === "boolean") { - return state ? this.show() : this.hide(); - } + if (symbol === '$') { + prefix = locale.currency[0]; + suffix = locale.currency[1]; + } - return this.each(function () { - if (isHidden(this)) { - jQuery(this).show(); - } else { - jQuery(this).hide(); - } - }); - } - }); + // If no precision is specified for `'r'`, fallback to general notation. + if (type == 'r' && !precision) type = 'g'; - jQuery.extend({ - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function (elem, computed) { - if (computed) { - // We should always get a number back from opacity - var ret = curCSS(elem, "opacity"); - return ret === "" ? "1" : ret; + // Ensure that the requested precision is in the supported range. + if (precision != null) { + if (type == 'g') precision = Math.max(1, Math.min(21, precision)); + else if (type == 'e' || type == 'f') precision = Math.max(0, Math.min(20, precision)); } - } - } - }, - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "columnCount": true, - "fillOpacity": true, - "fontWeight": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: { - // normalize float css property - "float": "cssFloat" - }, + var zcomma = zfill && comma; - // Get and set the style property on a DOM Node - style: function (elem, name, value, extra) { - // Don't set styles on text and comment nodes - if (!elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style) { - return; - } + // Return the empty string for floats formatted as ints. + if (integer && (value % 1)) return ''; - // Make sure that we're working with the right name - var ret, type, hooks, - origName = jQuery.camelCase(name), - style = elem.style; + // Convert negative to positive, and record the sign prefix. + var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, '-') : sign; - name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName(style, origName) ); + var fullSuffix = suffix; - // gets hook for the prefixed version - // followed by the unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + // Apply the scale, computing it from the value's exponent for si format. + // Preserve the existing suffix, if any, such as the currency symbol. + if (scale < 0) { + var unit = this.prefix(value, precision); + value = unit.scale(value); + fullSuffix = unit.symbol + suffix; + } else { + value *= scale; + } - // Check if we're setting a value - if (value !== undefined) { - type = typeof value; + // Convert to the desired precision. + value = this.convert(type, value, precision); - // convert relative number strings (+= or -=) to relative numbers. #7345 - if (type === "string" && (ret = rrelNum.exec(value))) { - value = ( ret[1] + 1 ) * ret[2] + parseFloat(jQuery.css(elem, name)); - // Fixes bug #9237 - type = "number"; - } + // Break the value into the integer part (before) and decimal part (after). + var i = value.lastIndexOf('.'); + var before = i < 0 ? value : value.substring(0, i); + var after = i < 0 ? '' : locale.decimal + value.substring(i + 1); - // Make sure that NaN and null values aren't set. See: #7116 - if (value == null || type === "number" && isNaN(value)) { - return; - } + function formatGroup(value) { - // If a number was passed in, add 'px' to the (except for certain CSS properties) - if (type === "number" && !jQuery.cssNumber[ origName ]) { - value += "px"; - } + var i = value.length; + var t = []; + var j = 0; + var g = locale.grouping[0]; + while (i > 0 && g > 0) { + t.push(value.substring(i -= g, i + g)); + g = locale.grouping[j = (j + 1) % locale.grouping.length]; + } + return t.reverse().join(locale.thousands); + } - // Fixes #8908, it can be done more correctly by specifying setters in cssHooks, - // but it would mean to define eight (for every problematic property) identical functions - if (!jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0) { - style[ name ] = "inherit"; - } + // If the fill character is not `'0'`, grouping is applied before padding. + if (!zfill && comma && locale.grouping) { - // If a hook was provided, use that value, otherwise just set the specified value - if (!hooks || !("set" in hooks) || (value = hooks.set(elem, value, extra)) !== undefined) { - style[ name ] = value; - } + before = formatGroup(before); + } - } else { - // If a hook was provided get the non-computed value from there - if (hooks && "get" in hooks && (ret = hooks.get(elem, false, extra)) !== undefined) { - return ret; - } + var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length); + var padding = length < width ? new Array(length = width - length + 1).join(fill) : ''; - // Otherwise just get the value from the style object - return style[ name ]; - } - }, + // If the fill character is `'0'`, grouping is applied after padding. + if (zcomma) before = formatGroup(padding + before); - css: function (elem, name, extra, styles) { - var val, num, hooks, - origName = jQuery.camelCase(name); + // Apply prefix. + negative += prefix; - // Make sure that we're working with the right name - name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName(elem.style, origName) ); + // Rejoin integer and decimal parts. + value = before + after; - // gets hook for the prefixed version - // followed by the unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + return (align === '<' ? negative + value + padding + : align === '>' ? padding + negative + value + : align === '^' ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) + : negative + (zcomma ? value : padding + value)) + fullSuffix; + }, - // If a hook was provided get the computed value from there - if (hooks && "get" in hooks) { - val = hooks.get(elem, true, extra); - } + // Formatting string via the Python Format string. + // See https://docs.python.org/2/library/string.html#format-string-syntax) + string: function(formatString, value) { - // Otherwise, if a way to get the computed value exists, use that - if (val === undefined) { - val = curCSS(elem, name, styles); - } + var fieldDelimiterIndex; + var fieldDelimiter = '{'; + var endPlaceholder = false; + var formattedStringArray = []; - //convert "normal" to computed value - if (val === "normal" && name in cssNormalTransform) { - val = cssNormalTransform[ name ]; - } + while ((fieldDelimiterIndex = formatString.indexOf(fieldDelimiter)) !== -1) { - // Return, converting to number if forced or a qualifier was provided and val looks numeric - if (extra === "" || extra) { - num = parseFloat(val); - return extra === true || jQuery.isNumeric(num) ? num || 0 : val; - } - return val; - } - }); + var pieceFormatedString, formatSpec, fieldName; - curCSS = function (elem, name, _computed) { - var width, minWidth, maxWidth, - computed = _computed || getStyles(elem), + pieceFormatedString = formatString.slice(0, fieldDelimiterIndex); - // Support: IE9 - // getPropertyValue is only needed for .css('filter') in IE9, see #12537 - ret = computed ? computed.getPropertyValue(name) || computed[ name ] : undefined, - style = elem.style; + if (endPlaceholder) { + formatSpec = pieceFormatedString.split(':'); + fieldName = formatSpec.shift().split('.'); + pieceFormatedString = value; - if (computed) { + for (var i = 0; i < fieldName.length; i++) + pieceFormatedString = pieceFormatedString[fieldName[i]]; - if (ret === "" && !jQuery.contains(elem.ownerDocument, elem)) { - ret = jQuery.style(elem, name); - } + if (formatSpec.length) + pieceFormatedString = this.number(formatSpec, pieceFormatedString); + } - // Support: Safari 5.1 - // A tribute to the "awesome hack by Dean Edwards" - // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels - // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values - if (rnumnonpx.test(ret) && rmargin.test(name)) { + formattedStringArray.push(pieceFormatedString); - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; + formatString = formatString.slice(fieldDelimiterIndex + 1); + fieldDelimiter = (endPlaceholder = !endPlaceholder) ? '}' : '{'; + } + formattedStringArray.push(formatString); - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; + return formattedStringArray.join(''); + }, - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } + convert: function(type, value, precision) { - return ret; - }; + switch (type) { + case 'b': return value.toString(2); + case 'c': return String.fromCharCode(value); + case 'o': return value.toString(8); + case 'x': return value.toString(16); + case 'X': return value.toString(16).toUpperCase(); + case 'g': return value.toPrecision(precision); + case 'e': return value.toExponential(precision); + case 'f': return value.toFixed(precision); + case 'r': return (value = this.round(value, this.precision(value, precision))).toFixed(Math.max(0, Math.min(20, this.precision(value * (1 + 1e-15), precision)))); + default: return value + ''; + } + }, + round: function(value, precision) { - function setPositiveNumber(elem, value, subtract) { - var matches = rnumsplit.exec(value); - return matches ? - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max(0, matches[ 1 ] - ( subtract || 0 )) + ( matches[ 2 ] || "px" ) : - value; - } + return precision + ? Math.round(value * (precision = Math.pow(10, precision))) / precision + : Math.round(value); + }, - function augmentWidthOrHeight(elem, name, extra, isBorderBox, styles) { - var i = extra === ( isBorderBox ? "border" : "content" ) ? - // If we already have the right measurement, avoid augmentation - 4 : - // Otherwise initialize for horizontal or vertical properties - name === "width" ? 1 : 0, + precision: function(value, precision) { - val = 0; + return precision - (value ? Math.ceil(Math.log(value) / Math.LN10) : 1); + }, - for (; i < 4; i += 2) { - // both box models exclude margin, so add it if we want it - if (extra === "margin") { - val += jQuery.css(elem, extra + cssExpand[ i ], true, styles); - } + prefix: function(value, precision) { - if (isBorderBox) { - // border-box includes padding, so remove it if we want content - if (extra === "content") { - val -= jQuery.css(elem, "padding" + cssExpand[ i ], true, styles); - } + var prefixes = _.map(['y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'], function(d, i) { + var k = Math.pow(10, Math.abs(8 - i) * 3); + return { + scale: i > 8 ? function(d) { return d / k; } : function(d) { return d * k; }, + symbol: d + }; + }); - // at this point, extra isn't border nor margin, so remove border - if (extra !== "margin") { - val -= jQuery.css(elem, "border" + cssExpand[ i ] + "Width", true, styles); + var i = 0; + if (value) { + if (value < 0) value *= -1; + if (precision) value = this.round(value, this.precision(value, precision)); + i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); + i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3)); + } + return prefixes[8 + i / 3]; } - } else { - // at this point, extra isn't content, so add padding - val += jQuery.css(elem, "padding" + cssExpand[ i ], true, styles); + }, - // at this point, extra isn't content nor padding, so add border - if (extra !== "padding") { - val += jQuery.css(elem, "border" + cssExpand[ i ] + "Width", true, styles); - } - } - } + /* + Pre-compile the HTML to be used as a template. + */ + template: function(html) { - return val; - } + /* + Must support the variation in templating syntax found here: + https://lodash.com/docs#template + */ + var regex = /<%= ([^ ]+) %>|\$\{ ?([^\{\} ]+) ?\}|\{\{([^\{\} ]+)\}\}/g; - function getWidthOrHeight(elem, name, extra) { + return function(data) { - // Start with offset property, which is equivalent to the border-box value - var valueIsBorderBox = true, - val = name === "width" ? elem.offsetWidth : elem.offsetHeight, - styles = getStyles(elem), - isBorderBox = jQuery.support.boxSizing && jQuery.css(elem, "boxSizing", false, styles) === "border-box"; + data = data || {}; - // some non-html elements return undefined for offsetWidth, so check for null/undefined - // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 - // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 - if (val <= 0 || val == null) { - // Fall back to computed then uncomputed css if necessary - val = curCSS(elem, name, styles); - if (val < 0 || val == null) { - val = elem.style[ name ]; - } + return html.replace(regex, function(match) { - // Computed unit is not pixels. Stop here and return. - if (rnumnonpx.test(val)) { - return val; - } + var args = Array.prototype.slice.call(arguments); + var attr = _.find(args.slice(1, 4), function(_attr) { + return !!_attr; + }); - // we need the check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); + var attrArray = attr.split('.'); + var value = data[attrArray.shift()]; - // Normalize "", auto, and prepare for extra - val = parseFloat(val) || 0; - } + while (!_.isUndefined(value) && attrArray.length) { + value = value[attrArray.shift()]; + } - // use the active box-sizing model to add/subtract irrelevant styles - return ( val + - augmentWidthOrHeight( - elem, - name, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles - ) - ) + "px"; - } + return !_.isUndefined(value) ? value : ''; + }); + }; + }, -// Try to determine the default display value of an element - function css_defaultDisplay(nodeName) { - var doc = document, - display = elemdisplay[ nodeName ]; + /** + * @param {Element=} el Element, which content is intent to display in full-screen mode, 'document.body' is default. + */ + toggleFullScreen: function(el) { - if (!display) { - display = actualDisplay(nodeName, doc); + el = el || document.body; - // If the simple way fails, read from inside an iframe - if (display === "none" || !display) { - // Use the already-created iframe if possible - iframe = ( iframe || - jQuery("