From 8766ae2c7939eecfd932914f46810fd6d052381c Mon Sep 17 00:00:00 2001 From: Dmitry Baranovskiy Date: Mon, 15 Jul 2013 22:57:30 +1000 Subject: [PATCH] Markers, Animation of colour, transform and path, mouse and touch events --- demojs.html | 142 +++++++------ mina.js | 29 ++- savage.equal.js | 135 ++++++++++++ savage.mouse.js | 540 ++++++++++++++++++++++++++++++++++++++++++++++++ svg.js | 247 ++++++++++++++++++---- 5 files changed, 995 insertions(+), 98 deletions(-) create mode 100644 savage.equal.js create mode 100644 savage.mouse.js diff --git a/demojs.html b/demojs.html index 65dee12..22483a2 100644 --- a/demojs.html +++ b/demojs.html @@ -13,6 +13,8 @@ + + diff --git a/mina.js b/mina.js index 7a716f7..b8c9b4c 100644 --- a/mina.js +++ b/mina.js @@ -21,7 +21,18 @@ window.mina = (function () { function (callback) { setTimeout(callback, 16); }, + isArray = Array.isArray || function (a) { + return a instanceof Array || + Object.prototype.toString.call(a) == "[object Array]"; + }, diff = function (a, b, A, B) { + if (isArray(a)) { + res = []; + for (var i = 0, ii = a.length; i < ii; i++) { + res[i] = diff(a[i], b, A[i], B); + } + return res; + } var dif = (A - a) / (B - b); return function (bb) { return a + dif * (bb - b); @@ -31,12 +42,24 @@ window.mina = (function () { return +new Date; }, frame = function () { + var value, one; for (var i = 0; i < animations.length; i++) { var a = animations[i], - gen = a.b + (a.gen() - a.b) * a["*"] + a["+"], - value = a.dif(gen), + gen = a.b + (a.gen() - a.b) * a["*"] + a["+"]; + 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; - value = a.a + a.easing((value - a.a) / one) * one; + value = a.a + a.easing((value - a.a) / one) * one; + } try { if (a.stopper(gen)) { if (--a.iterations) { diff --git a/savage.equal.js b/savage.equal.js new file mode 100644 index 0000000..a04ac0b --- /dev/null +++ b/savage.equal.js @@ -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 + }; + } + }; +}); \ No newline at end of file diff --git a/savage.mouse.js b/savage.mouse.js new file mode 100644 index 0000000..b4153b9 --- /dev/null +++ b/savage.mouse.js @@ -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.` on start, + * `drag.end.` on end and `drag.move.` on every move. When element will be dragged over another element + * `drag.over.` 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.` 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); + }; +}); \ No newline at end of file diff --git a/svg.js b/svg.js index 045aba3..c32befd 100644 --- a/svg.js +++ b/svg.js @@ -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, 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"), @@ -860,15 +861,105 @@ function box(x, y, width, height) { }; } // Transformations -function path2string() { +var path2string = Savage.path2string = function () { return this.join(",").replace(p2s, "$1"); -} +}; function pathClone(pathArray) { var res = clone(pathArray); res.toString = path2string; 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) { return null; } @@ -889,7 +980,7 @@ function parseTransformString(TString) { } data.toString = path2string; return data; -} +}; function svgTransform2string(tstr) { var res = []; tstr = tstr.replace(/(?:^|\s)(\w+)\(([^)]+)\)/g, function (all, name, params) { @@ -1086,7 +1177,7 @@ function ellipsePath(x, y, rx, ry, a) { res.toString = path2string; return res; } -function unit2px(el) { +function unit2px(el, name, value) { var defs = el.paper.defs, out = {}, mgr = el.paper.measurer; @@ -1115,28 +1206,57 @@ function unit2px(el) { $(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": - out.rx = getW(el.attr("rx")); - out.ry = getH(el.attr("ry")); + set("rx", getW); + set("ry", getH); case "image": - out.width = getW(el.attr("width")); - out.height = getH(el.attr("height")); + set("width", getW); + set("height", getH); case "text": - out.x = getW(el.attr("x")); - out.y = getH(el.attr("y")); + set("x", getW); + set("y", getH); break; case "circle": - out.cx = getW(el.attr("cx")); - out.cy = getH(el.attr("cy")); - out.r = getW(el.attr("r")); + set("cx", getW); + set("cy", getH); + set("r", getW); break; case "ellipse": - out.cx = getW(el.attr("cx")); - out.cy = getH(el.attr("cy")); - out.rx = getW(el.attr("rx")); - out.ry = getH(el.attr("ry")); + 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; } @@ -1223,7 +1343,7 @@ var pathDimensions = function (path) { return pathClone(pth.rel); } if (!is(pathArray, array) || !is(pathArray && pathArray[0], array)) { // rough assumption - pathArray = parsePathString(pathArray); + pathArray = Savage.parsePathString(pathArray); } var res = [], x = 0, @@ -1302,7 +1422,7 @@ var pathDimensions = function (path) { return pathClone(pth.abs); } if (!is(pathArray, "array") || !is(pathArray && pathArray[0], "array")) { // rough assumption - pathArray = parsePathString(pathArray); + pathArray = Savage.parsePathString(pathArray); } if (!pathArray || !pathArray.length) { return [["M", 0, 0]]; @@ -1565,7 +1685,7 @@ var pathDimensions = function (path) { 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); if (!path2 && pth.curve) { return pathClone(pth.curve); @@ -1764,17 +1884,9 @@ function arrayFirstValue(arr) { } return box(_.bbox); }; - function prop(name) { - return function () { - return this[name]; - }; - } - function always(x) { - return function () { - return x; - }; - } - var propString = prop("string"); + var propString = function () { + return this.local; + }; elproto.transform = function (tstr) { var _ = this._; if (tstr == null) { @@ -1864,6 +1976,9 @@ function arrayFirstValue(arr) { } return set; }; + elproto.asPX = function (attr, value) { + return unit2px(this, attr, value); + }; elproto.use = function () { var use, id = this.node.id; @@ -1939,17 +2054,22 @@ function arrayFirstValue(arr) { return p; }; // animation - function applyAttr(el, key) { + function applyAttr(el, key, f) { var at = {}; return function (value) { - at[key] = value; + at[key] = f ? f(value) : value; el.attr(at); }; } elproto.animate = function (attrs, ms, callback) { - var anims = []; + var anims = [], eq; 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)); @@ -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) { this.attr({d: value}); eve.stop(); @@ -2528,6 +2659,50 @@ 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();