aboutsummaryrefslogtreecommitdiffstats
path: root/dist/svg.js
diff options
context:
space:
mode:
Diffstat (limited to 'dist/svg.js')
-rw-r--r--dist/svg.js1322
1 files changed, 868 insertions, 454 deletions
diff --git a/dist/svg.js b/dist/svg.js
index fac7f95..aae000b 100644
--- a/dist/svg.js
+++ b/dist/svg.js
@@ -6,7 +6,7 @@
* @copyright Wout Fierens <wout@impinc.co.uk>
* @license MIT
*
-* BUILT: Wed Dec 23 2015 19:09:42 GMT+0100 (Mitteleuropäische Zeit)
+* BUILT: Wed Mar 23 2016 22:45:17 GMT+0100 (Mitteleuropäische Zeit)
*/;
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
@@ -170,7 +170,7 @@ SVG.prepare = function(element) {
// Storage for regular expressions
SVG.regex = {
// Parse unit value
- unit: /^(-?[\d\.]+)([a-z%]{0,2})$/
+ numberAndUnit: /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i
// Parse hex value
, hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i
@@ -203,7 +203,7 @@ SVG.regex = {
, isBlank: /^(\s+)?$/
// Test for numeric string
-, isNumber: /^-?[\d\.]+$/
+, isNumber: /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i
// Test for percent value
, isPercent: /^-?[\d\.]+%$/
@@ -234,6 +234,7 @@ SVG.regex = {
// matches X
, X: /X/g
}
+
SVG.utils = {
// Map function
map: function(array, block) {
@@ -832,20 +833,20 @@ SVG.Number = SVG.invent({
this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value
} else if (typeof value === 'string') {
- unit = value.match(SVG.regex.unit)
+ unit = value.match(SVG.regex.numberAndUnit)
if (unit) {
// make value numeric
this.value = parseFloat(unit[1])
// normalize
- if (unit[2] == '%')
+ if (unit[5] == '%')
this.value /= 100
- else if (unit[2] == 's')
+ else if (unit[5] == 's')
this.value *= 1000
// store unit
- this.unit = unit[2]
+ this.unit = unit[5]
}
} else {
@@ -868,6 +869,9 @@ SVG.Number = SVG.invent({
this.value
) + this.unit
}
+ , toJSON: function() {
+ return this.toString()
+ }
, // Convert to primitive
valueOf: function() {
return this.value
@@ -918,65 +922,117 @@ SVG.Number = SVG.invent({
}
})
-SVG.ViewBox = function(element) {
- var x, y, width, height
- , wm = 1 // width multiplier
- , hm = 1 // height multiplier
- , box = element.bbox()
- , view = (element.attr('viewBox') || '').match(/-?[\d\.]+/g)
- , we = element
- , he = element
-
- // get dimensions of current node
- width = new SVG.Number(element.width())
- height = new SVG.Number(element.height())
-
- // find nearest non-percentual dimensions
- while (width.unit == '%') {
- wm *= width.value
- width = new SVG.Number(we instanceof SVG.Doc ? we.parent().offsetWidth : we.parent().width())
- we = we.parent()
- }
- while (height.unit == '%') {
- hm *= height.value
- height = new SVG.Number(he instanceof SVG.Doc ? he.parent().offsetHeight : he.parent().height())
- he = he.parent()
- }
-
- // ensure defaults
- this.x = box.x
- this.y = box.y
- this.width = width * wm
- this.height = height * hm
- this.zoom = 1
-
- if (view) {
- // get width and height from viewbox
- x = parseFloat(view[0])
- y = parseFloat(view[1])
- width = parseFloat(view[2])
- height = parseFloat(view[3])
-
- // calculate zoom accoring to viewbox
- this.zoom = ((this.width / this.height) > (width / height)) ?
- this.height / height :
- this.width / width
-
- // calculate real pixel dimensions on parent SVG.Doc element
- this.x = x
- this.y = y
- this.width = width
- this.height = height
+SVG.ViewBox = SVG.invent({
+
+ create: function(source) {
+ var i, base = [1, 0, 0, 1]
+
+ var x, y, width, height, box, view, we, he
+ , wm = 1 // width multiplier
+ , hm = 1 // height multiplier
+ , reg = /-?[\d\.]+/g
+
+ if(source instanceof SVG.Element){
+
+ we = source
+ he = source
+ view = (source.attr('viewBox') || '').match(reg)
+ box = source.bbox
+
+ // get dimensions of current node
+ width = new SVG.Number(source.width())
+ height = new SVG.Number(source.height())
+
+ // find nearest non-percentual dimensions
+ while (width.unit == '%') {
+ wm *= width.value
+ width = new SVG.Number(we instanceof SVG.Doc ? we.parent().offsetWidth : we.parent().width())
+ we = we.parent()
+ }
+ while (height.unit == '%') {
+ hm *= height.value
+ height = new SVG.Number(he instanceof SVG.Doc ? he.parent().offsetHeight : he.parent().height())
+ he = he.parent()
+ }
+
+ // ensure defaults
+ this.x = 0
+ this.y = 0
+ this.width = width * wm
+ this.height = height * hm
+ this.zoom = 1
+
+ if (view) {
+ // get width and height from viewbox
+ x = parseFloat(view[0])
+ y = parseFloat(view[1])
+ width = parseFloat(view[2])
+ height = parseFloat(view[3])
+
+ // calculate zoom accoring to viewbox
+ this.zoom = ((this.width / this.height) > (width / height)) ?
+ this.height / height :
+ this.width / width
+
+ // calculate real pixel dimensions on parent SVG.Doc element
+ this.x = x
+ this.y = y
+ this.width = width
+ this.height = height
+
+ }
+
+ }else{
+ // ensure source as object
+ source = typeof source === 'string' ?
+ source.match(reg).map(function(el){ return parseFloat(el) }) :
+ Array.isArray(source) ?
+ source :
+ typeof source == 'object' ?
+ [source.x, source.y, source.width, source.height] :
+ arguments.length == 4 ?
+ [].slice.call(arguments) :
+ base
+
+ this.x = source[0]
+ this.y = source[1]
+ this.width = source[2]
+ this.height = source[3]
+ }
+
}
-}
+, extend: {
+
+ toString: function() {
+ return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height
+ }
+ , morph: function(v){
+
+ var v = arguments.length == 1 ?
+ [v.x, v.y, v.width, v.height] :
+ [].slice.call(arguments)
+
+ this.destination = new SVG.ViewBox(v)
+
+ return this
+
+ }
+
+ , at: function(pos) {
+
+ if(!this.destination) return this
+
+ return new SVG.ViewBox([
+ this.x + (this.destination.x - this.x) * pos
+ , this.y + (this.destination.y - this.y) * pos
+ , this.width + (this.destination.width - this.width) * pos
+ , this.height + (this.destination.height - this.height) * pos
+ ])
+
+ }
-//
-SVG.extend(SVG.ViewBox, {
- // Parse viewbox to string
- toString: function() {
- return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height
}
})
@@ -1153,7 +1209,7 @@ SVG.Element = SVG.invent({
if(!type) return parent
// loop trough ancestors if type is given
- while(parent.node instanceof SVGElement){
+ while(parent && parent.node instanceof SVGElement){
if(typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent
parent = SVG.adopt(parent.node.parentNode)
}
@@ -1229,7 +1285,7 @@ SVG.Element = SVG.invent({
this.node.removeAttribute('svgjs:data')
if(Object.keys(this.dom).length)
- this.node.setAttributeNS(SVG.svgjs, 'svgjs:data', JSON.stringify(this.dom))
+ this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)) // see #428
return this
}
@@ -1238,6 +1294,9 @@ SVG.Element = SVG.invent({
this.dom = o
return this
}
+ , is: function(obj){
+ return is(this, obj)
+ }
}
})
@@ -1250,137 +1309,132 @@ SVG.easing = {
var someVar = 0
-SVG.FX = SVG.invent({
+SVG.Situation = SVG.invent({
- create: function(element) {
+ create: function(o){
+ this.init = false
+ this.reversed = false
- this.pos = 0 // position before easing
- this.lastPos = 0 // needed for once callbacks
- this.paused = false
- this.finished = false
- this.active = false
- this.shared = {current:this}
- this.target = element
- this._next = null
- this._prev = null
+ this.duration = o.duration
+ this.delay = o.delay
- this.id = someVar++
+ this.start = +new Date() + this.delay
+ this.end = this.start + this.duration
+ this.easing = o.easing
this.animations = {
- // functionToCall: [morphable object, destination value]
- // e.g. x: [SVG.Number, 5]
- // this way its assured, that the start value is set correctly
+ // 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 of teh form:
+ // holds all transformations of the form:
// [A, B, C] or, [A, [25, 0, 0]] where ABC are matrixes and the array represents a rotation
]
- this._once = {
+ this.once = {
// functions to fire at a specific position
// e.g. "0.5": function foo(){}
}
-
}
-, extend: {
-
- // sets up the animation
- animate: function(o){
- // the end time of the previous is our start
- var start = this._prev ? this._prev._end : +new Date
-
- o = o || {}
-
- if(typeof o == 'number') o = {duration:o}
+})
- this._duration = o.duration || 1000
- this._delay = o.delay || 0
+SVG.Delay = function(delay){
+ this.delay = delay
+}
- this._start = start + this._delay
- this._end = this._start + this._duration
+SVG.FX = SVG.invent({
- this.easing = SVG.easing[o.easing || '-'] || o.easing // when easing is a function, its not in SVG.easing
+ create: function(element) {
+ this._target = element
+ this.situations = []
+ this.active = false
+ this.current = null
+ this.paused = false
+ this.lastPos = 0
+ this.pos = 0
+ }
- this.init = false
+, extend: {
- return this
- }
+ /**
+ * 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 easing 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, easing, delay){
+
+ if(typeof o == 'object'){
+ easing = o.ease
+ delay = o.delay
+ o = o.duration
+ }
- // adds a new fx obj to the animation chain
- , enqueue: function(o){
- // create new istance from o or use it directly
- return this.next(
- o instanceof SVG.FX ? o :
- new SVG.FX(this.target)
- ).next().share(this.shared).animate(o)
- }
+ var situation = new SVG.Situation({
+ duration: o || 1000,
+ delay: delay || 0,
+ easing: SVG.easing[easing || '-'] || easing
+ })
- // sets or gets the next situation object in the animation queue
- , next: function(next){
- if(!next) return this._next
+ this.queue(situation)
- this._next = next
- next._prev = this // dont use function to prevent recursion
return this
}
- // sets or gets the previous situation object in the animation queue
- , prev: function(prev){
- if(!prev) return this._prev
-
- this._prev = prev
- prev._next = this
- return this
- }
+ /**
+ * sets a delay before the next element of the queue is called
+ * @param delay Duration of delay in milliseconds
+ * @return this.target()
+ */
+ // FIXME: the function needs to get a delay property to make sure, that the totalProgress can be calculated
+ , delay: function(delay){
+ var delay = new SVG.Delay(delay)
- // returns the first situation object...
- , first: function(){
- var prev = this
- while(prev.prev()){
- prev = prev.prev()
- }
- return prev
+ return this.queue(delay)
}
- // returns the last situation object...
- , last: function(){
- var next = this
- while(next.next()){
- next = next.next()
+ /**
+ * sets or returns the target of this animation
+ * @param null || target SVG.Elemenet 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 next
- }
- // sets the shared object which is just a shared reference between all objects
- , share: function(shared){
- this.shared = shared
- return this
+ return this._target
}
// returns the position at a given time
, timeToPos: function(timestamp){
- return (timestamp - this._start) / (this._duration)
+ return (timestamp - this.current.start) / (this.current.duration)
}
// returns the timestamp from a given positon
, posToTime: function(pos){
- return this._duration * pos + this._start
+ return this.current.duration * pos + this.current.start
}
// starts the animationloop
// TODO: It may be enough to call just this.step()
, startAnimFrame: function(){
+ this.stopAnimFrame()
this.animationFrame = requestAnimationFrame(function(){ this.step() }.bind(this))
}
@@ -1390,29 +1444,64 @@ SVG.FX = SVG.invent({
cancelAnimationFrame(this.animationFrame)
}
- // returns the current (active) fx object
- , current: function(){
- return this.shared.current
+ // kicks off the animation - only does something when the queue is curretly not active and at least one situation is set
+ , start: function(){
+ // dont start if already started
+ if(!this.active && this.current){
+ this.current.start = +new Date + this.current.delay
+ this.current.end = this.current.start + this.current.duration
+
+ this.initAnimations()
+ this.active = true
+ this.startAnimFrame()
+ }
+
+ return this
}
- // set this object as current
- , setAsCurrent: function(){
- this.shared.current = this
+ /**
+ * 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 || fn instanceof SVG.Delay)
+ this.situations.push(fn)
+
+ if(!this.current) this.current = this.situations.shift()
+
return this
}
- // kicks off the animation - only does something when this obejct is the current
- // todo: remove timeout. we dont rly need a delay. it can be accomplished from the user itself
- , start: function(){
- // dont start if already started
- if(!this.active && this.current() == this){
- this._start = +new Date + this._delay
- this._end = this._start + this._duration
- this.active = true
+ /**
+ * pulls next element from the queue and execute it
+ * @return this
+ */
+ , dequeue: function(){
+ // stop current animation
+ this.current && this.current.stop && this.current.stop()
- this.init || this.initAnimations()
+ // get next animation from queue
+ this.current = this.situations.shift()
+
+ if(this.current){
+
+ var fn = function(){
+ if(this.current instanceof SVG.Situation)
+ this.initAnimations().seek(0)
+ else if(this.current instanceof SVG.Delay)
+ this.dequeue()
+ else
+ this.current.call(this)
+ }.bind(this)
+
+ // start next animation
+ if(this.current.delay){
+ setTimeout(function(){fn()}, this.current.delay)
+ }else{
+ fn()
+ }
- this.timeout = setTimeout(function(){ this.startAnimFrame() }.bind(this), this.delay)
}
return this
@@ -1422,68 +1511,129 @@ SVG.FX = SVG.invent({
// this is important when one property could be changed from another property
, initAnimations: function() {
var i
+ var s = this.current
+
+ if(s.init) return this
+
+ for(i in s.animations){
+
+ if(i == 'viewbox'){
+ s.animations[i] = this.target().viewbox().morph(s.animations[i])
+ }else{
+
+ // TODO: this is not a clean clone of the array. We may have some unchecked references
+ s.animations[i].value = (i == 'plot' ? this.target().array().value : this.target()[i]())
+
+ // sometimes we get back an object and not the real value, fix this
+ if(s.animations[i].value.value){
+ s.animations[i].value = s.animations[i].value.value
+ }
+
+ if(s.animations[i].relative)
+ s.animations[i].destination.value = s.animations[i].destination.value + s.animations[i].value
+
+ }
- for(i in this.animations){
- // TODO: this is not a clean clone of the array. We may have some unchecked references
- this.animations[i].value = (i == 'plot' ? this.target.array().value : this.target[i]())
- if(this.animations[i].relative)
- this.animations[i].destination.value = this.animations[i].destination.value + this.animations[i].value
}
- for(i in this.attrs){
- if(this.attrs[i] instanceof SVG.Color){
- var color = new SVG.Color(this.target.attr(i))
- this.attrs[i].r = color.r
- this.attrs[i].g = color.g
- this.attrs[i].b = color.b
+ for(i in s.attrs){
+ if(s.attrs[i] instanceof SVG.Color){
+ var color = new SVG.Color(this.target().attr(i))
+ s.attrs[i].r = color.r
+ s.attrs[i].g = color.g
+ s.attrs[i].b = color.b
}else{
- this.attrs[i].value = this.target.attr(i) + this.attrs[i].value
+ s.attrs[i].value = this.target().attr(i)// + s.attrs[i].value
}
}
- for(i in this.styles){
- this.styles[i].value = this.target.style(i)
+ for(i in s.styles){
+ s.styles[i].value = this.target().style(i)
}
- this.transformations = this.target.matrixify()
+ s.transformations = this.target().matrixify()
- this.init = true
+ s.init = true
+ return this
}
+ , clearQueue: function(){
+ this.situations = []
+ return this
+ }
+ , clearCurrent: function(){
+ this.current = 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){
+ if(!this.active) this.start()
+
+ if(clearQueue){
+ this.clearQueue()
+ }
- // resets the animation to the initial state
- // TODO: maybe rename to reset
- , stop: function(){
- if(!this.active) return false
this.active = false
+
+ if(jumpToEnd){
+ this.seek(1)
+ }
+
this.stopAnimFrame()
clearTimeout(this.timeout)
- return this.seek(0)
+ return this.clearCurrent()
}
- // finish off the animation
- // TODO: does it kickoff the next animation in the queue?
- // global finish or fx specific finish?
+ /** resets the element to the state where the current element has started
+ * @return this
+ */
+ , reset: function(){
+ if(this.current){
+ var temp = this.current
+ this.stop()
+ this.current = temp
+ this.seek(0)
+ }
+ return this
+ }
+
+ // Stop the currently-running animation, remove all queued animations, and complete all animations for the element.
, finish: function(){
- this.finished = true
- return this.stop().seek(1)
+
+ this.stop(true, false)
+
+ while(this.dequeue().current && this.stop(true, false));
+
+ return this.clearQueue().clearCurrent()
}
// set the internal animation pointer to the specified position and updates the visualisation
, seek: function(pos){
this.pos = pos
- this._start = +new Date - pos * this._duration
- this._end = this._start + this._duration
+ this.current.start = +new Date - pos * this.current.duration
+ this.current.end = this.current.start + this.current.duration
return this.step(true)
}
// speeds up the animation by the given factor
// this changes the duration of the animation
, speed: function(speed){
- this._duration = this._duration * this.pos + (1-this.pos) * this._duration / speed
- this._end = this._start + this._duration
+ this.current.duration = this.current.duration * this.pos + (1-this.pos) * this.current.duration / speed
+ this.current.end = this.current.start + this.current.duration
return this.seek(this.pos)
}
+ // Make loopable
+ , loop: function(times, reverse) {
+ // store current loop and total loops
+ this.current.loop = times || true
+
+ if(reverse) return this.reverse()
+ return this
+ }
// pauses the animation
, pause: function(){
@@ -1493,61 +1643,66 @@ SVG.FX = SVG.invent({
return this
}
- // sets the direction to forward
+ // unpause the animation
, play: function(){
- if(this.shared.reversed){
- this.shared.reversed = false
- this.seek(this.pos)
- }
-
- return this
+ if(!this.paused) return this
+ this.paused = false
+ return this.seek(this.pos)
}
- // sets the direction to backwards
- , reverse: function(){
- if(!this.shared.reversed){
- this.shared.reversed = true
- this.seek(1-this.pos)
- }
+ /** 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
}
- // resumes a currently paused animation
- , resume: function(){
- if(this.paused){
- this.seek(this.shared.reversed ? 1-this.pos : this.pos)
- this.paused = false
- this.startAnimFrame()
- }
- 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.current.easing(this.pos) : this.pos
}
- // adds a callback function for the current animation which is called when this animation finished
+ /**
+ * 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 _this = this
+ var c = this.last()
, wrapper = function wrapper(e){
- if(e.detail.fx == _this){
- fn.call(this)
+ if(e.detail.situation == c){
+ fn.call(this, c)
this.off('finished.fx', wrapper) // prevent memory leak
}
}
- // unbind previously set bindings because they would be overwritten anyway
- this.target.off('finished.fx', wrapper).on('finished.fx', wrapper)
+ this.target().on('finished.fx', wrapper)
return this
}
// adds a callback which is called whenever one animation step is performed
, during: function(fn){
- var _this = this
+ var c = this.last()
, wrapper = function(e){
- if(e.detail.fx == _this){
- fn.call(this, e.detail.pos, e.detail.eased, e.detail.fx)
+ if(e.detail.situation == c){
+ fn.call(this, e.detail.pos, e.detail.eased, c)
}
}
// see above
- this.target.off('during.fx', wrapper).on('during.fx', wrapper)
+ this.target().off('during.fx', wrapper).on('during.fx', wrapper)
return this.after(function(){
this.off('during.fx', wrapper)
@@ -1562,137 +1717,113 @@ SVG.FX = SVG.invent({
}
// see above
- this.target.off('allfinished.fx', wrapper).on('allfinished.fx', wrapper)
+ this.target().off('allfinished.fx', wrapper).on('allfinished.fx', wrapper)
return this
}
// calls on every animation step for all animations
, duringAll: function(fn){
- var _this = this
- , wrapper = function(e){
- fn.call(this, e.detail.fx.totalPosition(), e.detail.pos, e.detail.eased, e.detail.fx)
+ var wrapper = function(e){
+ fn.call(this, e.detail.pos, e.detail.eased, e.detail.fx, e.detail.situation)
}
- this.target.off('during.fx', wrapper).on('during.fx', wrapper)
+ this.target().off('during.fx', wrapper).on('during.fx', wrapper)
return this.afterAll(function(){
this.off('during.fx', wrapper)
})
}
- // returns an integer from 0-1 indicating the progress of the whole animation queue
- // we recalculate the end time because it may be changed from methods like seek()
- // todo: rename position to progress?
- , totalPosition: function(){
- var start = this.first()._start
- , end = this._end
- , next = this
+ /**
+ * returns a float from 0-1 indicating the progress of the whole animation queue
+ * we recalculate the end time because it may be changed from methods like seek()
+ * @return number
+ */
+ // FIXME: current start always changes so the progress get a reset whenever one situation finishes. We need a global start which is only modified on pause and stop
+ , totalProgress: function(){
+ var start = this.current.start
+ , end = this.current
- while(next = next.next()){
- end += next._duration + next._delay
+ for(var i = 0, len = this.situations.length; i < len; ++i){
+ end += (situations[i].duration || 0) + (situations[i].delay || 0)
}
- return (this.pos * this._duration + this._start - start) / (end - start)
- }
-
- // adds one property to the animations
- , push: function(method, args, type){
- this[type || 'animations'][method] = args
- setTimeout(function(){this.start()}.bind(this), 0)
- return this
+ return (this.pos * this.current.duration + this.start - start) / (end - start)
}
- // removes the specified animation and returns it
- , pop: function(method, type){
- var ret = this[type || 'animations'][method]
- this.drop(method)
- return ret
+ , last: function(){
+ return this.situations.length ? this.situations[this.situations.length-1] : this.current
}
- // removes the specified animation
- , drop: function(method, type){
- delete this[type || 'animations'][method]
+ // adds one property to the animations
+ , add: function(method, args, type){
+ //if(this.situations.length){
+ // this.situations[this.situations.length-1][type || 'animations'][method] = args
+ //}else{
+ // this.current[type || 'animations'][method] = args
+ //}
+ this.last()[type || 'animations'][method] = args
+ setTimeout(function(){this.start()}.bind(this), 0)
return this
}
- // returns the specified animation
- , get: function(method, type){
- return this[type || 'animations'][method]
- }
-
- // perform one step of the animation
- // when ignoreTime is set the method uses the currently set position.
- // Otherwise it will calculate the position based on the time passed
+ /** 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){
+ console.log(this.current)
+
// convert current time to position
if(!ignoreTime) this.pos = this.timeToPos(+new Date)
- if(this.shared.reversed) this.pos = 1 - this.pos
+ if(this.pos >= 1 && (this.current.loop === true || (typeof this.current.loop == 'number' && --this.current.loop))){
+ return this.seek(this.pos-1)
+ }
+
+ if(this.current.reversed) this.pos = 1 - this.pos
// correct position
- if(this.pos > 1) this.pos = 1
- if(this.pos < 0) this.pos = 0
+ if(this.pos > 1)this.pos = 1
+ if(this.pos < 0)this.pos = 0
// apply easing
- var eased = this.easing(this.pos)
+ var eased = this.current.easing(this.pos)
// call once-callbacks
- for(var i in this._once){
+ for(var i in this.current.once){
if(i > this.lastPos && i <= eased){
- this._once[i](this.pos, eased)
- delete this._once[i]
+ this.current.once[i].call(this.target(), this.pos, eased)
+ delete this.current.once[i]
}
}
// fire during callback with position, eased position and current situation as parameter
- this.target.fire('during', {pos: this.pos, eased: eased, fx: this})
+ this.target().fire('during', {pos: this.pos, eased: eased, fx: this, situation: this.current})
// apply the actual animation to every property
this.eachAt()
// do final code when situation is finished
- if(this.pos == 1){
+ if((this.pos == 1 && !this.current.reversed) || (this.current.reversed && this.pos == 0)){
// stop animation callback
cancelAnimationFrame(this.animationFrame)
- this.finished = true
- this.active = false
-
// fire finished callback with current situation as parameter
- this.target.fire('finished', {fx:this})
+ this.target().fire('finished', {fx:this, situation: this.current})
- // start the next animation in the queue and mark it as current
- if(this.next()){
- this.next().setAsCurrent().start()
- // or finish off the animation
- }else{
- this.target.fire('allfinished')
- this.target.off('.fx')
- this.target.fx = null
+ if(!this.situations.length && !this.current && this.active){
+ this.target().fire('allfinished')
+ this.target().off('.fx')
+ this.active = false
}
- // todo: this is more or less duplicate code. has to be removed
- }else if(this.shared.reversed && this.pos == 0){
- // stop animation callback
- cancelAnimationFrame(this.animationFrame)
+ // start next animation
+ if(this.active) this.dequeue()
+ else this.clearCurrent()
- this.finished = true
- this.active = false
-
- // fire finished callback with current situation as parameter
- this.target.fire('finished', {fx:this})
-
- // start the next animation in the queue and mark it as current
- if(this.prev()){
- this.prev().setAsCurrent().start()
- // or finish off the animation
- }else{
- this.target.fire('allfinished')
- this.target.off('.fx')
- this.target.fx = null
- }
}else if(!this.paused && this.active){
// we continue animating when we are not at the end
this.startAnimFrame()
@@ -1707,13 +1838,13 @@ SVG.FX = SVG.invent({
// calculates the step for every property and calls block with it
// todo: include block directly cause it is used only for this purpose
, eachAt: function(){
- var i, at, self = this, target = this.target
-
+ var i, at, self = this, target = this.target(), c = this.current
+ console.log(this.current, c)
// apply animations which can be called trough a method
- for(i in this.animations){
+ for(i in c.animations){
- at = [].concat(this.animations[i]).map(function(el){
- return el.at ? el.at(self.easing(self.pos), self.pos) : el
+ at = [].concat(c.animations[i]).map(function(el){
+ return el.at ? el.at(c.easing(self.pos), self.pos) : el
})
target[i].apply(target, at)
@@ -1721,10 +1852,10 @@ SVG.FX = SVG.invent({
}
// apply animation which has to be applied with attr()
- for(i in this.attrs){
+ for(i in c.attrs){
- at = [i].concat(this.attrs[i]).map(function(el){
- return el.at ? el.at(self.easing(self.pos), self.pos) : el
+ at = [i].concat(c.attrs[i]).map(function(el){
+ return el.at ? el.at(c.easing(self.pos), self.pos) : el
})
target.attr.apply(target, at)
@@ -1732,10 +1863,10 @@ SVG.FX = SVG.invent({
}
// apply animation which has to be applied with style()
- for(i in this.styles){
+ for(i in c.styles){
- at = [i].concat(this.styles[i]).map(function(el){
- return el.at ? el.at(self.easing(self.pos), self.pos) : el
+ at = [i].concat(c.styles[i]).map(function(el){
+ return el.at ? el.at(c.easing(self.pos), self.pos) : el
})
target.style.apply(target, at)
@@ -1743,16 +1874,37 @@ SVG.FX = SVG.invent({
}
// animate transformations which has to be chained
- if(this.transforms.length){
- at = this.transformations
- for(i in this.transforms){
- if(Array.isArray(this.transforms[i])){
- at = at.rotate(this.transforms[i][0].at(this.easing(this.pos)), this.transforms[i][1], this.transforms[i][2])
- }else{
- at = at.multiply(this.transforms[i].at(this.easing(this.pos)))
+ if(c.transforms.length){
+
+ // get inital transformations
+ at = c.transformations
+ for(i in c.transforms){
+
+ // get next transformation in chain
+ var a = c.transforms[i]
+
+ // multiply matrix directly
+ if(a instanceof SVG.Matrix){
+
+ if(a.relative){
+ at = at.multiply(a.at(this.pos))
+ }else{
+ at = at.morph(a).at(c.easing(this.pos))
+ }
+ continue
}
+
+ // when transformation is absolute we have to reset the needed transformation first
+ if(!a.relative)
+ a.undo(at.extract())
+
+ // and reapply it after
+ at = at.multiply(a.at(c.easing(this.pos)))
+ continue;
+
}
+ // set new matrix on element
target.matrix(at)
}
@@ -1764,34 +1916,13 @@ SVG.FX = SVG.invent({
// adds an once-callback which is called at a specific position and never again
, once: function(pos, fn, isEased){
- if(!isEased)pos = this.easing(pos)
+ if(!isEased)pos = this.current.easing(pos)
- this._once[pos] = fn
+ this.current.once[pos] = fn
return this
}
- // searchs for a property in the animation chain to make relative movement possible
- // TODO: this method is outdated because of the use of initAnimations which cover this topic quite well
- , search: function(method, key) {
- var situation = this
-
- while(situation = situation.prev()){
-
- // get method of situation if present (always try animation object first before accessing attrs or styles)
- var attr = situation.get(key || method) || this.styles[key] || this.attrs[key]
- if(!attr) continue
-
- // return value from the morphed object
- return attr.destination
-
- }
-
- // return the elements attribute as fallback
- return this.target[method](key)
-
- }
-
}
, parent: SVG.Element
@@ -1799,11 +1930,31 @@ SVG.FX = SVG.invent({
// Add method to parent elements
, construct: {
// Get fx module or create a new one, then animate with given duration and ease
- animate: function(o) {
- return (this.fx || (this.fx = new SVG.FX(this))).animate(o)
+ animate: function(o, easing, delay) {
+ return (this.fx || (this.fx = new SVG.FX(this))).animate(o, easing, delay)
}
, delay: function(delay){
- return (this.fx || (this.fx = new SVG.FX(this))).animate({delay:delay})
+ return (this.fx || (this.fx = new SVG.FX(this))).delay(delay)
+ }
+ , stop: function(jumpToEnd, clearQueue) {
+ if (this.fx)
+ this.fx.stop(jumpToEnd, clearQueue)
+
+ 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
}
}
@@ -1812,14 +1963,14 @@ SVG.FX = SVG.invent({
// MorphObj is used whenever no morphable object is given
SVG.MorphObj = SVG.invent({
- create: function(from, to){
+ create: function(to){
// prepare color for morphing
- if(SVG.Color.isColor(to)) return new SVG.Color(from).morph(to)
+ if(SVG.Color.isColor(to)) return new SVG.Color().morph(to)
// prepare number for morphing
- if(SVG.regex.unit.test(to) || typeof from == 'number') return new SVG.Number(from).morph(to)
+ if(SVG.regex.numberAndUnit.test(to)) return new SVG.Number().morph(to)
// prepare for plain morphing
- this.value = from
+ this.value = 0
this.destination = to
}
@@ -1837,52 +1988,15 @@ SVG.MorphObj = SVG.invent({
SVG.extend(SVG.FX, {
// Add animatable attributes
- attr: function(a, v) {
+ attr: function(a, v, relative) {
// apply attributes individually
if (typeof a == 'object') {
for (var key in a)
this.attr(key, a[key])
} else {
- // get the current state
- var from = this.search('attr', a)
-
- // detect format
- if (a == 'transform') {
- // merge given transformation with an existing one
- //if (this.attrs[a])
- // v = this.attrs[a].multiply(v)
-
- if(v.rotation && !v.a){
- this.transforms.push([new SVG.Number(0).morph(v.rotation), v.cx || 0, v.cy || 0])
- this.start()
- }else{
- this.transforms.push(new SVG.Matrix(this.target).morph(v))
- this.start()
- }
-
- // prepare matrix for morphing
- //this.push(a, (new SVG.Matrix(this.target)).morph(v), 'transforms')
-
- // add parametric rotation values
- /*if (this.param) {
- // get initial rotation
- v = this.target.transform('rotation')
-
- // add param
- this.attrs[a].param = {
- from: this.target.param || { rotation: v, cx: this.param.cx, cy: this.param.cy }
- , to: this.param
- }
- }*/
-
- } else {
- /*if(typeof this[a] == 'function'){
- return this[a](v)
- }*/
-
- this.push(a, new SVG.MorphObj(from, v), 'attrs')
- }
+ // the MorphObj takes care about the right function used
+ this.add(a, new SVG.MorphObj(v), 'attrs')
}
return this
@@ -1894,29 +2008,39 @@ SVG.extend(SVG.FX, {
this.style(key, s[key])
else
- this.push(s, new SVG.MorphObj(this.search('style', s), v), 'styles')
+ this.add(s, new SVG.MorphObj(v), 'styles')
return this
}
// Animatable x-axis
, x: function(x, relative) {
- var num = new SVG.Number(/*this.search('x')*/0).morph(x)
+ if(this.target() instanceof SVG.G){
+ this.transform({x:x}, relative)
+ return this
+ }
+
+ var num = new SVG.Number().morph(x)
num.relative = relative
- return this.push('x', num)
+ return this.add('x', num)
}
// Animatable y-axis
, y: function(y, relative) {
- var num = new SVG.Number(/*this.search('y')*/).morph(y)
+ if(this.target() instanceof SVG.G){
+ this.transform({y:y}, relative)
+ return this
+ }
+
+ var num = new SVG.Number().morph(y)
num.relative = relative
- return this.push('y', num)
+ return this.add('y', num)
}
// Animatable center x-axis
, cx: function(x) {
- return this.push('cx', new SVG.Number(/*this.search('cx')*/).morph(x))
+ return this.add('cx', new SVG.Number().morph(x))
}
// Animatable center y-axis
, cy: function(y) {
- return this.push('cy', new SVG.Number(/*this.search('cy')*/).morph(y))
+ return this.add('cy', new SVG.Number().morph(y))
}
// Add animatable move
, move: function(x, y) {
@@ -1928,22 +2052,28 @@ SVG.extend(SVG.FX, {
}
// Add animatable size
, size: function(width, height) {
- if (this.target instanceof SVG.Text) {
+ 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 w = this.search('width')
- , h = this.search('height')
- , box
+ var box
- if(!w || !h){
- box = this.target.bbox()
+ if(!width || !height){
+ box = this.target().bbox()
}
- this.push('width' , new SVG.Number(/*w || box.width*/).morph(width))
- .push('height', new SVG.Number(/*h || box.height*/).morph(height))
+ if(!width){
+ width = box.width / box.height * height
+ }
+
+ if(!height){
+ height = box.height / box.width * width
+ }
+
+ this.add('width' , new SVG.Number().morph(width))
+ .add('height', new SVG.Number().morph(height))
}
@@ -1951,24 +2081,35 @@ SVG.extend(SVG.FX, {
}
// Add animatable plot
, plot: function(p) {
- return this.push('plot', this.target.array().morph(p))
+ return this.add('plot', this.target().array().morph(p))
}
// Add leading method
, leading: function(value) {
- return this.target.leading ?
- this.push('leading', new SVG.Number(/*this.search('leading')*/).morph(value)) :
+ return this.target().leading ?
+ this.add('leading', new SVG.Number().morph(value)) :
this
}
// Add animatable viewbox
, viewbox: function(x, y, width, height) {
- if (this.target instanceof SVG.Container) {
- var box = this.target.viewbox()
- this.push('viewbox', [
- new SVG.Number(box.x).morph(x),
- new SVG.Number(box.y).morph(y),
- new SVG.Number(box.width).morph(width),
- new SVG.Number(box.height).morph(height)
- ])
+ if (this.target() instanceof SVG.Container) {
+ this.add('viewbox', new SVG.ViewBox(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
@@ -2155,7 +2296,7 @@ SVG.Matrix = SVG.invent({
source : base
// merge source
- for (i = abcdef.length - 1; i >= 0; i--)
+ for (i = abcdef.length - 1; i >= 0; --i)
this[abcdef[i]] = source && typeof source[abcdef[i]] === 'number' ?
source[abcdef[i]] : base[abcdef[i]]
}
@@ -2173,6 +2314,8 @@ SVG.Matrix = SVG.invent({
// translation
x: this.e
, y: this.f
+ , transformedX:(this.e * Math.cos(skewX * Math.PI / 180) + this.f * Math.sin(skewX * Math.PI / 180)) / Math.sqrt(this.a * this.a + this.b * this.b)
+ , transformedY:(this.f * Math.cos(skewX * Math.PI / 180) + this.e * Math.sin(-skewX * Math.PI / 180)) / Math.sqrt(this.c * this.c + this.d * this.d)
// skew
, skewX: -skewX
, skewY: 180 / Math.PI * Math.atan2(py.y, py.x)
@@ -2187,6 +2330,7 @@ SVG.Matrix = SVG.invent({
, d: this.d
, e: this.e
, f: this.f
+ , matrix: new SVG.Matrix(this)
}
}
// Clone matrix
@@ -2325,6 +2469,80 @@ SVG.Matrix = SVG.invent({
}
})
+
+SVG.Point = SVG.invent({
+ // Initialize
+ create: function(x,y) {
+ var i, source, base = {x:0, y:0}
+
+ // ensure source as object
+ source = Array.isArray(x) ?
+ {x:x[0], y:x[1]} :
+ typeof x === 'object' ?
+ {x:x.x, y:x.y} :
+ y != null ?
+ {x:x, y:y} : base
+
+ // merge source
+ this.x = source.x
+ this.y = source.y
+ }
+
+ // Add methods
+, extend: {
+ // Clone point
+ clone: function() {
+ return new SVG.Point(this)
+ }
+ // Morph one point into another
+ , morph: function(point) {
+ // store new destination
+ this.destination = new SVG.Point(point)
+
+ return this
+ }
+ // Get morphed point at a given position
+ , at: function(pos) {
+ // make sure a destination is defined
+ if (!this.destination) return this
+
+ // calculate morphed matrix at a given position
+ var point = new SVG.Point({
+ x: this.x + (this.destination.x - this.x) * pos
+ , y: this.y + (this.destination.y - this.y) * pos
+ })
+
+ return point
+ }
+ // Convert to native SVGPoint
+ , native: function() {
+ // create new point
+ var point = SVG.parser.draw.node.createSVGPoint()
+
+ // update with current values
+ point.x = this.x
+ point.y = this.y
+
+ return point
+ }
+ // transform point with matrix
+ , transform: function(matrix) {
+ return new SVG.Point(this.native().matrixTransform(matrix.native()))
+ }
+
+ }
+
+})
+
+SVG.extend(SVG.Element, {
+
+ // Get point
+ point: function(x, y) {
+ return new SVG.Point(x,y).transform(this.screenCTM().inverse());
+ }
+
+})
+
SVG.extend(SVG.Element, {
// Set svg element attribute
attr: function(a, v, n) {
@@ -2408,11 +2626,11 @@ SVG.extend(SVG.Element, {
return this
}
})
-SVG.extend(SVG.Element, SVG.FX, {
+SVG.extend(SVG.Element, {
// Add transformations
transform: function(o, relative) {
// get target in case of the fx module, otherwise reference this
- var target = this.target || this
+ var target = this
, matrix
// act as a getter
@@ -2420,20 +2638,11 @@ SVG.extend(SVG.Element, SVG.FX, {
// get current matrix
matrix = new SVG.Matrix(target).extract()
- // add parametric rotation
- if (typeof this.param === 'object') {
- matrix.rotation = this.param.rotation
- matrix.cx = this.param.cx
- matrix.cy = this.param.cy
- }
-
return typeof o === 'string' ? matrix[o] : matrix
}
// get current matrix
- matrix = /*this instanceof SVG.FX && this.attrs.transform ?
- this.attrs.transform :*/
- new SVG.Matrix(target)
+ matrix = new SVG.Matrix(target)
// ensure relative flag
relative = !!relative || !!o.relative
@@ -2451,26 +2660,12 @@ SVG.extend(SVG.Element, SVG.FX, {
// ensure centre point
ensureCentre(o, target)
- // relativize rotation value
- /*if (relative) {
- o.rotation += this.param && this.param.rotation != null ?
- this.param.rotation :
- matrix.extract().rotation
- }*/
-
- // store parametric values
- //this.param = o
-
- if(this instanceof SVG.FX) return this.attr('transform', o)
-
// apply transformation
- //if (this instanceof SVG.Element) {
- matrix = relative ?
- // relative
- matrix.rotate(o.rotation, o.cx, o.cy) :
- // absolute
- matrix.rotate(o.rotation - matrix.extract().rotation, o.cx, o.cy)
- //}
+ matrix = relative ?
+ // relative
+ matrix.rotate(o.rotation, o.cx, o.cy) :
+ // absolute
+ matrix.rotate(o.rotation - matrix.extract().rotation, o.cx, o.cy)
// act on scale
} else if (o.scale != null || o.scaleX != null || o.scaleY != null) {
@@ -2526,7 +2721,84 @@ SVG.extend(SVG.Element, SVG.FX, {
}
}
- return this.attr(this instanceof SVG.Pattern ? 'patternTransform' : this instanceof SVG.Gradient ? 'gradientTransform' : 'transform', matrix)
+ return this.attr('transform', matrix)
+ }
+})
+
+SVG.extend(SVG.FX, {
+ transform: function(o, relative) {
+ // get target in case of the fx module, otherwise reference this
+ var target = this.target()
+ , matrix
+
+ // act as a getter
+ if (typeof o !== 'object') {
+ // get current matrix
+ matrix = new SVG.Matrix(target).extract()
+
+ return typeof o === 'string' ? matrix[o] : matrix
+ }
+
+ // ensure relative flag
+ relative = !!relative || !!o.relative
+
+ // act on matrix
+ if (o.a != null) {
+ matrix = new SVG.Matrix(o)
+
+ // act on rotation
+ } else if (o.rotation != null) {
+ // ensure centre point
+ ensureCentre(o, target)
+
+ // apply transformation
+ matrix = new SVG.Rotate(o.rotation, o.cx, o.cy)
+
+ // act on scale
+ } else if (o.scale != null || o.scaleX != null || o.scaleY != null) {
+ // ensure centre point
+ ensureCentre(o, target)
+
+ // ensure scale values on both axes
+ o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1
+ o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1
+
+ matrix = new SVG.Scale(o.scaleX, o.scaleY, o.cx, o.cy)
+
+ // act on skew
+ } else if (o.skewX != null || o.skewY != null) {
+ // ensure centre point
+ ensureCentre(o, target)
+
+ // ensure skew values on both axes
+ o.skewX = o.skewX != null ? o.skewX : 0
+ o.skewY = o.skewY != null ? o.skewY : 0
+
+ matrix = new SVG.Skew(o.skewX, o.skewY, o.cx, o.cy)
+
+ // act on flip
+ } else if (o.flip) {
+ matrix = new SVG.Matrix().morph(new SVG.Matrix().flip(
+ o.flip
+ , o.offset == null ? target.bbox()['c' + o.flip] : o.offset
+ ))
+
+ // act on translate
+ } else if (o.x != null || o.y != null) {
+ matrix = new SVG.Translate(o.x, o.y)
+ }
+
+ if(!matrix) return this
+
+ matrix.relative = relative
+
+ var situation = this.situations.length ? this.situations[this.situations.length-1] : this.current
+
+ situation.transforms.push(matrix)
+
+ setTimeout(function(){this.start()}.bind(this), 0)
+
+ return this
}
})
@@ -2535,7 +2807,7 @@ SVG.extend(SVG.Element, {
untransform: function() {
return this.attr('transform', null)
},
- // merge the whole transformation chain into one matrix
+ // merge the whole transformation chain into one matrix and returns it
matrixify: function() {
var matrix = (this.attr('transform') || '')
@@ -2552,8 +2824,6 @@ SVG.extend(SVG.Element, {
return matrix[transform[0]].apply(matrix, transform[1])
}, new SVG.Matrix())
- // apply calculated matrix to element
- this.attr('transform', matrix)
return matrix
},
@@ -2576,6 +2846,136 @@ SVG.extend(SVG.Element, {
})
+SVG.Transformation = SVG.invent({
+
+ create: function(source, inversed){
+
+ if(arguments.length > 1 && typeof inversed != 'boolean'){
+ return this.create([].slice.call(arguments))
+ }
+
+ if(typeof source == 'object'){
+ for(var i = 0, len = this.arguments.length; i < len; ++i){
+ this[this.arguments[i]] = source[this.arguments[i]]
+ }
+ }
+
+ if(Array.isArray(source)){
+ for(var i = 0, len = this.arguments.length; i < len; ++i){
+ this[this.arguments[i]] = source[i]
+ }
+ }
+
+ this.inversed = false
+
+ if(inversed === true){
+ this.inversed = true
+ }
+
+ }
+
+, extend: {
+
+ at: function(pos){
+
+ var params = []
+
+ for(var i = 0, len = this.arguments.length; i < len; ++i){
+ params.push(this[this.arguments[i]])
+ }
+
+ var m = this._undo || new SVG.Matrix()
+
+ m = new SVG.Matrix().morph(SVG.Matrix.prototype[this.method].apply(m, params)).at(pos)
+
+ return this.inversed ? m.inverse() : m
+
+ }
+
+ , undo: function(o){
+ this._undo = new SVG[capitalize(this.method)](o, true).at(1)
+ return this
+ }
+
+ }
+
+})
+
+SVG.Translate = SVG.invent({
+
+ parent: SVG.Matrix
+, inherit: SVG.Transformation
+
+, create: function(source, inversed){
+ if(typeof source == 'object') this.constructor.call(this, source, inversed)
+ else this.constructor.call(this, [].slice.call(arguments))
+ }
+
+, extend: {
+ arguments: ['transformedX', 'transformedY']
+ , method: 'translate'
+ }
+
+})
+
+SVG.Rotate = SVG.invent({
+
+ parent: SVG.Matrix
+, inherit: SVG.Transformation
+
+, create: function(source, inversed){
+ if(typeof source == 'object') this.constructor.call(this, source, inversed)
+ else this.constructor.call(this, [].slice.call(arguments))
+ }
+
+, extend: {
+ arguments: ['rotation', 'cx', 'cy']
+ , method: 'rotate'
+ , at: function(pos){
+ var m = new SVG.Matrix().rotate(new SVG.Number().morph(this.rotation - (this._undo ? this._undo.rotation : 0)).at(pos), this.cx, this.cy)
+ return this.inversed ? m.inverse() : m
+ }
+ , undo: function(o){
+ this._undo = o
+ }
+ }
+
+})
+
+SVG.Scale = SVG.invent({
+
+ parent: SVG.Matrix
+, inherit: SVG.Transformation
+
+, create: function(source, inversed){
+ if(typeof source == 'object') this.constructor.call(this, source, inversed)
+ else this.constructor.call(this, [].slice.call(arguments))
+ }
+
+, extend: {
+ arguments: ['scaleX', 'scaleY', 'cx', 'cy']
+ , method: 'scale'
+ }
+
+})
+
+SVG.Skew = SVG.invent({
+
+ parent: SVG.Matrix
+, inherit: SVG.Transformation
+
+, create: function(source, inversed){
+ if(typeof source == 'object') this.constructor.call(this, source, inversed)
+ else this.constructor.call(this, [].slice.call(arguments))
+ }
+
+, extend: {
+ arguments: ['skewX', 'skewY', 'cx', 'cy']
+ , method: 'skew'
+ }
+
+})
+
SVG.extend(SVG.Element, {
// Dynamic style generator
style: function(s, v) {
@@ -2786,6 +3186,7 @@ SVG.Container = SVG.invent({
// Initialize listeners stack
SVG.listeners = []
SVG.handlerMap = []
+SVG.listenerId = 0
// Add event binder in the SVG namespace
SVG.on = function(node, event, listener, binding) {
@@ -2801,8 +3202,11 @@ SVG.on = function(node, event, listener, binding) {
SVG.listeners[index][ev] = SVG.listeners[index][ev] || {}
SVG.listeners[index][ev][ns] = SVG.listeners[index][ev][ns] || {}
+ if(!listener._svgjsListenerId)
+ listener._svgjsListenerId = ++SVG.listenerId
+
// reference listener
- SVG.listeners[index][ev][ns][listener] = l
+ SVG.listeners[index][ev][ns][listener._svgjsListenerId] = l
// add listener
node.addEventListener(ev, l, false)
@@ -2817,6 +3221,9 @@ SVG.off = function(node, event, listener) {
if(index == -1) return
if (listener) {
+ if(typeof listener == 'function') listener = listener._svgjsListenerId
+ if(!listener) return
+
// remove listener reference
if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns || '*']) {
// remove listener
@@ -2910,19 +3317,19 @@ SVG.G = SVG.invent({
, extend: {
// Move over x-axis
x: function(x) {
- return x == null ? this.transform('x') : this.transform({ x: -this.x() + x }, true)
+ return x == null ? this.transform('x') : this.transform({ x: x - this.x() }, true)
}
// Move over y-axis
, y: function(y) {
- return y == null ? this.transform('y') : this.transform({ y: -this.y() + y }, true)
+ return y == null ? this.transform('y') : this.transform({ y: y - this.y() }, true)
}
// Move by center over x-axis
, cx: function(x) {
- return x == null ? this.tbox().cx : this.x(x - this.tbox().width / 2)
+ return x == null ? this.gbox().cx : this.x(x - this.gbox().width / 2)
}
// Move by center over y-axis
, cy: function(y) {
- return y == null ? this.tbox().cy : this.y(y - this.tbox().height / 2)
+ return y == null ? this.gbox().cy : this.y(y - this.gbox().height / 2)
}
, gbox: function() {
@@ -2949,6 +3356,7 @@ SVG.G = SVG.invent({
}
}
})
+
// ### This module adds backward / forward functionality to elements.
//
@@ -3934,8 +4342,10 @@ SVG.Text = SVG.invent({
}
// Get all the first level lines
, lines: function() {
+ var node = (this.textPath && this.textPath() || this).node
+
// filter tspans and map them to SVG.js instances
- var lines = SVG.utils.map(SVG.utils.filterSVGElements(this.node.childNodes), function(el){
+ var lines = SVG.utils.map(SVG.utils.filterSVGElements(node.childNodes), function(el){
return SVG.adopt(el)
})
@@ -3981,7 +4391,7 @@ SVG.Text = SVG.invent({
// overwrite method from parent to set data properly
, setData: function(o){
this.dom = o
- this.dom.leading = o.leading ? new SVG.Number(o.leading.value, o.leading.unit) : new SVG.Number(1.3)
+ this.dom.leading = new SVG.Number(o.leading || 1.3)
return this
}
}
@@ -4087,7 +4497,7 @@ SVG.TextPath = SVG.invent({
create: 'textPath'
// Inherit from
-, inherit: SVG.Element
+, inherit: SVG.Parent
// Define parent class
, parent: SVG.Text
@@ -4634,6 +5044,10 @@ SVG.extend(SVG.Parent, {
}
})
+function is(el, obj){
+ return el instanceof obj
+}
+
// tests if a given selector matches an element
function matches(el, selector) {
return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector);