UNPKG

snapsvg

Version:
1,603 lines (1,580 loc) 283 kB
// Snap.svg 0.5.0 // // Copyright (c) 2013 – 2017 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: 2017-02-06 // 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. // ┌────────────────────────────────────────────────────────────┐ \\ // │ Eve 0.5.0 - JavaScript Events Library │ \\ // ├────────────────────────────────────────────────────────────┤ \\ // │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\ // └────────────────────────────────────────────────────────────┘ \\ (function (glob) { var version = "0.5.0", 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]; } } }, objtos = Object.prototype.toString, Str = String, isArray = Array.isArray || function (ar) { return ar instanceof Array || objtos.call(ar) == "[object Array]"; }; /*\ * eve [ method ] * Fires event with given `name`, given scope and other parameters. > Arguments - 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. Array has two methods `.firstDefined()` and `.lastDefined()` to get first or last not `undefined` value. \*/ eve = function (name, scope) { var e = events, oldstop = stop, args = Array.prototype.slice.call(arguments, 2), listeners = eve.listeners(name), z = 0, f = false, l, 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) { queue[listeners[i].zIndex] = listeners[i]; } } indexed.sort(numsort); while (indexed[z] < 0) { l = queue[indexed[z++]]; out.push(l.apply(scope, args)); if (stop) { stop = oldstop; return out; } } for (i = 0; i < ii; i++) { l = listeners[i]; if ("zIndex" in l) { if (l.zIndex == indexed[z]) { out.push(l.apply(scope, args)); if (stop) { break; } do { z++; l = queue[indexed[z]]; l && out.push(l.apply(scope, args)); if (stop) { break; } } while (l) } else { queue[l.zIndex] = l; } } else { out.push(l.apply(scope, args)); if (stop) { break; } } } stop = oldstop; current_event = ce; return out; }; // 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`. > Arguments - name (string) name of the event, dot (`.`) or slash (`/`) separated = (array) array of event handlers \*/ eve.listeners = function (name) { var names = isArray(name) ? name : name.split(separator), e = events, item, items, k, i, ii, j, jj, nes, es = [e], out = []; for (i = 0, ii = names.length; i < ii; i++) { nes = []; for (j = 0, jj = es.length; j < jj; j++) { e = es[j].n; items = [e[names[i]], e[wildcard]]; k = 2; while (k--) { item = items[k]; if (item) { nes.push(item); out = out.concat(item.f || []); } } } es = nes; } return out; }; /*\ * eve.separator [ method ] * If for some reasons you don’t like default separators (`.` or `/`) you can specify yours * here. Be aware that if you pass a string longer than one character it will be treated as * a list of characters. - separator (string) new separator. Empty string resets to default: `.` or `/`. \*/ eve.separator = function (sep) { if (sep) { sep = Str(sep).replace(/(?=[\.\^\]\[\-])/g, "\\"); sep = "[" + sep + "]"; separator = new RegExp(sep); } else { separator = /[\.\/]/; } }; /*\ * eve.on [ method ] ** * Binds given event handler with a given name. You can use wildcards “`*`” for the names: | eve.on("*.under.*", f); | eve("mouse.under.floor"); // triggers f * Use @eve to trigger the listener. ** - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards - f (function) event handler function ** - name (array) if you don’t want to use separators, you can use array of strings - f (function) event handler function ** = (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`. * * 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) { if (typeof f != "function") { return function () {}; } var names = isArray(name) ? (isArray(name[0]) ? name : [name]) : Str(name).split(comaseparator); for (var i = 0, ii = names.length; i < ii; i++) { (function (name) { var names = isArray(name) ? name : Str(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])); } return function (zIndex) { if (+zIndex == +zIndex) { f.zIndex = +zIndex; } }; }; /*\ * 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 - 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.stop [ method ] ** * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing. \*/ eve.stop = function () { stop = 1; }; /*\ * eve.nt [ method ] ** * Could be used inside event handler to figure out actual name of the event. ** > Arguments ** - subname (string) #optional subname of the event ** = (string) name of the event, if `subname` is not specified * or = (boolean) `true`, if current event’s name contains `subname` \*/ eve.nt = function (subname) { var cur = isArray(current_event) ? current_event.join(".") : current_event; if (subname) { return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(cur); } return cur; }; /*\ * eve.nts [ method ] ** * Could be used inside event handler to figure out actual name of the event. ** ** = (array) names of the event \*/ eve.nts = function () { return isArray(current_event) ? current_event : current_event.split(separator); }; /*\ * 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. ** > Arguments ** - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards - f (function) event handler function \*/ /*\ * eve.unbind [ method ] ** * See @eve.off \*/ eve.off = eve.unbind = function (name, f) { if (!name) { eve._events = events = {n: {}}; return; } var names = isArray(name) ? (isArray(name[0]) ? name : [name]) : Str(name).split(comaseparator); if (names.length > 1) { for (var i = 0, ii = names.length; i < ii; i++) { eve.off(names[i], f); } return; } names = isArray(name) ? name : Str(name).split(separator); var e, key, splice, i, ii, j, jj, cur = [events], inodes = []; for (i = 0, ii = names.length; i < ii; i++) { for (j = 0; j < cur.length; j += splice.length - 2) { splice = [j, 1]; e = cur[j].n; if (names[i] != wildcard) { if (e[names[i]]) { splice.push(e[names[i]]); inodes.unshift({ n: e, name: names[i] }); } } else { for (key in e) if (e[has](key)) { splice.push(e[key]); inodes.unshift({ n: e, name: key }); } } cur.splice.apply(cur, splice); } } for (i = 0, ii = cur.length; i < ii; i++) { e = cur[i]; while (e.n) { if (f) { if (e.f) { for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) { e.f.splice(j, 1); break; } !e.f.length && delete e.f; } for (key in e.n) if (e.n[has](key) && e.n[key].f) { var funcs = e.n[key].f; for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) { funcs.splice(j, 1); break; } !funcs.length && delete e.n[key].f; } } else { delete e.f; for (key in e.n) if (e.n[has](key) && e.n[key].f) { delete e.n[key].f; } } e = e.n; } } // prune inner nodes in path prune: for (i = 0, ii = inodes.length; i < ii; i++) { e = inodes[i]; for (key in e.n[e.name].f) { // not empty (has listeners) continue prune; } for (key in e.n[e.name].n) { // not empty (has children) continue prune; } // is empty delete e.n[e.name]; } }; /*\ * eve.once [ method ] ** * Binds given event handler with a given name to only run once then unbind itself. | eve.once("login", f); | eve("login"); // triggers f | eve("login"); // no listeners * Use @eve to trigger the listener. ** > Arguments ** - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards - f (function) event handler function ** = (function) same return function as @eve.on \*/ eve.once = function (name, f) { var f2 = function () { eve.off(name, f2); return f.apply(this, arguments); }; return eve.on(name, f2); }; /*\ * eve.version [ property (string) ] ** * Current version of the library. \*/ eve.version = version; eve.toString = function () { return "You are running Eve " + version; }; (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) { // Define as an anonymous module 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); } }(window || this, function (window, eve) { // Copyright (c) 2017 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. var mina = (function (eve) { var animations = {}, requestAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { setTimeout(callback, 16, new Date().getTime()); return true; }, requestID, isArray = Array.isArray || function (a) { return a instanceof Array || Object.prototype.toString.call(a) == "[object Array]"; }, idgen = 0, idprefix = "M" + (+new Date).toString(36), ID = function () { return idprefix + (idgen++).toString(36); }, diff = function (a, b, A, B) { if (isArray(a)) { res = []; for (var i = 0, ii = a.length; i < ii; i++) { res[i] = diff(a[i], b, A[i], B); } return res; } var dif = (A - a) / (B - b); return function (bb) { return a + dif * (bb - b); }; }, timer = Date.now || function () { return +new Date; }, sta = function (val) { var a = this; if (val == null) { return a.s; } var ds = a.s - val; a.b += a.dur * ds; a.B += a.dur * ds; a.s = val; }, speed = function (val) { var a = this; if (val == null) { return a.spd; } a.spd = val; }, duration = function (val) { var a = this; if (val == null) { return a.dur; } a.s = a.s * val / a.dur; 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) { return; } a.b = a.get() - a.pdif; delete a.pdif; animations[a.id] = a; frame(); }, 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 (timeStamp) { // Manual invokation? if (!timeStamp) { // Frame loop stopped? if (!requestID) { // Start frame loop... requestID = requestAnimFrame(frame); } return; } var len = 0; for (var i in animations) if (animations.hasOwnProperty(i)) { var a = animations[i], b = a.get(), res; len++; a.s = (b - a.b) / (a.dur / a.spd); if (a.s >= 1) { delete animations[i]; a.s = 1; len--; (function (a) { setTimeout(function () { eve("mina.finish." + a.id, a); }); }(a)); } a.update(); } requestID = len ? requestAnimFrame(frame) : false; }, /*\ * mina [ method ] ** * Generic animation of numbers ** - a (number) start _slave_ number - A (number) end _slave_ number - b (number) start _master_ number (start time in general case) - B (number) end _master_ number (end time in general case) - get (function) getter of _master_ number (see @mina.time) - set (function) setter of _slave_ number - easing (function) #optional easing function, default is @mina.linear = (object) animation descriptor o { o id (string) animation id, o start (number) start _slave_ number, o end (number) end _slave_ number, o b (number) start _master_ number, o s (number) animation status (0..1), o dur (number) animation duration, o spd (number) animation speed, o get (function) getter of _master_ number (see @mina.time), o set (function) setter of _slave_ number, 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(), start: a, end: A, b: b, s: 0, dur: B - b, spd: 1, get: get, set: set, easing: easing || mina.linear, status: sta, speed: speed, duration: duration, stop: stopit, pause: pause, resume: resume, update: update }; animations[anim.id] = anim; var len = 0, i; for (i in animations) if (animations.hasOwnProperty(i)) { len++; if (len == 2) { break; } } len == 1 && frame(); return anim; }; /*\ * mina.time [ method ] ** * Returns the current time. Equivalent to: | function () { | return (new Date).getTime(); | } \*/ mina.time = timer; /*\ * mina.getById [ method ] ** * Returns an animation by its id - id (string) animation's id = (object) See @mina \*/ mina.getById = function (id) { return animations[id] || null; }; /*\ * mina.linear [ method ] ** * Default linear easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.linear = function (n) { return n; }; /*\ * mina.easeout [ method ] ** * Easeout easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.easeout = function (n) { return Math.pow(n, 1.7); }; /*\ * mina.easein [ method ] ** * Easein easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.easein = function (n) { return Math.pow(n, .48); }; /*\ * mina.easeinout [ method ] ** * Easeinout easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.easeinout = function (n) { if (n == 1) { return 1; } if (n == 0) { return 0; } var q = .48 - n / 1.04, Q = Math.sqrt(.1734 + q * q), x = Q - q, X = Math.pow(Math.abs(x), 1 / 3) * (x < 0 ? -1 : 1), y = -Q - q, Y = Math.pow(Math.abs(y), 1 / 3) * (y < 0 ? -1 : 1), t = X + Y + .5; return (1 - t) * 3 * t * t + t * t * t; }; /*\ * mina.backin [ method ] ** * Backin easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.backin = function (n) { if (n == 1) { return 1; } var s = 1.70158; return n * n * ((s + 1) * n - s); }; /*\ * mina.backout [ method ] ** * Backout easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.backout = function (n) { if (n == 0) { return 0; } n = n - 1; var s = 1.70158; return n * n * ((s + 1) * n + s) + 1; }; /*\ * mina.elastic [ method ] ** * Elastic easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.elastic = function (n) { if (n == !!n) { return n; } return Math.pow(2, -10 * n) * Math.sin((n - .075) * (2 * Math.PI) / .3) + 1; }; /*\ * mina.bounce [ method ] ** * Bounce easing - n (number) input 0..1 = (number) output 0..1 \*/ mina.bounce = function (n) { var s = 7.5625, p = 2.75, l; if (n < 1 / p) { l = s * n * n; } else { if (n < 2 / p) { n -= 1.5 / p; l = s * n * n + .75; } else { if (n < 2.5 / p) { n -= 2.25 / p; l = s * n * n + .9375; } else { n -= 2.625 / p; l = s * n * n + .984375; } } } return l; }; window.mina = mina; return mina; })(typeof eve == "undefined" ? function () {} : eve); // Copyright (c) 2013 - 2017 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. var Snap = (function(root) { Snap.version = "0.5.1"; /*\ * Snap [ method ] ** * Creates a drawing surface or wraps existing SVG element. ** - 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.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) { // try { w = glob.doc.querySelector(String(w)); return wrap(w); // } catch (e) { // return null; // } } } w = w == null ? "100%" : w; h = h == null ? "100%" : h; return new Paper(w, h); } Snap.toString = function () { return "Snap v" + this.version; }; Snap._ = {}; var glob = { win: root.window, doc: root.window.document }; Snap._.glob = glob; var has = "hasOwnProperty", Str = String, toFloat = parseFloat, toInt = parseInt, math = Math, mmax = math.max, mmin = math.min, abs = math.abs, pow = math.pow, PI = math.PI, round = math.round, E = "", S = " ", 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\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/, separator = Snap._.separator = /[,\s]+/, whitespace = /[\s]/g, commaSpaces = /[\s]*,[\s]*/, hsrg = {hs: 1, rg: 1}, 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 (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 = {}, /*\ * Snap.url [ method ] ** * Wraps path into `"url('<path>')"`. - value (string) path = (string) wrapped path \*/ 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 (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 (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.removeAttribute(key); } } } else if ("text" in attr) { el.nodeValue = attr.text; } } else { el = glob.doc.createElementNS(xmlns, el); } return el; } Snap._.$ = $; Snap._.id = ID; function getAttrs(el) { var attrs = el.attributes, name, out = {}; for (var i = 0; i < attrs.length; i++) { if (attrs[i].namespaceURI == xlink) { name = "xlink:"; } else { name = ""; } name += attrs[i].name; out[name] = attrs[i].textContent; } return out; } function is(o, type) { type = Str.prototype.toLowerCase.call(type); if (type == "finite") { return isFinite(o); } if (type == "array" && (o instanceof Array || Array.isArray && Array.isArray(o))) { return true; } return type == "null" && o === null || type == typeof o && o !== null || type == "object" && o === Object(o) || objectToString.call(o).slice(8, -1).toLowerCase() == type; } /*\ * Snap.format [ method ] ** * Replaces construction of type `{<name>}` to the corresponding argument ** - token (string) string to format - json (object) object which properties are used as a replacement = (string) formatted string > Usage | // this draws a rectangular shape equivalent to "M10,20h40v50h-40z" | paper.path(Snap.format("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", { | x: 10, | y: 20, | dim: { | width: 40, | height: 50, | "negative width": -40 | } | })); \*/ Snap.format = (function () { var tokenRegex = /\{([^\}]+)\}/g, objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties replacer = function (all, key, obj) { var res = obj; key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) { name = name || quotedName; if (res) { if (name in res) { res = res[name]; } typeof res == "function" && isFunc && (res = res()); } }); res = (res == null || res == obj ? all : res) + ""; return res; }; return function (str, obj) { return Str(str).replace(tokenRegex, function (all, key) { return replacer(all, key, obj); }); }; })(); function clone(obj) { if (typeof obj == "function" || Object(obj) !== obj) { return obj; } var res = new obj.constructor; for (var key in obj) if (obj[has](key)) { res[key] = clone(obj[key]); } return res; } Snap._.clone = clone; function repush(array, item) { for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) { return array.push(array.splice(i, 1)[0]); } } function cacher(f, scope, postprocessor) { function newf() { var arg = Array.prototype.slice.call(arguments, 0), args = arg.join("\u2400"), cache = newf.cache = newf.cache || {}, count = newf.count = newf.count || []; if (cache[has](args)) { repush(count, args); return postprocessor ? postprocessor(cache[args]) : cache[args]; } count.length >= 1e3 && delete cache[count.shift()]; count.push(args); cache[args] = f.apply(scope, arg); return postprocessor ? postprocessor(cache[args]) : cache[args]; } return newf; } Snap._.cacher = cacher; function angle(x1, y1, x2, y2, x3, y3) { if (x3 == null) { var x = x1 - x2, y = y1 - y2; if (!x && !y) { return 0; } return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360; } else { return angle(x1, y1, x3, y3) - angle(x2, y2, x3, y3); } } function rad(deg) { return deg % 360 * PI / 180; } function deg(rad) { return rad * 180 / PI % 360; } function x_y() { return this.x + S + this.y; } function x_y_w_h() { return this.x + S + this.y + S + this.width + " \xd7 " + this.height; } /*\ * Snap.rad [ method ] ** * Transform angle to radians - deg (number) angle in degrees = (number) angle in radians \*/ Snap.rad = rad; /*\ * Snap.deg [ method ] ** * Transform angle to degrees - rad (number) angle in radians = (number) angle in degrees \*/ Snap.deg = deg; /*\ * 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 - 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 - x3 (number) #optional x coord of third point - y3 (number) #optional y coord of third point = (number) angle in degrees \*/ Snap.angle = angle; /*\ * Snap.len [ method ] ** * Returns distance between two points - 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 - 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. - 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 - type (string) name of the type, e.g., `string`, `function`, `number`, etc. = (boolean) `true` if given value is of given type \*/ Snap.is = is; /*\ * Snap.snapTo [ method ] ** * Snaps given value to given grid - values (array|number) given array of values or step of the grid - value (number) value to adjust - tolerance (number) #optional maximum distance to the target value that would trigger the snap. Default is `10`. = (number) adjusted value \*/ Snap.snapTo = function (values, value, tolerance) { tolerance = is(tolerance, "finite") ? tolerance : 10; if (is(values, "array")) { var i = values.length; while (i--) if (abs(values[i] - value) <= tolerance) { return values[i]; } } else { values = +values; var rem = value % values; if (rem < tolerance) { return value - rem; } if (rem > values - tolerance) { return value - rem + values; } } return value; }; // Colour /*\ * Snap.getRGB [ method ] ** * Parses color string as RGB object - color (string) color string in one of the following formats: # <ul> # <li>Color name (<code>red</code>, <code>green</code>, <code>cornflowerblue</code>, etc)</li> # <li>#••• — shortened HTML color: (<code>#000</code>, <code>#fc0</code>, etc.)</li> # <li>#•••••• — full length HTML color: (<code>#000000</code>, <code>#bd2300</code>)</li> # <li>rgb(•••, •••, •••) — red, green and blue channels values: (<code>rgb(200,&nbsp;100,&nbsp;0)</code>)</li> # <li>rgba(•••, •••, •••, •••) — also with opacity</li> # <li>rgb(•••%, •••%, •••%) — same as above, but in %: (<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>)</li> # <li>rgba(•••%, •••%, •••%, •••%) — also with opacity</li> # <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>)</li> # <li>hsba(•••, •••, •••, •••) — also with opacity</li> # <li>hsb(•••%, •••%, •••%) — same as above, but in %</li> # <li>hsba(•••%, •••%, •••%, •••%) — also with opacity</li> # <li>hsl(•••, •••, •••) — hue, saturation and luminosity values: (<code>hsb(0.5,&nbsp;0.25,&nbsp;0.5)</code>)</li> # <li>hsla(•••, •••, •••, •••) — also with opacity</li> # <li>hsl(•••%, •••%, •••%) — same as above, but in %</li> # <li>hsla(•••%, •••%, •••%, •••%) — also with opacity</li> # </ul> * Note that `%` can be used any time: `rgb(20%, 255, 50%)`. = (object) RGB object in the following format: o { o r (number) red, o g (number) green, o b (number) blue, o hex (string) color in HTML/CSS format: #••••••, o error (boolean) true if string can't be parsed o } \*/ Snap.getRGB = cacher(function (colour) { if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) { return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString}; } if (colour == "none") { return {r: -1, g: -1, b: -1, hex: "none", toString: rgbtoString}; } !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour)); if (!colour) { return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString}; } var res, red, green, blue, opacity, t, values, rgb = colour.match(colourRegExp); if (rgb) { if (rgb[2]) { blue = toInt(rgb[2].substring(5), 16); green = toInt(rgb[2].substring(3, 5), 16); red = toInt(rgb[2].substring(1, 3), 16); } if (rgb[3]) { blue = toInt((t = rgb[3].charAt(3)) + t, 16); green = toInt((t = rgb[3].charAt(2)) + t, 16); red = toInt((t = rgb[3].charAt(1)) + t, 16); } if (rgb[4]) { values = rgb[4].split(commaSpaces); red = toFloat(values[0]); values[0].slice(-1) == "%" && (red *= 2.55); green = toFloat(values[1]); values[1].slice(-1) == "%" && (green *= 2.55); blue = toFloat(values[2]); values[2].slice(-1) == "%" && (blue *= 2.55); rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3])); values[3] && values[3].slice(-1) == "%" && (opacity /= 100); } if (rgb[5]) { values = rgb[5].split(commaSpaces); red = toFloat(values[0]); values[0].slice(-1) == "%" && (red /= 100); green = toFloat(values[1]); values[1].slice(-1) == "%" && (green /= 100); blue = toFloat(values[2]); values[2].slice(-1) == "%" && (blue /= 100); (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360); rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3])); values[3] && values[3].slice(-1) == "%" && (opacity /= 100); return Snap.hsb2rgb(red, green, blue, opacity); } if (rgb[6]) { values = rgb[6].split(commaSpaces); red = toFloat(values[0]); values[0].slice(-1) == "%" && (red /= 100); green = toFloat(values[1]); values[1].slice(-1) == "%" && (green /= 100); blue = toFloat(values[2]); values[2].slice(-1) == "%" && (blue /= 100); (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360); rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3])); values[3] && values[3].slice(-1) == "%" && (opacity /= 100); return Snap.hsl2rgb(red, green, blue, opacity); } red = mmin(math.round(red), 255); green = mmin(math.round(green), 255); blue = mmin(math.round(blue), 255); opacity = mmin(mmax(opacity, 0), 1); rgb = {r: red, g: green, b: blue, toString: rgbtoString}; rgb.hex = "#" + (16777216 | blue | green << 8 | red << 16).toString(16).slice(1); rgb.opacity = is(opacity, "finite") ? opacity : 1; return rgb; } return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: rgbtoString}; }, Snap); /*\ * Snap.hsb [ method ] ** * Converts HSB values to a hex representation of the color - h (number) hue - s (number) saturation - b (number) value or brightness = (string) hex representation of the color \*/ Snap.hsb = cacher(function (h, s, b) { return Snap.hsb2rgb(h, s, b).hex; }); /*\ * Snap.hsl [ method ] ** * Converts HSL values to a hex representation of the color - h (number) hue - s (number) saturation - l (number) luminosity = (string) hex representation of the color \*/ Snap.hsl = cacher(function (h, s, l) { return Snap.hsl2rgb(h, s, l).hex; }); /*\ * Snap.rgb [ method ] ** * Converts RGB values to a hex representation of the color - r (number) red - g (number) green - b (number) blue = (string) hex representation of the color \*/ Snap.rgb = cacher(function (r, g, b, o) { if (is(o, "finite")) { var round = math.round; 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] || glob.doc.getElementsByTagName("svg")[0], red = "rgb(255, 0, 0)"; toHex = cacher(function (color) { if (color.toLowerCase() == "red") { return red; } i.style.color = red; i.style.color = color; var out = glob.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color"); return out == red ? null : out; }); return toHex(color); }, hsbtoString = function () { return "hsb(" + [this.h, this.s, this.b] + ")"; }, hsltoString = function () { return "hsl(" + [this.h, this.s, this.l] + ")"; }, rgbtoString = function () { return this.opacity == 1 || this.opacity == null ? this.hex : "rgba(" + [this.r, this.g, this.b, this.opacity] + ")"; }, prepareRGB = function (r, g, b) { if (g == null && is(r, "object") && "r" in r && "g" in r && "b" in r) { b = r.b; g = r.g; r = r.r; } if (g == null && is(r, string)) { var clr = Snap.getRGB(r); r = clr.r; g = clr.g; b = clr.b; } if (r > 1 || g > 1 || b > 1) { r /= 255; g /= 255; b /= 255; } return [r, g, b]; }, packageRGB = function (r, g, b, o) { r = math.round(r * 255); g = math.round(g * 255); b = math.round(b * 255); var rgb = { r: r, g: g, b: b, opacity: is(o, "finite") ? o : 1, hex: Snap.rgb(r, g, b), toString: rgbtoStri