SVG.easing = { '-': function (pos) { return pos }, '<>': function (pos) { return -Math.cos(pos * Math.PI) / 2 + 0.5 }, '>': function (pos) { return Math.sin(pos * Math.PI / 2) }, '<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 } } SVG.morph = function (pos) { return function (from, to) { return new SVG.MorphObj(from, to).at(pos) } } let time = window.performance || window.Date SVG.Timeline = SVG.invent ({ create: function () { // Store all of the closures to animate this._closures = [] this._startTime = time.now() this._duration = 0 this._running = true }, extend: { animate (duration, ease, delay, epoch) { } loop (times, reverse) { } duration (time) { this._duration = time } delay (by, epoch) { if (epoch) { this._startTime = time.now() } this._duration = 0 this._startTime += by } ease (fn) { } play () pause () stop () finish (all=true) speed (newSpeed) seek (dt) persist (dt || forever) // 0 by default reverse () // fn is a function that takes a position in range [0, 1] schedule (fn) { // fn can not take parameters let declarative = rect.animate(300, '>', 200) .loop().color('blue') .animate(SVG.Spring(300)) onmousemove() { declarative.x(mouseX).y(mouseY) } SVG.MorphObj = SVG.invent({ create: function (from, to) { // prepare color for morphing if (SVG.Color.isColor(to)) return new SVG.Color(from).morph(to) // prepare value list for morphing if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to) // prepare number for morphing if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) // prepare for plain morphing this.value = from this.destination = to }, extend: { at: function (pos, real) { return real < 1 ? this.value : this.destination }, valueOf: function () { return this.value } } }) add('fill-color', val) add('x', val, 'animations') add('x', val, 'styles') add('line-cap', val, 'attrs') .style(name, val) { styleAttr ('style', name, val) } .animate(spring) onmousemove(() => { el.animate(SVG.Spring(500)) .move(event.pointX, event.pointY) .finish() }) Morphable () Controlable () new Controller(target, controller) Number Array PathArray ViewBox PointArray Color SVG.Timeline = { styleAttr (type, name, val) { let morpher = new Morph(val).controller(this.controller) queue ( ()=> { morpher = morpher.morph(element[type]('name')) }, morpher.at ) } } .styleAttr (type, name, val) { let morpher = declarative ? new Controller(target) : new Morph().to(val) queue ( ()=> { morpher = morpher.from(element[type](name)) }, () => { this.element[type](name, morpher.at(pos)) } ) } viewbox(box) { new Box let morpher = new Morph().to(box) // box: {width, heught, x, y} } new Morph(from, to) new Morpg(from, to, controller = (from, to, pos) => {from + pos * (to - from)}) // Something line path = "a, b, c" SVG.color { toArray: [r, g, b] fromArray: new Color({r, g, b}) } morph: function (pathArray) { pathArray = new SVG.PathArray(pathArray) if (this.equalCommands(pathArray)) { this.destination = pathArray } else { this.destination = null } return this }, [['M', 3, 5], ['L', 5, 6]] ['M', 3, 4, 'L', ...] function detectSomething (item) { if(from instanceof SVG.Morphable) return from.controller(controller) // prepare color for morphing if (SVG.Color.isColor(to)) return new SVG.Color(from, controller) // prepare value list for morphing if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to) // prepare number for morphing if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) return item } foo->bar all of these things implement interface Morphable { from: (thing)=> {} to: (thing)=> {} at: (pos)=> {} controller: (fn (nowOrFrom, target, pos))=> {} } new SVG.MorphObj(el.attr(name)) animate().attr('line-joint', 5) SVG.MorphObj = SVG.invent({ create: function (from, to) { // prepare color for morphing if (SVG.Color.isColor(to)) return new SVG.Color(from).morph(to) // prepare value list for morphing if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to) // prepare number for morphing if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) // prepare for plain morphing this.value = from this.destination = to }, extend: { at: function (pos, real) { return real < 1 ? this.value : this.destination }, valueOf: function () { return this.value } } }) // Only works with a single number new MorphObj { constr: (control= (from, to, c)=> {from + pos * (to - from)}) { } _detect: // Gets the user input and returns the right kind of object from: (from) => { if (SVG.Color.isColor(to)) return new SVG.Color(from).morph(to) // prepare value list for morphing if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to) // prepare number for morphing if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) // prepare for plain morphing this.value = from this.destination = to } to: (val) => { } at (pos) { let type = from.type let from = from.toArray() let to = to.toArray() result = [] for (i) result[i] = this.controller(from[i], to[i], pos) : to[i] type.fromArray(result) } } if(declartive) { mropher.init() morpher.at(pos/fn) } controller(currentPos, target) morph interface detect type function if (mouse in box) move box animate(spring) zoom(level, point) { let morpher = SVG.Number(level).controller(this.controller) this.queue( () => {morpher = morpher.from(element.zoom())}, (pos) => {element.zoom(morpher.at(pos), point)} ) } x (x) { } this.queue(fn, morpher) new Morph(x(), xGiven) x: function (x, relative) { if (this.target() instanceof SVG.G) { this.transform({x: x}, relative) return this } var num = new SVG.Number(x) num.relative = relative return this.add('x', num) }, viewbox: function(box) { var m = SVG.Box(box) } new Runner (function(time) { }) var closure = function (time) { // If it is time to do something, act now. var running = start < time && time < end if (running && this._running) { closure.position = (time - closure.start) / closure.duration fn (time) } // If we are not paused or stopped, request another frame if (this._running) SVG.Animator.frame(closure, this._startTime) // Tell the caller whether this animation is finished closure.finished = !running }.bind(this) closure.stop() // toggles a stop flag closure.pause() closure.run(t) // If it was paused, it closure.start = this._startTime closure.end = this._startTime + this._duration closure.positon = var forwards = true // Decide if running forward based on looping // TODO: Store a list of closures SVG.Animator.timeout(closure, this._startTime) _continue() } _step (dt) { } // Checks if we are running and continues the animation _continue () { , continue: function () { if (this.paused) return if (!this.nextFrame) this.step() return this } } }, construct: { animate: function(o, ease, delay, epoch) { return (this.timeline = this.timeline || new SVG.Timeline(o, ease, delay, epoch)) } } }) // SVG.Situation = SVG.invent({ // // create: function (o) { // this.init = false // this.reversed = false // this.reversing = false // // this.duration = new SVG.Number(o.duration).valueOf() // this.delay = new SVG.Number(o.delay).valueOf() // // this.start = +new Date() + this.delay // this.finish = this.start + this.duration // this.ease = o.ease // // // this.loop is incremented from 0 to this.loops // // it is also incremented when in an infinite loop (when this.loops is true) // this.loop = 0 // this.loops = false // // this.animations = { // // functionToCall: [list of morphable objects] // // e.g. move: [SVG.Number, SVG.Number] // } // // this.attrs = { // // holds all attributes which are not represented from a function svg.js provides // // e.g. someAttr: SVG.Number // } // // this.styles = { // // holds all styles which should be animated // // e.g. fill-color: SVG.Color // } // // this.transforms = [ // // holds all transformations as transformation objects // // e.g. [SVG.Rotate, SVG.Translate, SVG.Matrix] // ] // // this.once = { // // functions to fire at a specific position // // e.g. "0.5": function foo(){} // } // } // // }) // // SVG.Timeline = SVG.invent({ // // create: function (element) { // this._target = element // this.situations = [] // this.active = false // this.situation = null // this.paused = false // this.lastPos = 0 // this.pos = 0 // // The absolute position of an animation is its position in the context of its complete duration (including delay and loops) // // When performing a delay, absPos is below 0 and when performing a loop, its value is above 1 // this.absPos = 0 // this._speed = 1 // }, // // extend: { // // /** // * sets or returns the target of this animation // * @param o object || number In case of Object it holds all parameters. In case of number its the duration of the animation // * @param ease function || string Function which should be used for easing or easing keyword // * @param delay Number indicating the delay before the animation starts // * @return target || this // */ // animate: function (o, ease, delay) { // if (typeof o === 'object') { // ease = o.ease // delay = o.delay // o = o.duration // } // // var situation = new SVG.Situation({ // duration: o || 1000, // delay: delay || 0, // ease: SVG.easing[ease || '-'] || ease // }) // // this.queue(situation) // // return this // }, // // /** // * sets a delay before the next element of the queue is called // * @param delay Duration of delay in milliseconds // * @return this.target() // */ // delay: function (delay) { // // The delay is performed by an empty situation with its duration // // attribute set to the duration of the delay // var situation = new SVG.Situation({ // duration: delay, // delay: 0, // ease: SVG.easing['-'] // }) // // return this.queue(situation) // }, // // /** // * sets or returns the target of this animation // * @param null || target SVG.Element which should be set as new target // * @return target || this // */ // target: function (target) { // if (target && target instanceof SVG.Element) { // this._target = target // return this // } // // return this._target // }, // // // returns the absolute position at a given time // timeToAbsPos: function (timestamp) { // return (timestamp - this.situation.start) / (this.situation.duration / this._speed) // }, // // // returns the timestamp from a given absolute positon // absPosToTime: function (absPos) { // return this.situation.duration / this._speed * absPos + this.situation.start // }, // // // starts the animationloop // startAnimFrame: function () { // this.stopAnimFrame() // this.animationFrame = window.requestAnimationFrame(function () { this.step() }.bind(this)) // }, // // // cancels the animationframe // stopAnimFrame: function () { // window.cancelAnimationFrame(this.animationFrame) // }, // // // kicks off the animation - only does something when the queue is currently not active and at least one situation is set // start: function () { // // dont start if already started // if (!this.active && this.situation) { // this.active = true // this.startCurrent() // } // // return this // }, // // // start the current situation // startCurrent: function () { // this.situation.start = +new Date() + this.situation.delay / this._speed // this.situation.finish = this.situation.start + this.situation.duration / this._speed // return this.initAnimations().step() // }, // // /** // * adds a function / Situation to the animation queue // * @param fn function / situation to add // * @return this // */ // queue: function (fn) { // if (typeof fn === 'function' || fn instanceof SVG.Situation) { // this.situations.push(fn) // } // // if (!this.situation) this.situation = this.situations.shift() // // return this // }, // // /** // * pulls next element from the queue and execute it // * @return this // */ // dequeue: function () { // // stop current animation // this.stop() // // // get next animation from queue // this.situation = this.situations.shift() // // if (this.situation) { // if (this.situation instanceof SVG.Situation) { // this.start() // } else { // // If it is not a SVG.Situation, then it is a function, we execute it // this.situation(this) // } // } // // return this // }, // // // updates all animations to the current state of the element // // this is important when one property could be changed from another property // initAnimations: function () { // var i, j, source // var s = this.situation // // if (s.init) return this // // for (i in s.animations) { // source = this.target()[i]() // // if (!Array.isArray(source)) { // source = [source] // } // // if (!Array.isArray(s.animations[i])) { // s.animations[i] = [s.animations[i]] // } // // // if(s.animations[i].length > source.length) { // // source.concat = source.concat(s.animations[i].slice(source.length, s.animations[i].length)) // // } // // for (j = source.length; j--;) { // // The condition is because some methods return a normal number instead // // of a SVG.Number // if (s.animations[i][j] instanceof SVG.Number) { // source[j] = new SVG.Number(source[j]) // } // // s.animations[i][j] = source[j].morph(s.animations[i][j]) // } // } // // for (i in s.attrs) { // s.attrs[i] = new SVG.MorphObj(this.target().attr(i), s.attrs[i]) // } // // for (i in s.styles) { // s.styles[i] = new SVG.MorphObj(this.target().css(i), s.styles[i]) // } // // s.initialTransformation = this.target().matrixify() // // s.init = true // return this // }, // // clearQueue: function () { // this.situations = [] // return this // }, // // clearCurrent: function () { // this.situation = null // return this // }, // // /** stops the animation immediately // * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately. // * @param clearQueue A Boolean indicating whether to remove queued animation as well. // * @return this // */ // stop: function (jumpToEnd, clearQueue) { // var active = this.active // this.active = false // // if (clearQueue) { // this.clearQueue() // } // // if (jumpToEnd && this.situation) { // // initialize the situation if it was not // !active && this.startCurrent() // this.atEnd() // } // // this.stopAnimFrame() // // return this.clearCurrent() // }, // // /** resets the element to the state where the current element has started // * @return this // */ // reset: function () { // if (this.situation) { // var temp = this.situation // this.stop() // this.situation = temp // this.atStart() // } // return this // }, // // // Stop the currently-running animation, remove all queued animations, and complete all animations for the element. // finish: function () { // this.stop(true, false) // // while (this.dequeue().situation && this.stop(true, false)); // // this.clearQueue().clearCurrent() // // return this // }, // // // set the internal animation pointer at the start position, before any loops, and updates the visualisation // atStart: function () { // return this.at(0, true) // }, // // // set the internal animation pointer at the end position, after all the loops, and updates the visualisation // atEnd: function () { // if (this.situation.loops === true) { // // If in a infinite loop, we end the current iteration // this.situation.loops = this.situation.loop + 1 // } // // if (typeof this.situation.loops === 'number') { // // If performing a finite number of loops, we go after all the loops // return this.at(this.situation.loops, true) // } else { // // If no loops, we just go at the end // return this.at(1, true) // } // }, // // // set the internal animation pointer to the specified position and updates the visualisation // // if isAbsPos is true, pos is treated as an absolute position // at: function (pos, isAbsPos) { // var durDivSpd = this.situation.duration / this._speed // // this.absPos = pos // // If pos is not an absolute position, we convert it into one // if (!isAbsPos) { // if (this.situation.reversed) this.absPos = 1 - this.absPos // this.absPos += this.situation.loop // } // // this.situation.start = +new Date() - this.absPos * durDivSpd // this.situation.finish = this.situation.start + durDivSpd // // return this.step(true) // }, // // /** // * sets or returns the speed of the animations // * @param speed null || Number The new speed of the animations // * @return Number || this // */ // speed: function (speed) { // if (speed === 0) return this.pause() // // if (speed) { // this._speed = speed // // We use an absolute position here so that speed can affect the delay before the animation // return this.at(this.absPos, true) // } else return this._speed // }, // // // Make loopable // loop: function (times, reverse) { // var c = this.last() // // // store total loops // c.loops = (times != null) ? times : true // c.loop = 0 // // if (reverse) c.reversing = true // return this // }, // // // pauses the animation // pause: function () { // this.paused = true // this.stopAnimFrame() // // return this // }, // // // unpause the animation // play: function () { // if (!this.paused) return this // this.paused = false // // We use an absolute position here so that the delay before the animation can be paused // return this.at(this.absPos, true) // }, // // /** // * toggle or set the direction of the animation // * true sets direction to backwards while false sets it to forwards // * @param reversed Boolean indicating whether to reverse the animation or not (default: toggle the reverse status) // * @return this // */ // reverse: function (reversed) { // var c = this.last() // // if (typeof reversed === 'undefined') c.reversed = !c.reversed // else c.reversed = reversed // // return this // }, // // /** // * returns a float from 0-1 indicating the progress of the current animation // * @param eased Boolean indicating whether the returned position should be eased or not // * @return number // */ // progress: function (easeIt) { // return easeIt ? this.situation.ease(this.pos) : this.pos // }, // // /** // * adds a callback function which is called when the current animation is finished // * @param fn Function which should be executed as callback // * @return number // */ // after: function (fn) { // var c = this.last() // function wrapper (e) { // if (e.detail.situation === c) { // fn.call(this, c) // this.off('finished.fx', wrapper) // prevent memory leak // } // } // // this.target().on('finished.fx', wrapper) // // return this._callStart() // }, // // // adds a callback which is called whenever one animation step is performed // during: function (fn) { // var c = this.last() // function wrapper (e) { // if (e.detail.situation === c) { // fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, c) // } // } // // // see above // this.target().off('during.fx', wrapper).on('during.fx', wrapper) // // this.after(function () { // this.off('during.fx', wrapper) // }) // // return this._callStart() // }, // // // calls after ALL animations in the queue are finished // afterAll: function (fn) { // var wrapper = function wrapper (e) { // fn.call(this) // this.off('allfinished.fx', wrapper) // } // // // see above // this.target().off('allfinished.fx', wrapper).on('allfinished.fx', wrapper) // // return this._callStart() // }, // // // calls on every animation step for all animations // duringAll: function (fn) { // var wrapper = function (e) { // fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, e.detail.situation) // } // // this.target().off('during.fx', wrapper).on('during.fx', wrapper) // // this.afterAll(function () { // this.off('during.fx', wrapper) // }) // // return this._callStart() // }, // // last: function () { // return this.situations.length ? this.situations[this.situations.length - 1] : this.situation // }, // // // adds one property to the animations // add: function (method, args, type) { // this.last()[type || 'animations'][method] = args // return this._callStart() // }, // // /** perform one step of the animation // * @param ignoreTime Boolean indicating whether to ignore time and use position directly or recalculate position based on time // * @return this // */ // step: function (ignoreTime) { // // convert current time to an absolute position // if (!ignoreTime) this.absPos = this.timeToAbsPos(+new Date()) // // // This part convert an absolute position to a position // if (this.situation.loops !== false) { // var absPos, absPosInt, lastLoop // // // If the absolute position is below 0, we just treat it as if it was 0 // absPos = Math.max(this.absPos, 0) // absPosInt = Math.floor(absPos) // // if (this.situation.loops === true || absPosInt < this.situation.loops) { // this.pos = absPos - absPosInt // lastLoop = this.situation.loop // this.situation.loop = absPosInt // } else { // this.absPos = this.situation.loops // this.pos = 1 // // The -1 here is because we don't want to toggle reversed when all the loops have been completed // lastLoop = this.situation.loop - 1 // this.situation.loop = this.situation.loops // } // // if (this.situation.reversing) { // // Toggle reversed if an odd number of loops as occured since the last call of step // this.situation.reversed = this.situation.reversed !== Boolean((this.situation.loop - lastLoop) % 2) // } // } else { // // If there are no loop, the absolute position must not be above 1 // this.absPos = Math.min(this.absPos, 1) // this.pos = this.absPos // } // // // while the absolute position can be below 0, the position must not be below 0 // if (this.pos < 0) this.pos = 0 // // if (this.situation.reversed) this.pos = 1 - this.pos // // // apply easing // var eased = this.situation.ease(this.pos) // // // call once-callbacks // for (var i in this.situation.once) { // if (i > this.lastPos && i <= eased) { // this.situation.once[i].call(this.target(), this.pos, eased) // delete this.situation.once[i] // } // } // // // fire during callback with position, eased position and current situation as parameter // if (this.active) this.target().fire('during', {pos: this.pos, eased: eased, fx: this, situation: this.situation}) // // // the user may call stop or finish in the during callback // // so make sure that we still have a valid situation // if (!this.situation) { // return this // } // // // apply the actual animation to every property // this.eachAt() // // // do final code when situation is finished // if ((this.pos === 1 && !this.situation.reversed) || (this.situation.reversed && this.pos === 0)) { // // stop animation callback // this.stopAnimFrame() // // // fire finished callback with current situation as parameter // this.target().fire('finished', {fx: this, situation: this.situation}) // // if (!this.situations.length) { // this.target().fire('allfinished') // // // Recheck the length since the user may call animate in the afterAll callback // if (!this.situations.length) { // this.target().off('.fx') // there shouldnt be any binding left, but to make sure... // this.active = false // } // } // // // start next animation // if (this.active) this.dequeue() // else this.clearCurrent() // } else if (!this.paused && this.active) { // // we continue animating when we are not at the end // this.startAnimFrame() // } // // // save last eased position for once callback triggering // this.lastPos = eased // return this // }, // // // calculates the step for every property and calls block with it // eachAt: function () { // var i, at // var self = this // var target = this.target() // var s = this.situation // // // apply animations which can be called trough a method // for (i in s.animations) { // at = [].concat(s.animations[i]).map(function (el) { // return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el // }) // // target[i].apply(target, at) // } // // // apply animation which has to be applied with attr() // for (i in s.attrs) { // at = [i].concat(s.attrs[i]).map(function (el) { // return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el // }) // // target.attr.apply(target, at) // } // // // apply animation which has to be applied with css() // for (i in s.styles) { // at = [i].concat(s.styles[i]).map(function (el) { // return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el // }) // // target.css.apply(target, at) // } // // // animate initialTransformation which has to be chained // if (s.transforms.length) { // // // TODO: ANIMATE THE TRANSFORMS // // // // get initial initialTransformation // // at = s.initialTransformation // // for(i = 0, len = s.transforms.length; i < len; i++){ // // // // // get next transformation in chain // // var a = s.transforms[i] // // // // // multiply matrix directly // // if(a instanceof SVG.Matrix){ // // // // if(a.relative){ // // at = at.multiply(new SVG.Matrix().morph(a).at(s.ease(this.pos))) // // }else{ // // at = at.morph(a).at(s.ease(this.pos)) // // } // // continue // // } // // // // // when transformation is absolute we have to reset the needed transformation first // // if(!a.relative) // // a.undo(at.decompose()) // // // // // and reapply it after // // at = at.multiply(a.at(s.ease(this.pos))) // // // // } // // // // // set new matrix on element // // target.matrix(at) // } // // return this // }, // // // adds an once-callback which is called at a specific position and never again // once: function (pos, fn, isEased) { // var c = this.last() // if (!isEased) pos = c.ease(pos) // // c.once[pos] = fn // // return this // }, // // _callStart: function () { // setTimeout(function () { this.start() }.bind(this), 0) // return this // } // // }, // // parent: SVG.Element, // // // Add method to parent elements // construct: { // // Get fx module or create a new one, then animate with given duration and ease // animate: function (o, ease, delay) { // return (this.fx || (this.fx = new SVG.Timeline(this))).animate(o, ease, delay) // }, // delay: function (delay) { // return (this.fx || (this.fx = new SVG.Timeline(this))).delay(delay) // }, // stop: function (jumpToEnd, clearQueue) { // if (this.fx) { // this.fx.stop(jumpToEnd, clearQueue) // } // // return this // }, // finish: function () { // if (this.fx) { // this.fx.finish() // } // // return this // }, // // Pause current animation // pause: function () { // if (this.fx) { // this.fx.pause() // } // // return this // }, // // Play paused current animation // play: function () { // if (this.fx) { this.fx.play() } // // return this // }, // // Set/Get the speed of the animations // speed: function (speed) { // if (this.fx) { // if (speed == null) { return this.fx.speed() } else { this.fx.speed(speed) } // } // // return this // } // } // // }) // // // MorphObj is used whenever no morphable object is given // SVG.MorphObj = SVG.invent({ // // create: function (from, to) { // // prepare color for morphing // if (SVG.Color.isColor(to)) return new SVG.Color(from).morph(to) // // prepare value list for morphing // if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to) // // prepare number for morphing // if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) // // // prepare for plain morphing // this.value = from // this.destination = to // }, // // extend: { // at: function (pos, real) { // return real < 1 ? this.value : this.destination // }, // // valueOf: function () { // return this.value // } // } // // }) // // SVG.extend(SVG.Timeline, { // // Add animatable attributes // attr: function (a, v, relative) { // // apply attributes individually // if (typeof a === 'object') { // for (var key in a) { // this.attr(key, a[key]) // } // } else { // this.add(a, v, 'attrs') // } // // return this // }, // // Add animatable styles // css: function (s, v) { // if (typeof s === 'object') { // for (var key in s) { // this.css(key, s[key]) // } // } else { // this.add(s, v, 'styles') // } // // return this // }, // // Animatable x-axis // x: function (x, relative) { // if (this.target() instanceof SVG.G) { // this.transform({x: x}, relative) // return this // } // // var num = new SVG.Number(x) // num.relative = relative // return this.add('x', num) // }, // // Animatable y-axis // y: function (y, relative) { // if (this.target() instanceof SVG.G) { // this.transform({y: y}, relative) // return this // } // // var num = new SVG.Number(y) // num.relative = relative // return this.add('y', num) // }, // // Animatable center x-axis // cx: function (x) { // return this.add('cx', new SVG.Number(x)) // }, // // Animatable center y-axis // cy: function (y) { // return this.add('cy', new SVG.Number(y)) // }, // // Add animatable move // move: function (x, y) { // return this.x(x).y(y) // }, // // Add animatable center // center: function (x, y) { // return this.cx(x).cy(y) // }, // // Add animatable size // size: function (width, height) { // if (this.target() instanceof SVG.Text) { // // animate font size for Text elements // this.attr('font-size', width) // } else { // // animate bbox based size for all other elements // var box // // if (!width || !height) { // box = this.target().bbox() // } // // if (!width) { // width = box.width / box.height * height // } // // if (!height) { // height = box.height / box.width * width // } // // this.add('width', new SVG.Number(width)) // .add('height', new SVG.Number(height)) // } // // return this // }, // // Add animatable width // width: function (width) { // return this.add('width', new SVG.Number(width)) // }, // // Add animatable height // height: function (height) { // return this.add('height', new SVG.Number(height)) // }, // // Add animatable plot // plot: function (a, b, c, d) { // // Lines can be plotted with 4 arguments // if (arguments.length === 4) { // return this.plot([a, b, c, d]) // } // // return this.add('plot', new (this.target().MorphArray)(a)) // }, // // Add leading method // leading: function (value) { // return this.target().leading // ? this.add('leading', new SVG.Number(value)) // : this // }, // // Add animatable viewbox // viewbox: function (x, y, width, height) { // if (this.target() instanceof SVG.Container) { // this.add('viewbox', new SVG.Box(x, y, width, height)) // } // // return this // }, // update: function (o) { // if (this.target() instanceof SVG.Stop) { // if (typeof o === 'number' || o instanceof SVG.Number) { // return this.update({ // offset: arguments[0], // color: arguments[1], // opacity: arguments[2] // }) // } // // if (o.opacity != null) this.attr('stop-opacity', o.opacity) // if (o.color != null) this.attr('stop-color', o.color) // if (o.offset != null) this.attr('offset', o.offset) // } // // return this // } // })