snap.js/savage.js

5650 lines
176 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// .--.--.
// / / '.
// | : /`. /
// ; | |--` .---. ,----._,.
// | : ;_ ,--.--. /. ./| ,--.--. / / ' / ,---.
// \ \ `. / \ .-' . ' | / \ | : | / \
// `----. \.--. .-. /___/ \: | .--. .-. || | .\ . / / |
// __ \ \ | \__\/: . . \ ' . \__\/: . .. ; '; |. ' / |
// / /`--' / ," .--.; |\ \ ' ," .--.; |' . . |' ; /|
// '--'. / / / ,. | \ \ / / ,. | `---`-'| |' | / |
// `--'---' ; : .' \ \ \ |; : .' \.'__/\_: || : |
// | , .-./ '---" | , .-./| : : \ \ /
// `--`---' `--`---' \ \ / `----'
// `--`-'
// Savage 0.0.1
//
// 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.
//
// build: 2013-08-15
// 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.4.2 - JavaScript Events Library │ \\
// ├────────────────────────────────────────────────────────────┤ \\
// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
// └────────────────────────────────────────────────────────────┘ \\
(function (glob) {
var version = "0.4.2",
has = "hasOwnProperty",
separator = /[\.\/]/,
wildcard = "*",
fun = function () {},
numsort = function (a, b) {
return a - b;
},
current_event,
stop,
events = {n: {}},
/*\
* 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
\*/
eve = function (name, scope) {
name = String(name);
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 = [];
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.length ? out : null;
};
// 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 = 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.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.
**
> Arguments
**
- name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
- 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 dont need to worry about z-index, but its 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;
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) {
return fun;
}
e.f.push(f);
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 events name contains `subname`
\*/
eve.nt = function (subname) {
if (subname) {
return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
}
return current_event;
};
/*\
* 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 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 = name.split(separator),
e,
key,
splice,
i, ii, j, jj,
cur = [events];
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]]);
}
} else {
for (key in e) if (e[has](key)) {
splice.push(e[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;
}
}
};
/*\
* 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.unbind(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 != "undefined" ? (define("eve", [], function() { return eve; })) : (glob.eve = eve));
})(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.
window.mina = (function () {
var animations = [],
requestAnimFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
setTimeout(callback, 16);
},
isArray = Array.isArray || function (a) {
return a instanceof Array ||
Object.prototype.toString.call(a) == "[object Array]";
},
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 = 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;
},
frame = function () {
for (var i = 0, ii = animations.length; i < ii; i++) {
var a = animations[i],
b = a.get(),
res;
a.s = (b - a.b) / (a.dur / a.spd);
if (a.s >= 1) {
animations.splice(i, 1);
a.s = 1;
}
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);
}
animations.length && requestAnimFrame(frame);
},
mina = function (a, A, b, B, get, set, easing) {
var anim = {
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
};
animations.push(anim);
animations.length == 1 && requestAnimFrame(frame);
return anim;
};
mina.time = timer;
mina.linear = function (n) {
return n;
};
mina.easeout = function (n) {
return Math.pow(n, 1.7);
};
mina.easein = function (n) {
return Math.pow(n, .48);
};
mina.easeinout = function (n) {
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 = function (n) {
var s = 1.70158;
return n * n * ((s + 1) * n - s);
};
mina.backout = function (n) {
n = n - 1;
var s = 1.70158;
return n * n * ((s + 1) * n + s) + 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 = 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;
};
return mina;
})();
/*
* Elemental 0.2.1 - Simple JavaScript Tag Parser
*
* Copyright (c) 2010 Dmitry Baranovskiy (http://dmitry.baranovskiy.com/)
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
*/
(function () {
function parse(s) {
s = s || Object(s);
var pos = 1,
len = s.length + 1,
p, c, n = at(s, 0);
for (;pos < len; pos++) {
p = c;
c = n;
n = at(s, pos);
this.raw += c;
step.call(this, c, n, p);
}
this._beforeEnd = function () {
step.call(this, "", "", c);
};
return this;
}
function at(s, i) {
return s && (s.charAt ? s.charAt(i) : s[i]);
}
function on(name, f) {
this.events = this.events || {};
this.events[name] = this.events[name] || [];
this.events[name].push(f);
}
function event(name, data, extra) {
if (typeof eve == "function") {
eve("elemental." + name + "." + data, null, data, extra || "", this.raw);
}
var a = this.events && this.events[name],
i = a && a.length;
while (i--) try {
this.events[name][i](data, extra || "", this.raw);
} catch (e) {}
this.raw = "";
}
function end() {
step.call(this, "eof");
// this._beforeEnd && this._beforeEnd();
// this.raw && this.event("text", this.raw);
// this.mode = "text";
// this.textchunk = "";
// delete this._beforeEnd;
this.event("eof");
}
var whitespace = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]/,
fireAttrEvent = function () {
for (var key in this.attr) if (this.attr.hasOwnProperty(key)) {
this.event("attr", key, {
value: this.attr[key],
tagname: this.tagname,
attr: this.attr
});
}
},
act = {
text: function (c, n, p) {
switch (c) {
case "<":
case "eof":
this.nodename = "";
this.attr = {};
this.mode = "tag name start";
this.raw = this.raw.slice(0, -1);
this.textchunk && this.event("text", this.textchunk);
this.raw += c;
this.textchunk = "";
break;
default:
this.textchunk += c;
break;
}
},
special: function (c, n, p) {
if (p == "!" && c == "-" && n == "-") {
this.mode = "comment start";
return;
}
if (this.textchunk == "[CDATA" && c == "[") {
this.mode = "cdata";
this.textchunk = "";
return;
}
if (c == ">" || c == "eof") {
this.event("special", this.textchunk);
this.mode = "text";
this.textchunk = "";
return;
}
this.textchunk += c;
},
cdata: function (c, n, p) {
if (p == "]" && c == "]" && n == ">") {
this.mode = "cdata end";
this.textchunk = this.textchunk.slice(0, -1);
return;
}
if (c == "eof") {
act["cdata end"].call(this);
}
this.textchunk += c;
},
"cdata end": function (c, n, p) {
this.event("cdata", this.textchunk);
this.textchunk = "";
this.mode = "text";
},
"comment start": function (c, n, p) {
if (n == ">" || c == "eof") {
this.event("comment", "");
this.mode = "comment instant end";
} else {
this.mode = "comment";
}
},
"comment instant end": function (c, n, p) {
this.mode = "text";
},
comment: function (c, n, p) {
if (c == "-" && p == "-" && n == ">") {
this.mode = "comment end";
this.textchunk = this.textchunk.slice(0, -1);
} else if (c == "eof") {
this.event("comment", this.textchunk);
} else {
this.textchunk += c;
}
},
"comment end": function (c, n, p) {
this.event("comment", this.textchunk);
this.textchunk = "";
this.mode = "text";
},
declaration: function (c, n, p) {
if (c == "?" && n == ">") {
this.mode = "declaration end";
return;
}
if (c == "eof") {
this.event("comment", this.textchunk);
}
this.textchunk += c;
},
"declaration end": function (c, n, p) {
this.event("comment", this.textchunk);
this.textchunk = "";
this.mode = "text";
},
"tag name start": function (c, n, p) {
if (c == "eof") {
this.event("text", "<");
return;
}
if (!whitespace.test(c)) {
this.mode = "tag name";
if (c == "/") {
this.mode = "close tag name start";
return;
} else if (c == "!") {
this.mode = "special";
this.textchunk = "";
return;
} else if (c == "?") {
this.mode = "declaration";
return;
}
act[this.mode].call(this, c, n, p);
}
},
"close tag name start": function (c, n, p) {
if (!whitespace.test(c)) {
this.mode = "close tag name";
this.tagname = "";
this.nodename = "";
act[this.mode].call(this, c, n, p);
}
},
"close tag name": function (c, n, p) {
if (whitespace.test(c)) {
this.tagname = this.nodename;
} else switch (c) {
case ">":
this.event("/tag", (this.tagname || this.nodename));
this.mode = "text";
break;
default:
!this.tagname && (this.nodename += c);
break;
}
},
"tag name": function (c, n, p) {
if (whitespace.test(c)) {
this.tagname = this.nodename;
this.nodename = "";
this.mode = "attr start";
} else switch (c) {
case ">":
this.event("tag", this.nodename);
this.mode = "text";
break;
default:
this.nodename += c;
break;
}
},
"attr start": function (c, n, p) {
if (!whitespace.test(c)) {
this.mode = "attr";
this.nodename = "";
act[this.mode].call(this, c, n, p);
}
},
attr: function (c, n, p) {
if (whitespace.test(c) || c == "=") {
this.attr[this.nodename] = "";
this.mode = "attr value start";
} else switch (c) {
case ">":
if (this.nodename == "/") {
delete this.attr["/"];
this.event("tag", this.tagname, this.attr);
fireAttrEvent.call(this);
this.event("/tag", this.tagname, true);
} else {
this.nodename && (this.attr[this.nodename] = "");
this.event("tag", this.tagname, this.attr);
fireAttrEvent.call(this);
}
this.mode = "text";
break;
default:
this.nodename += c;
break;
}
},
"attr value start": function (c, n, p) {
if (!whitespace.test(c)) {
this.mode = "attr value";
this.quote = false;
if (c == "'" || c == '"') {
this.quote = c;
return;
}
act[this.mode].call(this, c, n, p);
}
},
"attr value": function (c, n, p) {
if (whitespace.test(c) && !this.quote) {
this.mode = "attr start";
} else if (c == ">" && !this.quote) {
this.event("tag", this.tagname, this.attr);
this.mode = "text";
} else switch (c) {
case '"':
case "'":
if (this.quote == c && p != "\\") {
this.mode = "attr start";
}
break;
default:
this.attr[this.nodename] += c;
break;
}
}
};
function step(c, n, p) {
c == "\n" && this.event("newline");
act[this.mode].call(this, c, n, p);
}
function elemental(type) {
var out = function (s) {
out.parse(s);
};
out.mode = "text";
out.type = String(type || "html").toLowerCase();
out.textchunk = "";
out.raw = "";
out.parse = parse;
out.on = on;
out.event = event;
out.end = end;
return out;
}
elemental.version = "0.2.1";
(typeof exports == "undefined" ? this : exports).elemental = elemental;
})();
var $VG, Savage = $VG = (function () {
Savage.version = "0.0.1";
function Savage(w, h) {
if (w) {
if (w.tagName) {
return wrap(w);
}
if (w instanceof Element) {
return w;
}
if (h == null) {
w = glob.doc.querySelector(w);
return wrap(w);
}
}
w = w == null ? "100%" : w;
h = h == null ? "100%" : h;
return new Paper(w, h);
}
Savage.toString = function () {
return "Savage v" + this.version;
};
Savage._ = {};
var glob = {
win: window,
doc: window.document
};
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,
isnan = {"NaN": 1, "Infinity": 1, "-Infinity": 1},
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 + "]*"),
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"),
idgen = 0,
idprefix = "S" + (+new Date).toString(36),
ID = function () {
return idprefix + (idgen++).toString(36);
},
xlink = "http://www.w3.org/1999/xlink",
hub = {};
function $(el, attr) {
if (attr) {
if (typeof el == "string") {
el = $(el);
}
if (typeof attr == "string") {
if (attr.substring(0, 6) == "xlink:") {
return el.getAttributeNS(xlink, attr.substring(6));
}
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 {
el.setAttribute(key, val);
}
} else {
el.removeAttribute(key);
}
}
} else {
el = glob.doc.createElementNS("http://www.w3.org/2000/svg", el);
// el.style && (el.style.webkitTapHighlightColor = "rgba(0,0,0,0)");
}
return el;
}
Savage._.$ = $;
Savage._.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 !isnan[has](+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;
}
/*\
* Savage.format
[ method ]
**
* Replaces construction of type “`{<name>}`” to the corresponding argument.
**
> Parameters
**
- token (string) string to format
- json (object) object which properties will be used as a replacement
= (string) formated string
> Usage
| // this will draw a rectangular shape equivalent to "M10,20h40v50h-40z"
| paper.path(Savage.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
| }
| }));
\*/
Savage.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);
});
};
})();
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;
for (var key in obj) if (obj[has](key)) {
res[key] = clone(obj[key]);
}
return res;
}
Savage._.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;
}
Savage._.cacher = cacher;
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;
}
/*\
* Raphael.rad
[ method ]
**
* Transform angle to radians
> Parameters
- deg (number) angle in degrees
= (number) angle in radians.
\*/
Savage.rad = rad;
/*\
* Raphael.deg
[ method ]
**
* Transform angle to degrees
> Parameters
- deg (number) angle in radians
= (number) angle in degrees.
\*/
Savage.deg = deg;
/*\
* Savage.is
[ method ]
**
* Handfull replacement for `typeof` operator.
> Parameters
- o (…) any object or primitive
- type (string) name of the type, i.e. “string”, “function”, “number”, etc.
= (boolean) is given value is of given type
\*/
Savage.is = is;
/*\
* Savage.snapTo
[ method ]
**
* Snaps given value to given grid.
> Parameters
- values (array|number) given array of values or step of the grid
- value (number) value to adjust
- tolerance (number) #optional tolerance for snapping. Default is `10`.
= (number) adjusted value.
\*/
Savage.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;
};
// 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 given matrix to existing one.
> Parameters
- 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 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 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
> Parameters
- x (number)
- y (number)
\*/
matrixproto.translate = function (x, y) {
return this.add(1, 0, 0, 1, x, y);
};
/*\
* Matrix.scale
[ method ]
**
* Scales the matrix
> Parameters
- x (number)
- y (number) #optional
- cx (number) #optional
- cy (number) #optional
\*/
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
> Parameters
- a (number)
- x (number)
- y (number)
\*/
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 ]
**
* Return x coordinate for given point after transformation described by the matrix. See also @Matrix.y
> Parameters
- x (number)
- y (number)
= (number) x
\*/
matrixproto.x = function (x, y) {
return x * this.a + y * this.c + this.e;
};
/*\
* Matrix.y
[ method ]
**
* Return y coordinate for given point after transformation described by the matrix. See also @Matrix.x
> Parameters
- 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 ]
**
* Return 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);
/*\
* Raphael.Matrix
[ method ]
**
* Utility method
**
* Returns matrix based on given parameters.
> Parameters
- a (number)
- b (number)
- c (number)
- d (number)
- e (number)
- f (number)
* or
- svgMatrix (SVGMatrix)
= (object) @Matrix
\*/
Savage.Matrix = Matrix;
// Colour
/*\
* Savage.getRGB
[ method ]
**
* Parses colour string as RGB object
> Parameters
- colour (string) colour string in one of formats:
# <ul>
# <li>Colour name (“<code>red</code>”, “<code>green</code>”, “<code>cornflowerblue</code>”, etc)</li>
# <li>#••• — shortened HTML colour: (“<code>#000</code>”, “<code>#fc0</code>”, etc)</li>
# <li>#•••••• — full length HTML colour: (“<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>rgb(•••%, •••%, •••%) — same as above, but in %: (“<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>”)</li>
# <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (“<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>”)</li>
# <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
# <li>hsl(•••, •••, •••) — same as hsb</li>
# <li>hsl(•••%, •••%, •••%) — same as hsb</li>
# </ul>
= (object) RGB object in 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 cant be parsed
o }
\*/
Savage.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));
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 *= 2.55);
green = toFloat(values[1]);
values[1].slice(-1) == "%" && (green *= 2.55);
blue = toFloat(values[2]);
values[2].slice(-1) == "%" && (blue *= 2.55);
(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 Savage.hsb2rgb(red, green, blue, opacity);
}
if (rgb[6]) {
values = rgb[6].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);
(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 Savage.hsl2rgb(red, green, blue, opacity);
}
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};
}, Savage);
/*\
* Savage.hsb
[ method ]
**
* Converts HSB values to hex representation of the colour.
> Parameters
- h (number) hue
- s (number) saturation
- b (number) value or brightness
= (string) hex representation of the colour.
\*/
Savage.hsb = cacher(function (h, s, b) {
return Savage.hsb2rgb(h, s, b).hex;
});
/*\
* Savage.hsl
[ method ]
**
* Converts HSL values to hex representation of the colour.
> Parameters
- h (number) hue
- s (number) saturation
- l (number) luminosity
= (string) hex representation of the colour.
\*/
Savage.hsl = cacher(function (h, s, l) {
return Savage.hsl2rgb(h, s, l).hex;
});
/*\
* Savage.rgb
[ method ]
**
* Converts RGB values to hex representation of the colour.
> Parameters
- r (number) red
- g (number) green
- b (number) blue
= (string) hex representation of the colour.
\*/
Savage.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];
toHex = cacher(function (color) {
i.style.color = "inherit";
i.style.color = color;
var out = glob.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
return out == "inherit" ? 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 = Savage.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: Savage.rgb(r, g, b),
toString: rgbtoString
};
is(o, "finite") && (rgb.opacity = o);
return rgb;
};
/*\
* Savage.color
[ method ]
**
* Parses the color string and returns object with all values for the given color.
> Parameters
- clr (string) color string in one of the supported formats (see @Savage.getRGB)
= (object) Combined RGB & HSB object in 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 cant be parsed,
o h (number) hue,
o s (number) saturation,
o v (number) value (brightness),
o l (number) lightness
o }
\*/
Savage.color = function (clr) {
var rgb;
if (is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) {
rgb = Savage.hsb2rgb(clr);
clr.r = rgb.r;
clr.g = rgb.g;
clr.b = rgb.b;
clr.opacity = 1;
clr.hex = rgb.hex;
} else if (is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) {
rgb = Savage.hsl2rgb(clr);
clr.r = rgb.r;
clr.g = rgb.g;
clr.b = rgb.b;
clr.opacity = 1;
clr.hex = rgb.hex;
} else {
if (is(clr, "string")) {
clr = Savage.getRGB(clr);
}
if (is(clr, "object") && "r" in clr && "g" in clr && "b" in clr) {
rgb = Savage.rgb2hsl(clr);
clr.h = rgb.h;
clr.s = rgb.s;
clr.l = rgb.l;
rgb = Savage.rgb2hsb(clr);
clr.v = rgb.b;
} else {
clr = {hex: "none"};
clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
}
}
clr.toString = rgbtoString;
return clr;
};
/*\
* Savage.hsb2rgb
[ method ]
**
* Converts HSB values to RGB object.
> Parameters
- h (number) hue
- s (number) saturation
- v (number) value or brightness
= (object) RGB object in format:
o {
o r (number) red,
o g (number) green,
o b (number) blue,
o hex (string) color in HTML/CSS format: #••••••
o }
\*/
Savage.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 *= 360;
var R, G, B, X, C;
h = (h % 360) / 60;
C = v * s;
X = C * (1 - abs(h % 2 - 1));
R = G = B = v - C;
h = ~~h;
R += [C, X, 0, 0, X, C][h];
G += [X, C, C, X, 0, 0][h];
B += [0, 0, X, C, C, X][h];
return packageRGB(R, G, B, o);
};
/*\
* Savage.hsl2rgb
[ method ]
**
* Converts HSL values to RGB object.
> Parameters
- h (number) hue
- s (number) saturation
- l (number) luminosity
= (object) RGB object in format:
o {
o r (number) red,
o g (number) green,
o b (number) blue,
o hex (string) color in HTML/CSS format: #••••••
o }
\*/
Savage.hsl2rgb = function (h, s, l, o) {
if (is(h, "object") && "h" in h && "s" in h && "l" in h) {
l = h.l;
s = h.s;
h = h.h;
}
if (h > 1 || s > 1 || l > 1) {
h /= 360;
s /= 100;
l /= 100;
}
h *= 360;
var R, G, B, X, C;
h = (h % 360) / 60;
C = 2 * s * (l < .5 ? l : 1 - l);
X = C * (1 - abs(h % 2 - 1));
R = G = B = l - C / 2;
h = ~~h;
R += [C, X, 0, 0, X, C][h];
G += [X, C, C, X, 0, 0][h];
B += [0, 0, X, C, C, X][h];
return packageRGB(R, G, B, o);
};
/*\
* Savage.rgb2hsb
[ method ]
**
* Converts RGB values to HSB object.
> Parameters
- r (number) red
- g (number) green
- b (number) blue
= (object) HSB object in format:
o {
o h (number) hue
o s (number) saturation
o b (number) brightness
o }
\*/
Savage.rgb2hsb = function (r, g, b) {
b = prepareRGB(r, g, b);
r = b[0];
g = b[1];
b = b[2];
var H, S, V, C;
V = mmax(r, g, b);
C = V - mmin(r, g, b);
H = (C == 0 ? null :
V == r ? (g - b) / C :
V == g ? (b - r) / C + 2 :
(r - g) / C + 4
);
H = ((H + 360) % 6) * 60 / 360;
S = C == 0 ? 0 : C / V;
return {h: H, s: S, b: V, toString: hsbtoString};
};
/*\
* Savage.rgb2hsl
[ method ]
**
* Converts RGB values to HSL object.
> Parameters
- r (number) red
- g (number) green
- b (number) blue
= (object) HSL object in format:
o {
o h (number) hue
o s (number) saturation
o l (number) luminosity
o }
\*/
Savage.rgb2hsl = function (r, g, b) {
b = prepareRGB(r, g, b);
r = b[0];
g = b[1];
b = b[2];
var H, S, L, M, m, C;
M = mmax(r, g, b);
m = mmin(r, g, b);
C = M - m;
H = (C == 0 ? null :
M == r ? (g - b) / C :
M == g ? (b - r) / C + 2 :
(r - g) / C + 4);
H = ((H + 360) % 6) * 60 / 360;
L = (M + m) / 2;
S = (C == 0 ? 0 :
L < .5 ? C / (2 * L) :
C / (2 - 2 * L));
return {h: H, s: S, l: L, toString: hsltoString};
};
// Transformations
/*\
* Savage.parsePathString
[ method ]
**
* Utility method
**
* Parses given path string into an array of arrays of path segments.
> Parameters
- pathString (string|array) path string or array of segments (in the last case it will be returned straight away)
= (array) array of segments.
\*/
Savage.parsePathString = function (pathString) {
if (!pathString) {
return null;
}
var pth = Savage.path(pathString);
if (pth.arr) {
return Savage.path.clone(pth.arr);
}
var paramCounts = {a: 7, c: 6, o: 2, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, u: 3, z: 0},
data = [];
if (is(pathString, "array") && is(pathString[0], "array")) { // rough assumption
data = Savage.path.clone(pathString);
}
if (!data.length) {
Str(pathString).replace(pathCommand, function (a, b, c) {
var params = [],
name = b.toLowerCase();
c.replace(pathValues, function (a, b) {
b && params.push(+b);
});
if (name == "m" && params.length > 2) {
data.push([b].concat(params.splice(0, 2)));
name = "l";
b = b == "m" ? "l" : "L";
}
if (name == "o" && params.length == 1) {
data.push([b, params[0]]);
}
if (name == "r") {
data.push([b].concat(params));
} else while (params.length >= paramCounts[name]) {
data.push([b].concat(params.splice(0, paramCounts[name])));
if (!paramCounts[name]) {
break;
}
}
});
}
data.toString = Savage.path.toString;
pth.arr = Savage.path.clone(data);
return data;
};
/*\
* Savage.parseTransformString
[ method ]
**
* Utility method
**
* Parses given path string into an array of transformations.
> Parameters
- TString (string|array) transform string or array of transformations (in the last case it will be returned straight away)
= (array) array of transformations.
\*/
var parseTransformString = Savage.parseTransformString = function (TString) {
if (!TString) {
return null;
}
var paramCounts = {r: 3, s: 4, t: 2, m: 6},
data = [];
if (is(TString, "array") && is(TString[0], "array")) { // rough assumption
data = Savage.path.clone(TString);
}
if (!data.length) {
Str(TString).replace(tCommand, function (a, b, c) {
var params = [],
name = b.toLowerCase();
c.replace(pathValues, function (a, b) {
b && params.push(+b);
});
data.push([b].concat(params));
});
}
data.toString = Savage.path.toString;
return data;
};
function svgTransform2string(tstr) {
var res = [];
tstr = tstr.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) {
params = params.split(/\s*,\s*/);
if (name == "rotate" && params.length == 1) {
params.push(0, 0);
}
if (name == "scale") {
if (params.length == 2) {
params.push(0, 0);
}
if (params.length == 1) {
params.push(params[0], 0, 0);
}
}
if (name == "skewX") {
res.push(["m", 1, 0, math.tan(rad(params[0])), 1, 0, 0]);
} else if (name == "skewY") {
res.push(["m", 1, math.tan(rad(params[0])), 0, 1, 0, 0]);
} else {
res.push([name.charAt(0)].concat(params));
}
return all;
});
return res;
}
var rgTransform = new RegExp("^[a-z][" + spaces + "]*-?\\.?\\d");
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 (!rgTransform.test(tstr)) {
tstr = svgTransform2string(tstr);
} else {
tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || E);
}
var tdata = parseTransformString(tstr),
deg = 0,
dx = 0,
dy = 0,
sx = 1,
sy = 1,
_ = el._,
m = new Matrix;
_.transform = tdata || [];
if (tdata) {
for (var i = 0, ii = tdata.length; i < ii; i++) {
var t = tdata[i],
tlen = t.length,
command = Str(t[0]).toLowerCase(),
absolute = t[0] != command,
inver = absolute ? m.invert() : 0,
x1,
y1,
x2,
y2,
bb;
if (command == "t" && tlen == 3) {
if (absolute) {
x1 = inver.x(0, 0);
y1 = inver.y(0, 0);
x2 = inver.x(t[1], t[2]);
y2 = inver.y(t[1], t[2]);
m.translate(x2 - x1, y2 - y1);
} else {
m.translate(t[1], t[2]);
}
} else if (command == "r") {
if (tlen == 2) {
bb = bb || el.getBBox(1);
m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
deg += t[1];
} else if (tlen == 4) {
if (absolute) {
x2 = inver.x(t[2], t[3]);
y2 = inver.y(t[2], t[3]);
m.rotate(t[1], x2, y2);
} else {
m.rotate(t[1], t[2], t[3]);
}
deg += t[1];
}
} else if (command == "s") {
if (tlen == 2 || tlen == 3) {
bb = bb || el.getBBox(1);
m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
sx *= t[1];
sy *= t[tlen - 1];
} else if (tlen == 5) {
if (absolute) {
x2 = inver.x(t[3], t[4]);
y2 = inver.y(t[3], t[4]);
m.scale(t[1], t[2], x2, y2);
} else {
m.scale(t[1], t[2], t[3], t[4]);
}
sx *= t[1];
sy *= t[2];
}
} else if (command == "m" && tlen == 7) {
m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
}
}
if (doReturn) {
return m;
} else {
_.dirtyT = 1;
el.matrix = m;
}
}
el.matrix = m;
_.sx = sx;
_.sy = sy;
_.deg = deg;
_.dx = dx = m.e;
_.dy = dy = m.f;
if (sx == 1 && sy == 1 && !deg && _.bbox) {
_.bbox.x += +dx;
_.bbox.y += +dy;
} else {
_.dirtyT = 1;
}
}
function unit2px(el, name, value) {
var defs = el.paper.defs,
out = {},
mgr = el.paper.measurer;
if (!mgr) {
el.paper.measurer = mgr = $("rect");
$(mgr, {width: 10, height: 10});
defs.appendChild(mgr);
}
function getW(val) {
if (val == null) {
return E;
}
if (val == +val) {
return val;
}
$(mgr, {width: val});
return mgr.getBBox().width;
}
function getH(val) {
if (val == null) {
return E;
}
if (val == +val) {
return val;
}
$(mgr, {height: val});
return mgr.getBBox().height;
}
function set(nam, f) {
if (name == null) {
out[nam] = f(el.attr(nam));
} else if (nam == name) {
out = f(value == null ? el.attr(nam) : value);
}
}
switch (el.type) {
case "rect":
set("rx", getW);
set("ry", getH);
case "image":
set("width", getW);
set("height", getH);
case "text":
set("x", getW);
set("y", getH);
break;
case "circle":
set("cx", getW);
set("cy", getH);
set("r", getW);
break;
case "ellipse":
set("cx", getW);
set("cy", getH);
set("rx", getW);
set("ry", getH);
break;
case "line":
set("x1", getW);
set("x2", getW);
set("y1", getH);
set("y2", getH);
break;
case "marker":
set("refX", getW);
set("markerWidth", getW);
set("refY", getH);
set("markerHeight", getH);
break;
case "radialGradient":
set("fx", getW);
set("fy", getH);
break;
case "tspan":
set("dx", getW);
set("dy", getH);
break;
default:
out = null;
}
return out;
}
/*\
* Savage.select
[ method ]
**
* Wraps DOM element specified by CSS selector as @Element
> Parameters
- query (string) CSS selector of the element
= (Element)
\*/
Savage.select = function (query) {
return wrap(glob.doc.querySelector(query));
};
/*\
* Savage.selectAll
[ method ]
**
* Wraps DOM elements specified by CSS selector as set or array of @Element
> Parameters
- query (string) CSS selector of the element
= (Element)
\*/
Savage.selectAll = function (query) {
var nodelist = glob.doc.querySelectorAll(query),
set = (Savage.set || Array)();
for (var i = 0; i < nodelist.length; i++) {
set.push(wrap(nodelist[i]));
}
return set;
};
function Element(el) {
if (el.savage in hub) {
return hub[el.savage];
}
var id = this.id = ID(),
svg;
try {
svg = el.ownerSVGElement;
} catch(e) {}
this.node = el;
if (svg) {
this.paper = new Paper(svg);
}
this.type = el.tagName;
this._ = {
transform: [],
sx: 1,
sy: 1,
deg: 0,
dx: 0,
dy: 0,
dirty: 1
};
el.savage = id;
hub[id] = this;
}
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) {
elproto.attr = function (params) {
var node = this.node;
if (is(params, "string")) {
return arrayFirstValue(eve("savage.util.getattr." + params, this));
}
for (var att in params) {
if (params[has](att)) {
eve("savage.util.attr." + att, this, params[att]);
}
}
return this;
};
elproto.getBBox = function (isWithoutTransform) {
if (this.removed) {
return {};
}
var _ = this._;
if (isWithoutTransform) {
if (_.dirty || !_.bboxwt) {
this.realPath = Savage.path.get[this.type](this);
_.bboxwt = Savage.path.getBBox(this.realPath);
_.bboxwt.toString = x_y_w_h;
_.dirty = 0;
}
return Savage._.box(_.bboxwt);
}
if (_.dirty || _.dirtyT || !_.bbox) {
if (_.dirty || !this.realPath) {
_.bboxwt = 0;
this.realPath = Savage.path.get[this.type](this);
}
_.bbox = Savage.path.getBBox(Savage.path.map(this.realPath, this.matrix));
_.bbox.toString = x_y_w_h;
_.dirty = _.dirtyT = 0;
}
return Savage._.box(_.bbox);
};
var propString = function () {
return this.local;
};
elproto.transform = function (tstr) {
var _ = this._;
if (tstr == null) {
var global = new Matrix(this.node.getCTM()),
local = extractTransform(this);
return {
string: _.transform || "",
globalMatrix: global,
localMatrix: local,
diffMatrix: global.clone().add(local.invert()),
global: global.toTransformString(),
local: local.toTransformString(),
toString: propString
};
}
if (tstr instanceof Matrix) {
// may be need to apply it directly
// TODO: investigate
tstr = tstr.toTransformString();
}
extractTransform(this, tstr);
if (this.node) {
if (this.type == "linearGradient" || this.type == "radialGradient") {
$(this.node, {gradientTransform: this.matrix});
} else if (this.type == "pattern") {
$(this.node, {patternTransform: this.matrix});
} else {
$(this.node, {transform: this.matrix});
}
}
return this;
};
elproto.parent = function () {
return Savage(this.node.parentNode);
};
elproto.append = function (el) {
if (el.type == "set") {
var it = this;
el.forEach(function (el) {
it.append(el);
});
return this;
}
el = wrap(el);
this.node.appendChild(el.node);
el.paper = this.paper;
return this;
};
elproto.prepend = function (el) {
el = wrap(el);
this.node.parentNode.insertBefore(el.node, this.node.firstChild);
el.paper = this.paper;
return this;
};
elproto.before = function (el) {
el = wrap(el);
this.node.parentNode.insertBefore(el.node, this.node);
el.paper = this.paper;
return this;
};
elproto.after = function (el) {
el = wrap(el);
this.node.parentNode.insertBefore(el.node, this.node.nextSibling);
el.paper = this.paper;
return this;
};
elproto.insertBefore = function (el) {
el = wrap(el);
el.node.parentNode.insertBefore(this.node, el.node);
this.paper = el.paper;
return this;
};
elproto.insertAfter = function (el) {
el = wrap(el);
el.node.parentNode.insertBefore(this.node, el.node.nextSibling);
this.paper = el.paper;
return this;
};
elproto.remove = function () {
this.node.parentNode.removeChild(this.node);
delete this.paper;
this.removed = true;
};
elproto.select = function (query) {
return wrap(this.node.querySelector(query));
};
elproto.selectAll = function (query) {
var nodelist = this.node.querySelectorAll(query),
set = (Savage.set || Array)();
for (var i = 0; i < nodelist.length; i++) {
set.push(wrap(nodelist[i]));
}
return set;
};
elproto.asPX = function (attr, value) {
return unit2px(this, attr, value);
};
elproto.use = function () {
var use,
id = this.node.id;
if (!id) {
id = this.id;
$(this.node, {
id: id
});
}
if (this.type == "linearGradient" || this.type == "radialGradient" ||
this.type == "pattern") {
use = make(this.type, this.node.parentNode);
} else {
use = make("use", this.node.parentNode);
}
$(use.node, {
"xlink:href": "#" + id
});
return use;
};
elproto.clone = function () {
var clone = this.node.cloneNode(true);
// TODO replace with this.insertAfter()
this.node.parentNode.insertBefore(clone, this.node);
return wrap(clone);
};
elproto.pattern = function (x, y, width, height) {
var p = make("pattern", this.paper.defs);
if (x == null) {
x = this.getBBox();
}
if (x && "x" in x) {
y = x.y;
width = x.width;
height = x.height;
x = x.x;
}
$(p.node, {
x: x,
y: y,
width: width,
height: height,
patternUnits: "userSpaceOnUse",
id: p.id,
viewBox: [x, y, width, height].join(" ")
});
p.node.appendChild(this.node);
return p;
};
elproto.marker = function (x, y, width, height, refX, refY) {
var p = make("marker", this.paper.defs);
if (x == null) {
x = this.getBBox();
}
if (x && "x" in x) {
y = x.y;
width = x.width;
height = x.height;
refX = x.refX;
refY = x.refY;
x = x.x;
}
$(p.node, {
viewBox: [x, y, width, height].join(S),
markerWidth: width,
markerHeight: height,
orient: "auto",
refX: refX || 0,
refY: refY || 0,
id: p.id
});
p.node.appendChild(this.node);
return p;
};
// animation
function slice(from, to, f) {
return function (arr) {
var res = arr.slice(from, to);
if (res.length == 1) {
res = res[0];
}
return f ? f(res) : res;
};
}
elproto.animate = function (attrs, ms, callback) {
var fkeys = [], tkeys = [], keys = {}, from, to, f, eq;
for (var key in attrs) if (attrs[has](key)) {
if (this.equal) {
eq = this.equal(key, Str(attrs[key]));
from = eq.from;
to = eq.to;
f = eq.f;
} else {
from = +this.attr(key);
to = +attrs[key];
}
var len = is(from, "array") ? from.length : 1;
keys[key] = slice(fkeys.length, fkeys.length + len, f);
fkeys = fkeys.concat(from);
tkeys = tkeys.concat(to);
}
var now = mina.time(),
el = this;
return mina(fkeys, tkeys, now, now + ms, mina.time, function (val) {
var attr = {};
for (var key in keys) if (keys[has](key)) {
attr[key] = keys[key](val);
}
el.attr(attr);
});
};
}(Element.prototype));
Savage.parse = function (svg) {
var f = document.createDocumentFragment(),
pointer = f;
eve.on("elemental.tag", function (data, extra, raw) {
var tag = $(data);
extra && $(tag, extra);
pointer.appendChild(tag);
pointer = tag;
});
eve.on("elemental.text", function (text) {
pointer.appendChild(document.createTextNode(text));
});
eve.on("elemental./tag", function () {
pointer = pointer.parentNode;
});
eve.on("elemental.eof", function () {
eve.off("elemental.*");
eve("savage.parsed", f);
});
elemental().parse(svg).end();
return new Fragment(f);
};
function Fragment(frag) {
this.node = frag;
}
Fragment.prototype.select = Element.prototype.select;
Fragment.prototype.selectAll = Element.prototype.selectAll;
Savage.fragment = function () {
var args = Array.prototype.slice.call(arguments, 0),
f = document.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(Savage.parse(item).node);
}
}
return new Fragment(f);
};
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.savage in hub) {
return hub[w.savage];
}
res = new Element(w);
desc = w.getElementsByTagName("desc")[0];
defs = w.getElementsByTagName("defs")[0];
} else {
res = make("svg", glob.doc.body);
$(res.node, {
height: h,
version: 1.1,
width: w,
xmlns: "http://www.w3.org/2000/svg"
});
}
if (!desc) {
desc = $("desc");
desc.appendChild(glob.doc.createTextNode("Created with Savage"));
res.node.appendChild(desc);
}
if (!defs) {
defs = $("defs");
res.node.appendChild(defs);
}
for (var key in proto) if (proto[has](key)) {
res[key] = proto[key];
}
res.paper = res;
res.defs = defs;
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);
}
(function (proto) {
proto.el = function (name) {
var el = make(name, this.node);
return el;
};
proto.rect = function (x, y, w, h, rx, ry) {
var el = make("rect", this.node);
if (ry == null) {
ry = rx;
}
if (is(x, "object") && "x" in x) {
el.attr(x);
} else if (x != null) {
el.attr({
x: x,
y: y,
width: w,
height: h
});
if (rx != null) {
el.attr({
rx: rx,
ry: ry
});
}
}
return el;
};
proto.circle = function (cx, cy, r) {
var el = make("circle", this.node);
if (is(cx, "object") && "cx" in cx) {
el.attr(cx);
} else if (cx != null) {
el.attr({
cx: cx,
cy: cy,
r: r
});
}
return el;
};
proto.image = function (src, x, y, width, height) {
var el = make("image", this.node);
if (is(src, "object") && "src" in src) {
el.attr(src);
} else if (src != null) {
var set = {
"xlink:href": src,
preserveAspectRatio: "none"
};
if (x != null && y != null) {
set.x = x;
set.y = y;
}
if (width != null && height != null) {
set.width = width;
set.height = height;
} else {
preload(src, function () {
$(el.node, {
width: this.offsetWidth,
height: this.offsetHeight
});
});
}
$(el.node, set);
}
return el;
};
proto.custom = function (name) {
var el = make(name, this.node);
return el;
};
proto.ellipse = function (cx, cy, rx, ry) {
var el = make("ellipse", this.node);
if (is(cx, "object") && "cx" in cx) {
el.attr(cx);
} else if (cx != null) {
el.attr({
cx: cx,
cy: cy,
rx: rx,
ry: ry
});
}
return el;
};
proto.path = function (d) {
var el = make("path", this.node);
if (is(d, "object")) {
el.attr(d);
} else if (d) {
el.attr({
d: d
});
}
return el;
};
function add2group(list) {
if (!is(list, "array")) {
list = Array.prototype.slice.call(arguments, 0);
}
var i = 0,
j = 0,
node = this.node;
while (this[i]) delete this[i++];
for (i = 0; i < list.length; i++) {
if (list[i].type == "set") {
list[i].forEach(function (el) {
node.appendChild(el.node);
});
} else {
node.appendChild(list[i].node);
}
}
var children = node.childNodes;
for (i = 0; i < children.length; i++) if (children[i].savage) {
this[j++] = hub[children[i].savage];
}
}
proto.group = proto.g = function () {
var el = make("g", this.node);
el.add = add2group;
if (arguments.length) {
add2group.call(el, Array.prototype.slice.call(arguments, 0));
}
return el;
};
proto.text = function (x, y, text) {
var el = make("text", this.node);
if (is(x, "object")) {
le.attr(x);
} else if (x != null) {
el.attr({
x: x,
y: y,
text: text || ""
});
}
return el;
};
proto.line = function (x1, y1, x2, y2) {
var el = make("line", this.node);
if (is(x1, "object")) {
el.attr(x1);
} else if (x1 != null) {
el.attr({
x1: x1,
x2: x2,
y1: y1,
y2: y2
});
}
return el;
};
proto.polyline = function (points) {
if (arguments.length > 1) {
points = Array.prototype.slice.call(arguments, 0);
}
var el = make("polyline", this.node);
if (is(points, "object") && !is(points, "array")) {
el.attr(points);
} else if (points != null) {
el.attr({
points: points
});
}
return el;
};
proto.polygon = function (points) {
if (arguments.length > 1) {
points = Array.prototype.slice.call(arguments, 0);
}
var el = make("polygon", this.node);
if (is(points, "object") && !is(points, "array")) {
el.attr(points);
} else if (points != null) {
el.attr({
points: points
});
}
return el;
};
// gradients
(function () {
proto.gradient = function (str) {
var grad = arrayFirstValue(eve("savage.util.grad.parse", null, str)),
el;
if (grad.type.toLowerCase() == "l") {
el = this.gradientLinear.apply(this, grad.params);
} else {
el = this.gradientRadial.apply(this, 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 stops() {
return this.selectAll("stop");
}
function addStop(color, offset) {
var stop = $("stop");
$(stop, {
"stop-color": color,
offset: +offset + "%"
});
this.node.appendChild(stop);
return this;
}
function getBBox() {
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 Savage._.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 Savage._.box(cx - r, cy - r, r * 2, r * 2);
}
}
proto.gradientLinear = function (x1, y1, x2, y2) {
var el = make("linearGradient", this.node);
el.stops = stops;
el.addStop = addStop;
el.getBBox = getBBox;
if (x1 != null) {
$(el.node, {
x1: x1,
y1: y1,
x2: x2,
y2: y2
});
}
return el;
};
proto.gradientRadial = function (cx, cy, r, fx, fy) {
var el = make("radialGradient", this.node);
el.stops = stops;
el.addStop = addStop;
el.getBBox = getBBox;
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.prototype));
// Attributes event handlers
eve.on("savage.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;
this.paper.defs.appendChild(value);
value = wrap(value);
}
if (value.type == "mask") {
var mask = value;
} else {
mask = make("mask", this.paper.defs);
mask.node.appendChild(value.node);
!mask.node.id && $(mask.node, {
id: mask.id
});
}
$(this.node, {
mask: "url(#" + mask.id + ")"
});
}
});
(function (clipIt) {
eve.on("savage.util.attr.clip", clipIt);
eve.on("savage.util.attr.clip-path", clipIt);
eve.on("savage.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", this.paper.defs);
clip.node.appendChild(value.node);
!clip.node.id && $(clip.node, {
id: clip.id
});
}
$(this.node, {
"clip-path": "url(#" + clip.id + ")"
});
}
}));
eve.on("savage.util.attr.fill", 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;
this.paper.defs.appendChild(value);
value = wrap(value);
}
if (value instanceof Element &&
(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 = Savage.color(value);
if (fill.error) {
var grad = this.paper.gradient(value);
if (!grad.node.id) {
$(grad.node, {
id: grad.id
});
}
fill = "url(#" + grad.node.id + ")";
} else {
fill = Str(fill);
}
}
$(this.node, {fill: fill});
this.node.style.fill = E;
});
var gradrg = /^([lr])(?:\(([^)]*)\))?(.*)$/i;
eve.on("savage.util.grad.parse", function parseGrad(string) {
string = Str(string);
var tokens = string.match(gradrg),
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("savage.util.attr.d", function (value) {
eve.stop();
if (is(value, "array") && is(value[0], "array")) {
value = Savage.path.toString.call(value);
}
value = Str(value);
if (value.match(/[ruo]/i)) {
value = Savage.path.toAbsolute(value);
}
$(this.node, {d: value});
})(-1);
eve.on("savage.util.attr.path", function (value) {
eve.stop();
this.attr({d: value});
})(-1);
eve.on("savage.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("savage.util.attr.transform", function (value) {
this.transform(value);
eve.stop();
})(-1);
eve.on("savage.util.attr.r", function (value) {
if (this.type == "rect") {
eve.stop();
$(this.node, {
rx: value,
ry: value
});
}
})(-1);
eve.on("savage.util.attr.text", function (value) {
if (this.type == "text") {
var i = 0,
node = this.node;
var 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);
}
node.appendChild(tuner(value));
}
eve.stop();
})(-1);
// default
var availableAttributes = {
rect: {
x: 0,
y: 0,
width: 0,
height: 0,
rx: 0,
ry: 0,
"class": 0
},
circle: {
cx: 0,
cy: 0,
r: 0,
"class": 0
},
ellipse: {
cx: 0,
cy: 0,
rx: 0,
ry: 0,
"class": 0
},
line: {
x1: 0,
y1: 0,
x2: 0,
y2: 0,
"class": 0
},
polyline: {
points: "",
"class": 0
},
polygon: {
points: "",
"class": 0
},
text: {
x: 0,
y: 0,
dx: 0,
dy: 0,
rotate: 0,
textLength: 0,
lengthAdjust: 0,
"class": 0
},
tspan: {
x: 0,
y: 0,
dx: 0,
dy: 0,
rotate: 0,
textLength: 0,
lengthAdjust: 0,
"class": 0
},
textPath: {
"xlink:href": 0,
startOffset: 0,
method: 0,
spacing: 0,
"class": 0
},
marker: {
viewBox: 0,
preserveAspectRatio: 0,
refX: 0,
refY: 0,
markerUnits: 0,
markerWidth: 0,
markerHeight: 0,
orient: 0,
"class": 0
},
linearGradient: {
x1: 0,
y1: 0,
x2: 0,
y2: 0,
gradientUnits: 0,
gradientTransform: 0,
spreadMethod: 0,
"xlink:href": 0,
"class": 0
},
radialGradient: {
cx: 0,
cy: 0,
r: 0,
fx: 0,
fy: 0,
gradientUnits: 0,
gradientTransform: 0,
spreadMethod: 0,
"xlink:href": 0,
"class": 0
},
stop: {
offset: 0,
"class": 0
},
pattern: {
viewBox: 0,
preserveAspectRatio: 0,
x: 0,
y: 0,
width: 0,
height: 0,
patternUnits: 0,
patternContentUnits: 0,
patternTransform: 0,
"xlink:href": 0,
"class": 0
},
clipPath: {
transform: 0,
clipPathUnits: 0,
"class": 0
},
mask: {
x: 0,
y: 0,
width: 0,
height: 0,
maskUnits: 0,
maskContentUnits: 0,
"class": 0
},
image: {
preserveAspectRatio: 0,
transform: 0,
x: 0,
y: 0,
width: 0,
height: 0,
"xlink:href": 0,
"class": 0
},
path: {
d: "",
"class": 0
}
};
eve.on("savage.util.attr", function (value) {
var att = eve.nt();
att = att.substring(att.lastIndexOf(".") + 1);
var style = att.replace(/-(\w)/gi, function (all, letter) {
return letter.toUpperCase();
});
if (availableAttributes[has](this.type) && availableAttributes[this.type][has](att)) {
value == null ? this.node.removeAttribute(att) : this.node.setAttribute(att, value);
} else {
this.node.style[style] = value == null ? E : value;
}
});
eve.on("savage.util.getattr.transform", function () {
eve.stop();
return this.transform();
})(-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 Savage(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("savage.util.getattr.marker-end", getter("end"))(-1);
eve.on("savage.util.getattr.markerEnd", getter("end"))(-1);
eve.on("savage.util.getattr.marker-start", getter("start"))(-1);
eve.on("savage.util.getattr.markerStart", getter("start"))(-1);
eve.on("savage.util.getattr.marker-mid", getter("mid"))(-1);
eve.on("savage.util.getattr.markerMid", getter("mid"))(-1);
eve.on("savage.util.attr.marker-end", setter("end"))(-1);
eve.on("savage.util.attr.markerEnd", setter("end"))(-1);
eve.on("savage.util.attr.marker-start", setter("start"))(-1);
eve.on("savage.util.attr.markerStart", setter("start"))(-1);
eve.on("savage.util.attr.marker-mid", setter("mid"))(-1);
eve.on("savage.util.attr.markerMid", setter("mid"))(-1);
}());
eve.on("savage.util.getattr.r", function () {
if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) {
eve.stop();
return $(this.node, "rx");
}
})(-1);
eve.on("savage.util.getattr.viewBox", function () {
eve.stop();
var vb = $(this.node, "viewBox").split(separator);
return Savage._.box(+vb[0], +vb[1], +vb[2], +vb[3]);
// TODO: investigate why I need to z-index it
})(-1);
eve.on("savage.util.getattr.points", function () {
var p = $(this.node, "points");
eve.stop();
return p.split(separator);
});
eve.on("savage.util.getattr.path", function () {
var p = $(this.node, "d");
eve.stop();
return p;
});
// default
eve.on("savage.util.getattr", function () {
var att = eve.nt();
att = att.substring(att.lastIndexOf(".") + 1);
var style = att.replace(/-(\w)/gi, function (all, letter) {
return letter.toUpperCase();
});
if (availableAttributes[has](this.type) && availableAttributes[this.type][has](att)) {
return this.node.getAttribute(att);
} else {
return glob.doc.defaultView.getComputedStyle(this.node, null).getPropertyValue(style);
}
});
Savage.plugin = function (f) {
f(Savage, Element, Paper, glob);
};
return Savage;
}());
// 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.
Savage.plugin(function (Savage, Element, Paper, glob) {
var elproto = Element.prototype,
is = Savage.is,
clone = Savage._.clone,
has = "hasOwnProperty",
p2s = /,?([a-z]),?/gi,
toFloat = parseFloat,
math = Math,
PI = math.PI,
mmin = math.min,
mmax = math.max,
pow = math.pow,
abs = math.abs;
function paths(ps) {
var p = paths.ps = paths.ps || {};
if (p[ps]) {
p[ps].sleep = 100;
} else {
p[ps] = {
sleep: 100
};
}
setTimeout(function () {
for (var key in p) if (p[has](key) && key != ps) {
p[key].sleep--;
!p[key].sleep && delete p[key];
}
});
return p[ps];
}
function box(x, y, width, height) {
if (x == null) {
x = y = width = height = 0;
}
if (y == null) {
y = x.y;
width = x.width;
height = x.height;
x = x.x;
}
return {
x: x,
y: y,
width: width,
w: width,
height: height,
h: height,
x2: x + width,
y2: y + height,
cx: x + width / 2,
cy: y + height / 2,
r1: math.min(width, height) / 2,
r2: math.max(width, height) / 2,
r0: math.sqrt(width * width + height * height) / 2,
path: rectPath(x, y, width, height),
vb: [x, y, width, height].join(" ")
};
}
function toString() {
return this.join(",").replace(p2s, "$1");
}
function pathClone(pathArray) {
var res = clone(pathArray);
res.toString = toString;
return res;
}
function getPointAtSegmentLength(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
if (length == null) {
return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
} else {
return findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y,
getTotLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length));
}
}
function getLengthFactory(istotal, subpath) {
function O(val) {
return +(+val).toFixed(3);
}
return function (path, length, onlystart) {
path = path2curve(path);
var x, y, p, l, sp = "", subpaths = {}, point,
len = 0;
for (var i = 0, ii = path.length; i < ii; i++) {
p = path[i];
if (p[0] == "M") {
x = +p[1];
y = +p[2];
} else {
l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
if (len + l > length) {
if (subpath && !subpaths.start) {
point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
sp += [
"C" + O(point.start.x),
O(point.start.y),
O(point.m.x),
O(point.m.y),
O(point.x),
O(point.y)
];
if (onlystart) {return sp;}
subpaths.start = sp;
sp = [
"M" + O(point.x),
O(point.y) + "C" + O(point.n.x),
O(point.n.y),
O(point.end.x),
O(point.end.y),
O(p[5]),
O(p[6])
].join();
len += l;
x = +p[5];
y = +p[6];
continue;
}
if (!istotal && !subpath) {
point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
return point;
}
}
len += l;
x = +p[5];
y = +p[6];
}
sp += p.shift() + p;
}
subpaths.end = sp;
point = istotal ? len : subpath ? subpaths : findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
return point;
};
}
var getTotalLength = getLengthFactory(1),
getPointAtLength = getLengthFactory(),
getSubpathsAtLength = getLengthFactory(0, 1);
function findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
var t1 = 1 - t,
t13 = pow(t1, 3),
t12 = pow(t1, 2),
t2 = t * t,
t3 = t2 * t,
x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y,
mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x),
my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y),
nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x),
ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y),
ax = t1 * p1x + t * c1x,
ay = t1 * p1y + t * c1y,
cx = t1 * c2x + t * p2x,
cy = t1 * c2y + t * p2y,
alpha = (90 - math.atan2(mx - nx, my - ny) * 180 / PI);
// (mx > nx || my < ny) && (alpha += 180);
return {
x: x,
y: y,
m: {x: mx, y: my},
n: {x: nx, y: ny},
start: {x: ax, y: ay},
end: {x: cx, y: cy},
alpha: alpha
};
}
function bezierBBox(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
if (!Savage.is(p1x, "array")) {
p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
}
var bbox = curveDim.apply(null, p1x);
return box(
bbox.min.x,
bbox.min.y,
bbox.max.x - bbox.min.x,
bbox.max.y - bbox.min.y
);
}
function isPointInsideBBox(bbox, x, y) {
return x >= bbox.x &&
x <= bbox.x + bbox.width &&
y >= bbox.y &&
y <= bbox.y + bbox.height;
}
function isBBoxIntersect(bbox1, bbox2) {
bbox1 = box(bbox1);
bbox2 = box(bbox2);
return isPointInsideBBox(bbox2, bbox1.x, bbox1.y)
|| isPointInsideBBox(bbox2, bbox1.x2, bbox1.y)
|| isPointInsideBBox(bbox2, bbox1.x, bbox1.y2)
|| isPointInsideBBox(bbox2, bbox1.x2, bbox1.y2)
|| isPointInsideBBox(bbox1, bbox2.x, bbox2.y)
|| isPointInsideBBox(bbox1, bbox2.x2, bbox2.y)
|| isPointInsideBBox(bbox1, bbox2.x, bbox2.y2)
|| isPointInsideBBox(bbox1, bbox2.x2, bbox2.y2)
|| (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x
|| bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
&& (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y
|| bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
}
function base3(t, p1, p2, p3, p4) {
var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
return t * t2 - 3 * p1 + 3 * p2;
}
function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
if (z == null) {
z = 1;
}
z = z > 1 ? 1 : z < 0 ? 0 : z;
var z2 = z / 2,
n = 12,
Tvalues = [-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],
Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],
sum = 0;
for (var i = 0; i < n; i++) {
var ct = z2 * Tvalues[i] + z2,
xbase = base3(ct, x1, x2, x3, x4),
ybase = base3(ct, y1, y2, y3, y4),
comb = xbase * xbase + ybase * ybase;
sum += Cvalues[i] * math.sqrt(comb);
}
return z2 * sum;
}
function getTotLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) {
if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) {
return;
}
var t = 1,
step = t / 2,
t2 = t - step,
l,
e = .01;
l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
while (abs(l - ll) > e) {
step /= 2;
t2 += (l < ll ? 1 : -1) * step;
l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
}
return t2;
}
function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
if (
mmax(x1, x2) < mmin(x3, x4) ||
mmin(x1, x2) > mmax(x3, x4) ||
mmax(y1, y2) < mmin(y3, y4) ||
mmin(y1, y2) > mmax(y3, y4)
) {
return;
}
var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (!denominator) {
return;
}
var px = nx / denominator,
py = ny / denominator,
px2 = +px.toFixed(2),
py2 = +py.toFixed(2);
if (
px2 < +mmin(x1, x2).toFixed(2) ||
px2 > +mmax(x1, x2).toFixed(2) ||
px2 < +mmin(x3, x4).toFixed(2) ||
px2 > +mmax(x3, x4).toFixed(2) ||
py2 < +mmin(y1, y2).toFixed(2) ||
py2 > +mmax(y1, y2).toFixed(2) ||
py2 < +mmin(y3, y4).toFixed(2) ||
py2 > +mmax(y3, y4).toFixed(2)
) {
return;
}
return {x: px, y: py};
}
function inter(bez1, bez2) {
return interHelper(bez1, bez2);
}
function interCount(bez1, bez2) {
return interHelper(bez1, bez2, 1);
}
function interHelper(bez1, bez2, justCount) {
var bbox1 = bezierBBox(bez1),
bbox2 = bezierBBox(bez2);
if (!isBBoxIntersect(bbox1, bbox2)) {
return justCount ? 0 : [];
}
var l1 = bezlen.apply(0, bez1),
l2 = bezlen.apply(0, bez2),
n1 = ~~(l1 / 5),
n2 = ~~(l2 / 5),
dots1 = [],
dots2 = [],
xy = {},
res = justCount ? 0 : [];
for (var i = 0; i < n1 + 1; i++) {
var p = findDotsAtSegment.apply(0, bez1.concat(i / n1));
dots1.push({x: p.x, y: p.y, t: i / n1});
}
for (i = 0; i < n2 + 1; i++) {
p = findDotsAtSegment.apply(0, bez2.concat(i / n2));
dots2.push({x: p.x, y: p.y, t: i / n2});
}
for (i = 0; i < n1; i++) {
for (var j = 0; j < n2; j++) {
var di = dots1[i],
di1 = dots1[i + 1],
dj = dots2[j],
dj1 = dots2[j + 1],
ci = abs(di1.x - di.x) < .001 ? "y" : "x",
cj = abs(dj1.x - dj.x) < .001 ? "y" : "x",
is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
if (is) {
if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) {
continue;
}
xy[is.x.toFixed(4)] = is.y.toFixed(4);
var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
if (justCount) {
res++;
} else {
res.push({
x: is.x,
y: is.y,
t1: t1,
t2: t2
});
}
}
}
}
}
return res;
}
function pathIntersection(path1, path2) {
return interPathHelper(path1, path2);
}
function pathIntersectionNumber(path1, path2) {
return interPathHelper(path1, path2, 1);
}
function interPathHelper(path1, path2, justCount) {
path1 = path2curve(path1);
path2 = path2curve(path2);
var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
res = justCount ? 0 : [];
for (var i = 0, ii = path1.length; i < ii; i++) {
var pi = path1[i];
if (pi[0] == "M") {
x1 = x1m = pi[1];
y1 = y1m = pi[2];
} else {
if (pi[0] == "C") {
bez1 = [x1, y1].concat(pi.slice(1));
x1 = bez1[6];
y1 = bez1[7];
} else {
bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
x1 = x1m;
y1 = y1m;
}
for (var j = 0, jj = path2.length; j < jj; j++) {
var pj = path2[j];
if (pj[0] == "M") {
x2 = x2m = pj[1];
y2 = y2m = pj[2];
} else {
if (pj[0] == "C") {
bez2 = [x2, y2].concat(pj.slice(1));
x2 = bez2[6];
y2 = bez2[7];
} else {
bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
x2 = x2m;
y2 = y2m;
}
var intr = interHelper(bez1, bez2, justCount);
if (justCount) {
res += intr;
} else {
for (var k = 0, kk = intr.length; k < kk; k++) {
intr[k].segment1 = i;
intr[k].segment2 = j;
intr[k].bez1 = bez1;
intr[k].bez2 = bez2;
}
res = res.concat(intr);
}
}
}
}
}
return res;
}
function isPointInsidePath(path, x, y) {
var bbox = pathBBox(path);
return isPointInsideBBox(bbox, x, y) &&
interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1;
}
function pathBBox(path) {
var pth = paths(path);
if (pth.bbox) {
return clone(pth.bbox);
}
if (!path) {
return box();
}
path = path2curve(path);
var x = 0,
y = 0,
X = [],
Y = [],
p;
for (var i = 0, ii = path.length; i < ii; i++) {
p = path[i];
if (p[0] == "M") {
x = p[1];
y = p[2];
X.push(x);
Y.push(y);
} else {
var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
X = X.concat(dim.min.x, dim.max.x);
Y = Y.concat(dim.min.y, dim.max.y);
x = p[5];
y = p[6];
}
}
var xmin = mmin.apply(0, X),
ymin = mmin.apply(0, Y),
xmax = mmax.apply(0, X),
ymax = mmax.apply(0, Y),
bb = box(xmin, ymin, xmax - xmin, ymax - ymin);
pth.bbox = clone(bb);
return bb;
}
function rectPath(x, y, w, h, r) {
if (r) {
return [
["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],
["a", r, r, 0, 0, 1, -r, -r],
["l", 0, r * 2 - h],
["a", r, r, 0, 0, 1, r, -r],
["z"]
];
}
var res = [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
res.toString = toString;
return res;
}
function ellipsePath(x, y, rx, ry, a) {
if (a == null && ry == null) {
ry = rx;
}
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),
y2 = y + rx * Math.sin(-a * rad),
res = [["M", x1, y1], ["A", rx, rx, 0, +(a - ry > 180), 0, x2, y2]];
} else {
res = [
["M", x, y],
["m", 0, -ry],
["a", rx, ry, 0, 1, 1, 0, 2 * ry],
["a", rx, ry, 0, 1, 1, 0, -2 * ry],
["z"]
];
}
res.toString = toString;
return res;
}
var getPath = {
path: function (el) {
return el.attr("path");
},
circle: function (el) {
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);
},
rect: function (el) {
var attr = unit2px(el);
return rectPath(attr.x, attr.y, 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);
},
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);
}
};
function pathToRelative(pathArray) {
var pth = paths(pathArray);
if (pth.rel) {
return pathClone(pth.rel);
}
if (!Savage.is(pathArray, "array") || !Savage.is(pathArray && pathArray[0], "array")) {
pathArray = Savage.parsePathString(pathArray);
}
var res = [],
x = 0,
y = 0,
mx = 0,
my = 0,
start = 0;
if (pathArray[0][0] == "M") {
x = pathArray[0][1];
y = pathArray[0][2];
mx = x;
my = y;
start++;
res.push(["M", x, y]);
}
for (var i = start, ii = pathArray.length; i < ii; i++) {
var r = res[i] = [],
pa = pathArray[i];
if (pa[0] != lowerCase.call(pa[0])) {
r[0] = lowerCase.call(pa[0]);
switch (r[0]) {
case "a":
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).toFixed(3);
r[7] = +(pa[7] - y).toFixed(3);
break;
case "v":
r[1] = +(pa[1] - y).toFixed(3);
break;
case "m":
mx = pa[1];
my = pa[2];
default:
for (var j = 1, jj = pa.length; j < jj; j++) {
r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
}
}
} else {
r = res[i] = [];
if (pa[0] == "m") {
mx = pa[1] + x;
my = pa[2] + y;
}
for (var k = 0, kk = pa.length; k < kk; k++) {
res[i][k] = pa[k];
}
}
var len = res[i].length;
switch (res[i][0]) {
case "z":
x = mx;
y = my;
break;
case "h":
x += +res[i][len - 1];
break;
case "v":
y += +res[i][len - 1];
break;
default:
x += +res[i][len - 2];
y += +res[i][len - 1];
}
}
res.toString = toString;
pth.rel = pathClone(res);
return res;
}
function pathToAbsolute(pathArray) {
var pth = paths(pathArray);
if (pth.abs) {
return pathClone(pth.abs);
}
if (!is(pathArray, "array") || !is(pathArray && pathArray[0], "array")) { // rough assumption
pathArray = Savage.parsePathString(pathArray);
}
if (!pathArray || !pathArray.length) {
return [["M", 0, 0]];
}
var res = [],
x = 0,
y = 0,
mx = 0,
my = 0,
start = 0,
pa0;
if (pathArray[0][0] == "M") {
x = +pathArray[0][1];
y = +pathArray[0][2];
mx = x;
my = y;
start++;
res[0] = ["M", x, y];
}
var crz = pathArray.length == 3 &&
pathArray[0][0] == "M" &&
pathArray[1][0].toUpperCase() == "R" &&
pathArray[2][0].toUpperCase() == "Z";
for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
res.push(r = []);
pa = pathArray[i];
pa0 = pa[0];
if (pa0 != pa0.toUpperCase()) {
r[0] = pa0.toUpperCase();
switch (r[0]) {
case "A":
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);
break;
case "V":
r[1] = +pa[1] + y;
break;
case "H":
r[1] = +pa[1] + x;
break;
case "R":
var dots = [x, y].concat(pa.slice(1));
for (var j = 2, jj = dots.length; j < jj; j++) {
dots[j] = +dots[j] + x;
dots[++j] = +dots[j] + y;
}
res.pop();
res = res.concat(catmullRom2bezier(dots, crz));
break;
case "O":
res.pop();
dots = ellipsePath(x, y, pa[1], pa[2]);
dots.push(dots[0]);
res = res.concat(dots);
break;
case "U":
res.pop();
res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3]));
r = ["U"].concat(res[res.length - 1].slice(-2));
break;
case "M":
mx = +pa[1] + x;
my = +pa[2] + y;
default:
for (j = 1, jj = pa.length; j < jj; j++) {
r[j] = +pa[j] + ((j % 2) ? x : y);
}
}
} else if (pa0 == "R") {
dots = [x, y].concat(pa.slice(1));
res.pop();
res = res.concat(catmullRom2bezier(dots, crz));
r = ["R"].concat(pa.slice(-2));
} else if (pa0 == "O") {
res.pop();
dots = ellipsePath(x, y, pa[1], pa[2]);
dots.push(dots[0]);
res = res.concat(dots);
} else if (pa0 == "U") {
res.pop();
res = res.concat(ellipsePath(x, y, pa[1], pa[2], pa[3]));
r = ["U"].concat(res[res.length - 1].slice(-2));
} else {
for (var k = 0, kk = pa.length; k < kk; k++) {
r[k] = pa[k];
}
}
pa0 = pa0.toUpperCase();
if (pa0 != "O") {
switch (r[0]) {
case "Z":
x = mx;
y = my;
break;
case "H":
x = r[1];
break;
case "V":
y = r[1];
break;
case "M":
mx = r[r.length - 2];
my = r[r.length - 1];
default:
x = r[r.length - 2];
y = r[r.length - 1];
}
}
}
res.toString = toString;
pth.abs = pathClone(res);
return res;
}
function l2c(x1, y1, x2, y2) {
return [x1, y1, x2, y2, x2, y2];
}
function q2c(x1, y1, ax, ay, x2, y2) {
var _13 = 1 / 3,
_23 = 2 / 3;
return [
_13 * x1 + _23 * ax,
_13 * y1 + _23 * ay,
_13 * x2 + _23 * ax,
_13 * y2 + _23 * ay,
x2,
y2
];
}
function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
// for more information of where this math came from visit:
// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
var _120 = PI * 120 / 180,
rad = PI / 180 * (+angle || 0),
res = [],
xy,
rotate = cacher(function (x, y, rad) {
var X = x * math.cos(rad) - y * math.sin(rad),
Y = x * math.sin(rad) + y * math.cos(rad);
return {x: X, y: Y};
});
if (!recursive) {
xy = rotate(x1, y1, -rad);
x1 = xy.x;
y1 = xy.y;
xy = rotate(x2, y2, -rad);
x2 = xy.x;
y2 = xy.y;
var cos = math.cos(PI / 180 * angle),
sin = math.sin(PI / 180 * angle),
x = (x1 - x2) / 2,
y = (y1 - y2) / 2;
var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
if (h > 1) {
h = math.sqrt(h);
rx = h * rx;
ry = h * ry;
}
var rx2 = rx * rx,
ry2 = ry * ry,
k = (large_arc_flag == sweep_flag ? -1 : 1) *
math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
cx = k * rx * y / ry + (x1 + x2) / 2,
cy = k * -ry * x / rx + (y1 + y2) / 2,
f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
f2 = math.asin(((y2 - cy) / ry).toFixed(9));
f1 = x1 < cx ? PI - f1 : f1;
f2 = x2 < cx ? PI - f2 : f2;
f1 < 0 && (f1 = PI * 2 + f1);
f2 < 0 && (f2 = PI * 2 + f2);
if (sweep_flag && f1 > f2) {
f1 = f1 - PI * 2;
}
if (!sweep_flag && f2 > f1) {
f2 = f2 - PI * 2;
}
} else {
f1 = recursive[0];
f2 = recursive[1];
cx = recursive[2];
cy = recursive[3];
}
var df = f2 - f1;
if (abs(df) > _120) {
var f2old = f2,
x2old = x2,
y2old = y2;
f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
x2 = cx + rx * math.cos(f2);
y2 = cy + ry * math.sin(f2);
res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
}
df = f2 - f1;
var c1 = math.cos(f1),
s1 = math.sin(f1),
c2 = math.cos(f2),
s2 = math.sin(f2),
t = math.tan(df / 4),
hx = 4 / 3 * rx * t,
hy = 4 / 3 * ry * t,
m1 = [x1, y1],
m2 = [x1 + hx * s1, y1 - hy * c1],
m3 = [x2 + hx * s2, y2 - hy * c2],
m4 = [x2, y2];
m2[0] = 2 * m1[0] - m2[0];
m2[1] = 2 * m1[1] - m2[1];
if (recursive) {
return [m2, m3, m4].concat(res);
} else {
res = [m2, m3, m4].concat(res).join().split(",");
var newres = [];
for (var i = 0, ii = res.length; i < ii; i++) {
newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
}
return newres;
}
}
function findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
var t1 = 1 - t;
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);
}
if (t2 > 0 && t2 < 1) {
dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
x.push(dot.x);
y.push(dot.y);
}
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);
}
return {
min: {x: mmin.apply(0, x), y: mmin.apply(0, y)},
max: {x: mmax.apply(0, x), y: mmax.apply(0, y)}
};
}
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) {
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);
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));
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));
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];
path = ["C"].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
break;
case "L":
path = ["C"].concat(l2c(d.x, d.y, path[1], path[2]));
break;
case "H":
path = ["C"].concat(l2c(d.x, d.y, path[1], d.y));
break;
case "V":
path = ["C"].concat(l2c(d.x, d.y, d.x, path[1]));
break;
case "Z":
path = ["C"].concat(l2c(d.x, d.y, d.X, d.Y));
break;
}
return path;
},
fixArc = function (pp, i) {
if (pp[i].length > 7) {
pp[i].shift();
var pi = pp[i];
while (pi.length) {
pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
}
pp.splice(i, 1);
ii = mmax(p.length, p2 && p2.length || 0);
}
},
fixM = function (path1, path2, a1, a2, i) {
if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
path2.splice(i, 0, ["M", a2.x, a2.y]);
a1.bx = 0;
a1.by = 0;
a1.x = path1[i][1];
a1.y = path1[i][2];
ii = mmax(p.length, p2 && p2.length || 0);
}
};
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);
fixM(p, p2, attrs, attrs2, i);
fixM(p2, p, attrs2, attrs, i);
var seg = p[i],
seg2 = p2 && p2[i],
seglen = seg.length,
seg2len = p2 && seg2.length;
attrs.x = seg[seglen - 2];
attrs.y = seg[seglen - 1];
attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x);
attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y);
attrs2.x = p2 && seg2[seg2len - 2];
attrs2.y = p2 && seg2[seg2len - 1];
}
if (!p2) {
pth.curve = pathClone(p);
}
return p2 ? [p, p2] : p;
}
function mapPath(path, matrix) {
if (!matrix) {
return path;
}
var x, y, i, j, ii, jj, pathi;
path = path2curve(path);
for (i = 0, ii = path.length; i < ii; i++) {
pathi = path[i];
for (j = 1, jj = pathi.length; j < jj; j += 2) {
x = matrix.x(pathi[j], pathi[j + 1]);
y = matrix.y(pathi[j], pathi[j + 1]);
pathi[j] = x;
pathi[j + 1] = y;
}
}
return path;
}
// http://schepers.cc/getting-to-the-point
function catmullRom2bezier(crp, z) {
var d = [];
for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
var p = [
{x: +crp[i - 2], y: +crp[i - 1]},
{x: +crp[i], y: +crp[i + 1]},
{x: +crp[i + 2], y: +crp[i + 3]},
{x: +crp[i + 4], y: +crp[i + 5]}
];
if (z) {
if (!i) {
p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
} else if (iLen - 4 == i) {
p[3] = {x: +crp[0], y: +crp[1]};
} else if (iLen - 2 == i) {
p[2] = {x: +crp[0], y: +crp[1]};
p[3] = {x: +crp[2], y: +crp[3]};
}
} else {
if (iLen - 4 == i) {
p[3] = p[2];
} else if (!i) {
p[0] = {x: +crp[i], y: +crp[i + 1]};
}
}
d.push(["C",
(-p[0].x + 6 * p[1].x + p[2].x) / 6,
(-p[0].y + 6 * p[1].y + p[2].y) / 6,
(p[1].x + 6 * p[2].x - p[3].x) / 6,
(p[1].y + 6*p[2].y - p[3].y) / 6,
p[2].x,
p[2].y
]);
}
return d;
}
// export
Savage.path = paths;
/*\
* Savage.path.getTotalLength
[ method ]
**
* Returns length of the given path in pixels.
**
> Parameters
**
- path (string) SVG path string.
**
= (number) length.
\*/
Savage.path.getTotalLength = getTotalLength;
/*\
* Savage.path.getPointAtLength
[ method ]
**
* Return coordinates of the point located at the given length on the given path.
**
> Parameters
**
- path (string) SVG path string
- length (number)
**
= (object) representation of the point:
o {
o x: (number) x coordinate
o y: (number) y coordinate
o alpha: (number) angle of derivative
o }
\*/
Savage.path.getPointAtLength = getPointAtLength;
/*\
* Savage.path.getSubpath
[ method ]
**
* Return subpath of a given path from given length to given length.
**
> Parameters
**
- path (string) SVG path string
- from (number) position of the start of the segment
- to (number) position of the end of the segment
**
= (string) pathstring for the segment
\*/
Savage.path.getSubpath = function (path, from, to) {
if (this.getTotalLength(path) - to < 1e-6) {
return getSubpathsAtLength(path, from).end;
}
var a = getSubpathsAtLength(path, to, 1);
return from ? getSubpathsAtLength(a, from).end : a;
};
/*\
* Element.getTotalLength
[ method ]
**
* Returns length of the path in pixels. Only works for element of “path” type.
= (number) length.
\*/
elproto.getTotalLength = function () {
if (this.node.getTotalLength) {
return this.node.getTotalLength();
}
};
/*\
* Element.getPointAtLength
[ method ]
**
* Return coordinates of the point located at the given length on the given path. Only works for element of “path” type.
**
> Parameters
**
- length (number)
**
= (object) representation of the point:
o {
o x: (number) x coordinate
o y: (number) y coordinate
o alpha: (number) angle of derivative
o }
\*/
elproto.getPointAtLength = function (length) {
return getPointAtLength(this.attr("d"), length);
};
/*\
* Element.getSubpath
[ method ]
**
* Return subpath of a given element from given length to given length. Only works for element of “path” type.
**
> Parameters
**
- from (number) position of the start of the segment
- to (number) position of the end of the segment
**
= (string) pathstring for the segment
\*/
elproto.getSubpath = function (from, to) {
return getSubpath(this.attr("d"), from, to);
};
Savage._.box = box;
/*\
* Savage.findDotsAtSegment
[ method ]
**
* Utility method
**
* Find dot coordinates on the given cubic bezier curve at the given t.
> Parameters
- p1x (number) x of the first point of the curve
- p1y (number) y of the first point of the curve
- c1x (number) x of the first anchor of the curve
- c1y (number) y of the first anchor of the curve
- c2x (number) x of the second anchor of the curve
- c2y (number) y of the second anchor of the curve
- p2x (number) x of the second point of the curve
- p2y (number) y of the second point of the curve
- t (number) position on the curve (0..1)
= (object) point information in format:
o {
o x: (number) x coordinate of the point
o y: (number) y coordinate of the point
o m: {
o x: (number) x coordinate of the left anchor
o y: (number) y coordinate of the left anchor
o }
o n: {
o x: (number) x coordinate of the right anchor
o y: (number) y coordinate of the right anchor
o }
o start: {
o x: (number) x coordinate of the start of the curve
o y: (number) y coordinate of the start of the curve
o }
o end: {
o x: (number) x coordinate of the end of the curve
o y: (number) y coordinate of the end of the curve
o }
o alpha: (number) angle of the curve derivative at the point
o }
\*/
Savage.path.findDotsAtSegment = findDotsAtSegment;
/*\
* Savage.path.bezierBBox
[ method ]
**
* Utility method
**
* Return bounding box of a given cubic bezier curve
> Parameters
- p1x (number) x of the first point of the curve
- p1y (number) y of the first point of the curve
- c1x (number) x of the first anchor of the curve
- c1y (number) y of the first anchor of the curve
- c2x (number) x of the second anchor of the curve
- c2y (number) y of the second anchor of the curve
- p2x (number) x of the second point of the curve
- p2y (number) y of the second point of the curve
* or
- bez (array) array of six points for bezier curve
= (object) point information in format:
o {
o min: {
o x: (number) x coordinate of the left point
o y: (number) y coordinate of the top point
o }
o max: {
o x: (number) x coordinate of the right point
o y: (number) y coordinate of the bottom point
o }
o }
\*/
Savage.path.bezierBBox = bezierBBox;
/*\
* Savage.path.isPointInsideBBox
[ method ]
**
* Utility method
**
* Returns `true` if given point is inside bounding box.
> Parameters
- bbox (string) bounding box
- x (string) x coordinate of the point
- y (string) y coordinate of the point
= (boolean) `true` if point inside
\*/
Savage.path.isPointInsideBBox = isPointInsideBBox;
/*\
* Savage.path.isBBoxIntersect
[ method ]
**
* Utility method
**
* Returns `true` if two bounding boxes intersect
> Parameters
- bbox1 (string) first bounding box
- bbox2 (string) second bounding box
= (boolean) `true` if they intersect
\*/
Savage.path.isBBoxIntersect = isBBoxIntersect;
/*\
* Savage.path.intersection
[ method ]
**
* Utility method
**
* Finds intersections of two paths
> Parameters
- path1 (string) path string
- path2 (string) path string
= (array) dots of intersection
o [
o {
o x: (number) x coordinate of the point
o y: (number) y coordinate of the point
o t1: (number) t value for segment of path1
o t2: (number) t value for segment of path2
o segment1: (number) order number for segment of path1
o segment2: (number) order number for segment of path2
o bez1: (array) eight coordinates representing beziér curve for the segment of path1
o bez2: (array) eight coordinates representing beziér curve for the segment of path2
o }
o ]
\*/
Savage.path.intersection = pathIntersection;
Savage.path.intersectionNumber = pathIntersectionNumber;
/*\
* Savage.path.isPointInside
[ method ]
**
* Utility method
**
* Returns `true` if given point is inside a given closed path.
> Parameters
- path (string) path string
- x (number) x of the point
- y (number) y of the point
= (boolean) true, if point is inside the path
\*/
Savage.path.isPointInside = isPointInsidePath;
/*\
* Savage.pathBBox
[ method ]
**
* Utility method
**
* Return bounding box of a given path
> Parameters
- path (string) path string
= (object) bounding box
o {
o x: (number) x coordinate of the left top point of the box
o y: (number) y coordinate of the left top point of the box
o x2: (number) x coordinate of the right bottom point of the box
o y2: (number) y coordinate of the right bottom point of the box
o width: (number) width of the box
o height: (number) height of the box
o }
\*/
Savage.path.getBBox = pathBBox;
Savage.path.get = getPath;
/*\
* Savage.path.toRelative
[ method ]
**
* Utility method
**
* Converts path coordinates into relative values.
> Parameters
- path (string) path string
= (array) path string
\*/
Savage.path.toRelative = pathToRelative;
/*\
* Savage.path.toAbsolute
[ method ]
**
* Utility method
**
* Converts path coordinates into absolute values.
> Parameters
- path (string) path string
= (array) path string
\*/
Savage.path.toAbsolute = pathToAbsolute;
/*\
* Savage.path.toCubic
[ method ]
**
* Utility method
**
* Converts path to a new path where all segments are cubic bezier curves.
> Parameters
- pathString (string|array) path string or array of segments
= (array) array of segments.
\*/
Savage.path.toCubic = path2curve;
/*\
* Savage.path.map
[ method ]
**
* Transform the path string with given matrix.
> Parameters
- path (string) path string
- matrix (object) see @Matrix
= (string) transformed path string
\*/
Savage.path.map = mapPath;
Savage.path.toString = toString;
Savage.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
//
// 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.
Savage.plugin(function (Savage, Element, Paper, glob) {
var mmax = Math.max,
mmin = Math.min;
// Set
var Set = function (items) {
this.items = [];
this.length = 0;
this.type = "set";
if (items) {
for (var i = 0, ii = items.length; i < ii; i++) {
if (items[i]) {
this[this.items.length] = this.items[this.items.length] = items[i];
this.length++;
}
}
}
},
setproto = Set.prototype;
/*\
* Set.push
[ method ]
**
* Adds each argument to the current set.
= (object) original element
\*/
setproto.push = function () {
var item,
len;
for (var i = 0, ii = arguments.length; i < ii; i++) {
item = arguments[i];
if (item) {
len = this.items.length;
this[len] = this.items[len] = item;
this.length++;
}
}
return this;
};
/*\
* Set.pop
[ method ]
**
* Removes last element and returns it.
= (object) element
\*/
setproto.pop = function () {
this.length && delete this[this.length--];
return this.items.pop();
};
/*\
* Set.forEach
[ method ]
**
* Executes given function for each element in the set.
*
* If function returns `false` it will stop loop running.
**
> Parameters
**
- callback (function) function to run
- thisArg (object) context object for the callback
= (object) Set object
\*/
setproto.forEach = function (callback, thisArg) {
for (var i = 0, ii = this.items.length; i < ii; i++) {
if (callback.call(thisArg, this.items[i], i) === false) {
return this;
}
}
return this;
};
setproto.attr = function (value) {
for (var i = 0, ii = this.items.length; i < ii; i++) {
this.items[i].attr(value);
}
return this;
};
/*\
* Set.clear
[ method ]
**
* Removeds all elements from the set
\*/
setproto.clear = function () {
while (this.length) {
this.pop();
}
};
/*\
* Set.splice
[ method ]
**
* Removes given element from the set
**
> Parameters
**
- index (number) position of the deletion
- count (number) number of element to remove
- insertion… (object) #optional elements to insert
= (object) set elements that were deleted
\*/
setproto.splice = function (index, count, insertion) {
index = index < 0 ? mmax(this.length + index, 0) : index;
count = mmax(0, mmin(this.length - index, count));
var tail = [],
todel = [],
args = [],
i;
for (i = 2; i < arguments.length; i++) {
args.push(arguments[i]);
}
for (i = 0; i < count; i++) {
todel.push(this[index + i]);
}
for (; i < this.length - index; i++) {
tail.push(this[index + i]);
}
var arglen = args.length;
for (i = 0; i < arglen + tail.length; i++) {
this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen];
}
i = this.items.length = this.length -= count - arglen;
while (this[i]) {
delete this[i++];
}
return new Set(todel);
};
/*\
* Set.exclude
[ method ]
**
* Removes given element from the set
**
> Parameters
**
- element (object) element to remove
= (boolean) `true` if object was found & removed from the set
\*/
setproto.exclude = function (el) {
for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) {
this.splice(i, 1);
return true;
}
};
setproto.insertAfter = function (el) {
var i = this.items.length;
while (i--) {
this.items[i].insertAfter(el);
}
return this;
};
setproto.getBBox = function () {
var x = [],
y = [],
x2 = [],
y2 = [];
for (var i = this.items.length; i--;) if (!this.items[i].removed) {
var box = this.items[i].getBBox();
x.push(box.x);
y.push(box.y);
x2.push(box.x + box.width);
y2.push(box.y + box.height);
}
x = mmin.apply(0, x);
y = mmin.apply(0, y);
x2 = mmax.apply(0, x2);
y2 = mmax.apply(0, y2);
return {
x: x,
y: y,
x2: x2,
y2: y2,
width: x2 - x,
height: y2 - y,
cx: x + (x2 - x) / 2,
cy: y + (y2 - y) / 2
};
};
setproto.clone = function (s) {
s = new Set;
for (var i = 0, ii = this.items.length; i < ii; i++) {
s.push(this.items[i].clone());
}
return s;
};
setproto.toString = function () {
return "Savage\u2018s set";
};
setproto.type = "set";
// export
Savage.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
//
// 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.
Savage.plugin(function (Savage, Element, Paper, glob) {
var names = {},
reUnit = /[a-z]+$/i,
Str = String;
names.stroke = names.fill = "colour";
function getEmpty(item) {
var l = item[0];
switch (l.toLowerCase()) {
case "t": return [l, 0, 0];
case "m": return [l, 1, 0, 0, 1, 0, 0];
case "r": if (item.length == 4) {
return [l, 0, item[2], item[3]];
} else {
return [l, 0];
}
case "s": if (item.length == 5) {
return [l, 1, 1, item[3], item[4]];
} else if (item.length == 3) {
return [l, 1, 1];
} else {
return [l, 1];
}
}
}
function equaliseTransform(t1, t2) {
t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
t1 = Savage.parseTransformString(t1) || [];
t2 = Savage.parseTransformString(t2) || [];
var maxlength = Math.max(t1.length, t2.length),
from = [],
to = [],
i = 0, j, jj,
tt1, tt2;
for (; i < maxlength; i++) {
tt1 = t1[i] || getEmpty(t2[i]);
tt2 = t2[i] || getEmpty(tt1);
if ((tt1[0] != tt2[0]) ||
(tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
(tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
) {
return;
}
from[i] = [];
to[i] = [];
for (j = 0, jj = Math.max(tt1.length, tt2.length); j < jj; j++) {
j in tt1 && (from[i][j] = tt1[j]);
j in tt2 && (to[i][j] = tt2[j]);
}
}
return {
from: path2array(from),
to: path2array(to),
f: getPath(from)
};
}
function getNumber(val) {
return val;
}
function getUnit(unit) {
return function (val) {
return +val.toFixed(3) + unit;
};
}
function getColour(clr) {
return Savage.rgb(clr[0], clr[1], clr[2]);
}
function getPath(path) {
var k = 0, i, ii, j, jj, out, a, b = [];
for (i = 0, ii = path.length; i < ii; i++) {
out = "[";
a = ['"' + path[i][0] + '"'];
for (j = 1, jj = path[i].length; j < jj; j++) {
a[j] = "val[" + (k++) + "]";
}
out += a + "]";
b[i] = out;
}
return Function("val", "return Savage.path.toString.call([" + b + "])");
}
function path2array(path) {
var out = [];
for (var i = 0, ii = path.length; i < ii; i++) {
for (var j = 1, jj = path[i].length; j < jj; j++) {
out.push(path[i][j]);
}
}
return out;
}
Element.prototype.equal = function (name, b) {
var A, B, a = Str(this.attr(name) || "");
if (a == +a && b == +b) {
return {
from: +a,
to: +b,
f: getNumber
};
}
if (names[name] == "colour") {
A = Savage.color(a);
B = Savage.color(b);
return {
from: [A.r, A.g, A.b, A.opacity],
to: [B.r, B.g, B.b, B.opacity],
f: getColour
};
}
if (name == "transform" || name == "gradientTransform" || name == "patternTransform") {
// TODO: b could be an SVG transform string or matrix
return equaliseTransform(a.local, b);
}
if (name == "d" || name == "path") {
A = Savage.path.toCubic(a, b);
return {
from: path2array(A[0]),
to: path2array(A[1]),
f: getPath(A[0])
};
}
var aUnit = a.match(reUnit),
bUnit = b.match(reUnit);
if (aUnit && aUnit == bUnit) {
return {
from: parseFloat(a),
to: parseFloat(b),
f: getUnit(aUnit)
};
} else {
return {
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
//
// 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.
Savage.plugin(function (Savage, Element, Paper, glob) {
var elproto = Element.prototype,
has = "hasOwnProperty",
supportsTouch = "createTouch" in glob.doc,
events = [
"click", "dblclick", "mousedown", "mousemove", "mouseout",
"mouseover", "mouseup", "touchstart", "touchmove", "touchend",
"touchcancel"
],
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];
},
preventDefault = function () {
this.returnValue = false;
},
preventTouch = function () {
return this.originalEvent.preventDefault();
},
stopPropagation = function () {
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"),
x = e.clientX + scrollX,
y = e.clientY + scrollY;
if (supportsTouch && touchMap[has](type)) {
for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
if (e.targetTouches[i].target == obj) {
var olde = e;
e = e.targetTouches[i];
e.originalEvent = olde;
e.preventDefault = preventTouch;
e.stopPropagation = stopTouch;
break;
}
}
}
return fn.call(element, e, x, y);
};
obj.addEventListener(realName, f, false);
return function () {
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;
};
}
})(),
drag = [],
dragMove = function (e) {
var x = e.clientX,
y = e.clientY,
scrollY = getScroll("y"),
scrollX = getScroll("x"),
dragi,
j = drag.length;
while (j--) {
dragi = drag[j];
if (supportsTouch) {
var i = e.touches.length,
touch;
while (i--) {
touch = e.touches[i];
if (touch.identifier == dragi.el._drag.id) {
x = touch.clientX;
y = touch.clientY;
(e.originalEvent ? e.originalEvent : e).preventDefault();
break;
}
}
} else {
e.preventDefault();
}
var node = dragi.el.node,
o,
next = node.nextSibling,
parent = node.parentNode,
display = node.style.display;
// glob.win.opera && parent.removeChild(node);
// node.style.display = "none";
// o = dragi.el.paper.getElementByPoint(x, y);
// node.style.display = display;
// glob.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
// o && eve("savage.drag.over." + dragi.el.id, dragi.el, o);
x += scrollX;
y += scrollY;
eve("savage.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
}
},
dragUp = function (e) {
Savage.unmousemove(dragMove).unmouseup(dragUp);
var i = drag.length,
dragi;
while (i--) {
dragi = drag[i];
dragi.el._drag = {};
eve("savage.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
}
drag = [];
};
/*\
* Element.click
[ method ]
**
* Adds event handler for click for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unclick
[ method ]
**
* Removes event handler for click for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.dblclick
[ method ]
**
* Adds event handler for double click for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.undblclick
[ method ]
**
* Removes event handler for double click for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mousedown
[ method ]
**
* Adds event handler for mousedown for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmousedown
[ method ]
**
* Removes event handler for mousedown for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mousemove
[ method ]
**
* Adds event handler for mousemove for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmousemove
[ method ]
**
* Removes event handler for mousemove for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mouseout
[ method ]
**
* Adds event handler for mouseout for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmouseout
[ method ]
**
* Removes event handler for mouseout for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mouseover
[ method ]
**
* Adds event handler for mouseover for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmouseover
[ method ]
**
* Removes event handler for mouseover for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mouseup
[ method ]
**
* Adds event handler for mouseup for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmouseup
[ method ]
**
* Removes event handler for mouseup for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.touchstart
[ method ]
**
* Adds event handler for touchstart for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.untouchstart
[ method ]
**
* Removes event handler for touchstart for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.touchmove
[ method ]
**
* Adds event handler for touchmove for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.untouchmove
[ method ]
**
* Removes event handler for touchmove for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.touchend
[ method ]
**
* Adds event handler for touchend for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.untouchend
[ method ]
**
* Removes event handler for touchend for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.touchcancel
[ method ]
**
* Adds event handler for touchcancel for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.untouchcancel
[ method ]
**
* Removes event handler for touchcancel for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
for (var i = events.length; i--;) {
(function (eventName) {
Savage[eventName] = elproto[eventName] = function (fn, scope) {
if (Savage.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)
});
}
return this;
};
Savage["un" + eventName] = elproto["un" + eventName] = function (fn) {
var events = this.events || [],
l = events.length;
while (l--) if (events[l].name == eventName && events[l].f == fn) {
events[l].unbind();
events.splice(l, 1);
!events.length && delete this.events;
return this;
}
return this;
};
})(events[i]);
}
/*\
* Element.data
[ method ]
**
* Adds or retrieves given value asociated with given key.
**
* See also @Element.removeData
> Parameters
- key (string) key to store data
- value (any) #optional value to store
= (object) @Element
* or, if value is not specified:
= (any) value
> Usage
| for (var i = 0, i < 5, i++) {
| paper.circle(10 + 15 * i, 10, 10)
| .attr({fill: "#000"})
| .data("i", i)
| .click(function () {
| alert(this.data("i"));
| });
| }
\*/
elproto.data = function (key, value) {
var data = eldata[this.id] = eldata[this.id] || {};
if (arguments.length == 1) {
if (Savage.is(key, "object")) {
for (var i in key) if (key[has](i)) {
this.data(i, key[i]);
}
return this;
}
eve("savage.data.get." + this.id, this, data[key], key);
return data[key];
}
data[key] = value;
eve("savage.data.set." + this.id, this, value, key);
return this;
};
/*\
* Element.removeData
[ method ]
**
* Removes value associated with an element by given key.
* If key is not provided, removes all the data of the element.
> Parameters
- key (string) #optional key
= (object) @Element
\*/
elproto.removeData = function (key) {
if (key == null) {
eldata[this.id] = {};
} else {
eldata[this.id] && delete eldata[this.id][key];
}
return this;
};
/*\
* Element.hover
[ method ]
**
* Adds event handlers for hover for the element.
> Parameters
- f_in (function) handler for hover in
- f_out (function) handler for hover out
- icontext (object) #optional context for hover in handler
- ocontext (object) #optional context for hover out handler
= (object) @Element
\*/
elproto.hover = function (f_in, f_out, scope_in, scope_out) {
return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
};
/*\
* Element.unhover
[ method ]
**
* Removes event handlers for hover for the element.
> Parameters
- f_in (function) handler for hover in
- f_out (function) handler for hover out
= (object) @Element
\*/
elproto.unhover = function (f_in, f_out) {
return this.unmouseover(f_in).unmouseout(f_out);
};
var draggable = [];
/*\
* Element.drag
[ method ]
**
* Adds event handlers for drag of the element.
> Parameters
- onmove (function) handler for moving
- onstart (function) handler for drag start
- onend (function) handler for drag end
- mcontext (object) #optional context for moving handler
- scontext (object) #optional context for drag start handler
- econtext (object) #optional context for drag end handler
* Additionaly following `drag` events will be triggered: `drag.start.<id>` on start,
* `drag.end.<id>` on end and `drag.move.<id>` on every move. When element will be dragged over another element
* `drag.over.<id>` will be fired as well.
*
* Start event and start handler will be called in specified context or in context of the element with following parameters:
o x (number) x position of the mouse
o y (number) y position of the mouse
o event (object) DOM event object
* Move event and move handler will be called in specified context or in context of the element with following parameters:
o dx (number) shift by x from the start point
o dy (number) shift by y from the start point
o x (number) x position of the mouse
o y (number) y position of the mouse
o event (object) DOM event object
* End event and end handler will be 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) {
if (!arguments.length) {
var origTransform;
return this.drag(function (dx, dy) {
this.attr({
transform: origTransform + (origTransform ? "T" : "t") + [dx, dy]
});
}, function () {
origTransform = this.transform().local;
});
}
function start(e) {
(e.originalEvent || e).preventDefault();
var scrollY = getScroll("y"),
scrollX = getScroll("x");
this._drag.x = e.clientX + scrollX;
this._drag.y = e.clientY + scrollY;
this._drag.id = e.identifier;
!drag.length && Savage.mousemove(dragMove).mouseup(dragUp);
drag.push({el: this, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
onstart && eve.on("savage.drag.start." + this.id, onstart);
onmove && eve.on("savage.drag.move." + this.id, onmove);
onend && eve.on("savage.drag.end." + this.id, onend);
eve("savage.drag.start." + this.id, start_scope || move_scope || this, e.clientX + scrollX, e.clientY + scrollY, e);
}
this._drag = {};
draggable.push({el: this, start: start});
this.mousedown(start);
return this;
};
/*\
* Element.onDragOver
[ method ]
**
* Shortcut for assigning event handler for `drag.over.<id>` event, where id is id of the element (see @Element.id).
> Parameters
- f (function) handler for event, first argument would be the element you are dragging over
\*/
elproto.onDragOver = function (f) {
f ? eve.on("savage.drag.over." + this.id, f) : eve.unbind("savage.drag.over." + this.id);
};
/*\
* Element.undrag
[ method ]
**
* Removes all drag event handlers from given element.
\*/
elproto.undrag = function () {
var i = draggable.length;
while (i--) if (draggable[i].el == this) {
this.unmousedown(draggable[i].start);
draggable.splice(i, 1);
eve.unbind("savage.drag.*." + this.id);
}
!draggable.length && Savage.unmousemove(dragMove).unmouseup(dragUp);
};
});
// 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.
Savage.plugin(function (Savage, Element, Paper, glob) {
var elproto = Element.prototype,
pproto = Paper.prototype,
rgurl = /^\s*url\((.+)\)/,
Str = String,
$ = Savage._.$;
Savage.filter = {};
pproto.filter = function (filstr) {
var f = Savage.parse(Str(filstr)),
id = Savage._.id(),
width = $(this.node, "width") || this.node.offsetWidth,
height = $(this.node, "height") || this.node.offsetHeight,
filter = $("filter");
$(filter, {
id: id,
filterUnits: "userSpaceOnUse",
x: 0,
y: 0,
width: width,
height: height
});
filter.appendChild(f.node);
this.defs.appendChild(filter);
return new Element(filter);
};
eve.on("savage.util.getattr.filter", function () {
eve.stop();
var p = $(this.node, "filter");
if (p) {
var match = Str(p).match(rgurl);
return match && Savage.select(match[1]);
}
});
eve.on("savage.util.attr.filter", function (value) {
if (value instanceof Element && value.type == "filter") {
eve.stop();
var id = value.node.id;
if (!id) {
$(value.node, {id: value.id});
id = value.id;
}
$(this.node, {
filter: "url(#" + id + ")"
});
}
if (!value) {
eve.stop();
this.node.removeAttribute("filter");
}
});
Savage.filter.blur = function (x, y) {
if (x == null) {
x = 2;
}
var def = y == null ? x : [x, y];
return Savage.format('\<feGaussianBlur stdDeviation="{def}"/>', {
def: def
});
};
Savage.filter.blur.toString = function () {
return this();
};
Savage.filter.shadow = function (dx, dy, blur, color) {
color = Savage.color(color || "#000");
if (blur == null) {
blur = 2;
}
if (dx == null) {
dx = 0;
dy = 4;
}
if (dy == null) {
dy = dx;
}
return Savage.format('<feColorMatrix type="matrix" in="SourceAlpha" result="colored" values="0 0 0 0 {r} 0 0 0 0 {g} 0 0 0 0 {b} 0 0 0 {o} 0"/><feGaussianBlur in="colored" stdDeviation="{blur}" result="blur"/><feOffset in="blur" dx="{dx}" dy="{dy}" result="offsetBlur"/><feMerge><feMergeNode in="offsetBlur"/><feMergeNode in="SourceGraphic"/></feMerge>', {
r: color.r,
g: color.g,
b: color.b,
o: color.opacity,
dx: dx,
dy: dy,
blur: blur
});
};
Savage.filter.shadow.toString = function () {
return this();
};
});