vendor/assets/javascripts/snapsvg.js in snapsvg-rails-0.2.0 vs vendor/assets/javascripts/snapsvg.js in snapsvg-rails-0.4.1

- old
+ new

@@ -1,22 +1,23 @@ -// Snap.svg 0.2.0 -// -// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. -// +// Snap.svg 0.4.1 +// +// Copyright (c) 2013 – 2015 Adobe Systems Incorporated. All rights reserved. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// -// build: 2013-12-23 +// +// build: 2015-04-13 + // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -36,18 +37,34 @@ (function (glob) { var version = "0.4.2", has = "hasOwnProperty", separator = /[\.\/]/, + comaseparator = /\s*,\s*/, wildcard = "*", fun = function () {}, numsort = function (a, b) { return a - b; }, current_event, stop, events = {n: {}}, + firstDefined = function () { + for (var i = 0, ii = this.length; i < ii; i++) { + if (typeof this[i] != "undefined") { + return this[i]; + } + } + }, + lastDefined = function () { + var i = this.length; + while (--i) { + if (typeof this[i] != "undefined") { + return this[i]; + } + } + }, /*\ * eve [ method ] * Fires event with given `name`, given scope and other parameters. @@ -56,14 +73,14 @@ - name (string) name of the *event*, dot (`.`) or slash (`/`) separated - scope (object) context for the event handlers - varargs (...) the rest of arguments will be sent to event handlers - = (object) array of returned values from the listeners + = (object) array of returned values from the listeners. Array has two methods `.firstDefined()` and `.lastDefined()` to get first or last not `undefined` value. \*/ eve = function (name, scope) { - name = String(name); + name = String(name); var e = events, oldstop = stop, args = Array.prototype.slice.call(arguments, 2), listeners = eve.listeners(name), z = 0, @@ -72,10 +89,12 @@ indexed = [], queue = {}, out = [], ce = current_event, errors = []; + out.firstDefined = firstDefined; + out.lastDefined = lastDefined; current_event = name; stop = 0; for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) { indexed.push(listeners[i].zIndex); if (listeners[i].zIndex < 0) { @@ -117,14 +136,14 @@ } } } stop = oldstop; current_event = ce; - return out.length ? out : null; + return out; }; - // Undocumented. Debug only. - eve._events = events; + // Undocumented. Debug only. + eve._events = events; /*\ * eve.listeners [ method ] * Internal method which gives you array of all event handlers that will be triggered by the given `name`. @@ -184,31 +203,38 @@ = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment. > Example: | eve.on("mouse", eatIt)(2); | eve.on("mouse", scream); | eve.on("mouse", catchIt)(1); - * This will ensure that `catchIt()` function will be called before `eatIt()`. - * + * This will ensure that `catchIt` function will be called before `eatIt`. + * * If you want to put your handler before non-indexed handlers, specify a negative value. * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”. \*/ eve.on = function (name, f) { - name = String(name); - if (typeof f != "function") { - return function () {}; - } - var names = name.split(separator), - e = events; + name = String(name); + if (typeof f != "function") { + return function () {}; + } + var names = name.split(comaseparator); for (var i = 0, ii = names.length; i < ii; i++) { - e = e.n; - e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}}); + (function (name) { + var names = name.split(separator), + e = events, + exist; + for (var i = 0, ii = names.length; i < ii; i++) { + e = e.n; + e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}}); + } + e.f = e.f || []; + for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) { + exist = true; + break; + } + !exist && e.f.push(f); + }(names[i])); } - e.f = e.f || []; - for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) { - return fun; - } - e.f.push(f); return function (zIndex) { if (+zIndex == +zIndex) { f.zIndex = +zIndex; } }; @@ -216,27 +242,27 @@ /*\ * eve.f [ method ] ** * Returns function that will fire given event with optional arguments. - * Arguments that will be passed to the result function will be also - * concated to the list of final arguments. - | el.onclick = eve.f("click", 1, 2); - | eve.on("click", function (a, b, c) { - | console.log(a, b, c); // 1, 2, [event object] - | }); + * Arguments that will be passed to the result function will be also + * concated to the list of final arguments. + | el.onclick = eve.f("click", 1, 2); + | eve.on("click", function (a, b, c) { + | console.log(a, b, c); // 1, 2, [event object] + | }); > Arguments - - event (string) event name - - varargs (…) and any other arguments - = (function) possible event handler function + - event (string) event name + - varargs (…) and any other arguments + = (function) possible event handler function \*/ - eve.f = function (event) { - var attrs = [].slice.call(arguments, 1); - return function () { - eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0))); - }; - }; + eve.f = function (event) { + var attrs = [].slice.call(arguments, 1); + return function () { + eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0))); + }; + }; /*\ * eve.stop [ method ] ** * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing. @@ -279,11 +305,11 @@ /*\ * eve.off [ method ] ** * Removes given function from the list of event listeners assigned to given name. - * If no arguments specified all the events will be cleared. + * If no arguments specified all the events will be cleared. ** > Arguments ** - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards - f (function) event handler function @@ -293,16 +319,23 @@ [ method ] ** * See @eve.off \*/ eve.off = eve.unbind = function (name, f) { - if (!name) { - eve._events = events = {n: {}}; - return; - } - var names = name.split(separator), - e, + if (!name) { + eve._events = events = {n: {}}; + return; + } + var names = name.split(comaseparator); + if (names.length > 1) { + for (var i = 0, ii = names.length; i < ii; i++) { + eve.off(names[i], f); + } + return; + } + names = name.split(separator); + var e, key, splice, i, ii, j, jj, cur = [events]; for (i = 0, ii = names.length; i < ii; i++) { @@ -382,26 +415,31 @@ \*/ eve.version = version; eve.toString = function () { return "You are running Eve " + version; }; - (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define != "undefined" ? (define("eve", [], function() { return eve; })) : (glob.eve = eve)); + (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define === "function" && define.amd ? (define("eve", [], function() { return eve; })) : (glob.eve = eve)); })(this); (function (glob, factory) { // AMD support - if (typeof define === "function" && define.amd) { + if (typeof define == "function" && define.amd) { // Define as an anonymous module - define(["eve"], function( eve ) { + define(["eve"], function (eve) { return factory(glob, eve); }); + } else if (typeof exports != 'undefined') { + // Next for Node.js or CommonJS + var eve = require('eve'); + module.exports = factory(glob, eve); } else { // Browser globals (glob is window) // Snap adds itself to window factory(glob, glob.eve); } -}(this, function (window, eve) { +}(window || this, function (window, eve) { + // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -474,18 +512,20 @@ a.dur = val; }, stopit = function () { var a = this; delete animations[a.id]; + a.update(); eve("mina.stop." + a.id, a); }, pause = function () { var a = this; if (a.pdif) { return; } delete animations[a.id]; + a.update(); a.pdif = a.get() - a.b; }, resume = function () { var a = this; if (!a.pdif) { @@ -493,10 +533,24 @@ } a.b = a.get() - a.pdif; delete a.pdif; animations[a.id] = a; }, + update = function () { + var a = this, + res; + if (isArray(a.start)) { + res = []; + for (var j = 0, jj = a.start.length; j < jj; j++) { + res[j] = +a.start[j] + + (a.end[j] - a.start[j]) * a.easing(a.s); + } + } else { + res = +a.start + (a.end - a.start) * a.easing(a.s); + } + a.set(res); + }, frame = function () { var len = 0; for (var i in animations) if (animations.hasOwnProperty(i)) { var a = animations[i], b = a.get(), @@ -511,24 +565,14 @@ setTimeout(function () { eve("mina.finish." + a.id, a); }); }(a)); } - if (isArray(a.start)) { - res = []; - for (var j = 0, jj = a.start.length; j < jj; j++) { - res[j] = +a.start[j] + - (a.end[j] - a.start[j]) * a.easing(a.s); - } - } else { - res = +a.start + (a.end - a.start) * a.easing(a.s); - } - a.set(res); + a.update(); } len && requestAnimFrame(frame); }, - // SIERRA Unfamiliar with the word _slave_ in this context. Also, I don't know what _gereal_ means. Do you mean _general_? /*\ * mina [ method ] ** * Generic animation of numbers @@ -554,10 +598,13 @@ o easing (function) easing function, default is @mina.linear, o status (function) status getter/setter, o speed (function) speed getter/setter, o duration (function) duration getter/setter, o stop (function) animation stopper + o pause (function) pauses the animation + o resume (function) resumes the animation + o update (function) calles setter with the right value of the animation o } \*/ mina = function (a, A, b, B, get, set, easing) { var anim = { id: ID(), @@ -573,11 +620,12 @@ status: sta, speed: speed, duration: duration, stop: stopit, pause: pause, - resume: resume + resume: resume, + update: update }; animations[anim.id] = anim; var len = 0, i; for (i in animations) if (animations.hasOwnProperty(i)) { len++; @@ -744,11 +792,11 @@ return l; }; window.mina = mina; return mina; })(typeof eve == "undefined" ? function () {} : eve); -// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. +// Copyright (c) 2013 - 2015 Adobe Systems Incorporated. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // @@ -758,12 +806,12 @@ // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -var Snap = (function() { -Snap.version = "0.2.0"; +var Snap = (function(root) { +Snap.version = "0.4.0"; /*\ * Snap [ method ] ** * Creates a drawing surface or wraps existing SVG element. @@ -771,23 +819,28 @@ - width (number|string) width of surface - height (number|string) height of surface * or - DOM (SVGElement) element to be wrapped into Snap structure * or + - array (array) array of elements (will return set of elements) + * or - query (string) CSS query selector = (object) @Element \*/ function Snap(w, h) { if (w) { - if (w.tagName) { + if (w.nodeType) { return wrap(w); } + if (is(w, "array") && Snap.set) { + return Snap.set.apply(Snap, w); + } if (w instanceof Element) { return w; } if (h == null) { - w = glob.doc.querySelector(w); + w = glob.doc.querySelector(String(w)); return wrap(w); } } w = w == null ? "100%" : w; h = h == null ? "100%" : h; @@ -796,12 +849,12 @@ Snap.toString = function () { return "Snap v" + this.version; }; Snap._ = {}; var glob = { - win: window, - doc: window.document + win: root.window, + doc: root.window.document }; Snap._.glob = glob; var has = "hasOwnProperty", Str = String, toFloat = parseFloat, @@ -818,61 +871,75 @@ objectToString = Object.prototype.toString, ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i, colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?%?)\s*\))\s*$/i, bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/, reURLValue = /^url\(#?([^)]+)\)$/, - spaces = "\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029", - separator = new RegExp("[," + spaces + "]+"), - whitespace = new RegExp("[" + spaces + "]", "g"), - commaSpaces = new RegExp("[" + spaces + "]*,[" + spaces + "]*"), + separator = Snap._.separator = /[,\s]+/, + whitespace = /[\s]/g, + commaSpaces = /[\s]*,[\s]*/, hsrg = {hs: 1, rg: 1}, - pathCommand = new RegExp("([a-z])[" + spaces + ",]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?[" + spaces + "]*,?[" + spaces + "]*)+)", "ig"), - tCommand = new RegExp("([rstm])[" + spaces + ",]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?[" + spaces + "]*,?[" + spaces + "]*)+)", "ig"), - pathValues = new RegExp("(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[" + spaces + "]*,?[" + spaces + "]*", "ig"), + pathCommand = /([a-z])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig, + tCommand = /([rstm])[\s,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\s]*,?[\s]*)+)/ig, + pathValues = /(-?\d*\.?\d*(?:e[\-+]?\\d+)?)[\s]*,?[\s]*/ig, idgen = 0, idprefix = "S" + (+new Date).toString(36), - ID = function () { - return idprefix + (idgen++).toString(36); + ID = function (el) { + return (el && el.type ? el.type : E) + idprefix + (idgen++).toString(36); }, xlink = "http://www.w3.org/1999/xlink", xmlns = "http://www.w3.org/2000/svg", hub = {}, URL = Snap.url = function (url) { return "url('#" + url + "')"; }; function $(el, attr) { if (attr) { + if (el == "#text") { + el = glob.doc.createTextNode(attr.text || attr["#text"] || ""); + } + if (el == "#comment") { + el = glob.doc.createComment(attr.text || attr["#text"] || ""); + } if (typeof el == "string") { el = $(el); } if (typeof attr == "string") { - if (attr.substring(0, 6) == "xlink:") { - return el.getAttributeNS(xlink, attr.substring(6)); + if (el.nodeType == 1) { + if (attr.substring(0, 6) == "xlink:") { + return el.getAttributeNS(xlink, attr.substring(6)); + } + if (attr.substring(0, 4) == "xml:") { + return el.getAttributeNS(xmlns, attr.substring(4)); + } + return el.getAttribute(attr); + } else if (attr == "text") { + return el.nodeValue; + } else { + return null; } - if (attr.substring(0, 4) == "xml:") { - return el.getAttributeNS(xmlns, attr.substring(4)); - } - return el.getAttribute(attr); } - for (var key in attr) if (attr[has](key)) { - var val = Str(attr[key]); - if (val) { - if (key.substring(0, 6) == "xlink:") { - el.setAttributeNS(xlink, key.substring(6), val); - } else if (key.substring(0, 4) == "xml:") { - el.setAttributeNS(xmlns, key.substring(4), val); + if (el.nodeType == 1) { + for (var key in attr) if (attr[has](key)) { + var val = Str(attr[key]); + if (val) { + if (key.substring(0, 6) == "xlink:") { + el.setAttributeNS(xlink, key.substring(6), val); + } else if (key.substring(0, 4) == "xml:") { + el.setAttributeNS(xmlns, key.substring(4), val); + } else { + el.setAttribute(key, val); + } } else { - el.setAttribute(key, val); + el.removeAttribute(key); } - } else { - el.removeAttribute(key); } + } else if ("text" in attr) { + el.nodeValue = attr.text; } } else { el = glob.doc.createElementNS(xmlns, el); - // el.style && (el.style.webkitTapHighlightColor = "rgba(0,0,0,0)"); } return el; } Snap._.$ = $; Snap._.id = ID; @@ -947,28 +1014,10 @@ return Str(str).replace(tokenRegex, function (all, key) { return replacer(all, key, obj); }); }; })(); -var preload = (function () { - function onerror() { - this.parentNode.removeChild(this); - } - return function (src, f) { - var img = glob.doc.createElement("img"), - body = glob.doc.body; - img.style.cssText = "position:absolute;left:-9999em;top:-9999em"; - img.onload = function () { - f.call(img); - img.onload = img.onerror = null; - body.removeChild(img); - }; - img.onerror = onerror; - body.appendChild(img); - img.src = src; - }; -}()); function clone(obj) { if (typeof obj == "function" || Object(obj) !== obj) { return obj; } var res = new obj.constructor; @@ -1042,12 +1091,88 @@ * Transform angle to degrees - rad (number) angle in radians = (number) angle in degrees \*/ Snap.deg = deg; -// SIERRA for which point is the angle calculated? /*\ + * Snap.sin + [ method ] + ** + * Equivalent to `Math.sin()` only works with degrees, not radians. + - angle (number) angle in degrees + = (number) sin +\*/ +Snap.sin = function (angle) { + return math.sin(Snap.rad(angle)); +}; +/*\ + * Snap.tan + [ method ] + ** + * Equivalent to `Math.tan()` only works with degrees, not radians. + - angle (number) angle in degrees + = (number) tan +\*/ +Snap.tan = function (angle) { + return math.tan(Snap.rad(angle)); +}; +/*\ + * Snap.cos + [ method ] + ** + * Equivalent to `Math.cos()` only works with degrees, not radians. + - angle (number) angle in degrees + = (number) cos +\*/ +Snap.cos = function (angle) { + return math.cos(Snap.rad(angle)); +}; +/*\ + * Snap.asin + [ method ] + ** + * Equivalent to `Math.asin()` only works with degrees, not radians. + - num (number) value + = (number) asin in degrees +\*/ +Snap.asin = function (num) { + return Snap.deg(math.asin(num)); +}; +/*\ + * Snap.acos + [ method ] + ** + * Equivalent to `Math.acos()` only works with degrees, not radians. + - num (number) value + = (number) acos in degrees +\*/ +Snap.acos = function (num) { + return Snap.deg(math.acos(num)); +}; +/*\ + * Snap.atan + [ method ] + ** + * Equivalent to `Math.atan()` only works with degrees, not radians. + - num (number) value + = (number) atan in degrees +\*/ +Snap.atan = function (num) { + return Snap.deg(math.atan(num)); +}; +/*\ + * Snap.atan2 + [ method ] + ** + * Equivalent to `Math.atan2()` only works with degrees, not radians. + - num (number) value + = (number) atan2 in degrees +\*/ +Snap.atan2 = function (num) { + return Snap.deg(math.atan2(num)); +}; +/*\ * Snap.angle [ method ] ** * Returns an angle between two or three points > Parameters @@ -1059,10 +1184,104 @@ - y3 (number) #optional y coord of third point = (number) angle in degrees \*/ Snap.angle = angle; /*\ + * Snap.len + [ method ] + ** + * Returns distance between two points + > Parameters + - x1 (number) x coord of first point + - y1 (number) y coord of first point + - x2 (number) x coord of second point + - y2 (number) y coord of second point + = (number) distance +\*/ +Snap.len = function (x1, y1, x2, y2) { + return Math.sqrt(Snap.len2(x1, y1, x2, y2)); +}; +/*\ + * Snap.len2 + [ method ] + ** + * Returns squared distance between two points + > Parameters + - x1 (number) x coord of first point + - y1 (number) y coord of first point + - x2 (number) x coord of second point + - y2 (number) y coord of second point + = (number) distance +\*/ +Snap.len2 = function (x1, y1, x2, y2) { + return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); +}; +/*\ + * Snap.closestPoint + [ method ] + ** + * Returns closest point to a given one on a given path. + > Parameters + - path (Element) path element + - x (number) x coord of a point + - y (number) y coord of a point + = (object) in format + { + x (number) x coord of the point on the path + y (number) y coord of the point on the path + length (number) length of the path to the point + distance (number) distance from the given point to the path + } +\*/ +// Copied from http://bl.ocks.org/mbostock/8027637 +Snap.closestPoint = function (path, x, y) { + function distance2(p) { + var dx = p.x - x, + dy = p.y - y; + return dx * dx + dy * dy; + } + var pathNode = path.node, + pathLength = pathNode.getTotalLength(), + precision = pathLength / pathNode.pathSegList.numberOfItems * .125, + best, + bestLength, + bestDistance = Infinity; + + // linear scan for coarse approximation + for (var scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision) { + if ((scanDistance = distance2(scan = pathNode.getPointAtLength(scanLength))) < bestDistance) { + best = scan, bestLength = scanLength, bestDistance = scanDistance; + } + } + + // binary search for precise estimate + precision *= .5; + while (precision > .5) { + var before, + after, + beforeLength, + afterLength, + beforeDistance, + afterDistance; + if ((beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2(before = pathNode.getPointAtLength(beforeLength))) < bestDistance) { + best = before, bestLength = beforeLength, bestDistance = beforeDistance; + } else if ((afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2(after = pathNode.getPointAtLength(afterLength))) < bestDistance) { + best = after, bestLength = afterLength, bestDistance = afterDistance; + } else { + precision *= .5; + } + } + + best = { + x: best.x, + y: best.y, + length: bestLength, + distance: Math.sqrt(bestDistance) + }; + return best; +} +/*\ * Snap.is [ method ] ** * Handy replacement for the `typeof` operator - o (…) any object or primitive @@ -1097,278 +1316,10 @@ return value - rem + values; } } return value; }; - -// MATRIX -function Matrix(a, b, c, d, e, f) { - if (b == null && objectToString.call(a) == "[object SVGMatrix]") { - this.a = a.a; - this.b = a.b; - this.c = a.c; - this.d = a.d; - this.e = a.e; - this.f = a.f; - return; - } - if (a != null) { - this.a = +a; - this.b = +b; - this.c = +c; - this.d = +d; - this.e = +e; - this.f = +f; - } else { - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.e = 0; - this.f = 0; - } -} -(function (matrixproto) { - /*\ - * Matrix.add - [ method ] - ** - * Adds the given matrix to existing one - - a (number) - - b (number) - - c (number) - - d (number) - - e (number) - - f (number) - * or - - matrix (object) @Matrix - \*/ - matrixproto.add = function (a, b, c, d, e, f) { - var out = [[], [], []], - m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]], - matrix = [[a, c, e], [b, d, f], [0, 0, 1]], - x, y, z, res; - - if (a && a instanceof Matrix) { - matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]]; - } - - for (x = 0; x < 3; x++) { - for (y = 0; y < 3; y++) { - res = 0; - for (z = 0; z < 3; z++) { - res += m[x][z] * matrix[z][y]; - } - out[x][y] = res; - } - } - this.a = out[0][0]; - this.b = out[1][0]; - this.c = out[0][1]; - this.d = out[1][1]; - this.e = out[0][2]; - this.f = out[1][2]; - return this; - }; - /*\ - * Matrix.invert - [ method ] - ** - * Returns an inverted version of the matrix - = (object) @Matrix - \*/ - matrixproto.invert = function () { - var me = this, - x = me.a * me.d - me.b * me.c; - return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x); - }; - /*\ - * Matrix.clone - [ method ] - ** - * Returns a copy of the matrix - = (object) @Matrix - \*/ - matrixproto.clone = function () { - return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f); - }; - /*\ - * Matrix.translate - [ method ] - ** - * Translate the matrix - - x (number) horizontal offset distance - - y (number) vertical offset distance - \*/ - matrixproto.translate = function (x, y) { - return this.add(1, 0, 0, 1, x, y); - }; - /*\ - * Matrix.scale - [ method ] - ** - * Scales the matrix - - x (number) amount to be scaled, with `1` resulting in no change - - y (number) #optional amount to scale along the vertical axis. (Otherwise `x` applies to both axes.) - - cx (number) #optional horizontal origin point from which to scale - - cy (number) #optional vertical origin point from which to scale - * Default cx, cy is the middle point of the element. - \*/ - matrixproto.scale = function (x, y, cx, cy) { - y == null && (y = x); - (cx || cy) && this.add(1, 0, 0, 1, cx, cy); - this.add(x, 0, 0, y, 0, 0); - (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy); - return this; - }; - /*\ - * Matrix.rotate - [ method ] - ** - * Rotates the matrix - - a (number) angle of rotation, in degrees - - x (number) horizontal origin point from which to rotate - - y (number) vertical origin point from which to rotate - \*/ - matrixproto.rotate = function (a, x, y) { - a = rad(a); - x = x || 0; - y = y || 0; - var cos = +math.cos(a).toFixed(9), - sin = +math.sin(a).toFixed(9); - this.add(cos, sin, -sin, cos, x, y); - return this.add(1, 0, 0, 1, -x, -y); - }; - /*\ - * Matrix.x - [ method ] - ** - * Returns x coordinate for given point after transformation described by the matrix. See also @Matrix.y - - x (number) - - y (number) - = (number) x - \*/ - matrixproto.x = function (x, y) { - return x * this.a + y * this.c + this.e; - }; - /*\ - * Matrix.y - [ method ] - ** - * Returns y coordinate for given point after transformation described by the matrix. See also @Matrix.x - - x (number) - - y (number) - = (number) y - \*/ - matrixproto.y = function (x, y) { - return x * this.b + y * this.d + this.f; - }; - matrixproto.get = function (i) { - return +this[Str.fromCharCode(97 + i)].toFixed(4); - }; - matrixproto.toString = function () { - return "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")"; - }; - matrixproto.offset = function () { - return [this.e.toFixed(4), this.f.toFixed(4)]; - }; - function norm(a) { - return a[0] * a[0] + a[1] * a[1]; - } - function normalize(a) { - var mag = math.sqrt(norm(a)); - a[0] && (a[0] /= mag); - a[1] && (a[1] /= mag); - } - /*\ - * Matrix.split - [ method ] - ** - * Splits matrix into primitive transformations - = (object) in format: - o dx (number) translation by x - o dy (number) translation by y - o scalex (number) scale by x - o scaley (number) scale by y - o shear (number) shear - o rotate (number) rotation in deg - o isSimple (boolean) could it be represented via simple transformations - \*/ - matrixproto.split = function () { - var out = {}; - // translation - out.dx = this.e; - out.dy = this.f; - - // scale and shear - var row = [[this.a, this.c], [this.b, this.d]]; - out.scalex = math.sqrt(norm(row[0])); - normalize(row[0]); - - out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1]; - row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear]; - - out.scaley = math.sqrt(norm(row[1])); - normalize(row[1]); - out.shear /= out.scaley; - - // rotation - var sin = -row[0][1], - cos = row[1][1]; - if (cos < 0) { - out.rotate = deg(math.acos(cos)); - if (sin < 0) { - out.rotate = 360 - out.rotate; - } - } else { - out.rotate = deg(math.asin(sin)); - } - - out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate); - out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate; - out.noRotation = !+out.shear.toFixed(9) && !out.rotate; - return out; - }; - /*\ - * Matrix.toTransformString - [ method ] - ** - * Returns transform string that represents given matrix - = (string) transform string - \*/ - matrixproto.toTransformString = function (shorter) { - var s = shorter || this.split(); - if (s.isSimple) { - s.scalex = +s.scalex.toFixed(4); - s.scaley = +s.scaley.toFixed(4); - s.rotate = +s.rotate.toFixed(4); - return (s.dx || s.dy ? "t" + [+s.dx.toFixed(4), +s.dy.toFixed(4)] : E) + - (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) + - (s.rotate ? "r" + [+s.rotate.toFixed(4), 0, 0] : E); - } else { - return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)]; - } - }; -})(Matrix.prototype); -/*\ - * Snap.Matrix - [ method ] - ** - * Utility method - ** - * Returns a matrix based on the given parameters - - a (number) - - b (number) - - c (number) - - d (number) - - e (number) - - f (number) - * or - - svgMatrix (SVGMatrix) - = (object) @Matrix -\*/ -Snap.Matrix = Matrix; // Colour /*\ * Snap.getRGB [ method ] ** @@ -1477,11 +1428,10 @@ rgb.opacity = is(opacity, "finite") ? opacity : 1; return rgb; } return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString}; }, Snap); -// SIERRA It seems odd that the following 3 conversion methods are not expressed as .this2that(), like the others. /*\ * Snap.hsb [ method ] ** * Converts HSB values to a hex representation of the color @@ -1522,11 +1472,11 @@ return "rgba(" + [round(r), round(g), round(b), +o.toFixed(2)] + ")"; } return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1); }); var toHex = function (color) { - var i = glob.doc.getElementsByTagName("head")[0], + var i = glob.doc.getElementsByTagName("head")[0] || glob.doc.getElementsByTagName("svg")[0], red = "rgb(255, 0, 0)"; toHex = cacher(function (color) { if (color.toLowerCase() == "red") { return red; } @@ -1581,11 +1531,10 @@ toString: rgbtoString }; is(o, "finite") && (rgb.opacity = o); return rgb; }; -// SIERRA Clarify if Snap does not support consolidated HSLA/RGBA colors. E.g., can you specify a semi-transparent value for Snap.filter.shadow()? /*\ * Snap.color [ method ] ** * Parses the color string and returns an object featuring the color's component values @@ -1657,12 +1606,12 @@ \*/ Snap.hsb2rgb = function (h, s, v, o) { if (is(h, "object") && "h" in h && "s" in h && "b" in h) { v = h.b; s = h.s; - h = h.h; o = h.o; + h = h.h; } h *= 360; var R, G, B, X, C; h = (h % 360) / 60; C = v * s; @@ -1784,11 +1733,10 @@ C / (2 - 2 * L)); return {h: H, s: S, l: L, toString: hsltoString}; }; // Transformations -// SIERRA Snap.parsePathString(): By _array of arrays,_ I assume you mean a format like this for two separate segments? [ ["M10,10","L90,90"], ["M90,10","L10,90"] ] Otherwise how is each command structured? /*\ * Snap.parsePathString [ method ] ** * Utility method @@ -1878,11 +1826,13 @@ params = params.split(/\s*,\s*|\s+/); if (name == "rotate" && params.length == 1) { params.push(0, 0); } if (name == "scale") { - if (params.length == 2) { + if (params.length > 2) { + params = params.slice(0, 2); + } else if (params.length == 2) { params.push(0, 0); } if (params.length == 1) { params.push(params[0], 0, 0); } @@ -1897,14 +1847,14 @@ return all; }); return res; } Snap._.svgTransform2string = svgTransform2string; -Snap._.rgTransform = new RegExp("^[a-z][" + spaces + "]*-?\\.?\\d", "i"); +Snap._.rgTransform = /^[a-z][\s]*-?\.?\d/i; function transform2matrix(tstr, bbox) { var tdata = parseTransformString(tstr), - m = new Matrix; + m = new Snap.Matrix; if (tdata) { for (var i = 0, ii = tdata.length; i < ii; i++) { var t = tdata[i], tlen = t.length, command = Str(t[0]).toLowerCase(), @@ -1967,42 +1917,10 @@ } } return m; } Snap._.transform2matrix = transform2matrix; -function extractTransform(el, tstr) { - if (tstr == null) { - var doReturn = true; - if (el.type == "linearGradient" || el.type == "radialGradient") { - tstr = el.node.getAttribute("gradientTransform"); - } else if (el.type == "pattern") { - tstr = el.node.getAttribute("patternTransform"); - } else { - tstr = el.node.getAttribute("transform"); - } - if (!tstr) { - return new Matrix; - } - tstr = svgTransform2string(tstr); - } else { - if (!Snap._.rgTransform.test(tstr)) { - tstr = svgTransform2string(tstr); - } else { - tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || E); - } - if (is(tstr, "array")) { - tstr = Snap.path ? Snap.path.toString.call(tstr) : Str(tstr); - } - el._.transform = tstr; - } - var m = transform2matrix(tstr, el.getBBox(1)); - if (doReturn) { - return m; - } else { - el.matrix = m; - } -} Snap._unit2px = unit2px; var contains = glob.doc.contains || glob.doc.compareDocumentPosition ? function (a, b) { var adown = a.nodeType == 9 ? a.documentElement : a, bup = b && b.parentNode; @@ -2022,61 +1940,68 @@ } } return false; }; function getSomeDefs(el) { - var cache = Snap._.someDefs; - if (cache && contains(cache.ownerDocument.documentElement, cache)) { - return cache; - } var p = (el.node.ownerSVGElement && wrap(el.node.ownerSVGElement)) || (el.node.parentNode && wrap(el.node.parentNode)) || Snap.select("svg") || Snap(0, 0), pdefs = p.select("defs"), defs = pdefs == null ? false : pdefs.node; if (!defs) { defs = make("defs", p.node).node; } - Snap._.someDefs = defs; return defs; } +function getSomeSVG(el) { + return el.node.ownerSVGElement && wrap(el.node.ownerSVGElement) || Snap.select("svg"); +} Snap._.getSomeDefs = getSomeDefs; +Snap._.getSomeSVG = getSomeSVG; function unit2px(el, name, value) { - var defs = getSomeDefs(el), + var svg = getSomeSVG(el).node, out = {}, - mgr = defs.querySelector(".svg---mgr"); + mgr = svg.querySelector(".svg---mgr"); if (!mgr) { mgr = $("rect"); - $(mgr, {width: 10, height: 10, "class": "svg---mgr"}); - defs.appendChild(mgr); + $(mgr, {x: -9e9, y: -9e9, width: 10, height: 10, "class": "svg---mgr", fill: "none"}); + svg.appendChild(mgr); } function getW(val) { if (val == null) { return E; } if (val == +val) { return val; } $(mgr, {width: val}); - return mgr.getBBox().width; + try { + return mgr.getBBox().width; + } catch (e) { + return 0; + } } function getH(val) { if (val == null) { return E; } if (val == +val) { return val; } $(mgr, {height: val}); - return mgr.getBBox().height; + try { + return mgr.getBBox().height; + } catch (e) { + return 0; + } } function set(nam, f) { if (name == null) { - out[nam] = f(el.attr(nam)); + out[nam] = f(el.attr(nam) || 0); } else if (nam == name) { - out = f(value == null ? el.attr(nam) : value); + out = f(value == null ? el.attr(nam) || 0 : value); } } switch (el.type) { case "rect": set("rx", getW); @@ -2120,10 +2045,11 @@ set("dy", getH); break; default: set(name, getW); } + svg.removeChild(mgr); return out; } /*\ * Snap.select [ method ] @@ -2131,10 +2057,11 @@ * Wraps a DOM element specified by CSS selector as @Element - query (string) CSS selector of the element = (Element) the current element \*/ Snap.select = function (query) { + query = Str(query).replace(/([^\\]):/g, "$1\\:"); return wrap(glob.doc.querySelector(query)); }; /*\ * Snap.selectAll [ method ] @@ -2173,52 +2100,72 @@ for (i = 0; i < children.length; i++) { this[j++] = wrap(children[i]); } return this; } +// Hub garbage collector every 10s +setInterval(function () { + for (var key in hub) if (hub[has](key)) { + var el = hub[key], + node = el.node; + if (el.type != "svg" && !node.ownerSVGElement || el.type == "svg" && (!node.parentNode || "ownerSVGElement" in node.parentNode && !node.ownerSVGElement)) { + delete hub[key]; + } + } +}, 1e4); function Element(el) { if (el.snap in hub) { return hub[el.snap]; } - var id = this.id = ID(), - svg; + var svg; try { svg = el.ownerSVGElement; } catch(e) {} + /*\ + * Element.node + [ property (object) ] + ** + * Gives you a reference to the DOM object, so you can assign event handlers or just mess around. + > Usage + | // draw a circle at coordinate 10,10 with radius of 10 + | var c = paper.circle(10, 10, 10); + | c.node.onclick = function () { + | c.attr("fill", "red"); + | }; + \*/ this.node = el; if (svg) { this.paper = new Paper(svg); } - this.type = el.tagName; + /*\ + * Element.type + [ property (string) ] + ** + * SVG tag name of the given element. + \*/ + this.type = el.tagName || el.nodeName; + var id = this.id = ID(this); this.anims = {}; this._ = { transform: [] }; el.snap = id; hub[id] = this; if (this.type == "g") { this.add = add2group; + } + if (this.type in {g: 1, mask: 1, pattern: 1, symbol: 1}) { for (var method in Paper.prototype) if (Paper.prototype[has](method)) { this[method] = Paper.prototype[method]; } } } -function arrayFirstValue(arr) { - var res; - for (var i = 0, ii = arr.length; i < ii; i++) { - res = res || arr[i]; - if (res) { - return res; - } - } -} -(function (elproto) { - /*\ + /*\ * Element.attr [ method ] ** - * Gets or sets given attributes of the element + * Gets or sets given attributes of the element. ** - params (object) contains key-value pairs of attributes you want to set * or - param (string) name of the attribute = (Element) the current element @@ -2227,38 +2174,501 @@ > Usage | el.attr({ | fill: "#fc0", | stroke: "#000", | strokeWidth: 2, // CamelCase... - | "fill-opacity": 0.5 // or dash-separated names + | "fill-opacity": 0.5, // or dash-separated names + | width: "*=2" // prefixed values | }); | console.log(el.attr("fill")); // #fc0 + * Prefixed values in format `"+=10"` supported. All four operations + * (`+`, `-`, `*` and `/`) could be used. Optionally you can use units for `+` + * and `-`: `"+=2em"`. \*/ - elproto.attr = function (params, value) { + Element.prototype.attr = function (params, value) { var el = this, node = el.node; if (!params) { - return el; + if (node.nodeType != 1) { + return { + text: node.nodeValue + }; + } + var attr = node.attributes, + out = {}; + for (var i = 0, ii = attr.length; i < ii; i++) { + out[attr[i].nodeName] = attr[i].nodeValue; + } + return out; } if (is(params, "string")) { if (arguments.length > 1) { var json = {}; json[params] = value; params = json; } else { - return arrayFirstValue(eve("snap.util.getattr."+params, el)); + return eve("snap.util.getattr." + params, el).firstDefined(); } } for (var att in params) { if (params[has](att)) { eve("snap.util.attr." + att, el, params[att]); } } return el; }; -// SIERRA Element.getBBox(): Unclear why you would want to express the dimension of the box as a path. -// SIERRA Element.getBBox(): Unclear why you would want to use r0/r1/r2. Also, basic definitions: wouldn't the _smallest circle that can be enclosed_ be a zero-radius point? +/*\ + * Snap.parse + [ method ] + ** + * Parses SVG fragment and converts it into a @Fragment + ** + - svg (string) SVG string + = (Fragment) the @Fragment +\*/ +Snap.parse = function (svg) { + var f = glob.doc.createDocumentFragment(), + full = true, + div = glob.doc.createElement("div"); + svg = Str(svg); + if (!svg.match(/^\s*<\s*svg(?:\s|>)/)) { + svg = "<svg>" + svg + "</svg>"; + full = false; + } + div.innerHTML = svg; + svg = div.getElementsByTagName("svg")[0]; + if (svg) { + if (full) { + f = svg; + } else { + while (svg.firstChild) { + f.appendChild(svg.firstChild); + } + } + } + return new Fragment(f); +}; +function Fragment(frag) { + this.node = frag; +} +/*\ + * Snap.fragment + [ method ] + ** + * Creates a DOM fragment from a given list of elements or strings + ** + - varargs (…) SVG string + = (Fragment) the @Fragment +\*/ +Snap.fragment = function () { + var args = Array.prototype.slice.call(arguments, 0), + f = glob.doc.createDocumentFragment(); + for (var i = 0, ii = args.length; i < ii; i++) { + var item = args[i]; + if (item.node && item.node.nodeType) { + f.appendChild(item.node); + } + if (item.nodeType) { + f.appendChild(item); + } + if (typeof item == "string") { + f.appendChild(Snap.parse(item).node); + } + } + return new Fragment(f); +}; + +function make(name, parent) { + var res = $(name); + parent.appendChild(res); + var el = wrap(res); + return el; +} +function Paper(w, h) { + var res, + desc, + defs, + proto = Paper.prototype; + if (w && w.tagName == "svg") { + if (w.snap in hub) { + return hub[w.snap]; + } + var doc = w.ownerDocument; + res = new Element(w); + desc = w.getElementsByTagName("desc")[0]; + defs = w.getElementsByTagName("defs")[0]; + if (!desc) { + desc = $("desc"); + desc.appendChild(doc.createTextNode("Created with Snap")); + res.node.appendChild(desc); + } + if (!defs) { + defs = $("defs"); + res.node.appendChild(defs); + } + res.defs = defs; + for (var key in proto) if (proto[has](key)) { + res[key] = proto[key]; + } + res.paper = res.root = res; + } else { + res = make("svg", glob.doc.body); + $(res.node, { + height: h, + version: 1.1, + width: w, + xmlns: xmlns + }); + } + return res; +} +function wrap(dom) { + if (!dom) { + return dom; + } + if (dom instanceof Element || dom instanceof Fragment) { + return dom; + } + if (dom.tagName && dom.tagName.toLowerCase() == "svg") { + return new Paper(dom); + } + if (dom.tagName && dom.tagName.toLowerCase() == "object" && dom.type == "image/svg+xml") { + return new Paper(dom.contentDocument.getElementsByTagName("svg")[0]); + } + return new Element(dom); +} + +Snap._.make = make; +Snap._.wrap = wrap; +/*\ + * Paper.el + [ method ] + ** + * Creates an element on paper with a given name and no attributes + ** + - name (string) tag name + - attr (object) attributes + = (Element) the current element + > Usage + | var c = paper.circle(10, 10, 10); // is the same as... + | var c = paper.el("circle").attr({ + | cx: 10, + | cy: 10, + | r: 10 + | }); + | // and the same as + | var c = paper.el("circle", { + | cx: 10, + | cy: 10, + | r: 10 + | }); +\*/ +Paper.prototype.el = function (name, attr) { + var el = make(name, this.node); + attr && el.attr(attr); + return el; +}; +/*\ + * Element.children + [ method ] + ** + * Returns array of all the children of the element. + = (array) array of Elements +\*/ +Element.prototype.children = function () { + var out = [], + ch = this.node.childNodes; + for (var i = 0, ii = ch.length; i < ii; i++) { + out[i] = Snap(ch[i]); + } + return out; +}; +function jsonFiller(root, o) { + for (var i = 0, ii = root.length; i < ii; i++) { + var item = { + type: root[i].type, + attr: root[i].attr() + }, + children = root[i].children(); + o.push(item); + if (children.length) { + jsonFiller(children, item.childNodes = []); + } + } +} +/*\ + * Element.toJSON + [ method ] + ** + * Returns object representation of the given element and all its children. + = (object) in format + o { + o type (string) this.type, + o attr (object) attributes map, + o childNodes (array) optional array of children in the same format + o } +\*/ +Element.prototype.toJSON = function () { + var out = []; + jsonFiller([this], out); + return out[0]; +}; +// default +eve.on("snap.util.getattr", function () { + var att = eve.nt(); + att = att.substring(att.lastIndexOf(".") + 1); + var css = att.replace(/[A-Z]/g, function (letter) { + return "-" + letter.toLowerCase(); + }); + if (cssAttr[has](css)) { + return this.node.ownerDocument.defaultView.getComputedStyle(this.node, null).getPropertyValue(css); + } else { + return $(this.node, att); + } +}); +var cssAttr = { + "alignment-baseline": 0, + "baseline-shift": 0, + "clip": 0, + "clip-path": 0, + "clip-rule": 0, + "color": 0, + "color-interpolation": 0, + "color-interpolation-filters": 0, + "color-profile": 0, + "color-rendering": 0, + "cursor": 0, + "direction": 0, + "display": 0, + "dominant-baseline": 0, + "enable-background": 0, + "fill": 0, + "fill-opacity": 0, + "fill-rule": 0, + "filter": 0, + "flood-color": 0, + "flood-opacity": 0, + "font": 0, + "font-family": 0, + "font-size": 0, + "font-size-adjust": 0, + "font-stretch": 0, + "font-style": 0, + "font-variant": 0, + "font-weight": 0, + "glyph-orientation-horizontal": 0, + "glyph-orientation-vertical": 0, + "image-rendering": 0, + "kerning": 0, + "letter-spacing": 0, + "lighting-color": 0, + "marker": 0, + "marker-end": 0, + "marker-mid": 0, + "marker-start": 0, + "mask": 0, + "opacity": 0, + "overflow": 0, + "pointer-events": 0, + "shape-rendering": 0, + "stop-color": 0, + "stop-opacity": 0, + "stroke": 0, + "stroke-dasharray": 0, + "stroke-dashoffset": 0, + "stroke-linecap": 0, + "stroke-linejoin": 0, + "stroke-miterlimit": 0, + "stroke-opacity": 0, + "stroke-width": 0, + "text-anchor": 0, + "text-decoration": 0, + "text-rendering": 0, + "unicode-bidi": 0, + "visibility": 0, + "word-spacing": 0, + "writing-mode": 0 +}; + +eve.on("snap.util.attr", function (value) { + var att = eve.nt(), + attr = {}; + att = att.substring(att.lastIndexOf(".") + 1); + attr[att] = value; + var style = att.replace(/-(\w)/gi, function (all, letter) { + return letter.toUpperCase(); + }), + css = att.replace(/[A-Z]/g, function (letter) { + return "-" + letter.toLowerCase(); + }); + if (cssAttr[has](css)) { + this.node.style[style] = value == null ? E : value; + } else { + $(this.node, attr); + } +}); +(function (proto) {}(Paper.prototype)); + +// simple ajax +/*\ + * Snap.ajax + [ method ] + ** + * Simple implementation of Ajax + ** + - url (string) URL + - postData (object|string) data for post request + - callback (function) callback + - scope (object) #optional scope of callback + * or + - url (string) URL + - callback (function) callback + - scope (object) #optional scope of callback + = (XMLHttpRequest) the XMLHttpRequest object, just in case +\*/ +Snap.ajax = function (url, postData, callback, scope){ + var req = new XMLHttpRequest, + id = ID(); + if (req) { + if (is(postData, "function")) { + scope = callback; + callback = postData; + postData = null; + } else if (is(postData, "object")) { + var pd = []; + for (var key in postData) if (postData.hasOwnProperty(key)) { + pd.push(encodeURIComponent(key) + "=" + encodeURIComponent(postData[key])); + } + postData = pd.join("&"); + } + req.open((postData ? "POST" : "GET"), url, true); + if (postData) { + req.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + } + if (callback) { + eve.once("snap.ajax." + id + ".0", callback); + eve.once("snap.ajax." + id + ".200", callback); + eve.once("snap.ajax." + id + ".304", callback); + } + req.onreadystatechange = function() { + if (req.readyState != 4) return; + eve("snap.ajax." + id + "." + req.status, scope, req); + }; + if (req.readyState == 4) { + return req; + } + req.send(postData); + return req; + } +}; +/*\ + * Snap.load + [ method ] + ** + * Loads external SVG file as a @Fragment (see @Snap.ajax for more advanced AJAX) + ** + - url (string) URL + - callback (function) callback + - scope (object) #optional scope of callback +\*/ +Snap.load = function (url, callback, scope) { + Snap.ajax(url, function (req) { + var f = Snap.parse(req.responseText); + scope ? callback.call(scope, f) : callback(f); + }); +}; +var getOffset = function (elem) { + var box = elem.getBoundingClientRect(), + doc = elem.ownerDocument, + body = doc.body, + docElem = doc.documentElement, + clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, + top = box.top + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop, + left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft; + return { + y: top, + x: left + }; +}; +/*\ + * Snap.getElementByPoint + [ method ] + ** + * Returns you topmost element under given point. + ** + = (object) Snap element object + - x (number) x coordinate from the top left corner of the window + - y (number) y coordinate from the top left corner of the window + > Usage + | Snap.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"}); +\*/ +Snap.getElementByPoint = function (x, y) { + var paper = this, + svg = paper.canvas, + target = glob.doc.elementFromPoint(x, y); + if (glob.win.opera && target.tagName == "svg") { + var so = getOffset(target), + sr = target.createSVGRect(); + sr.x = x - so.x; + sr.y = y - so.y; + sr.width = sr.height = 1; + var hits = target.getIntersectionList(sr, null); + if (hits.length) { + target = hits[hits.length - 1]; + } + } + if (!target) { + return null; + } + return wrap(target); +}; +/*\ + * Snap.plugin + [ method ] + ** + * Let you write plugins. You pass in a function with five arguments, like this: + | Snap.plugin(function (Snap, Element, Paper, global, Fragment) { + | Snap.newmethod = function () {}; + | Element.prototype.newmethod = function () {}; + | Paper.prototype.newmethod = function () {}; + | }); + * Inside the function you have access to all main objects (and their + * prototypes). This allow you to extend anything you want. + ** + - f (function) your plugin body +\*/ +Snap.plugin = function (f) { + f(Snap, Element, Paper, glob, Fragment); +}; +glob.win.Snap = Snap; +return Snap; +}(window || this)); + +// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var elproto = Element.prototype, + is = Snap.is, + Str = String, + unit2px = Snap._unit2px, + $ = Snap._.$, + make = Snap._.make, + getSomeDefs = Snap._.getSomeDefs, + has = "hasOwnProperty", + wrap = Snap._.wrap; /*\ * Element.getBBox [ method ] ** * Returns the bounding box descriptor for the given element @@ -2281,33 +2691,81 @@ o y2: (number) y of the bottom edge, o y: (number) y of the top edge o } \*/ elproto.getBBox = function (isWithoutTransform) { - var el = this; - if (el.type == "use") { - el = el.original; + if (!Snap.Matrix || !Snap.path) { + return this.node.getBBox(); } + var el = this, + m = new Snap.Matrix; if (el.removed) { - return {}; + return Snap._.box(); } - var _ = el._; - if (isWithoutTransform) { - _.bboxwt = Snap.path.get[el.type] ? Snap.path.getBBox(el.realPath = Snap.path.get[el.type](el)) : Snap._.box(el.node.getBBox()); - return Snap._.box(_.bboxwt); - } else { - el.realPath = (Snap.path.get[el.type] || Snap.path.get.deflt)(el); - _.bbox = Snap.path.getBBox(Snap.path.map(el.realPath, el.matrix)); + while (el.type == "use") { + if (!isWithoutTransform) { + m = m.add(el.transform().localMatrix.translate(el.attr("x") || 0, el.attr("y") || 0)); + } + if (el.original) { + el = el.original; + } else { + var href = el.attr("xlink:href"); + el = el.original = el.node.ownerDocument.getElementById(href.substring(href.indexOf("#") + 1)); + } } - return Snap._.box(_.bbox); + var _ = el._, + pathfinder = Snap.path.get[el.type] || Snap.path.get.deflt; + try { + if (isWithoutTransform) { + _.bboxwt = pathfinder ? Snap.path.getBBox(el.realPath = pathfinder(el)) : Snap._.box(el.node.getBBox()); + return Snap._.box(_.bboxwt); + } else { + el.realPath = pathfinder(el); + el.matrix = el.transform().localMatrix; + _.bbox = Snap.path.getBBox(Snap.path.map(el.realPath, m.add(el.matrix))); + return Snap._.box(_.bbox); + } + } catch (e) { + // Firefox doesn’t give you bbox of hidden element + return Snap._.box(); + } }; var propString = function () { return this.string; }; -// SIERRA Element.transform(): seems to allow two return values, one of which (_Element_) is undefined. -// SIERRA Element.transform(): if this only accepts one argument, it's unclear how it can both _get_ and _set_ a transform. -// SIERRA Element.transform(): Unclear how Snap transform string format differs from SVG's. + function extractTransform(el, tstr) { + if (tstr == null) { + var doReturn = true; + if (el.type == "linearGradient" || el.type == "radialGradient") { + tstr = el.node.getAttribute("gradientTransform"); + } else if (el.type == "pattern") { + tstr = el.node.getAttribute("patternTransform"); + } else { + tstr = el.node.getAttribute("transform"); + } + if (!tstr) { + return new Snap.Matrix; + } + tstr = Snap._.svgTransform2string(tstr); + } else { + if (!Snap._.rgTransform.test(tstr)) { + tstr = Snap._.svgTransform2string(tstr); + } else { + tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || ""); + } + if (is(tstr, "array")) { + tstr = Snap.path ? Snap.path.toString.call(tstr) : Str(tstr); + } + el._.transform = tstr; + } + var m = Snap._.transform2matrix(tstr, el.getBBox(1)); + if (doReturn) { + return m; + } else { + el.matrix = m; + } + } /*\ * Element.transform [ method ] ** * Gets or sets transformation of the element @@ -2327,31 +2785,44 @@ o } \*/ elproto.transform = function (tstr) { var _ = this._; if (tstr == null) { - var global = new Matrix(this.node.getCTM()), + var papa = this, + global = new Snap.Matrix(this.node.getCTM()), local = extractTransform(this), + ms = [local], + m = new Snap.Matrix, + i, localString = local.toTransformString(), string = Str(local) == Str(this.matrix) ? - _.transform : localString; + Str(_.transform) : localString; + while (papa.type != "svg" && (papa = papa.parent())) { + ms.push(extractTransform(papa)); + } + i = ms.length; + while (i--) { + m.add(ms[i]); + } return { string: string, globalMatrix: global, + totalMatrix: m, localMatrix: local, diffMatrix: global.clone().add(local.invert()), global: global.toTransformString(), + total: m.toTransformString(), local: localString, toString: propString }; } - if (tstr instanceof Matrix) { - // may be need to apply it directly - // TODO: investigate - tstr = tstr.toTransformString(); + if (tstr instanceof Snap.Matrix) { + this.matrix = tstr; + this._.transform = tstr.toTransformString(); + } else { + extractTransform(this, tstr); } - extractTransform(this, tstr); if (this.node) { if (this.type == "linearGradient" || this.type == "radialGradient") { $(this.node, {gradientTransform: this.matrix}); } else if (this.type == "pattern") { @@ -2429,10 +2900,23 @@ - el (Element) element to prepend = (Element) the parent element \*/ elproto.prepend = function (el) { if (el) { + if (el.type == "set") { + var it = this, + first; + el.forEach(function (el) { + if (first) { + first.after(el); + } else { + it.prepend(el); + } + first = el; + }); + return this; + } el = wrap(el); var parent = el.parent(); this.node.insertBefore(el.node, this.node.firstChild); this.add && this.add(); el.paper = this.paper; @@ -2629,18 +3113,10 @@ "xlink:href": "#" + id }); use.original = this; return use; }; - /*\ - * Element.clone - [ method ] - ** - * Creates a clone of the element and inserts it after the element - ** - = (Element) the clone - \*/ function fixids(el) { var els = el.selectAll("*"), it, url = /^\s*url\(("|'|)(.*)\1\)\s*$/, ids = [], @@ -2699,37 +3175,42 @@ fs[j](ids[i].id); } } } } + /*\ + * Element.clone + [ method ] + ** + * Creates a clone of the element and inserts it after the element + ** + = (Element) the clone + \*/ elproto.clone = function () { var clone = wrap(this.node.cloneNode(true)); if ($(clone.node, "id")) { $(clone.node, {id: clone.id}); } fixids(clone); clone.insertAfter(this); return clone; }; -// SIERRA Element.toDefs(): If this _moves_ an element to the <defs> region, why is the return value a _clone_? Also unclear why it's called the _relative_ <defs> section. Perhaps _shared_? /*\ * Element.toDefs [ method ] ** * Moves element to the shared `<defs>` area ** - = (Element) the clone + = (Element) the element \*/ elproto.toDefs = function () { var defs = getSomeDefs(this); defs.appendChild(this.node); return this; }; -// SIERRA Element.pattern(): x/y/width/height data types are listed as both String and Number. Is that an error, or does it mean strings are coerced? -// SIERRA Element.pattern(): clarify that x/y are offsets that e.g., may add gutters between the tiles. /*\ - * Element.pattern + * Element.toPattern [ method ] ** * Creates a `<pattern>` element from the current element ** * To create a pattern you have to specify the pattern rect: @@ -2747,11 +3228,11 @@ | c = paper.circle(200, 200, 100); | c.attr({ | fill: p | }); \*/ - elproto.pattern = function (x, y, width, height) { + elproto.pattern = elproto.toPattern = function (x, y, width, height) { var p = make("pattern", getSomeDefs(this)); if (x == null) { x = this.getBBox(); } if (is(x, "object") && "x" in x) { @@ -2803,11 +3284,11 @@ refX = x.refX || x.cx; refY = x.refY || x.cy; x = x.x; } $(p.node, { - viewBox: [x, y, width, height].join(S), + viewBox: [x, y, width, height].join(" "), markerWidth: width, markerHeight: height, orient: "auto", refX: refX || 0, refY: refY || 0, @@ -2834,11 +3315,11 @@ this.attr = attr; this.dur = ms; easing && (this.easing = easing); callback && (this.callback = callback); }; - // SIERRA All object methods should feature sample code. This is just one instance. + Snap._.Animation = Animation; /*\ * Snap.animation [ method ] ** * Creates an animation object @@ -2859,10 +3340,11 @@ * Returns a set of animations that may be able to manipulate the current element ** = (object) in format: o { o anim (object) animation object, + o mina (object) @mina object, o curStatus (number) 0..1 — status of the animation: 0 — just started, 1 — just finished, o status (function) gets or sets the status of the animation, o stop (function) stops the animation o } \*/ @@ -2871,10 +3353,11 @@ res = []; for (var id in el.anims) if (el.anims[has](id)) { (function (a) { res.push({ anim: new Animation(a._attrs, a.dur, a.easing, a._callback), + mina: a, curStatus: a.status(), status: function (val) { return a.status(val); }, stop: function () { @@ -2938,12 +3421,10 @@ for (var i = 0, ii = anims.length; i < ii; i++) { anims[i].stop(); } return this; }; - // SIERRA Element.animate(): For _attrs_, clarify if they represent the destination values, and if the animation executes relative to the element's current attribute values. - // SIERRA would a _custom_ animation function be an SVG keySplines value? /*\ * Element.animate [ method ] ** * Animates the given attributes of the element @@ -2960,11 +3441,11 @@ easing = mina.linear; } if (attrs instanceof Animation) { callback = attrs.callback; easing = attrs.easing; - ms = easing.dur; + ms = attrs.dur; attrs = attrs.attr; } var fkeys = [], tkeys = [], keys = {}, from, to, f, eq, el = this; for (var key in attrs) if (attrs[has](key)) { @@ -2991,10 +3472,11 @@ el.attr(attr); }, easing); el.anims[anim.id] = anim; anim._attrs = attrs; anim._callback = callback; + eve("snap.animcreated." + el.id, anim); eve.once("mina.finish." + anim.id, function () { delete el.anims[anim.id]; callback && callback.call(el); }); eve.once("mina.stop." + anim.id, function () { @@ -3112,282 +3594,1014 @@ type && (res += "/>"); } return res; }; } -}(Element.prototype)); -// SIERRA Snap.parse() accepts & returns a fragment, but there's no info on what it does in between. What if it doesn't parse? -/*\ - * Snap.parse - [ method ] - ** - * Parses SVG fragment and converts it into a @Fragment - ** - - svg (string) SVG string - = (Fragment) the @Fragment -\*/ -Snap.parse = function (svg) { - var f = glob.doc.createDocumentFragment(), - full = true, - div = glob.doc.createElement("div"); - svg = Str(svg); - if (!svg.match(/^\s*<\s*svg(?:\s|>)/)) { - svg = "<svg>" + svg + "</svg>"; - full = false; - } - div.innerHTML = svg; - svg = div.getElementsByTagName("svg")[0]; - if (svg) { - if (full) { - f = svg; - } else { - while (svg.firstChild) { - f.appendChild(svg.firstChild); - } + elproto.toDataURL = function () { + if (window && window.btoa) { + var bb = this.getBBox(), + svg = Snap.format('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{width}" height="{height}" viewBox="{x} {y} {width} {height}">{contents}</svg>', { + x: +bb.x.toFixed(3), + y: +bb.y.toFixed(3), + width: +bb.width.toFixed(3), + height: +bb.height.toFixed(3), + contents: this.outerSVG() + }); + return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svg))); } - } - div.innerHTML = E; - return new Fragment(f); -}; -function Fragment(frag) { - this.node = frag; -} -/*\ - * Fragment.select - [ method ] - ** - * See @Element.select -\*/ -Fragment.prototype.select = Element.prototype.select; -/*\ - * Fragment.selectAll - [ method ] - ** - * See @Element.selectAll -\*/ -Fragment.prototype.selectAll = Element.prototype.selectAll; -// SIERRA Snap.fragment() could especially use a code example -/*\ - * Snap.fragment - [ method ] - ** - * Creates a DOM fragment from a given list of elements or strings - ** - - varargs (…) SVG string - = (Fragment) the @Fragment -\*/ -Snap.fragment = function () { - var args = Array.prototype.slice.call(arguments, 0), - f = glob.doc.createDocumentFragment(); - for (var i = 0, ii = args.length; i < ii; i++) { - var item = args[i]; - if (item.node && item.node.nodeType) { - f.appendChild(item.node); + }; + /*\ + * Fragment.select + [ method ] + ** + * See @Element.select + \*/ + Fragment.prototype.select = elproto.select; + /*\ + * Fragment.selectAll + [ method ] + ** + * See @Element.selectAll + \*/ + Fragment.prototype.selectAll = elproto.selectAll; +}); + +// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var objectToString = Object.prototype.toString, + Str = String, + math = Math, + E = ""; + function Matrix(a, b, c, d, e, f) { + if (b == null && objectToString.call(a) == "[object SVGMatrix]") { + this.a = a.a; + this.b = a.b; + this.c = a.c; + this.d = a.d; + this.e = a.e; + this.f = a.f; + return; } - if (item.nodeType) { - f.appendChild(item); + if (a != null) { + this.a = +a; + this.b = +b; + this.c = +c; + this.d = +d; + this.e = +e; + this.f = +f; + } else { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.e = 0; + this.f = 0; } - if (typeof item == "string") { - f.appendChild(Snap.parse(item).node); - } } - return new Fragment(f); -}; + (function (matrixproto) { + /*\ + * Matrix.add + [ method ] + ** + * Adds the given matrix to existing one + - a (number) + - b (number) + - c (number) + - d (number) + - e (number) + - f (number) + * or + - matrix (object) @Matrix + \*/ + matrixproto.add = function (a, b, c, d, e, f) { + var out = [[], [], []], + m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]], + matrix = [[a, c, e], [b, d, f], [0, 0, 1]], + x, y, z, res; -function make(name, parent) { - var res = $(name); - parent.appendChild(res); - var el = wrap(res); - el.type = name; - return el; -} -function Paper(w, h) { - var res, - desc, - defs, - proto = Paper.prototype; - if (w && w.tagName == "svg") { - if (w.snap in hub) { - return hub[w.snap]; + if (a && a instanceof Matrix) { + matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]]; + } + + for (x = 0; x < 3; x++) { + for (y = 0; y < 3; y++) { + res = 0; + for (z = 0; z < 3; z++) { + res += m[x][z] * matrix[z][y]; + } + out[x][y] = res; + } + } + this.a = out[0][0]; + this.b = out[1][0]; + this.c = out[0][1]; + this.d = out[1][1]; + this.e = out[0][2]; + this.f = out[1][2]; + return this; + }; + /*\ + * Matrix.invert + [ method ] + ** + * Returns an inverted version of the matrix + = (object) @Matrix + \*/ + matrixproto.invert = function () { + var me = this, + x = me.a * me.d - me.b * me.c; + return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x); + }; + /*\ + * Matrix.clone + [ method ] + ** + * Returns a copy of the matrix + = (object) @Matrix + \*/ + matrixproto.clone = function () { + return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f); + }; + /*\ + * Matrix.translate + [ method ] + ** + * Translate the matrix + - x (number) horizontal offset distance + - y (number) vertical offset distance + \*/ + matrixproto.translate = function (x, y) { + return this.add(1, 0, 0, 1, x, y); + }; + /*\ + * Matrix.scale + [ method ] + ** + * Scales the matrix + - x (number) amount to be scaled, with `1` resulting in no change + - y (number) #optional amount to scale along the vertical axis. (Otherwise `x` applies to both axes.) + - cx (number) #optional horizontal origin point from which to scale + - cy (number) #optional vertical origin point from which to scale + * Default cx, cy is the middle point of the element. + \*/ + matrixproto.scale = function (x, y, cx, cy) { + y == null && (y = x); + (cx || cy) && this.add(1, 0, 0, 1, cx, cy); + this.add(x, 0, 0, y, 0, 0); + (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy); + return this; + }; + /*\ + * Matrix.rotate + [ method ] + ** + * Rotates the matrix + - a (number) angle of rotation, in degrees + - x (number) horizontal origin point from which to rotate + - y (number) vertical origin point from which to rotate + \*/ + matrixproto.rotate = function (a, x, y) { + a = Snap.rad(a); + x = x || 0; + y = y || 0; + var cos = +math.cos(a).toFixed(9), + sin = +math.sin(a).toFixed(9); + this.add(cos, sin, -sin, cos, x, y); + return this.add(1, 0, 0, 1, -x, -y); + }; + /*\ + * Matrix.x + [ method ] + ** + * Returns x coordinate for given point after transformation described by the matrix. See also @Matrix.y + - x (number) + - y (number) + = (number) x + \*/ + matrixproto.x = function (x, y) { + return x * this.a + y * this.c + this.e; + }; + /*\ + * Matrix.y + [ method ] + ** + * Returns y coordinate for given point after transformation described by the matrix. See also @Matrix.x + - x (number) + - y (number) + = (number) y + \*/ + matrixproto.y = function (x, y) { + return x * this.b + y * this.d + this.f; + }; + matrixproto.get = function (i) { + return +this[Str.fromCharCode(97 + i)].toFixed(4); + }; + matrixproto.toString = function () { + return "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")"; + }; + matrixproto.offset = function () { + return [this.e.toFixed(4), this.f.toFixed(4)]; + }; + function norm(a) { + return a[0] * a[0] + a[1] * a[1]; } - res = new Element(w); - desc = w.getElementsByTagName("desc")[0]; - defs = w.getElementsByTagName("defs")[0]; - if (!desc) { - desc = $("desc"); - desc.appendChild(glob.doc.createTextNode("Created with Snap")); - res.node.appendChild(desc); + function normalize(a) { + var mag = math.sqrt(norm(a)); + a[0] && (a[0] /= mag); + a[1] && (a[1] /= mag); } - if (!defs) { - defs = $("defs"); - res.node.appendChild(defs); + /*\ + * Matrix.determinant + [ method ] + ** + * Finds determinant of the given matrix. + = (number) determinant + \*/ + matrixproto.determinant = function () { + return this.a * this.d - this.b * this.c; + }; + /*\ + * Matrix.split + [ method ] + ** + * Splits matrix into primitive transformations + = (object) in format: + o dx (number) translation by x + o dy (number) translation by y + o scalex (number) scale by x + o scaley (number) scale by y + o shear (number) shear + o rotate (number) rotation in deg + o isSimple (boolean) could it be represented via simple transformations + \*/ + matrixproto.split = function () { + var out = {}; + // translation + out.dx = this.e; + out.dy = this.f; + + // scale and shear + var row = [[this.a, this.c], [this.b, this.d]]; + out.scalex = math.sqrt(norm(row[0])); + normalize(row[0]); + + out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1]; + row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear]; + + out.scaley = math.sqrt(norm(row[1])); + normalize(row[1]); + out.shear /= out.scaley; + + if (this.determinant() < 0) { + out.scalex = -out.scalex; + } + + // rotation + var sin = -row[0][1], + cos = row[1][1]; + if (cos < 0) { + out.rotate = Snap.deg(math.acos(cos)); + if (sin < 0) { + out.rotate = 360 - out.rotate; + } + } else { + out.rotate = Snap.deg(math.asin(sin)); + } + + out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate); + out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate; + out.noRotation = !+out.shear.toFixed(9) && !out.rotate; + return out; + }; + /*\ + * Matrix.toTransformString + [ method ] + ** + * Returns transform string that represents given matrix + = (string) transform string + \*/ + matrixproto.toTransformString = function (shorter) { + var s = shorter || this.split(); + if (!+s.shear.toFixed(9)) { + s.scalex = +s.scalex.toFixed(4); + s.scaley = +s.scaley.toFixed(4); + s.rotate = +s.rotate.toFixed(4); + return (s.dx || s.dy ? "t" + [+s.dx.toFixed(4), +s.dy.toFixed(4)] : E) + + (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) + + (s.rotate ? "r" + [+s.rotate.toFixed(4), 0, 0] : E); + } else { + return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)]; + } + }; + })(Matrix.prototype); + /*\ + * Snap.Matrix + [ method ] + ** + * Matrix constructor, extend on your own risk. + * To create matrices use @Snap.matrix. + \*/ + Snap.Matrix = Matrix; + /*\ + * Snap.matrix + [ method ] + ** + * Utility method + ** + * Returns a matrix based on the given parameters + - a (number) + - b (number) + - c (number) + - d (number) + - e (number) + - f (number) + * or + - svgMatrix (SVGMatrix) + = (object) @Matrix + \*/ + Snap.matrix = function (a, b, c, d, e, f) { + return new Matrix(a, b, c, d, e, f); + }; +}); +// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var has = "hasOwnProperty", + make = Snap._.make, + wrap = Snap._.wrap, + is = Snap.is, + getSomeDefs = Snap._.getSomeDefs, + reURLValue = /^url\(#?([^)]+)\)$/, + $ = Snap._.$, + URL = Snap.url, + Str = String, + separator = Snap._.separator, + E = ""; + // Attributes event handlers + eve.on("snap.util.attr.mask", function (value) { + if (value instanceof Element || value instanceof Fragment) { + eve.stop(); + if (value instanceof Fragment && value.node.childNodes.length == 1) { + value = value.node.firstChild; + getSomeDefs(this).appendChild(value); + value = wrap(value); + } + if (value.type == "mask") { + var mask = value; + } else { + mask = make("mask", getSomeDefs(this)); + mask.node.appendChild(value.node); + } + !mask.node.id && $(mask.node, { + id: mask.id + }); + $(this.node, { + mask: URL(mask.id) + }); } - res.defs = defs; - for (var key in proto) if (proto[has](key)) { - res[key] = proto[key]; + }); + (function (clipIt) { + eve.on("snap.util.attr.clip", clipIt); + eve.on("snap.util.attr.clip-path", clipIt); + eve.on("snap.util.attr.clipPath", clipIt); + }(function (value) { + if (value instanceof Element || value instanceof Fragment) { + eve.stop(); + if (value.type == "clipPath") { + var clip = value; + } else { + clip = make("clipPath", getSomeDefs(this)); + clip.node.appendChild(value.node); + !clip.node.id && $(clip.node, { + id: clip.id + }); + } + $(this.node, { + "clip-path": URL(clip.node.id || clip.id) + }); } - res.paper = res.root = res; - } else { - res = make("svg", glob.doc.body); - $(res.node, { - height: h, - version: 1.1, - width: w, - xmlns: xmlns - }); - } - return res; -} -function wrap(dom) { - if (!dom) { - return dom; - } - if (dom instanceof Element || dom instanceof Fragment) { - return dom; - } - if (dom.tagName == "svg") { - return new Paper(dom); - } - return new Element(dom); -} -// gradients' helpers -function Gstops() { - return this.selectAll("stop"); -} -function GaddStop(color, offset) { - var stop = $("stop"), - attr = { - offset: +offset + "%" + })); + function fillStroke(name) { + return function (value) { + eve.stop(); + if (value instanceof Fragment && value.node.childNodes.length == 1 && + (value.node.firstChild.tagName == "radialGradient" || + value.node.firstChild.tagName == "linearGradient" || + value.node.firstChild.tagName == "pattern")) { + value = value.node.firstChild; + getSomeDefs(this).appendChild(value); + value = wrap(value); + } + if (value instanceof Element) { + if (value.type == "radialGradient" || value.type == "linearGradient" + || value.type == "pattern") { + if (!value.node.id) { + $(value.node, { + id: value.id + }); + } + var fill = URL(value.node.id); + } else { + fill = value.attr(name); + } + } else { + fill = Snap.color(value); + if (fill.error) { + var grad = Snap(getSomeDefs(this).ownerSVGElement).gradient(value); + if (grad) { + if (!grad.node.id) { + $(grad.node, { + id: grad.id + }); + } + fill = URL(grad.node.id); + } else { + fill = value; + } + } else { + fill = Str(fill); + } + } + var attrs = {}; + attrs[name] = fill; + $(this.node, attrs); + this.node.style[name] = E; }; - color = Snap.color(color); - attr["stop-color"] = color.hex; - if (color.opacity < 1) { - attr["stop-opacity"] = color.opacity; } - $(stop, attr); - this.node.appendChild(stop); - return this; -} -function GgetBBox() { - if (this.type == "linearGradient") { - var x1 = $(this.node, "x1") || 0, - x2 = $(this.node, "x2") || 1, - y1 = $(this.node, "y1") || 0, - y2 = $(this.node, "y2") || 0; - return Snap._.box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1)); - } else { - var cx = this.node.cx || .5, - cy = this.node.cy || .5, - r = this.node.r || 0; - return Snap._.box(cx - r, cy - r, r * 2, r * 2); - } -} -function gradient(defs, str) { - var grad = arrayFirstValue(eve("snap.util.grad.parse", null, str)), - el; - if (!grad) { - return null; - } - grad.params.unshift(defs); - if (grad.type.toLowerCase() == "l") { - el = gradientLinear.apply(0, grad.params); - } else { - el = gradientRadial.apply(0, grad.params); - } - if (grad.type != grad.type.toLowerCase()) { - $(el.node, { - gradientUnits: "userSpaceOnUse" + eve.on("snap.util.attr.fill", fillStroke("fill")); + eve.on("snap.util.attr.stroke", fillStroke("stroke")); + var gradrg = /^([lr])(?:\(([^)]*)\))?(.*)$/i; + eve.on("snap.util.grad.parse", function parseGrad(string) { + string = Str(string); + var tokens = string.match(gradrg); + if (!tokens) { + return null; + } + var type = tokens[1], + params = tokens[2], + stops = tokens[3]; + params = params.split(/\s*,\s*/).map(function (el) { + return +el == el ? +el : el; }); - } - var stops = grad.stops, - len = stops.length, - start = 0, - j = 0; - function seed(i, end) { - var step = (end - start) / (i - j); - for (var k = j; k < i; k++) { - stops[k].offset = +(+start + step * (k - j)).toFixed(2); + if (params.length == 1 && params[0] == 0) { + params = []; } - j = i; - start = end; - } - len--; - for (var i = 0; i < len; i++) if ("offset" in stops[i]) { - seed(i, stops[i].offset); - } - stops[len].offset = stops[len].offset || 100; - seed(len, stops[len].offset); - for (i = 0; i <= len; i++) { - var stop = stops[i]; - el.addStop(stop.color, stop.offset); - } - return el; -} -function gradientLinear(defs, x1, y1, x2, y2) { - var el = make("linearGradient", defs); - el.stops = Gstops; - el.addStop = GaddStop; - el.getBBox = GgetBBox; - if (x1 != null) { - $(el.node, { - x1: x1, - y1: y1, - x2: x2, - y2: y2 + stops = stops.split("-"); + stops = stops.map(function (el) { + el = el.split(":"); + var out = { + color: el[0] + }; + if (el[1]) { + out.offset = parseFloat(el[1]); + } + return out; }); - } - return el; -} -function gradientRadial(defs, cx, cy, r, fx, fy) { - var el = make("radialGradient", defs); - el.stops = Gstops; - el.addStop = GaddStop; - el.getBBox = GgetBBox; - if (cx != null) { - $(el.node, { - cx: cx, - cy: cy, - r: r + return { + type: type, + params: params, + stops: stops + }; + }); + + eve.on("snap.util.attr.d", function (value) { + eve.stop(); + if (is(value, "array") && is(value[0], "array")) { + value = Snap.path.toString.call(value); + } + value = Str(value); + if (value.match(/[ruo]/i)) { + value = Snap.path.toAbsolute(value); + } + $(this.node, {d: value}); + })(-1); + eve.on("snap.util.attr.#text", function (value) { + eve.stop(); + value = Str(value); + var txt = glob.doc.createTextNode(value); + while (this.node.firstChild) { + this.node.removeChild(this.node.firstChild); + } + this.node.appendChild(txt); + })(-1); + eve.on("snap.util.attr.path", function (value) { + eve.stop(); + this.attr({d: value}); + })(-1); + eve.on("snap.util.attr.class", function (value) { + eve.stop(); + this.node.className.baseVal = value; + })(-1); + eve.on("snap.util.attr.viewBox", function (value) { + var vb; + if (is(value, "object") && "x" in value) { + vb = [value.x, value.y, value.width, value.height].join(" "); + } else if (is(value, "array")) { + vb = value.join(" "); + } else { + vb = value; + } + $(this.node, { + viewBox: vb }); + eve.stop(); + })(-1); + eve.on("snap.util.attr.transform", function (value) { + this.transform(value); + eve.stop(); + })(-1); + eve.on("snap.util.attr.r", function (value) { + if (this.type == "rect") { + eve.stop(); + $(this.node, { + rx: value, + ry: value + }); + } + })(-1); + eve.on("snap.util.attr.textpath", function (value) { + eve.stop(); + if (this.type == "text") { + var id, tp, node; + if (!value && this.textPath) { + tp = this.textPath; + while (tp.node.firstChild) { + this.node.appendChild(tp.node.firstChild); + } + tp.remove(); + delete this.textPath; + return; + } + if (is(value, "string")) { + var defs = getSomeDefs(this), + path = wrap(defs.parentNode).path(value); + defs.appendChild(path.node); + id = path.id; + path.attr({id: id}); + } else { + value = wrap(value); + if (value instanceof Element) { + id = value.attr("id"); + if (!id) { + id = value.id; + value.attr({id: id}); + } + } + } + if (id) { + tp = this.textPath; + node = this.node; + if (tp) { + tp.attr({"xlink:href": "#" + id}); + } else { + tp = $("textPath", { + "xlink:href": "#" + id + }); + while (node.firstChild) { + tp.appendChild(node.firstChild); + } + node.appendChild(tp); + this.textPath = wrap(tp); + } + } + } + })(-1); + eve.on("snap.util.attr.text", function (value) { + if (this.type == "text") { + var i = 0, + node = this.node, + tuner = function (chunk) { + var out = $("tspan"); + if (is(chunk, "array")) { + for (var i = 0; i < chunk.length; i++) { + out.appendChild(tuner(chunk[i])); + } + } else { + out.appendChild(glob.doc.createTextNode(chunk)); + } + out.normalize && out.normalize(); + return out; + }; + while (node.firstChild) { + node.removeChild(node.firstChild); + } + var tuned = tuner(value); + while (tuned.firstChild) { + node.appendChild(tuned.firstChild); + } + } + eve.stop(); + })(-1); + function setFontSize(value) { + eve.stop(); + if (value == +value) { + value += "px"; + } + this.node.style.fontSize = value; } - if (fx != null && fy != null) { - $(el.node, { - fx: fx, - fy: fy - }); + eve.on("snap.util.attr.fontSize", setFontSize)(-1); + eve.on("snap.util.attr.font-size", setFontSize)(-1); + + + eve.on("snap.util.getattr.transform", function () { + eve.stop(); + return this.transform(); + })(-1); + eve.on("snap.util.getattr.textpath", function () { + eve.stop(); + return this.textPath; + })(-1); + // Markers + (function () { + function getter(end) { + return function () { + eve.stop(); + var style = glob.doc.defaultView.getComputedStyle(this.node, null).getPropertyValue("marker-" + end); + if (style == "none") { + return style; + } else { + return Snap(glob.doc.getElementById(style.match(reURLValue)[1])); + } + }; + } + function setter(end) { + return function (value) { + eve.stop(); + var name = "marker" + end.charAt(0).toUpperCase() + end.substring(1); + if (value == "" || !value) { + this.node.style[name] = "none"; + return; + } + if (value.type == "marker") { + var id = value.node.id; + if (!id) { + $(value.node, {id: value.id}); + } + this.node.style[name] = URL(id); + return; + } + }; + } + eve.on("snap.util.getattr.marker-end", getter("end"))(-1); + eve.on("snap.util.getattr.markerEnd", getter("end"))(-1); + eve.on("snap.util.getattr.marker-start", getter("start"))(-1); + eve.on("snap.util.getattr.markerStart", getter("start"))(-1); + eve.on("snap.util.getattr.marker-mid", getter("mid"))(-1); + eve.on("snap.util.getattr.markerMid", getter("mid"))(-1); + eve.on("snap.util.attr.marker-end", setter("end"))(-1); + eve.on("snap.util.attr.markerEnd", setter("end"))(-1); + eve.on("snap.util.attr.marker-start", setter("start"))(-1); + eve.on("snap.util.attr.markerStart", setter("start"))(-1); + eve.on("snap.util.attr.marker-mid", setter("mid"))(-1); + eve.on("snap.util.attr.markerMid", setter("mid"))(-1); + }()); + eve.on("snap.util.getattr.r", function () { + if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) { + eve.stop(); + return $(this.node, "rx"); + } + })(-1); + function textExtract(node) { + var out = []; + var children = node.childNodes; + for (var i = 0, ii = children.length; i < ii; i++) { + var chi = children[i]; + if (chi.nodeType == 3) { + out.push(chi.nodeValue); + } + if (chi.tagName == "tspan") { + if (chi.childNodes.length == 1 && chi.firstChild.nodeType == 3) { + out.push(chi.firstChild.nodeValue); + } else { + out.push(textExtract(chi)); + } + } + } + return out; } - return el; -} -// Paper prototype methods -(function (proto) { + eve.on("snap.util.getattr.text", function () { + if (this.type == "text" || this.type == "tspan") { + eve.stop(); + var out = textExtract(this.node); + return out.length == 1 ? out[0] : out; + } + })(-1); + eve.on("snap.util.getattr.#text", function () { + return this.node.textContent; + })(-1); + eve.on("snap.util.getattr.viewBox", function () { + eve.stop(); + var vb = $(this.node, "viewBox"); + if (vb) { + vb = vb.split(separator); + return Snap._.box(+vb[0], +vb[1], +vb[2], +vb[3]); + } else { + return; + } + })(-1); + eve.on("snap.util.getattr.points", function () { + var p = $(this.node, "points"); + eve.stop(); + if (p) { + return p.split(separator); + } else { + return; + } + })(-1); + eve.on("snap.util.getattr.path", function () { + var p = $(this.node, "d"); + eve.stop(); + return p; + })(-1); + eve.on("snap.util.getattr.class", function () { + return this.node.className.baseVal; + })(-1); + function getFontSize() { + eve.stop(); + return this.node.style.fontSize; + } + eve.on("snap.util.getattr.fontSize", getFontSize)(-1); + eve.on("snap.util.getattr.font-size", getFontSize)(-1); +}); + +// Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var rgNotSpace = /\S+/g, + rgBadSpace = /[\t\r\n\f]/g, + rgTrim = /(^\s+|\s+$)/g, + Str = String, + elproto = Element.prototype; /*\ - * Paper.el + * Element.addClass [ method ] ** - * Creates an element on paper with a given name and no attributes + * Adds given class name or list of class names to the element. + - value (string) class name or space separated list of class names ** - - name (string) tag name - - attr (object) attributes - = (Element) the current element - > Usage - | var c = paper.circle(10, 10, 10); // is the same as... - | var c = paper.el("circle").attr({ - | cx: 10, - | cy: 10, - | r: 10 - | }); - | // and the same as - | var c = paper.el("circle", { - | cx: 10, - | cy: 10, - | r: 10 - | }); + = (Element) original element. \*/ - proto.el = function (name, attr) { - return make(name, this.node).attr(attr); + elproto.addClass = function (value) { + var classes = Str(value || "").match(rgNotSpace) || [], + elem = this.node, + className = elem.className.baseVal, + curClasses = className.match(rgNotSpace) || [], + j, + pos, + clazz, + finalValue; + + if (classes.length) { + j = 0; + while ((clazz = classes[j++])) { + pos = curClasses.indexOf(clazz); + if (!~pos) { + curClasses.push(clazz); + } + } + + finalValue = curClasses.join(" "); + if (className != finalValue) { + elem.className.baseVal = finalValue; + } + } + return this; }; /*\ + * Element.removeClass + [ method ] + ** + * Removes given class name or list of class names from the element. + - value (string) class name or space separated list of class names + ** + = (Element) original element. + \*/ + elproto.removeClass = function (value) { + var classes = Str(value || "").match(rgNotSpace) || [], + elem = this.node, + className = elem.className.baseVal, + curClasses = className.match(rgNotSpace) || [], + j, + pos, + clazz, + finalValue; + if (curClasses.length) { + j = 0; + while ((clazz = classes[j++])) { + pos = curClasses.indexOf(clazz); + if (~pos) { + curClasses.splice(pos, 1); + } + } + + finalValue = curClasses.join(" "); + if (className != finalValue) { + elem.className.baseVal = finalValue; + } + } + return this; + }; + /*\ + * Element.hasClass + [ method ] + ** + * Checks if the element has a given class name in the list of class names applied to it. + - value (string) class name + ** + = (boolean) `true` if the element has given class + \*/ + elproto.hasClass = function (value) { + var elem = this.node, + className = elem.className.baseVal, + curClasses = className.match(rgNotSpace) || []; + return !!~curClasses.indexOf(value); + }; + /*\ + * Element.toggleClass + [ method ] + ** + * Add or remove one or more classes from the element, depending on either + * the class’s presence or the value of the `flag` argument. + - value (string) class name or space separated list of class names + - flag (boolean) value to determine whether the class should be added or removed + ** + = (Element) original element. + \*/ + elproto.toggleClass = function (value, flag) { + if (flag != null) { + if (flag) { + return this.addClass(value); + } else { + return this.removeClass(value); + } + } + var classes = (value || "").match(rgNotSpace) || [], + elem = this.node, + className = elem.className.baseVal, + curClasses = className.match(rgNotSpace) || [], + j, + pos, + clazz, + finalValue; + j = 0; + while ((clazz = classes[j++])) { + pos = curClasses.indexOf(clazz); + if (~pos) { + curClasses.splice(pos, 1); + } else { + curClasses.push(clazz); + } + } + + finalValue = curClasses.join(" "); + if (className != finalValue) { + elem.className.baseVal = finalValue; + } + return this; + }; +}); + +// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var operators = { + "+": function (x, y) { + return x + y; + }, + "-": function (x, y) { + return x - y; + }, + "/": function (x, y) { + return x / y; + }, + "*": function (x, y) { + return x * y; + } + }, + Str = String, + reUnit = /[a-z]+$/i, + reAddon = /^\s*([+\-\/*])\s*=\s*([\d.eE+\-]+)\s*([^\d\s]+)?\s*$/; + function getNumber(val) { + return val; + } + function getUnit(unit) { + return function (val) { + return +val.toFixed(3) + unit; + }; + } + eve.on("snap.util.attr", function (val) { + var plus = Str(val).match(reAddon); + if (plus) { + var evnt = eve.nt(), + name = evnt.substring(evnt.lastIndexOf(".") + 1), + a = this.attr(name), + atr = {}; + eve.stop(); + var unit = plus[3] || "", + aUnit = a.match(reUnit), + op = operators[plus[1]]; + if (aUnit && aUnit == unit) { + val = op(parseFloat(a), +plus[2]); + } else { + a = this.asPX(name); + val = op(this.asPX(name), this.asPX(name, plus[2] + unit)); + } + if (isNaN(a) || isNaN(val)) { + return; + } + atr[name] = val; + this.attr(atr); + } + })(-10); + eve.on("snap.util.equal", function (name, b) { + var A, B, a = Str(this.attr(name) || ""), + el = this, + bplus = Str(b).match(reAddon); + if (bplus) { + eve.stop(); + var unit = bplus[3] || "", + aUnit = a.match(reUnit), + op = operators[bplus[1]]; + if (aUnit && aUnit == unit) { + return { + from: parseFloat(a), + to: op(parseFloat(a), +bplus[2]), + f: getUnit(aUnit) + }; + } else { + a = this.asPX(name); + return { + from: a, + to: op(a, this.asPX(name, bplus[2] + unit)), + f: getNumber + }; + } + } + })(-10); +}); +// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var proto = Paper.prototype, + is = Snap.is; + /*\ * Paper.rect [ method ] * * Draws a rectangle ** @@ -3408,11 +4622,11 @@ proto.rect = function (x, y, w, h, rx, ry) { var attr; if (ry == null) { ry = rx; } - if (is(x, "object") && "x" in x) { + if (is(x, "object") && x == "[object Object]") { attr = x; } else if (x != null) { attr = { x: x, y: y, @@ -3440,11 +4654,11 @@ > Usage | var c = paper.circle(50, 50, 40); \*/ proto.circle = function (cx, cy, r) { var attr; - if (is(cx, "object") && "cx" in cx) { + if (is(cx, "object") && cx == "[object Object]") { attr = cx; } else if (cx != null) { attr = { cx: cx, cy: cy, @@ -3452,10 +4666,29 @@ }; } return this.el("circle", attr); }; + var preload = (function () { + function onerror() { + this.parentNode.removeChild(this); + } + return function (src, f) { + var img = glob.doc.createElement("img"), + body = glob.doc.body; + img.style.cssText = "position:absolute;left:-9999em;top:-9999em"; + img.onload = function () { + f.call(img); + img.onload = img.onerror = null; + body.removeChild(img); + }; + img.onerror = onerror; + body.appendChild(img); + img.src = src; + }; + }()); + /*\ * Paper.image [ method ] ** * Places an image on the surface @@ -3471,11 +4704,11 @@ ** > Usage | var c = paper.image("apple.png", 10, 10, 80, 80); \*/ proto.image = function (src, x, y, width, height) { - var el = make("image", this.node); + var el = this.el("image"); if (is(src, "object") && "src" in src) { el.attr(src); } else if (src != null) { var set = { "xlink:href": src, @@ -3488,17 +4721,17 @@ if (width != null && height != null) { set.width = width; set.height = height; } else { preload(src, function () { - $(el.node, { + Snap._.$(el.node, { width: this.offsetWidth, height: this.offsetHeight }); }); } - $(el.node, set); + Snap._.$(el.node, set); } return el; }; /*\ * Paper.ellipse @@ -3514,22 +4747,22 @@ ** > Usage | var c = paper.ellipse(50, 50, 40, 20); \*/ proto.ellipse = function (cx, cy, rx, ry) { - var el = make("ellipse", this.node); - if (is(cx, "object") && "cx" in cx) { - el.attr(cx); + var attr; + if (is(cx, "object") && cx == "[object Object]") { + attr = cx; } else if (cx != null) { - el.attr({ + attr ={ cx: cx, cy: cy, rx: rx, ry: ry - }); + }; } - return el; + return this.el("ellipse", attr); }; // SIERRA Paper.path(): Unclear from the link what a Catmull-Rom curveto is, and why it would make life any easier. /*\ * Paper.path [ method ] @@ -3559,21 +4792,18 @@ | var c = paper.path("M10 10L90 90"); | // draw a diagonal line: | // move to 10,10, line to 90,90 \*/ proto.path = function (d) { - var el = make("path", this.node); + var attr; if (is(d, "object") && !is(d, "array")) { - el.attr(d); + attr = d; } else if (d) { - el.attr({ - d: d - }); + attr = {d: d}; } - return el; + return this.el("path", attr); }; -// SIERRA Paper.g(): Don't understand the code comment about the order being _different._ Wouldn't it be a rect followed by a circle? /*\ * Paper.g [ method ] ** * Creates a group element @@ -3596,23 +4826,169 @@ [ method ] ** * See @Paper.g \*/ proto.group = proto.g = function (first) { - var el = make("g", this.node); - el.add = add2group; - for (var method in proto) if (proto[has](method)) { - el[method] = proto[method]; + var attr, + el = this.el("g"); + if (arguments.length == 1 && first && !first.type) { + el.attr(first); + } else if (arguments.length) { + el.add(Array.prototype.slice.call(arguments, 0)); } + return el; + }; + /*\ + * Paper.svg + [ method ] + ** + * Creates a nested SVG element. + - x (number) @optional X of the element + - y (number) @optional Y of the element + - width (number) @optional width of the element + - height (number) @optional height of the element + - vbx (number) @optional viewbox X + - vby (number) @optional viewbox Y + - vbw (number) @optional viewbox width + - vbh (number) @optional viewbox height + ** + = (object) the `svg` element + ** + \*/ + proto.svg = function (x, y, width, height, vbx, vby, vbw, vbh) { + var attrs = {}; + if (is(x, "object") && y == null) { + attrs = x; + } else { + if (x != null) { + attrs.x = x; + } + if (y != null) { + attrs.y = y; + } + if (width != null) { + attrs.width = width; + } + if (height != null) { + attrs.height = height; + } + if (vbx != null && vby != null && vbw != null && vbh != null) { + attrs.viewBox = [vbx, vby, vbw, vbh]; + } + } + return this.el("svg", attrs); + }; + /*\ + * Paper.mask + [ method ] + ** + * Equivalent in behaviour to @Paper.g, except it’s a mask. + ** + = (object) the `mask` element + ** + \*/ + proto.mask = function (first) { + var attr, + el = this.el("mask"); if (arguments.length == 1 && first && !first.type) { el.attr(first); } else if (arguments.length) { el.add(Array.prototype.slice.call(arguments, 0)); } return el; }; /*\ + * Paper.ptrn + [ method ] + ** + * Equivalent in behaviour to @Paper.g, except it’s a pattern. + - x (number) @optional X of the element + - y (number) @optional Y of the element + - width (number) @optional width of the element + - height (number) @optional height of the element + - vbx (number) @optional viewbox X + - vby (number) @optional viewbox Y + - vbw (number) @optional viewbox width + - vbh (number) @optional viewbox height + ** + = (object) the `pattern` element + ** + \*/ + proto.ptrn = function (x, y, width, height, vx, vy, vw, vh) { + if (is(x, "object")) { + var attr = x; + } else { + attr = {patternUnits: "userSpaceOnUse"}; + if (x) { + attr.x = x; + } + if (y) { + attr.y = y; + } + if (width != null) { + attr.width = width; + } + if (height != null) { + attr.height = height; + } + if (vx != null && vy != null && vw != null && vh != null) { + attr.viewBox = [vx, vy, vw, vh]; + } else { + attr.viewBox = [x || 0, y || 0, width || 0, height || 0]; + } + } + return this.el("pattern", attr); + }; + /*\ + * Paper.use + [ method ] + ** + * Creates a <use> element. + - id (string) @optional id of element to link + * or + - id (Element) @optional element to link + ** + = (object) the `use` element + ** + \*/ + proto.use = function (id) { + if (id != null) { + if (id instanceof Element) { + if (!id.attr("id")) { + id.attr({id: Snap._.id(id)}); + } + id = id.attr("id"); + } + if (String(id).charAt() == "#") { + id = id.substring(1); + } + return this.el("use", {"xlink:href": "#" + id}); + } else { + return Element.prototype.use.call(this); + } + }; + /*\ + * Paper.symbol + [ method ] + ** + * Creates a <symbol> element. + - vbx (number) @optional viewbox X + - vby (number) @optional viewbox Y + - vbw (number) @optional viewbox width + - vbh (number) @optional viewbox height + = (object) the `symbol` element + ** + \*/ + proto.symbol = function (vx, vy, vw, vh) { + var attr = {}; + if (vx != null && vy != null && vw != null && vh != null) { + attr.viewBox = [vx, vy, vw, vh]; + } + + return this.el("symbol", attr); + }; + /*\ * Paper.text [ method ] ** * Draws a text string ** @@ -3629,21 +5005,21 @@ | // or | var pth = paper.path("M10,10L100,100"); | t1.attr({textpath: pth}); \*/ proto.text = function (x, y, text) { - var el = make("text", this.node); + var attr = {}; if (is(x, "object")) { - el.attr(x); + attr = x; } else if (x != null) { - el.attr({ + attr = { x: x, y: y, text: text || "" - }); + }; } - return el; + return this.el("text", attr); }; /*\ * Paper.line [ method ] ** @@ -3657,22 +5033,22 @@ ** > Usage | var t1 = paper.line(50, 50, 100, 100); \*/ proto.line = function (x1, y1, x2, y2) { - var el = make("line", this.node); + var attr = {}; if (is(x1, "object")) { - el.attr(x1); + attr = x1; } else if (x1 != null) { - el.attr({ + attr = { x1: x1, x2: x2, y1: y1, y2: y2 - }); + }; } - return el; + return this.el("line", attr); }; /*\ * Paper.polyline [ method ] ** @@ -3689,19 +5065,17 @@ \*/ proto.polyline = function (points) { if (arguments.length > 1) { points = Array.prototype.slice.call(arguments, 0); } - var el = make("polyline", this.node); + var attr = {}; if (is(points, "object") && !is(points, "array")) { - el.attr(points); + attr = points; } else if (points != null) { - el.attr({ - points: points - }); + attr = {points: points}; } - return el; + return this.el("polyline", attr); }; /*\ * Paper.polygon [ method ] ** @@ -3709,22 +5083,129 @@ \*/ proto.polygon = function (points) { if (arguments.length > 1) { points = Array.prototype.slice.call(arguments, 0); } - var el = make("polygon", this.node); + var attr = {}; if (is(points, "object") && !is(points, "array")) { - el.attr(points); + attr = points; } else if (points != null) { - el.attr({ - points: points - }); + attr = {points: points}; } - return el; + return this.el("polygon", attr); }; // gradients (function () { + var $ = Snap._.$; + // gradients' helpers + function Gstops() { + return this.selectAll("stop"); + } + function GaddStop(color, offset) { + var stop = $("stop"), + attr = { + offset: +offset + "%" + }; + color = Snap.color(color); + attr["stop-color"] = color.hex; + if (color.opacity < 1) { + attr["stop-opacity"] = color.opacity; + } + $(stop, attr); + this.node.appendChild(stop); + return this; + } + function GgetBBox() { + if (this.type == "linearGradient") { + var x1 = $(this.node, "x1") || 0, + x2 = $(this.node, "x2") || 1, + y1 = $(this.node, "y1") || 0, + y2 = $(this.node, "y2") || 0; + return Snap._.box(x1, y1, math.abs(x2 - x1), math.abs(y2 - y1)); + } else { + var cx = this.node.cx || .5, + cy = this.node.cy || .5, + r = this.node.r || 0; + return Snap._.box(cx - r, cy - r, r * 2, r * 2); + } + } + function gradient(defs, str) { + var grad = eve("snap.util.grad.parse", null, str).firstDefined(), + el; + if (!grad) { + return null; + } + grad.params.unshift(defs); + if (grad.type.toLowerCase() == "l") { + el = gradientLinear.apply(0, grad.params); + } else { + el = gradientRadial.apply(0, grad.params); + } + if (grad.type != grad.type.toLowerCase()) { + $(el.node, { + gradientUnits: "userSpaceOnUse" + }); + } + var stops = grad.stops, + len = stops.length, + start = 0, + j = 0; + function seed(i, end) { + var step = (end - start) / (i - j); + for (var k = j; k < i; k++) { + stops[k].offset = +(+start + step * (k - j)).toFixed(2); + } + j = i; + start = end; + } + len--; + for (var i = 0; i < len; i++) if ("offset" in stops[i]) { + seed(i, stops[i].offset); + } + stops[len].offset = stops[len].offset || 100; + seed(len, stops[len].offset); + for (i = 0; i <= len; i++) { + var stop = stops[i]; + el.addStop(stop.color, stop.offset); + } + return el; + } + function gradientLinear(defs, x1, y1, x2, y2) { + var el = Snap._.make("linearGradient", defs); + el.stops = Gstops; + el.addStop = GaddStop; + el.getBBox = GgetBBox; + if (x1 != null) { + $(el.node, { + x1: x1, + y1: y1, + x2: x2, + y2: y2 + }); + } + return el; + } + function gradientRadial(defs, cx, cy, r, fx, fy) { + var el = Snap._.make("radialGradient", defs); + el.stops = Gstops; + el.addStop = GaddStop; + el.getBBox = GgetBBox; + if (cx != null) { + $(el.node, { + cx: cx, + cy: cy, + r: r + }); + } + if (fx != null && fy != null) { + $(el.node, { + fx: fx, + fy: fy + }); + } + return el; + } /*\ * Paper.gradient [ method ] ** * Creates a gradient element @@ -3748,11 +5229,11 @@ * Linear gradient, relative from top-left corner to bottom-right * corner, from black through red to white: | var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff"); * Linear gradient, absolute from (0, 0) to (100, 100), from black * through red at 25% to white: - | var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25%-#fff"); + | var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25-#fff"); * Radial gradient, relative from the center of the element with radius * half the width, from black to white: | var g = paper.gradient("r(0.5, 0.5, 0.5)#000-#fff"); * To apply the gradient: | paper.circle(50, 50, 40).attr({ @@ -3775,22 +5256,35 @@ ** * Returns SVG code for the @Paper = (string) SVG code for the @Paper \*/ proto.toString = function () { - var f = glob.doc.createDocumentFragment(), - d = glob.doc.createElement("div"), + var doc = this.node.ownerDocument, + f = doc.createDocumentFragment(), + d = doc.createElement("div"), svg = this.node.cloneNode(true), res; f.appendChild(d); d.appendChild(svg); - $(svg, {xmlns: xmlns}); + Snap._.$(svg, {xmlns: "http://www.w3.org/2000/svg"}); res = d.innerHTML; f.removeChild(f.firstChild); return res; }; /*\ + * Paper.toDataURL + [ method ] + ** + * Returns SVG code for the @Paper as Data URI string. + = (string) Data URI string + \*/ + proto.toDataURL = function () { + if (window && window.btoa) { + return "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(this))); + } + }; + /*\ * Paper.clear [ method ] ** * Removes all child nodes of the paper, except <defs>. \*/ @@ -3799,601 +5293,19 @@ next; while (node) { next = node.nextSibling; if (node.tagName != "defs") { node.parentNode.removeChild(node); + } else { + proto.clear.call({node: node}); } node = next; } }; }()); -}(Paper.prototype)); - -// simple ajax -/*\ - * Snap.ajax - [ method ] - ** - * Simple implementation of Ajax - ** - - url (string) URL - - postData (object|string) data for post request - - callback (function) callback - - scope (object) #optional scope of callback - * or - - url (string) URL - - callback (function) callback - - scope (object) #optional scope of callback - = (XMLHttpRequest) the XMLHttpRequest object, just in case -\*/ -Snap.ajax = function (url, postData, callback, scope){ - var req = new XMLHttpRequest, - id = ID(); - if (req) { - if (is(postData, "function")) { - scope = callback; - callback = postData; - postData = null; - } else if (is(postData, "object")) { - var pd = []; - for (var key in postData) if (postData.hasOwnProperty(key)) { - pd.push(encodeURIComponent(key) + "=" + encodeURIComponent(postData[key])); - } - postData = pd.join("&"); - } - req.open((postData ? "POST" : "GET"), url, true); - req.setRequestHeader("X-Requested-With", "XMLHttpRequest"); - if (postData) { - req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - } - if (callback) { - eve.once("snap.ajax." + id + ".0", callback); - eve.once("snap.ajax." + id + ".200", callback); - eve.once("snap.ajax." + id + ".304", callback); - } - req.onreadystatechange = function() { - if (req.readyState != 4) return; - eve("snap.ajax." + id + "." + req.status, scope, req); - }; - if (req.readyState == 4) { - return req; - } - req.send(postData); - return req; - } -}; -/*\ - * Snap.load - [ method ] - ** - * Loads external SVG file as a @Fragment (see @Snap.ajax for more advanced AJAX) - ** - - url (string) URL - - callback (function) callback - - scope (object) #optional scope of callback -\*/ -Snap.load = function (url, callback, scope) { - Snap.ajax(url, function (req) { - var f = Snap.parse(req.responseText); - scope ? callback.call(scope, f) : callback(f); - }); -}; - -// Attributes event handlers -eve.on("snap.util.attr.mask", function (value) { - if (value instanceof Element || value instanceof Fragment) { - eve.stop(); - if (value instanceof Fragment && value.node.childNodes.length == 1) { - value = value.node.firstChild; - getSomeDefs(this).appendChild(value); - value = wrap(value); - } - if (value.type == "mask") { - var mask = value; - } else { - mask = make("mask", getSomeDefs(this)); - mask.node.appendChild(value.node); - !mask.node.id && $(mask.node, { - id: mask.id - }); - } - $(this.node, { - mask: URL(mask.id) - }); - } }); -(function (clipIt) { - eve.on("snap.util.attr.clip", clipIt); - eve.on("snap.util.attr.clip-path", clipIt); - eve.on("snap.util.attr.clipPath", clipIt); -}(function (value) { - if (value instanceof Element || value instanceof Fragment) { - eve.stop(); - if (value.type == "clipPath") { - var clip = value; - } else { - clip = make("clipPath", getSomeDefs(this)); - clip.node.appendChild(value.node); - !clip.node.id && $(clip.node, { - id: clip.id - }); - } - $(this.node, { - "clip-path": URL(clip.id) - }); - } -})); -function fillStroke(name) { - return function (value) { - eve.stop(); - if (value instanceof Fragment && value.node.childNodes.length == 1 && - (value.node.firstChild.tagName == "radialGradient" || - value.node.firstChild.tagName == "linearGradient" || - value.node.firstChild.tagName == "pattern")) { - value = value.node.firstChild; - getSomeDefs(this).appendChild(value); - value = wrap(value); - } - if (value instanceof Element) { - if (value.type == "radialGradient" || value.type == "linearGradient" - || value.type == "pattern") { - if (!value.node.id) { - $(value.node, { - id: value.id - }); - } - var fill = URL(value.node.id); - } else { - fill = value.attr(name); - } - } else { - fill = Snap.color(value); - if (fill.error) { - var grad = gradient(getSomeDefs(this), value); - if (grad) { - if (!grad.node.id) { - $(grad.node, { - id: grad.id - }); - } - fill = URL(grad.node.id); - } else { - fill = value; - } - } else { - fill = Str(fill); - } - } - var attrs = {}; - attrs[name] = fill; - $(this.node, attrs); - this.node.style[name] = E; - }; -} -eve.on("snap.util.attr.fill", fillStroke("fill")); -eve.on("snap.util.attr.stroke", fillStroke("stroke")); -var gradrg = /^([lr])(?:\(([^)]*)\))?(.*)$/i; -eve.on("snap.util.grad.parse", function parseGrad(string) { - string = Str(string); - var tokens = string.match(gradrg); - if (!tokens) { - return null; - } - var type = tokens[1], - params = tokens[2], - stops = tokens[3]; - params = params.split(/\s*,\s*/).map(function (el) { - return +el == el ? +el : el; - }); - if (params.length == 1 && params[0] == 0) { - params = []; - } - stops = stops.split("-"); - stops = stops.map(function (el) { - el = el.split(":"); - var out = { - color: el[0] - }; - if (el[1]) { - out.offset = el[1]; - } - return out; - }); - return { - type: type, - params: params, - stops: stops - }; -}); -eve.on("snap.util.attr.d", function (value) { - eve.stop(); - if (is(value, "array") && is(value[0], "array")) { - value = Snap.path.toString.call(value); - } - value = Str(value); - if (value.match(/[ruo]/i)) { - value = Snap.path.toAbsolute(value); - } - $(this.node, {d: value}); -})(-1); -eve.on("snap.util.attr.#text", function (value) { - eve.stop(); - value = Str(value); - var txt = glob.doc.createTextNode(value); - while (this.node.firstChild) { - this.node.removeChild(this.node.firstChild); - } - this.node.appendChild(txt); -})(-1); -eve.on("snap.util.attr.path", function (value) { - eve.stop(); - this.attr({d: value}); -})(-1); -eve.on("snap.util.attr.viewBox", function (value) { - var vb; - if (is(value, "object") && "x" in value) { - vb = [value.x, value.y, value.width, value.height].join(" "); - } else if (is(value, "array")) { - vb = value.join(" "); - } else { - vb = value; - } - $(this.node, { - viewBox: vb - }); - eve.stop(); -})(-1); -eve.on("snap.util.attr.transform", function (value) { - this.transform(value); - eve.stop(); -})(-1); -eve.on("snap.util.attr.r", function (value) { - if (this.type == "rect") { - eve.stop(); - $(this.node, { - rx: value, - ry: value - }); - } -})(-1); -eve.on("snap.util.attr.textpath", function (value) { - eve.stop(); - if (this.type == "text") { - var id, tp, node; - if (!value && this.textPath) { - tp = this.textPath; - while (tp.node.firstChild) { - this.node.appendChild(tp.node.firstChild); - } - tp.remove(); - delete this.textPath; - return; - } - if (is(value, "string")) { - var defs = getSomeDefs(this), - path = wrap(defs.parentNode).path(value); - defs.appendChild(path.node); - id = path.id; - path.attr({id: id}); - } else { - value = wrap(value); - if (value instanceof Element) { - id = value.attr("id"); - if (!id) { - id = value.id; - value.attr({id: id}); - } - } - } - if (id) { - tp = this.textPath; - node = this.node; - if (tp) { - tp.attr({"xlink:href": "#" + id}); - } else { - tp = $("textPath", { - "xlink:href": "#" + id - }); - while (node.firstChild) { - tp.appendChild(node.firstChild); - } - node.appendChild(tp); - this.textPath = wrap(tp); - } - } - } -})(-1); -eve.on("snap.util.attr.text", function (value) { - if (this.type == "text") { - var i = 0, - node = this.node, - tuner = function (chunk) { - var out = $("tspan"); - if (is(chunk, "array")) { - for (var i = 0; i < chunk.length; i++) { - out.appendChild(tuner(chunk[i])); - } - } else { - out.appendChild(glob.doc.createTextNode(chunk)); - } - out.normalize && out.normalize(); - return out; - }; - while (node.firstChild) { - node.removeChild(node.firstChild); - } - var tuned = tuner(value); - while (tuned.firstChild) { - node.appendChild(tuned.firstChild); - } - } - eve.stop(); -})(-1); -// default -var cssAttr = { - "alignment-baseline": 0, - "baseline-shift": 0, - "clip": 0, - "clip-path": 0, - "clip-rule": 0, - "color": 0, - "color-interpolation": 0, - "color-interpolation-filters": 0, - "color-profile": 0, - "color-rendering": 0, - "cursor": 0, - "direction": 0, - "display": 0, - "dominant-baseline": 0, - "enable-background": 0, - "fill": 0, - "fill-opacity": 0, - "fill-rule": 0, - "filter": 0, - "flood-color": 0, - "flood-opacity": 0, - "font": 0, - "font-family": 0, - "font-size": 0, - "font-size-adjust": 0, - "font-stretch": 0, - "font-style": 0, - "font-variant": 0, - "font-weight": 0, - "glyph-orientation-horizontal": 0, - "glyph-orientation-vertical": 0, - "image-rendering": 0, - "kerning": 0, - "letter-spacing": 0, - "lighting-color": 0, - "marker": 0, - "marker-end": 0, - "marker-mid": 0, - "marker-start": 0, - "mask": 0, - "opacity": 0, - "overflow": 0, - "pointer-events": 0, - "shape-rendering": 0, - "stop-color": 0, - "stop-opacity": 0, - "stroke": 0, - "stroke-dasharray": 0, - "stroke-dashoffset": 0, - "stroke-linecap": 0, - "stroke-linejoin": 0, - "stroke-miterlimit": 0, - "stroke-opacity": 0, - "stroke-width": 0, - "text-anchor": 0, - "text-decoration": 0, - "text-rendering": 0, - "unicode-bidi": 0, - "visibility": 0, - "word-spacing": 0, - "writing-mode": 0 -}; - -eve.on("snap.util.attr", function (value) { - var att = eve.nt(), - attr = {}; - att = att.substring(att.lastIndexOf(".") + 1); - attr[att] = value; - var style = att.replace(/-(\w)/gi, function (all, letter) { - return letter.toUpperCase(); - }), - css = att.replace(/[A-Z]/g, function (letter) { - return "-" + letter.toLowerCase(); - }); - if (cssAttr[has](css)) { - this.node.style[style] = value == null ? E : value; - } else { - $(this.node, attr); - } -}); -eve.on("snap.util.getattr.transform", function () { - eve.stop(); - return this.transform(); -})(-1); -eve.on("snap.util.getattr.textpath", function () { - eve.stop(); - return this.textPath; -})(-1); -// Markers -(function () { - function getter(end) { - return function () { - eve.stop(); - var style = glob.doc.defaultView.getComputedStyle(this.node, null).getPropertyValue("marker-" + end); - if (style == "none") { - return style; - } else { - return Snap(glob.doc.getElementById(style.match(reURLValue)[1])); - } - }; - } - function setter(end) { - return function (value) { - eve.stop(); - var name = "marker" + end.charAt(0).toUpperCase() + end.substring(1); - if (value == "" || !value) { - this.node.style[name] = "none"; - return; - } - if (value.type == "marker") { - var id = value.node.id; - if (!id) { - $(value.node, {id: value.id}); - } - this.node.style[name] = URL(id); - return; - } - }; - } - eve.on("snap.util.getattr.marker-end", getter("end"))(-1); - eve.on("snap.util.getattr.markerEnd", getter("end"))(-1); - eve.on("snap.util.getattr.marker-start", getter("start"))(-1); - eve.on("snap.util.getattr.markerStart", getter("start"))(-1); - eve.on("snap.util.getattr.marker-mid", getter("mid"))(-1); - eve.on("snap.util.getattr.markerMid", getter("mid"))(-1); - eve.on("snap.util.attr.marker-end", setter("end"))(-1); - eve.on("snap.util.attr.markerEnd", setter("end"))(-1); - eve.on("snap.util.attr.marker-start", setter("start"))(-1); - eve.on("snap.util.attr.markerStart", setter("start"))(-1); - eve.on("snap.util.attr.marker-mid", setter("mid"))(-1); - eve.on("snap.util.attr.markerMid", setter("mid"))(-1); -}()); -eve.on("snap.util.getattr.r", function () { - if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) { - eve.stop(); - return $(this.node, "rx"); - } -})(-1); -function textExtract(node) { - var out = []; - var children = node.childNodes; - for (var i = 0, ii = children.length; i < ii; i++) { - var chi = children[i]; - if (chi.nodeType == 3) { - out.push(chi.nodeValue); - } - if (chi.tagName == "tspan") { - if (chi.childNodes.length == 1 && chi.firstChild.nodeType == 3) { - out.push(chi.firstChild.nodeValue); - } else { - out.push(textExtract(chi)); - } - } - } - return out; -} -eve.on("snap.util.getattr.text", function () { - if (this.type == "text" || this.type == "tspan") { - eve.stop(); - var out = textExtract(this.node); - return out.length == 1 ? out[0] : out; - } -})(-1); -eve.on("snap.util.getattr.#text", function () { - return this.node.textContent; -})(-1); -eve.on("snap.util.getattr.viewBox", function () { - eve.stop(); - var vb = $(this.node, "viewBox").split(separator); - return Snap._.box(+vb[0], +vb[1], +vb[2], +vb[3]); - // TODO: investigate why I need to z-index it -})(-1); -eve.on("snap.util.getattr.points", function () { - var p = $(this.node, "points"); - eve.stop(); - return p.split(separator); -}); -eve.on("snap.util.getattr.path", function () { - var p = $(this.node, "d"); - eve.stop(); - return p; -}); -// default -eve.on("snap.util.getattr", function () { - var att = eve.nt(); - att = att.substring(att.lastIndexOf(".") + 1); - var css = att.replace(/[A-Z]/g, function (letter) { - return "-" + letter.toLowerCase(); - }); - if (cssAttr[has](css)) { - return glob.doc.defaultView.getComputedStyle(this.node, null).getPropertyValue(css); - } else { - return $(this.node, att); - } -}); -var getOffset = function (elem) { - var box = elem.getBoundingClientRect(), - doc = elem.ownerDocument, - body = doc.body, - docElem = doc.documentElement, - clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, - top = box.top + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop, - left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft; - return { - y: top, - x: left - }; -}; -/*\ - * Snap.getElementByPoint - [ method ] - ** - * Returns you topmost element under given point. - ** - = (object) Snap element object - - x (number) x coordinate from the top left corner of the window - - y (number) y coordinate from the top left corner of the window - > Usage - | Snap.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"}); -\*/ -Snap.getElementByPoint = function (x, y) { - var paper = this, - svg = paper.canvas, - target = glob.doc.elementFromPoint(x, y); - if (glob.win.opera && target.tagName == "svg") { - var so = getOffset(target), - sr = target.createSVGRect(); - sr.x = x - so.x; - sr.y = y - so.y; - sr.width = sr.height = 1; - var hits = target.getIntersectionList(sr, null); - if (hits.length) { - target = hits[hits.length - 1]; - } - } - if (!target) { - return null; - } - return wrap(target); -}; -/*\ - * Snap.plugin - [ method ] - ** - * Let you write plugins. You pass in a function with four arguments, like this: - | Snap.plugin(function (Snap, Element, Paper, global) { - | Snap.newmethod = function () {}; - | Element.prototype.newmethod = function () {}; - | Paper.prototype.newmethod = function () {}; - | }); - * Inside the function you have access to all main objects (and their - * prototypes). This allow you to extend anything you want. - ** - - f (function) your plugin body -\*/ -Snap.plugin = function (f) { - f(Snap, Element, Paper, glob); -}; -glob.win.Snap = Snap; -return Snap; -}()); // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -4692,12 +5604,12 @@ if (!isBBoxIntersect(bbox1, bbox2)) { return justCount ? 0 : []; } var l1 = bezlen.apply(0, bez1), l2 = bezlen.apply(0, bez2), - n1 = ~~(l1 / 5), - n2 = ~~(l2 / 5), + n1 = ~~(l1 / 8), + n2 = ~~(l2 / 8), dots1 = [], dots2 = [], xy = {}, res = justCount ? 0 : []; for (var i = 0; i < n1 + 1; i++) { @@ -4843,11 +5755,11 @@ return bb; } function rectPath(x, y, w, h, r) { if (r) { return [ - ["M", x + r, y], + ["M", +x + (+r), y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], @@ -4863,10 +5775,14 @@ } function ellipsePath(x, y, rx, ry, a) { if (a == null && ry == null) { ry = rx; } + x = +x; + y = +y; + rx = +rx; + ry = +ry; if (a != null) { var rad = Math.PI / 180, x1 = x + rx * Math.cos(-ry * rad), x2 = x + rx * Math.cos(-a * rad), y1 = y + rx * Math.sin(-ry * rad), @@ -4893,45 +5809,29 @@ var attr = unit2px(el); return ellipsePath(attr.cx, attr.cy, attr.r); }, ellipse: function (el) { var attr = unit2px(el); - return ellipsePath(attr.cx, attr.cy, attr.rx, attr.ry); + return ellipsePath(attr.cx || 0, attr.cy || 0, attr.rx, attr.ry); }, rect: function (el) { var attr = unit2px(el); - return rectPath(attr.x, attr.y, attr.width, attr.height, attr.rx, attr.ry); + return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height, attr.rx, attr.ry); }, image: function (el) { var attr = unit2px(el); - return rectPath(attr.x, attr.y, attr.width, attr.height); + return rectPath(attr.x || 0, attr.y || 0, attr.width, attr.height); }, - text: function (el) { - var bbox = el.node.getBBox(); - return rectPath(bbox.x, bbox.y, bbox.width, bbox.height); - }, - g: function (el) { - var bbox = el.node.getBBox(); - return rectPath(bbox.x, bbox.y, bbox.width, bbox.height); - }, - symbol: function (el) { - var bbox = el.getBBox(); - return rectPath(bbox.x, bbox.y, bbox.width, bbox.height); - }, line: function (el) { - return "M" + [el.attr("x1"), el.attr("y1"), el.attr("x2"), el.attr("y2")]; + return "M" + [el.attr("x1") || 0, el.attr("y1") || 0, el.attr("x2"), el.attr("y2")]; }, polyline: function (el) { return "M" + el.attr("points"); }, polygon: function (el) { return "M" + el.attr("points") + "z"; }, - svg: function (el) { - var bbox = el.node.getBBox(); - return rectPath(bbox.x, bbox.y, bbox.width, bbox.height); - }, deflt: function (el) { var bbox = el.node.getBBox(); return rectPath(bbox.x, bbox.y, bbox.width, bbox.height); } }; @@ -5056,12 +5956,12 @@ r[1] = pa[1]; r[2] = pa[2]; r[3] = pa[3]; r[4] = pa[4]; r[5] = pa[5]; - r[6] = +(pa[6] + x); - r[7] = +(pa[7] + y); + r[6] = +pa[6] + x; + r[7] = +pa[7] + y; break; case "V": r[1] = +pa[1] + y; break; case "H": @@ -5116,12 +6016,12 @@ } pa0 = pa0.toUpperCase(); if (pa0 != "O") { switch (r[0]) { case "Z": - x = mx; - y = my; + x = +mx; + y = +my; break; case "H": x = r[1]; break; case "V": @@ -5249,84 +6149,120 @@ return { x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x, y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y }; } - function curveDim(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) { - var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x), - b = 2 * (c1x - p1x) - 2 * (c2x - c1x), - c = p1x - c1x, - t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a, - t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a, - y = [p1y, p2y], - x = [p1x, p2x], - dot; - abs(t1) > "1e12" && (t1 = .5); - abs(t2) > "1e12" && (t2 = .5); - if (t1 > 0 && t1 < 1) { - dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1); - x.push(dot.x); - y.push(dot.y); + + // Returns bounding box of cubic bezier curve. + // Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html + // Original version: NISHIO Hirokazu + // Modifications: https://github.com/timo22345 + function curveDim(x0, y0, x1, y1, x2, y2, x3, y3) { + var tvalues = [], + bounds = [[], []], + a, b, c, t, t1, t2, b2ac, sqrtb2ac; + for (var i = 0; i < 2; ++i) { + if (i == 0) { + b = 6 * x0 - 12 * x1 + 6 * x2; + a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; + c = 3 * x1 - 3 * x0; + } else { + b = 6 * y0 - 12 * y1 + 6 * y2; + a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; + c = 3 * y1 - 3 * y0; + } + if (abs(a) < 1e-12) { + if (abs(b) < 1e-12) { + continue; + } + t = -c / b; + if (0 < t && t < 1) { + tvalues.push(t); + } + continue; + } + b2ac = b * b - 4 * c * a; + sqrtb2ac = math.sqrt(b2ac); + if (b2ac < 0) { + continue; + } + t1 = (-b + sqrtb2ac) / (2 * a); + if (0 < t1 && t1 < 1) { + tvalues.push(t1); + } + t2 = (-b - sqrtb2ac) / (2 * a); + if (0 < t2 && t2 < 1) { + tvalues.push(t2); + } } - if (t2 > 0 && t2 < 1) { - dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2); - x.push(dot.x); - y.push(dot.y); + + var x, y, j = tvalues.length, + jlen = j, + mt; + while (j--) { + t = tvalues[j]; + mt = 1 - t; + bounds[0][j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3); + bounds[1][j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3); } - a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y); - b = 2 * (c1y - p1y) - 2 * (c2y - c1y); - c = p1y - c1y; - t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a; - t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a; - abs(t1) > "1e12" && (t1 = .5); - abs(t2) > "1e12" && (t2 = .5); - if (t1 > 0 && t1 < 1) { - dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1); - x.push(dot.x); - y.push(dot.y); - } - if (t2 > 0 && t2 < 1) { - dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2); - x.push(dot.x); - y.push(dot.y); - } + + bounds[0][jlen] = x0; + bounds[1][jlen] = y0; + bounds[0][jlen + 1] = x3; + bounds[1][jlen + 1] = y3; + bounds[0].length = bounds[1].length = jlen + 2; + + return { - min: {x: mmin.apply(0, x), y: mmin.apply(0, y)}, - max: {x: mmax.apply(0, x), y: mmax.apply(0, y)} + min: {x: mmin.apply(0, bounds[0]), y: mmin.apply(0, bounds[1])}, + max: {x: mmax.apply(0, bounds[0]), y: mmax.apply(0, bounds[1])} }; } + function path2curve(path, path2) { var pth = !path2 && paths(path); if (!path2 && pth.curve) { return pathClone(pth.curve); } var p = pathToAbsolute(path), p2 = path2 && pathToAbsolute(path2), attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null}, attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null}, - processPath = function (path, d) { + processPath = function (path, d, pcom) { var nx, ny; if (!path) { return ["C", d.x, d.y, d.x, d.y, d.x, d.y]; } - !(path[0] in {T:1, Q:1}) && (d.qx = d.qy = null); + !(path[0] in {T: 1, Q: 1}) && (d.qx = d.qy = null); switch (path[0]) { case "M": d.X = path[1]; d.Y = path[2]; break; case "A": path = ["C"].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1)))); break; case "S": - nx = d.x + (d.x - (d.bx || d.x)); - ny = d.y + (d.y - (d.by || d.y)); + if (pcom == "C" || pcom == "S") { // In "S" case we have to take into account, if the previous command is C/S. + nx = d.x * 2 - d.bx; // And reflect the previous + ny = d.y * 2 - d.by; // command's control point relative to the current point. + } + else { // or some else or nothing + nx = d.x; + ny = d.y; + } path = ["C", nx, ny].concat(path.slice(1)); break; case "T": - d.qx = d.x + (d.x - (d.qx || d.x)); - d.qy = d.y + (d.y - (d.qy || d.y)); + if (pcom == "Q" || pcom == "T") { // In "T" case we have to take into account, if the previous command is Q/T. + d.qx = d.x * 2 - d.qx; // And make a reflection similar + d.qy = d.y * 2 - d.qy; // to case "S". + } + else { // or something else or nothing + d.qx = d.x; + d.qy = d.y; + } path = ["C"].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2])); break; case "Q": d.qx = path[1]; d.qy = path[2]; @@ -5350,10 +6286,12 @@ fixArc = function (pp, i) { if (pp[i].length > 7) { pp[i].shift(); var pi = pp[i]; while (pi.length) { + pcoms1[i] = "A"; // if created multiple C:s, their original seg is saved + p2 && (pcoms2[i] = "A"); // the same as above pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6))); } pp.splice(i, 1); ii = mmax(p.length, p2 && p2.length || 0); } @@ -5365,16 +6303,45 @@ a1.by = 0; a1.x = path1[i][1]; a1.y = path1[i][2]; ii = mmax(p.length, p2 && p2.length || 0); } - }; + }, + pcoms1 = [], // path commands of original path p + pcoms2 = [], // path commands of original path p2 + pfirst = "", // temporary holder for original path command + pcom = ""; // holder for previous path command of original path for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) { - p[i] = processPath(p[i], attrs); - fixArc(p, i); - p2 && (p2[i] = processPath(p2[i], attrs2)); - p2 && fixArc(p2, i); + p[i] && (pfirst = p[i][0]); // save current path command + + if (pfirst != "C") // C is not saved yet, because it may be result of conversion + { + pcoms1[i] = pfirst; // Save current path command + i && ( pcom = pcoms1[i - 1]); // Get previous path command pcom + } + p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath + + if (pcoms1[i] != "A" && pfirst == "C") pcoms1[i] = "C"; // A is the only command + // which may produce multiple C:s + // so we have to make sure that C is also C in original path + + fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1 + + if (p2) { // the same procedures is done to p2 + p2[i] && (pfirst = p2[i][0]); + if (pfirst != "C") { + pcoms2[i] = pfirst; + i && (pcom = pcoms2[i - 1]); + } + p2[i] = processPath(p2[i], attrs2, pcom); + + if (pcoms2[i] != "A" && pfirst == "C") { + pcoms2[i] = "C"; + } + + fixArc(p2, i); + } fixM(p, p2, attrs, attrs2, i); fixM(p2, p, attrs2, attrs, i); var seg = p[i], seg2 = p2 && p2[i], seglen = seg.length, @@ -5626,10 +6593,55 @@ - x (string) x coordinate of the point - y (string) y coordinate of the point = (boolean) `true` if point is inside \*/ Snap.path.isPointInsideBBox = isPointInsideBBox; + Snap.closest = function (x, y, X, Y) { + var r = 100, + b = box(x - r / 2, y - r / 2, r, r), + inside = [], + getter = X[0].hasOwnProperty("x") ? function (i) { + return { + x: X[i].x, + y: X[i].y + }; + } : function (i) { + return { + x: X[i], + y: Y[i] + }; + }, + found = 0; + while (r <= 1e6 && !found) { + for (var i = 0, ii = X.length; i < ii; i++) { + var xy = getter(i); + if (isPointInsideBBox(b, xy.x, xy.y)) { + found++; + inside.push(xy); + break; + } + } + if (!found) { + r *= 2; + b = box(x - r / 2, y - r / 2, r, r) + } + } + if (r == 1e6) { + return; + } + var len = Infinity, + res; + for (i = 0, ii = inside.length; i < ii; i++) { + var l = Snap.len(x, y, inside[i].x, inside[i].y); + if (len > l) { + len = l; + inside[i].len = l; + res = inside[i]; + } + } + return res; + }; /*\ * Snap.path.isBBoxIntersect [ method ] ** * Utility method @@ -5744,10 +6756,11 @@ \*/ Snap.path.map = mapPath; Snap.path.toString = toString; Snap.path.clone = pathClone; }); + // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -5764,10 +6777,11 @@ mmin = Math.min; // Set var Set = function (items) { this.items = []; + this.bindings = {}; this.length = 0; this.type = "set"; if (items) { for (var i = 0, ii = items.length; i < ii; i++) { if (items[i]) { @@ -5827,19 +6841,119 @@ return this; } } return this; }; + /*\ + * Set.animate + [ method ] + ** + * Animates each element in set in sync. + * + ** + - attrs (object) key-value pairs of destination attributes + - duration (number) duration of the animation in milliseconds + - easing (function) #optional easing function from @mina or custom + - callback (function) #optional callback function that executes when the animation ends + * or + - animation (array) array of animation parameter for each element in set in format `[attrs, duration, easing, callback]` + > Usage + | // animate all elements in set to radius 10 + | set.animate({r: 10}, 500, mina.easein); + | // or + | // animate first element to radius 10, but second to radius 20 and in different time + | set.animate([{r: 10}, 500, mina.easein], [{r: 20}, 1500, mina.easein]); + = (Element) the current element + \*/ + setproto.animate = function (attrs, ms, easing, callback) { + if (typeof easing == "function" && !easing.length) { + callback = easing; + easing = mina.linear; + } + if (attrs instanceof Snap._.Animation) { + callback = attrs.callback; + easing = attrs.easing; + ms = easing.dur; + attrs = attrs.attr; + } + var args = arguments; + if (Snap.is(attrs, "array") && Snap.is(args[args.length - 1], "array")) { + var each = true; + } + var begin, + handler = function () { + if (begin) { + this.b = begin; + } else { + begin = this.b; + } + }, + cb = 0, + set = this, + callbacker = callback && function () { + if (++cb == set.length) { + callback.call(this); + } + }; + return this.forEach(function (el, i) { + eve.once("snap.animcreated." + el.id, handler); + if (each) { + args[i] && el.animate.apply(el, args[i]); + } else { + el.animate(attrs, ms, easing, callbacker); + } + }); + }; setproto.remove = function () { while (this.length) { this.pop().remove(); } return this; }; + /*\ + * Set.bind + [ method ] + ** + * Specifies how to handle a specific attribute when applied + * to a set. + * + ** + - attr (string) attribute name + - callback (function) function to run + * or + - attr (string) attribute name + - element (Element) specific element in the set to apply the attribute to + * or + - attr (string) attribute name + - element (Element) specific element in the set to apply the attribute to + - eattr (string) attribute on the element to bind the attribute to + = (object) Set object + \*/ + setproto.bind = function (attr, a, b) { + var data = {}; + if (typeof a == "function") { + this.bindings[attr] = a; + } else { + var aname = b || attr; + this.bindings[attr] = function (v) { + data[aname] = v; + a.attr(data); + }; + } + return this; + }; setproto.attr = function (value) { + var unbound = {}; + for (var k in value) { + if (this.bindings[k]) { + this.bindings[k](value[k]); + } else { + unbound[k] = value[k]; + } + } for (var i = 0, ii = this.items.length; i < ii; i++) { - this.items[i].attr(value); + this.items[i].attr(unbound); } return this; }; /*\ * Set.clear @@ -5949,18 +7063,20 @@ setproto.toString = function () { return "Snap\u2018s set"; }; setproto.type = "set"; // export + Snap.Set = Set; Snap.set = function () { var set = new Set; if (arguments.length) { set.push.apply(set, Array.prototype.slice.call(arguments, 0)); } return set; }; }); + // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -6037,10 +7153,13 @@ function getUnit(unit) { return function (val) { return +val.toFixed(3) + unit; }; } + function getViewBox(val) { + return val.join(" "); + } function getColour(clr) { return Snap.rgb(clr[0], clr[1], clr[2]); } function getPath(path) { var k = 0, i, ii, j, jj, out, a, b = []; @@ -6062,17 +7181,29 @@ out.push(path[i][j]); } } return out; } + function isNumeric(obj) { + return isFinite(parseFloat(obj)); + } + function arrayEqual(arr1, arr2) { + if (!Snap.is(arr1, "array") || !Snap.is(arr2, "array")) { + return false; + } + return arr1.toString() == arr2.toString(); + } Element.prototype.equal = function (name, b) { + return eve("snap.util.equal", this, name, b).firstDefined(); + }; + eve.on("snap.util.equal", function (name, b) { var A, B, a = Str(this.attr(name) || ""), el = this; - if (a == +a && b == +b) { + if (isNumeric(a) && isNumeric(b)) { return { - from: +a, - to: +b, + from: parseFloat(a), + to: parseFloat(b), f: getNumber }; } if (names[name] == "colour") { A = Snap.color(a); @@ -6081,10 +7212,19 @@ from: [A.r, A.g, A.b, A.opacity], to: [B.r, B.g, B.b, B.opacity], f: getColour }; } + if (name == "viewBox") { + A = this.attr(name).vb.split(" ").map(Number); + B = b.split(" ").map(Number); + return { + from: A, + to: B, + f: getViewBox + }; + } if (name == "transform" || name == "gradientTransform" || name == "patternTransform") { if (b instanceof Snap.Matrix) { b = b.toTransformString(); } if (!Snap._.rgTransform.test(b)) { @@ -6101,21 +7241,21 @@ to: path2array(A[1]), f: getPath(A[0]) }; } if (name == "points") { - A = Str(a).split(","); - B = Str(b).split(","); + A = Str(a).split(Snap._.separator); + B = Str(b).split(Snap._.separator); return { from: A, to: B, f: function (val) { return val; } }; } var aUnit = a.match(reUnit), bUnit = Str(b).match(reUnit); - if (aUnit && aUnit == bUnit) { + if (aUnit && arrayEqual(aUnit, bUnit)) { return { from: parseFloat(a), to: parseFloat(b), f: getUnit(aUnit) }; @@ -6124,12 +7264,13 @@ from: this.asPX(name), to: this.asPX(name, b), f: getNumber }; } - }; + }); }); + // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -6153,13 +7294,14 @@ touchMap = { mousedown: "touchstart", mousemove: "touchmove", mouseup: "touchend" }, - getScroll = function (xy) { - var name = xy == "y" ? "scrollTop" : "scrollLeft"; - return glob.doc.documentElement[name] || glob.doc.body[name]; + getScroll = function (xy, el) { + var name = xy == "y" ? "scrollTop" : "scrollLeft", + doc = el && el.node ? el.node.ownerDocument : glob.doc; + return doc[name in doc.documentElement ? "documentElement" : "body"][name]; }, preventDefault = function () { this.returnValue = false; }, preventTouch = function () { @@ -6169,70 +7311,47 @@ this.cancelBubble = true; }, stopTouch = function () { return this.originalEvent.stopPropagation(); }, - addEvent = (function () { - if (glob.doc.addEventListener) { - return function (obj, type, fn, element) { - var realName = supportsTouch && touchMap[type] ? touchMap[type] : type, - f = function (e) { - var scrollY = getScroll("y"), - scrollX = getScroll("x"); - if (supportsTouch && touchMap[has](type)) { - for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) { - if (e.targetTouches[i].target == obj || obj.contains(e.targetTouches[i].target)) { - var olde = e; - e = e.targetTouches[i]; - e.originalEvent = olde; - e.preventDefault = preventTouch; - e.stopPropagation = stopTouch; - break; - } - } + addEvent = function (obj, type, fn, element) { + var realName = supportsTouch && touchMap[type] ? touchMap[type] : type, + f = function (e) { + var scrollY = getScroll("y", element), + scrollX = getScroll("x", element); + if (supportsTouch && touchMap[has](type)) { + for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) { + if (e.targetTouches[i].target == obj || obj.contains(e.targetTouches[i].target)) { + var olde = e; + e = e.targetTouches[i]; + e.originalEvent = olde; + e.preventDefault = preventTouch; + e.stopPropagation = stopTouch; + break; } - var x = e.clientX + scrollX, - y = e.clientY + scrollY; - return fn.call(element, e, x, y); - }; - - if (type !== realName) { - obj.addEventListener(type, f, false); + } } + var x = e.clientX + scrollX, + y = e.clientY + scrollY; + return fn.call(element, e, x, y); + }; - obj.addEventListener(realName, f, false); + if (type !== realName) { + obj.addEventListener(type, f, false); + } - return function () { - if (type !== realName) { - obj.removeEventListener(type, f, false); - } + obj.addEventListener(realName, f, false); - obj.removeEventListener(realName, f, false); - return true; - }; - }; - } else if (glob.doc.attachEvent) { - return function (obj, type, fn, element) { - var f = function (e) { - e = e || glob.win.event; - var scrollY = getScroll("y"), - scrollX = getScroll("x"), - x = e.clientX + scrollX, - y = e.clientY + scrollY; - e.preventDefault = e.preventDefault || preventDefault; - e.stopPropagation = e.stopPropagation || stopPropagation; - return fn.call(element, e, x, y); - }; - obj.attachEvent("on" + type, f); - var detacher = function () { - obj.detachEvent("on" + type, f); - return true; - }; - return detacher; - }; - } - })(), + return function () { + if (type !== realName) { + obj.removeEventListener(type, f, false); + } + + obj.removeEventListener(realName, f, false); + return true; + }; + }, drag = [], dragMove = function (e) { var x = e.clientX, y = e.clientY, scrollY = getScroll("y"), @@ -6256,11 +7375,10 @@ } else { e.preventDefault(); } var node = dragi.el.node, o, - glob = Snap._.glob, next = node.nextSibling, parent = node.parentNode, display = node.style.display; // glob.win.opera && parent.removeChild(node); // node.style.display = "none"; @@ -6279,10 +7397,11 @@ dragi; while (i--) { dragi = drag[i]; dragi.el._drag = {}; eve("snap.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e); + eve.off("snap.drag.*." + dragi.el.id); } drag = []; }; /*\ * Element.click @@ -6476,12 +7595,18 @@ if (Snap.is(fn, "function")) { this.events = this.events || []; this.events.push({ name: eventName, f: fn, - unbind: addEvent(this.shape || this.node || glob.doc, eventName, fn, scope || this) + unbind: addEvent(this.node || document, eventName, fn, scope || this) }); + } else { + for (var i = 0, ii = this.events.length; i < ii; i++) if (this.events[i].name == eventName) { + try { + this.events[i].f.call(this); + } catch (e) {} + } } return this; }; Snap["un" + eventName] = elproto["un" + eventName] = function (fn) { @@ -6558,36 +7683,41 @@ * End event and end handler are called in specified context or in context of the element with following parameters: o event (object) DOM event object = (object) @Element \*/ elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) { + var el = this; if (!arguments.length) { var origTransform; - return this.drag(function (dx, dy) { + return el.drag(function (dx, dy) { this.attr({ transform: origTransform + (origTransform ? "T" : "t") + [dx, dy] }); }, function () { origTransform = this.transform().local; }); } function start(e, x, y) { (e.originalEvent || e).preventDefault(); - this._drag.x = x; - this._drag.y = y; - this._drag.id = e.identifier; + el._drag.x = x; + el._drag.y = y; + el._drag.id = e.identifier; !drag.length && Snap.mousemove(dragMove).mouseup(dragUp); - drag.push({el: this, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope}); - onstart && eve.on("snap.drag.start." + this.id, onstart); - onmove && eve.on("snap.drag.move." + this.id, onmove); - onend && eve.on("snap.drag.end." + this.id, onend); - eve("snap.drag.start." + this.id, start_scope || move_scope || this, x, y, e); + drag.push({el: el, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope}); + onstart && eve.on("snap.drag.start." + el.id, onstart); + onmove && eve.on("snap.drag.move." + el.id, onmove); + onend && eve.on("snap.drag.end." + el.id, onend); + eve("snap.drag.start." + el.id, start_scope || move_scope || el, x, y, e); } - this._drag = {}; - draggable.push({el: this, start: start}); - this.mousedown(start); - return this; + function init(e, x, y) { + eve("snap.draginit." + el.id, el, e, x, y); + } + eve.on("snap.draginit." + el.id, start); + el._drag = {}; + draggable.push({el: el, start: start, init: init}); + el.mousedown(init); + return el; }; /* * Element.onDragOver [ method ] ** @@ -6604,18 +7734,20 @@ * Removes all drag event handlers from the given element \*/ elproto.undrag = function () { var i = draggable.length; while (i--) if (draggable[i].el == this) { - this.unmousedown(draggable[i].start); + this.unmousedown(draggable[i].init); draggable.splice(i, 1); eve.unbind("snap.drag.*." + this.id); + eve.unbind("snap.draginit." + this.id); } !draggable.length && Snap.unmousemove(dragMove).unmouseup(dragUp); return this; }; }); + // Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -6632,11 +7764,10 @@ pproto = Paper.prototype, rgurl = /^\s*url\((.+)\)/, Str = String, $ = Snap._.$; Snap.filter = {}; -// SIERRA Paper.filter(): I don't understand the note. Does that mean an HTML should dedicate a separate SVG region for a filter definition? What's the advantage over a DEFS? /*\ * Paper.filter [ method ] ** * Creates a `<filter>` element @@ -6725,43 +7856,62 @@ * Snap.filter.shadow [ method ] ** * Returns an SVG markup string for the shadow filter ** - - dx (number) horizontal shift of the shadow, in pixels - - dy (number) vertical shift of the shadow, in pixels + - dx (number) #optional horizontal shift of the shadow, in pixels + - dy (number) #optional vertical shift of the shadow, in pixels - blur (number) #optional amount of blur - color (string) #optional color of the shadow + - opacity (number) #optional `0..1` opacity of the shadow + * or + - dx (number) #optional horizontal shift of the shadow, in pixels + - dy (number) #optional vertical shift of the shadow, in pixels + - color (string) #optional color of the shadow + - opacity (number) #optional `0..1` opacity of the shadow + * which makes blur default to `4`. Or + - dx (number) #optional horizontal shift of the shadow, in pixels + - dy (number) #optional vertical shift of the shadow, in pixels + - opacity (number) #optional `0..1` opacity of the shadow = (string) filter representation > Usage | var f = paper.filter(Snap.filter.shadow(0, 2, 3)), | c = paper.circle(10, 10, 10).attr({ | filter: f | }); \*/ - Snap.filter.shadow = function (dx, dy, blur, color) { + Snap.filter.shadow = function (dx, dy, blur, color, opacity) { + if (typeof blur == "string") { + color = blur; + opacity = color; + blur = 4; + } + if (typeof color != "string") { + opacity = color; + color = "#000"; + } color = color || "#000"; if (blur == null) { blur = 4; } - if (typeof blur == "string") { - color = blur; - blur = 4; + if (opacity == null) { + opacity = 1; } if (dx == null) { dx = 0; dy = 2; } if (dy == null) { dy = dx; } color = Snap.color(color); - return Snap.format('<feGaussianBlur in="SourceAlpha" stdDeviation="{blur}"/><feOffset dx="{dx}" dy="{dy}" result="offsetblur"/><feFlood flood-color="{color}"/><feComposite in2="offsetblur" operator="in"/><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>', { + return Snap.format('<feGaussianBlur in="SourceAlpha" stdDeviation="{blur}"/><feOffset dx="{dx}" dy="{dy}" result="offsetblur"/><feFlood flood-color="{color}"/><feComposite in2="offsetblur" operator="in"/><feComponentTransfer><feFuncA type="linear" slope="{opacity}"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>', { color: color, dx: dx, dy: dy, - blur: blur + blur: blur, + opacity: opacity }); }; Snap.filter.shadow.toString = function () { return this(); }; @@ -6869,10 +8019,11 @@ \*/ Snap.filter.invert = function (amount) { if (amount == null) { amount = 1; } +// <feColorMatrix type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" color-interpolation-filters="sRGB"/> return Snap.format('<feComponentTransfer><feFuncR type="table" tableValues="{amount} {amount2}"/><feFuncG type="table" tableValues="{amount} {amount2}"/><feFuncB type="table" tableValues="{amount} {amount2}"/></feComponentTransfer>', { amount: amount, amount2: 1 - amount }); }; @@ -6919,7 +8070,101 @@ }; Snap.filter.contrast.toString = function () { return this(); }; }); + +// Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +Snap.plugin(function (Snap, Element, Paper, glob, Fragment) { + var box = Snap._.box, + is = Snap.is, + firstLetter = /^[^a-z]*([tbmlrc])/i, + toString = function () { + return "T" + this.dx + "," + this.dy; + }; + /*\ + * Element.getAlign + [ method ] + ** + * Returns shift needed to align the element relatively to given element. + * If no elements specified, parent `<svg>` container will be used. + - el (object) @optional alignment element + - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"` + = (object|string) Object in format `{dx: , dy: }` also has a string representation as a transformation string + > Usage + | el.transform(el.getAlign(el2, "top")); + * or + | var dy = el.getAlign(el2, "top").dy; + \*/ + Element.prototype.getAlign = function (el, way) { + if (way == null && is(el, "string")) { + way = el; + el = null; + } + el = el || this.paper; + var bx = el.getBBox ? el.getBBox() : box(el), + bb = this.getBBox(), + out = {}; + way = way && way.match(firstLetter); + way = way ? way[1].toLowerCase() : "c"; + switch (way) { + case "t": + out.dx = 0; + out.dy = bx.y - bb.y; + break; + case "b": + out.dx = 0; + out.dy = bx.y2 - bb.y2; + break; + case "m": + out.dx = 0; + out.dy = bx.cy - bb.cy; + break; + case "l": + out.dx = bx.x - bb.x; + out.dy = 0; + break; + case "r": + out.dx = bx.x2 - bb.x2; + out.dy = 0; + break; + default: + out.dx = bx.cx - bb.cx; + out.dy = 0; + break; + } + out.toString = toString; + return out; + }; + /*\ + * Element.align + [ method ] + ** + * Aligns the element relatively to given one via transformation. + * If no elements specified, parent `<svg>` container will be used. + - el (object) @optional alignment element + - way (string) one of six values: `"top"`, `"middle"`, `"bottom"`, `"left"`, `"center"`, `"right"` + = (object) this element + > Usage + | el.align(el2, "top"); + * or + | el.align("middle"); + \*/ + Element.prototype.align = function (el, way) { + return this.transform("..." + this.getAlign(el, way)); + }; +}); + return Snap; -})); \ No newline at end of file +}));