snap.js/dist/savage.js

6801 lines
208 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-09-09
// 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.
var mina = (function (eve) {
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]";
},
idgen = 0,
idprefix = "M" + (+new Date).toString(36),
ID = function () {
return idprefix + (idgen++).toString(36);
},
diff = function (a, b, A, B) {
if (isArray(a)) {
res = [];
for (var i = 0, ii = a.length; i < ii; i++) {
res[i] = diff(a[i], b, A[i], B);
}
return res;
}
var dif = (A - a) / (B - b);
return function (bb) {
return a + dif * (bb - b);
};
},
timer = function () {
return +new Date;
},
sta = function (val) {
var a = this;
if (val == null) {
return a.s;
}
var ds = a.s - val;
a.b += a.dur * ds;
a.B += a.dur * ds;
a.s = val;
},
speed = function (val) {
var a = this;
if (val == null) {
return a.spd;
}
a.spd = val;
},
duration = function (val) {
var a = this;
if (val == null) {
return a.dur;
}
a.s = a.s * val / a.dur;
a.dur = val;
},
stopit = function () {
var a = this;
delete animations[a.id];
eve("mina.stop." + a.id, a);
},
frame = function () {
var len = 0;
for (var i in animations) if (animations.hasOwnProperty(i)) {
var a = animations[i],
b = a.get(),
res;
len++;
a.s = (b - a.b) / (a.dur / a.spd);
if (a.s >= 1) {
delete animations[i];
a.s = 1;
len--;
}
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);
if (a.s == 1) {
eve("mina.finish." + a.id, a);
}
}
len && requestAnimFrame(frame);
},
/*\
* mina
[ method ]
**
* Generic animation of numbers.
**
- a (number) start “slave” number
- A (number) end “slave” number
- b (number) start “master” number (start time in gereal case)
- B (number) end “master” number (end time in gereal case)
- get (function) getter of “master” number (see @mina.time)
- set (function) setter of “slave” number
- easing (function) #optional easing function, default is @mina.linear
= (object) animation descriptor
o {
o id (string) animation id,
o start (number) start “slave” number,
o end (number) end “slave” number,
o b (number) start “master” number,
o s (number) animation status (0..1),
o dur (number) animation duration,
o spd (number) animation speed,
o get (function) getter of “master” number (see @mina.time),
o set (function) setter of “slave” number,
o easing (function) easing function, default is @mina.linear,
o status (function) status getter/setter,
o speed (function) speed getter/setter,
o duration (function) duration getter/setter,
o stop (function) animation stopper
o }
\*/
mina = function (a, A, b, B, get, set, easing) {
var anim = {
id: ID(),
start: a,
end: A,
b: b,
s: 0,
dur: B - b,
spd: 1,
get: get,
set: set,
easing: easing || mina.linear,
status: sta,
speed: speed,
duration: duration,
stop: stopit
};
animations[anim.id] = anim;
var len = 0, i;
for (i in animations) if (animations.hasOwnProperty(i)) {
len++;
if (len == 2) {
break;
}
}
len == 1 && requestAnimFrame(frame);
return anim;
};
/*\
* mina.time
[ method ]
**
* Returns current time. Equal to
| function () {
| return (new Date).getTime();
| }
\*/
mina.time = timer;
/*\
* mina.getById
[ method ]
**
* Returns animation by its id.
- id (string) animations id
= (object) See @mina
\*/
mina.getById = function (id) {
return animations[anim.id] || null;
};
/*\
* mina.linear
[ method ]
**
* Default linear easing.
- n (number) input 0..1
= (number) output 0..1
\*/
mina.linear = function (n) {
return n;
};
/*\
* mina.easeout
[ method ]
**
* Easeout easing.
- n (number) input 0..1
= (number) output 0..1
\*/
mina.easeout = function (n) {
return Math.pow(n, 1.7);
};
/*\
* mina.easein
[ method ]
**
* Easein easing.
- n (number) input 0..1
= (number) output 0..1
\*/
mina.easein = function (n) {
return Math.pow(n, .48);
};
/*\
* mina.easeinout
[ method ]
**
* Easeinout easing.
- n (number) input 0..1
= (number) output 0..1
\*/
mina.easeinout = function (n) {
var q = .48 - n / 1.04,
Q = Math.sqrt(.1734 + q * q),
x = Q - q,
X = Math.pow(Math.abs(x), 1 / 3) * (x < 0 ? -1 : 1),
y = -Q - q,
Y = Math.pow(Math.abs(y), 1 / 3) * (y < 0 ? -1 : 1),
t = X + Y + .5;
return (1 - t) * 3 * t * t + t * t * t;
};
/*\
* mina.backin
[ method ]
**
* Backin easing.
- n (number) input 0..1
= (number) output 0..1
\*/
mina.backin = function (n) {
var s = 1.70158;
return n * n * ((s + 1) * n - s);
};
/*\
* mina.backout
[ method ]
**
* Backout easing.
- n (number) input 0..1
= (number) output 0..1
\*/
mina.backout = function (n) {
n = n - 1;
var s = 1.70158;
return n * n * ((s + 1) * n + s) + 1;
};
/*\
* mina.elastic
[ method ]
**
* Elastic easing.
- n (number) input 0..1
= (number) output 0..1
\*/
mina.elastic = function (n) {
if (n == !!n) {
return n;
}
return Math.pow(2, -10 * n) * Math.sin((n - .075) *
(2 * Math.PI) / .3) + 1;
};
/*\
* mina.bounce
[ method ]
**
* Bounce easing.
- n (number) input 0..1
= (number) output 0..1
\*/
mina.bounce = function (n) {
var s = 7.5625,
p = 2.75,
l;
if (n < (1 / p)) {
l = s * n * n;
} else {
if (n < (2 / p)) {
n -= (1.5 / p);
l = s * n * n + .75;
} else {
if (n < (2.5 / p)) {
n -= (2.25 / p);
l = s * n * n + .9375;
} else {
n -= (2.625 / p);
l = s * n * n + .984375;
}
}
}
return l;
};
return mina;
})(typeof eve == "undefined" ? function () {} : eve);
/*
* Elemental 0.2.2 - Simple JavaScript Tag Parser
*
* Copyright (c) 2010 - 2013 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.2";
(typeof exports == "undefined" ? this : exports).elemental = elemental;
})();
// 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.
var Savage = (function () {
Savage.version = "0.0.1";
/*\
* Savage
[ method ]
**
* Creates drawing surface or wraps existing SVG element.
**
- width (number|string) width of surface
- height (number|string) height of surface
* or
- dom (SVGElement) element to be wrapped into Savage structure
* or
- query (string) CSS query selector
= (object) @Element
\*/
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.
**
- 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;
}
/*\
* Savage.rad
[ method ]
**
* Transform angle to radians
- deg (number) angle in degrees
= (number) angle in radians.
\*/
Savage.rad = rad;
/*\
* Savage.deg
[ method ]
**
* Transform angle to degrees
- deg (number) angle in radians
= (number) angle in degrees.
\*/
Savage.deg = deg;
/*\
* Savage.is
[ method ]
**
* Handfull replacement for `typeof` operator.
- 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.
- 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.
- 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
- x (number)
- y (number)
\*/
matrixproto.translate = function (x, y) {
return this.add(1, 0, 0, 1, x, y);
};
/*\
* Matrix.scale
[ method ]
**
* Scales the matrix
- 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
- 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
- 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
- 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);
/*\
* Savage.Matrix
[ method ]
**
* Utility method
**
* Returns matrix based on given 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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
- 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
- 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 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];
}
}
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.anims = {};
this._ = {
transform: [],
sx: 1,
sy: 1,
deg: 0,
dx: 0,
dy: 0,
dirty: 1
};
el.savage = id;
hub[id] = this;
if (this.type == "g") {
this.add = add2group;
for (var method in Paper.prototype) if (Paper.prototype[has](method)) {
this[method] = Paper.prototype[method];
}
}
}
function arrayFirstValue(arr) {
var res;
for (var i = 0, ii = arr.length; i < ii; i++) {
res = res || arr[i];
if (res) {
return res;
}
}
}
(function (elproto) {
/*\
* Element.attr
[ method ]
**
* Gets or sets given attributes of the element
**
- params (object) key-value pairs of attributes you want to set
* or
- param (string) name of the attribute
= (Element)
* or
= (string) value of attribute
> Usage
| el.attr({
| fill: "#fc0",
| stroke: "#000",
| strokeWidth: 2, // CamelCase...
| "fill-opacity": 0.5 // or dash-separated names
| });
| console.log(el.attr("fill")); // “#fc0”
\*/
elproto.attr = function (params) {
var node = this.node;
if (!params) {
return this;
}
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;
};
/*\
* Element.getBBox
[ method ]
**
* Returns bounding box descriptor for the given element.
**
= (object) bounding box descriptor:
o {
o cx: (number) x of the center,
o cy: (number) x of the center,
o h: (number) height,
o height: (number) height,
o path: (string) path command for the box,
o r0: (number) radius of the circle that will enclose the box,
o r1: (number) radius of the smallest circle that can be enclosed,
o r2: (number) radius of the biggest circle that can be enclosed,
o vb: (string) box as a viewbox command,
o w: (number) width,
o width: (number) width,
o x2: (number) x of the right side,
o x: (number) x of the left side,
o y2: (number) y of the right side,
o y: (number) y of the left side
o }
\*/
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;
};
/*\
* Element.transform
[ method ]
**
* Gets or sets transformation of the element
**
- tstr (string) transform string in Savage or SVG format
= (Element)
* or
= (object) transformation descriptor:
o {
o string (string) transform string,
o globalMatrix (Matrix) matrix of all transformations applied to element or its parents,
o localMatrix (Matrix) matrix of transformations applied only to the element,
o diffMatrix (Matrix) matrix of difference between global and local transformations,
o global (string) global transformation as string,
o local (string) local transformation as string,
o toString (function) returns `string` property
o }
\*/
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;
};
/*\
* Element.parent
[ method ]
**
* Returns parent of the element
**
= (Element) parent
\*/
elproto.parent = function () {
return wrap(this.node.parentNode);
};
/*\
* Element.append
[ method ]
**
* Appends given element to current one.
**
- el (Element|Set) element to append
= (Element) parent
\*/
/*\
* Element.add
[ method ]
**
* See @Element.append.
\*/
elproto.append = elproto.add = 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;
};
/*\
* Element.prepend
[ method ]
**
* Prepends given element to current one.
**
- el (Element) element to prepend
= (Element) parent
\*/
elproto.prepend = function (el) {
el = wrap(el);
this.node.parentNode.insertBefore(el.node, this.node.firstChild);
el.paper = this.paper;
return this;
};
/*\
* Element.before
[ method ]
**
* Inserts given element before the current one.
**
- el (Element) element to insert
= (Element) parent
\*/
// TODO make it work for sets too
elproto.before = function (el) {
el = wrap(el);
this.node.parentNode.insertBefore(el.node, this.node);
el.paper = this.paper;
return this;
};
/*\
* Element.after
[ method ]
**
* Inserts given element after the current one.
**
- el (Element) element to insert
= (Element) parent
\*/
elproto.after = function (el) {
el = wrap(el);
this.node.parentNode.insertBefore(el.node, this.node.nextSibling);
el.paper = this.paper;
return this;
};
/*\
* Element.insertBefore
[ method ]
**
* Inserts the element after the given one.
**
- el (Element) element next to whom insert to
= (Element) parent
\*/
elproto.insertBefore = function (el) {
el = wrap(el);
el.node.parentNode.insertBefore(this.node, el.node);
this.paper = el.paper;
return this;
};
/*\
* Element.insertAfter
[ method ]
**
* Inserts the element after the given one.
**
- el (Element) element next to whom insert to
= (Element) parent
\*/
elproto.insertAfter = function (el) {
el = wrap(el);
el.node.parentNode.insertBefore(this.node, el.node.nextSibling);
this.paper = el.paper;
return this;
};
/*\
* Element.remove
[ method ]
**
* Removes element from the DOM
\*/
elproto.remove = function () {
this.node.parentNode.removeChild(this.node);
delete this.paper;
this.removed = true;
};
/*\
* Element.select
[ method ]
**
* Applies CSS selector with the element as a parent and returns the result as an @Element.
**
- query (string) CSS selector
= (Element) result of query selection
\*/
elproto.select = function (query) {
return wrap(this.node.querySelector(query));
};
/*\
* Element.selectAll
[ method ]
**
* Applies CSS selector with the element as a parent and returns the result as a set or array of elements.
**
- query (string) CSS selector
= (Set|array) result of query selection
\*/
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;
};
/*\
* Element.asPX
[ method ]
**
* Return given attribute of the element as a `px` value. (Not %, em, etc)
**
- attr (string) attribute name
- value (string) #optional attribute value
= (Element) result of query selection
\*/
elproto.asPX = function (attr, value) {
if (value == null) {
value = this.attr(attr);
}
return unit2px(this, attr, value);
};
/*\
* Element.use
[ method ]
**
* Creates `<use>` element linked to the current element.
**
= (Element) `<use>` element
\*/
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;
};
/*\
* Element.clone
[ method ]
**
* Creates `<use>` element linked to the current element.
**
= (Element) `<use>` element
\*/
elproto.clone = function () {
var clone = wrap(this.node.cloneNode(true));
clone.insertAfter(this);
return clone;
};
/*\
* Element.pattern
[ method ]
**
* Creates `<pattern>` element from the current element.
**
* To create a pattern you have to specify the pattern rect:
- x (string|number)
- y (string|number)
- width (string|number)
- height (string|number)
= (Element) `<pattern>` element
* You can use pattern later on as an argument for `fill` attribute:
| var p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({
| fill: "none",
| stroke: "#bada55",
| strokeWidth: 5
| }).pattern(0, 0, 10, 10),
| c = paper.circle(200, 200, 100);
| c.attr({
| fill: p
| });
\*/
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;
};
/*\
* Element.marker
[ method ]
**
* Creates `<marker>` element from the current element.
**
* To create a marker you have to specify the bounding rect and reference point:
- x (number)
- y (number)
- width (number)
- height (number)
- refX (number)
- refY (number)
= (Element) `<marker>` element
* You can use pattern later on as an argument for `marker-start` or `marker-end` attributes.
\*/
// TODO add usage for markers
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 || x.cx;
refY = x.refY || x.cy;
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;
};
}
var Animation = function (attr, ms, easing, callback) {
if (typeof easing == "function") {
callback = easing;
easing = mina.linear;
}
this.attr = attr;
this.dur = ms;
easing && (this.easing = easing);
callback && (this.callback = callback);
};
/*\
* Savage.animation
[ method ]
**
* Creates animation object.
**
- attr (object) attributes of final destination
- ms (number) animation duration
- easing (function) #optional one of easing functions of @mina or custom one
- callback (function) #optional callback
= (object) animation object
\*/
Savage.animation = function (attr, ms, easing, callback) {
return new Animation(attr, ms, easing, callback);
};
/*\
* Element.inAnim
[ method ]
**
* Returns an array of animations element currently in
**
= (object) in format
o {
o anim (object) animation object,
o curStatus (number) 0..1 — status of the animation: 0 — just started, 1 — just finished,
o status (function) gets or sets the status of the animation,
o stop (function) stops the animation
o }
\*/
elproto.inAnim = function () {
var el = this,
res = [];
for (var id in el.anims) if (el.anims[has](id)) {
(function (a) {
res.push({
anim: new Animation(a._attrs, a.dur, a.easing, a._callback),
curStatus: a.status(),
status: function (val) {
return a.status(val);
},
stop: function () {
a.stop();
}
});
}(el.anims[id]));
}
return res;
};
/*\
* Savage.animate
[ method ]
**
* Runs generic animation of one number into another with a caring function.
**
- from (number|array) number or array of numbers
- to (number|array) number or array of numbers
- setter (function) caring function that will take one number argument
- ms (number) duration
- easing (function) #optional easing function from @mina or custom
- callback (function) #optional
= (object) animation object in @mina format
o {
o id (string) animation id, consider it read-only,
o duration (function) gets or sets the duration of the animation,
o easing (function) easing,
o speed (function) gets or sets the speed of the animation,
o status (function) gets or sets the status of the animation,
o stop (function) stops the animation
o }
\*/
Savage.animate = function (from, to, setter, ms, easing, callback) {
if (typeof easing == "function") {
callback = easing;
easing = mina.linear;
}
var now = mina.time(),
anim = mina(from, to, now, now + ms, mina.time, setter, easing);
callback && eve.once("mina.finish." + anim.id, callback);
return anim;
};
/*\
* Element.stop
[ method ]
**
* Stops all the animations of the current element.
**
= (Element) the element
\*/
elproto.stop = function () {
var anims = this.inAnim();
for (var i = 0, ii = anims.length; i < ii; i++) {
anims[i].stop();
}
return this;
};
/*\
* Element.animate
[ method ]
**
* Animate given attributes of the element.
**
- attrs (object) key-value pairs of destination attributes
- ms (number) duration
- easing (function) #optional easing function from @mina or custom
- callback (function) #optional
= (Element) the element
\*/
elproto.animate = function (attrs, ms, easing, callback) {
if (typeof easing == "function") {
callback = easing;
easing = mina.linear;
}
if (attrs instanceof Animation) {
callback = attrs.callback;
easing = attrs.easing;
ms = easing.dur;
attrs = attrs.attr;
}
var fkeys = [], tkeys = [], keys = {}, from, to, f, eq,
el = this;
for (var key in attrs) if (attrs[has](key)) {
if (el.equal) {
eq = el.equal(key, Str(attrs[key]));
from = eq.from;
to = eq.to;
f = eq.f;
} else {
from = +el.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(),
anim = 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);
}, easing);
el.anims[anim.id] = anim;
anim._attrs = attrs;
anim._callback = callback;
eve.once("mina.finish." + anim.id, function () {
delete el.anims[anim.id];
callback && callback.call(el);
});
eve.once("mina.stop." + anim.id, function () {
delete el.anims[anim.id];
});
return el;
};
}(Element.prototype));
/*\
* Savage.parse
[ method ]
**
* Parses SVG fragment and converts it into @Fragment.
**
- svg (string) SVG string
= (Fragment) the fragment
\*/
Savage.parse = function (svg) {
var f = glob.doc.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(glob.doc.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.select
[ method ]
**
* See @Element.select
\*/
Fragment.prototype.select = Element.prototype.select;
/*\
* Fragment.selectAll
[ method ]
**
* See @Element.selectAll
\*/
Fragment.prototype.selectAll = Element.prototype.selectAll;
/*\
* Savage.fragment
[ method ]
**
* Creates DOM fragment from given list of elements or strings
**
- varargs (…) SVG string
= (Fragment) the @Fragment
\*/
Savage.fragment = function () {
var args = Array.prototype.slice.call(arguments, 0),
f = glob.doc.createDocumentFragment();
for (var i = 0, ii = args.length; i < ii; i++) {
var item = args[i];
if (item.node && item.node.nodeType) {
f.appendChild(item.node);
}
if (item.nodeType) {
f.appendChild(item);
}
if (typeof item == "string") {
f.appendChild(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.root = 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) {
/*\
* Paper.el
[ method ]
**
* Creates element on paper with a given name and no attributes.
**
- name (string) tag name
- attr (object) attributes
= (Element) the element
> Usage
| var c = paper.circle(10, 10, 10); // is the same as...
| var c = paper.el("circle").attr({
| cx: 10,
| cy: 10,
| r: 10
| });
\*/
proto.el = function (name, attr) {
return make(name, this.node).attr(attr);
};
/*\
* Paper.rect
[ method ]
*
* Draws a rectangle.
**
- x (number) x coordinate of the top left corner
- y (number) y coordinate of the top left corner
- width (number) width
- height (number) height
- rx (number) #optional horisontal radius for rounded corners, default is 0
- ry (number) #optional vertical radius for rounded corners, default is rx or 0
= (object) Element object with type “rect”
**
> Usage
| // regular rectangle
| var c = paper.rect(10, 10, 50, 50);
| // rectangle with rounded corners
| var c = paper.rect(40, 40, 50, 50, 10);
\*/
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;
};
/*\
* Paper.circle
[ method ]
**
* Draws a circle.
**
- x (number) x coordinate of the centre
- y (number) y coordinate of the centre
- r (number) radius
= (object) Element object with type “circle”
**
> Usage
| var c = paper.circle(50, 50, 40);
\*/
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;
};
/*\
* Paper.image
[ method ]
**
* Embeds an image into the surface.
**
- src (string) URI of the source image
- x (number) x coordinate position
- y (number) y coordinate position
- width (number) width of the image
- height (number) height of the image
= (object) Raphaël element object with type “image”
**
> Usage
| var c = paper.image("apple.png", 10, 10, 80, 80);
\*/
/*\
* Paper.image
[ method ]
**
* Embeds an image into the surface.
**
- src (string) URI of the source image
- x (number) x coordinate position
- y (number) y coordinate position
- width (number) width of the image
- height (number) height of the image
= (object) Element object with type “image”
**
> Usage
| var c = paper.image("apple.png", 10, 10, 80, 80);
\*/
proto.image = function (src, x, y, width, height) {
var el = make("image", this.node);
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;
};
/*\
* Paper.ellipse
[ method ]
**
* Draws an ellipse.
**
- x (number) x coordinate of the centre
- y (number) y coordinate of the centre
- rx (number) horizontal radius
- ry (number) vertical radius
= (object) Element object with type “ellipse”
**
> Usage
| var c = paper.ellipse(50, 50, 40, 20);
\*/
proto.ellipse = function (cx, cy, rx, ry) {
var el = make("ellipse", this.node);
if (is(cx, "object") && "cx" in cx) {
el.attr(cx);
} else if (cx != null) {
el.attr({
cx: cx,
cy: cy,
rx: rx,
ry: ry
});
}
return el;
};
/*\
* Paper.path
[ method ]
**
* Creates a path element by given path data string.
- pathString (string) #optional path string in SVG format.
* Path string consists of one-letter commands, followed by comma seprarated arguments in numercal form. Example:
| "M10,20L30,40"
* Here we can see two commands: “M”, with arguments `(10, 20)` and “L” with arguments `(30, 40)`. Upper case letter mean command is absolute, lower case—relative.
*
# <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a> or <a href="https://developer.mozilla.org/en/SVG/Tutorial/Paths">article about path strings at MDN</a>.</p>
# <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody>
# <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr>
# <tr><td>Z</td><td>closepath</td><td>(none)</td></tr>
# <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr>
# <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr>
# <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr>
# <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr>
# <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr>
# <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr>
# <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr>
# <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr>
# <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/CatmullRom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table>
* * “Catmull-Rom curveto” is a not standard SVG command and added to make life easier.
* Note: there is a special case when path consist of just three commands: “M10,10R…z”. In this case path will smoothly connects to its beginning.
> Usage
| var c = paper.path("M10 10L90 90");
| // draw a diagonal line:
| // move to 10,10, line to 90,90
\*/
proto.path = function (d) {
var el = make("path", this.node);
if (is(d, "object")) {
el.attr(d);
} else if (d) {
el.attr({
d: d
});
}
return el;
};
/*\
* Paper.g
[ method ]
**
* Makes a group element.
**
- varargs (…) #optional elements
= (object) Element object with type “g”
**
> Usage
| var c1 = paper.circle(),
| c2 = paper.rect(),
| g = paper.g(c2, c1); // note that the order of elements will be different
* or
| var c1 = paper.circle(),
| c2 = paper.rect(),
| g = paper.g();
| g.add(c2, c1);
\*/
/*\
* Paper.group
[ method ]
**
* See @Paper.g
\*/
proto.group = proto.g = function (first) {
var el = make("g", this.node);
el.add = add2group;
for (var method in proto) if (proto[has](method)) {
el[method] = proto[method];
}
if (arguments.length == 1 && first && !first.type) {
el.attr(first);
} else if (arguments.length) {
el.add(Array.prototype.slice.call(arguments, 0));
}
return el;
};
/*\
* Paper.text
[ method ]
**
* Draws a text string.
**
- x (number) x coordinate position
- y (number) y coordinate position
- text (string|array) The text string to draw or array of <tspan>s
= (object) Element object with type “text”
**
> Usage
| var t1 = paper.text(50, 50, "Savage");
| var t2 = paper.text(50, 50, ["S","a","v","a","g","e"]);
\*/
proto.text = function (x, y, text) {
var el = make("text", this.node);
if (is(x, "object")) {
el.attr(x);
} else if (x != null) {
el.attr({
x: x,
y: y,
text: text || ""
});
}
return el;
};
/*\
* Paper.line
[ method ]
**
* Draws a line.
**
- x1 (number) x coordinate position of the start
- y1 (number) y coordinate position of the start
- x2 (number) x coordinate position of the end
- y2 (number) y coordinate position of the end
= (object) Element object with type “line”
**
> Usage
| var t1 = paper.line(50, 50, 100, 100);
\*/
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;
};
/*\
* Paper.polyline
[ method ]
**
* Draws a polyline.
**
- points (array) array of points
* or
- varargs (…) points
= (object) Element object with type “text”
**
> Usage
| var p1 = paper.polyline([10, 10, 100, 100]);
| var p2 = paper.polyline(10, 10, 100, 100);
\*/
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;
};
/*\
* Paper.polygon
[ method ]
**
* Draws a polygon. See @Paper.polyline
\*/
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 () {
/*\
* Paper.gradient
[ method ]
**
* Creates a gradient element.
**
- gradient (string) gradient descriptor
> Gradient Descriptor
* Gradient descriptor consists of `<type>(<coords>)<colors>`. Type
* could be linear or radial, which presented as letter “L” or “R”. Any
* type could be absolute or relative, absolute gradient take it coords
* relative to the SVG surface, while relative takes them relative to
* the bounding box of the element it applied to. For absolute
* coordinates you specify type as an upper case letter (“L” or “R”).
* For relative use low case letter (“l” or “r”). Coordinates specify
* vector of gradient for linear as x1, y1, x2, y2. For radial as cx,
* cy, r and optional fx, fy. Colors are list of dash separated colors.
* Optionally color could have offset after colon.
> Example
| var g = paper.gradient("l(0, 0, 1, 1)#000-#f00-#fff");
* Linear gradient, relative from top-left corner to bottom-right
* corner, from black through red to white.
| var g = paper.gradient("L(0, 0, 100, 100)#000-#f00:25%-#fff");
* Linear gradient, absolute from (0, 0) to (100, 100), from black
* through red at 25% to white.
| var g = paper.gradient("r(0.5, 0.5, 0.5)#000-#fff");
* Radial gradient, relative from the center of the element with radius
* 0.5 of the width, from black to white.
| paper.circle(50, 50, 40).attr({
| fill: g
| });
= (object) Element object with type “gradient”
\*/
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.toString
[ method ]
**
* Returns SVG code of the @Paper.
= (string) SVG code of the @Paper.
\*/
proto.toString = function () {
var f = glob.doc.createDocumentFragment(),
d = glob.doc.createElement("div"),
svg = this.node.cloneNode(true),
res;
f.appendChild(d);
d.appendChild(svg);
$(svg, {xmlns: "http://www.w3.org/2000/svg"});
res = d.innerHTML;
f.removeChild(f.firstChild);
return res;
};
}());
}(Paper.prototype));
// simple ajax
/*\
* Savage.ajax
[ method ]
**
* Simple implementation of Ajax.
**
- url (string) URL
- postData (object|string) data for post request
- callback (function) callback
- scope (object) #optional scope of callback
* or
- url (string) URL
- callback (function) callback
- scope (object) #optional scope of callback
= (XMLHttpRequest) XMLHttpRequest (just in case)
\*/
Savage.ajax = function (url, postData, callback, scope){
var req = new XMLHttpRequest,
id = ID();
if (req) {
if (is(postData, "function")) {
scope = callback;
callback = postData;
postData = null;
} else if (is(postData, "object")) {
var pd = [];
for (var key in postData) if (postData.hasOwnProperty(key)) {
pd.push(encodeURIComponent(key) + "=" + encodeURIComponent(postData[key]));
}
postData = pd.join("&");
}
req.open((postData ? "POST" : "GET"), url, true);
req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
if (postData) {
req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
}
if (callback) {
eve.once("savage.ajax." + id + ".0", callback);
eve.once("savage.ajax." + id + ".200", callback);
eve.once("savage.ajax." + id + ".304", callback);
}
req.onreadystatechange = function() {
if (req.readyState != 4) return;
eve("savage.ajax." + id + "." + req.status, scope, req);
};
if (req.readyState == 4) {
return req;
}
req.send(postData);
return req;
}
};
/*\
* Savage.load
[ method ]
**
* Loads external SVG file as a @Fragment. For more advanced AJAX see @Savage.ajax.
**
- url (string) URL
- callback (function) callback
- scope (object) #optional scope of callback
\*/
Savage.load = function (url, callback, scope) {
Savage.ajax(url, function (req) {
var f = Savage.parse(req.responseText);
scope ? callback.call(scope, f) : callback(f);
});
};
// 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.#text", function (value) {
eve.stop();
value = Str(value);
var txt = glob.doc.createTextNode(value);
while (this.node.firstChild) {
this.node.removeChild(this.node.firstChild);
}
this.node.appendChild(txt);
})(-1);
eve.on("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,
tuner = function (chunk) {
var out = $("tspan");
if (is(chunk, "array")) {
for (var i = 0; i < chunk.length; i++) {
out.appendChild(tuner(chunk[i]));
}
} else {
out.appendChild(glob.doc.createTextNode(chunk));
}
out.normalize && out.normalize();
return out;
};
while (node.firstChild) {
node.removeChild(node.firstChild);
}
var tuned = tuner(value);
while (tuned.firstChild) {
node.appendChild(tuned.firstChild);
}
}
eve.stop();
})(-1);
// default
var 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
},
use: {
"class": 0,
externalResourcesRequired: 0,
x: 0,
y: 0,
width: 0,
height: 0,
"xlink:href": 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
},
feDistantLight: {
azimuth: 0,
elevation: 0
},
fePointLight: {
x: 0,
y: 0,
z: 0
},
feSpotLight: {
x: 0,
y: 0,
z: 0,
pointsAtX: 0,
pointsAtY: 0,
pointsAtZ: 0,
specularExponent: 0,
limitingConeAngle: 0
},
feBlend: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
"in": 0,
in2: 0,
mode: 0
},
feColorMatrix: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
"in": 0,
type: 0,
values: 0
},
feComponentTransfer: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
"in": 0
},
feComposite: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
"in": 0,
in2: 0,
operator: 0,
k1: 0,
k2: 0,
k3: 0,
k4: 0
},
feConvolveMatrix: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
"in": 0,
order: 0,
kernelMatrix: 0,
divisor: 0,
bias: 0,
targetX: 0,
targetY: 0,
edgeMode: 0,
kernelUnitLength: 0,
preserveAlpha: 0
},
feDiffuseLighting: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
"in": 0,
surfaceScale: 0,
diffuseConstant: 0,
kernelUnitLength: 0
},
feDisplacementMap: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
"in": 0,
in2: 0,
scale: 0,
xChannelSelector: 0,
yChannelSelector: 0
},
feFlood: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
"flood-color": 0,
"flood-opacity": 0
},
feGaussianBlur: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
"in": 0,
stdDeviation: 0
},
feImage : {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
externalResourcesRequired: 0,
preserveAspectRatio: 0,
"xlink:href": 0
},
feMerge: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0
},
feMergeNode: {
"in": 0
},
feMorphology: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
"in": 0,
operator: 0,
radius: 0
},
feOffset: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
"in": 0,
dx: 0,
dy: 0
},
feSpecularLighting: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
"in": 0,
surfaceScale: 0,
specularConstant: 0,
specularExponent: 0,
kernelUnitLength: 0
},
feTile: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
"in": 0
},
feTurbulence: {
height: 0,
result: 0,
width: 0,
x: 0,
y: 0,
"class": 0,
style: 0,
baseFrequency: 0,
numOctaves: 0,
seed: 0,
stitchTiles: 0,
type: 0
}
};
availableAttributes.feFuncR = availableAttributes.feFuncG = availableAttributes.feFuncB = availableAttributes.feFuncA = {
type: 0,
tableValues: 0,
slope: 0,
intercept: 0,
amplitude: 0,
exponent: 0,
offset: 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);
function textExtract(node) {
var out = [];
var children = node.childNodes;
for (var i = 0, ii = children.length; i < ii; i++) {
var chi = children[i];
if (chi.nodeType == 3) {
out.push(chi.nodeValue);
}
if (chi.tagName == "tspan") {
if (chi.childNodes.length == 1 && chi.firstChild.nodeType == 3) {
out.push(chi.firstChild.nodeValue);
} else {
out.push(textExtract(chi));
}
}
}
return out;
}
eve.on("savage.util.getattr.text", function () {
if (this.type == "text" || this.type == "tspan") {
eve.stop();
var out = textExtract(this.node);
return out.length == 1 ? out[0] : out;
}
})(-1);
eve.on("savage.util.getattr.#text", function () {
return this.node.textContent;
})(-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) {
if (path instanceof Element) {
path = path.attr("d");
}
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.
**
- 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.
**
- 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.
**
- 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.
**
- 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.
**
- 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 Savage.path.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.
- 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
- 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.
- 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
- 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
- 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.
- 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
- 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.
- path (string) path string
= (array) path string
\*/
Savage.path.toRelative = pathToRelative;
/*\
* Savage.path.toAbsolute
[ method ]
**
* Utility method
**
* Converts path coordinates into absolute values.
- 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.
- 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.
- 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.
**
- 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
**
- 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
**
- 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, 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.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unclick
[ method ]
**
* Removes event handler for click for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.dblclick
[ method ]
**
* Adds event handler for double click for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.undblclick
[ method ]
**
* Removes event handler for double click for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mousedown
[ method ]
**
* Adds event handler for mousedown for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmousedown
[ method ]
**
* Removes event handler for mousedown for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mousemove
[ method ]
**
* Adds event handler for mousemove for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmousemove
[ method ]
**
* Removes event handler for mousemove for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mouseout
[ method ]
**
* Adds event handler for mouseout for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmouseout
[ method ]
**
* Removes event handler for mouseout for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mouseover
[ method ]
**
* Adds event handler for mouseover for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmouseover
[ method ]
**
* Removes event handler for mouseover for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mouseup
[ method ]
**
* Adds event handler for mouseup for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmouseup
[ method ]
**
* Removes event handler for mouseup for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.touchstart
[ method ]
**
* Adds event handler for touchstart for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.untouchstart
[ method ]
**
* Removes event handler for touchstart for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.touchmove
[ method ]
**
* Adds event handler for touchmove for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.untouchmove
[ method ]
**
* Removes event handler for touchmove for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.touchend
[ method ]
**
* Adds event handler for touchend for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.untouchend
[ method ]
**
* Removes event handler for touchend for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.touchcancel
[ method ]
**
* Adds event handler for touchcancel for the element.
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.untouchcancel
[ method ]
**
* Removes event handler for touchcancel for the element.
- 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
- 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.
- 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.
- 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.
- 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.
- 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).
- 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 = {};
/*\
* Paper.filter
[ method ]
**
* Creates filter element
**
- filstr (string) SVG fragment of filter provided as a string.
= (object) @Element
* Note: It is recommended to use filters embedded into page inside empty SVG element.
> Usage
| var f = paper.filter('<feGaussianBlur stdDeviation="2"/>'),
| c = paper.circle(10, 10, 10).attr({
| filter: f
| });
\*/
pproto.filter = function (filstr) {
var f = Savage.parse(Str(filstr)),
id = Savage._.id(),
width = this.node.offsetWidth,
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 || value == "none") {
eve.stop();
this.node.removeAttribute("filter");
}
});
/*\
* Paper.filter.blur
[ method ]
**
* Returns string of the blur filter.
**
- x (number) amount of horisontal blur in px.
- y (number) #optional amount of vertical blur in px.
= (string) filter representation
> Usage
| var f = paper.filter(Savage.filter.blur(5, 10)),
| c = paper.circle(10, 10, 10).attr({
| filter: f
| });
\*/
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();
};
/*\
* Paper.filter.blur
[ method ]
**
* Returns string of the blur filter.
**
- dx (number) horisontal shift of the shadow in px.
- dy (number) vertical shift of the shadow in px.
- blur (number) #optional amount of blur.
- color (string) #optional color of the shadow.
= (string) filter representation
> Usage
| var f = paper.filter(Savage.filter.shadow(5, 10)),
| c = paper.circle(10, 10, 10).attr({
| filter: f
| });
\*/
Savage.filter.shadow = function (dx, dy, blur, color) {
color = Savage.color(color || "#000");
if (blur == null) {
blur = 4;
}
if (dx == null) {
dx = 0;
dy = 2;
}
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();
};
});