Markers, Animation of colour, transform and path, mouse and touch events

master
Dmitry Baranovskiy 2013-07-15 22:57:30 +10:00
parent 2270a96f12
commit 8766ae2c79
5 changed files with 995 additions and 98 deletions

View File

@ -13,6 +13,8 @@
<script src="elemental.js"></script> <script src="elemental.js"></script>
<script src="svg.js"></script> <script src="svg.js"></script>
<script src="savage.set.js"></script> <script src="savage.set.js"></script>
<script src="savage.equal.js"></script>
<script src="savage.mouse.js"></script>
<script type="text/x-svg" id="svg"> <script type="text/x-svg" id="svg">
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
@ -30,66 +32,88 @@
</script> </script>
<script> <script>
window.onload = function () { window.onload = function () {
// 1 // 1;
var s = Savage("100%", 600); // var s = Savage("100%", 600);
// 2 //
var c = s.circle(100, 100, 50); // 2;
// 3 // var c = s.circle(100, 100, 50);
c.attr({ //
fill: "#bada55", // 3;
stroke: "#000", // c.attr({
"stroke-width": 5 // fill: "#bada55",
}); // stroke: "#000",
// 4 // "stroke-width": 5
var c2 = s.circle(70, 100, 40); // });
var g = s.group(c2, s.circle(130, 100, 40)); //
g.attr({ // 4;
fill: "#fff" // var c2 = s.circle(70, 100, 40);
}); // var g = s.group(c2, s.circle(130, 100, 40));
// 5 // g.attr({
c.attr({ // fill: "#fff"
mask: g // });
}); //
// 6 // 5;
c2.animate({r: 25}, 1000); // c.attr({
// 7 // mask: g
g.select("circle:nth-child(2)").animate({r: 25}, 1000); // });
// 8 //
var p = s.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({ // 6;
fill: "none", // c2.animate({r: 25}, 1000);
stroke: "#bada55", //
strokeWidth: 5 // 7;
}).pattern(0, 0, 10, 10); // g.select("circle:nth-child(2)").animate({r: 25}, 1000);
c.attr({ //
fill: p // 8;
}); // var p = s.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({
// 9 // fill: "none",
g.attr({ // stroke: "#bada55",
fill: Savage(document.getElementById("pattern")) // strokeWidth: 5
}); // }).pattern(0, 0, 10, 10);
// 10 // c.attr({
g.attr({fill: "r()#fff-#000"}); // fill: p
// 11 // });
g.attr({fill: "R(100, 100, 50)#fff-#000"}); //
// 12 // 9;
var svg = document.getElementById("svg").text; // g.attr({
var f = Savage.fragment(svg); // fill: Savage(document.getElementById("pattern"))
f.select("path[fill='#D40000']").attr({fill: "#bada55"}); // });
g = s.group(f.selectAll("path")); //
s.append(g); // 10;
// 13 // g.attr({fill: "r()#fff-#000"});
g.attr({ //
transform: "r45t100,0s.5" // 11;
}); // g.attr({fill: "R(100, 100, 50)#fff-#000"});
//
// 12;
// p.select("path").animate({stroke: "#f00"}, 1000);
//
// 13;
// var svg = document.getElementById("svg").text;
// var f = Savage.fragment(svg);
// // f.select("path[fill='#D40000']").attr({fill: "#bada55"});
// g = s.group(f.selectAll("path"));
// s.append(g);
//
// 14;
// g.attr({
// transform: "r45t100,0s.5"
// });
//
// 15;
// console.log(g.attr("transform")); // console.log(g.attr("transform"));
// 14 //
s.text(200, 100, "SAVAGE"); // 16;
// 15 // g.drag();
var t = s.text(200, 120, "SAVAGE".split("")); //
t.selectAll("tspan tspan:nth-child(odd)").attr({ // 17;
fill: "#900", // s.text(200, 100, "SAVAGE");
"font-size": "20px" //
}); // 18;
// var t = s.text(200, 120, "SAVAGE".split(""));
// t.selectAll("tspan tspan:nth-child(odd)").attr({
// fill: "#900",
// "font-size": "20px"
// });
}; };
</script> </script>
</head> </head>

29
mina.js
View File

@ -21,7 +21,18 @@ window.mina = (function () {
function (callback) { function (callback) {
setTimeout(callback, 16); setTimeout(callback, 16);
}, },
isArray = Array.isArray || function (a) {
return a instanceof Array ||
Object.prototype.toString.call(a) == "[object Array]";
},
diff = function (a, b, A, B) { 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); var dif = (A - a) / (B - b);
return function (bb) { return function (bb) {
return a + dif * (bb - b); return a + dif * (bb - b);
@ -31,12 +42,24 @@ window.mina = (function () {
return +new Date; return +new Date;
}, },
frame = function () { frame = function () {
var value, one;
for (var i = 0; i < animations.length; i++) { for (var i = 0; i < animations.length; i++) {
var a = animations[i], var a = animations[i],
gen = a.b + (a.gen() - a.b) * a["*"] + a["+"], gen = a.b + (a.gen() - a.b) * a["*"] + a["+"];
value = a.dif(gen), if (isArray(a.a)) {
value = [];
for (var j = 0, jj = a.a.length; j < jj; j++) {
value[j] = a.dif[j](gen);
one = a.A[j] - a.a[j];
value[j] = one ?
a.a[j] + a.easing((value[j] - a.a[j]) / one) * one :
a.a[j];
}
} else {
value = a.dif(gen);
one = a.A - a.a; one = a.A - a.a;
value = a.a + a.easing((value - a.a) / one) * one; value = a.a + a.easing((value - a.a) / one) * one;
}
try { try {
if (a.stopper(gen)) { if (a.stopper(gen)) {
if (--a.iterations) { if (--a.iterations) {

135
savage.equal.js Normal file
View File

@ -0,0 +1,135 @@
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.path2string.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],
to: [B.r, B.g, B.b],
f: getColour
};
}
if (name == "transform" || name == "gradientTransform" || name == "patternTransform") {
// TODO: b could be an SVG transform string or matrix
return equaliseTransform(a.local, b);
}
if (name == "d" || name == "path") {
A = Savage.path2curve(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
};
}
};
});

540
savage.mouse.js Normal file
View File

@ -0,0 +1,540 @@
Savage.plugin(function (Savage, Element, Paper, glob) {
var elproto = Element.prototype,
has = "hasOwnProperty",
supportsTouch = "createTouch" in glob.doc,
events = [
"click", "dblclick", "mousedown", "mousemove", "mouseout",
"mouseover", "mouseup", "touchstart", "touchmove", "touchend",
"touchcancel"
],
touchMap = {
mousedown: "touchstart",
mousemove: "touchmove",
mouseup: "touchend"
},
getScroll = function (xy) {
var name = xy == "y" ? "scrollTop" : "scrollLeft";
return glob.doc.documentElement[name] || glob.doc.body[name];
},
preventDefault = function () {
this.returnValue = false;
},
preventTouch = function () {
return this.originalEvent.preventDefault();
},
stopPropagation = function () {
this.cancelBubble = true;
},
stopTouch = function () {
return this.originalEvent.stopPropagation();
},
addEvent = (function () {
if (glob.doc.addEventListener) {
return function (obj, type, fn, element) {
var realName = supportsTouch && touchMap[type] ? touchMap[type] : type,
f = function (e) {
var scrollY = getScroll("y"),
scrollX = getScroll("x"),
x = e.clientX + scrollX,
y = e.clientY + scrollY;
if (supportsTouch && touchMap[has](type)) {
for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
if (e.targetTouches[i].target == obj) {
var olde = e;
e = e.targetTouches[i];
e.originalEvent = olde;
e.preventDefault = preventTouch;
e.stopPropagation = stopTouch;
break;
}
}
}
return fn.call(element, e, x, y);
};
obj.addEventListener(realName, f, false);
return function () {
obj.removeEventListener(realName, f, false);
return true;
};
};
} else if (glob.doc.attachEvent) {
return function (obj, type, fn, element) {
var f = function (e) {
e = e || glob.win.event;
var scrollY = getScroll("y"),
scrollX = getScroll("x"),
x = e.clientX + scrollX,
y = e.clientY + scrollY;
e.preventDefault = e.preventDefault || preventDefault;
e.stopPropagation = e.stopPropagation || stopPropagation;
return fn.call(element, e, x, y);
};
obj.attachEvent("on" + type, f);
var detacher = function () {
obj.detachEvent("on" + type, f);
return true;
};
return detacher;
};
}
})(),
drag = [],
dragMove = function (e) {
var x = e.clientX,
y = e.clientY,
scrollY = getScroll("y"),
scrollX = getScroll("x"),
dragi,
j = drag.length;
while (j--) {
dragi = drag[j];
if (supportsTouch) {
var i = e.touches.length,
touch;
while (i--) {
touch = e.touches[i];
if (touch.identifier == dragi.el._drag.id) {
x = touch.clientX;
y = touch.clientY;
(e.originalEvent ? e.originalEvent : e).preventDefault();
break;
}
}
} else {
e.preventDefault();
}
var node = dragi.el.node,
o,
next = node.nextSibling,
parent = node.parentNode,
display = node.style.display;
// glob.win.opera && parent.removeChild(node);
// node.style.display = "none";
// o = dragi.el.paper.getElementByPoint(x, y);
// node.style.display = display;
// glob.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
// o && eve("savage.drag.over." + dragi.el.id, dragi.el, o);
x += scrollX;
y += scrollY;
eve("savage.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
}
},
dragUp = function (e) {
Savage.unmousemove(dragMove).unmouseup(dragUp);
var i = drag.length,
dragi;
while (i--) {
dragi = drag[i];
dragi.el._drag = {};
eve("savage.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
}
drag = [];
};
/*\
* Element.click
[ method ]
**
* Adds event handler for click for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unclick
[ method ]
**
* Removes event handler for click for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.dblclick
[ method ]
**
* Adds event handler for double click for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.undblclick
[ method ]
**
* Removes event handler for double click for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mousedown
[ method ]
**
* Adds event handler for mousedown for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmousedown
[ method ]
**
* Removes event handler for mousedown for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mousemove
[ method ]
**
* Adds event handler for mousemove for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmousemove
[ method ]
**
* Removes event handler for mousemove for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mouseout
[ method ]
**
* Adds event handler for mouseout for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmouseout
[ method ]
**
* Removes event handler for mouseout for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mouseover
[ method ]
**
* Adds event handler for mouseover for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmouseover
[ method ]
**
* Removes event handler for mouseover for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.mouseup
[ method ]
**
* Adds event handler for mouseup for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.unmouseup
[ method ]
**
* Removes event handler for mouseup for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.touchstart
[ method ]
**
* Adds event handler for touchstart for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.untouchstart
[ method ]
**
* Removes event handler for touchstart for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.touchmove
[ method ]
**
* Adds event handler for touchmove for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.untouchmove
[ method ]
**
* Removes event handler for touchmove for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.touchend
[ method ]
**
* Adds event handler for touchend for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.untouchend
[ method ]
**
* Removes event handler for touchend for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.touchcancel
[ method ]
**
* Adds event handler for touchcancel for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
/*\
* Element.untouchcancel
[ method ]
**
* Removes event handler for touchcancel for the element.
> Parameters
- handler (function) handler for the event
= (object) @Element
\*/
for (var i = events.length; i--;) {
(function (eventName) {
Savage[eventName] = elproto[eventName] = function (fn, scope) {
if (Savage.is(fn, "function")) {
this.events = this.events || [];
this.events.push({
name: eventName,
f: fn,
unbind: addEvent(this.shape || this.node || glob.doc, eventName, fn, scope || this)
});
}
return this;
};
Savage["un" + eventName] = elproto["un" + eventName] = function (fn) {
var events = this.events || [],
l = events.length;
while (l--) if (events[l].name == eventName && events[l].f == fn) {
events[l].unbind();
events.splice(l, 1);
!events.length && delete this.events;
return this;
}
return this;
};
})(events[i]);
}
/*\
* Element.data
[ method ]
**
* Adds or retrieves given value asociated with given key.
**
* See also @Element.removeData
> Parameters
- key (string) key to store data
- value (any) #optional value to store
= (object) @Element
* or, if value is not specified:
= (any) value
> Usage
| for (var i = 0, i < 5, i++) {
| paper.circle(10 + 15 * i, 10, 10)
| .attr({fill: "#000"})
| .data("i", i)
| .click(function () {
| alert(this.data("i"));
| });
| }
\*/
elproto.data = function (key, value) {
var data = eldata[this.id] = eldata[this.id] || {};
if (arguments.length == 1) {
if (Savage.is(key, "object")) {
for (var i in key) if (key[has](i)) {
this.data(i, key[i]);
}
return this;
}
eve("savage.data.get." + this.id, this, data[key], key);
return data[key];
}
data[key] = value;
eve("savage.data.set." + this.id, this, value, key);
return this;
};
/*\
* Element.removeData
[ method ]
**
* Removes value associated with an element by given key.
* If key is not provided, removes all the data of the element.
> Parameters
- key (string) #optional key
= (object) @Element
\*/
elproto.removeData = function (key) {
if (key == null) {
eldata[this.id] = {};
} else {
eldata[this.id] && delete eldata[this.id][key];
}
return this;
};
/*\
* Element.hover
[ method ]
**
* Adds event handlers for hover for the element.
> Parameters
- f_in (function) handler for hover in
- f_out (function) handler for hover out
- icontext (object) #optional context for hover in handler
- ocontext (object) #optional context for hover out handler
= (object) @Element
\*/
elproto.hover = function (f_in, f_out, scope_in, scope_out) {
return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
};
/*\
* Element.unhover
[ method ]
**
* Removes event handlers for hover for the element.
> Parameters
- f_in (function) handler for hover in
- f_out (function) handler for hover out
= (object) @Element
\*/
elproto.unhover = function (f_in, f_out) {
return this.unmouseover(f_in).unmouseout(f_out);
};
var draggable = [];
/*\
* Element.drag
[ method ]
**
* Adds event handlers for drag of the element.
> Parameters
- onmove (function) handler for moving
- onstart (function) handler for drag start
- onend (function) handler for drag end
- mcontext (object) #optional context for moving handler
- scontext (object) #optional context for drag start handler
- econtext (object) #optional context for drag end handler
* Additionaly following `drag` events will be triggered: `drag.start.<id>` on start,
* `drag.end.<id>` on end and `drag.move.<id>` on every move. When element will be dragged over another element
* `drag.over.<id>` will be fired as well.
*
* Start event and start handler will be called in specified context or in context of the element with following parameters:
o x (number) x position of the mouse
o y (number) y position of the mouse
o event (object) DOM event object
* Move event and move handler will be called in specified context or in context of the element with following parameters:
o dx (number) shift by x from the start point
o dy (number) shift by y from the start point
o x (number) x position of the mouse
o y (number) y position of the mouse
o event (object) DOM event object
* End event and end handler will be called in specified context or in context of the element with following parameters:
o event (object) DOM event object
= (object) @Element
\*/
elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
if (!arguments.length) {
var origTransform;
return this.drag(function (dx, dy) {
this.attr({
transform: origTransform + (origTransform ? "T" : "t") + [dx, dy]
});
}, function () {
origTransform = this.transform().local;
});
}
function start(e) {
(e.originalEvent || e).preventDefault();
var scrollY = getScroll("y"),
scrollX = getScroll("x");
this._drag.x = e.clientX + scrollX;
this._drag.y = e.clientY + scrollY;
this._drag.id = e.identifier;
!drag.length && Savage.mousemove(dragMove).mouseup(dragUp);
drag.push({el: this, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
onstart && eve.on("savage.drag.start." + this.id, onstart);
onmove && eve.on("savage.drag.move." + this.id, onmove);
onend && eve.on("savage.drag.end." + this.id, onend);
eve("savage.drag.start." + this.id, start_scope || move_scope || this, e.clientX + scrollX, e.clientY + scrollY, e);
}
this._drag = {};
draggable.push({el: this, start: start});
this.mousedown(start);
return this;
};
/*\
* Element.onDragOver
[ method ]
**
* Shortcut for assigning event handler for `drag.over.<id>` event, where id is id of the element (see @Element.id).
> Parameters
- f (function) handler for event, first argument would be the element you are dragging over
\*/
elproto.onDragOver = function (f) {
f ? eve.on("savage.drag.over." + this.id, f) : eve.unbind("savage.drag.over." + this.id);
};
/*\
* Element.undrag
[ method ]
**
* Removes all drag event handlers from given element.
\*/
elproto.undrag = function () {
var i = draggable.length;
while (i--) if (draggable[i].el == this) {
this.unmousedown(draggable[i].start);
draggable.splice(i, 1);
eve.unbind("savage.drag.*." + this.id);
}
!draggable.length && Savage.unmousemove(dragMove).unmouseup(dragUp);
};
});

247
svg.js
View File

@ -37,6 +37,7 @@ var has = "hasOwnProperty",
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, 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}, isnan = {"NaN": 1, "Infinity": 1, "-Infinity": 1},
bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/, 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", 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 + "]+"), separator = new RegExp("[," + spaces + "]+"),
whitespace = new RegExp("[" + spaces + "]", "g"), whitespace = new RegExp("[" + spaces + "]", "g"),
@ -860,15 +861,105 @@ function box(x, y, width, height) {
}; };
} }
// Transformations // Transformations
function path2string() { var path2string = Savage.path2string = function () {
return this.join(",").replace(p2s, "$1"); return this.join(",").replace(p2s, "$1");
} };
function pathClone(pathArray) { function pathClone(pathArray) {
var res = clone(pathArray); var res = clone(pathArray);
res.toString = path2string; res.toString = path2string;
return res; return res;
} }
function parseTransformString(TString) {
// 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;
}
/*\
* Savage.parsePathString
[ method ]
**
* Utility method
**
* Parses given path string into an array of arrays of path segments.
> Parameters
- pathString (string|array) path string or array of segments (in the last case it will be returned straight away)
= (array) array of segments.
\*/
Savage.parsePathString = function (pathString) {
if (!pathString) {
return null;
}
var pth = paths(pathString);
if (pth.arr) {
return pathClone(pth.arr);
}
var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0},
data = [];
if (is(pathString, "array") && is(pathString[0], "array")) { // rough assumption
data = pathClone(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 == "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 = path2string;
pth.arr = pathClone(data);
return data;
};
var parseTransformString = Savage.parseTransformString = function (TString) {
if (!TString) { if (!TString) {
return null; return null;
} }
@ -889,7 +980,7 @@ function parseTransformString(TString) {
} }
data.toString = path2string; data.toString = path2string;
return data; return data;
} };
function svgTransform2string(tstr) { function svgTransform2string(tstr) {
var res = []; var res = [];
tstr = tstr.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) { tstr = tstr.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) {
@ -1086,7 +1177,7 @@ function ellipsePath(x, y, rx, ry, a) {
res.toString = path2string; res.toString = path2string;
return res; return res;
} }
function unit2px(el) { function unit2px(el, name, value) {
var defs = el.paper.defs, var defs = el.paper.defs,
out = {}, out = {},
mgr = el.paper.measurer; mgr = el.paper.measurer;
@ -1115,28 +1206,57 @@ function unit2px(el) {
$(mgr, {height: val}); $(mgr, {height: val});
return mgr.getBBox().height; 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) { switch (el.type) {
case "rect": case "rect":
out.rx = getW(el.attr("rx")); set("rx", getW);
out.ry = getH(el.attr("ry")); set("ry", getH);
case "image": case "image":
out.width = getW(el.attr("width")); set("width", getW);
out.height = getH(el.attr("height")); set("height", getH);
case "text": case "text":
out.x = getW(el.attr("x")); set("x", getW);
out.y = getH(el.attr("y")); set("y", getH);
break; break;
case "circle": case "circle":
out.cx = getW(el.attr("cx")); set("cx", getW);
out.cy = getH(el.attr("cy")); set("cy", getH);
out.r = getW(el.attr("r")); set("r", getW);
break; break;
case "ellipse": case "ellipse":
out.cx = getW(el.attr("cx")); set("cx", getW);
out.cy = getH(el.attr("cy")); set("cy", getH);
out.rx = getW(el.attr("rx")); set("rx", getW);
out.ry = getH(el.attr("ry")); set("ry", getH);
break; 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; return out;
} }
@ -1223,7 +1343,7 @@ var pathDimensions = function (path) {
return pathClone(pth.rel); return pathClone(pth.rel);
} }
if (!is(pathArray, array) || !is(pathArray && pathArray[0], array)) { // rough assumption if (!is(pathArray, array) || !is(pathArray && pathArray[0], array)) { // rough assumption
pathArray = parsePathString(pathArray); pathArray = Savage.parsePathString(pathArray);
} }
var res = [], var res = [],
x = 0, x = 0,
@ -1302,7 +1422,7 @@ var pathDimensions = function (path) {
return pathClone(pth.abs); return pathClone(pth.abs);
} }
if (!is(pathArray, "array") || !is(pathArray && pathArray[0], "array")) { // rough assumption if (!is(pathArray, "array") || !is(pathArray && pathArray[0], "array")) { // rough assumption
pathArray = parsePathString(pathArray); pathArray = Savage.parsePathString(pathArray);
} }
if (!pathArray || !pathArray.length) { if (!pathArray || !pathArray.length) {
return [["M", 0, 0]]; return [["M", 0, 0]];
@ -1565,7 +1685,7 @@ var pathDimensions = function (path) {
max: {x: mmax.apply(0, x), y: mmax.apply(0, y)} max: {x: mmax.apply(0, x), y: mmax.apply(0, y)}
}; };
}), }),
path2curve = cacher(function (path, path2) { path2curve = Savage.path2curve = cacher(function (path, path2) {
var pth = !path2 && paths(path); var pth = !path2 && paths(path);
if (!path2 && pth.curve) { if (!path2 && pth.curve) {
return pathClone(pth.curve); return pathClone(pth.curve);
@ -1764,17 +1884,9 @@ function arrayFirstValue(arr) {
} }
return box(_.bbox); return box(_.bbox);
}; };
function prop(name) { var propString = function () {
return function () { return this.local;
return this[name]; };
};
}
function always(x) {
return function () {
return x;
};
}
var propString = prop("string");
elproto.transform = function (tstr) { elproto.transform = function (tstr) {
var _ = this._; var _ = this._;
if (tstr == null) { if (tstr == null) {
@ -1864,6 +1976,9 @@ function arrayFirstValue(arr) {
} }
return set; return set;
}; };
elproto.asPX = function (attr, value) {
return unit2px(this, attr, value);
};
elproto.use = function () { elproto.use = function () {
var use, var use,
id = this.node.id; id = this.node.id;
@ -1939,17 +2054,22 @@ function arrayFirstValue(arr) {
return p; return p;
}; };
// animation // animation
function applyAttr(el, key) { function applyAttr(el, key, f) {
var at = {}; var at = {};
return function (value) { return function (value) {
at[key] = value; at[key] = f ? f(value) : value;
el.attr(at); el.attr(at);
}; };
} }
elproto.animate = function (attrs, ms, callback) { elproto.animate = function (attrs, ms, callback) {
var anims = []; var anims = [], eq;
for (var key in attrs) if (attrs[has](key)) { for (var key in attrs) if (attrs[has](key)) {
anims.push(mina(+this.attr(key), +attrs[key], ms, applyAttr(this, key))); if (this.equal) {
eq = this.equal(key, Str(attrs[key]));
anims.push(mina(eq.from, eq.to, ms, applyAttr(this, key, eq.f)));
} else {
anims.push(mina(+this.attr(key), +attrs[key], ms, applyAttr(this, key)));
}
} }
}; };
}(Element.prototype)); }(Element.prototype));
@ -2436,6 +2556,17 @@ eve.on("savage.util.grad.parse", function parseGrad(string) {
}; };
}); });
eve.on("savage.util.attr.d", function (value) {
if (is(value, "array") && is(value[0], "array")) {
value = path2string.call(value);
}
value = Str(value);
if (value.match(/[ruo]/i)) {
value = pathToAbsolute(value);
}
$(this.node, {d: value});
eve.stop();
});
eve.on("savage.util.attr.path", function (value) { eve.on("savage.util.attr.path", function (value) {
this.attr({d: value}); this.attr({d: value});
eve.stop(); eve.stop();
@ -2528,6 +2659,50 @@ eve.on("savage.util.getattr.transform", function () {
eve.stop(); eve.stop();
return this.transform(); return this.transform();
})(-1); })(-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 () { eve.on("savage.util.getattr.r", function () {
if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) { if (this.type == "rect" && $(this.node, "rx") == $(this.node, "ry")) {
eve.stop(); eve.stop();