summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlrich-Matthias Schäfer <ulima.ums@googlemail.com>2016-03-24 23:44:59 +0100
committerUlrich-Matthias Schäfer <ulima.ums@googlemail.com>2016-03-24 23:44:59 +0100
commit000b071d04d67091c148cbe66e515e63b4b46986 (patch)
tree433bc79e68b00e06d2c1657b9a0b48e24248b944
parent595777968f56ebafc8ab0b25d8605ce258f87c98 (diff)
parentc33e7f6c5b6cd2e4ef595fce65559eb74b8c5e63 (diff)
downloadsvg.js-000b071d04d67091c148cbe66e515e63b4b46986.tar.gz
svg.js-000b071d04d67091c148cbe66e515e63b4b46986.zip
Merge branch '3.0.0' introducing several changes:
- textpath now is a parent element, the lines method of text will return the tspans inside the textpath (#450) - fx module rewritten to support animation chaining and several other stuff (see docs when commited) - fixed absolute transformation animations (not perfect but better) - fixed event listeners which didnt work correctly when identic funtions used - added `element.is()` which helps to check for the object instance faster (instanceof check) - added more fx specs
-rw-r--r--CHANGELOG.md6
-rw-r--r--dist/svg.js1493
-rw-r--r--dist/svg.min.js4
-rw-r--r--spec/SpecRunner.html90
-rw-r--r--spec/index.html120
-rw-r--r--spec/lib/jasmine-2.0.1/jasmine.css59
-rw-r--r--spec/lib/jasmine-2.4.1/MIT.LICENSE20
-rw-r--r--spec/lib/jasmine-2.4.1/boot.js (renamed from spec/lib/jasmine-2.0.1/boot.js)89
-rw-r--r--spec/lib/jasmine-2.4.1/console.js (renamed from spec/lib/jasmine-2.0.1/console.js)31
-rw-r--r--spec/lib/jasmine-2.4.1/jasmine-html.js (renamed from spec/lib/jasmine-2.0.1/jasmine-html.js)201
-rw-r--r--spec/lib/jasmine-2.4.1/jasmine.css58
-rw-r--r--spec/lib/jasmine-2.4.1/jasmine.js (renamed from spec/lib/jasmine-2.0.1/jasmine.js)2102
-rw-r--r--spec/lib/jasmine-2.4.1/jasmine_favicon.png (renamed from spec/lib/jasmine-2.0.1/jasmine_favicon.png)bin1486 -> 1486 bytes
-rw-r--r--spec/spec/element.js2
-rw-r--r--spec/spec/event.js46
-rw-r--r--spec/spec/fx.js267
-rw-r--r--spec/spec/sugar.js679
-rw-r--r--src/element.js3
-rw-r--r--src/event.js11
-rw-r--r--src/fx.js1095
-rw-r--r--src/group.js4
-rw-r--r--src/helpers.js4
-rw-r--r--src/matrix.js5
-rw-r--r--src/sugar.js4
-rw-r--r--src/text.js4
-rw-r--r--src/textpath.js2
-rw-r--r--src/transform.js246
-rw-r--r--src/viewbox.js170
28 files changed, 4973 insertions, 1842 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ac08aa5..1cc30e9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,12 @@
- fixed `svgjs:data` attribute which was not set properly in all browsers (#428)
- fixed `isNumber` and `numberAndUnit` regex (#405)
- fixed error where a parent node is not found when loading an image but the canvas was cleared (#447)
+- textpath now is a parent element, the lines method of text will return the tspans inside the textpath (#450)
+- fx module rewritten to support animation chaining and several other stuff (see docs)
+- fixed absolute transformation animations (not perfect but better)
+- fixed event listeners which didnt work correctly when identic funtions used
+- added `element.is()` which helps to check for the object instance faster (instanceof check)
+- added more fx specs
# 2.2.5 (29/12/2015)
- added check for existence of node (#431)
diff --git a/dist/svg.js b/dist/svg.js
index bcf231d..b6d2c3c 100644
--- a/dist/svg.js
+++ b/dist/svg.js
@@ -6,7 +6,7 @@
* @copyright Wout Fierens <wout@impinc.co.uk>
* @license MIT
*
-* BUILT: Sat Mar 12 2016 12:40:26 GMT+0100 (CET)
+* BUILT: Thu Mar 24 2016 23:31:03 GMT+0100 (Mitteleuropäische Zeit)
*/;
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
@@ -922,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
}
})
@@ -1242,449 +1294,649 @@ SVG.Element = SVG.invent({
this.dom = o
return this
}
+ , is: function(obj){
+ return is(this, obj)
+ }
+ }
+})
+
+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}
+}
+
+var someVar = 0
+
+SVG.Situation = SVG.invent({
+
+ create: function(o){
+ this.init = false
+ this.reversed = false
+
+ this.duration = o.duration
+ this.delay = o.delay
+
+ this.start = +new Date() + this.delay
+ this.end = this.start + this.duration
+ this.easing = o.easing
+
+ 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 of the form:
+ // [A, B, C] or, [A, [25, 0, 0]] where ABC are matrixes and the array represents a rotation
+ ]
+
+ this.once = {
+ // functions to fire at a specific position
+ // e.g. "0.5": function foo(){}
+ }
+
}
+
})
+SVG.Delay = function(delay){
+ this.delay = delay
+}
+
SVG.FX = SVG.invent({
- // Initialize FX object
+
create: function(element) {
- // store target element
- this.target = element
+ this._target = element
+ this.situations = []
+ this.active = false
+ this.current = null
+ this.paused = false
+ this.lastPos = 0
+ this.pos = 0
}
- // Add class methods
, extend: {
- // Add animation parameters and start animation
- animate: function(d, ease, delay) {
- var akeys, skeys, key
- , element = this.target
- , fx = this
-
- // dissect object if one is passed
- if (typeof d == 'object') {
- delay = d.delay
- ease = d.ease
- d = d.duration
+
+ /**
+ * 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
}
- // ensure default duration and easing
- d = d == '=' ? d : d == null ? 1000 : new SVG.Number(d).valueOf()
- ease = ease || '<>'
-
- // process values
- fx.at = function(pos) {
- var i
-
- // normalise pos
- pos = pos < 0 ? 0 : pos > 1 ? 1 : pos
-
- // collect attribute keys
- if (akeys == null) {
- akeys = []
- for (key in fx.attrs)
- akeys.push(key)
-
- // make sure morphable elements are scaled, translated and morphed all together
- if (element.morphArray && (fx.destination.plot || akeys.indexOf('points') > -1)) {
- // get destination
- var box
- , p = new element.morphArray(fx.destination.plot || fx.attrs.points || element.array())
-
- // add size
- if (fx.destination.size)
- p.size(fx.destination.size.width.to, fx.destination.size.height.to)
-
- // add movement
- box = p.bbox()
- if (fx.destination.x)
- p.move(fx.destination.x.to, box.y)
- else if (fx.destination.cx)
- p.move(fx.destination.cx.to - box.width / 2, box.y)
-
- box = p.bbox()
- if (fx.destination.y)
- p.move(box.x, fx.destination.y.to)
- else if (fx.destination.cy)
- p.move(box.x, fx.destination.cy.to - box.height / 2)
-
- // reset destination values
- fx.destination = {
- plot: element.array().morph(p)
- }
- }
- }
+ var situation = new SVG.Situation({
+ duration: o || 1000,
+ delay: delay || 0,
+ easing: SVG.easing[easing || '-'] || easing
+ })
- // collect style keys
- if (skeys == null) {
- skeys = []
- for (key in fx.styles)
- skeys.push(key)
- }
+ this.queue(situation)
- // apply easing
- pos = ease == '<>' ?
- (-Math.cos(pos * Math.PI) / 2) + 0.5 :
- ease == '>' ?
- Math.sin(pos * Math.PI / 2) :
- ease == '<' ?
- -Math.cos(pos * Math.PI / 2) + 1 :
- ease == '-' ?
- pos :
- typeof ease == 'function' ?
- ease(pos) :
- pos
-
- // run plot function
- if (fx.destination.plot) {
- element.plot(fx.destination.plot.at(pos))
-
- } else {
- // run all x-position properties
- if (fx.destination.x)
- element.x(fx.destination.x.at(pos))
- else if (fx.destination.cx)
- element.cx(fx.destination.cx.at(pos))
-
- // run all y-position properties
- if (fx.destination.y)
- element.y(fx.destination.y.at(pos))
- else if (fx.destination.cy)
- element.cy(fx.destination.cy.at(pos))
-
- // run all size properties
- if (fx.destination.size)
- element.size(fx.destination.size.width.at(pos), fx.destination.size.height.at(pos))
- }
+ return this
+ }
- // run all viewbox properties
- if (fx.destination.viewbox)
- element.viewbox(
- fx.destination.viewbox.x.at(pos)
- , fx.destination.viewbox.y.at(pos)
- , fx.destination.viewbox.width.at(pos)
- , fx.destination.viewbox.height.at(pos)
- )
-
- // run leading property
- if (fx.destination.leading)
- element.leading(fx.destination.leading.at(pos))
-
- // animate attributes
- for (i = akeys.length - 1; i >= 0; i--)
- element.attr(akeys[i], at(fx.attrs[akeys[i]], pos))
-
- // animate styles
- for (i = skeys.length - 1; i >= 0; i--)
- element.style(skeys[i], at(fx.styles[skeys[i]], pos))
-
- // callback for each keyframe
- if (fx.situation.during)
- fx.situation.during.call(element, pos, function(from, to) {
- return at({ from: from, to: to }, pos)
- })
+ /**
+ * 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)
+
+ return this.queue(delay)
+ }
+
+ /**
+ * 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
}
- if (typeof d === 'number') {
- // delay animation
- this.timeout = setTimeout(function() {
- var start = new Date().getTime()
-
- // initialize situation object
- fx.situation.start = start
- fx.situation.play = true
- fx.situation.finish = start + d
- fx.situation.duration = d
- fx.situation.ease = ease
-
- // render function
- fx.render = function() {
-
- if (fx.situation.play === true) {
- // calculate pos
- var time = new Date().getTime()
- , pos = time > fx.situation.finish ? 1 : (time - fx.situation.start) / d
-
- // reverse pos if animation is reversed
- if (fx.situation.reversing)
- pos = -pos + 1
-
- // process values
- fx.at(pos)
-
- // finish off animation
- if (time > fx.situation.finish) {
- if (fx.destination.plot)
- element.plot(new SVG.PointArray(fx.destination.plot.destination).settle())
-
- if (fx.situation.loop === true || (typeof fx.situation.loop == 'number' && fx.situation.loop > 0)) {
- // register reverse
- if (fx.situation.reverse)
- fx.situation.reversing = !fx.situation.reversing
-
- if (typeof fx.situation.loop == 'number') {
- // reduce loop count
- if (!fx.situation.reverse || fx.situation.reversing)
- --fx.situation.loop
-
- // remove last loop if reverse is disabled
- if (!fx.situation.reverse && fx.situation.loop == 1)
- --fx.situation.loop
- }
-
- fx.animate(d, ease, delay)
- } else {
- fx.situation.after ? fx.situation.after.apply(element, [fx]) : fx.stop()
- }
-
- } else {
- fx.animationFrame = requestAnimationFrame(fx.render)
- }
- } else {
- fx.animationFrame = requestAnimationFrame(fx.render)
- }
+ return this._target
+ }
- }
+ // returns the position at a given time
+ , timeToPos: function(timestamp){
+ return (timestamp - this.current.start) / (this.current.duration)
+ }
+
+ // returns the timestamp from a given positon
+ , posToTime: function(pos){
+ return this.current.duration * pos + this.current.start
+ }
- // start animation
- fx.render()
+ // 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))
+ }
- }, new SVG.Number(delay).valueOf())
+ // cancels the animationframe
+ // TODO: remove this in favour of the oneliner
+ , stopAnimFrame: function(){
+ cancelAnimationFrame(this.animationFrame)
+ }
+
+ // 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
}
- // Get bounding box of target element
- , bbox: function() {
- return this.target.bbox()
+
+ /**
+ * 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
}
- // Add animatable attributes
- , attr: function(a, v) {
- // 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.target.attr(a)
-
- // detect format
- if (a == 'transform') {
- // merge given transformation with an existing one
- if (this.attrs[a])
- v = this.attrs[a].destination.multiply(v)
-
- // prepare matrix for morphing
- this.attrs[a] = (new SVG.Matrix(this.target)).morph(v)
-
- // 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
- }
- }
+ /**
+ * pulls next element from the queue and execute it
+ * @return this
+ */
+ , dequeue: function(){
+ // stop current animation
+ this.current && this.current.stop && this.current.stop()
+
+ // get next animation from queue
+ this.current = this.situations.shift()
- } else {
- this.attrs[a] = SVG.Color.isColor(v) ?
- // prepare color for morphing
- new SVG.Color(from).morph(v) :
- SVG.regex.numberAndUnit.test(v) ?
- // prepare number for morphing
- new SVG.Number(from).morph(v) :
- // prepare for plain morphing
- { from: from, to: v }
+ 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()
}
+
}
return this
}
- // Add animatable styles
- , style: function(s, v) {
- if (typeof s == 'object')
- for (var key in s)
- this.style(key, s[key])
- else
- this.styles[s] = { from: this.target.style(s), to: v }
+ // 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
+ 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 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{
+ s.attrs[i].value = this.target().attr(i)// + s.attrs[i].value
+ }
+ }
+
+ for(i in s.styles){
+ s.styles[i].value = this.target().style(i)
+ }
+
+ s.transformations = this.target().matrixify()
+
+ s.init = true
return this
}
- // Animatable x-axis
- , x: function(x) {
- this.destination.x = new SVG.Number(this.target.x()).morph(x)
-
+ , clearQueue: function(){
+ this.situations = []
return this
}
- // Animatable y-axis
- , y: function(y) {
- this.destination.y = new SVG.Number(this.target.y()).morph(y)
-
+ , clearCurrent: function(){
+ this.current = null
return this
}
- // Animatable center x-axis
- , cx: function(x) {
- this.destination.cx = new SVG.Number(this.target.cx()).morph(x)
+ /** 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()
- return this
+ if(clearQueue){
+ this.clearQueue()
+ }
+
+ this.active = false
+
+ if(jumpToEnd){
+ this.seek(1)
+ }
+
+ this.stopAnimFrame()
+ clearTimeout(this.timeout)
+
+ return this.clearCurrent()
}
- // Animatable center y-axis
- , cy: function(y) {
- this.destination.cy = new SVG.Number(this.target.cy()).morph(y)
+ /** 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
}
- // 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)
+
+ // 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().current && this.stop(true, false));
+
+ return this.clearQueue().clearCurrent()
}
- // 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 = this.target.bbox()
+ // set the internal animation pointer to the specified position and updates the visualisation
+ , seek: function(pos){
+ this.pos = pos
+ this.current.start = +new Date - pos * this.current.duration
+ this.current.end = this.current.start + this.current.duration
+ return this.step(true)
+ }
- this.destination.size = {
- width: new SVG.Number(box.width).morph(width)
- , height: new SVG.Number(box.height).morph(height)
- }
- }
+ // speeds up the animation by the given factor
+ // this changes the duration of the animation
+ , speed: function(speed){
+ 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
}
- // Add animatable plot
- , plot: function(p) {
- this.destination.plot = p
+ // pauses the animation
+ , pause: function(){
+ this.paused = true
+ this.stopAnimFrame()
+ clearTimeout(this.timeout)
return this
}
- // Add leading method
- , leading: function(value) {
- if (this.target.destination.leading)
- this.destination.leading = new SVG.Number(this.target.destination.leading).morph(value)
- return this
+ // unpause the animation
+ , play: function(){
+ if(!this.paused) return this
+ this.paused = false
+ return this.seek(this.pos)
}
- // Add animatable viewbox
- , viewbox: function(x, y, width, height) {
- if (this.target instanceof SVG.Container) {
- var box = this.target.viewbox()
- this.destination.viewbox = {
- x: new SVG.Number(box.x).morph(x)
- , y: new SVG.Number(box.y).morph(y)
- , width: new SVG.Number(box.width).morph(width)
- , height: new SVG.Number(box.height).morph(height)
- }
- }
+ /** 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
}
- // Add animateable gradient update
- , update: function(o) {
- if (this.target instanceof SVG.Stop) {
- 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', new SVG.Number(o.offset))
- }
- 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
}
- // Add callback for each keyframe
- , during: function(during) {
- this.situation.during = during
+ /**
+ * 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()
+ , wrapper = 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
}
- // Callback after animation
- , after: function(after) {
- this.situation.after = after
- return this
+ // adds a callback which is called whenever one animation step is performed
+ , during: function(fn){
+ var c = this.last()
+ , wrapper = function(e){
+ 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)
+
+ return this.after(function(){
+ this.off('during.fx', wrapper)
+ })
}
- // Make loopable
- , loop: function(times, reverse) {
- // store current loop and total loops
- this.situation.loop = this.situation.loops = times || true
- // make reversable
- this.situation.reverse = !!reverse
+ // 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
}
- // Stop running animation
- , stop: function(fulfill) {
- // fulfill animation
- if (fulfill === true) {
- this.animate(0)
+ // calls on every animation step for all animations
+ , duringAll: function(fn){
+ var wrapper = function(e){
+ fn.call(this, e.detail.pos, e.detail.eased, e.detail.fx, e.detail.situation)
+ }
- if (this.situation.after)
- this.situation.after.apply(this.target, [this])
+ this.target().off('during.fx', wrapper).on('during.fx', wrapper)
- } else {
- // stop current animation
- clearTimeout(this.timeout)
- cancelAnimationFrame(this.animationFrame);
-
- // reset storage for properties
- this.attrs = {}
- this.styles = {}
- this.situation = {}
- this.destination = {}
+ return this.afterAll(function(){
+ this.off('during.fx', wrapper)
+ })
+ }
+
+ /**
+ * 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
+
+ for(var i = 0, len = this.situations.length; i < len; ++i){
+ end += (situations[i].duration || 0) + (situations[i].delay || 0)
}
+ return (this.pos * this.current.duration + this.start - start) / (end - start)
+ }
+
+ , last: function(){
+ return this.situations.length ? this.situations[this.situations.length-1] : this.current
+ }
+
+ // 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
}
- // Pause running animation
- , pause: function() {
- if (this.situation.play === true) {
- this.situation.play = false
- this.situation.pause = new Date().getTime()
+
+ /** 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 position
+ if(!ignoreTime) this.pos = this.timeToPos(+new Date)
+
+ 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
+
+ // apply easing
+ var eased = this.current.easing(this.pos)
+
+ // call once-callbacks
+ for(var i in this.current.once){
+ if(i > this.lastPos && i <= eased){
+ 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, situation: this.current})
+
+ // apply the actual animation to every property
+ this.eachAt()
+
+ // do final code when situation is finished
+ if((this.pos == 1 && !this.current.reversed) || (this.current.reversed && this.pos == 0)){
+
+ // stop animation callback
+ cancelAnimationFrame(this.animationFrame)
+
+ // fire finished callback with current situation as parameter
+ this.target().fire('finished', {fx:this, situation: this.current})
+
+ if(!this.situations.length && !this.current && this.active){
+ this.target().fire('allfinished')
+ this.target().off('.fx')
+ 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
+
}
- // Play running animation
- , play: function() {
- if (this.situation.play === false) {
- var pause = new Date().getTime() - this.situation.pause
- this.situation.finish += pause
- this.situation.start += pause
- this.situation.play = true
+ // 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(), c = this.current
+
+ // apply animations which can be called trough a method
+ for(i in c.animations){
+
+ 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)
+
+ }
+
+ // apply animation which has to be applied with attr()
+ for(i in c.attrs){
+
+ 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)
+
}
+ // apply animation which has to be applied with style()
+ for(i in c.styles){
+
+ 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)
+
+ }
+
+ // animate transformations which has to be chained
+ 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)
+ }
+
+ return this
+
+ }
+
+
+ // adds an once-callback which is called at a specific position and never again
+ , once: function(pos, fn, isEased){
+
+ if(!isEased)pos = this.current.easing(pos)
+
+ this.current.once[pos] = fn
+
return this
}
}
- // Define parent class
, 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(d, ease, delay) {
- return (this.fx || (this.fx = new SVG.FX(this))).stop().animate(d, ease, delay)
+ 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))).delay(delay)
}
- // Stop current animation; this is an alias to the fx instance
- , stop: function(fulfill) {
+ , stop: function(jumpToEnd, clearQueue) {
if (this.fx)
- this.fx.stop(fulfill)
+ this.fx.stop(jumpToEnd, clearQueue)
return this
}
@@ -1702,10 +1954,165 @@ SVG.FX = SVG.invent({
return this
}
+ }
+
+})
+
+// MorphObj is used whenever no morphable object is given
+SVG.MorphObj = SVG.invent({
+
+ create: function(to){
+ // prepare color for morphing
+ if(SVG.Color.isColor(to)) return new SVG.Color().morph(to)
+ // prepare number for morphing
+ if(SVG.regex.numberAndUnit.test(to)) return new SVG.Number().morph(to)
+
+ // prepare for plain morphing
+ this.value = 0
+ this.destination = to
+ }
+
+, extend: {
+ at: function(pos, real){
+ return real < 1 ? this.value : this.destination
+ },
+ valueOf: function(){
+ return this.value
+ }
}
+
})
+SVG.extend(SVG.FX, {
+ // 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 {
+ // the MorphObj takes care about the right function used
+ this.add(a, new SVG.MorphObj(v), 'attrs')
+ }
+
+ return this
+ }
+ // Add animatable styles
+, style: function(s, v) {
+ if (typeof s == 'object')
+ for (var key in s)
+ this.style(key, s[key])
+
+ else
+ this.add(s, new SVG.MorphObj(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().morph(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().morph(y)
+ num.relative = relative
+ return this.add('y', num)
+ }
+ // Animatable center x-axis
+, cx: function(x) {
+ return this.add('cx', new SVG.Number().morph(x))
+ }
+ // Animatable center y-axis
+, cy: function(y) {
+ return this.add('cy', new SVG.Number().morph(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().morph(width))
+ .add('height', new SVG.Number().morph(height))
+
+ }
+
+ return this
+ }
+ // Add animatable plot
+, plot: function(p) {
+ return this.add('plot', this.target().array().morph(p))
+ }
+ // Add leading method
+, leading: function(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) {
+ 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
+ }
+})
SVG.BBox = SVG.invent({
// Initialize
create: function(element) {
@@ -1887,7 +2294,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]]
}
@@ -1905,6 +2312,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)
@@ -1919,6 +2328,7 @@ SVG.Matrix = SVG.invent({
, d: this.d
, e: this.e
, f: this.f
+ , matrix: new SVG.Matrix(this)
}
}
// Clone matrix
@@ -2214,11 +2624,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
@@ -2226,20 +2636,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
@@ -2257,24 +2658,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
-
// 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) {
@@ -2330,7 +2719,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
}
})
@@ -2378,6 +2844,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) {
@@ -2588,6 +3184,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) {
@@ -2603,8 +3200,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)
@@ -2619,6 +3219,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
@@ -2720,11 +3323,11 @@ SVG.G = SVG.invent({
}
// 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() {
@@ -3737,8 +4340,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)
})
@@ -3890,7 +4495,7 @@ SVG.TextPath = SVG.invent({
create: 'textPath'
// Inherit from
-, inherit: SVG.Element
+, inherit: SVG.Parent
// Define parent class
, parent: SVG.Text
@@ -4146,11 +4751,11 @@ SVG.extend(SVG.Element, SVG.FX, {
}
// Relative move over x axis
, dx: function(x) {
- return this.x((this.target || this).x() + x)
+ return this.x((this instanceof SVG.FX ? 0 : this.x()) + x, true)
}
// Relative move over y axis
, dy: function(y) {
- return this.y((this.target || this).y() + y)
+ return this.y((this instanceof SVG.FX ? 0 : this.y()) + y, true)
}
// Relative move over x and y axes
, dmove: function(x, y) {
@@ -4437,6 +5042,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);
diff --git a/dist/svg.min.js b/dist/svg.min.js
index ba178e4..d562ee4 100644
--- a/dist/svg.min.js
+++ b/dist/svg.min.js
@@ -1,2 +1,2 @@
-/*! svg.js v2.2.5 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t,t.document)}):"object"==typeof exports?module.exports=t.document?e(t,t.document):function(t){return e(t,t.document)}:t.SVG=e(t,t.document)}("undefined"!=typeof window?window:this,function(t,e){function n(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function i(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function r(t){return t.charAt(0).toUpperCase()+t.slice(1)}function s(t){return 4==t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}function o(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function a(t,e,n){return null==n?n=t.height/t.width*e:null==e&&(e=t.width/t.height*n),{width:e,height:n}}function h(t,e,n){return{x:e*t.a+n*t.c+0,y:e*t.b+n*t.d+0}}function u(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function l(t){return t instanceof y.Matrix||(t=new y.Matrix(t)),t}function c(t,e){t.cx=null==t.cx?e.bbox().cx:t.cx,t.cy=null==t.cy?e.bbox().cy:t.cy}function f(t){return t=t.replace(y.regex.whitespace,"").replace(y.regex.matrix,"").split(y.regex.matrixElements),u(y.utils.map(t,function(t){return parseFloat(t)}))}function d(t,e){return"number"==typeof t.from?t.from+(t.to-t.from)*e:t instanceof y.Color||t instanceof y.Number||t instanceof y.Matrix?t.at(e):1>e?t.from:t.to}function p(t){for(var e=0,n=t.length,i="";n>e;e++)i+=t[e][0],null!=t[e][1]&&(i+=t[e][1],null!=t[e][2]&&(i+=" ",i+=t[e][2],null!=t[e][3]&&(i+=" ",i+=t[e][3],i+=" ",i+=t[e][4],null!=t[e][5]&&(i+=" ",i+=t[e][5],i+=" ",i+=t[e][6],null!=t[e][7]&&(i+=" ",i+=t[e][7])))));return i+" "}function m(t){for(var e=t.childNodes.length-1;e>=0;e--)t.childNodes[e]instanceof SVGElement&&m(t.childNodes[e]);return y.adopt(t).id(y.eid(t.nodeName))}function x(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function g(t){var e=t.toString().match(y.regex.reference);return e?e[1]:void 0}var y=this.SVG=function(t){return y.supported?(t=new y.Doc(t),y.parser||y.prepare(t),t):void 0};if(y.ns="http://www.w3.org/2000/svg",y.xmlns="http://www.w3.org/2000/xmlns/",y.xlink="http://www.w3.org/1999/xlink",y.svgjs="http://svgjs.com/svgjs",y.supported=function(){return!!e.createElementNS&&!!e.createElementNS(y.ns,"svg").createSVGRect}(),!y.supported)return!1;y.did=1e3,y.eid=function(t){return"Svgjs"+r(t)+y.did++},y.create=function(t){var n=e.createElementNS(this.ns,t);return n.setAttribute("id",this.eid(t)),n},y.extend=function(){var t,e,n,i;for(t=[].slice.call(arguments),e=t.pop(),i=t.length-1;i>=0;i--)if(t[i])for(n in e)t[i].prototype[n]=e[n];y.Set&&y.Set.inherit&&y.Set.inherit()},y.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,y.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&y.extend(e,t.extend),t.construct&&y.extend(t.parent||y.Container,t.construct),e},y.adopt=function(t){if(!t)return null;if(t.instance)return t.instance;var e;return e="svg"==t.nodeName?t.parentNode instanceof SVGElement?new y.Nested:new y.Doc:"linearGradient"==t.nodeName?new y.Gradient("linear"):"radialGradient"==t.nodeName?new y.Gradient("radial"):y[r(t.nodeName)]?new(y[r(t.nodeName)]):new y.Element(t),e.type=t.nodeName,e.node=t,t.instance=e,e instanceof y.Doc&&e.namespace().defs(),e.setData(JSON.parse(t.getAttribute("svgjs:data"))||{}),e},y.prepare=function(t){var n=e.getElementsByTagName("body")[0],i=(n?new y.Doc(n):t.nested()).size(2,0),r=y.create("path");i.node.appendChild(r),y.parser={body:n||t.parent(),draw:i.style("opacity:0;position:fixed;left:100%;top:100%;overflow:hidden"),poly:i.polyline().node,path:r}},y.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,matrix:/matrix\(|\)/g,matrixElements:/,*\s+|,/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,negExp:/e\-/gi,comma:/,/g,hyphen:/\-/g,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,whitespaces:/\s+/,X:/X/g},y.utils={map:function(t,e){var n,i=t.length,r=[];for(n=0;i>n;n++)r.push(e(t[n]));return r},radians:function(t){return t%360*Math.PI/180},degrees:function(t){return 180*t/Math.PI%360},filterSVGElements:function(t){return[].filter.call(t,function(t){return t instanceof SVGElement})}},y.defaults={attrs:{"fill-opacity":1,"stroke-opacity":1,"stroke-width":0,"stroke-linejoin":"miter","stroke-linecap":"butt",fill:"#000000",stroke:"#000000",opacity:1,x:0,y:0,cx:0,cy:0,width:0,height:0,r:0,rx:0,ry:0,offset:0,"stop-opacity":1,"stop-color":"#000000","font-size":16,"font-family":"Helvetica, Arial, sans-serif","text-anchor":"start"}},y.Color=function(t){var e;this.r=0,this.g=0,this.b=0,"string"==typeof t?y.regex.isRgb.test(t)?(e=y.regex.rgb.exec(t.replace(/\s/g,"")),this.r=parseInt(e[1]),this.g=parseInt(e[2]),this.b=parseInt(e[3])):y.regex.isHex.test(t)&&(e=y.regex.hex.exec(s(t)),this.r=parseInt(e[1],16),this.g=parseInt(e[2],16),this.b=parseInt(e[3],16)):"object"==typeof t&&(this.r=t.r,this.g=t.g,this.b=t.b)},y.extend(y.Color,{toString:function(){return this.toHex()},toHex:function(){return"#"+o(this.r)+o(this.g)+o(this.b)},toRgb:function(){return"rgb("+[this.r,this.g,this.b].join()+")"},brightness:function(){return this.r/255*.3+this.g/255*.59+this.b/255*.11},morph:function(t){return this.destination=new y.Color(t),this},at:function(t){return this.destination?(t=0>t?0:t>1?1:t,new y.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),y.Color.test=function(t){return t+="",y.regex.isHex.test(t)||y.regex.isRgb.test(t)},y.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},y.Color.isColor=function(t){return y.Color.isRgb(t)||y.Color.test(t)},y.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},y.extend(y.Array,{morph:function(t){if(this.destination=this.parse(t),this.value.length!=this.destination.length){for(var e=this.value[this.value.length-1],n=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(n);for(;this.value.length<this.destination.length;)this.value.push(e)}return this},settle:function(){for(var t=0,e=this.value.length,n=[];e>t;t++)-1==n.indexOf(this.value[t])&&n.push(this.value[t]);return this.value=n},at:function(t){if(!this.destination)return this;for(var e=0,n=this.value.length,i=[];n>e;e++)i.push(this.value[e]+(this.destination[e]-this.value[e])*t);return new y.Array(i)},toString:function(){return this.value.join(" ")},valueOf:function(){return this.value},parse:function(t){return t=t.valueOf(),Array.isArray(t)?t:this.split(t)},split:function(t){return t.trim().split(/\s+/)},reverse:function(){return this.value.reverse(),this}}),y.PointArray=function(t,e){this.constructor.call(this,t,e||[[0,0]])},y.PointArray.prototype=new y.Array,y.extend(y.PointArray,{toString:function(){for(var t=0,e=this.value.length,n=[];e>t;t++)n.push(this.value[t].join(","));return n.join(" ")},toLine:function(){return{x1:this.value[0][0],y1:this.value[0][1],x2:this.value[1][0],y2:this.value[1][1]}},at:function(t){if(!this.destination)return this;for(var e=0,n=this.value.length,i=[];n>e;e++)i.push([this.value[e][0]+(this.destination[e][0]-this.value[e][0])*t,this.value[e][1]+(this.destination[e][1]-this.value[e][1])*t]);return new y.PointArray(i)},parse:function(t){if(t=t.valueOf(),Array.isArray(t))return t;t=this.split(t);for(var e,n=0,i=t.length,r=[];i>n;n++)e=t[n].split(","),r.push([parseFloat(e[0]),parseFloat(e[1])]);return r},move:function(t,e){var n=this.bbox();if(t-=n.x,e-=n.y,!isNaN(t)&&!isNaN(e))for(var i=this.value.length-1;i>=0;i--)this.value[i]=[this.value[i][0]+t,this.value[i][1]+e];return this},size:function(t,e){var n,i=this.bbox();for(n=this.value.length-1;n>=0;n--)this.value[n][0]=(this.value[n][0]-i.x)*t/i.width+i.x,this.value[n][1]=(this.value[n][1]-i.y)*e/i.height+i.y;return this},bbox:function(){return y.parser.poly.setAttribute("points",this.toString()),y.parser.poly.getBBox()}}),y.PathArray=function(t,e){this.constructor.call(this,t,e||[["M",0,0]])},y.PathArray.prototype=new y.Array,y.extend(y.PathArray,{toString:function(){return p(this.value)},move:function(t,e){var n=this.bbox();if(t-=n.x,e-=n.y,!isNaN(t)&&!isNaN(e))for(var i,r=this.value.length-1;r>=0;r--)i=this.value[r][0],"M"==i||"L"==i||"T"==i?(this.value[r][1]+=t,this.value[r][2]+=e):"H"==i?this.value[r][1]+=t:"V"==i?this.value[r][1]+=e:"C"==i||"S"==i||"Q"==i?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"==i&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"==i&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var n,i,r=this.bbox();for(n=this.value.length-1;n>=0;n--)i=this.value[n][0],"M"==i||"L"==i||"T"==i?(this.value[n][1]=(this.value[n][1]-r.x)*t/r.width+r.x,this.value[n][2]=(this.value[n][2]-r.y)*e/r.height+r.y):"H"==i?this.value[n][1]=(this.value[n][1]-r.x)*t/r.width+r.x:"V"==i?this.value[n][1]=(this.value[n][1]-r.y)*e/r.height+r.y:"C"==i||"S"==i||"Q"==i?(this.value[n][1]=(this.value[n][1]-r.x)*t/r.width+r.x,this.value[n][2]=(this.value[n][2]-r.y)*e/r.height+r.y,this.value[n][3]=(this.value[n][3]-r.x)*t/r.width+r.x,this.value[n][4]=(this.value[n][4]-r.y)*e/r.height+r.y,"C"==i&&(this.value[n][5]=(this.value[n][5]-r.x)*t/r.width+r.x,this.value[n][6]=(this.value[n][6]-r.y)*e/r.height+r.y)):"A"==i&&(this.value[n][1]=this.value[n][1]*t/r.width,this.value[n][2]=this.value[n][2]*e/r.height,this.value[n][6]=(this.value[n][6]-r.x)*t/r.width+r.x,this.value[n][7]=(this.value[n][7]-r.y)*e/r.height+r.y);return this},parse:function(t){if(t instanceof y.PathArray)return t.valueOf();var e,n,i,r,s,o,a=0,h=0,u={M:2,L:2,H:1,V:1,C:6,S:4,Q:4,T:2,A:7};if("string"==typeof t){for(t=t.replace(y.regex.negExp,"X").replace(y.regex.pathLetters," $& ").replace(y.regex.hyphen," -").replace(y.regex.comma," ").replace(y.regex.X,"e-").trim().split(y.regex.whitespaces),e=t.length;--e;)if(t[e].indexOf(".")!=t[e].lastIndexOf(".")){var l=t[e].split("."),c=[l.shift(),l.shift()].join(".");t.splice.apply(t,[e,1].concat(c,l.map(function(t){return"."+t})))}}else t=t.reduce(function(t,e){return[].concat.apply(t,e)},[]);var o=[];do{for(y.regex.isPathLetter.test(t[0])?(r=t[0],t.shift()):"M"==r?r="L":"m"==r&&(r="l"),s=[r.toUpperCase()],e=0;e<u[s[0]];++e)s.push(parseFloat(t.shift()));r==s[0]?"M"==r||"L"==r||"C"==r||"Q"==r?(a=s[u[s[0]]-1],h=s[u[s[0]]]):"V"==r?h=s[1]:"H"==r?a=s[1]:"A"==r&&(a=s[6],h=s[7]):"m"==r||"l"==r||"c"==r||"s"==r||"q"==r||"t"==r?(s[1]+=a,s[2]+=h,null!=s[3]&&(s[3]+=a,s[4]+=h),null!=s[5]&&(s[5]+=a,s[6]+=h),a=s[u[s[0]]-1],h=s[u[s[0]]]):"v"==r?(s[1]+=h,h=s[1]):"h"==r?(s[1]+=a,a=s[1]):"a"==r&&(s[6]+=a,s[7]+=h,a=s[6],h=s[7]),"M"==s[0]&&(n=a,i=h),"Z"==s[0]&&(a=n,h=i),o.push(s)}while(t.length);return o},bbox:function(){return y.parser.path.setAttribute("d",this.toString()),y.parser.path.getBBox()}}),y.Number=y.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:0>t?-3.4e38:3.4e38:"string"==typeof t?(e=t.match(y.regex.numberAndUnit),e&&(this.value=parseFloat(e[1]),"%"==e[5]?this.value/=100:"s"==e[5]&&(this.value*=1e3),this.unit=e[5])):t instanceof y.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"==this.unit?~~(1e8*this.value)/1e6:"s"==this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},valueOf:function(){return this.value},plus:function(t){return new y.Number(this+new y.Number(t),this.unit)},minus:function(t){return this.plus(-new y.Number(t))},times:function(t){return new y.Number(this*new y.Number(t),this.unit)},divide:function(t){return new y.Number(this/new y.Number(t),this.unit)},to:function(t){var e=new y.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new y.Number(t),this},at:function(t){return this.destination?new y.Number(this.destination).minus(this).times(t).plus(this):this}}}),y.ViewBox=function(t){var e,n,i,r,s=1,o=1,a=t.bbox(),h=(t.attr("viewBox")||"").match(/-?[\d\.]+/g),u=t,l=t;for(i=new y.Number(t.width()),r=new y.Number(t.height());"%"==i.unit;)s*=i.value,i=new y.Number(u instanceof y.Doc?u.parent().offsetWidth:u.parent().width()),u=u.parent();for(;"%"==r.unit;)o*=r.value,r=new y.Number(l instanceof y.Doc?l.parent().offsetHeight:l.parent().height()),l=l.parent();this.x=a.x,this.y=a.y,this.width=i*s,this.height=r*o,this.zoom=1,h&&(e=parseFloat(h[0]),n=parseFloat(h[1]),i=parseFloat(h[2]),r=parseFloat(h[3]),this.zoom=this.width/this.height>i/r?this.height/r:this.width/i,this.x=e,this.y=n,this.width=i,this.height=r)},y.extend(y.ViewBox,{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height}}),y.Element=y.invent({create:function(t){this._stroke=y.defaults.attrs.stroke,this.dom={},(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke)},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var n=a(this.bbox(),t,e);return this.width(new y.Number(n.width)).height(new y.Number(n.height))},clone:function(){var t=m(this.node.cloneNode(!0));return this.after(t),t},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return t.put(this)},putIn:function(t){return t.add(this)},id:function(t){return this.attr("id",t)},inside:function(t,e){var n=this.bbox();return t>n.x&&e>n.y&&t<n.x+n.width&&e<n.y+n.height},show:function(){return this.style("display","")},hide:function(){return this.style("display","none")},visible:function(){return"none"!=this.style("display")},toString:function(){return this.attr("id")},classes:function(){var t=this.attr("class");return null==t?[]:t.trim().split(/\s+/)},hasClass:function(t){return-1!=this.classes().indexOf(t)},addClass:function(t){if(!this.hasClass(t)){var e=this.classes();e.push(t),this.attr("class",e.join(" "))}return this},removeClass:function(t){return this.hasClass(t)&&this.attr("class",this.classes().filter(function(e){return e!=t}).join(" ")),this},toggleClass:function(t){return this.hasClass(t)?this.removeClass(t):this.addClass(t)},reference:function(t){return y.get(this.attr(t))},parent:function(t){var e=this;if(!e.node.parentNode)return null;if(e=y.adopt(e.node.parentNode),!t)return e;for(;e&&e.node instanceof SVGElement;){if("string"==typeof t?e.matches(t):e instanceof t)return e;e=y.adopt(e.node.parentNode)}},doc:function(){return this instanceof y.Doc?this:this.parent(y.Doc)},parents:function(t){var e=[],n=this;do{if(n=n.parent(t),!n||!n.node)break;e.push(n)}while(n.parent);return e},matches:function(t){return n(this.node,t)},"native":function(){return this.node},svg:function(t){var n=e.createElement("svg");if(!(t&&this instanceof y.Parent))return n.appendChild(t=e.createElement("svg")),this.writeDataToDom(),t.appendChild(this.node.cloneNode(!0)),n.innerHTML.replace(/^<svg>/,"").replace(/<\/svg>$/,"");n.innerHTML="<svg>"+t.replace(/\n/,"").replace(/<(\w+)([^<]+?)\/>/g,"<$1$2></$1>")+"</svg>";for(var i=0,r=n.firstChild.childNodes.length;r>i;i++)this.node.appendChild(n.firstChild.firstChild);return this},writeDataToDom:function(){if(this.each||this.lines){var t=this.each?this:this.lines();t.each(function(){this.writeDataToDom()})}return this.node.removeAttribute("svgjs:data"),Object.keys(this.dom).length&&this.node.setAttribute("svgjs:data",JSON.stringify(this.dom)),this},setData:function(t){return this.dom=t,this}}}),y.FX=y.invent({create:function(t){this.target=t},extend:{animate:function(t,e,n){var i,r,s,o=this.target,a=this;return"object"==typeof t&&(n=t.delay,e=t.ease,t=t.duration),t="="==t?t:null==t?1e3:new y.Number(t).valueOf(),e=e||"<>",a.at=function(t){var n;if(t=0>t?0:t>1?1:t,null==i){i=[];for(s in a.attrs)i.push(s);if(o.morphArray&&(a.destination.plot||i.indexOf("points")>-1)){var h,u=new o.morphArray(a.destination.plot||a.attrs.points||o.array());a.destination.size&&u.size(a.destination.size.width.to,a.destination.size.height.to),h=u.bbox(),a.destination.x?u.move(a.destination.x.to,h.y):a.destination.cx&&u.move(a.destination.cx.to-h.width/2,h.y),h=u.bbox(),a.destination.y?u.move(h.x,a.destination.y.to):a.destination.cy&&u.move(h.x,a.destination.cy.to-h.height/2),a.destination={plot:o.array().morph(u)}}}if(null==r){r=[];for(s in a.styles)r.push(s)}for(t="<>"==e?-Math.cos(t*Math.PI)/2+.5:">"==e?Math.sin(t*Math.PI/2):"<"==e?-Math.cos(t*Math.PI/2)+1:"-"==e?t:"function"==typeof e?e(t):t,a.destination.plot?o.plot(a.destination.plot.at(t)):(a.destination.x?o.x(a.destination.x.at(t)):a.destination.cx&&o.cx(a.destination.cx.at(t)),a.destination.y?o.y(a.destination.y.at(t)):a.destination.cy&&o.cy(a.destination.cy.at(t)),a.destination.size&&o.size(a.destination.size.width.at(t),a.destination.size.height.at(t))),a.destination.viewbox&&o.viewbox(a.destination.viewbox.x.at(t),a.destination.viewbox.y.at(t),a.destination.viewbox.width.at(t),a.destination.viewbox.height.at(t)),a.destination.leading&&o.leading(a.destination.leading.at(t)),n=i.length-1;n>=0;n--)o.attr(i[n],d(a.attrs[i[n]],t));for(n=r.length-1;n>=0;n--)o.style(r[n],d(a.styles[r[n]],t));a.situation.during&&a.situation.during.call(o,t,function(e,n){return d({from:e,to:n},t)})},"number"==typeof t&&(this.timeout=setTimeout(function(){var i=(new Date).getTime();a.situation.start=i,a.situation.play=!0,a.situation.finish=i+t,a.situation.duration=t,a.situation.ease=e,a.render=function(){if(a.situation.play===!0){var i=(new Date).getTime(),r=i>a.situation.finish?1:(i-a.situation.start)/t;a.situation.reversing&&(r=-r+1),a.at(r),i>a.situation.finish?(a.destination.plot&&o.plot(new y.PointArray(a.destination.plot.destination).settle()),a.situation.loop===!0||"number"==typeof a.situation.loop&&a.situation.loop>0?(a.situation.reverse&&(a.situation.reversing=!a.situation.reversing),"number"==typeof a.situation.loop&&((!a.situation.reverse||a.situation.reversing)&&--a.situation.loop,a.situation.reverse||1!=a.situation.loop||--a.situation.loop),a.animate(t,e,n)):a.situation.after?a.situation.after.apply(o,[a]):a.stop()):a.animationFrame=requestAnimationFrame(a.render)}else a.animationFrame=requestAnimationFrame(a.render)},a.render()},new y.Number(n).valueOf())),this},bbox:function(){return this.target.bbox()},attr:function(t,e){if("object"==typeof t)for(var n in t)this.attr(n,t[n]);else{var i=this.target.attr(t);"transform"==t?(this.attrs[t]&&(e=this.attrs[t].destination.multiply(e)),this.attrs[t]=new y.Matrix(this.target).morph(e),this.param&&(e=this.target.transform("rotation"),this.attrs[t].param={from:this.target.param||{rotation:e,cx:this.param.cx,cy:this.param.cy},to:this.param})):this.attrs[t]=y.Color.isColor(e)?new y.Color(i).morph(e):y.regex.numberAndUnit.test(e)?new y.Number(i).morph(e):{from:i,to:e}}return this},style:function(t,e){if("object"==typeof t)for(var n in t)this.style(n,t[n]);else this.styles[t]={from:this.target.style(t),to:e};return this},x:function(t){return this.destination.x=new y.Number(this.target.x()).morph(t),this},y:function(t){return this.destination.y=new y.Number(this.target.y()).morph(t),this},cx:function(t){return this.destination.cx=new y.Number(this.target.cx()).morph(t),this},cy:function(t){return this.destination.cy=new y.Number(this.target.cy()).morph(t),this},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},size:function(t,e){if(this.target instanceof y.Text)this.attr("font-size",t);else{var n=this.target.bbox();this.destination.size={width:new y.Number(n.width).morph(t),height:new y.Number(n.height).morph(e)}}return this},plot:function(t){return this.destination.plot=t,this},leading:function(t){return this.target.destination.leading&&(this.destination.leading=new y.Number(this.target.destination.leading).morph(t)),this},viewbox:function(t,e,n,i){if(this.target instanceof y.Container){var r=this.target.viewbox();this.destination.viewbox={x:new y.Number(r.x).morph(t),y:new y.Number(r.y).morph(e),width:new y.Number(r.width).morph(n),height:new y.Number(r.height).morph(i)}}return this},update:function(t){return this.target instanceof y.Stop&&(null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new y.Number(t.offset))),this},during:function(t){return this.situation.during=t,this},after:function(t){return this.situation.after=t,this},loop:function(t,e){return this.situation.loop=this.situation.loops=t||!0,this.situation.reverse=!!e,this},stop:function(t){return t===!0?(this.animate(0),this.situation.after&&this.situation.after.apply(this.target,[this])):(clearTimeout(this.timeout),cancelAnimationFrame(this.animationFrame),this.attrs={},this.styles={},this.situation={},this.destination={}),this},pause:function(){return this.situation.play===!0&&(this.situation.play=!1,this.situation.pause=(new Date).getTime()),this},play:function(){if(this.situation.play===!1){var t=(new Date).getTime()-this.situation.pause;this.situation.finish+=t,this.situation.start+=t,this.situation.play=!0}return this}},parent:y.Element,construct:{animate:function(t,e,n){return(this.fx||(this.fx=new y.FX(this))).stop().animate(t,e,n)},stop:function(t){return this.fx&&this.fx.stop(t),this},pause:function(){return this.fx&&this.fx.pause(),this},play:function(){return this.fx&&this.fx.play(),this}}}),y.BBox=y.invent({create:function(t){if(t){var e;try{e=t.node.getBBox()}catch(n){if(t instanceof y.Shape){var i=t.clone().addTo(y.parser.draw);e=i.bbox(),i.remove()}else e={x:t.node.clientLeft,y:t.node.clientTop,width:t.node.clientWidth,height:t.node.clientHeight}}this.x=e.x,this.y=e.y,this.width=e.width,this.height=e.height}x(this)},parent:y.Element,construct:{bbox:function(){return new y.BBox(this)}}}),y.TBox=y.invent({create:function(t){if(t){var e=t.ctm().extract(),n=t.bbox();this.width=n.width*e.scaleX,this.height=n.height*e.scaleY,this.x=n.x+e.x,this.y=n.y+e.y}x(this)},parent:y.Element,construct:{tbox:function(){return new y.TBox(this)}}}),y.RBox=y.invent({create:function(e){if(e){var n=e.doc().parent(),i=e.node.getBoundingClientRect(),r=1;for(this.x=i.left,this.y=i.top,this.x-=n.offsetLeft,this.y-=n.offsetTop;n=n.offsetParent;)this.x-=n.offsetLeft,this.y-=n.offsetTop;for(n=e;n.parent&&(n=n.parent());)n.viewbox&&(r*=n.viewbox().zoom,this.x-=n.x()||0,this.y-=n.y()||0);this.width=i.width/=r,this.height=i.height/=r}x(this),this.x+=t.pageXOffset,this.y+=t.pageYOffset},parent:y.Element,construct:{rbox:function(){return new y.RBox(this)}}}),[y.BBox,y.TBox,y.RBox].forEach(function(t){y.extend(t,{merge:function(e){var n=new t;return n.x=Math.min(this.x,e.x),n.y=Math.min(this.y,e.y),n.width=Math.max(this.x+this.width,e.x+e.width)-n.x,n.height=Math.max(this.y+this.height,e.y+e.height)-n.y,x(n)}})}),y.Matrix=y.invent({create:function(t){var e,n=u([1,0,0,1,0,0]);for(t=t instanceof y.Element?t.matrixify():"string"==typeof t?f(t):6==arguments.length?u([].slice.call(arguments)):"object"==typeof t?t:n,e=b.length-1;e>=0;e--)this[b[e]]=t&&"number"==typeof t[b[e]]?t[b[e]]:n[b[e]]},extend:{extract:function(){var t=h(this,0,1),e=h(this,1,0),n=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,skewX:-n,skewY:180/Math.PI*Math.atan2(e.y,e.x),scaleX:Math.sqrt(this.a*this.a+this.b*this.b),scaleY:Math.sqrt(this.c*this.c+this.d*this.d),rotation:n,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f}},clone:function(){return new y.Matrix(this)},morph:function(t){return this.destination=new y.Matrix(t),this},at:function(t){if(!this.destination)return this;var e=new y.Matrix({a:this.a+(this.destination.a-this.a)*t,b:this.b+(this.destination.b-this.b)*t,c:this.c+(this.destination.c-this.c)*t,d:this.d+(this.destination.d-this.d)*t,e:this.e+(this.destination.e-this.e)*t,f:this.f+(this.destination.f-this.f)*t});if(this.param&&this.param.to){var n={rotation:this.param.from.rotation+(this.param.to.rotation-this.param.from.rotation)*t,cx:this.param.from.cx,cy:this.param.from.cy};e=e.rotate((this.param.to.rotation-2*this.param.from.rotation)*t,n.cx,n.cy),e.param=n}return e},multiply:function(t){return new y.Matrix(this.native().multiply(l(t).native()))},inverse:function(){return new y.Matrix(this.native().inverse())},translate:function(t,e){return new y.Matrix(this.native().translate(t||0,e||0))},scale:function(t,e,n,i){return(1==arguments.length||3==arguments.length)&&(e=t),3==arguments.length&&(i=n,n=e),this.around(n,i,new y.Matrix(t,0,0,e,0,0))},rotate:function(t,e,n){return t=y.utils.radians(t),this.around(e,n,new y.Matrix(Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0))},flip:function(t,e){return"x"==t?this.scale(-1,1,e,0):this.scale(1,-1,0,e)},skew:function(t,e,n,i){return this.around(n,i,this.native().skewX(t||0).skewY(e||0))},skewX:function(t,e,n){return this.around(e,n,this.native().skewX(t||0))},skewY:function(t,e,n){return this.around(e,n,this.native().skewY(t||0))},around:function(t,e,n){return this.multiply(new y.Matrix(1,0,0,1,t||0,e||0)).multiply(n).multiply(new y.Matrix(1,0,0,1,-t||0,-e||0))},"native":function(){for(var t=y.parser.draw.node.createSVGMatrix(),e=b.length-1;e>=0;e--)t[b[e]]=this[b[e]];return t},toString:function(){return"matrix("+this.a+","+this.b+","+this.c+","+this.d+","+this.e+","+this.f+")"}},parent:y.Element,construct:{ctm:function(){return new y.Matrix(this.node.getCTM())},screenCTM:function(){return new y.Matrix(this.node.getScreenCTM())}}}),y.Point=y.invent({create:function(t,e){var n,i={x:0,y:0};n=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:null!=e?{x:t,y:e}:i,this.x=n.x,this.y=n.y},extend:{clone:function(){return new y.Point(this)},morph:function(t){return this.destination=new y.Point(t),this},at:function(t){if(!this.destination)return this;var e=new y.Point({x:this.x+(this.destination.x-this.x)*t,y:this.y+(this.destination.y-this.y)*t});return e},"native":function(){var t=y.parser.draw.node.createSVGPoint();return t.x=this.x,t.y=this.y,t},transform:function(t){return new y.Point(this.native().matrixTransform(t.native()))}}}),y.extend(y.Element,{point:function(t,e){return new y.Point(t,e).transform(this.screenCTM().inverse())}}),y.extend(y.Element,{attr:function(t,e,n){if(null==t){for(t={},e=this.node.attributes,n=e.length-1;n>=0;n--)t[e[n].nodeName]=y.regex.isNumber.test(e[n].nodeValue)?parseFloat(e[n].nodeValue):e[n].nodeValue;return t}if("object"==typeof t)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return e=this.node.getAttribute(t),null==e?y.defaults.attrs[t]:y.regex.isNumber.test(e)?parseFloat(e):e;"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),("fill"==t||"stroke"==t)&&(y.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof y.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new y.Number(e):y.Color.isColor(e)?e=new y.Color(e):Array.isArray(e)?e=new y.Array(e):e instanceof y.Matrix&&e.param&&(this.param=e.param),"leading"==t?this.leading&&this.leading(e):"string"==typeof n?this.node.setAttributeNS(n,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this}}),y.extend(y.Element,y.FX,{transform:function(t,e){var n,i=this.target||this;if("object"!=typeof t)return n=new y.Matrix(i).extract(),"object"==typeof this.param&&(n.rotation=this.param.rotation,n.cx=this.param.cx,n.cy=this.param.cy),"string"==typeof t?n[t]:n;if(n=this instanceof y.FX&&this.attrs.transform?this.attrs.transform:new y.Matrix(i),e=!!e||!!t.relative,null!=t.a)n=e?n.multiply(new y.Matrix(t)):new y.Matrix(t);else if(null!=t.rotation)c(t,i),e&&(t.rotation+=this.param&&null!=this.param.rotation?this.param.rotation:n.extract().rotation),this.param=t,this instanceof y.Element&&(n=e?n.rotate(t.rotation,t.cx,t.cy):n.rotate(t.rotation-n.extract().rotation,t.cx,t.cy));else if(null!=t.scale||null!=t.scaleX||null!=t.scaleY){if(c(t,i),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,!e){var r=n.extract();t.scaleX=1*t.scaleX/r.scaleX,t.scaleY=1*t.scaleY/r.scaleY}n=n.scale(t.scaleX,t.scaleY,t.cx,t.cy)}else if(null!=t.skewX||null!=t.skewY){if(c(t,i),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,!e){var r=n.extract();n=n.multiply((new y.Matrix).skew(r.skewX,r.skewY,t.cx,t.cy).inverse())}n=n.skew(t.skewX,t.skewY,t.cx,t.cy)}else t.flip?n=n.flip(t.flip,null==t.offset?i.bbox()["c"+t.flip]:t.offset):(null!=t.x||null!=t.y)&&(e?n=n.translate(t.x,t.y):(null!=t.x&&(n.e=t.x),null!=t.y&&(n.f=t.y)));return this.attr(this instanceof y.Pattern?"patternTransform":this instanceof y.Gradient?"gradientTransform":"transform",n)}}),y.extend(y.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){var t=(this.attr("transform")||"").split(/\)\s*/).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(y.regex.matrixElements).map(function(t){return parseFloat(t)})]}).reduce(function(t,e){return"matrix"==e[0]?t.multiply(u(e[1])):t[e[0]].apply(t,e[1])},new y.Matrix);return t},toParent:function(t){if(this==t)return this;var e=this.screenCTM(),n=t.rect(1,1),i=n.screenCTM().inverse();return n.remove(),this.addTo(t).untransform().transform(i.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),y.extend(y.Element,{style:function(t,e){if(0==arguments.length)return this.node.style.cssText||"";if(arguments.length<2)if("object"==typeof t)for(e in t)this.style(e,t[e]);else{if(!y.regex.isCss.test(t))return this.node.style[i(t)];t=t.split(";");for(var n=0;n<t.length;n++)e=t[n].split(":"),this.style(e[0].replace(/\s+/g,""),e[1])}else this.node.style[i(t)]=null===e||y.regex.isBlank.test(e)?"":e;return this}}),y.Parent=y.invent({create:function(t){this.constructor.call(this,t)},inherit:y.Element,extend:{children:function(){return y.utils.map(y.utils.filterSVGElements(this.node.childNodes),function(t){return y.adopt(t)})},add:function(t,e){return this.has(t)||(e=null==e?this.children().length:e,this.node.insertBefore(t.node,this.node.childNodes[e]||null)),this},put:function(t,e){return this.add(t,e),t},has:function(t){return this.index(t)>=0},index:function(t){return this.children().indexOf(t)},get:function(t){return this.children()[t]},first:function(){return this.children()[0]},last:function(){return this.children()[this.children().length-1]},each:function(t,e){var n,i,r=this.children();for(n=0,i=r.length;i>n;n++)r[n]instanceof y.Element&&t.apply(r[n],[n,r]),e&&r[n]instanceof y.Container&&r[n].each(t,e);return this},removeElement:function(t){return this.node.removeChild(t.node),this},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return delete this._defs,this},defs:function(){return this.doc().defs()}}}),y.extend(y.Parent,{ungroup:function(t,e){return 0===e||this instanceof y.Defs?this:(t=t||(this instanceof y.Doc?this:this.parent(y.Parent)),e=e||1/0,this.each(function(){return this instanceof y.Defs?this:this instanceof y.Parent?this.ungroup(t,e-1):this.toParent(t)
-}),this.node.firstChild||this.remove(),this)},flatten:function(t,e){return this.ungroup(t,e)}}),y.Container=y.invent({create:function(t){this.constructor.call(this,t)},inherit:y.Parent,extend:{viewbox:function(t){return 0==arguments.length?new y.ViewBox(this):(t=1==arguments.length?[t.x,t.y,t.width,t.height]:[].slice.call(arguments),this.attr("viewBox",t))}}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){y.Element.prototype[t]=function(e){var n=this;return this.node["on"+t]="function"==typeof e?function(){return e.apply(n,arguments)}:null,this}}),y.listeners=[],y.handlerMap=[],y.on=function(t,e,n,i){var r=n.bind(i||t.instance||t),s=(y.handlerMap.indexOf(t)+1||y.handlerMap.push(t))-1,o=e.split(".")[0],a=e.split(".")[1]||"*";y.listeners[s]=y.listeners[s]||{},y.listeners[s][o]=y.listeners[s][o]||{},y.listeners[s][o][a]=y.listeners[s][o][a]||{},y.listeners[s][o][a][n]=r,t.addEventListener(o,r,!1)},y.off=function(t,e,n){var i=y.handlerMap.indexOf(t),r=e&&e.split(".")[0],s=e&&e.split(".")[1];if(-1!=i)if(n)y.listeners[i][r]&&y.listeners[i][r][s||"*"]&&(t.removeEventListener(r,y.listeners[i][r][s||"*"][n],!1),delete y.listeners[i][r][s||"*"][n]);else if(s&&r){if(y.listeners[i][r]&&y.listeners[i][r][s]){for(n in y.listeners[i][r][s])y.off(t,[r,s].join("."),n);delete y.listeners[i][r][s]}}else if(s)for(e in y.listeners[i])for(namespace in y.listeners[i][e])s===namespace&&y.off(t,[e,s].join("."));else if(r){if(y.listeners[i][r]){for(namespace in y.listeners[i][r])y.off(t,[r,namespace].join("."));delete y.listeners[i][r]}}else{for(e in y.listeners[i])y.off(t,e);delete y.listeners[i]}},y.extend(y.Element,{on:function(t,e,n){return y.on(this.node,t,e,n),this},off:function(t,e){return y.off(this.node,t,e),this},fire:function(t,e){return t instanceof Event?this.node.dispatchEvent(t):this.node.dispatchEvent(new w(t,{detail:e})),this}}),y.Defs=y.invent({create:"defs",inherit:y.Container}),y.G=y.invent({create:"g",inherit:y.Container,extend:{x:function(t){return null==t?this.transform("x"):this.transform({x:t-this.x()},!0)},y:function(t){return null==t?this.transform("y"):this.transform({y:t-this.y()},!0)},cx:function(t){return null==t?this.tbox().cx:this.x(t-this.tbox().width/2)},cy:function(t){return null==t?this.tbox().cy:this.y(t-this.tbox().height/2)},gbox:function(){var t=this.bbox(),e=this.transform();return t.x+=e.x,t.x2+=e.x,t.cx+=e.x,t.y+=e.y,t.y2+=e.y,t.cy+=e.y,t}},construct:{group:function(){return this.put(new y.G)}}}),y.extend(y.Element,{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){var t=this.position()+1,e=this.parent();return e.removeElement(this).add(this,t),e instanceof y.Doc&&e.node.appendChild(e.defs().node),this},backward:function(){var t=this.position();return t>0&&this.parent().removeElement(this).add(this,t-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof y.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(t){t.remove();var e=this.position();return this.parent().add(t,e),this},after:function(t){t.remove();var e=this.position();return this.parent().add(t,e+1),this}}),y.Mask=y.invent({create:function(){this.constructor.call(this,y.create("mask")),this.targets=[]},inherit:y.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return this.targets=[],this.parent().removeElement(this),this}},construct:{mask:function(){return this.defs().put(new y.Mask)}}}),y.extend(y.Element,{maskWith:function(t){return this.masker=t instanceof y.Mask?t:this.parent().mask().add(t),this.masker.targets.push(this),this.attr("mask",'url("#'+this.masker.attr("id")+'")')},unmask:function(){return delete this.masker,this.attr("mask",null)}}),y.ClipPath=y.invent({create:function(){this.constructor.call(this,y.create("clipPath")),this.targets=[]},inherit:y.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return this.targets=[],this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new y.ClipPath)}}}),y.extend(y.Element,{clipWith:function(t){return this.clipper=t instanceof y.ClipPath?t:this.parent().clip().add(t),this.clipper.targets.push(this),this.attr("clip-path",'url("#'+this.clipper.attr("id")+'")')},unclip:function(){return delete this.clipper,this.attr("clip-path",null)}}),y.Gradient=y.invent({create:function(t){this.constructor.call(this,y.create(t+"Gradient")),this.type=t},inherit:y.Container,extend:{at:function(t,e,n){return this.put(new y.Stop).update(t,e,n)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},fill:function(){return"url(#"+this.id()+")"},toString:function(){return this.fill()},attr:function(t,e,n){return"transform"==t&&(t="gradientTransform"),y.Container.prototype.attr.call(this,t,e,n)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),y.extend(y.Gradient,y.FX,{from:function(t,e){return"radial"==(this.target||this).type?this.attr({fx:new y.Number(t),fy:new y.Number(e)}):this.attr({x1:new y.Number(t),y1:new y.Number(e)})},to:function(t,e){return"radial"==(this.target||this).type?this.attr({cx:new y.Number(t),cy:new y.Number(e)}):this.attr({x2:new y.Number(t),y2:new y.Number(e)})}}),y.extend(y.Defs,{gradient:function(t,e){return this.put(new y.Gradient(t)).update(e)}}),y.Stop=y.invent({create:"stop",inherit:y.Element,extend:{update:function(t){return("number"==typeof t||t instanceof y.Number)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new y.Number(t.offset)),this}}}),y.Pattern=y.invent({create:"pattern",inherit:y.Container,extend:{fill:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.fill()},attr:function(t,e,n){return"transform"==t&&(t="patternTransform"),y.Container.prototype.attr.call(this,t,e,n)}},construct:{pattern:function(t,e,n){return this.defs().pattern(t,e,n)}}}),y.extend(y.Defs,{pattern:function(t,e,n){return this.put(new y.Pattern).update(n).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),y.Doc=y.invent({create:function(t){t&&(t="string"==typeof t?e.getElementById(t):t,"svg"==t.nodeName?this.constructor.call(this,t):(this.constructor.call(this,y.create("svg")),t.appendChild(this.node)),this.namespace().size("100%","100%").defs())},inherit:y.Container,extend:{namespace:function(){return this.attr({xmlns:y.ns,version:"1.1"}).attr("xmlns:xlink",y.xlink,y.xmlns).attr("xmlns:svgjs",y.svgjs,y.xmlns)},defs:function(){if(!this._defs){var t;this._defs=(t=this.node.getElementsByTagName("defs")[0])?y.adopt(t):new y.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return"#document"==this.node.parentNode.nodeName?null:this.node.parentNode},spof:function(){var t=this.node.getScreenCTM();return t&&this.style("left",-t.e%1+"px").style("top",-t.f%1+"px"),this},remove:function(){return this.parent()&&this.parent().removeChild(this.node),this}}}),y.Shape=y.invent({create:function(t){this.constructor.call(this,t)},inherit:y.Element}),y.Bare=y.invent({create:function(t,e){if(this.constructor.call(this,y.create(t)),e)for(var n in e.prototype)"function"==typeof e.prototype[n]&&(this[n]=e.prototype[n])},inherit:y.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(e.createTextNode(t)),this}}}),y.extend(y.Parent,{element:function(t,e){return this.put(new y.Bare(t,e))},symbol:function(){return this.defs().element("symbol",y.Container)}}),y.Use=y.invent({create:"use",inherit:y.Shape,extend:{element:function(t,e){return this.attr("href",(e||"")+"#"+t,y.xlink)}},construct:{use:function(t,e){return this.put(new y.Use).element(t,e)}}}),y.Rect=y.invent({create:"rect",inherit:y.Shape,construct:{rect:function(t,e){return this.put(new y.Rect).size(t,e)}}}),y.Circle=y.invent({create:"circle",inherit:y.Shape,construct:{circle:function(t){return this.put(new y.Circle).rx(new y.Number(t).divide(2)).move(0,0)}}}),y.extend(y.Circle,y.FX,{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),y.Ellipse=y.invent({create:"ellipse",inherit:y.Shape,construct:{ellipse:function(t,e){return this.put(new y.Ellipse).size(t,e).move(0,0)}}}),y.extend(y.Ellipse,y.Rect,y.FX,{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),y.extend(y.Circle,y.Ellipse,{x:function(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())},y:function(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",t)},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",t)},width:function(t){return null==t?2*this.rx():this.rx(new y.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new y.Number(t).divide(2))},size:function(t,e){var n=a(this.bbox(),t,e);return this.rx(new y.Number(n.width).divide(2)).ry(new y.Number(n.height).divide(2))}}),y.Line=y.invent({create:"line",inherit:y.Shape,extend:{array:function(){return new y.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,n,i){return t=4==arguments.length?{x1:t,y1:e,x2:n,y2:i}:new y.PointArray(t).toLine(),this.attr(t)},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var n=a(this.bbox(),t,e);return this.attr(this.array().size(n.width,n.height).toLine())}},construct:{line:function(t,e,n,i){return this.put(new y.Line).plot(t,e,n,i)}}}),y.Polyline=y.invent({create:"polyline",inherit:y.Shape,construct:{polyline:function(t){return this.put(new y.Polyline).plot(t)}}}),y.Polygon=y.invent({create:"polygon",inherit:y.Shape,construct:{polygon:function(t){return this.put(new y.Polygon).plot(t)}}}),y.extend(y.Polyline,y.Polygon,{array:function(){return this._array||(this._array=new y.PointArray(this.attr("points")))},plot:function(t){return this.attr("points",this._array=new y.PointArray(t))},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var n=a(this.bbox(),t,e);return this.attr("points",this.array().size(n.width,n.height))}}),y.extend(y.Line,y.Polyline,y.Polygon,{morphArray:y.PointArray,x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},width:function(t){var e=this.bbox();return null==t?e.width:this.size(t,e.height)},height:function(t){var e=this.bbox();return null==t?e.height:this.size(e.width,t)}}),y.Path=y.invent({create:"path",inherit:y.Shape,extend:{morphArray:y.PathArray,array:function(){return this._array||(this._array=new y.PathArray(this.attr("d")))},plot:function(t){return this.attr("d",this._array=new y.PathArray(t))},move:function(t,e){return this.attr("d",this.array().move(t,e))},x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},size:function(t,e){var n=a(this.bbox(),t,e);return this.attr("d",this.array().size(n.width,n.height))},width:function(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)},height:function(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}},construct:{path:function(t){return this.put(new y.Path).plot(t)}}}),y.Image=y.invent({create:"image",inherit:y.Shape,extend:{load:function(t){if(!t)return this;var n=this,i=e.createElement("img");return i.onload=function(){var e=n.parent(y.Pattern);null!==e&&(0==n.width()&&0==n.height()&&n.size(i.width,i.height),e&&0==e.width()&&0==e.height()&&e.size(n.width(),n.height()),"function"==typeof n._loaded&&n._loaded.call(n,{width:i.width,height:i.height,ratio:i.width/i.height,url:t}))},this.attr("href",i.src=this.src=t,y.xlink)},loaded:function(t){return this._loaded=t,this}},construct:{image:function(t,e,n){return this.put(new y.Image).load(t).size(e||0,n||e||0)}}}),y.Text=y.invent({create:function(){this.constructor.call(this,y.create("text")),this.dom.leading=new y.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",y.defaults.attrs["font-family"])},inherit:y.Shape,extend:{clone:function(){var t=m(this.node.cloneNode(!0));return this.after(t),t},x:function(t){return null==t?this.attr("x"):(this.textPath||this.lines().each(function(){this.dom.newLined&&this.x(t)}),this.attr("x",t))},y:function(t){var e=this.attr("y"),n="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-n:e:this.attr("y","number"==typeof t?t+n:t)},cx:function(t){return null==t?this.bbox().cx:this.x(t-this.bbox().width/2)},cy:function(t){return null==t?this.bbox().cy:this.y(t-this.bbox().height/2)},text:function(t){if("undefined"==typeof t){for(var t="",e=this.node.childNodes,n=0,i=e.length;i>n;++n)0!=n&&3!=e[n].nodeType&&1==y.adopt(e[n]).dom.newLined&&(t+="\n"),t+=e[n].textContent;return t}if(this.clear().build(!0),"function"==typeof t)t.call(this,this);else{t=t.split("\n");for(var n=0,r=t.length;r>n;n++)this.tspan(t[n]).newLine()}return this.build(!1).rebuild()},size:function(t){return this.attr("font-size",t).rebuild()},leading:function(t){return null==t?this.dom.leading:(this.dom.leading=new y.Number(t),this.rebuild())},lines:function(){var t=y.utils.map(y.utils.filterSVGElements(this.node.childNodes),function(t){return y.adopt(t)});return new y.Set(t)},rebuild:function(t){if("boolean"==typeof t&&(this._rebuild=t),this._rebuild){var e=this,n=0,i=this.dom.leading*new y.Number(this.attr("font-size"));this.lines().each(function(){this.dom.newLined&&(this.textPath||this.attr("x",e.attr("x")),"\n"==this.text()?n+=i:(this.attr("dy",i+n),n=0))}),this.fire("rebuild")}return this},build:function(t){return this._build=!!t,this},setData:function(t){return this.dom=t,this.dom.leading=new y.Number(t.leading||1.3),this}},construct:{text:function(t){return this.put(new y.Text).text(t)},plain:function(t){return this.put(new y.Text).plain(t)}}}),y.Tspan=y.invent({create:"tspan",inherit:y.Shape,extend:{text:function(t){return null==t?this.node.textContent+(this.dom.newLined?"\n":""):("function"==typeof t?t.call(this,this):this.plain(t),this)},dx:function(t){return this.attr("dx",t)},dy:function(t){return this.attr("dy",t)},newLine:function(){var t=this.parent(y.Text);return this.dom.newLined=!0,this.dy(t.dom.leading*t.attr("font-size")).attr("x",t.x())}}}),y.extend(y.Text,y.Tspan,{plain:function(t){return this._build===!1&&this.clear(),this.node.appendChild(e.createTextNode(t)),this},tspan:function(t){var e=(this.textPath&&this.textPath()||this).node,n=new y.Tspan;return this._build===!1&&this.clear(),e.appendChild(n.node),n.text(t)},clear:function(){for(var t=(this.textPath&&this.textPath()||this).node;t.hasChildNodes();)t.removeChild(t.lastChild);return this},length:function(){return this.node.getComputedTextLength()}}),y.TextPath=y.invent({create:"textPath",inherit:y.Element,parent:y.Text,construct:{path:function(t){for(var e=new y.TextPath,n=this.doc().defs().path(t);this.node.hasChildNodes();)e.node.appendChild(this.node.firstChild);return this.node.appendChild(e.node),e.attr("href","#"+n,y.xlink),this},plot:function(t){var e=this.track();return e&&e.plot(t),this},track:function(){var t=this.textPath();return t?t.reference("href"):void 0},textPath:function(){return this.node.firstChild&&"textPath"==this.node.firstChild.nodeName?y.adopt(this.node.firstChild):void 0}}}),y.Nested=y.invent({create:function(){this.constructor.call(this,y.create("svg")),this.style("overflow","visible")},inherit:y.Container,construct:{nested:function(){return this.put(new y.Nested)}}}),y.A=y.invent({create:"a",inherit:y.Container,extend:{to:function(t){return this.attr("href",t,y.xlink)},show:function(t){return this.attr("show",t,y.xlink)},target:function(t){return this.attr("target",t)}},construct:{link:function(t){return this.put(new y.A).to(t)}}}),y.extend(y.Element,{linkTo:function(t){var e=new y.A;return"function"==typeof t?t.call(e,e):e.to(t),this.parent().put(e).put(this)}}),y.Marker=y.invent({create:"marker",inherit:y.Container,extend:{width:function(t){return this.attr("markerWidth",t)},height:function(t){return this.attr("markerHeight",t)},ref:function(t,e){return this.attr("refX",t).attr("refY",e)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return"url(#"+this.id()+")"}},construct:{marker:function(t,e,n){return this.defs().marker(t,e,n)}}}),y.extend(y.Defs,{marker:function(t,e,n){return this.put(new y.Marker).size(t,e).ref(t/2,e/2).viewbox(0,0,t,e).attr("orient","auto").update(n)}}),y.extend(y.Line,y.Polyline,y.Polygon,y.Path,{marker:function(t,e,n,i){var r=["marker"];return"all"!=t&&r.push(t),r=r.join("-"),t=arguments[1]instanceof y.Marker?arguments[1]:this.doc().marker(e,n,i),this.attr(r,t)}});var v={stroke:["color","width","opacity","linecap","linejoin","miterlimit","dasharray","dashoffset"],fill:["color","opacity","rule"],prefix:function(t,e){return"color"==e?t:t+"-"+e}};["fill","stroke"].forEach(function(t){var e,n={};n[t]=function(n){if("string"==typeof n||y.Color.isRgb(n)||n&&"function"==typeof n.fill)this.attr(t,n);else for(e=v[t].length-1;e>=0;e--)null!=n[v[t][e]]&&this.attr(v.prefix(t,v[t][e]),n[v[t][e]]);return this},y.extend(y.Element,y.FX,n)}),y.extend(y.Element,y.FX,{rotate:function(t,e,n){return this.transform({rotation:t,cx:e,cy:n})},skew:function(t,e,n,i){return this.transform({skewX:t,skewY:e,cx:n,cy:i})},scale:function(t,e,n,i){return 1==arguments.length||3==arguments.length?this.transform({scale:t,cx:e,cy:n}):this.transform({scaleX:t,scaleY:e,cx:n,cy:i})},translate:function(t,e){return this.transform({x:t,y:e})},flip:function(t,e){return this.transform({flip:t,offset:e})},matrix:function(t){return this.attr("transform",new y.Matrix(t))},opacity:function(t){return this.attr("opacity",t)},dx:function(t){return this.x((this.target||this).x()+t)},dy:function(t){return this.y((this.target||this).y()+t)},dmove:function(t,e){return this.dx(t).dy(e)}}),y.extend(y.Rect,y.Ellipse,y.Circle,y.Gradient,y.FX,{radius:function(t,e){var n=(this.target||this).type;return"radial"==n||"circle"==n?this.attr({r:new y.Number(t)}):this.rx(t).ry(null==e?t:e)}}),y.extend(y.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),y.extend(y.Parent,y.Text,y.FX,{font:function(t){for(var e in t)"leading"==e?this.leading(t[e]):"anchor"==e?this.attr("text-anchor",t[e]):"size"==e||"family"==e||"weight"==e||"stretch"==e||"variant"==e||"style"==e?this.attr("font-"+e,t[e]):this.attr(e,t[e]);return this}}),y.Set=y.invent({create:function(t){Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){var t,e,n=[].slice.call(arguments);for(t=0,e=n.length;e>t;t++)this.members.push(n[t]);return this},remove:function(t){var e=this.index(t);return e>-1&&this.members.splice(e,1),this},each:function(t){for(var e=0,n=this.members.length;n>e;e++)t.apply(this.members[e],[e,this.members]);return this},clear:function(){return this.members=[],this},length:function(){return this.members.length},has:function(t){return this.index(t)>=0},index:function(t){return this.members.indexOf(t)},get:function(t){return this.members[t]},first:function(){return this.get(0)},last:function(){return this.get(this.members.length-1)},valueOf:function(){return this.members},bbox:function(){var t=new y.BBox;if(0==this.members.length)return t;var e=this.members[0].rbox();return t.x=e.x,t.y=e.y,t.width=e.width,t.height=e.height,this.each(function(){t=t.merge(this.rbox())}),t}},construct:{set:function(t){return new y.Set(t)}}}),y.FX.Set=y.invent({create:function(t){this.set=t}}),y.Set.inherit=function(){var t,e=[];for(var t in y.Shape.prototype)"function"==typeof y.Shape.prototype[t]&&"function"!=typeof y.Set.prototype[t]&&e.push(t);e.forEach(function(t){y.Set.prototype[t]=function(){for(var e=0,n=this.members.length;n>e;e++)this.members[e]&&"function"==typeof this.members[e][t]&&this.members[e][t].apply(this.members[e],arguments);return"animate"==t?this.fx||(this.fx=new y.FX.Set(this)):this}}),e=[];for(var t in y.FX.prototype)"function"==typeof y.FX.prototype[t]&&"function"!=typeof y.FX.Set.prototype[t]&&e.push(t);e.forEach(function(t){y.FX.Set.prototype[t]=function(){for(var e=0,n=this.set.members.length;n>e;e++)this.set.members[e].fx[t].apply(this.set.members[e].fx,arguments);return this}})},y.extend(y.Element,{data:function(t,e,n){if("object"==typeof t)for(e in t)this.data(e,t[e]);else if(arguments.length<2)try{return JSON.parse(this.attr("data-"+t))}catch(i){return this.attr("data-"+t)}else this.attr("data-"+t,null===e?null:n===!0||"string"==typeof e||"number"==typeof e?e:JSON.stringify(e));return this}}),y.extend(y.Element,{remember:function(t,e){if("object"==typeof arguments[0])for(var e in t)this.remember(e,t[e]);else{if(1==arguments.length)return this.memory()[t];this.memory()[t]=e}return this},forget:function(){if(0==arguments.length)this._memory={};else for(var t=arguments.length-1;t>=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),y.get=function(t){var n=e.getElementById(g(t)||t);return y.adopt(n)},y.select=function(t,n){return new y.Set(y.utils.map((n||e).querySelectorAll(t),function(t){return y.adopt(t)}))},y.extend(y.Parent,{select:function(t){return y.select(t,this.node)}});var b="abcdef".split("");if("function"!=typeof w){var w=function(t,n){n=n||{bubbles:!1,cancelable:!1,detail:void 0};var i=e.createEvent("CustomEvent");return i.initCustomEvent(t,n.bubbles,n.cancelable,n.detail),i};w.prototype=t.Event.prototype,t.CustomEvent=w}return function(e){for(var n=0,i=["moz","webkit"],r=0;r<i.length&&!t.requestAnimationFrame;++r)e.requestAnimationFrame=e[i[r]+"RequestAnimationFrame"],e.cancelAnimationFrame=e[i[r]+"CancelAnimationFrame"]||e[i[r]+"CancelRequestAnimationFrame"];e.requestAnimationFrame=e.requestAnimationFrame||function(t){var i=(new Date).getTime(),r=Math.max(0,16-(i-n)),s=e.setTimeout(function(){t(i+r)},r);return n=i+r,s},e.cancelAnimationFrame=e.cancelAnimationFrame||e.clearTimeout}(t),y}); \ No newline at end of file
+/*! svg.js v2.2.5 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t,t.document)}):"object"==typeof exports?module.exports=t.document?e(t,t.document):function(t){return e(t,t.document)}:t.SVG=e(t,t.document)}("undefined"!=typeof window?window:this,function(t,e){function n(t,e){return t instanceof e}function i(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function r(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function s(t){return t.charAt(0).toUpperCase()+t.slice(1)}function a(t){return 4==t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}function h(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function o(t,e,n){return null==n?n=t.height/t.width*e:null==e&&(e=t.width/t.height*n),{width:e,height:n}}function u(t,e,n){return{x:e*t.a+n*t.c+0,y:e*t.b+n*t.d+0}}function c(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function l(t){return t instanceof v.Matrix||(t=new v.Matrix(t)),t}function f(t,e){t.cx=null==t.cx?e.bbox().cx:t.cx,t.cy=null==t.cy?e.bbox().cy:t.cy}function d(t){return t=t.replace(v.regex.whitespace,"").replace(v.regex.matrix,"").split(v.regex.matrixElements),c(v.utils.map(t,function(t){return parseFloat(t)}))}function p(t){for(var e=0,n=t.length,i="";n>e;e++)i+=t[e][0],null!=t[e][1]&&(i+=t[e][1],null!=t[e][2]&&(i+=" ",i+=t[e][2],null!=t[e][3]&&(i+=" ",i+=t[e][3],i+=" ",i+=t[e][4],null!=t[e][5]&&(i+=" ",i+=t[e][5],i+=" ",i+=t[e][6],null!=t[e][7]&&(i+=" ",i+=t[e][7])))));return i+" "}function m(t){for(var e=t.childNodes.length-1;e>=0;e--)t.childNodes[e]instanceof SVGElement&&m(t.childNodes[e]);return v.adopt(t).id(v.eid(t.nodeName))}function x(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function g(t){var e=t.toString().match(v.regex.reference);return e?e[1]:void 0}var v=this.SVG=function(t){return v.supported?(t=new v.Doc(t),v.parser||v.prepare(t),t):void 0};if(v.ns="http://www.w3.org/2000/svg",v.xmlns="http://www.w3.org/2000/xmlns/",v.xlink="http://www.w3.org/1999/xlink",v.svgjs="http://svgjs.com/svgjs",v.supported=function(){return!!e.createElementNS&&!!e.createElementNS(v.ns,"svg").createSVGRect}(),!v.supported)return!1;v.did=1e3,v.eid=function(t){return"Svgjs"+s(t)+v.did++},v.create=function(t){var n=e.createElementNS(this.ns,t);return n.setAttribute("id",this.eid(t)),n},v.extend=function(){var t,e,n,i;for(t=[].slice.call(arguments),e=t.pop(),i=t.length-1;i>=0;i--)if(t[i])for(n in e)t[i].prototype[n]=e[n];v.Set&&v.Set.inherit&&v.Set.inherit()},v.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,v.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&v.extend(e,t.extend),t.construct&&v.extend(t.parent||v.Container,t.construct),e},v.adopt=function(t){if(!t)return null;if(t.instance)return t.instance;var e;return e="svg"==t.nodeName?t.parentNode instanceof SVGElement?new v.Nested:new v.Doc:"linearGradient"==t.nodeName?new v.Gradient("linear"):"radialGradient"==t.nodeName?new v.Gradient("radial"):v[s(t.nodeName)]?new(v[s(t.nodeName)]):new v.Element(t),e.type=t.nodeName,e.node=t,t.instance=e,e instanceof v.Doc&&e.namespace().defs(),e.setData(JSON.parse(t.getAttribute("svgjs:data"))||{}),e},v.prepare=function(t){var n=e.getElementsByTagName("body")[0],i=(n?new v.Doc(n):t.nested()).size(2,0),r=v.create("path");i.node.appendChild(r),v.parser={body:n||t.parent(),draw:i.style("opacity:0;position:fixed;left:100%;top:100%;overflow:hidden"),poly:i.polyline().node,path:r}},v.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,matrix:/matrix\(|\)/g,matrixElements:/,*\s+|,/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,negExp:/e\-/gi,comma:/,/g,hyphen:/\-/g,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,whitespaces:/\s+/,X:/X/g},v.utils={map:function(t,e){var n,i=t.length,r=[];for(n=0;i>n;n++)r.push(e(t[n]));return r},radians:function(t){return t%360*Math.PI/180},degrees:function(t){return 180*t/Math.PI%360},filterSVGElements:function(t){return[].filter.call(t,function(t){return t instanceof SVGElement})}},v.defaults={attrs:{"fill-opacity":1,"stroke-opacity":1,"stroke-width":0,"stroke-linejoin":"miter","stroke-linecap":"butt",fill:"#000000",stroke:"#000000",opacity:1,x:0,y:0,cx:0,cy:0,width:0,height:0,r:0,rx:0,ry:0,offset:0,"stop-opacity":1,"stop-color":"#000000","font-size":16,"font-family":"Helvetica, Arial, sans-serif","text-anchor":"start"}},v.Color=function(t){var e;this.r=0,this.g=0,this.b=0,"string"==typeof t?v.regex.isRgb.test(t)?(e=v.regex.rgb.exec(t.replace(/\s/g,"")),this.r=parseInt(e[1]),this.g=parseInt(e[2]),this.b=parseInt(e[3])):v.regex.isHex.test(t)&&(e=v.regex.hex.exec(a(t)),this.r=parseInt(e[1],16),this.g=parseInt(e[2],16),this.b=parseInt(e[3],16)):"object"==typeof t&&(this.r=t.r,this.g=t.g,this.b=t.b)},v.extend(v.Color,{toString:function(){return this.toHex()},toHex:function(){return"#"+h(this.r)+h(this.g)+h(this.b)},toRgb:function(){return"rgb("+[this.r,this.g,this.b].join()+")"},brightness:function(){return this.r/255*.3+this.g/255*.59+this.b/255*.11},morph:function(t){return this.destination=new v.Color(t),this},at:function(t){return this.destination?(t=0>t?0:t>1?1:t,new v.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),v.Color.test=function(t){return t+="",v.regex.isHex.test(t)||v.regex.isRgb.test(t)},v.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},v.Color.isColor=function(t){return v.Color.isRgb(t)||v.Color.test(t)},v.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},v.extend(v.Array,{morph:function(t){if(this.destination=this.parse(t),this.value.length!=this.destination.length){for(var e=this.value[this.value.length-1],n=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(n);for(;this.value.length<this.destination.length;)this.value.push(e)}return this},settle:function(){for(var t=0,e=this.value.length,n=[];e>t;t++)-1==n.indexOf(this.value[t])&&n.push(this.value[t]);return this.value=n},at:function(t){if(!this.destination)return this;for(var e=0,n=this.value.length,i=[];n>e;e++)i.push(this.value[e]+(this.destination[e]-this.value[e])*t);return new v.Array(i)},toString:function(){return this.value.join(" ")},valueOf:function(){return this.value},parse:function(t){return t=t.valueOf(),Array.isArray(t)?t:this.split(t)},split:function(t){return t.trim().split(/\s+/)},reverse:function(){return this.value.reverse(),this}}),v.PointArray=function(t,e){this.constructor.call(this,t,e||[[0,0]])},v.PointArray.prototype=new v.Array,v.extend(v.PointArray,{toString:function(){for(var t=0,e=this.value.length,n=[];e>t;t++)n.push(this.value[t].join(","));return n.join(" ")},toLine:function(){return{x1:this.value[0][0],y1:this.value[0][1],x2:this.value[1][0],y2:this.value[1][1]}},at:function(t){if(!this.destination)return this;for(var e=0,n=this.value.length,i=[];n>e;e++)i.push([this.value[e][0]+(this.destination[e][0]-this.value[e][0])*t,this.value[e][1]+(this.destination[e][1]-this.value[e][1])*t]);return new v.PointArray(i)},parse:function(t){if(t=t.valueOf(),Array.isArray(t))return t;t=this.split(t);for(var e,n=0,i=t.length,r=[];i>n;n++)e=t[n].split(","),r.push([parseFloat(e[0]),parseFloat(e[1])]);return r},move:function(t,e){var n=this.bbox();if(t-=n.x,e-=n.y,!isNaN(t)&&!isNaN(e))for(var i=this.value.length-1;i>=0;i--)this.value[i]=[this.value[i][0]+t,this.value[i][1]+e];return this},size:function(t,e){var n,i=this.bbox();for(n=this.value.length-1;n>=0;n--)this.value[n][0]=(this.value[n][0]-i.x)*t/i.width+i.x,this.value[n][1]=(this.value[n][1]-i.y)*e/i.height+i.y;return this},bbox:function(){return v.parser.poly.setAttribute("points",this.toString()),v.parser.poly.getBBox()}}),v.PathArray=function(t,e){this.constructor.call(this,t,e||[["M",0,0]])},v.PathArray.prototype=new v.Array,v.extend(v.PathArray,{toString:function(){return p(this.value)},move:function(t,e){var n=this.bbox();if(t-=n.x,e-=n.y,!isNaN(t)&&!isNaN(e))for(var i,r=this.value.length-1;r>=0;r--)i=this.value[r][0],"M"==i||"L"==i||"T"==i?(this.value[r][1]+=t,this.value[r][2]+=e):"H"==i?this.value[r][1]+=t:"V"==i?this.value[r][1]+=e:"C"==i||"S"==i||"Q"==i?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"==i&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"==i&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var n,i,r=this.bbox();for(n=this.value.length-1;n>=0;n--)i=this.value[n][0],"M"==i||"L"==i||"T"==i?(this.value[n][1]=(this.value[n][1]-r.x)*t/r.width+r.x,this.value[n][2]=(this.value[n][2]-r.y)*e/r.height+r.y):"H"==i?this.value[n][1]=(this.value[n][1]-r.x)*t/r.width+r.x:"V"==i?this.value[n][1]=(this.value[n][1]-r.y)*e/r.height+r.y:"C"==i||"S"==i||"Q"==i?(this.value[n][1]=(this.value[n][1]-r.x)*t/r.width+r.x,this.value[n][2]=(this.value[n][2]-r.y)*e/r.height+r.y,this.value[n][3]=(this.value[n][3]-r.x)*t/r.width+r.x,this.value[n][4]=(this.value[n][4]-r.y)*e/r.height+r.y,"C"==i&&(this.value[n][5]=(this.value[n][5]-r.x)*t/r.width+r.x,this.value[n][6]=(this.value[n][6]-r.y)*e/r.height+r.y)):"A"==i&&(this.value[n][1]=this.value[n][1]*t/r.width,this.value[n][2]=this.value[n][2]*e/r.height,this.value[n][6]=(this.value[n][6]-r.x)*t/r.width+r.x,this.value[n][7]=(this.value[n][7]-r.y)*e/r.height+r.y);return this},parse:function(t){if(t instanceof v.PathArray)return t.valueOf();var e,n,i,r,s,a,h=0,o=0,u={M:2,L:2,H:1,V:1,C:6,S:4,Q:4,T:2,A:7};if("string"==typeof t){for(t=t.replace(v.regex.negExp,"X").replace(v.regex.pathLetters," $& ").replace(v.regex.hyphen," -").replace(v.regex.comma," ").replace(v.regex.X,"e-").trim().split(v.regex.whitespaces),e=t.length;--e;)if(t[e].indexOf(".")!=t[e].lastIndexOf(".")){var c=t[e].split("."),l=[c.shift(),c.shift()].join(".");t.splice.apply(t,[e,1].concat(l,c.map(function(t){return"."+t})))}}else t=t.reduce(function(t,e){return[].concat.apply(t,e)},[]);var a=[];do{for(v.regex.isPathLetter.test(t[0])?(r=t[0],t.shift()):"M"==r?r="L":"m"==r&&(r="l"),s=[r.toUpperCase()],e=0;e<u[s[0]];++e)s.push(parseFloat(t.shift()));r==s[0]?"M"==r||"L"==r||"C"==r||"Q"==r?(h=s[u[s[0]]-1],o=s[u[s[0]]]):"V"==r?o=s[1]:"H"==r?h=s[1]:"A"==r&&(h=s[6],o=s[7]):"m"==r||"l"==r||"c"==r||"s"==r||"q"==r||"t"==r?(s[1]+=h,s[2]+=o,null!=s[3]&&(s[3]+=h,s[4]+=o),null!=s[5]&&(s[5]+=h,s[6]+=o),h=s[u[s[0]]-1],o=s[u[s[0]]]):"v"==r?(s[1]+=o,o=s[1]):"h"==r?(s[1]+=h,h=s[1]):"a"==r&&(s[6]+=h,s[7]+=o,h=s[6],o=s[7]),"M"==s[0]&&(n=h,i=o),"Z"==s[0]&&(h=n,o=i),a.push(s)}while(t.length);return a},bbox:function(){return v.parser.path.setAttribute("d",this.toString()),v.parser.path.getBBox()}}),v.Number=v.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:0>t?-3.4e38:3.4e38:"string"==typeof t?(e=t.match(v.regex.numberAndUnit),e&&(this.value=parseFloat(e[1]),"%"==e[5]?this.value/=100:"s"==e[5]&&(this.value*=1e3),this.unit=e[5])):t instanceof v.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"==this.unit?~~(1e8*this.value)/1e6:"s"==this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},valueOf:function(){return this.value},plus:function(t){return new v.Number(this+new v.Number(t),this.unit)},minus:function(t){return this.plus(-new v.Number(t))},times:function(t){return new v.Number(this*new v.Number(t),this.unit)},divide:function(t){return new v.Number(this/new v.Number(t),this.unit)},to:function(t){var e=new v.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new v.Number(t),this},at:function(t){return this.destination?new v.Number(this.destination).minus(this).times(t).plus(this):this}}}),v.ViewBox=v.invent({create:function(t){var e,n,i,r,s,a,h,o,u=[1,0,0,1],c=1,l=1,f=/-?[\d\.]+/g;if(t instanceof v.Element){for(h=t,o=t,a=(t.attr("viewBox")||"").match(f),s=t.bbox,i=new v.Number(t.width()),r=new v.Number(t.height());"%"==i.unit;)c*=i.value,i=new v.Number(h instanceof v.Doc?h.parent().offsetWidth:h.parent().width()),h=h.parent();for(;"%"==r.unit;)l*=r.value,r=new v.Number(o instanceof v.Doc?o.parent().offsetHeight:o.parent().height()),o=o.parent();this.x=0,this.y=0,this.width=i*c,this.height=r*l,this.zoom=1,a&&(e=parseFloat(a[0]),n=parseFloat(a[1]),i=parseFloat(a[2]),r=parseFloat(a[3]),this.zoom=this.width/this.height>i/r?this.height/r:this.width/i,this.x=e,this.y=n,this.width=i,this.height=r)}else t="string"==typeof t?t.match(f).map(function(t){return parseFloat(t)}):Array.isArray(t)?t:"object"==typeof t?[t.x,t.y,t.width,t.height]:4==arguments.length?[].slice.call(arguments):u,this.x=t[0],this.y=t[1],this.width=t[2],this.height=t[3]},extend:{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height},morph:function(t){var t=1==arguments.length?[t.x,t.y,t.width,t.height]:[].slice.call(arguments);return this.destination=new v.ViewBox(t),this},at:function(t){return this.destination?new v.ViewBox([this.x+(this.destination.x-this.x)*t,this.y+(this.destination.y-this.y)*t,this.width+(this.destination.width-this.width)*t,this.height+(this.destination.height-this.height)*t]):this}}}),v.Element=v.invent({create:function(t){this._stroke=v.defaults.attrs.stroke,this.dom={},(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke)},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var n=o(this.bbox(),t,e);return this.width(new v.Number(n.width)).height(new v.Number(n.height))},clone:function(){var t=m(this.node.cloneNode(!0));return this.after(t),t},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return t.put(this)},putIn:function(t){return t.add(this)},id:function(t){return this.attr("id",t)},inside:function(t,e){var n=this.bbox();return t>n.x&&e>n.y&&t<n.x+n.width&&e<n.y+n.height},show:function(){return this.style("display","")},hide:function(){return this.style("display","none")},visible:function(){return"none"!=this.style("display")},toString:function(){return this.attr("id")},classes:function(){var t=this.attr("class");return null==t?[]:t.trim().split(/\s+/)},hasClass:function(t){return-1!=this.classes().indexOf(t)},addClass:function(t){if(!this.hasClass(t)){var e=this.classes();e.push(t),this.attr("class",e.join(" "))}return this},removeClass:function(t){return this.hasClass(t)&&this.attr("class",this.classes().filter(function(e){return e!=t}).join(" ")),this},toggleClass:function(t){return this.hasClass(t)?this.removeClass(t):this.addClass(t)},reference:function(t){return v.get(this.attr(t))},parent:function(t){var e=this;if(!e.node.parentNode)return null;if(e=v.adopt(e.node.parentNode),!t)return e;for(;e&&e.node instanceof SVGElement;){if("string"==typeof t?e.matches(t):e instanceof t)return e;e=v.adopt(e.node.parentNode)}},doc:function(){return this instanceof v.Doc?this:this.parent(v.Doc)},parents:function(t){var e=[],n=this;do{if(n=n.parent(t),!n||!n.node)break;e.push(n)}while(n.parent);return e},matches:function(t){return i(this.node,t)},"native":function(){return this.node},svg:function(t){var n=e.createElement("svg");if(!(t&&this instanceof v.Parent))return n.appendChild(t=e.createElement("svg")),this.writeDataToDom(),t.appendChild(this.node.cloneNode(!0)),n.innerHTML.replace(/^<svg>/,"").replace(/<\/svg>$/,"");n.innerHTML="<svg>"+t.replace(/\n/,"").replace(/<(\w+)([^<]+?)\/>/g,"<$1$2></$1>")+"</svg>";for(var i=0,r=n.firstChild.childNodes.length;r>i;i++)this.node.appendChild(n.firstChild.firstChild);return this},writeDataToDom:function(){if(this.each||this.lines){var t=this.each?this:this.lines();t.each(function(){this.writeDataToDom()})}return this.node.removeAttribute("svgjs:data"),Object.keys(this.dom).length&&this.node.setAttribute("svgjs:data",JSON.stringify(this.dom)),this},setData:function(t){return this.dom=t,this},is:function(t){return n(this,t)}}}),v.easing={"-":function(t){return t},"<>":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return-Math.cos(t*Math.PI/2)+1}};v.Situation=v.invent({create:function(t){this.init=!1,this.reversed=!1,this.duration=t.duration,this.delay=t.delay,this.start=+new Date+this.delay,this.end=this.start+this.duration,this.easing=t.easing,this.animations={},this.attrs={},this.styles={},this.transforms=[],this.once={}}}),v.Delay=function(t){this.delay=t},v.FX=v.invent({create:function(t){this._target=t,this.situations=[],this.active=!1,this.current=null,this.paused=!1,this.lastPos=0,this.pos=0},extend:{animate:function(t,e,n){"object"==typeof t&&(e=t.ease,n=t.delay,t=t.duration);var i=new v.Situation({duration:t||1e3,delay:n||0,easing:v.easing[e||"-"]||e});return this.queue(i),this},delay:function(t){var t=new v.Delay(t);return this.queue(t)},target:function(t){return t&&t instanceof v.Element?(this._target=t,this):this._target},timeToPos:function(t){return(t-this.current.start)/this.current.duration},posToTime:function(t){return this.current.duration*t+this.current.start},startAnimFrame:function(){this.stopAnimFrame(),this.animationFrame=requestAnimationFrame(function(){this.step()}.bind(this))},stopAnimFrame:function(){cancelAnimationFrame(this.animationFrame)},start:function(){return!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=!0,this.startAnimFrame()),this},queue:function(t){return("function"==typeof t||t instanceof v.Situation||t instanceof v.Delay)&&this.situations.push(t),this.current||(this.current=this.situations.shift()),this},dequeue:function(){if(this.current&&this.current.stop&&this.current.stop(),this.current=this.situations.shift(),this.current){var t=function(){this.current instanceof v.Situation?this.initAnimations().seek(0):this.current instanceof v.Delay?this.dequeue():this.current.call(this)}.bind(this);this.current.delay?setTimeout(function(){t()},this.current.delay):t()}return this},initAnimations:function(){var t,e=this.current;if(e.init)return this;for(t in e.animations)"viewbox"==t?e.animations[t]=this.target().viewbox().morph(e.animations[t]):(e.animations[t].value="plot"==t?this.target().array().value:this.target()[t](),e.animations[t].value.value&&(e.animations[t].value=e.animations[t].value.value),e.animations[t].relative&&(e.animations[t].destination.value=e.animations[t].destination.value+e.animations[t].value));for(t in e.attrs)if(e.attrs[t]instanceof v.Color){var n=new v.Color(this.target().attr(t));e.attrs[t].r=n.r,e.attrs[t].g=n.g,e.attrs[t].b=n.b}else e.attrs[t].value=this.target().attr(t);for(t in e.styles)e.styles[t].value=this.target().style(t);return e.transformations=this.target().matrixify(),e.init=!0,this},clearQueue:function(){return this.situations=[],this},clearCurrent:function(){return this.current=null,this},stop:function(t,e){return this.active||this.start(),e&&this.clearQueue(),this.active=!1,t&&this.seek(1),this.stopAnimFrame(),clearTimeout(this.timeout),this.clearCurrent()},reset:function(){if(this.current){var t=this.current;this.stop(),this.current=t,this.seek(0)}return this},finish:function(){for(this.stop(!0,!1);this.dequeue().current&&this.stop(!0,!1););return this.clearQueue().clearCurrent()},seek:function(t){return this.pos=t,this.current.start=+new Date-t*this.current.duration,this.current.end=this.current.start+this.current.duration,this.step(!0)},speed:function(t){return this.current.duration=this.current.duration*this.pos+(1-this.pos)*this.current.duration/t,this.current.end=this.current.start+this.current.duration,this.seek(this.pos)},loop:function(t,e){return this.current.loop=t||!0,e?this.reverse():this},pause:function(){return this.paused=!0,this.stopAnimFrame(),clearTimeout(this.timeout),this},play:function(){return this.paused?(this.paused=!1,this.seek(this.pos)):this},reverse:function(t){var e=this.last();return e.reversed="undefined"==typeof t?!e.reversed:t,this},progress:function(t){return t?this.current.easing(this.pos):this.pos},after:function(t){var e=this.last(),n=function i(n){n.detail.situation==e&&(t.call(this,e),this.off("finished.fx",i))};return this.target().on("finished.fx",n),this},during:function(t){var e=this.last(),n=function(n){n.detail.situation==e&&t.call(this,n.detail.pos,n.detail.eased,e)};return this.target().off("during.fx",n).on("during.fx",n),this.after(function(){this.off("during.fx",n)})},afterAll:function(t){var e=function n(){t.call(this),this.off("allfinished.fx",n)};return this.target().off("allfinished.fx",e).on("allfinished.fx",e),this},duringAll:function(t){var e=function(e){t.call(this,e.detail.pos,e.detail.eased,e.detail.fx,e.detail.situation)};return this.target().off("during.fx",e).on("during.fx",e),this.afterAll(function(){this.off("during.fx",e)})},totalProgress:function(){for(var t=this.current.start,e=this.current,n=0,i=this.situations.length;i>n;++n)e+=(situations[n].duration||0)+(situations[n].delay||0);return(this.pos*this.current.duration+this.start-t)/(e-t)},last:function(){return this.situations.length?this.situations[this.situations.length-1]:this.current},add:function(t,e,n){return this.last()[n||"animations"][t]=e,setTimeout(function(){this.start()}.bind(this),0),this},step:function(t){if(t||(this.pos=this.timeToPos(+new Date)),this.pos>=1&&(this.current.loop===!0||"number"==typeof this.current.loop&&--this.current.loop))return this.seek(this.pos-1);this.current.reversed&&(this.pos=1-this.pos),this.pos>1&&(this.pos=1),this.pos<0&&(this.pos=0);var e=this.current.easing(this.pos);for(var n in this.current.once)n>this.lastPos&&e>=n&&(this.current.once[n].call(this.target(),this.pos,e),delete this.current.once[n]);return this.target().fire("during",{pos:this.pos,eased:e,fx:this,situation:this.current}),this.eachAt(),1==this.pos&&!this.current.reversed||this.current.reversed&&0==this.pos?(cancelAnimationFrame(this.animationFrame),this.target().fire("finished",{fx:this,situation:this.current}),this.situations.length||this.current||!this.active||(this.target().fire("allfinished"),this.target().off(".fx"),this.active=!1),this.active?this.dequeue():this.clearCurrent()):!this.paused&&this.active&&this.startAnimFrame(),this.lastPos=e,this},eachAt:function(){var t,e,n=this,i=this.target(),r=this.current;for(t in r.animations)e=[].concat(r.animations[t]).map(function(t){return t.at?t.at(r.easing(n.pos),n.pos):t}),i[t].apply(i,e);for(t in r.attrs)e=[t].concat(r.attrs[t]).map(function(t){return t.at?t.at(r.easing(n.pos),n.pos):t}),i.attr.apply(i,e);for(t in r.styles)e=[t].concat(r.styles[t]).map(function(t){return t.at?t.at(r.easing(n.pos),n.pos):t}),i.style.apply(i,e);if(r.transforms.length){e=r.transformations;for(t in r.transforms){var s=r.transforms[t];s instanceof v.Matrix?e=s.relative?e.multiply(s.at(this.pos)):e.morph(s).at(r.easing(this.pos)):(s.relative||s.undo(e.extract()),e=e.multiply(s.at(r.easing(this.pos))))}i.matrix(e)}return this},once:function(t,e,n){return n||(t=this.current.easing(t)),this.current.once[t]=e,this}},parent:v.Element,construct:{animate:function(t,e,n){return(this.fx||(this.fx=new v.FX(this))).animate(t,e,n)},delay:function(t){return(this.fx||(this.fx=new v.FX(this))).delay(t)},stop:function(t,e){return this.fx&&this.fx.stop(t,e),this},pause:function(){return this.fx&&this.fx.pause(),this},play:function(){return this.fx&&this.fx.play(),this}}}),v.MorphObj=v.invent({create:function(t){return v.Color.isColor(t)?(new v.Color).morph(t):v.regex.numberAndUnit.test(t)?(new v.Number).morph(t):(this.value=0,this.destination=t,void 0)},extend:{at:function(t,e){return 1>e?this.value:this.destination},valueOf:function(){return this.value}}}),v.extend(v.FX,{attr:function(t,e){if("object"==typeof t)for(var n in t)this.attr(n,t[n]);else this.add(t,new v.MorphObj(e),"attrs");return this},style:function(t,e){if("object"==typeof t)for(var n in t)this.style(n,t[n]);else this.add(t,new v.MorphObj(e),"styles");return this},x:function(t,e){if(this.target()instanceof v.G)return this.transform({x:t},e),this;var n=(new v.Number).morph(t);return n.relative=e,this.add("x",n)},y:function(t,e){if(this.target()instanceof v.G)return this.transform({y:t},e),this;var n=(new v.Number).morph(t);return n.relative=e,this.add("y",n)},cx:function(t){return this.add("cx",(new v.Number).morph(t))},cy:function(t){return this.add("cy",(new v.Number).morph(t))},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},size:function(t,e){if(this.target()instanceof v.Text)this.attr("font-size",t);else{var n;t&&e||(n=this.target().bbox()),t||(t=n.width/n.height*e),e||(e=n.height/n.width*t),this.add("width",(new v.Number).morph(t)).add("height",(new v.Number).morph(e))}return this},plot:function(t){return this.add("plot",this.target().array().morph(t))},leading:function(t){return this.target().leading?this.add("leading",(new v.Number).morph(t)):this},viewbox:function(t,e,n,i){return this.target()instanceof v.Container&&this.add("viewbox",new v.ViewBox(t,e,n,i)),this},update:function(t){if(this.target()instanceof v.Stop){if("number"==typeof t||t instanceof v.Number)return this.update({offset:arguments[0],color:arguments[1],opacity:arguments[2]});null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",t.offset)}return this}}),v.BBox=v.invent({create:function(t){if(t){var e;try{e=t.node.getBBox()}catch(n){if(t instanceof v.Shape){var i=t.clone().addTo(v.parser.draw);e=i.bbox(),i.remove()}else e={x:t.node.clientLeft,y:t.node.clientTop,width:t.node.clientWidth,height:t.node.clientHeight}}this.x=e.x,this.y=e.y,this.width=e.width,this.height=e.height}x(this)},parent:v.Element,construct:{bbox:function(){return new v.BBox(this)}}}),v.TBox=v.invent({create:function(t){if(t){var e=t.ctm().extract(),n=t.bbox();this.width=n.width*e.scaleX,this.height=n.height*e.scaleY,this.x=n.x+e.x,this.y=n.y+e.y}x(this)},parent:v.Element,construct:{tbox:function(){return new v.TBox(this)}}}),v.RBox=v.invent({create:function(e){if(e){var n=e.doc().parent(),i=e.node.getBoundingClientRect(),r=1;for(this.x=i.left,this.y=i.top,this.x-=n.offsetLeft,this.y-=n.offsetTop;n=n.offsetParent;)this.x-=n.offsetLeft,this.y-=n.offsetTop;for(n=e;n.parent&&(n=n.parent());)n.viewbox&&(r*=n.viewbox().zoom,this.x-=n.x()||0,this.y-=n.y()||0);this.width=i.width/=r,this.height=i.height/=r}x(this),this.x+=t.pageXOffset,this.y+=t.pageYOffset},parent:v.Element,construct:{rbox:function(){return new v.RBox(this)}}}),[v.BBox,v.TBox,v.RBox].forEach(function(t){v.extend(t,{merge:function(e){var n=new t;return n.x=Math.min(this.x,e.x),n.y=Math.min(this.y,e.y),n.width=Math.max(this.x+this.width,e.x+e.width)-n.x,n.height=Math.max(this.y+this.height,e.y+e.height)-n.y,x(n)}})}),v.Matrix=v.invent({create:function(t){var e,n=c([1,0,0,1,0,0]);for(t=t instanceof v.Element?t.matrixify():"string"==typeof t?d(t):6==arguments.length?c([].slice.call(arguments)):"object"==typeof t?t:n,e=w.length-1;e>=0;--e)this[w[e]]=t&&"number"==typeof t[w[e]]?t[w[e]]:n[w[e]]},extend:{extract:function(){var t=u(this,0,1),e=u(this,1,0),n=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,transformedX:(this.e*Math.cos(n*Math.PI/180)+this.f*Math.sin(n*Math.PI/180))/Math.sqrt(this.a*this.a+this.b*this.b),transformedY:(this.f*Math.cos(n*Math.PI/180)+this.e*Math.sin(-n*Math.PI/180))/Math.sqrt(this.c*this.c+this.d*this.d),skewX:-n,skewY:180/Math.PI*Math.atan2(e.y,e.x),scaleX:Math.sqrt(this.a*this.a+this.b*this.b),scaleY:Math.sqrt(this.c*this.c+this.d*this.d),rotation:n,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f,matrix:new v.Matrix(this)}},clone:function(){return new v.Matrix(this)},morph:function(t){return this.destination=new v.Matrix(t),this},at:function(t){if(!this.destination)return this;var e=new v.Matrix({a:this.a+(this.destination.a-this.a)*t,b:this.b+(this.destination.b-this.b)*t,c:this.c+(this.destination.c-this.c)*t,d:this.d+(this.destination.d-this.d)*t,e:this.e+(this.destination.e-this.e)*t,f:this.f+(this.destination.f-this.f)*t});if(this.param&&this.param.to){var n={rotation:this.param.from.rotation+(this.param.to.rotation-this.param.from.rotation)*t,cx:this.param.from.cx,cy:this.param.from.cy};e=e.rotate((this.param.to.rotation-2*this.param.from.rotation)*t,n.cx,n.cy),e.param=n}return e},multiply:function(t){return new v.Matrix(this.native().multiply(l(t).native()))},inverse:function(){return new v.Matrix(this.native().inverse())},translate:function(t,e){return new v.Matrix(this.native().translate(t||0,e||0))},scale:function(t,e,n,i){return(1==arguments.length||3==arguments.length)&&(e=t),3==arguments.length&&(i=n,n=e),this.around(n,i,new v.Matrix(t,0,0,e,0,0))},rotate:function(t,e,n){return t=v.utils.radians(t),this.around(e,n,new v.Matrix(Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0))},flip:function(t,e){return"x"==t?this.scale(-1,1,e,0):this.scale(1,-1,0,e)},skew:function(t,e,n,i){return this.around(n,i,this.native().skewX(t||0).skewY(e||0))},skewX:function(t,e,n){return this.around(e,n,this.native().skewX(t||0))},skewY:function(t,e,n){return this.around(e,n,this.native().skewY(t||0))},around:function(t,e,n){return this.multiply(new v.Matrix(1,0,0,1,t||0,e||0)).multiply(n).multiply(new v.Matrix(1,0,0,1,-t||0,-e||0))},"native":function(){for(var t=v.parser.draw.node.createSVGMatrix(),e=w.length-1;e>=0;e--)t[w[e]]=this[w[e]];return t},toString:function(){return"matrix("+this.a+","+this.b+","+this.c+","+this.d+","+this.e+","+this.f+")"}},parent:v.Element,construct:{ctm:function(){return new v.Matrix(this.node.getCTM())},screenCTM:function(){return new v.Matrix(this.node.getScreenCTM())}}}),v.Point=v.invent({create:function(t,e){var n,i={x:0,y:0};n=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:null!=e?{x:t,y:e}:i,this.x=n.x,this.y=n.y},extend:{clone:function(){return new v.Point(this)},morph:function(t){return this.destination=new v.Point(t),this},at:function(t){if(!this.destination)return this;var e=new v.Point({x:this.x+(this.destination.x-this.x)*t,y:this.y+(this.destination.y-this.y)*t});return e},"native":function(){var t=v.parser.draw.node.createSVGPoint();return t.x=this.x,t.y=this.y,t},transform:function(t){return new v.Point(this.native().matrixTransform(t.native()))}}}),v.extend(v.Element,{point:function(t,e){return new v.Point(t,e).transform(this.screenCTM().inverse())}}),v.extend(v.Element,{attr:function(t,e,n){if(null==t){for(t={},e=this.node.attributes,n=e.length-1;n>=0;n--)t[e[n].nodeName]=v.regex.isNumber.test(e[n].nodeValue)?parseFloat(e[n].nodeValue):e[n].nodeValue;return t}if("object"==typeof t)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return e=this.node.getAttribute(t),null==e?v.defaults.attrs[t]:v.regex.isNumber.test(e)?parseFloat(e):e;"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),("fill"==t||"stroke"==t)&&(v.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof v.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)
+}))),"number"==typeof e?e=new v.Number(e):v.Color.isColor(e)?e=new v.Color(e):Array.isArray(e)?e=new v.Array(e):e instanceof v.Matrix&&e.param&&(this.param=e.param),"leading"==t?this.leading&&this.leading(e):"string"==typeof n?this.node.setAttributeNS(n,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this}}),v.extend(v.Element,{transform:function(t,e){var n,i=this;if("object"!=typeof t)return n=new v.Matrix(i).extract(),"string"==typeof t?n[t]:n;if(n=new v.Matrix(i),e=!!e||!!t.relative,null!=t.a)n=e?n.multiply(new v.Matrix(t)):new v.Matrix(t);else if(null!=t.rotation)f(t,i),n=e?n.rotate(t.rotation,t.cx,t.cy):n.rotate(t.rotation-n.extract().rotation,t.cx,t.cy);else if(null!=t.scale||null!=t.scaleX||null!=t.scaleY){if(f(t,i),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,!e){var r=n.extract();t.scaleX=1*t.scaleX/r.scaleX,t.scaleY=1*t.scaleY/r.scaleY}n=n.scale(t.scaleX,t.scaleY,t.cx,t.cy)}else if(null!=t.skewX||null!=t.skewY){if(f(t,i),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,!e){var r=n.extract();n=n.multiply((new v.Matrix).skew(r.skewX,r.skewY,t.cx,t.cy).inverse())}n=n.skew(t.skewX,t.skewY,t.cx,t.cy)}else t.flip?n=n.flip(t.flip,null==t.offset?i.bbox()["c"+t.flip]:t.offset):(null!=t.x||null!=t.y)&&(e?n=n.translate(t.x,t.y):(null!=t.x&&(n.e=t.x),null!=t.y&&(n.f=t.y)));return this.attr("transform",n)}}),v.extend(v.FX,{transform:function(t,e){var n,i=this.target();if("object"!=typeof t)return n=new v.Matrix(i).extract(),"string"==typeof t?n[t]:n;if(e=!!e||!!t.relative,null!=t.a?n=new v.Matrix(t):null!=t.rotation?(f(t,i),n=new v.Rotate(t.rotation,t.cx,t.cy)):null!=t.scale||null!=t.scaleX||null!=t.scaleY?(f(t,i),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,n=new v.Scale(t.scaleX,t.scaleY,t.cx,t.cy)):null!=t.skewX||null!=t.skewY?(f(t,i),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,n=new v.Skew(t.skewX,t.skewY,t.cx,t.cy)):t.flip?n=(new v.Matrix).morph((new v.Matrix).flip(t.flip,null==t.offset?i.bbox()["c"+t.flip]:t.offset)):(null!=t.x||null!=t.y)&&(n=new v.Translate(t.x,t.y)),!n)return this;n.relative=e;var r=this.situations.length?this.situations[this.situations.length-1]:this.current;return r.transforms.push(n),setTimeout(function(){this.start()}.bind(this),0),this}}),v.extend(v.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){var t=(this.attr("transform")||"").split(/\)\s*/).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(v.regex.matrixElements).map(function(t){return parseFloat(t)})]}).reduce(function(t,e){return"matrix"==e[0]?t.multiply(c(e[1])):t[e[0]].apply(t,e[1])},new v.Matrix);return t},toParent:function(t){if(this==t)return this;var e=this.screenCTM(),n=t.rect(1,1),i=n.screenCTM().inverse();return n.remove(),this.addTo(t).untransform().transform(i.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),v.Transformation=v.invent({create:function(t,e){if(arguments.length>1&&"boolean"!=typeof e)return this.create([].slice.call(arguments));if("object"==typeof t)for(var n=0,i=this.arguments.length;i>n;++n)this[this.arguments[n]]=t[this.arguments[n]];if(Array.isArray(t))for(var n=0,i=this.arguments.length;i>n;++n)this[this.arguments[n]]=t[n];this.inversed=!1,e===!0&&(this.inversed=!0)},extend:{at:function(t){for(var e=[],n=0,i=this.arguments.length;i>n;++n)e.push(this[this.arguments[n]]);var r=this._undo||new v.Matrix;return r=(new v.Matrix).morph(v.Matrix.prototype[this.method].apply(r,e)).at(t),this.inversed?r.inverse():r},undo:function(t){return this._undo=new(v[s(this.method)])(t,!0).at(1),this}}}),v.Translate=v.invent({parent:v.Matrix,inherit:v.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["transformedX","transformedY"],method:"translate"}}),v.Rotate=v.invent({parent:v.Matrix,inherit:v.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["rotation","cx","cy"],method:"rotate",at:function(t){var e=(new v.Matrix).rotate((new v.Number).morph(this.rotation-(this._undo?this._undo.rotation:0)).at(t),this.cx,this.cy);return this.inversed?e.inverse():e},undo:function(t){this._undo=t}}}),v.Scale=v.invent({parent:v.Matrix,inherit:v.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["scaleX","scaleY","cx","cy"],method:"scale"}}),v.Skew=v.invent({parent:v.Matrix,inherit:v.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["skewX","skewY","cx","cy"],method:"skew"}}),v.extend(v.Element,{style:function(t,e){if(0==arguments.length)return this.node.style.cssText||"";if(arguments.length<2)if("object"==typeof t)for(e in t)this.style(e,t[e]);else{if(!v.regex.isCss.test(t))return this.node.style[r(t)];t=t.split(";");for(var n=0;n<t.length;n++)e=t[n].split(":"),this.style(e[0].replace(/\s+/g,""),e[1])}else this.node.style[r(t)]=null===e||v.regex.isBlank.test(e)?"":e;return this}}),v.Parent=v.invent({create:function(t){this.constructor.call(this,t)},inherit:v.Element,extend:{children:function(){return v.utils.map(v.utils.filterSVGElements(this.node.childNodes),function(t){return v.adopt(t)})},add:function(t,e){return this.has(t)||(e=null==e?this.children().length:e,this.node.insertBefore(t.node,this.node.childNodes[e]||null)),this},put:function(t,e){return this.add(t,e),t},has:function(t){return this.index(t)>=0},index:function(t){return this.children().indexOf(t)},get:function(t){return this.children()[t]},first:function(){return this.children()[0]},last:function(){return this.children()[this.children().length-1]},each:function(t,e){var n,i,r=this.children();for(n=0,i=r.length;i>n;n++)r[n]instanceof v.Element&&t.apply(r[n],[n,r]),e&&r[n]instanceof v.Container&&r[n].each(t,e);return this},removeElement:function(t){return this.node.removeChild(t.node),this},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return delete this._defs,this},defs:function(){return this.doc().defs()}}}),v.extend(v.Parent,{ungroup:function(t,e){return 0===e||this instanceof v.Defs?this:(t=t||(this instanceof v.Doc?this:this.parent(v.Parent)),e=e||1/0,this.each(function(){return this instanceof v.Defs?this:this instanceof v.Parent?this.ungroup(t,e-1):this.toParent(t)}),this.node.firstChild||this.remove(),this)},flatten:function(t,e){return this.ungroup(t,e)}}),v.Container=v.invent({create:function(t){this.constructor.call(this,t)},inherit:v.Parent,extend:{viewbox:function(t){return 0==arguments.length?new v.ViewBox(this):(t=1==arguments.length?[t.x,t.y,t.width,t.height]:[].slice.call(arguments),this.attr("viewBox",t))}}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){v.Element.prototype[t]=function(e){var n=this;return this.node["on"+t]="function"==typeof e?function(){return e.apply(n,arguments)}:null,this}}),v.listeners=[],v.handlerMap=[],v.listenerId=0,v.on=function(t,e,n,i){var r=n.bind(i||t.instance||t),s=(v.handlerMap.indexOf(t)+1||v.handlerMap.push(t))-1,a=e.split(".")[0],h=e.split(".")[1]||"*";v.listeners[s]=v.listeners[s]||{},v.listeners[s][a]=v.listeners[s][a]||{},v.listeners[s][a][h]=v.listeners[s][a][h]||{},n._svgjsListenerId||(n._svgjsListenerId=++v.listenerId),v.listeners[s][a][h][n._svgjsListenerId]=r,t.addEventListener(a,r,!1)},v.off=function(t,e,n){var i=v.handlerMap.indexOf(t),r=e&&e.split(".")[0],s=e&&e.split(".")[1];if(-1!=i)if(n){if("function"==typeof n&&(n=n._svgjsListenerId),!n)return;v.listeners[i][r]&&v.listeners[i][r][s||"*"]&&(t.removeEventListener(r,v.listeners[i][r][s||"*"][n],!1),delete v.listeners[i][r][s||"*"][n])}else if(s&&r){if(v.listeners[i][r]&&v.listeners[i][r][s]){for(n in v.listeners[i][r][s])v.off(t,[r,s].join("."),n);delete v.listeners[i][r][s]}}else if(s)for(e in v.listeners[i])for(namespace in v.listeners[i][e])s===namespace&&v.off(t,[e,s].join("."));else if(r){if(v.listeners[i][r]){for(namespace in v.listeners[i][r])v.off(t,[r,namespace].join("."));delete v.listeners[i][r]}}else{for(e in v.listeners[i])v.off(t,e);delete v.listeners[i]}},v.extend(v.Element,{on:function(t,e,n){return v.on(this.node,t,e,n),this},off:function(t,e){return v.off(this.node,t,e),this},fire:function(t,e){return t instanceof Event?this.node.dispatchEvent(t):this.node.dispatchEvent(new b(t,{detail:e})),this}}),v.Defs=v.invent({create:"defs",inherit:v.Container}),v.G=v.invent({create:"g",inherit:v.Container,extend:{x:function(t){return null==t?this.transform("x"):this.transform({x:t-this.x()},!0)},y:function(t){return null==t?this.transform("y"):this.transform({y:t-this.y()},!0)},cx:function(t){return null==t?this.gbox().cx:this.x(t-this.gbox().width/2)},cy:function(t){return null==t?this.gbox().cy:this.y(t-this.gbox().height/2)},gbox:function(){var t=this.bbox(),e=this.transform();return t.x+=e.x,t.x2+=e.x,t.cx+=e.x,t.y+=e.y,t.y2+=e.y,t.cy+=e.y,t}},construct:{group:function(){return this.put(new v.G)}}}),v.extend(v.Element,{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){var t=this.position()+1,e=this.parent();return e.removeElement(this).add(this,t),e instanceof v.Doc&&e.node.appendChild(e.defs().node),this},backward:function(){var t=this.position();return t>0&&this.parent().removeElement(this).add(this,t-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof v.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(t){t.remove();var e=this.position();return this.parent().add(t,e),this},after:function(t){t.remove();var e=this.position();return this.parent().add(t,e+1),this}}),v.Mask=v.invent({create:function(){this.constructor.call(this,v.create("mask")),this.targets=[]},inherit:v.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return this.targets=[],this.parent().removeElement(this),this}},construct:{mask:function(){return this.defs().put(new v.Mask)}}}),v.extend(v.Element,{maskWith:function(t){return this.masker=t instanceof v.Mask?t:this.parent().mask().add(t),this.masker.targets.push(this),this.attr("mask",'url("#'+this.masker.attr("id")+'")')},unmask:function(){return delete this.masker,this.attr("mask",null)}}),v.ClipPath=v.invent({create:function(){this.constructor.call(this,v.create("clipPath")),this.targets=[]},inherit:v.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return this.targets=[],this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new v.ClipPath)}}}),v.extend(v.Element,{clipWith:function(t){return this.clipper=t instanceof v.ClipPath?t:this.parent().clip().add(t),this.clipper.targets.push(this),this.attr("clip-path",'url("#'+this.clipper.attr("id")+'")')},unclip:function(){return delete this.clipper,this.attr("clip-path",null)}}),v.Gradient=v.invent({create:function(t){this.constructor.call(this,v.create(t+"Gradient")),this.type=t},inherit:v.Container,extend:{at:function(t,e,n){return this.put(new v.Stop).update(t,e,n)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},fill:function(){return"url(#"+this.id()+")"},toString:function(){return this.fill()},attr:function(t,e,n){return"transform"==t&&(t="gradientTransform"),v.Container.prototype.attr.call(this,t,e,n)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),v.extend(v.Gradient,v.FX,{from:function(t,e){return"radial"==(this.target||this).type?this.attr({fx:new v.Number(t),fy:new v.Number(e)}):this.attr({x1:new v.Number(t),y1:new v.Number(e)})},to:function(t,e){return"radial"==(this.target||this).type?this.attr({cx:new v.Number(t),cy:new v.Number(e)}):this.attr({x2:new v.Number(t),y2:new v.Number(e)})}}),v.extend(v.Defs,{gradient:function(t,e){return this.put(new v.Gradient(t)).update(e)}}),v.Stop=v.invent({create:"stop",inherit:v.Element,extend:{update:function(t){return("number"==typeof t||t instanceof v.Number)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new v.Number(t.offset)),this}}}),v.Pattern=v.invent({create:"pattern",inherit:v.Container,extend:{fill:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.fill()},attr:function(t,e,n){return"transform"==t&&(t="patternTransform"),v.Container.prototype.attr.call(this,t,e,n)}},construct:{pattern:function(t,e,n){return this.defs().pattern(t,e,n)}}}),v.extend(v.Defs,{pattern:function(t,e,n){return this.put(new v.Pattern).update(n).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),v.Doc=v.invent({create:function(t){t&&(t="string"==typeof t?e.getElementById(t):t,"svg"==t.nodeName?this.constructor.call(this,t):(this.constructor.call(this,v.create("svg")),t.appendChild(this.node)),this.namespace().size("100%","100%").defs())},inherit:v.Container,extend:{namespace:function(){return this.attr({xmlns:v.ns,version:"1.1"}).attr("xmlns:xlink",v.xlink,v.xmlns).attr("xmlns:svgjs",v.svgjs,v.xmlns)},defs:function(){if(!this._defs){var t;this._defs=(t=this.node.getElementsByTagName("defs")[0])?v.adopt(t):new v.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return"#document"==this.node.parentNode.nodeName?null:this.node.parentNode},spof:function(){var t=this.node.getScreenCTM();return t&&this.style("left",-t.e%1+"px").style("top",-t.f%1+"px"),this},remove:function(){return this.parent()&&this.parent().removeChild(this.node),this}}}),v.Shape=v.invent({create:function(t){this.constructor.call(this,t)},inherit:v.Element}),v.Bare=v.invent({create:function(t,e){if(this.constructor.call(this,v.create(t)),e)for(var n in e.prototype)"function"==typeof e.prototype[n]&&(this[n]=e.prototype[n])},inherit:v.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(e.createTextNode(t)),this}}}),v.extend(v.Parent,{element:function(t,e){return this.put(new v.Bare(t,e))},symbol:function(){return this.defs().element("symbol",v.Container)}}),v.Use=v.invent({create:"use",inherit:v.Shape,extend:{element:function(t,e){return this.attr("href",(e||"")+"#"+t,v.xlink)}},construct:{use:function(t,e){return this.put(new v.Use).element(t,e)}}}),v.Rect=v.invent({create:"rect",inherit:v.Shape,construct:{rect:function(t,e){return this.put(new v.Rect).size(t,e)}}}),v.Circle=v.invent({create:"circle",inherit:v.Shape,construct:{circle:function(t){return this.put(new v.Circle).rx(new v.Number(t).divide(2)).move(0,0)}}}),v.extend(v.Circle,v.FX,{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),v.Ellipse=v.invent({create:"ellipse",inherit:v.Shape,construct:{ellipse:function(t,e){return this.put(new v.Ellipse).size(t,e).move(0,0)}}}),v.extend(v.Ellipse,v.Rect,v.FX,{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),v.extend(v.Circle,v.Ellipse,{x:function(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())},y:function(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",t)},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",t)},width:function(t){return null==t?2*this.rx():this.rx(new v.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new v.Number(t).divide(2))},size:function(t,e){var n=o(this.bbox(),t,e);return this.rx(new v.Number(n.width).divide(2)).ry(new v.Number(n.height).divide(2))}}),v.Line=v.invent({create:"line",inherit:v.Shape,extend:{array:function(){return new v.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,n,i){return t=4==arguments.length?{x1:t,y1:e,x2:n,y2:i}:new v.PointArray(t).toLine(),this.attr(t)},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var n=o(this.bbox(),t,e);return this.attr(this.array().size(n.width,n.height).toLine())}},construct:{line:function(t,e,n,i){return this.put(new v.Line).plot(t,e,n,i)}}}),v.Polyline=v.invent({create:"polyline",inherit:v.Shape,construct:{polyline:function(t){return this.put(new v.Polyline).plot(t)}}}),v.Polygon=v.invent({create:"polygon",inherit:v.Shape,construct:{polygon:function(t){return this.put(new v.Polygon).plot(t)}}}),v.extend(v.Polyline,v.Polygon,{array:function(){return this._array||(this._array=new v.PointArray(this.attr("points")))},plot:function(t){return this.attr("points",this._array=new v.PointArray(t))},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var n=o(this.bbox(),t,e);return this.attr("points",this.array().size(n.width,n.height))}}),v.extend(v.Line,v.Polyline,v.Polygon,{morphArray:v.PointArray,x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},width:function(t){var e=this.bbox();return null==t?e.width:this.size(t,e.height)},height:function(t){var e=this.bbox();return null==t?e.height:this.size(e.width,t)}}),v.Path=v.invent({create:"path",inherit:v.Shape,extend:{morphArray:v.PathArray,array:function(){return this._array||(this._array=new v.PathArray(this.attr("d")))},plot:function(t){return this.attr("d",this._array=new v.PathArray(t))},move:function(t,e){return this.attr("d",this.array().move(t,e))},x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},size:function(t,e){var n=o(this.bbox(),t,e);return this.attr("d",this.array().size(n.width,n.height))},width:function(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)},height:function(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}},construct:{path:function(t){return this.put(new v.Path).plot(t)}}}),v.Image=v.invent({create:"image",inherit:v.Shape,extend:{load:function(t){if(!t)return this;var n=this,i=e.createElement("img");return i.onload=function(){var e=n.parent(v.Pattern);null!==e&&(0==n.width()&&0==n.height()&&n.size(i.width,i.height),e&&0==e.width()&&0==e.height()&&e.size(n.width(),n.height()),"function"==typeof n._loaded&&n._loaded.call(n,{width:i.width,height:i.height,ratio:i.width/i.height,url:t}))},this.attr("href",i.src=this.src=t,v.xlink)},loaded:function(t){return this._loaded=t,this}},construct:{image:function(t,e,n){return this.put(new v.Image).load(t).size(e||0,n||e||0)}}}),v.Text=v.invent({create:function(){this.constructor.call(this,v.create("text")),this.dom.leading=new v.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",v.defaults.attrs["font-family"])},inherit:v.Shape,extend:{clone:function(){var t=m(this.node.cloneNode(!0));return this.after(t),t},x:function(t){return null==t?this.attr("x"):(this.textPath||this.lines().each(function(){this.dom.newLined&&this.x(t)}),this.attr("x",t))},y:function(t){var e=this.attr("y"),n="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-n:e:this.attr("y","number"==typeof t?t+n:t)},cx:function(t){return null==t?this.bbox().cx:this.x(t-this.bbox().width/2)},cy:function(t){return null==t?this.bbox().cy:this.y(t-this.bbox().height/2)},text:function(t){if("undefined"==typeof t){for(var t="",e=this.node.childNodes,n=0,i=e.length;i>n;++n)0!=n&&3!=e[n].nodeType&&1==v.adopt(e[n]).dom.newLined&&(t+="\n"),t+=e[n].textContent;return t}if(this.clear().build(!0),"function"==typeof t)t.call(this,this);else{t=t.split("\n");for(var n=0,r=t.length;r>n;n++)this.tspan(t[n]).newLine()}return this.build(!1).rebuild()},size:function(t){return this.attr("font-size",t).rebuild()},leading:function(t){return null==t?this.dom.leading:(this.dom.leading=new v.Number(t),this.rebuild())},lines:function(){var t=(this.textPath&&this.textPath()||this).node,e=v.utils.map(v.utils.filterSVGElements(t.childNodes),function(t){return v.adopt(t)});return new v.Set(e)},rebuild:function(t){if("boolean"==typeof t&&(this._rebuild=t),this._rebuild){var e=this,n=0,i=this.dom.leading*new v.Number(this.attr("font-size"));this.lines().each(function(){this.dom.newLined&&(this.textPath||this.attr("x",e.attr("x")),"\n"==this.text()?n+=i:(this.attr("dy",i+n),n=0))}),this.fire("rebuild")}return this},build:function(t){return this._build=!!t,this},setData:function(t){return this.dom=t,this.dom.leading=new v.Number(t.leading||1.3),this}},construct:{text:function(t){return this.put(new v.Text).text(t)},plain:function(t){return this.put(new v.Text).plain(t)}}}),v.Tspan=v.invent({create:"tspan",inherit:v.Shape,extend:{text:function(t){return null==t?this.node.textContent+(this.dom.newLined?"\n":""):("function"==typeof t?t.call(this,this):this.plain(t),this)},dx:function(t){return this.attr("dx",t)},dy:function(t){return this.attr("dy",t)},newLine:function(){var t=this.parent(v.Text);return this.dom.newLined=!0,this.dy(t.dom.leading*t.attr("font-size")).attr("x",t.x())}}}),v.extend(v.Text,v.Tspan,{plain:function(t){return this._build===!1&&this.clear(),this.node.appendChild(e.createTextNode(t)),this},tspan:function(t){var e=(this.textPath&&this.textPath()||this).node,n=new v.Tspan;return this._build===!1&&this.clear(),e.appendChild(n.node),n.text(t)},clear:function(){for(var t=(this.textPath&&this.textPath()||this).node;t.hasChildNodes();)t.removeChild(t.lastChild);return this},length:function(){return this.node.getComputedTextLength()}}),v.TextPath=v.invent({create:"textPath",inherit:v.Parent,parent:v.Text,construct:{path:function(t){for(var e=new v.TextPath,n=this.doc().defs().path(t);this.node.hasChildNodes();)e.node.appendChild(this.node.firstChild);return this.node.appendChild(e.node),e.attr("href","#"+n,v.xlink),this},plot:function(t){var e=this.track();return e&&e.plot(t),this},track:function(){var t=this.textPath();return t?t.reference("href"):void 0},textPath:function(){return this.node.firstChild&&"textPath"==this.node.firstChild.nodeName?v.adopt(this.node.firstChild):void 0}}}),v.Nested=v.invent({create:function(){this.constructor.call(this,v.create("svg")),this.style("overflow","visible")},inherit:v.Container,construct:{nested:function(){return this.put(new v.Nested)}}}),v.A=v.invent({create:"a",inherit:v.Container,extend:{to:function(t){return this.attr("href",t,v.xlink)},show:function(t){return this.attr("show",t,v.xlink)},target:function(t){return this.attr("target",t)}},construct:{link:function(t){return this.put(new v.A).to(t)}}}),v.extend(v.Element,{linkTo:function(t){var e=new v.A;return"function"==typeof t?t.call(e,e):e.to(t),this.parent().put(e).put(this)}}),v.Marker=v.invent({create:"marker",inherit:v.Container,extend:{width:function(t){return this.attr("markerWidth",t)},height:function(t){return this.attr("markerHeight",t)},ref:function(t,e){return this.attr("refX",t).attr("refY",e)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return"url(#"+this.id()+")"}},construct:{marker:function(t,e,n){return this.defs().marker(t,e,n)}}}),v.extend(v.Defs,{marker:function(t,e,n){return this.put(new v.Marker).size(t,e).ref(t/2,e/2).viewbox(0,0,t,e).attr("orient","auto").update(n)}}),v.extend(v.Line,v.Polyline,v.Polygon,v.Path,{marker:function(t,e,n,i){var r=["marker"];return"all"!=t&&r.push(t),r=r.join("-"),t=arguments[1]instanceof v.Marker?arguments[1]:this.doc().marker(e,n,i),this.attr(r,t)}});var y={stroke:["color","width","opacity","linecap","linejoin","miterlimit","dasharray","dashoffset"],fill:["color","opacity","rule"],prefix:function(t,e){return"color"==e?t:t+"-"+e}};["fill","stroke"].forEach(function(t){var e,n={};n[t]=function(n){if("string"==typeof n||v.Color.isRgb(n)||n&&"function"==typeof n.fill)this.attr(t,n);else for(e=y[t].length-1;e>=0;e--)null!=n[y[t][e]]&&this.attr(y.prefix(t,y[t][e]),n[y[t][e]]);return this},v.extend(v.Element,v.FX,n)}),v.extend(v.Element,v.FX,{rotate:function(t,e,n){return this.transform({rotation:t,cx:e,cy:n})},skew:function(t,e,n,i){return this.transform({skewX:t,skewY:e,cx:n,cy:i})},scale:function(t,e,n,i){return 1==arguments.length||3==arguments.length?this.transform({scale:t,cx:e,cy:n}):this.transform({scaleX:t,scaleY:e,cx:n,cy:i})},translate:function(t,e){return this.transform({x:t,y:e})},flip:function(t,e){return this.transform({flip:t,offset:e})},matrix:function(t){return this.attr("transform",new v.Matrix(t))},opacity:function(t){return this.attr("opacity",t)},dx:function(t){return this.x((this instanceof v.FX?0:this.x())+t,!0)},dy:function(t){return this.y((this instanceof v.FX?0:this.y())+t,!0)},dmove:function(t,e){return this.dx(t).dy(e)}}),v.extend(v.Rect,v.Ellipse,v.Circle,v.Gradient,v.FX,{radius:function(t,e){var n=(this.target||this).type;return"radial"==n||"circle"==n?this.attr({r:new v.Number(t)}):this.rx(t).ry(null==e?t:e)}}),v.extend(v.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),v.extend(v.Parent,v.Text,v.FX,{font:function(t){for(var e in t)"leading"==e?this.leading(t[e]):"anchor"==e?this.attr("text-anchor",t[e]):"size"==e||"family"==e||"weight"==e||"stretch"==e||"variant"==e||"style"==e?this.attr("font-"+e,t[e]):this.attr(e,t[e]);return this}}),v.Set=v.invent({create:function(t){Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){var t,e,n=[].slice.call(arguments);for(t=0,e=n.length;e>t;t++)this.members.push(n[t]);return this},remove:function(t){var e=this.index(t);return e>-1&&this.members.splice(e,1),this},each:function(t){for(var e=0,n=this.members.length;n>e;e++)t.apply(this.members[e],[e,this.members]);return this},clear:function(){return this.members=[],this},length:function(){return this.members.length},has:function(t){return this.index(t)>=0},index:function(t){return this.members.indexOf(t)},get:function(t){return this.members[t]},first:function(){return this.get(0)},last:function(){return this.get(this.members.length-1)},valueOf:function(){return this.members},bbox:function(){var t=new v.BBox;if(0==this.members.length)return t;var e=this.members[0].rbox();return t.x=e.x,t.y=e.y,t.width=e.width,t.height=e.height,this.each(function(){t=t.merge(this.rbox())}),t}},construct:{set:function(t){return new v.Set(t)}}}),v.FX.Set=v.invent({create:function(t){this.set=t}}),v.Set.inherit=function(){var t,e=[];for(var t in v.Shape.prototype)"function"==typeof v.Shape.prototype[t]&&"function"!=typeof v.Set.prototype[t]&&e.push(t);e.forEach(function(t){v.Set.prototype[t]=function(){for(var e=0,n=this.members.length;n>e;e++)this.members[e]&&"function"==typeof this.members[e][t]&&this.members[e][t].apply(this.members[e],arguments);return"animate"==t?this.fx||(this.fx=new v.FX.Set(this)):this}}),e=[];for(var t in v.FX.prototype)"function"==typeof v.FX.prototype[t]&&"function"!=typeof v.FX.Set.prototype[t]&&e.push(t);e.forEach(function(t){v.FX.Set.prototype[t]=function(){for(var e=0,n=this.set.members.length;n>e;e++)this.set.members[e].fx[t].apply(this.set.members[e].fx,arguments);return this}})},v.extend(v.Element,{data:function(t,e,n){if("object"==typeof t)for(e in t)this.data(e,t[e]);else if(arguments.length<2)try{return JSON.parse(this.attr("data-"+t))}catch(i){return this.attr("data-"+t)}else this.attr("data-"+t,null===e?null:n===!0||"string"==typeof e||"number"==typeof e?e:JSON.stringify(e));return this}}),v.extend(v.Element,{remember:function(t,e){if("object"==typeof arguments[0])for(var e in t)this.remember(e,t[e]);else{if(1==arguments.length)return this.memory()[t];this.memory()[t]=e}return this},forget:function(){if(0==arguments.length)this._memory={};else for(var t=arguments.length-1;t>=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),v.get=function(t){var n=e.getElementById(g(t)||t);return v.adopt(n)},v.select=function(t,n){return new v.Set(v.utils.map((n||e).querySelectorAll(t),function(t){return v.adopt(t)}))},v.extend(v.Parent,{select:function(t){return v.select(t,this.node)}});var w="abcdef".split("");if("function"!=typeof b){var b=function(t,n){n=n||{bubbles:!1,cancelable:!1,detail:void 0};var i=e.createEvent("CustomEvent");return i.initCustomEvent(t,n.bubbles,n.cancelable,n.detail),i};b.prototype=t.Event.prototype,t.CustomEvent=b}return function(e){for(var n=0,i=["moz","webkit"],r=0;r<i.length&&!t.requestAnimationFrame;++r)e.requestAnimationFrame=e[i[r]+"RequestAnimationFrame"],e.cancelAnimationFrame=e[i[r]+"CancelAnimationFrame"]||e[i[r]+"CancelRequestAnimationFrame"];e.requestAnimationFrame=e.requestAnimationFrame||function(t){var i=(new Date).getTime(),r=Math.max(0,16-(i-n)),s=e.setTimeout(function(){t(i+r)},r);return n=i+r,s},e.cancelAnimationFrame=e.cancelAnimationFrame||e.clearTimeout}(t),v}); \ No newline at end of file
diff --git a/spec/SpecRunner.html b/spec/SpecRunner.html
new file mode 100644
index 0000000..81b64af
--- /dev/null
+++ b/spec/SpecRunner.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Jasmine Spec Runner v2.4.1</title>
+
+ <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.4.1/jasmine_favicon.png">
+ <link rel="stylesheet" href="lib/jasmine-2.4.1/jasmine.css">
+
+ <script src="lib/jasmine-2.4.1/jasmine.js"></script>
+ <script src="lib/jasmine-2.4.1/jasmine-html.js"></script>
+ <script src="lib/jasmine-2.4.1/boot.js"></script>
+
+ <style type="text/css" media="screen">
+ #drawing {
+ width: 500px;
+ height: 500px;
+ position: fixed;
+ z-index: -1;
+ }
+ </style>
+
+ <!-- include source files here... -->
+ <script src="../dist/svg.js" charset="utf-8"></script>
+
+</head>
+
+<body>
+ <svg height="0" width="0" id="inlineSVG">
+ <desc>Some description</desc>
+ <path id="lineAB" d="M 100 350 l 150 -300" stroke="red" stroke-width="3" fill="none" />
+ <path id="lineBC" d="M 250 50 l 150 300" stroke="red" stroke-width="3" fill="none" />
+ <path d="M 175 200 l 150 0" stroke="green" stroke-width="3" fill="none" />
+ <path d="M 100 350 q 150 -300 300 0" stroke="blue" stroke-width="5" fill="none" />
+ <g stroke="black" stroke-width="3" fill="black" id="pointGroup">
+ <circle id="pointA" cx="100" cy="350" r="3" />
+ <circle id="pointB" cx="250" cy="50" r="3" />
+ <circle id="pointC" cx="400" cy="350" r="3" />
+ </g>
+ <g font-size="30" font="sans-serif" fill="black" stroke="none" text-anchor="middle" id="labelGroup">
+ <text x="100" y="350" dx="-30">A</text>
+ <text x="250" y="50" dy="-10">B</text>
+ <text x="400" y="350" dx="30">C</text>
+ </g>
+ <polygon points="200,10 250,190 160,210" />
+ <polyline points="20,20 40,25 60,40 80,120 120,140 200,180" />
+ </svg>
+
+ <!-- include spec files here... -->
+ <script src="spec/svg.js"></script>
+ <script src="spec/selector.js"></script>
+ <script src="spec/adopter.js"></script>
+ <script src="spec/regex.js"></script>
+ <script src="spec/container.js"></script>
+ <script src="spec/element.js"></script>
+ <script src="spec/memory.js"></script>
+ <script src="spec/arrange.js"></script>
+ <script src="spec/event.js"></script>
+ <script src="spec/boxes.js"></script>
+ <script src="spec/matrix.js"></script>
+ <script src="spec/rect.js"></script>
+ <script src="spec/circle.js"></script>
+ <script src="spec/ellipse.js"></script>
+ <script src="spec/line.js"></script>
+ <script src="spec/polyline.js"></script>
+ <script src="spec/polygon.js"></script>
+ <script src="spec/path.js"></script>
+ <script src="spec/marker.js"></script>
+ <script src="spec/image.js"></script>
+ <script src="spec/text.js"></script>
+ <script src="spec/textpath.js"></script>
+ <script src="spec/doc.js"></script>
+ <script src="spec/defs.js"></script>
+ <script src="spec/group.js"></script>
+ <script src="spec/set.js"></script>
+ <script src="spec/gradient.js"></script>
+ <script src="spec/pattern.js"></script>
+ <script src="spec/use.js"></script>
+ <script src="spec/bare.js"></script>
+ <script src="spec/mask.js"></script>
+ <script src="spec/clip.js"></script>
+ <script src="spec/color.js"></script>
+ <script src="spec/number.js"></script>
+ <script src="spec/array.js"></script>
+ <script src="spec/hyperlink.js"></script>
+ <script src="spec/fx.js"></script>
+
+ <script type="text/javascript" src="spec/helper.js"></script>
+</body>
+</html>
diff --git a/spec/index.html b/spec/index.html
deleted file mode 100644
index 526cda6..0000000
--- a/spec/index.html
+++ /dev/null
@@ -1,120 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
- "http://www.w3.org/TR/html4/loose.dtd">
-<html>
-<head>
- <title>SVG.js - Jasmine Spec Runner</title>
-
- <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.0.1/jasmine_favicon.png">
- <link rel="stylesheet" type="text/css" href="lib/jasmine-2.0.1/jasmine.css">
-
- <style type="text/css" media="screen">
- #drawing {
- width: 500px;
- height: 500px;
- position: fixed;
- z-index: -1;
- }
- </style>
-
-</head>
-
-<body>
- <svg height="0" width="0" id="inlineSVG">
- <desc>Some description</desc>
- <path id="lineAB" d="M 100 350 l 150 -300" stroke="red" stroke-width="3" fill="none" />
- <path id="lineBC" d="M 250 50 l 150 300" stroke="red" stroke-width="3" fill="none" />
- <path d="M 175 200 l 150 0" stroke="green" stroke-width="3" fill="none" />
- <path d="M 100 350 q 150 -300 300 0" stroke="blue" stroke-width="5" fill="none" />
- <g stroke="black" stroke-width="3" fill="black" id="pointGroup">
- <circle id="pointA" cx="100" cy="350" r="3" />
- <circle id="pointB" cx="250" cy="50" r="3" />
- <circle id="pointC" cx="400" cy="350" r="3" />
- </g>
- <g font-size="30" font="sans-serif" fill="black" stroke="none" text-anchor="middle" id="labelGroup">
- <text x="100" y="350" dx="-30">A</text>
- <text x="250" y="50" dy="-10">B</text>
- <text x="400" y="350" dx="30">C</text>
- </g>
- <polygon points="200,10 250,190 160,210" />
- <polyline points="20,20 40,25 60,40 80,120 120,140 200,180" />
- </svg>
-</body>
-
-<script src="lib/jasmine-2.0.1/jasmine.js"></script>
-<script src="lib/jasmine-2.0.1/jasmine-html.js"></script>
-<script src="lib/jasmine-2.0.1/boot.js"></script>
-
-<!-- include source files here... -->
-<script src="../dist/svg.js" charset="utf-8"></script>
-
-<!-- include spec files here... -->
-<script src="spec/svg.js"></script>
-<script src="spec/selector.js"></script>
-<script src="spec/adopter.js"></script>
-<script src="spec/regex.js"></script>
-<script src="spec/container.js"></script>
-<script src="spec/element.js"></script>
-<script src="spec/memory.js"></script>
-<script src="spec/arrange.js"></script>
-<script src="spec/event.js"></script>
-<script src="spec/boxes.js"></script>
-<script src="spec/matrix.js"></script>
-<script src="spec/rect.js"></script>
-<script src="spec/circle.js"></script>
-<script src="spec/ellipse.js"></script>
-<script src="spec/line.js"></script>
-<script src="spec/polyline.js"></script>
-<script src="spec/polygon.js"></script>
-<script src="spec/path.js"></script>
-<script src="spec/marker.js"></script>
-<script src="spec/image.js"></script>
-<script src="spec/text.js"></script>
-<script src="spec/textpath.js"></script>
-<script src="spec/doc.js"></script>
-<script src="spec/defs.js"></script>
-<script src="spec/group.js"></script>
-<script src="spec/set.js"></script>
-<script src="spec/gradient.js"></script>
-<script src="spec/pattern.js"></script>
-<script src="spec/use.js"></script>
-<script src="spec/bare.js"></script>
-<script src="spec/mask.js"></script>
-<script src="spec/clip.js"></script>
-<script src="spec/color.js"></script>
-<script src="spec/number.js"></script>
-<script src="spec/array.js"></script>
-<script src="spec/hyperlink.js"></script>
-<script src="spec/fx.js"></script>
-
-<script type="text/javascript">
- (function() {
- var jasmineEnv = jasmine.getEnv();
- jasmineEnv.updateInterval = 1000;
-
- var htmlReporter = new jasmine.HtmlReporter();
-
- jasmineEnv.addReporter(htmlReporter);
-
- jasmineEnv.specFilter = function(spec) {
- return htmlReporter.specFilter(spec);
- };
-
- var currentWindowOnload = window.onload;
-
- window.onload = function() {
- if (currentWindowOnload) {
- currentWindowOnload();
- }
- execJasmine();
- };
-
- function execJasmine() {
- jasmineEnv.execute();
- }
-
- })();
-</script>
-
-<script type="text/javascript" src="spec/helper.js"></script>
-
-</html>
diff --git a/spec/lib/jasmine-2.0.1/jasmine.css b/spec/lib/jasmine-2.0.1/jasmine.css
deleted file mode 100644
index c54ff30..0000000
--- a/spec/lib/jasmine-2.0.1/jasmine.css
+++ /dev/null
@@ -1,59 +0,0 @@
-body { overflow-y: scroll; }
-
-.jasmine_html-reporter { background-color: #eeeeee; padding: 5px; margin: -8px; font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
-.jasmine_html-reporter a { text-decoration: none; }
-.jasmine_html-reporter a:hover { text-decoration: underline; }
-.jasmine_html-reporter p, .jasmine_html-reporter h1, .jasmine_html-reporter h2, .jasmine_html-reporter h3, .jasmine_html-reporter h4, .jasmine_html-reporter h5, .jasmine_html-reporter h6 { margin: 0; line-height: 14px; }
-.jasmine_html-reporter .banner, .jasmine_html-reporter .symbol-summary, .jasmine_html-reporter .summary, .jasmine_html-reporter .result-message, .jasmine_html-reporter .spec .description, .jasmine_html-reporter .spec-detail .description, .jasmine_html-reporter .alert .bar, .jasmine_html-reporter .stack-trace { padding-left: 9px; padding-right: 9px; }
-.jasmine_html-reporter .banner { position: relative; }
-.jasmine_html-reporter .banner .title { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAZCAMAAACGusnyAAACdlBMVEX/////AP+AgICqVaqAQICZM5mAVYCSSZKAQICOOY6ATYCLRouAQICJO4mSSYCIRIiPQICHPIeOR4CGQ4aMQICGPYaLRoCFQ4WKQICPPYWJRYCOQoSJQICNPoSIRICMQoSHQICHRICKQoOHQICKPoOJO4OJQYOMQICMQ4CIQYKLQICIPoKLQ4CKQICNPoKJQISMQ4KJQoSLQYKJQISLQ4KIQoSKQYKIQICIQISMQoSKQYKLQIOLQoOJQYGLQIOKQIOMQoGKQYOLQYGKQIOLQoGJQYOJQIOKQYGJQIOKQoGKQIGLQIKLQ4KKQoGLQYKJQIGKQYKJQIGKQIKJQoGKQYKLQIGKQYKLQIOJQoKKQoOJQYKKQIOJQoKKQoOKQIOLQoKKQYOLQYKJQIOKQoKKQYKKQoKJQYOKQYKLQIOKQoKLQYOKQYKLQIOJQoGKQYKJQYGJQoGKQYKLQoGLQYGKQoGJQYKKQYGJQIKKQoGJQYKLQIKKQYGLQYKKQYGKQYGKQYKJQYOKQoKJQYOKQYKLQYOLQYOKQYKLQYOKQoKKQYKKQYOKQYOJQYKKQYKLQYKKQIKKQoKKQYKKQYKKQoKJQIKKQYKLQYKKQYKKQIKKQYKKQYKKQYKKQIKKQYKJQYGLQYGKQYKKQYKKQYGKQIKKQYGKQYOJQoKKQYOLQYKKQYOKQoKKQYKKQoKKQYKKQYKJQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKJQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKmIDpEAAAA0XRSTlMAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAiIyQlJycoKissLS4wMTQ1Njc4OTo7PDw+P0BCQ0RISUpLTE1OUFNUVVdYWFlaW15fYGFiY2ZnaGlqa2xtb3BxcnN0dnh5ent8fX5/gIGChIWIioyNjo+QkZOUlZaYmZqbnJ2eoKGio6WmqKmsra6vsLGztre4ubq7vL2+wMHDxMjJysvNzs/Q0dLU1tfY2dvc3t/g4eLj5ebn6Onq6+zt7u/w8vP09fb3+Pn6+/z9/vkVQXAAAAMaSURBVHhe5dXxV1N1GMfxz2ABbDgIAm5VDJOyVDIJLUMaVpBWUZUaGbmqoGpZRSiGiRWp6KoZ5AB0ZY50RImZQIlahKkMYXv/R90dBvET/rJfOr3Ouc8v99zPec59zvf56j+vYKlViSf7250X4Mr3O29Tgq08BdGB4DhcekEJ5YkQKFsgWZdtj9JpV+I8xPjLFqkrsEIqO8PHSpis36jWazcqjEsfJjkvRssVU37SdIOu4XCf5vEJPsnwJpnRNU9JmxhMk8l1gehIrq7hTFjzOD+Vf88629qKMJVNltInFeRexRQyJlNeqd1iGDlSzrIUIyXbyFfm3RYprcQRe7lqtWyGYbfc6dT0R2vmdOOkX3u55C1rP37ftiH+tDby4r/RBT0w8TyEkr+epB9XgPDmSYYWbrhCuFYaIyw3fDQAXTnSkh+ANofiHmWf9l+FY1I90FdQTetstO00o23novzVsJ7uB3/C5TkbjRwZ5JerwV4iRWq9HFbFMaK/d0TYqayRiQPuIxxS3Bu8JWU90/60tKi7vkhaznez0a/TbVOKj5CaOZh6fWG6/Lyv9B/ZLR1gw/S/fpbeVD3MCW1li6SvWDOn65tr99/uvWtBS0XDm4s1t+sOHpG0kpBKx/l77wOSnxLpcx6TXmXLTPQOKYOf9Q1dfr8/SJ2mFdCvl1Yl93DiHUZvXeLJbGSzYu5gVJ2slbSakOR8dxCq5adQ2oFLqsE9Ex3L4qQO0eOPeU5x56bypXp4onSEb5OkICX6lDat55TeoztNKQcJaakrz9KCb95oD69IKq+yKW4XPjknaS52V0TZqE2cTtXjcHSCRmUO88e+85hj3EP74i9p8pylw7lxgMDyyl6OV7ZejnjNMfatu87LxRbH0IS35gt2a4ZjmGpVBdKK3Wr6INk8jWWSGqbA55CKgjBRC6E9w78ydTg3ABS3AFV1QN0Y4Aa2pgEjWnQURj9L0ayK6R2ysEqxHUKzYnLvvyU+i9KM2JHJzE4vyZOyDcOwOsySajeLPc8sNvPJkFlyJd20wpqAzZeAfZ3oWybxd+P/3j+SG3uSBdf2VQAAAABJRU5ErkJggg==') no-repeat; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNjgxLjk2MjUyIgogICBoZWlnaHQ9IjE4Ny41IgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhOCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczYiPjxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXBQYXRoMTgiPjxwYXRoCiAgICAgICAgIGQ9Ik0gMCwxNTAwIDAsMCBsIDU0NTUuNzQsMCAwLDE1MDAgTCAwLDE1MDAgeiIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgaWQ9InBhdGgyMCIgLz48L2NsaXBQYXRoPjwvZGVmcz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUsMCwwLC0xLjI1LDAsMTg3LjUpIgogICAgIGlkPSJnMTAiPjxnCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDAuMSwwLjEpIgogICAgICAgaWQ9ImcxMiI+PGcKICAgICAgICAgaWQ9ImcxNCI+PGcKICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgxOCkiCiAgICAgICAgICAgaWQ9ImcxNiI+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTU0NCw1OTkuNDM0IGMgMC45MiwtNDAuMzUyIDI1LjY4LC04MS42MDIgNzEuNTMsLTgxLjYwMiAyNy41MSwwIDQ3LjY4LDEyLjgzMiA2MS40NCwzNS43NTQgMTIuODMsMjIuOTMgMTIuODMsNTYuODUyIDEyLjgzLDgyLjUyNyBsIDAsMzI5LjE4NCAtNzEuNTIsMCAwLDEwNC41NDMgMjY2LjgzLDAgMCwtMTA0LjU0MyAtNzAuNiwwIDAsLTM0NC43NyBjIDAsLTU4LjY5MSAtMy42OCwtMTA0LjUzMSAtNDQuOTMsLTE1Mi4yMTggLTM2LjY4LC00Mi4xOCAtOTYuMjgsLTY2LjAyIC0xNTMuMTQsLTY2LjAyIC0xMTcuMzcsMCAtMjA3LjI0LDc3Ljk0MSAtMjAyLjY0LDE5Ny4xNDUgbCAxMzAuMiwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMjIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDIzMDEuNCw2NjIuNjk1IGMgMCw4MC43MDMgLTY2Ljk0LDE0NS44MTMgLTE0Ny42MywxNDUuODEzIC04My40NCwwIC0xNDcuNjMsLTY4Ljc4MSAtMTQ3LjYzLC0xNTEuMzAxIDAsLTc5Ljc4NSA2Ni45NCwtMTQ1LjgwMSAxNDUuOCwtMTQ1LjgwMSA4NC4zNSwwIDE0OS40Niw2Ny44NTIgMTQ5LjQ2LDE1MS4yODkgeiBtIC0xLjgzLC0xODEuNTQ3IGMgLTM1Ljc3LC01NC4wOTcgLTkzLjUzLC03OC44NTkgLTE1Ny43MiwtNzguODU5IC0xNDAuMywwIC0yNTEuMjQsMTE2LjQ0OSAtMjUxLjI0LDI1NC45MTggMCwxNDIuMTI5IDExMy43LDI2MC40MSAyNTYuNzQsMjYwLjQxIDYzLjI3LDAgMTE4LjI5LC0yOS4zMzYgMTUyLjIyLC04Mi41MjMgbCAwLDY5LjY4NyAxNzUuMTQsMCAwLC0xMDQuNTI3IC02MS40NCwwIDAsLTI4MC41OTggNjEuNDQsMCAwLC0xMDQuNTI3IC0xNzUuMTQsMCAwLDY2LjAxOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAyNjIyLjMzLDU1Ny4yNTggYyAzLjY3LC00NC4wMTYgMzMuMDEsLTczLjM0OCA3OC44NiwtNzMuMzQ4IDMzLjkzLDAgNjYuOTMsMjMuODI0IDY2LjkzLDYwLjUwNCAwLDQ4LjYwNiAtNDUuODQsNTYuODU2IC04My40NCw2Ni45NDEgLTg1LjI4LDIyLjAwNCAtMTc4LjgxLDQ4LjYwNiAtMTc4LjgxLDE1NS44NzkgMCw5My41MzYgNzguODYsMTQ3LjYzMyAxNjUuOTgsMTQ3LjYzMyA0NCwwIDgzLjQzLC05LjE3NiAxMTAuOTQsLTQ0LjAwOCBsIDAsMzMuOTIyIDgyLjUzLDAgMCwtMTMyLjk2NSAtMTA4LjIxLDAgYyAtMS44MywzNC44NTYgLTI4LjQyLDU3Ljc3NCAtNjMuMjYsNTcuNzc0IC0zMC4yNiwwIC02Mi4zNSwtMTcuNDIyIC02Mi4zNSwtNTEuMzQ4IDAsLTQ1Ljg0NyA0NC45MywtNTUuOTMgODAuNjksLTY0LjE4IDg4LjAyLC0yMC4xNzUgMTgyLjQ3LC00Ny42OTUgMTgyLjQ3LC0xNTcuNzM0IDAsLTk5LjAyNyAtODMuNDQsLTE1NC4wMzkgLTE3NS4xMywtMTU0LjAzOSAtNDkuNTMsMCAtOTQuNDYsMTUuNTgyIC0xMjYuNTUsNTMuMTggbCAwLC00MC4zNCAtODUuMjcsMCAwLDE0Mi4xMjkgMTE0LjYyLDAiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGgyNiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMjk4OC4xOCw4MDAuMjU0IC02My4yNiwwIDAsMTA0LjUyNyAxNjUuMDUsMCAwLC03My4zNTUgYyAzMS4xOCw1MS4zNDcgNzguODYsODUuMjc3IDE0MS4yMSw4NS4yNzcgNjcuODUsMCAxMjQuNzEsLTQxLjI1OCAxNTIuMjEsLTEwMi42OTkgMjYuNiw2Mi4zNTEgOTIuNjIsMTAyLjY5OSAxNjAuNDcsMTAyLjY5OSA1My4xOSwwIDEwNS40NiwtMjIgMTQxLjIxLC02Mi4zNTEgMzguNTIsLTQ0LjkzOCAzOC41MiwtOTMuNTMyIDM4LjUyLC0xNDkuNDU3IGwgMCwtMTg1LjIzOSA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40MiwwIDAsMTA0LjUyNyA2My4yOCwwIDAsMTU3LjcxNSBjIDAsMzIuMTAyIDAsNjAuNTI3IC0xNC42Nyw4OC45NTcgLTE4LjM0LDI2LjU4MiAtNDguNjEsNDAuMzQ0IC03OS43Nyw0MC4zNDQgLTMwLjI2LDAgLTYzLjI4LC0xMi44NDQgLTgyLjUzLC0zNi42NzIgLTIyLjkzLC0yOS4zNTUgLTIyLjkzLC01Ni44NjMgLTIyLjkzLC05Mi42MjkgbCAwLC0xNTcuNzE1IDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM4LjQxLDAgMCwxMDQuNTI3IDYzLjI4LDAgMCwxNTAuMzgzIGMgMCwyOS4zNDggMCw2Ni4wMjMgLTE0LjY3LDkxLjY5OSAtMTUuNTksMjkuMzM2IC00Ny42OSw0NC45MzQgLTgwLjcsNDQuOTM0IC0zMS4xOCwwIC01Ny43NywtMTEuMDA4IC03Ny45NCwtMzUuNzc0IC0yNC43NywtMzAuMjUzIC0yNi42LC02Mi4zNDMgLTI2LjYsLTk5Ljk0MSBsIDAsLTE1MS4zMDEgNjMuMjcsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNiwwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAzOTk4LjY2LDk1MS41NDcgLTExMS44NywwIDAsMTE4LjI5MyAxMTEuODcsMCAwLC0xMTguMjkzIHogbSAwLC00MzEuODkxIDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM5LjMzLDAgMCwxMDQuNTI3IDY0LjE5LDAgMCwyODAuNTk4IC02My4yNywwIDAsMTA0LjUyNyAxNzUuMTQsMCAwLC0zODUuMTI1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDQxNTkuMTIsODAwLjI1NCAtNjMuMjcsMCAwLDEwNC41MjcgMTc1LjE0LDAgMCwtNjkuNjg3IGMgMjkuMzUsNTQuMTAxIDg0LjM2LDgwLjY5OSAxNDQuODcsODAuNjk5IDUzLjE5LDAgMTA1LjQ1LC0yMi4wMTYgMTQxLjIyLC02MC41MjcgNDAuMzQsLTQ0LjkzNCA0MS4yNiwtODguMDMyIDQxLjI2LC0xNDMuOTU3IGwgMCwtMTkxLjY1MyA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40LDAgMCwxMDQuNTI3IDYzLjI2LDAgMCwxNTguNjM3IGMgMCwzMC4yNjIgMCw2MS40MzQgLTE5LjI2LDg4LjAzNSAtMjAuMTcsMjYuNTgyIC01My4xOCwzOS40MTQgLTg2LjE5LDM5LjQxNCAtMzMuOTMsMCAtNjguNzcsLTEzLjc1IC04OC45NCwtNDEuMjUgLTIxLjA5LC0yNy41IC0yMS4wOSwtNjkuNjg3IC0yMS4wOSwtMTAyLjcwNyBsIDAsLTE0Mi4xMjkgNjMuMjYsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNywwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDMyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA1MDgyLjQ4LDcwMy45NjUgYyAtMTkuMjQsNzAuNjA1IC04MS42LDExNS41NDcgLTE1NC4wNCwxMTUuNTQ3IC02Ni4wNCwwIC0xMjkuMywtNTEuMzQ4IC0xNDMuMDUsLTExNS41NDcgbCAyOTcuMDksMCB6IG0gODUuMjcsLTE0NC44ODMgYyAtMzguNTEsLTkzLjUyMyAtMTI5LjI3LC0xNTYuNzkzIC0yMzEuMDUsLTE1Ni43OTMgLTE0My4wNywwIC0yNTcuNjgsMTExLjg3MSAtMjU3LjY4LDI1NS44MzYgMCwxNDQuODgzIDEwOS4xMiwyNjEuMzI4IDI1NC45MSwyNjEuMzI4IDY3Ljg3LDAgMTM1LjcyLC0zMC4yNTggMTgzLjM5LC03OC44NjMgNDguNjIsLTUxLjM0NCA2OC43OSwtMTEzLjY5NSA2OC43OSwtMTgzLjM4MyBsIC0zLjY3LC0zOS40MzQgLTM5Ni4xMywwIGMgMTQuNjcsLTY3Ljg2MyA3Ny4wMywtMTE3LjM2MyAxNDYuNzIsLTExNy4zNjMgNDguNTksMCA5MC43NiwxOC4zMjggMTE4LjI4LDU4LjY3MiBsIDExNi40NCwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDY5MC44OTUsODUwLjcwMyA5MC43NSwwIDIyLjU0MywzMS4wMzUgMCwyNDMuMTIyIC0xMzUuODI5LDAgMCwtMjQzLjE0MSAyMi41MzYsLTMxLjAxNiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDM2IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA2MzIuMzk1LDc0Mi4yNTggMjguMDM5LDg2LjMwNCAtMjIuNTUxLDMxLjA0IC0yMzEuMjIzLDc1LjEyOCAtNDEuOTc2LC0xMjkuMTgzIDIzMS4yNTcsLTc1LjEzNyAzNi40NTQsMTEuODQ4IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzgiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDcxNy40NDksNjUzLjEwNSAtNzMuNDEsNTMuMzYgLTM2LjQ4OCwtMTEuODc1IC0xNDIuOTAzLC0xOTYuNjkyIDEwOS44ODMsLTc5LjgyOCAxNDIuOTE4LDE5Ni43MDMgMCwzOC4zMzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0MCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gODI4LjUyLDcwNi40NjUgLTczLjQyNiwtNTMuMzQgMC4wMTEsLTM4LjM1OSBMIDg5OC4wMDQsNDE4LjA3IDEwMDcuOSw0OTcuODk4IDg2NC45NzMsNjk0LjYwOSA4MjguNTIsNzA2LjQ2NSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA4MTIuMDg2LDgyOC41ODYgMjguMDU1LC04Ni4zMiAzNi40ODQsLTExLjgzNiAyMzEuMjI1LDc1LjExNyAtNDEuOTcsMTI5LjE4MyAtMjMxLjIzOSwtNzUuMTQgLTIyLjU1NSwtMzEuMDA0IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNDQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDczNi4zMDEsMTMzNS44OCBjIC0zMjMuMDQ3LDAgLTU4NS44NzUsLTI2Mi43OCAtNTg1Ljg3NSwtNTg1Ljc4MiAwLC0zMjMuMTE4IDI2Mi44MjgsLTU4NS45NzcgNTg1Ljg3NSwtNTg1Ljk3NyAzMjMuMDE5LDAgNTg1LjgwOSwyNjIuODU5IDU4NS44MDksNTg1Ljk3NyAwLDMyMy4wMDIgLTI2Mi43OSw1ODUuNzgyIC01ODUuODA5LDU4NS43ODIgbCAwLDAgeiBtIDAsLTExOC42MSBjIDI1Ny45NzIsMCA0NjcuMTg5LC0yMDkuMTMgNDY3LjE4OSwtNDY3LjE3MiAwLC0yNTguMTI5IC0yMDkuMjE3LC00NjcuMzQ4IC00NjcuMTg5LC00NjcuMzQ4IC0yNTguMDc0LDAgLTQ2Ny4yNTQsMjA5LjIxOSAtNDY3LjI1NCw0NjcuMzQ4IDAsMjU4LjA0MiAyMDkuMTgsNDY3LjE3MiA0NjcuMjU0LDQ2Ny4xNzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0NiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTA5MS4xMyw2MTkuODgzIC0xNzUuNzcxLDU3LjEyMSAxMS42MjksMzUuODA4IDE3NS43NjIsLTU3LjEyMSAtMTEuNjIsLTM1LjgwOCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQ4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA4NjYuOTU3LDkwMi4wNzQgODM2LjUsOTI0LjE5OSA5NDUuMTIxLDEwNzMuNzMgOTc1LjU4NiwxMDUxLjYxIDg2Ni45NTcsOTAyLjA3NCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDUwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA2MDcuNDY1LDkwMy40NDUgNDk4Ljg1NSwxMDUyLjk3IDUyOS4zMiwxMDc1LjEgNjM3LjkzLDkyNS41NjYgNjA3LjQ2NSw5MDMuNDQ1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDM4MC42ODgsNjIyLjEyOSAtMTEuNjI2LDM1LjgwMSAxNzUuNzU4LDU3LjA5IDExLjYyMSwtMzUuODAxIC0xNzUuNzUzLC01Ny4wOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDU0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA3MTYuMjg5LDM3Ni41OSAzNy42NDA2LDAgMCwxODQuODE2IC0zNy42NDA2LDAgMCwtMTg0LjgxNiB6IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=') no-repeat, none; -webkit-background-size: 100%; -moz-background-size: 100%; -o-background-size: 100%; background-size: 100%; display: block; float: left; width: 90px; height: 25px; }
-.jasmine_html-reporter .banner .version { margin-left: 14px; position: relative; top: 6px; }
-.jasmine_html-reporter .banner .duration { position: absolute; right: 14px; top: 6px; }
-.jasmine_html-reporter #jasmine_content { position: fixed; right: 100%; }
-.jasmine_html-reporter .version { color: #aaaaaa; }
-.jasmine_html-reporter .banner { margin-top: 14px; }
-.jasmine_html-reporter .duration { color: #aaaaaa; float: right; }
-.jasmine_html-reporter .symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; }
-.jasmine_html-reporter .symbol-summary li { display: inline-block; height: 8px; width: 14px; font-size: 16px; }
-.jasmine_html-reporter .symbol-summary li.passed { font-size: 14px; }
-.jasmine_html-reporter .symbol-summary li.passed:before { color: #007069; content: "\02022"; }
-.jasmine_html-reporter .symbol-summary li.failed { line-height: 9px; }
-.jasmine_html-reporter .symbol-summary li.failed:before { color: #ca3a11; content: "\d7"; font-weight: bold; margin-left: -1px; }
-.jasmine_html-reporter .symbol-summary li.disabled { font-size: 14px; }
-.jasmine_html-reporter .symbol-summary li.disabled:before { color: #bababa; content: "\02022"; }
-.jasmine_html-reporter .symbol-summary li.pending { line-height: 17px; }
-.jasmine_html-reporter .symbol-summary li.pending:before { color: #ba9d37; content: "*"; }
-.jasmine_html-reporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
-.jasmine_html-reporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
-.jasmine_html-reporter .bar.failed { background-color: #ca3a11; }
-.jasmine_html-reporter .bar.passed { background-color: #007069; }
-.jasmine_html-reporter .bar.skipped { background-color: #bababa; }
-.jasmine_html-reporter .bar.menu { background-color: #fff; color: #aaaaaa; }
-.jasmine_html-reporter .bar.menu a { color: #333333; }
-.jasmine_html-reporter .bar a { color: white; }
-.jasmine_html-reporter.spec-list .bar.menu.failure-list, .jasmine_html-reporter.spec-list .results .failures { display: none; }
-.jasmine_html-reporter.failure-list .bar.menu.spec-list, .jasmine_html-reporter.failure-list .summary { display: none; }
-.jasmine_html-reporter .running-alert { background-color: #666666; }
-.jasmine_html-reporter .results { margin-top: 14px; }
-.jasmine_html-reporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
-.jasmine_html-reporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
-.jasmine_html-reporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
-.jasmine_html-reporter.showDetails .summary { display: none; }
-.jasmine_html-reporter.showDetails #details { display: block; }
-.jasmine_html-reporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
-.jasmine_html-reporter .summary { margin-top: 14px; }
-.jasmine_html-reporter .summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; }
-.jasmine_html-reporter .summary ul.suite { margin-top: 7px; margin-bottom: 7px; }
-.jasmine_html-reporter .summary li.passed a { color: #007069; }
-.jasmine_html-reporter .summary li.failed a { color: #ca3a11; }
-.jasmine_html-reporter .summary li.empty a { color: #ba9d37; }
-.jasmine_html-reporter .summary li.pending a { color: #ba9d37; }
-.jasmine_html-reporter .description + .suite { margin-top: 0; }
-.jasmine_html-reporter .suite { margin-top: 14px; }
-.jasmine_html-reporter .suite a { color: #333333; }
-.jasmine_html-reporter .failures .spec-detail { margin-bottom: 28px; }
-.jasmine_html-reporter .failures .spec-detail .description { background-color: #ca3a11; }
-.jasmine_html-reporter .failures .spec-detail .description a { color: white; }
-.jasmine_html-reporter .result-message { padding-top: 14px; color: #333333; white-space: pre; }
-.jasmine_html-reporter .result-message span.result { display: block; }
-.jasmine_html-reporter .stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
diff --git a/spec/lib/jasmine-2.4.1/MIT.LICENSE b/spec/lib/jasmine-2.4.1/MIT.LICENSE
new file mode 100644
index 0000000..aff8ed4
--- /dev/null
+++ b/spec/lib/jasmine-2.4.1/MIT.LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008-2014 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/spec/lib/jasmine-2.0.1/boot.js b/spec/lib/jasmine-2.4.1/boot.js
index ec8baa0..a99774d 100644
--- a/spec/lib/jasmine-2.0.1/boot.js
+++ b/spec/lib/jasmine-2.4.1/boot.js
@@ -1,5 +1,5 @@
/**
- Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project.
+ Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project.
If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms.
@@ -32,77 +32,12 @@
*
* Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
*/
- var jasmineInterface = {
- describe: function(description, specDefinitions) {
- return env.describe(description, specDefinitions);
- },
-
- xdescribe: function(description, specDefinitions) {
- return env.xdescribe(description, specDefinitions);
- },
-
- it: function(desc, func) {
- return env.it(desc, func);
- },
-
- xit: function(desc, func) {
- return env.xit(desc, func);
- },
-
- beforeEach: function(beforeEachFunction) {
- return env.beforeEach(beforeEachFunction);
- },
-
- afterEach: function(afterEachFunction) {
- return env.afterEach(afterEachFunction);
- },
-
- expect: function(actual) {
- return env.expect(actual);
- },
-
- pending: function() {
- return env.pending();
- },
-
- spyOn: function(obj, methodName) {
- return env.spyOn(obj, methodName);
- },
-
- jsApiReporter: new jasmine.JsApiReporter({
- timer: new jasmine.Timer()
- })
- };
-
- /**
- * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
- */
- if (typeof window == "undefined" && typeof exports == "object") {
- extend(exports, jasmineInterface);
- } else {
- extend(window, jasmineInterface);
- }
-
- /**
- * Expose the interface for adding custom equality testers.
- */
- jasmine.addCustomEqualityTester = function(tester) {
- env.addCustomEqualityTester(tester);
- };
+ var jasmineInterface = jasmineRequire.interface(jasmine, env);
/**
- * Expose the interface for adding custom expectation matchers
+ * Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
*/
- jasmine.addMatchers = function(matchers) {
- return env.addMatchers(matchers);
- };
-
- /**
- * Expose the mock interface for the JavaScript timeout functions
- */
- jasmine.clock = function() {
- return env.clock;
- };
+ extend(window, jasmineInterface);
/**
* ## Runner Parameters
@@ -117,13 +52,27 @@
var catchingExceptions = queryString.getParam("catch");
env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions);
+ var throwingExpectationFailures = queryString.getParam("throwFailures");
+ env.throwOnExpectationFailure(throwingExpectationFailures);
+
+ var random = queryString.getParam("random");
+ env.randomizeTests(random);
+
+ var seed = queryString.getParam("seed");
+ if (seed) {
+ env.seed(seed);
+ }
+
/**
* ## Reporters
* The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
*/
var htmlReporter = new jasmine.HtmlReporter({
env: env,
- onRaiseExceptionsClick: function() { queryString.setParam("catch", !env.catchingExceptions()); },
+ onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); },
+ onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); },
+ onRandomClick: function() { queryString.navigateWithNewParam("random", !env.randomTests()); },
+ addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); },
getContainer: function() { return document.body; },
createElement: function() { return document.createElement.apply(document, arguments); },
createTextNode: function() { return document.createTextNode.apply(document, arguments); },
diff --git a/spec/lib/jasmine-2.0.1/console.js b/spec/lib/jasmine-2.4.1/console.js
index c54f72d..e154806 100644
--- a/spec/lib/jasmine-2.0.1/console.js
+++ b/spec/lib/jasmine-2.4.1/console.js
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2008-2014 Pivotal Labs
+Copyright (c) 2008-2015 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -54,7 +54,10 @@ getJasmineRequireObj().ConsoleReporter = function() {
red: '\x1B[31m',
yellow: '\x1B[33m',
none: '\x1B[0m'
- };
+ },
+ failedSuites = [];
+
+ print('ConsoleReporter is deprecated and will be removed in a future version.');
this.jasmineStarted = function() {
specCount = 0;
@@ -89,9 +92,12 @@ getJasmineRequireObj().ConsoleReporter = function() {
printNewline();
var seconds = timer.elapsed() / 1000;
print('Finished in ' + seconds + ' ' + plural('second', seconds));
-
printNewline();
+ for(i = 0; i < failedSuites.length; i++) {
+ suiteFailureDetails(failedSuites[i]);
+ }
+
onComplete(failureCount === 0);
};
@@ -116,6 +122,13 @@ getJasmineRequireObj().ConsoleReporter = function() {
}
};
+ this.suiteDone = function(result) {
+ if (result.failedExpectations && result.failedExpectations.length > 0) {
+ failureCount++;
+ failedSuites.push(result);
+ }
+ };
+
return this;
function printNewline() {
@@ -154,11 +167,23 @@ getJasmineRequireObj().ConsoleReporter = function() {
for (var i = 0; i < result.failedExpectations.length; i++) {
var failedExpectation = result.failedExpectations[i];
printNewline();
+ print(indent(failedExpectation.message, 2));
print(indent(failedExpectation.stack, 2));
}
printNewline();
}
+
+ function suiteFailureDetails(result) {
+ for (var i = 0; i < result.failedExpectations.length; i++) {
+ printNewline();
+ print(colored('red', 'An error was thrown in an afterAll'));
+ printNewline();
+ print(colored('red', 'AfterAll ' + result.failedExpectations[i].message));
+
+ }
+ printNewline();
+ }
}
return ConsoleReporter;
diff --git a/spec/lib/jasmine-2.0.1/jasmine-html.js b/spec/lib/jasmine-2.4.1/jasmine-html.js
index 9d95903..da23532 100644
--- a/spec/lib/jasmine-2.0.1/jasmine-html.js
+++ b/spec/lib/jasmine-2.4.1/jasmine-html.js
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2008-2014 Pivotal Labs
+Copyright (c) 2008-2015 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -40,30 +40,32 @@ jasmineRequire.HtmlReporter = function(j$) {
createElement = options.createElement,
createTextNode = options.createTextNode,
onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {},
+ onThrowExpectationsClick = options.onThrowExpectationsClick || function() {},
+ onRandomClick = options.onRandomClick || function() {},
+ addToExistingQueryString = options.addToExistingQueryString || defaultQueryString,
timer = options.timer || noopTimer,
results = [],
specsExecuted = 0,
failureCount = 0,
pendingSpecCount = 0,
htmlReporterMain,
- symbols;
+ symbols,
+ failedSuites = [];
this.initialize = function() {
clearPrior();
htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'},
- createDom('div', {className: 'banner'},
- createDom('a', {className: 'title', href: 'http://jasmine.github.io/', target: '_blank'}),
- createDom('span', {className: 'version'}, j$.version)
+ createDom('div', {className: 'jasmine-banner'},
+ createDom('a', {className: 'jasmine-title', href: 'http://jasmine.github.io/', target: '_blank'}),
+ createDom('span', {className: 'jasmine-version'}, j$.version)
),
- createDom('ul', {className: 'symbol-summary'}),
- createDom('div', {className: 'alert'}),
- createDom('div', {className: 'results'},
- createDom('div', {className: 'failures'})
+ createDom('ul', {className: 'jasmine-symbol-summary'}),
+ createDom('div', {className: 'jasmine-alert'}),
+ createDom('div', {className: 'jasmine-results'},
+ createDom('div', {className: 'jasmine-failures'})
)
);
getContainer().appendChild(htmlReporterMain);
-
- symbols = find('.symbol-summary');
};
var totalSpecsDefined;
@@ -72,7 +74,7 @@ jasmineRequire.HtmlReporter = function(j$) {
timer.start();
};
- var summary = createDom('div', {className: 'summary'});
+ var summary = createDom('div', {className: 'jasmine-summary'});
var topResults = new j$.ResultsNode({}, '', null),
currentParent = topResults;
@@ -83,6 +85,10 @@ jasmineRequire.HtmlReporter = function(j$) {
};
this.suiteDone = function(result) {
+ if (result.status == 'failed') {
+ failedSuites.push(result);
+ }
+
if (currentParent == topResults) {
return;
}
@@ -96,7 +102,7 @@ jasmineRequire.HtmlReporter = function(j$) {
var failures = [];
this.specDone = function(result) {
- if(noExpectations(result) && console && console.error) {
+ if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') {
console.error('Spec \'' + result.fullName + '\' has no expectations.');
}
@@ -104,8 +110,12 @@ jasmineRequire.HtmlReporter = function(j$) {
specsExecuted++;
}
+ if (!symbols){
+ symbols = find('.jasmine-symbol-summary');
+ }
+
symbols.appendChild(createDom('li', {
- className: noExpectations(result) ? 'empty' : result.status,
+ className: noExpectations(result) ? 'jasmine-empty' : 'jasmine-' + result.status,
id: 'spec_' + result.id,
title: result.fullName
}
@@ -115,18 +125,18 @@ jasmineRequire.HtmlReporter = function(j$) {
failureCount++;
var failure =
- createDom('div', {className: 'spec-detail failed'},
- createDom('div', {className: 'description'},
+ createDom('div', {className: 'jasmine-spec-detail jasmine-failed'},
+ createDom('div', {className: 'jasmine-description'},
createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName)
),
- createDom('div', {className: 'messages'})
+ createDom('div', {className: 'jasmine-messages'})
);
var messages = failure.childNodes[1];
for (var i = 0; i < result.failedExpectations.length; i++) {
var expectation = result.failedExpectations[i];
- messages.appendChild(createDom('div', {className: 'result-message'}, expectation.message));
- messages.appendChild(createDom('div', {className: 'stack-trace'}, expectation.stack));
+ messages.appendChild(createDom('div', {className: 'jasmine-result-message'}, expectation.message));
+ messages.appendChild(createDom('div', {className: 'jasmine-stack-trace'}, expectation.stack));
}
failures.push(failure);
@@ -137,48 +147,106 @@ jasmineRequire.HtmlReporter = function(j$) {
}
};
- this.jasmineDone = function() {
- var banner = find('.banner');
- banner.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
+ this.jasmineDone = function(doneResult) {
+ var banner = find('.jasmine-banner');
+ var alert = find('.jasmine-alert');
+ var order = doneResult && doneResult.order;
+ alert.appendChild(createDom('span', {className: 'jasmine-duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
+
+ banner.appendChild(
+ createDom('div', { className: 'jasmine-run-options' },
+ createDom('span', { className: 'jasmine-trigger' }, 'Options'),
+ createDom('div', { className: 'jasmine-payload' },
+ createDom('div', { className: 'jasmine-exceptions' },
+ createDom('input', {
+ className: 'jasmine-raise',
+ id: 'jasmine-raise-exceptions',
+ type: 'checkbox'
+ }),
+ createDom('label', { className: 'jasmine-label', 'for': 'jasmine-raise-exceptions' }, 'raise exceptions')),
+ createDom('div', { className: 'jasmine-throw-failures' },
+ createDom('input', {
+ className: 'jasmine-throw',
+ id: 'jasmine-throw-failures',
+ type: 'checkbox'
+ }),
+ createDom('label', { className: 'jasmine-label', 'for': 'jasmine-throw-failures' }, 'stop spec on expectation failure')),
+ createDom('div', { className: 'jasmine-random-order' },
+ createDom('input', {
+ className: 'jasmine-random',
+ id: 'jasmine-random-order',
+ type: 'checkbox'
+ }),
+ createDom('label', { className: 'jasmine-label', 'for': 'jasmine-random-order' }, 'run tests in random order'))
+ )
+ ));
+
+ var raiseCheckbox = find('#jasmine-raise-exceptions');
- var alert = find('.alert');
+ raiseCheckbox.checked = !env.catchingExceptions();
+ raiseCheckbox.onclick = onRaiseExceptionsClick;
- alert.appendChild(createDom('span', { className: 'exceptions' },
- createDom('label', { className: 'label', 'for': 'raise-exceptions' }, 'raise exceptions'),
- createDom('input', {
- className: 'raise',
- id: 'raise-exceptions',
- type: 'checkbox'
- })
- ));
- var checkbox = find('#raise-exceptions');
+ var throwCheckbox = find('#jasmine-throw-failures');
+ throwCheckbox.checked = env.throwingExpectationFailures();
+ throwCheckbox.onclick = onThrowExpectationsClick;
- checkbox.checked = !env.catchingExceptions();
- checkbox.onclick = onRaiseExceptionsClick;
+ var randomCheckbox = find('#jasmine-random-order');
+ randomCheckbox.checked = env.randomTests();
+ randomCheckbox.onclick = onRandomClick;
+
+ var optionsMenu = find('.jasmine-run-options'),
+ optionsTrigger = optionsMenu.querySelector('.jasmine-trigger'),
+ optionsPayload = optionsMenu.querySelector('.jasmine-payload'),
+ isOpen = /\bjasmine-open\b/;
+
+ optionsTrigger.onclick = function() {
+ if (isOpen.test(optionsPayload.className)) {
+ optionsPayload.className = optionsPayload.className.replace(isOpen, '');
+ } else {
+ optionsPayload.className += ' jasmine-open';
+ }
+ };
if (specsExecuted < totalSpecsDefined) {
var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all';
alert.appendChild(
- createDom('span', {className: 'bar skipped'},
+ createDom('span', {className: 'jasmine-bar jasmine-skipped'},
createDom('a', {href: '?', title: 'Run all specs'}, skippedMessage)
)
);
}
var statusBarMessage = '';
- var statusBarClassName = 'bar ';
+ var statusBarClassName = 'jasmine-bar ';
if (totalSpecsDefined > 0) {
statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount);
if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); }
- statusBarClassName += (failureCount > 0) ? 'failed' : 'passed';
+ statusBarClassName += (failureCount > 0) ? 'jasmine-failed' : 'jasmine-passed';
} else {
- statusBarClassName += 'skipped';
+ statusBarClassName += 'jasmine-skipped';
statusBarMessage += 'No specs found';
}
- alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage));
+ var seedBar;
+ if (order && order.random) {
+ seedBar = createDom('span', {className: 'jasmine-seed-bar'},
+ ', randomized with seed ',
+ createDom('a', {title: 'randomized with seed ' + order.seed, href: seedHref(order.seed)}, order.seed)
+ );
+ }
+
+ alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage, seedBar));
- var results = find('.results');
+ for(i = 0; i < failedSuites.length; i++) {
+ var failedSuite = failedSuites[i];
+ for(var j = 0; j < failedSuite.failedExpectations.length; j++) {
+ var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message;
+ var errorBarClassName = 'jasmine-bar jasmine-errored';
+ alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage));
+ }
+ }
+
+ var results = find('.jasmine-results');
results.appendChild(summary);
summaryList(topResults, summary);
@@ -188,8 +256,8 @@ jasmineRequire.HtmlReporter = function(j$) {
for (var i = 0; i < resultsTree.children.length; i++) {
var resultNode = resultsTree.children[i];
if (resultNode.type == 'suite') {
- var suiteListNode = createDom('ul', {className: 'suite', id: 'suite-' + resultNode.result.id},
- createDom('li', {className: 'suite-detail'},
+ var suiteListNode = createDom('ul', {className: 'jasmine-suite', id: 'suite-' + resultNode.result.id},
+ createDom('li', {className: 'jasmine-suite-detail'},
createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description)
)
);
@@ -198,17 +266,20 @@ jasmineRequire.HtmlReporter = function(j$) {
domParent.appendChild(suiteListNode);
}
if (resultNode.type == 'spec') {
- if (domParent.getAttribute('class') != 'specs') {
- specListNode = createDom('ul', {className: 'specs'});
+ if (domParent.getAttribute('class') != 'jasmine-specs') {
+ specListNode = createDom('ul', {className: 'jasmine-specs'});
domParent.appendChild(specListNode);
}
var specDescription = resultNode.result.description;
if(noExpectations(resultNode.result)) {
specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
}
+ if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') {
+ specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
+ }
specListNode.appendChild(
createDom('li', {
- className: resultNode.result.status,
+ className: 'jasmine-' + resultNode.result.status,
id: 'spec-' + resultNode.result.id
},
createDom('a', {href: specHref(resultNode.result)}, specDescription)
@@ -220,24 +291,24 @@ jasmineRequire.HtmlReporter = function(j$) {
if (failures.length) {
alert.appendChild(
- createDom('span', {className: 'menu bar spec-list'},
+ createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-spec-list'},
createDom('span', {}, 'Spec List | '),
- createDom('a', {className: 'failures-menu', href: '#'}, 'Failures')));
+ createDom('a', {className: 'jasmine-failures-menu', href: '#'}, 'Failures')));
alert.appendChild(
- createDom('span', {className: 'menu bar failure-list'},
- createDom('a', {className: 'spec-list-menu', href: '#'}, 'Spec List'),
+ createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-failure-list'},
+ createDom('a', {className: 'jasmine-spec-list-menu', href: '#'}, 'Spec List'),
createDom('span', {}, ' | Failures ')));
- find('.failures-menu').onclick = function() {
- setMenuModeTo('failure-list');
+ find('.jasmine-failures-menu').onclick = function() {
+ setMenuModeTo('jasmine-failure-list');
};
- find('.spec-list-menu').onclick = function() {
- setMenuModeTo('spec-list');
+ find('.jasmine-spec-list-menu').onclick = function() {
+ setMenuModeTo('jasmine-spec-list');
};
- setMenuModeTo('failure-list');
+ setMenuModeTo('jasmine-failure-list');
- var failureNode = find('.failures');
+ var failureNode = find('.jasmine-failures');
for (var i = 0; i < failures.length; i++) {
failureNode.appendChild(failures[i]);
}
@@ -253,7 +324,7 @@ jasmineRequire.HtmlReporter = function(j$) {
function clearPrior() {
// return the reporter
var oldReporter = find('');
-
+
if(oldReporter) {
getContainer().removeChild(oldReporter);
}
@@ -292,7 +363,15 @@ jasmineRequire.HtmlReporter = function(j$) {
}
function specHref(result) {
- return '?spec=' + encodeURIComponent(result.fullName);
+ return addToExistingQueryString('spec', result.fullName);
+ }
+
+ function seedHref(seed) {
+ return addToExistingQueryString('seed', seed);
+ }
+
+ function defaultQueryString(key, value) {
+ return '?' + key + '=' + value;
}
function setMenuModeTo(mode) {
@@ -344,10 +423,14 @@ jasmineRequire.ResultsNode = function() {
jasmineRequire.QueryString = function() {
function QueryString(options) {
- this.setParam = function(key, value) {
+ this.navigateWithNewParam = function(key, value) {
+ options.getWindowLocation().search = this.fullStringWithNewParam(key, value);
+ };
+
+ this.fullStringWithNewParam = function(key, value) {
var paramMap = queryStringToParamMap();
paramMap[key] = value;
- options.getWindowLocation().search = toQueryString(paramMap);
+ return toQueryString(paramMap);
};
this.getParam = function(key) {
diff --git a/spec/lib/jasmine-2.4.1/jasmine.css b/spec/lib/jasmine-2.4.1/jasmine.css
new file mode 100644
index 0000000..6319982
--- /dev/null
+++ b/spec/lib/jasmine-2.4.1/jasmine.css
@@ -0,0 +1,58 @@
+body { overflow-y: scroll; }
+
+.jasmine_html-reporter { background-color: #eee; padding: 5px; margin: -8px; font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333; }
+.jasmine_html-reporter a { text-decoration: none; }
+.jasmine_html-reporter a:hover { text-decoration: underline; }
+.jasmine_html-reporter p, .jasmine_html-reporter h1, .jasmine_html-reporter h2, .jasmine_html-reporter h3, .jasmine_html-reporter h4, .jasmine_html-reporter h5, .jasmine_html-reporter h6 { margin: 0; line-height: 14px; }
+.jasmine_html-reporter .jasmine-banner, .jasmine_html-reporter .jasmine-symbol-summary, .jasmine_html-reporter .jasmine-summary, .jasmine_html-reporter .jasmine-result-message, .jasmine_html-reporter .jasmine-spec .jasmine-description, .jasmine_html-reporter .jasmine-spec-detail .jasmine-description, .jasmine_html-reporter .jasmine-alert .jasmine-bar, .jasmine_html-reporter .jasmine-stack-trace { padding-left: 9px; padding-right: 9px; }
+.jasmine_html-reporter .jasmine-banner { position: relative; }
+.jasmine_html-reporter .jasmine-banner .jasmine-title { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAZCAMAAACGusnyAAACdlBMVEX/////AP+AgICqVaqAQICZM5mAVYCSSZKAQICOOY6ATYCLRouAQICJO4mSSYCIRIiPQICHPIeOR4CGQ4aMQICGPYaLRoCFQ4WKQICPPYWJRYCOQoSJQICNPoSIRICMQoSHQICHRICKQoOHQICKPoOJO4OJQYOMQICMQ4CIQYKLQICIPoKLQ4CKQICNPoKJQISMQ4KJQoSLQYKJQISLQ4KIQoSKQYKIQICIQISMQoSKQYKLQIOLQoOJQYGLQIOKQIOMQoGKQYOLQYGKQIOLQoGJQYOJQIOKQYGJQIOKQoGKQIGLQIKLQ4KKQoGLQYKJQIGKQYKJQIGKQIKJQoGKQYKLQIGKQYKLQIOJQoKKQoOJQYKKQIOJQoKKQoOKQIOLQoKKQYOLQYKJQIOKQoKKQYKKQoKJQYOKQYKLQIOKQoKLQYOKQYKLQIOJQoGKQYKJQYGJQoGKQYKLQoGLQYGKQoGJQYKKQYGJQIKKQoGJQYKLQIKKQYGLQYKKQYGKQYGKQYKJQYOKQoKJQYOKQYKLQYOLQYOKQYKLQYOKQoKKQYKKQYOKQYOJQYKKQYKLQYKKQIKKQoKKQYKKQYKKQoKJQIKKQYKLQYKKQYKKQIKKQYKKQYKKQYKKQIKKQYKJQYGLQYGKQYKKQYKKQYGKQIKKQYGKQYOJQoKKQYOLQYKKQYOKQoKKQYKKQoKKQYKKQYKJQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKJQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKmIDpEAAAA0XRSTlMAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAiIyQlJycoKissLS4wMTQ1Njc4OTo7PDw+P0BCQ0RISUpLTE1OUFNUVVdYWFlaW15fYGFiY2ZnaGlqa2xtb3BxcnN0dnh5ent8fX5/gIGChIWIioyNjo+QkZOUlZaYmZqbnJ2eoKGio6WmqKmsra6vsLGztre4ubq7vL2+wMHDxMjJysvNzs/Q0dLU1tfY2dvc3t/g4eLj5ebn6Onq6+zt7u/w8vP09fb3+Pn6+/z9/vkVQXAAAAMaSURBVHhe5dXxV1N1GMfxz2ABbDgIAm5VDJOyVDIJLUMaVpBWUZUaGbmqoGpZRSiGiRWp6KoZ5AB0ZY50RImZQIlahKkMYXv/R90dBvET/rJfOr3Ouc8v99zPec59zvf56j+vYKlViSf7250X4Mr3O29Tgq08BdGB4DhcekEJ5YkQKFsgWZdtj9JpV+I8xPjLFqkrsEIqO8PHSpis36jWazcqjEsfJjkvRssVU37SdIOu4XCf5vEJPsnwJpnRNU9JmxhMk8l1gehIrq7hTFjzOD+Vf88629qKMJVNltInFeRexRQyJlNeqd1iGDlSzrIUIyXbyFfm3RYprcQRe7lqtWyGYbfc6dT0R2vmdOOkX3u55C1rP37ftiH+tDby4r/RBT0w8TyEkr+epB9XgPDmSYYWbrhCuFYaIyw3fDQAXTnSkh+ANofiHmWf9l+FY1I90FdQTetstO00o23novzVsJ7uB3/C5TkbjRwZ5JerwV4iRWq9HFbFMaK/d0TYqayRiQPuIxxS3Bu8JWU90/60tKi7vkhaznez0a/TbVOKj5CaOZh6fWG6/Lyv9B/ZLR1gw/S/fpbeVD3MCW1li6SvWDOn65tr99/uvWtBS0XDm4s1t+sOHpG0kpBKx/l77wOSnxLpcx6TXmXLTPQOKYOf9Q1dfr8/SJ2mFdCvl1Yl93DiHUZvXeLJbGSzYu5gVJ2slbSakOR8dxCq5adQ2oFLqsE9Ex3L4qQO0eOPeU5x56bypXp4onSEb5OkICX6lDat55TeoztNKQcJaakrz9KCb95oD69IKq+yKW4XPjknaS52V0TZqE2cTtXjcHSCRmUO88e+85hj3EP74i9p8pylw7lxgMDyyl6OV7ZejnjNMfatu87LxRbH0IS35gt2a4ZjmGpVBdKK3Wr6INk8jWWSGqbA55CKgjBRC6E9w78ydTg3ABS3AFV1QN0Y4Aa2pgEjWnQURj9L0ayK6R2ysEqxHUKzYnLvvyU+i9KM2JHJzE4vyZOyDcOwOsySajeLPc8sNvPJkFlyJd20wpqAzZeAfZ3oWybxd+P/3j+SG3uSBdf2VQAAAABJRU5ErkJggg==') no-repeat; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNjgxLjk2MjUyIgogICBoZWlnaHQ9IjE4Ny41IgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhOCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczYiPjxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXBQYXRoMTgiPjxwYXRoCiAgICAgICAgIGQ9Ik0gMCwxNTAwIDAsMCBsIDU0NTUuNzQsMCAwLDE1MDAgTCAwLDE1MDAgeiIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgaWQ9InBhdGgyMCIgLz48L2NsaXBQYXRoPjwvZGVmcz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUsMCwwLC0xLjI1LDAsMTg3LjUpIgogICAgIGlkPSJnMTAiPjxnCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDAuMSwwLjEpIgogICAgICAgaWQ9ImcxMiI+PGcKICAgICAgICAgaWQ9ImcxNCI+PGcKICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgxOCkiCiAgICAgICAgICAgaWQ9ImcxNiI+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTU0NCw1OTkuNDM0IGMgMC45MiwtNDAuMzUyIDI1LjY4LC04MS42MDIgNzEuNTMsLTgxLjYwMiAyNy41MSwwIDQ3LjY4LDEyLjgzMiA2MS40NCwzNS43NTQgMTIuODMsMjIuOTMgMTIuODMsNTYuODUyIDEyLjgzLDgyLjUyNyBsIDAsMzI5LjE4NCAtNzEuNTIsMCAwLDEwNC41NDMgMjY2LjgzLDAgMCwtMTA0LjU0MyAtNzAuNiwwIDAsLTM0NC43NyBjIDAsLTU4LjY5MSAtMy42OCwtMTA0LjUzMSAtNDQuOTMsLTE1Mi4yMTggLTM2LjY4LC00Mi4xOCAtOTYuMjgsLTY2LjAyIC0xNTMuMTQsLTY2LjAyIC0xMTcuMzcsMCAtMjA3LjI0LDc3Ljk0MSAtMjAyLjY0LDE5Ny4xNDUgbCAxMzAuMiwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMjIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDIzMDEuNCw2NjIuNjk1IGMgMCw4MC43MDMgLTY2Ljk0LDE0NS44MTMgLTE0Ny42MywxNDUuODEzIC04My40NCwwIC0xNDcuNjMsLTY4Ljc4MSAtMTQ3LjYzLC0xNTEuMzAxIDAsLTc5Ljc4NSA2Ni45NCwtMTQ1LjgwMSAxNDUuOCwtMTQ1LjgwMSA4NC4zNSwwIDE0OS40Niw2Ny44NTIgMTQ5LjQ2LDE1MS4yODkgeiBtIC0xLjgzLC0xODEuNTQ3IGMgLTM1Ljc3LC01NC4wOTcgLTkzLjUzLC03OC44NTkgLTE1Ny43MiwtNzguODU5IC0xNDAuMywwIC0yNTEuMjQsMTE2LjQ0OSAtMjUxLjI0LDI1NC45MTggMCwxNDIuMTI5IDExMy43LDI2MC40MSAyNTYuNzQsMjYwLjQxIDYzLjI3LDAgMTE4LjI5LC0yOS4zMzYgMTUyLjIyLC04Mi41MjMgbCAwLDY5LjY4NyAxNzUuMTQsMCAwLC0xMDQuNTI3IC02MS40NCwwIDAsLTI4MC41OTggNjEuNDQsMCAwLC0xMDQuNTI3IC0xNzUuMTQsMCAwLDY2LjAxOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAyNjIyLjMzLDU1Ny4yNTggYyAzLjY3LC00NC4wMTYgMzMuMDEsLTczLjM0OCA3OC44NiwtNzMuMzQ4IDMzLjkzLDAgNjYuOTMsMjMuODI0IDY2LjkzLDYwLjUwNCAwLDQ4LjYwNiAtNDUuODQsNTYuODU2IC04My40NCw2Ni45NDEgLTg1LjI4LDIyLjAwNCAtMTc4LjgxLDQ4LjYwNiAtMTc4LjgxLDE1NS44NzkgMCw5My41MzYgNzguODYsMTQ3LjYzMyAxNjUuOTgsMTQ3LjYzMyA0NCwwIDgzLjQzLC05LjE3NiAxMTAuOTQsLTQ0LjAwOCBsIDAsMzMuOTIyIDgyLjUzLDAgMCwtMTMyLjk2NSAtMTA4LjIxLDAgYyAtMS44MywzNC44NTYgLTI4LjQyLDU3Ljc3NCAtNjMuMjYsNTcuNzc0IC0zMC4yNiwwIC02Mi4zNSwtMTcuNDIyIC02Mi4zNSwtNTEuMzQ4IDAsLTQ1Ljg0NyA0NC45MywtNTUuOTMgODAuNjksLTY0LjE4IDg4LjAyLC0yMC4xNzUgMTgyLjQ3LC00Ny42OTUgMTgyLjQ3LC0xNTcuNzM0IDAsLTk5LjAyNyAtODMuNDQsLTE1NC4wMzkgLTE3NS4xMywtMTU0LjAzOSAtNDkuNTMsMCAtOTQuNDYsMTUuNTgyIC0xMjYuNTUsNTMuMTggbCAwLC00MC4zNCAtODUuMjcsMCAwLDE0Mi4xMjkgMTE0LjYyLDAiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGgyNiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMjk4OC4xOCw4MDAuMjU0IC02My4yNiwwIDAsMTA0LjUyNyAxNjUuMDUsMCAwLC03My4zNTUgYyAzMS4xOCw1MS4zNDcgNzguODYsODUuMjc3IDE0MS4yMSw4NS4yNzcgNjcuODUsMCAxMjQuNzEsLTQxLjI1OCAxNTIuMjEsLTEwMi42OTkgMjYuNiw2Mi4zNTEgOTIuNjIsMTAyLjY5OSAxNjAuNDcsMTAyLjY5OSA1My4xOSwwIDEwNS40NiwtMjIgMTQxLjIxLC02Mi4zNTEgMzguNTIsLTQ0LjkzOCAzOC41MiwtOTMuNTMyIDM4LjUyLC0xNDkuNDU3IGwgMCwtMTg1LjIzOSA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40MiwwIDAsMTA0LjUyNyA2My4yOCwwIDAsMTU3LjcxNSBjIDAsMzIuMTAyIDAsNjAuNTI3IC0xNC42Nyw4OC45NTcgLTE4LjM0LDI2LjU4MiAtNDguNjEsNDAuMzQ0IC03OS43Nyw0MC4zNDQgLTMwLjI2LDAgLTYzLjI4LC0xMi44NDQgLTgyLjUzLC0zNi42NzIgLTIyLjkzLC0yOS4zNTUgLTIyLjkzLC01Ni44NjMgLTIyLjkzLC05Mi42MjkgbCAwLC0xNTcuNzE1IDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM4LjQxLDAgMCwxMDQuNTI3IDYzLjI4LDAgMCwxNTAuMzgzIGMgMCwyOS4zNDggMCw2Ni4wMjMgLTE0LjY3LDkxLjY5OSAtMTUuNTksMjkuMzM2IC00Ny42OSw0NC45MzQgLTgwLjcsNDQuOTM0IC0zMS4xOCwwIC01Ny43NywtMTEuMDA4IC03Ny45NCwtMzUuNzc0IC0yNC43NywtMzAuMjUzIC0yNi42LC02Mi4zNDMgLTI2LjYsLTk5Ljk0MSBsIDAsLTE1MS4zMDEgNjMuMjcsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNiwwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAzOTk4LjY2LDk1MS41NDcgLTExMS44NywwIDAsMTE4LjI5MyAxMTEuODcsMCAwLC0xMTguMjkzIHogbSAwLC00MzEuODkxIDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM5LjMzLDAgMCwxMDQuNTI3IDY0LjE5LDAgMCwyODAuNTk4IC02My4yNywwIDAsMTA0LjUyNyAxNzUuMTQsMCAwLC0zODUuMTI1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDQxNTkuMTIsODAwLjI1NCAtNjMuMjcsMCAwLDEwNC41MjcgMTc1LjE0LDAgMCwtNjkuNjg3IGMgMjkuMzUsNTQuMTAxIDg0LjM2LDgwLjY5OSAxNDQuODcsODAuNjk5IDUzLjE5LDAgMTA1LjQ1LC0yMi4wMTYgMTQxLjIyLC02MC41MjcgNDAuMzQsLTQ0LjkzNCA0MS4yNiwtODguMDMyIDQxLjI2LC0xNDMuOTU3IGwgMCwtMTkxLjY1MyA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40LDAgMCwxMDQuNTI3IDYzLjI2LDAgMCwxNTguNjM3IGMgMCwzMC4yNjIgMCw2MS40MzQgLTE5LjI2LDg4LjAzNSAtMjAuMTcsMjYuNTgyIC01My4xOCwzOS40MTQgLTg2LjE5LDM5LjQxNCAtMzMuOTMsMCAtNjguNzcsLTEzLjc1IC04OC45NCwtNDEuMjUgLTIxLjA5LC0yNy41IC0yMS4wOSwtNjkuNjg3IC0yMS4wOSwtMTAyLjcwNyBsIDAsLTE0Mi4xMjkgNjMuMjYsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNywwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDMyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA1MDgyLjQ4LDcwMy45NjUgYyAtMTkuMjQsNzAuNjA1IC04MS42LDExNS41NDcgLTE1NC4wNCwxMTUuNTQ3IC02Ni4wNCwwIC0xMjkuMywtNTEuMzQ4IC0xNDMuMDUsLTExNS41NDcgbCAyOTcuMDksMCB6IG0gODUuMjcsLTE0NC44ODMgYyAtMzguNTEsLTkzLjUyMyAtMTI5LjI3LC0xNTYuNzkzIC0yMzEuMDUsLTE1Ni43OTMgLTE0My4wNywwIC0yNTcuNjgsMTExLjg3MSAtMjU3LjY4LDI1NS44MzYgMCwxNDQuODgzIDEwOS4xMiwyNjEuMzI4IDI1NC45MSwyNjEuMzI4IDY3Ljg3LDAgMTM1LjcyLC0zMC4yNTggMTgzLjM5LC03OC44NjMgNDguNjIsLTUxLjM0NCA2OC43OSwtMTEzLjY5NSA2OC43OSwtMTgzLjM4MyBsIC0zLjY3LC0zOS40MzQgLTM5Ni4xMywwIGMgMTQuNjcsLTY3Ljg2MyA3Ny4wMywtMTE3LjM2MyAxNDYuNzIsLTExNy4zNjMgNDguNTksMCA5MC43NiwxOC4zMjggMTE4LjI4LDU4LjY3MiBsIDExNi40NCwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDY5MC44OTUsODUwLjcwMyA5MC43NSwwIDIyLjU0MywzMS4wMzUgMCwyNDMuMTIyIC0xMzUuODI5LDAgMCwtMjQzLjE0MSAyMi41MzYsLTMxLjAxNiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDM2IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA2MzIuMzk1LDc0Mi4yNTggMjguMDM5LDg2LjMwNCAtMjIuNTUxLDMxLjA0IC0yMzEuMjIzLDc1LjEyOCAtNDEuOTc2LC0xMjkuMTgzIDIzMS4yNTcsLTc1LjEzNyAzNi40NTQsMTEuODQ4IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzgiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDcxNy40NDksNjUzLjEwNSAtNzMuNDEsNTMuMzYgLTM2LjQ4OCwtMTEuODc1IC0xNDIuOTAzLC0xOTYuNjkyIDEwOS44ODMsLTc5LjgyOCAxNDIuOTE4LDE5Ni43MDMgMCwzOC4zMzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0MCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gODI4LjUyLDcwNi40NjUgLTczLjQyNiwtNTMuMzQgMC4wMTEsLTM4LjM1OSBMIDg5OC4wMDQsNDE4LjA3IDEwMDcuOSw0OTcuODk4IDg2NC45NzMsNjk0LjYwOSA4MjguNTIsNzA2LjQ2NSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA4MTIuMDg2LDgyOC41ODYgMjguMDU1LC04Ni4zMiAzNi40ODQsLTExLjgzNiAyMzEuMjI1LDc1LjExNyAtNDEuOTcsMTI5LjE4MyAtMjMxLjIzOSwtNzUuMTQgLTIyLjU1NSwtMzEuMDA0IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNDQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDczNi4zMDEsMTMzNS44OCBjIC0zMjMuMDQ3LDAgLTU4NS44NzUsLTI2Mi43OCAtNTg1Ljg3NSwtNTg1Ljc4MiAwLC0zMjMuMTE4IDI2Mi44MjgsLTU4NS45NzcgNTg1Ljg3NSwtNTg1Ljk3NyAzMjMuMDE5LDAgNTg1LjgwOSwyNjIuODU5IDU4NS44MDksNTg1Ljk3NyAwLDMyMy4wMDIgLTI2Mi43OSw1ODUuNzgyIC01ODUuODA5LDU4NS43ODIgbCAwLDAgeiBtIDAsLTExOC42MSBjIDI1Ny45NzIsMCA0NjcuMTg5LC0yMDkuMTMgNDY3LjE4OSwtNDY3LjE3MiAwLC0yNTguMTI5IC0yMDkuMjE3LC00NjcuMzQ4IC00NjcuMTg5LC00NjcuMzQ4IC0yNTguMDc0LDAgLTQ2Ny4yNTQsMjA5LjIxOSAtNDY3LjI1NCw0NjcuMzQ4IDAsMjU4LjA0MiAyMDkuMTgsNDY3LjE3MiA0NjcuMjU0LDQ2Ny4xNzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0NiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTA5MS4xMyw2MTkuODgzIC0xNzUuNzcxLDU3LjEyMSAxMS42MjksMzUuODA4IDE3NS43NjIsLTU3LjEyMSAtMTEuNjIsLTM1LjgwOCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQ4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA4NjYuOTU3LDkwMi4wNzQgODM2LjUsOTI0LjE5OSA5NDUuMTIxLDEwNzMuNzMgOTc1LjU4NiwxMDUxLjYxIDg2Ni45NTcsOTAyLjA3NCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDUwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA2MDcuNDY1LDkwMy40NDUgNDk4Ljg1NSwxMDUyLjk3IDUyOS4zMiwxMDc1LjEgNjM3LjkzLDkyNS41NjYgNjA3LjQ2NSw5MDMuNDQ1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDM4MC42ODgsNjIyLjEyOSAtMTEuNjI2LDM1LjgwMSAxNzUuNzU4LDU3LjA5IDExLjYyMSwtMzUuODAxIC0xNzUuNzUzLC01Ny4wOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDU0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA3MTYuMjg5LDM3Ni41OSAzNy42NDA2LDAgMCwxODQuODE2IC0zNy42NDA2LDAgMCwtMTg0LjgxNiB6IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=') no-repeat, none; -moz-background-size: 100%; -o-background-size: 100%; -webkit-background-size: 100%; background-size: 100%; display: block; float: left; width: 90px; height: 25px; }
+.jasmine_html-reporter .jasmine-banner .jasmine-version { margin-left: 14px; position: relative; top: 6px; }
+.jasmine_html-reporter #jasmine_content { position: fixed; right: 100%; }
+.jasmine_html-reporter .jasmine-version { color: #aaa; }
+.jasmine_html-reporter .jasmine-banner { margin-top: 14px; }
+.jasmine_html-reporter .jasmine-duration { color: #fff; float: right; line-height: 28px; padding-right: 9px; }
+.jasmine_html-reporter .jasmine-symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; }
+.jasmine_html-reporter .jasmine-symbol-summary li { display: inline-block; height: 10px; width: 14px; font-size: 16px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-passed { font-size: 14px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-passed:before { color: #007069; content: "\02022"; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-failed { line-height: 9px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-failed:before { color: #ca3a11; content: "\d7"; font-weight: bold; margin-left: -1px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-disabled { font-size: 14px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-disabled:before { color: #bababa; content: "\02022"; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-pending { line-height: 17px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-pending:before { color: #ba9d37; content: "*"; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-empty { font-size: 14px; }
+.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-empty:before { color: #ba9d37; content: "\02022"; }
+.jasmine_html-reporter .jasmine-run-options { float: right; margin-right: 5px; border: 1px solid #8a4182; color: #8a4182; position: relative; line-height: 20px; }
+.jasmine_html-reporter .jasmine-run-options .jasmine-trigger { cursor: pointer; padding: 8px 16px; }
+.jasmine_html-reporter .jasmine-run-options .jasmine-payload { position: absolute; display: none; right: -1px; border: 1px solid #8a4182; background-color: #eee; white-space: nowrap; padding: 4px 8px; }
+.jasmine_html-reporter .jasmine-run-options .jasmine-payload.jasmine-open { display: block; }
+.jasmine_html-reporter .jasmine-bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
+.jasmine_html-reporter .jasmine-bar.jasmine-failed { background-color: #ca3a11; }
+.jasmine_html-reporter .jasmine-bar.jasmine-passed { background-color: #007069; }
+.jasmine_html-reporter .jasmine-bar.jasmine-skipped { background-color: #bababa; }
+.jasmine_html-reporter .jasmine-bar.jasmine-errored { background-color: #ca3a11; }
+.jasmine_html-reporter .jasmine-bar.jasmine-menu { background-color: #fff; color: #aaa; }
+.jasmine_html-reporter .jasmine-bar.jasmine-menu a { color: #333; }
+.jasmine_html-reporter .jasmine-bar a { color: white; }
+.jasmine_html-reporter.jasmine-spec-list .jasmine-bar.jasmine-menu.jasmine-failure-list, .jasmine_html-reporter.jasmine-spec-list .jasmine-results .jasmine-failures { display: none; }
+.jasmine_html-reporter.jasmine-failure-list .jasmine-bar.jasmine-menu.jasmine-spec-list, .jasmine_html-reporter.jasmine-failure-list .jasmine-summary { display: none; }
+.jasmine_html-reporter .jasmine-results { margin-top: 14px; }
+.jasmine_html-reporter .jasmine-summary { margin-top: 14px; }
+.jasmine_html-reporter .jasmine-summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; }
+.jasmine_html-reporter .jasmine-summary ul.jasmine-suite { margin-top: 7px; margin-bottom: 7px; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-passed a { color: #007069; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-failed a { color: #ca3a11; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-empty a { color: #ba9d37; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-pending a { color: #ba9d37; }
+.jasmine_html-reporter .jasmine-summary li.jasmine-disabled a { color: #bababa; }
+.jasmine_html-reporter .jasmine-description + .jasmine-suite { margin-top: 0; }
+.jasmine_html-reporter .jasmine-suite { margin-top: 14px; }
+.jasmine_html-reporter .jasmine-suite a { color: #333; }
+.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail { margin-bottom: 28px; }
+.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description { background-color: #ca3a11; }
+.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description a { color: white; }
+.jasmine_html-reporter .jasmine-result-message { padding-top: 14px; color: #333; white-space: pre; }
+.jasmine_html-reporter .jasmine-result-message span.jasmine-result { display: block; }
+.jasmine_html-reporter .jasmine-stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666; border: 1px solid #ddd; background: white; white-space: pre; }
diff --git a/spec/lib/jasmine-2.0.1/jasmine.js b/spec/lib/jasmine-2.4.1/jasmine.js
index c943db1..bea469d 100644
--- a/spec/lib/jasmine-2.0.1/jasmine.js
+++ b/spec/lib/jasmine-2.4.1/jasmine.js
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2008-2014 Pivotal Labs
+Copyright (c) 2008-2015 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -20,45 +20,67 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
-function getJasmineRequireObj() {
+var getJasmineRequireObj = (function (jasmineGlobal) {
+ var jasmineRequire;
+
if (typeof module !== 'undefined' && module.exports) {
- return exports;
+ if (typeof global !== 'undefined') {
+ jasmineGlobal = global;
+ } else {
+ jasmineGlobal = {};
+ }
+ jasmineRequire = exports;
} else {
- window.jasmineRequire = window.jasmineRequire || {};
- return window.jasmineRequire;
+ if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]') {
+ jasmineGlobal = window;
+ }
+ jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {};
}
-}
-getJasmineRequireObj().core = function(jRequire) {
- var j$ = {};
-
- jRequire.base(j$);
- j$.util = jRequire.util();
- j$.Any = jRequire.Any();
- j$.CallTracker = jRequire.CallTracker();
- j$.MockDate = jRequire.MockDate();
- j$.Clock = jRequire.Clock();
- j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler();
- j$.Env = jRequire.Env(j$);
- j$.ExceptionFormatter = jRequire.ExceptionFormatter();
- j$.Expectation = jRequire.Expectation();
- j$.buildExpectationResult = jRequire.buildExpectationResult();
- j$.JsApiReporter = jRequire.JsApiReporter();
- j$.matchersUtil = jRequire.matchersUtil(j$);
- j$.ObjectContaining = jRequire.ObjectContaining(j$);
- j$.pp = jRequire.pp(j$);
- j$.QueueRunner = jRequire.QueueRunner(j$);
- j$.ReportDispatcher = jRequire.ReportDispatcher();
- j$.Spec = jRequire.Spec(j$);
- j$.SpyStrategy = jRequire.SpyStrategy();
- j$.Suite = jRequire.Suite();
- j$.Timer = jRequire.Timer();
- j$.version = jRequire.version();
-
- j$.matchers = jRequire.requireMatchers(jRequire, j$);
-
- return j$;
-};
+ function getJasmineRequire() {
+ return jasmineRequire;
+ }
+
+ getJasmineRequire().core = function(jRequire) {
+ var j$ = {};
+
+ jRequire.base(j$, jasmineGlobal);
+ j$.util = jRequire.util();
+ j$.errors = jRequire.errors();
+ j$.Any = jRequire.Any(j$);
+ j$.Anything = jRequire.Anything(j$);
+ j$.CallTracker = jRequire.CallTracker();
+ j$.MockDate = jRequire.MockDate();
+ j$.Clock = jRequire.Clock();
+ j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler();
+ j$.Env = jRequire.Env(j$);
+ j$.ExceptionFormatter = jRequire.ExceptionFormatter();
+ j$.Expectation = jRequire.Expectation();
+ j$.buildExpectationResult = jRequire.buildExpectationResult();
+ j$.JsApiReporter = jRequire.JsApiReporter();
+ j$.matchersUtil = jRequire.matchersUtil(j$);
+ j$.ObjectContaining = jRequire.ObjectContaining(j$);
+ j$.ArrayContaining = jRequire.ArrayContaining(j$);
+ j$.pp = jRequire.pp(j$);
+ j$.QueueRunner = jRequire.QueueRunner(j$);
+ j$.ReportDispatcher = jRequire.ReportDispatcher();
+ j$.Spec = jRequire.Spec(j$);
+ j$.SpyRegistry = jRequire.SpyRegistry(j$);
+ j$.SpyStrategy = jRequire.SpyStrategy();
+ j$.StringMatching = jRequire.StringMatching(j$);
+ j$.Suite = jRequire.Suite(j$);
+ j$.Timer = jRequire.Timer();
+ j$.TreeProcessor = jRequire.TreeProcessor();
+ j$.version = jRequire.version();
+ j$.Order = jRequire.Order();
+
+ j$.matchers = jRequire.requireMatchers(jRequire, j$);
+
+ return j$;
+ };
+
+ return getJasmineRequire;
+})(this);
getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
var availableMatchers = [
@@ -76,6 +98,7 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
'toEqual',
'toHaveBeenCalled',
'toHaveBeenCalledWith',
+ 'toHaveBeenCalledTimes',
'toMatch',
'toThrow',
'toThrowError'
@@ -90,108 +113,128 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) {
return matchers;
};
-getJasmineRequireObj().base = (function (jasmineGlobal) {
- if (typeof module !== 'undefined' && module.exports) {
- jasmineGlobal = global;
- }
+getJasmineRequireObj().base = function(j$, jasmineGlobal) {
+ j$.unimplementedMethod_ = function() {
+ throw new Error('unimplemented method');
+ };
- return function(j$) {
- j$.unimplementedMethod_ = function() {
- throw new Error('unimplemented method');
- };
+ j$.MAX_PRETTY_PRINT_DEPTH = 40;
+ j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100;
+ j$.DEFAULT_TIMEOUT_INTERVAL = 5000;
- j$.MAX_PRETTY_PRINT_DEPTH = 40;
- j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100;
- j$.DEFAULT_TIMEOUT_INTERVAL = 5000;
+ j$.getGlobal = function() {
+ return jasmineGlobal;
+ };
- j$.getGlobal = function() {
- return jasmineGlobal;
- };
+ j$.getEnv = function(options) {
+ var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options);
+ //jasmine. singletons in here (setTimeout blah blah).
+ return env;
+ };
- j$.getEnv = function(options) {
- var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options);
- //jasmine. singletons in here (setTimeout blah blah).
- return env;
- };
+ j$.isArray_ = function(value) {
+ return j$.isA_('Array', value);
+ };
- j$.isArray_ = function(value) {
- return j$.isA_('Array', value);
- };
+ j$.isString_ = function(value) {
+ return j$.isA_('String', value);
+ };
- j$.isString_ = function(value) {
- return j$.isA_('String', value);
- };
+ j$.isNumber_ = function(value) {
+ return j$.isA_('Number', value);
+ };
- j$.isNumber_ = function(value) {
- return j$.isA_('Number', value);
- };
+ j$.isA_ = function(typeName, value) {
+ return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+ };
- j$.isA_ = function(typeName, value) {
- return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
- };
+ j$.isDomNode = function(obj) {
+ return obj.nodeType > 0;
+ };
- j$.isDomNode = function(obj) {
- return obj.nodeType > 0;
- };
+ j$.fnNameFor = function(func) {
+ return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1];
+ };
- j$.any = function(clazz) {
- return new j$.Any(clazz);
- };
+ j$.any = function(clazz) {
+ return new j$.Any(clazz);
+ };
- j$.objectContaining = function(sample) {
- return new j$.ObjectContaining(sample);
- };
+ j$.anything = function() {
+ return new j$.Anything();
+ };
- j$.createSpy = function(name, originalFn) {
+ j$.objectContaining = function(sample) {
+ return new j$.ObjectContaining(sample);
+ };
- var spyStrategy = new j$.SpyStrategy({
- name: name,
- fn: originalFn,
- getSpy: function() { return spy; }
- }),
- callTracker = new j$.CallTracker(),
- spy = function() {
- callTracker.track({
- object: this,
- args: Array.prototype.slice.apply(arguments)
- });
- return spyStrategy.exec.apply(this, arguments);
+ j$.stringMatching = function(expected) {
+ return new j$.StringMatching(expected);
+ };
+
+ j$.arrayContaining = function(sample) {
+ return new j$.ArrayContaining(sample);
+ };
+
+ j$.createSpy = function(name, originalFn) {
+
+ var spyStrategy = new j$.SpyStrategy({
+ name: name,
+ fn: originalFn,
+ getSpy: function() { return spy; }
+ }),
+ callTracker = new j$.CallTracker(),
+ spy = function() {
+ var callData = {
+ object: this,
+ args: Array.prototype.slice.apply(arguments)
};
- for (var prop in originalFn) {
- if (prop === 'and' || prop === 'calls') {
- throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon');
- }
+ callTracker.track(callData);
+ var returnValue = spyStrategy.exec.apply(this, arguments);
+ callData.returnValue = returnValue;
+
+ return returnValue;
+ };
- spy[prop] = originalFn[prop];
+ for (var prop in originalFn) {
+ if (prop === 'and' || prop === 'calls') {
+ throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon');
}
- spy.and = spyStrategy;
- spy.calls = callTracker;
+ spy[prop] = originalFn[prop];
+ }
- return spy;
- };
+ spy.and = spyStrategy;
+ spy.calls = callTracker;
- j$.isSpy = function(putativeSpy) {
- if (!putativeSpy) {
- return false;
- }
- return putativeSpy.and instanceof j$.SpyStrategy &&
- putativeSpy.calls instanceof j$.CallTracker;
- };
+ return spy;
+ };
- j$.createSpyObj = function(baseName, methodNames) {
- if (!j$.isArray_(methodNames) || methodNames.length === 0) {
- throw 'createSpyObj requires a non-empty array of method names to create spies for';
- }
- var obj = {};
- for (var i = 0; i < methodNames.length; i++) {
- obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]);
- }
- return obj;
- };
+ j$.isSpy = function(putativeSpy) {
+ if (!putativeSpy) {
+ return false;
+ }
+ return putativeSpy.and instanceof j$.SpyStrategy &&
+ putativeSpy.calls instanceof j$.CallTracker;
};
-})(this);
+
+ j$.createSpyObj = function(baseName, methodNames) {
+ if (j$.isArray_(baseName) && j$.util.isUndefined(methodNames)) {
+ methodNames = baseName;
+ baseName = 'unknown';
+ }
+
+ if (!j$.isArray_(methodNames) || methodNames.length === 0) {
+ throw 'createSpyObj requires a non-empty array of method names to create spies for';
+ }
+ var obj = {};
+ for (var i = 0; i < methodNames.length; i++) {
+ obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]);
+ }
+ return obj;
+ };
+};
getJasmineRequireObj().util = function() {
@@ -228,13 +271,28 @@ getJasmineRequireObj().util = function() {
util.arrayContains = function(array, search) {
var i = array.length;
while (i--) {
- if (array[i] == search) {
+ if (array[i] === search) {
return true;
}
}
return false;
};
+ util.clone = function(obj) {
+ if (Object.prototype.toString.apply(obj) === '[object Array]') {
+ return obj.slice();
+ }
+
+ var cloned = {};
+ for (var prop in obj) {
+ if (obj.hasOwnProperty(prop)) {
+ cloned[prop] = obj[prop];
+ }
+ }
+
+ return cloned;
+ };
+
return util;
};
@@ -244,17 +302,17 @@ getJasmineRequireObj().Spec = function(j$) {
this.resultCallback = attrs.resultCallback || function() {};
this.id = attrs.id;
this.description = attrs.description || '';
- this.fn = attrs.fn;
- this.beforeFns = attrs.beforeFns || function() { return []; };
- this.afterFns = attrs.afterFns || function() { return []; };
+ this.queueableFn = attrs.queueableFn;
+ this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; };
+ this.userContext = attrs.userContext || function() { return {}; };
this.onStart = attrs.onStart || function() {};
- this.exceptionFormatter = attrs.exceptionFormatter || function() {};
this.getSpecName = attrs.getSpecName || function() { return ''; };
this.expectationResultFactory = attrs.expectationResultFactory || function() { };
this.queueRunnerFactory = attrs.queueRunnerFactory || function() {};
this.catchingExceptions = attrs.catchingExceptions || function() { return true; };
+ this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
- if (!this.fn) {
+ if (!this.queueableFn.fn) {
this.pend();
}
@@ -263,16 +321,21 @@ getJasmineRequireObj().Spec = function(j$) {
description: this.description,
fullName: this.getFullName(),
failedExpectations: [],
- passedExpectations: []
+ passedExpectations: [],
+ pendingReason: ''
};
}
- Spec.prototype.addExpectationResult = function(passed, data) {
+ Spec.prototype.addExpectationResult = function(passed, data, isError) {
var expectationResult = this.expectationResultFactory(data);
if (passed) {
this.result.passedExpectations.push(expectationResult);
} else {
this.result.failedExpectations.push(expectationResult);
+
+ if (this.throwOnExpectationFailure && !isError) {
+ throw new j$.errors.ExpectationFailed();
+ }
}
};
@@ -280,42 +343,28 @@ getJasmineRequireObj().Spec = function(j$) {
return this.expectationFactory(actual, this);
};
- Spec.prototype.execute = function(onComplete) {
+ Spec.prototype.execute = function(onComplete, enabled) {
var self = this;
this.onStart(this);
- if (this.markedPending || this.disabled) {
- complete();
+ if (!this.isExecutable() || this.markedPending || enabled === false) {
+ complete(enabled);
return;
}
- var allFns = this.beforeFns().concat(this.fn).concat(this.afterFns());
+ var fns = this.beforeAndAfterFns();
+ var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters);
this.queueRunnerFactory({
- fns: allFns,
- onException: onException,
+ queueableFns: allFns,
+ onException: function() { self.onException.apply(self, arguments); },
onComplete: complete,
- enforceTimeout: function() { return true; }
+ userContext: this.userContext()
});
- function onException(e) {
- if (Spec.isPendingSpecException(e)) {
- self.pend();
- return;
- }
-
- self.addExpectationResult(false, {
- matcherName: '',
- passed: false,
- expected: '',
- actual: '',
- error: e
- });
- }
-
- function complete() {
- self.result.status = self.status();
+ function complete(enabledAgain) {
+ self.result.status = self.status(enabledAgain);
self.resultCallback(self.result);
if (onComplete) {
@@ -324,16 +373,43 @@ getJasmineRequireObj().Spec = function(j$) {
}
};
+ Spec.prototype.onException = function onException(e) {
+ if (Spec.isPendingSpecException(e)) {
+ this.pend(extractCustomPendingMessage(e));
+ return;
+ }
+
+ if (e instanceof j$.errors.ExpectationFailed) {
+ return;
+ }
+
+ this.addExpectationResult(false, {
+ matcherName: '',
+ passed: false,
+ expected: '',
+ actual: '',
+ error: e
+ }, true);
+ };
+
Spec.prototype.disable = function() {
this.disabled = true;
};
- Spec.prototype.pend = function() {
+ Spec.prototype.pend = function(message) {
this.markedPending = true;
+ if (message) {
+ this.result.pendingReason = message;
+ }
};
- Spec.prototype.status = function() {
- if (this.disabled) {
+ Spec.prototype.getResult = function() {
+ this.result.status = this.status();
+ return this.result;
+ };
+
+ Spec.prototype.status = function(enabled) {
+ if (this.disabled || enabled === false) {
return 'disabled';
}
@@ -348,10 +424,22 @@ getJasmineRequireObj().Spec = function(j$) {
}
};
+ Spec.prototype.isExecutable = function() {
+ return !this.disabled;
+ };
+
Spec.prototype.getFullName = function() {
return this.getSpecName(this);
};
+ var extractCustomPendingMessage = function(e) {
+ var fullMessage = e.toString(),
+ boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage),
+ boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length;
+
+ return fullMessage.substr(boilerplateEnd);
+ };
+
Spec.pendingSpecExceptionMessage = '=> marked Pending';
Spec.isPendingSpecException = function(e) {
@@ -365,6 +453,53 @@ if (typeof window == void 0 && typeof exports == 'object') {
exports.Spec = jasmineRequire.Spec;
}
+/*jshint bitwise: false*/
+
+getJasmineRequireObj().Order = function() {
+ function Order(options) {
+ this.random = 'random' in options ? options.random : true;
+ var seed = this.seed = options.seed || generateSeed();
+ this.sort = this.random ? randomOrder : naturalOrder;
+
+ function naturalOrder(items) {
+ return items;
+ }
+
+ function randomOrder(items) {
+ var copy = items.slice();
+ copy.sort(function(a, b) {
+ return jenkinsHash(seed + a.id) - jenkinsHash(seed + b.id);
+ });
+ return copy;
+ }
+
+ function generateSeed() {
+ return String(Math.random()).slice(-5);
+ }
+
+ // Bob Jenkins One-at-a-Time Hash algorithm is a non-cryptographic hash function
+ // used to get a different output when the key changes slighly.
+ // We use your return to sort the children randomly in a consistent way when
+ // used in conjunction with a seed
+
+ function jenkinsHash(key) {
+ var hash, i;
+ for(hash = i = 0; i < key.length; ++i) {
+ hash += key.charCodeAt(i);
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+ return hash;
+ }
+
+ }
+
+ return Order;
+};
+
getJasmineRequireObj().Env = function(j$) {
function Env(options) {
options = options || {};
@@ -378,14 +513,25 @@ getJasmineRequireObj().Env = function(j$) {
var realSetTimeout = j$.getGlobal().setTimeout;
var realClearTimeout = j$.getGlobal().clearTimeout;
- this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler(), new j$.MockDate(global));
+ this.clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global));
var runnableLookupTable = {};
-
- var spies = [];
+ var runnableResources = {};
var currentSpec = null;
- var currentSuite = null;
+ var currentlyExecutingSuites = [];
+ var currentDeclarationSuite = null;
+ var throwOnExpectationFailure = false;
+ var random = false;
+ var seed = null;
+
+ var currentSuite = function() {
+ return currentlyExecutingSuites[currentlyExecutingSuites.length - 1];
+ };
+
+ var currentRunnable = function() {
+ return currentSpec || currentSuite();
+ };
var reporter = new j$.ReportDispatcher([
'jasmineStarted',
@@ -400,11 +546,21 @@ getJasmineRequireObj().Env = function(j$) {
return true;
};
- var equalityTesters = [];
-
- var customEqualityTesters = [];
this.addCustomEqualityTester = function(tester) {
- customEqualityTesters.push(tester);
+ if(!currentRunnable()) {
+ throw new Error('Custom Equalities must be added in a before function or a spec');
+ }
+ runnableResources[currentRunnable().id].customEqualityTesters.push(tester);
+ };
+
+ this.addMatchers = function(matchersToAdd) {
+ if(!currentRunnable()) {
+ throw new Error('Matchers must be added in a before function or a spec');
+ }
+ var customMatchers = runnableResources[currentRunnable().id].customMatchers;
+ for (var matcherName in matchersToAdd) {
+ customMatchers[matcherName] = matchersToAdd[matcherName];
+ }
};
j$.Expectation.addCoreMatchers(j$.matchers);
@@ -422,7 +578,8 @@ getJasmineRequireObj().Env = function(j$) {
var expectationFactory = function(actual, spec) {
return j$.Expectation.Factory({
util: j$.matchersUtil,
- customEqualityTesters: customEqualityTesters,
+ customEqualityTesters: runnableResources[spec.id].customEqualityTesters,
+ customMatchers: runnableResources[spec.id].customMatchers,
actual: actual,
addExpectationResult: addExpectationResult
});
@@ -432,30 +589,38 @@ getJasmineRequireObj().Env = function(j$) {
}
};
- var specStarted = function(spec) {
- currentSpec = spec;
- reporter.specStarted(spec.result);
+ var defaultResourcesForRunnable = function(id, parentRunnableId) {
+ var resources = {spies: [], customEqualityTesters: [], customMatchers: {}};
+
+ if(runnableResources[parentRunnableId]){
+ resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters);
+ resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers);
+ }
+
+ runnableResources[id] = resources;
};
- var beforeFns = function(suite) {
- return function() {
- var befores = [];
- while(suite) {
- befores = befores.concat(suite.beforeFns);
- suite = suite.parentSuite;
- }
- return befores.reverse();
- };
+ var clearResourcesForRunnable = function(id) {
+ spyRegistry.clearSpies();
+ delete runnableResources[id];
};
- var afterFns = function(suite) {
+ var beforeAndAfterFns = function(suite) {
return function() {
- var afters = [];
+ var befores = [],
+ afters = [];
+
while(suite) {
+ befores = befores.concat(suite.beforeFns);
afters = afters.concat(suite.afterFns);
+
suite = suite.parentSuite;
}
- return afters;
+
+ return {
+ befores: befores.reverse(),
+ afters: afters
+ };
};
};
@@ -500,10 +665,34 @@ getJasmineRequireObj().Env = function(j$) {
return j$.Spec.isPendingSpecException(e) || catchExceptions;
};
+ this.throwOnExpectationFailure = function(value) {
+ throwOnExpectationFailure = !!value;
+ };
+
+ this.throwingExpectationFailures = function() {
+ return throwOnExpectationFailure;
+ };
+
+ this.randomizeTests = function(value) {
+ random = !!value;
+ };
+
+ this.randomTests = function() {
+ return random;
+ };
+
+ this.seed = function(value) {
+ if (value) {
+ seed = value;
+ }
+ return seed;
+ };
+
var queueRunnerFactory = function(options) {
options.catchException = catchException;
options.clearStack = options.clearStack || clearStack;
- options.timer = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout};
+ options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout};
+ options.fail = self.fail;
new j$.QueueRunner(options).execute();
};
@@ -512,66 +701,79 @@ getJasmineRequireObj().Env = function(j$) {
env: this,
id: getNextSuiteId(),
description: 'Jasmine__TopLevel__Suite',
- queueRunner: queueRunnerFactory,
- resultCallback: function() {} // TODO - hook this up
+ queueRunner: queueRunnerFactory
});
runnableLookupTable[topSuite.id] = topSuite;
- currentSuite = topSuite;
+ defaultResourcesForRunnable(topSuite.id);
+ currentDeclarationSuite = topSuite;
this.topSuite = function() {
return topSuite;
};
this.execute = function(runnablesToRun) {
- runnablesToRun = runnablesToRun || [topSuite.id];
+ if(!runnablesToRun) {
+ if (focusedRunnables.length) {
+ runnablesToRun = focusedRunnables;
+ } else {
+ runnablesToRun = [topSuite.id];
+ }
+ }
+
+ var order = new j$.Order({
+ random: random,
+ seed: seed
+ });
- var allFns = [];
- for(var i = 0; i < runnablesToRun.length; i++) {
- var runnable = runnableLookupTable[runnablesToRun[i]];
- allFns.push((function(runnable) { return function(done) { runnable.execute(done); }; })(runnable));
+ var processor = new j$.TreeProcessor({
+ tree: topSuite,
+ runnableIds: runnablesToRun,
+ queueRunnerFactory: queueRunnerFactory,
+ nodeStart: function(suite) {
+ currentlyExecutingSuites.push(suite);
+ defaultResourcesForRunnable(suite.id, suite.parentSuite.id);
+ reporter.suiteStarted(suite.result);
+ },
+ nodeComplete: function(suite, result) {
+ if (!suite.disabled) {
+ clearResourcesForRunnable(suite.id);
+ }
+ currentlyExecutingSuites.pop();
+ reporter.suiteDone(result);
+ },
+ orderChildren: function(node) {
+ return order.sort(node.children);
+ }
+ });
+
+ if(!processor.processTree().valid) {
+ throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times');
}
reporter.jasmineStarted({
totalSpecsDefined: totalSpecsDefined
});
- queueRunnerFactory({fns: allFns, onComplete: reporter.jasmineDone});
+ processor.execute(function() {
+ reporter.jasmineDone({
+ order: order
+ });
+ });
};
this.addReporter = function(reporterToAdd) {
reporter.addReporter(reporterToAdd);
};
- this.addMatchers = function(matchersToAdd) {
- j$.Expectation.addMatchers(matchersToAdd);
- };
-
- this.spyOn = function(obj, methodName) {
- if (j$.util.isUndefined(obj)) {
- throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()');
- }
-
- if (j$.util.isUndefined(obj[methodName])) {
- throw new Error(methodName + '() method does not exist');
- }
-
- if (obj[methodName] && j$.isSpy(obj[methodName])) {
- //TODO?: should this return the current spy? Downside: may cause user confusion about spy state
- throw new Error(methodName + ' has already been spied upon');
+ var spyRegistry = new j$.SpyRegistry({currentSpies: function() {
+ if(!currentRunnable()) {
+ throw new Error('Spies must be created in a before function or a spec');
}
+ return runnableResources[currentRunnable().id].spies;
+ }});
- var spy = j$.createSpy(methodName, obj[methodName]);
-
- spies.push({
- spy: spy,
- baseObj: obj,
- methodName: methodName,
- originalValue: obj[methodName]
- });
-
- obj[methodName] = spy;
-
- return spy;
+ this.spyOn = function() {
+ return spyRegistry.spyOn.apply(spyRegistry, arguments);
};
var suiteFactory = function(description) {
@@ -579,12 +781,10 @@ getJasmineRequireObj().Env = function(j$) {
env: self,
id: getNextSuiteId(),
description: description,
- parentSuite: currentSuite,
- queueRunner: queueRunnerFactory,
- onStart: suiteStarted,
- resultCallback: function(attrs) {
- reporter.suiteDone(attrs);
- }
+ parentSuite: currentDeclarationSuite,
+ expectationFactory: expectationFactory,
+ expectationResultFactory: expectationResultFactory,
+ throwOnExpectationFailure: throwOnExpectationFailure
});
runnableLookupTable[suite.id] = suite;
@@ -593,10 +793,40 @@ getJasmineRequireObj().Env = function(j$) {
this.describe = function(description, specDefinitions) {
var suite = suiteFactory(description);
+ if (specDefinitions.length > 0) {
+ throw new Error('describe does not expect a done parameter');
+ }
+ if (currentDeclarationSuite.markedPending) {
+ suite.pend();
+ }
+ addSpecsToSuite(suite, specDefinitions);
+ return suite;
+ };
- var parentSuite = currentSuite;
+ this.xdescribe = function(description, specDefinitions) {
+ var suite = suiteFactory(description);
+ suite.pend();
+ addSpecsToSuite(suite, specDefinitions);
+ return suite;
+ };
+
+ var focusedRunnables = [];
+
+ this.fdescribe = function(description, specDefinitions) {
+ var suite = suiteFactory(description);
+ suite.isFocused = true;
+
+ focusedRunnables.push(suite.id);
+ unfocusAncestor();
+ addSpecsToSuite(suite, specDefinitions);
+
+ return suite;
+ };
+
+ function addSpecsToSuite(suite, specDefinitions) {
+ var parentSuite = currentDeclarationSuite;
parentSuite.addChild(suite);
- currentSuite = suite;
+ currentDeclarationSuite = suite;
var declarationError = null;
try {
@@ -606,31 +836,43 @@ getJasmineRequireObj().Env = function(j$) {
}
if (declarationError) {
- this.it('encountered a declaration exception', function() {
+ self.it('encountered a declaration exception', function() {
throw declarationError;
});
}
- currentSuite = parentSuite;
+ currentDeclarationSuite = parentSuite;
+ }
- return suite;
- };
+ function findFocusedAncestor(suite) {
+ while (suite) {
+ if (suite.isFocused) {
+ return suite.id;
+ }
+ suite = suite.parentSuite;
+ }
- this.xdescribe = function(description, specDefinitions) {
- var suite = this.describe(description, specDefinitions);
- suite.disable();
- return suite;
- };
+ return null;
+ }
- var specFactory = function(description, fn, suite) {
- totalSpecsDefined++;
+ function unfocusAncestor() {
+ var focusedAncestor = findFocusedAncestor(currentDeclarationSuite);
+ if (focusedAncestor) {
+ for (var i = 0; i < focusedRunnables.length; i++) {
+ if (focusedRunnables[i] === focusedAncestor) {
+ focusedRunnables.splice(i, 1);
+ break;
+ }
+ }
+ }
+ }
+ var specFactory = function(description, fn, suite, timeout) {
+ totalSpecsDefined++;
var spec = new j$.Spec({
id: getNextSpecId(),
- beforeFns: beforeFns(suite),
- afterFns: afterFns(suite),
+ beforeAndAfterFns: beforeAndAfterFns(suite),
expectationFactory: expectationFactory,
- exceptionFormatter: exceptionFormatter,
resultCallback: specResultCallback,
getSpecName: function(spec) {
return getSpecName(spec, suite);
@@ -639,7 +881,12 @@ getJasmineRequireObj().Env = function(j$) {
description: description,
expectationResultFactory: expectationResultFactory,
queueRunnerFactory: queueRunnerFactory,
- fn: fn
+ userContext: function() { return suite.clonedSharedUserContext(); },
+ queueableFn: {
+ fn: fn,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ },
+ throwOnExpectationFailure: throwOnExpectationFailure
});
runnableLookupTable[spec.id] = spec;
@@ -650,57 +897,101 @@ getJasmineRequireObj().Env = function(j$) {
return spec;
- function removeAllSpies() {
- for (var i = 0; i < spies.length; i++) {
- var spyEntry = spies[i];
- spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue;
- }
- spies = [];
- }
-
function specResultCallback(result) {
- removeAllSpies();
- j$.Expectation.resetMatchers();
- customEqualityTesters = [];
+ clearResourcesForRunnable(spec.id);
currentSpec = null;
reporter.specDone(result);
}
+
+ function specStarted(spec) {
+ currentSpec = spec;
+ defaultResourcesForRunnable(spec.id, suite.id);
+ reporter.specStarted(spec.result);
+ }
};
- var suiteStarted = function(suite) {
- reporter.suiteStarted(suite.result);
+ this.it = function(description, fn, timeout) {
+ var spec = specFactory(description, fn, currentDeclarationSuite, timeout);
+ if (currentDeclarationSuite.markedPending) {
+ spec.pend();
+ }
+ currentDeclarationSuite.addChild(spec);
+ return spec;
};
- this.it = function(description, fn) {
- var spec = specFactory(description, fn, currentSuite);
- currentSuite.addChild(spec);
+ this.xit = function() {
+ var spec = this.it.apply(this, arguments);
+ spec.pend('Temporarily disabled with xit');
return spec;
};
- this.xit = function(description, fn) {
- var spec = this.it(description, fn);
- spec.pend();
+ this.fit = function(description, fn, timeout){
+ var spec = specFactory(description, fn, currentDeclarationSuite, timeout);
+ currentDeclarationSuite.addChild(spec);
+ focusedRunnables.push(spec.id);
+ unfocusAncestor();
return spec;
};
this.expect = function(actual) {
- if (!currentSpec) {
+ if (!currentRunnable()) {
throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out');
}
- return currentSpec.expect(actual);
+ return currentRunnable().expect(actual);
};
- this.beforeEach = function(beforeEachFunction) {
- currentSuite.beforeEach(beforeEachFunction);
+ this.beforeEach = function(beforeEachFunction, timeout) {
+ currentDeclarationSuite.beforeEach({
+ fn: beforeEachFunction,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ });
};
- this.afterEach = function(afterEachFunction) {
- currentSuite.afterEach(afterEachFunction);
+ this.beforeAll = function(beforeAllFunction, timeout) {
+ currentDeclarationSuite.beforeAll({
+ fn: beforeAllFunction,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ });
+ };
+
+ this.afterEach = function(afterEachFunction, timeout) {
+ currentDeclarationSuite.afterEach({
+ fn: afterEachFunction,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ });
+ };
+
+ this.afterAll = function(afterAllFunction, timeout) {
+ currentDeclarationSuite.afterAll({
+ fn: afterAllFunction,
+ timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
+ });
+ };
+
+ this.pending = function(message) {
+ var fullMessage = j$.Spec.pendingSpecExceptionMessage;
+ if(message) {
+ fullMessage += message;
+ }
+ throw fullMessage;
};
- this.pending = function() {
- throw j$.Spec.pendingSpecExceptionMessage;
+ this.fail = function(error) {
+ var message = 'Failed';
+ if (error) {
+ message += ': ';
+ message += error.message || error;
+ }
+
+ currentRunnable().addExpectationResult(false, {
+ matcherName: '',
+ passed: false,
+ expected: '',
+ actual: '',
+ message: message,
+ error: error && error.message ? error : null
+ });
};
}
@@ -720,6 +1011,7 @@ getJasmineRequireObj().JsApiReporter = function() {
this.started = false;
this.finished = false;
+ this.runDetails = {};
this.jasmineStarted = function() {
this.started = true;
@@ -729,8 +1021,9 @@ getJasmineRequireObj().JsApiReporter = function() {
var executionTime;
- this.jasmineDone = function() {
+ this.jasmineDone = function(runDetails) {
this.finished = true;
+ this.runDetails = runDetails;
executionTime = timer.elapsed();
status = 'done';
};
@@ -739,26 +1032,31 @@ getJasmineRequireObj().JsApiReporter = function() {
return status;
};
- var suites = {};
+ var suites = [],
+ suites_hash = {};
this.suiteStarted = function(result) {
- storeSuite(result);
+ suites_hash[result.id] = result;
};
this.suiteDone = function(result) {
storeSuite(result);
};
+ this.suiteResults = function(index, length) {
+ return suites.slice(index, index + length);
+ };
+
function storeSuite(result) {
- suites[result.id] = result;
+ suites.push(result);
+ suites_hash[result.id] = result;
}
this.suites = function() {
- return suites;
+ return suites_hash;
};
var specs = [];
- this.specStarted = function(result) { };
this.specDone = function(result) {
specs.push(result);
@@ -781,43 +1079,6 @@ getJasmineRequireObj().JsApiReporter = function() {
return JsApiReporter;
};
-getJasmineRequireObj().Any = function() {
-
- function Any(expectedObject) {
- this.expectedObject = expectedObject;
- }
-
- Any.prototype.jasmineMatches = function(other) {
- if (this.expectedObject == String) {
- return typeof other == 'string' || other instanceof String;
- }
-
- if (this.expectedObject == Number) {
- return typeof other == 'number' || other instanceof Number;
- }
-
- if (this.expectedObject == Function) {
- return typeof other == 'function' || other instanceof Function;
- }
-
- if (this.expectedObject == Object) {
- return typeof other == 'object';
- }
-
- if (this.expectedObject == Boolean) {
- return typeof other == 'boolean';
- }
-
- return other instanceof this.expectedObject;
- };
-
- Any.prototype.jasmineToString = function() {
- return '<jasmine.any(' + this.expectedObject + ')>';
- };
-
- return Any;
-};
-
getJasmineRequireObj().CallTracker = function() {
function CallTracker() {
@@ -870,7 +1131,7 @@ getJasmineRequireObj().CallTracker = function() {
};
getJasmineRequireObj().Clock = function() {
- function Clock(global, delayedFunctionScheduler, mockDate) {
+ function Clock(global, delayedFunctionSchedulerFactory, mockDate) {
var self = this,
realTimingFunctions = {
setTimeout: global.setTimeout,
@@ -885,19 +1146,24 @@ getJasmineRequireObj().Clock = function() {
clearInterval: clearInterval
},
installed = false,
+ delayedFunctionScheduler,
timer;
self.install = function() {
+ if(!originalTimingFunctionsIntact()) {
+ throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?');
+ }
replace(global, fakeTimingFunctions);
timer = fakeTimingFunctions;
+ delayedFunctionScheduler = delayedFunctionSchedulerFactory();
installed = true;
return self;
};
self.uninstall = function() {
- delayedFunctionScheduler.reset();
+ delayedFunctionScheduler = null;
mockDate.uninstall();
replace(global, realTimingFunctions);
@@ -905,6 +1171,15 @@ getJasmineRequireObj().Clock = function() {
installed = false;
};
+ self.withMock = function(closure) {
+ this.install();
+ try {
+ closure();
+ } finally {
+ this.uninstall();
+ }
+ };
+
self.mockDate = function(initialDate) {
mockDate.install(initialDate);
};
@@ -948,6 +1223,13 @@ getJasmineRequireObj().Clock = function() {
return self;
+ function originalTimingFunctionsIntact() {
+ return global.setTimeout === realTimingFunctions.setTimeout &&
+ global.clearTimeout === realTimingFunctions.clearTimeout &&
+ global.setInterval === realTimingFunctions.setInterval &&
+ global.clearInterval === realTimingFunctions.clearInterval;
+ }
+
function legacyIE() {
//if these methods are polyfilled, apply will be present
return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply;
@@ -1057,13 +1339,6 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() {
}
};
- self.reset = function() {
- currentTime = 0;
- scheduledLookup = [];
- scheduledFunctions = {};
- delayedFnCount = 0;
- };
-
return self;
function indexOfFirstToPass(array, testFn) {
@@ -1099,6 +1374,12 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() {
scheduledFn.runAtMillis + scheduledFn.millis);
}
+ function forEachFunction(funcsToRun, callback) {
+ for (var i = 0; i < funcsToRun.length; ++i) {
+ callback(funcsToRun[i]);
+ }
+ }
+
function runScheduledFunctions(endTime) {
if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) {
return;
@@ -1110,14 +1391,15 @@ getJasmineRequireObj().DelayedFunctionScheduler = function() {
var funcsToRun = scheduledFunctions[currentTime];
delete scheduledFunctions[currentTime];
- for (var i = 0; i < funcsToRun.length; ++i) {
- var funcToRun = funcsToRun[i];
- funcToRun.funcToCall.apply(null, funcToRun.params || []);
-
+ forEachFunction(funcsToRun, function(funcToRun) {
if (funcToRun.recurring) {
reschedule(funcToRun);
}
- }
+ });
+
+ forEachFunction(funcsToRun, function(funcToRun) {
+ funcToRun.funcToCall.apply(null, funcToRun.params || []);
+ });
} while (scheduledLookup.length > 0 &&
// checking first if we're out of time prevents setTimeout(0)
// scheduled in a funcToRun from forcing an extra iteration
@@ -1161,8 +1443,6 @@ getJasmineRequireObj().ExceptionFormatter = function() {
getJasmineRequireObj().Expectation = function() {
- var matchers = {};
-
function Expectation(options) {
this.util = options.util || { buildFailureMessage: function() {} };
this.customEqualityTesters = options.customEqualityTesters || [];
@@ -1170,8 +1450,9 @@ getJasmineRequireObj().Expectation = function() {
this.addExpectationResult = options.addExpectationResult || function(){};
this.isNot = options.isNot;
- for (var matcherName in matchers) {
- this[matcherName] = matchers[matcherName];
+ var customMatchers = options.customMatchers || {};
+ for (var matcherName in customMatchers) {
+ this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]);
}
}
@@ -1238,19 +1519,6 @@ getJasmineRequireObj().Expectation = function() {
}
};
- Expectation.addMatchers = function(matchersToAdd) {
- for (var name in matchersToAdd) {
- var matcher = matchersToAdd[name];
- matchers[name] = Expectation.prototype.wrapCompare(name, matcher);
- }
- };
-
- Expectation.resetMatchers = function() {
- for (var name in matchers) {
- delete matchers[name];
- }
- };
-
Expectation.Factory = function(options) {
options = options || {};
@@ -1273,15 +1541,20 @@ getJasmineRequireObj().buildExpectationResult = function() {
var messageFormatter = options.messageFormatter || function() {},
stackFormatter = options.stackFormatter || function() {};
- return {
+ var result = {
matcherName: options.matcherName,
- expected: options.expected,
- actual: options.actual,
message: message(),
stack: stack(),
passed: options.passed
};
+ if(!result.passed) {
+ result.expected = options.expected;
+ result.actual = options.actual;
+ }
+
+ return result;
+
function message() {
if (options.passed) {
return 'Passed.';
@@ -1352,15 +1625,31 @@ getJasmineRequireObj().MockDate = function() {
return self;
function FakeDate() {
- if (arguments.length === 0) {
- return new GlobalDate(currentTime);
- } else {
- return new GlobalDate(arguments[0], arguments[1], arguments[2],
- arguments[3], arguments[4], arguments[5], arguments[6]);
+ switch(arguments.length) {
+ case 0:
+ return new GlobalDate(currentTime);
+ case 1:
+ return new GlobalDate(arguments[0]);
+ case 2:
+ return new GlobalDate(arguments[0], arguments[1]);
+ case 3:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2]);
+ case 4:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]);
+ case 5:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+ arguments[4]);
+ case 6:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+ arguments[4], arguments[5]);
+ default:
+ return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3],
+ arguments[4], arguments[5], arguments[6]);
}
}
function createDateProperties() {
+ FakeDate.prototype = GlobalDate.prototype;
FakeDate.now = function() {
if (GlobalDate.now) {
@@ -1380,41 +1669,6 @@ getJasmineRequireObj().MockDate = function() {
return MockDate;
};
-getJasmineRequireObj().ObjectContaining = function(j$) {
-
- function ObjectContaining(sample) {
- this.sample = sample;
- }
-
- ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
- if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); }
-
- mismatchKeys = mismatchKeys || [];
- mismatchValues = mismatchValues || [];
-
- var hasKey = function(obj, keyName) {
- return obj !== null && !j$.util.isUndefined(obj[keyName]);
- };
-
- for (var property in this.sample) {
- if (!hasKey(other, property) && hasKey(this.sample, property)) {
- mismatchKeys.push('expected has key \'' + property + '\', but missing from actual.');
- }
- else if (!j$.matchersUtil.equals(other[property], this.sample[property])) {
- mismatchValues.push('\'' + property + '\' was \'' + (other[property] ? j$.util.htmlEscape(other[property].toString()) : other[property]) + '\' in actual, but was \'' + (this.sample[property] ? j$.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + '\' in expected.');
- }
- }
-
- return (mismatchKeys.length === 0 && mismatchValues.length === 0);
- };
-
- ObjectContaining.prototype.jasmineToString = function() {
- return '<jasmine.objectContaining(' + j$.pp(this.sample) + ')>';
- };
-
- return ObjectContaining;
-};
-
getJasmineRequireObj().pp = function(j$) {
function PrettyPrinter() {
@@ -1447,6 +1701,8 @@ getJasmineRequireObj().pp = function(j$) {
this.emitScalar('HTMLNode');
} else if (value instanceof Date) {
this.emitScalar('Date(' + value + ')');
+ } else if (value.toString && typeof value === 'object' && !(value instanceof Array) && value.toString !== Object.prototype.toString) {
+ this.emitScalar(value.toString());
} else if (j$.util.arrayContains(this.seen, value)) {
this.emitScalar('<circular reference: ' + (j$.isArray_(value) ? 'Array' : 'Object') + '>');
} else if (j$.isArray_(value) || j$.isA_('Object', value)) {
@@ -1510,17 +1766,36 @@ getJasmineRequireObj().pp = function(j$) {
if(array.length > length){
this.append(', ...');
}
+
+ var self = this;
+ var first = array.length === 0;
+ this.iterateObject(array, function(property, isGetter) {
+ if (property.match(/^\d+$/)) {
+ return;
+ }
+
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.formatProperty(array, property, isGetter);
+ });
+
this.append(' ]');
};
StringPrettyPrinter.prototype.emitObject = function(obj) {
+ var constructorName = obj.constructor ? j$.fnNameFor(obj.constructor) : 'null';
+ this.append(constructorName);
+
if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) {
- this.append('Object');
return;
}
var self = this;
- this.append('{ ');
+ this.append('({ ');
var first = true;
this.iterateObject(obj, function(property, isGetter) {
@@ -1530,16 +1805,20 @@ getJasmineRequireObj().pp = function(j$) {
self.append(', ');
}
- self.append(property);
- self.append(': ');
+ self.formatProperty(obj, property, isGetter);
+ });
+
+ this.append(' })');
+ };
+
+ StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) {
+ this.append(property);
+ this.append(': ');
if (isGetter) {
- self.append('<getter>');
+ this.append('<getter>');
} else {
- self.format(obj[property]);
+ this.format(obj[property]);
}
- });
-
- this.append(' }');
};
StringPrettyPrinter.prototype.append = function(value) {
@@ -1566,31 +1845,33 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}
function QueueRunner(attrs) {
- this.fns = attrs.fns || [];
+ this.queueableFns = attrs.queueableFns || [];
this.onComplete = attrs.onComplete || function() {};
this.clearStack = attrs.clearStack || function(fn) {fn();};
this.onException = attrs.onException || function() {};
this.catchException = attrs.catchException || function() { return true; };
- this.enforceTimeout = attrs.enforceTimeout || function() { return false; };
- this.userContext = {};
- this.timer = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout};
+ this.userContext = attrs.userContext || {};
+ this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout};
+ this.fail = attrs.fail || function() {};
}
QueueRunner.prototype.execute = function() {
- this.run(this.fns, 0);
+ this.run(this.queueableFns, 0);
};
- QueueRunner.prototype.run = function(fns, recursiveIndex) {
- var length = fns.length,
- self = this,
- iterativeIndex;
+ QueueRunner.prototype.run = function(queueableFns, recursiveIndex) {
+ var length = queueableFns.length,
+ self = this,
+ iterativeIndex;
+
for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) {
- var fn = fns[iterativeIndex];
- if (fn.length > 0) {
- return attemptAsync(fn);
+ var queueableFn = queueableFns[iterativeIndex];
+ if (queueableFn.fn.length > 0) {
+ attemptAsync(queueableFn);
+ return;
} else {
- attemptSync(fn);
+ attemptSync(queueableFn);
}
}
@@ -1600,41 +1881,51 @@ getJasmineRequireObj().QueueRunner = function(j$) {
this.clearStack(this.onComplete);
}
- function attemptSync(fn) {
+ function attemptSync(queueableFn) {
try {
- fn.call(self.userContext);
+ queueableFn.fn.call(self.userContext);
} catch (e) {
- handleException(e);
+ handleException(e, queueableFn);
}
}
- function attemptAsync(fn) {
+ function attemptAsync(queueableFn) {
var clearTimeout = function () {
- Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeoutId]]);
+ Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]);
},
next = once(function () {
clearTimeout(timeoutId);
- self.run(fns, iterativeIndex + 1);
+ self.run(queueableFns, iterativeIndex + 1);
}),
timeoutId;
- if (self.enforceTimeout()) {
- timeoutId = Function.prototype.apply.apply(self.timer.setTimeout, [j$.getGlobal(), [function() {
- self.onException(new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'));
+ next.fail = function() {
+ self.fail.apply(null, arguments);
+ next();
+ };
+
+ if (queueableFn.timeout) {
+ timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() {
+ var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.');
+ onException(error);
next();
- }, j$.DEFAULT_TIMEOUT_INTERVAL]]);
+ }, queueableFn.timeout()]]);
}
try {
- fn.call(self.userContext, next);
+ queueableFn.fn.call(self.userContext, next);
} catch (e) {
- handleException(e);
+ handleException(e, queueableFn);
next();
}
}
- function handleException(e) {
+ function onException(e) {
self.onException(e);
+ }
+
+ function handleException(e, queueableFn) {
+ onException(e);
if (!self.catchException(e)) {
//TODO: set a var when we catch an exception and
//use a finally block to close the loop in a nice way..
@@ -1682,6 +1973,67 @@ getJasmineRequireObj().ReportDispatcher = function() {
};
+getJasmineRequireObj().SpyRegistry = function(j$) {
+
+ function SpyRegistry(options) {
+ options = options || {};
+ var currentSpies = options.currentSpies || function() { return []; };
+
+ this.spyOn = function(obj, methodName) {
+ if (j$.util.isUndefined(obj)) {
+ throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()');
+ }
+
+ if (j$.util.isUndefined(methodName)) {
+ throw new Error('No method name supplied');
+ }
+
+ if (j$.util.isUndefined(obj[methodName])) {
+ throw new Error(methodName + '() method does not exist');
+ }
+
+ if (obj[methodName] && j$.isSpy(obj[methodName])) {
+ //TODO?: should this return the current spy? Downside: may cause user confusion about spy state
+ throw new Error(methodName + ' has already been spied upon');
+ }
+
+ var descriptor;
+ try {
+ descriptor = Object.getOwnPropertyDescriptor(obj, methodName);
+ } catch(e) {
+ // IE 8 doesn't support `definePropery` on non-DOM nodes
+ }
+
+ if (descriptor && !(descriptor.writable || descriptor.set)) {
+ throw new Error(methodName + ' is not declared writable or has no setter');
+ }
+
+ var spy = j$.createSpy(methodName, obj[methodName]);
+
+ currentSpies().push({
+ spy: spy,
+ baseObj: obj,
+ methodName: methodName,
+ originalValue: obj[methodName]
+ });
+
+ obj[methodName] = spy;
+
+ return spy;
+ };
+
+ this.clearSpies = function() {
+ var spies = currentSpies();
+ for (var i = 0; i < spies.length; i++) {
+ var spyEntry = spies[i];
+ spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue;
+ }
+ };
+ }
+
+ return SpyRegistry;
+};
+
getJasmineRequireObj().SpyStrategy = function() {
function SpyStrategy(options) {
@@ -1712,6 +2064,14 @@ getJasmineRequireObj().SpyStrategy = function() {
return getSpy();
};
+ this.returnValues = function() {
+ var values = Array.prototype.slice.call(arguments);
+ plan = function () {
+ return values.shift();
+ };
+ return getSpy();
+ };
+
this.throwError = function(something) {
var error = (something instanceof Error) ? something : new Error(something);
plan = function() {
@@ -1734,31 +2094,36 @@ getJasmineRequireObj().SpyStrategy = function() {
return SpyStrategy;
};
-getJasmineRequireObj().Suite = function() {
+getJasmineRequireObj().Suite = function(j$) {
function Suite(attrs) {
this.env = attrs.env;
this.id = attrs.id;
this.parentSuite = attrs.parentSuite;
this.description = attrs.description;
- this.onStart = attrs.onStart || function() {};
- this.resultCallback = attrs.resultCallback || function() {};
- this.clearStack = attrs.clearStack || function(fn) {fn();};
+ this.expectationFactory = attrs.expectationFactory;
+ this.expectationResultFactory = attrs.expectationResultFactory;
+ this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
this.beforeFns = [];
this.afterFns = [];
- this.queueRunner = attrs.queueRunner || function() {};
+ this.beforeAllFns = [];
+ this.afterAllFns = [];
this.disabled = false;
this.children = [];
this.result = {
id: this.id,
- status: this.disabled ? 'disabled' : '',
description: this.description,
- fullName: this.getFullName()
+ fullName: this.getFullName(),
+ failedExpectations: []
};
}
+ Suite.prototype.expect = function(actual) {
+ return this.expectationFactory(actual, this);
+ };
+
Suite.prototype.getFullName = function() {
var fullName = this.description;
for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
@@ -1773,51 +2138,131 @@ getJasmineRequireObj().Suite = function() {
this.disabled = true;
};
+ Suite.prototype.pend = function(message) {
+ this.markedPending = true;
+ };
+
Suite.prototype.beforeEach = function(fn) {
this.beforeFns.unshift(fn);
};
+ Suite.prototype.beforeAll = function(fn) {
+ this.beforeAllFns.push(fn);
+ };
+
Suite.prototype.afterEach = function(fn) {
this.afterFns.unshift(fn);
};
+ Suite.prototype.afterAll = function(fn) {
+ this.afterAllFns.push(fn);
+ };
+
Suite.prototype.addChild = function(child) {
this.children.push(child);
};
- Suite.prototype.execute = function(onComplete) {
- var self = this;
+ Suite.prototype.status = function() {
if (this.disabled) {
- complete();
- return;
+ return 'disabled';
}
- var allFns = [];
+ if (this.markedPending) {
+ return 'pending';
+ }
- for (var i = 0; i < this.children.length; i++) {
- allFns.push(wrapChildAsAsync(this.children[i]));
+ if (this.result.failedExpectations.length > 0) {
+ return 'failed';
+ } else {
+ return 'finished';
}
+ };
- this.onStart(this);
+ Suite.prototype.isExecutable = function() {
+ return !this.disabled;
+ };
- this.queueRunner({
- fns: allFns,
- onComplete: complete
- });
+ Suite.prototype.canBeReentered = function() {
+ return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0;
+ };
- function complete() {
- self.resultCallback(self.result);
+ Suite.prototype.getResult = function() {
+ this.result.status = this.status();
+ return this.result;
+ };
- if (onComplete) {
- onComplete();
+ Suite.prototype.sharedUserContext = function() {
+ if (!this.sharedContext) {
+ this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {};
+ }
+
+ return this.sharedContext;
+ };
+
+ Suite.prototype.clonedSharedUserContext = function() {
+ return clone(this.sharedUserContext());
+ };
+
+ Suite.prototype.onException = function() {
+ if (arguments[0] instanceof j$.errors.ExpectationFailed) {
+ return;
+ }
+
+ if(isAfterAll(this.children)) {
+ var data = {
+ matcherName: '',
+ passed: false,
+ expected: '',
+ actual: '',
+ error: arguments[0]
+ };
+ this.result.failedExpectations.push(this.expectationResultFactory(data));
+ } else {
+ for (var i = 0; i < this.children.length; i++) {
+ var child = this.children[i];
+ child.onException.apply(child, arguments);
}
}
+ };
- function wrapChildAsAsync(child) {
- return function(done) { child.execute(done); };
+ Suite.prototype.addExpectationResult = function () {
+ if(isAfterAll(this.children) && isFailure(arguments)){
+ var data = arguments[1];
+ this.result.failedExpectations.push(this.expectationResultFactory(data));
+ if(this.throwOnExpectationFailure) {
+ throw new j$.errors.ExpectationFailed();
+ }
+ } else {
+ for (var i = 0; i < this.children.length; i++) {
+ var child = this.children[i];
+ try {
+ child.addExpectationResult.apply(child, arguments);
+ } catch(e) {
+ // keep going
+ }
+ }
}
};
+ function isAfterAll(children) {
+ return children && children[0].result.status;
+ }
+
+ function isFailure(args) {
+ return !args[0];
+ }
+
+ function clone(obj) {
+ var clonedObj = {};
+ for (var prop in obj) {
+ if (obj.hasOwnProperty(prop)) {
+ clonedObj[prop] = obj[prop];
+ }
+ }
+
+ return clonedObj;
+ }
+
return Suite;
};
@@ -1848,6 +2293,378 @@ getJasmineRequireObj().Timer = function() {
return Timer;
};
+getJasmineRequireObj().TreeProcessor = function() {
+ function TreeProcessor(attrs) {
+ var tree = attrs.tree,
+ runnableIds = attrs.runnableIds,
+ queueRunnerFactory = attrs.queueRunnerFactory,
+ nodeStart = attrs.nodeStart || function() {},
+ nodeComplete = attrs.nodeComplete || function() {},
+ orderChildren = attrs.orderChildren || function(node) { return node.children; },
+ stats = { valid: true },
+ processed = false,
+ defaultMin = Infinity,
+ defaultMax = 1 - Infinity;
+
+ this.processTree = function() {
+ processNode(tree, false);
+ processed = true;
+ return stats;
+ };
+
+ this.execute = function(done) {
+ if (!processed) {
+ this.processTree();
+ }
+
+ if (!stats.valid) {
+ throw 'invalid order';
+ }
+
+ var childFns = wrapChildren(tree, 0);
+
+ queueRunnerFactory({
+ queueableFns: childFns,
+ userContext: tree.sharedUserContext(),
+ onException: function() {
+ tree.onException.apply(tree, arguments);
+ },
+ onComplete: done
+ });
+ };
+
+ function runnableIndex(id) {
+ for (var i = 0; i < runnableIds.length; i++) {
+ if (runnableIds[i] === id) {
+ return i;
+ }
+ }
+ }
+
+ function processNode(node, parentEnabled) {
+ var executableIndex = runnableIndex(node.id);
+
+ if (executableIndex !== undefined) {
+ parentEnabled = true;
+ }
+
+ parentEnabled = parentEnabled && node.isExecutable();
+
+ if (!node.children) {
+ stats[node.id] = {
+ executable: parentEnabled && node.isExecutable(),
+ segments: [{
+ index: 0,
+ owner: node,
+ nodes: [node],
+ min: startingMin(executableIndex),
+ max: startingMax(executableIndex)
+ }]
+ };
+ } else {
+ var hasExecutableChild = false;
+
+ var orderedChildren = orderChildren(node);
+
+ for (var i = 0; i < orderedChildren.length; i++) {
+ var child = orderedChildren[i];
+
+ processNode(child, parentEnabled);
+
+ if (!stats.valid) {
+ return;
+ }
+
+ var childStats = stats[child.id];
+
+ hasExecutableChild = hasExecutableChild || childStats.executable;
+ }
+
+ stats[node.id] = {
+ executable: hasExecutableChild
+ };
+
+ segmentChildren(node, orderedChildren, stats[node.id], executableIndex);
+
+ if (!node.canBeReentered() && stats[node.id].segments.length > 1) {
+ stats = { valid: false };
+ }
+ }
+ }
+
+ function startingMin(executableIndex) {
+ return executableIndex === undefined ? defaultMin : executableIndex;
+ }
+
+ function startingMax(executableIndex) {
+ return executableIndex === undefined ? defaultMax : executableIndex;
+ }
+
+ function segmentChildren(node, orderedChildren, nodeStats, executableIndex) {
+ var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) },
+ result = [currentSegment],
+ lastMax = defaultMax,
+ orderedChildSegments = orderChildSegments(orderedChildren);
+
+ function isSegmentBoundary(minIndex) {
+ return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1;
+ }
+
+ for (var i = 0; i < orderedChildSegments.length; i++) {
+ var childSegment = orderedChildSegments[i],
+ maxIndex = childSegment.max,
+ minIndex = childSegment.min;
+
+ if (isSegmentBoundary(minIndex)) {
+ currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax};
+ result.push(currentSegment);
+ }
+
+ currentSegment.nodes.push(childSegment);
+ currentSegment.min = Math.min(currentSegment.min, minIndex);
+ currentSegment.max = Math.max(currentSegment.max, maxIndex);
+ lastMax = maxIndex;
+ }
+
+ nodeStats.segments = result;
+ }
+
+ function orderChildSegments(children) {
+ var specifiedOrder = [],
+ unspecifiedOrder = [];
+
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i],
+ segments = stats[child.id].segments;
+
+ for (var j = 0; j < segments.length; j++) {
+ var seg = segments[j];
+
+ if (seg.min === defaultMin) {
+ unspecifiedOrder.push(seg);
+ } else {
+ specifiedOrder.push(seg);
+ }
+ }
+ }
+
+ specifiedOrder.sort(function(a, b) {
+ return a.min - b.min;
+ });
+
+ return specifiedOrder.concat(unspecifiedOrder);
+ }
+
+ function executeNode(node, segmentNumber) {
+ if (node.children) {
+ return {
+ fn: function(done) {
+ nodeStart(node);
+
+ queueRunnerFactory({
+ onComplete: function() {
+ nodeComplete(node, node.getResult());
+ done();
+ },
+ queueableFns: wrapChildren(node, segmentNumber),
+ userContext: node.sharedUserContext(),
+ onException: function() {
+ node.onException.apply(node, arguments);
+ }
+ });
+ }
+ };
+ } else {
+ return {
+ fn: function(done) { node.execute(done, stats[node.id].executable); }
+ };
+ }
+ }
+
+ function wrapChildren(node, segmentNumber) {
+ var result = [],
+ segmentChildren = stats[node.id].segments[segmentNumber].nodes;
+
+ for (var i = 0; i < segmentChildren.length; i++) {
+ result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index));
+ }
+
+ if (!stats[node.id].executable) {
+ return result;
+ }
+
+ return node.beforeAllFns.concat(result).concat(node.afterAllFns);
+ }
+ }
+
+ return TreeProcessor;
+};
+
+getJasmineRequireObj().Any = function(j$) {
+
+ function Any(expectedObject) {
+ if (typeof expectedObject === 'undefined') {
+ throw new TypeError(
+ 'jasmine.any() expects to be passed a constructor function. ' +
+ 'Please pass one or use jasmine.anything() to match any object.'
+ );
+ }
+ this.expectedObject = expectedObject;
+ }
+
+ Any.prototype.asymmetricMatch = function(other) {
+ if (this.expectedObject == String) {
+ return typeof other == 'string' || other instanceof String;
+ }
+
+ if (this.expectedObject == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
+
+ if (this.expectedObject == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
+
+ if (this.expectedObject == Object) {
+ return typeof other == 'object';
+ }
+
+ if (this.expectedObject == Boolean) {
+ return typeof other == 'boolean';
+ }
+
+ return other instanceof this.expectedObject;
+ };
+
+ Any.prototype.jasmineToString = function() {
+ return '<jasmine.any(' + j$.fnNameFor(this.expectedObject) + ')>';
+ };
+
+ return Any;
+};
+
+getJasmineRequireObj().Anything = function(j$) {
+
+ function Anything() {}
+
+ Anything.prototype.asymmetricMatch = function(other) {
+ return !j$.util.isUndefined(other) && other !== null;
+ };
+
+ Anything.prototype.jasmineToString = function() {
+ return '<jasmine.anything>';
+ };
+
+ return Anything;
+};
+
+getJasmineRequireObj().ArrayContaining = function(j$) {
+ function ArrayContaining(sample) {
+ this.sample = sample;
+ }
+
+ ArrayContaining.prototype.asymmetricMatch = function(other) {
+ var className = Object.prototype.toString.call(this.sample);
+ if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not \'' + this.sample + '\'.'); }
+
+ for (var i = 0; i < this.sample.length; i++) {
+ var item = this.sample[i];
+ if (!j$.matchersUtil.contains(other, item)) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ ArrayContaining.prototype.jasmineToString = function () {
+ return '<jasmine.arrayContaining(' + jasmine.pp(this.sample) +')>';
+ };
+
+ return ArrayContaining;
+};
+
+getJasmineRequireObj().ObjectContaining = function(j$) {
+
+ function ObjectContaining(sample) {
+ this.sample = sample;
+ }
+
+ function getPrototype(obj) {
+ if (Object.getPrototypeOf) {
+ return Object.getPrototypeOf(obj);
+ }
+
+ if (obj.constructor.prototype == obj) {
+ return null;
+ }
+
+ return obj.constructor.prototype;
+ }
+
+ function hasProperty(obj, property) {
+ if (!obj) {
+ return false;
+ }
+
+ if (Object.prototype.hasOwnProperty.call(obj, property)) {
+ return true;
+ }
+
+ return hasProperty(getPrototype(obj), property);
+ }
+
+ ObjectContaining.prototype.asymmetricMatch = function(other) {
+ if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); }
+
+ for (var property in this.sample) {
+ if (!hasProperty(other, property) ||
+ !j$.matchersUtil.equals(this.sample[property], other[property])) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+
+ ObjectContaining.prototype.jasmineToString = function() {
+ return '<jasmine.objectContaining(' + j$.pp(this.sample) + ')>';
+ };
+
+ return ObjectContaining;
+};
+
+getJasmineRequireObj().StringMatching = function(j$) {
+
+ function StringMatching(expected) {
+ if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) {
+ throw new Error('Expected is not a String or a RegExp');
+ }
+
+ this.regexp = new RegExp(expected);
+ }
+
+ StringMatching.prototype.asymmetricMatch = function(other) {
+ return this.regexp.test(other);
+ };
+
+ StringMatching.prototype.jasmineToString = function() {
+ return '<jasmine.stringMatching(' + this.regexp + ')>';
+ };
+
+ return StringMatching;
+};
+
+getJasmineRequireObj().errors = function() {
+ function ExpectationFailed() {}
+
+ ExpectationFailed.prototype = new Error();
+ ExpectationFailed.prototype.constructor = ExpectationFailed;
+
+ return {
+ ExpectationFailed: ExpectationFailed
+ };
+};
getJasmineRequireObj().matchersUtil = function(j$) {
// TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter?
@@ -1861,7 +2678,9 @@ getJasmineRequireObj().matchersUtil = function(j$) {
contains: function(haystack, needle, customTesters) {
customTesters = customTesters || [];
- if (Object.prototype.toString.apply(haystack) === '[object Array]') {
+ if ((Object.prototype.toString.apply(haystack) === '[object Array]') ||
+ (!!haystack && !haystack.indexOf))
+ {
for (var i = 0; i < haystack.length; i++) {
if (eq(haystack[i], needle, [], [], customTesters)) {
return true;
@@ -1869,6 +2688,7 @@ getJasmineRequireObj().matchersUtil = function(j$) {
}
return false;
}
+
return !!haystack && haystack.indexOf(needle) >= 0;
},
@@ -1898,11 +2718,37 @@ getJasmineRequireObj().matchersUtil = function(j$) {
}
};
+ function isAsymmetric(obj) {
+ return obj && j$.isA_('Function', obj.asymmetricMatch);
+ }
+
+ function asymmetricMatch(a, b) {
+ var asymmetricA = isAsymmetric(a),
+ asymmetricB = isAsymmetric(b);
+
+ if (asymmetricA && asymmetricB) {
+ return undefined;
+ }
+
+ if (asymmetricA) {
+ return a.asymmetricMatch(b);
+ }
+
+ if (asymmetricB) {
+ return b.asymmetricMatch(a);
+ }
+ }
+
// Equality function lovingly adapted from isEqual in
// [Underscore](http://underscorejs.org)
function eq(a, b, aStack, bStack, customTesters) {
var result = true;
+ var asymmetricResult = asymmetricMatch(a, b);
+ if (!j$.util.isUndefined(asymmetricResult)) {
+ return asymmetricResult;
+ }
+
for (var i = 0; i < customTesters.length; i++) {
var customTesterResult = customTesters[i](a, b);
if (!j$.util.isUndefined(customTesterResult)) {
@@ -1910,27 +2756,6 @@ getJasmineRequireObj().matchersUtil = function(j$) {
}
}
- if (a instanceof j$.Any) {
- result = a.jasmineMatches(b);
- if (result) {
- return true;
- }
- }
-
- if (b instanceof j$.Any) {
- result = b.jasmineMatches(a);
- if (result) {
- return true;
- }
- }
-
- if (b instanceof j$.ObjectContaining) {
- result = b.jasmineMatches(a);
- if (result) {
- return true;
- }
- }
-
if (a instanceof Error && b instanceof Error) {
return a.message == b.message;
}
@@ -1966,6 +2791,29 @@ getJasmineRequireObj().matchersUtil = function(j$) {
a.ignoreCase == b.ignoreCase;
}
if (typeof a != 'object' || typeof b != 'object') { return false; }
+
+ var aIsDomNode = j$.isDomNode(a);
+ var bIsDomNode = j$.isDomNode(b);
+ if (aIsDomNode && bIsDomNode) {
+ // At first try to use DOM3 method isEqualNode
+ if (a.isEqualNode) {
+ return a.isEqualNode(b);
+ }
+ // IE8 doesn't support isEqualNode, try to use outerHTML && innerText
+ var aIsElement = a instanceof Element;
+ var bIsElement = b instanceof Element;
+ if (aIsElement && bIsElement) {
+ return a.outerHTML == b.outerHTML;
+ }
+ if (aIsElement || bIsElement) {
+ return false;
+ }
+ return a.innerText == b.innerText && a.textContent == b.textContent;
+ }
+ if (aIsDomNode || bIsDomNode) {
+ return false;
+ }
+
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var length = aStack.length;
@@ -1979,23 +2827,20 @@ getJasmineRequireObj().matchersUtil = function(j$) {
bStack.push(b);
var size = 0;
// Recursively compare objects and arrays.
- if (className == '[object Array]') {
- // Compare array lengths to determine if a deep comparison is necessary.
- size = a.length;
- result = size == b.length;
- if (result) {
- // Deep compare the contents, ignoring non-numeric properties.
- while (size--) {
- if (!(result = eq(a[size], b[size], aStack, bStack, customTesters))) { break; }
- }
- }
- } else {
+ // Compare array lengths to determine if a deep comparison is necessary.
+ if (className == '[object Array]' && a.length !== b.length) {
+ result = false;
+ }
+
+ if (result) {
// Objects with different constructors are not equivalent, but `Object`s
- // from different frames are.
- var aCtor = a.constructor, bCtor = b.constructor;
- if (aCtor !== bCtor && !(isFunction(aCtor) && (aCtor instanceof aCtor) &&
- isFunction(bCtor) && (bCtor instanceof bCtor))) {
- return false;
+ // or `Array`s from different frames are.
+ if (className !== '[object Array]') {
+ var aCtor = a.constructor, bCtor = b.constructor;
+ if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor &&
+ isFunction(bCtor) && bCtor instanceof bCtor)) {
+ return false;
+ }
}
// Deep compare objects.
for (var key in a) {
@@ -2021,7 +2866,7 @@ getJasmineRequireObj().matchersUtil = function(j$) {
return result;
function has(obj, key) {
- return obj.hasOwnProperty(key);
+ return Object.prototype.hasOwnProperty.call(obj, key);
}
function isFunction(obj) {
@@ -2256,6 +3101,37 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) {
return toHaveBeenCalled;
};
+getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) {
+
+ function toHaveBeenCalledTimes() {
+ return {
+ compare: function(actual, expected) {
+ if (!j$.isSpy(actual)) {
+ throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
+ }
+
+ var args = Array.prototype.slice.call(arguments, 0),
+ result = { pass: false };
+
+ if(!expected){
+ throw new Error('Expected times failed is required as an argument.');
+ }
+
+ actual = args[0];
+ var calls = actual.calls.count();
+ var timesMessage = expected === 1 ? 'once' : expected + ' times';
+ result.pass = calls === expected;
+ result.message = result.pass ?
+ 'Expected spy ' + actual.and.identity() + ' not to have been called ' + timesMessage + '. It was called ' + calls + ' times.' :
+ 'Expected spy ' + actual.and.identity() + ' to have been called ' + timesMessage + '. It was called ' + calls + ' times.';
+ return result;
+ }
+ };
+ }
+
+ return toHaveBeenCalledTimes;
+};
+
getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
function toHaveBeenCalledWith(util, customEqualityTesters) {
@@ -2290,11 +3166,15 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) {
return toHaveBeenCalledWith;
};
-getJasmineRequireObj().toMatch = function() {
+getJasmineRequireObj().toMatch = function(j$) {
function toMatch() {
return {
compare: function(actual, expected) {
+ if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) {
+ throw new Error('Expected is not a String or a RegExp');
+ }
+
var regexp = new RegExp(expected);
return {
@@ -2355,24 +3235,19 @@ getJasmineRequireObj().toThrow = function(j$) {
};
getJasmineRequireObj().toThrowError = function(j$) {
- function toThrowError (util) {
+ function toThrowError () {
return {
compare: function(actual) {
var threw = false,
pass = {pass: true},
fail = {pass: false},
- thrown,
- errorType,
- message,
- regexp,
- name,
- constructorName;
+ thrown;
if (typeof actual != 'function') {
throw new Error('Actual is not a Function');
}
- extractExpectedParams.apply(null, arguments);
+ var errorMatcher = getMatcher.apply(null, arguments);
try {
actual();
@@ -2391,126 +3266,189 @@ getJasmineRequireObj().toThrowError = function(j$) {
return fail;
}
- if (arguments.length == 1) {
- pass.message = 'Expected function not to throw an Error, but it threw ' + fnNameFor(thrown) + '.';
+ if (errorMatcher.hasNoSpecifics()) {
+ pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.';
return pass;
}
- if (errorType) {
- name = fnNameFor(errorType);
- constructorName = fnNameFor(thrown.constructor);
+ if (errorMatcher.matches(thrown)) {
+ pass.message = function() {
+ return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.';
+ };
+ return pass;
+ } else {
+ fail.message = function() {
+ return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() +
+ ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.';
+ };
+ return fail;
}
+ }
+ };
- if (errorType && message) {
- if (thrown.constructor == errorType && util.equals(thrown.message, message)) {
- pass.message = function() { return 'Expected function not to throw ' + name + ' with message ' + j$.pp(message) + '.'; };
- return pass;
- } else {
- fail.message = function() { return 'Expected function to throw ' + name + ' with message ' + j$.pp(message) +
- ', but it threw ' + constructorName + ' with message ' + j$.pp(thrown.message) + '.'; };
- return fail;
- }
- }
+ function getMatcher() {
+ var expected = null,
+ errorType = null;
- if (errorType && regexp) {
- if (thrown.constructor == errorType && regexp.test(thrown.message)) {
- pass.message = function() { return 'Expected function not to throw ' + name + ' with message matching ' + j$.pp(regexp) + '.'; };
- return pass;
- } else {
- fail.message = function() { return 'Expected function to throw ' + name + ' with message matching ' + j$.pp(regexp) +
- ', but it threw ' + constructorName + ' with message ' + j$.pp(thrown.message) + '.'; };
- return fail;
- }
+ if (arguments.length == 2) {
+ expected = arguments[1];
+ if (isAnErrorType(expected)) {
+ errorType = expected;
+ expected = null;
}
-
- if (errorType) {
- if (thrown.constructor == errorType) {
- pass.message = 'Expected function not to throw ' + name + '.';
- return pass;
- } else {
- fail.message = 'Expected function to throw ' + name + ', but it threw ' + constructorName + '.';
- return fail;
- }
+ } else if (arguments.length > 2) {
+ errorType = arguments[1];
+ expected = arguments[2];
+ if (!isAnErrorType(errorType)) {
+ throw new Error('Expected error type is not an Error.');
}
+ }
- if (message) {
- if (thrown.message == message) {
- pass.message = function() { return 'Expected function not to throw an exception with message ' + j$.pp(message) + '.'; };
- return pass;
- } else {
- fail.message = function() { return 'Expected function to throw an exception with message ' + j$.pp(message) +
- ', but it threw an exception with message ' + j$.pp(thrown.message) + '.'; };
- return fail;
- }
+ if (expected && !isStringOrRegExp(expected)) {
+ if (errorType) {
+ throw new Error('Expected error message is not a string or RegExp.');
+ } else {
+ throw new Error('Expected is not an Error, string, or RegExp.');
}
+ }
- if (regexp) {
- if (regexp.test(thrown.message)) {
- pass.message = function() { return 'Expected function not to throw an exception with a message matching ' + j$.pp(regexp) + '.'; };
- return pass;
- } else {
- fail.message = function() { return 'Expected function to throw an exception with a message matching ' + j$.pp(regexp) +
- ', but it threw an exception with message ' + j$.pp(thrown.message) + '.'; };
- return fail;
- }
+ function messageMatch(message) {
+ if (typeof expected == 'string') {
+ return expected == message;
+ } else {
+ return expected.test(message);
}
+ }
- function fnNameFor(func) {
- return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1];
- }
+ return {
+ errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception',
+ thrownDescription: function(thrown) {
+ var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception',
+ thrownMessage = '';
- function extractExpectedParams() {
- if (arguments.length == 1) {
- return;
+ if (expected) {
+ thrownMessage = ' with message ' + j$.pp(thrown.message);
}
- if (arguments.length == 2) {
- var expected = arguments[1];
-
- if (expected instanceof RegExp) {
- regexp = expected;
- } else if (typeof expected == 'string') {
- message = expected;
- } else if (checkForAnErrorType(expected)) {
- errorType = expected;
- }
-
- if (!(errorType || message || regexp)) {
- throw new Error('Expected is not an Error, string, or RegExp.');
- }
+ return thrownName + thrownMessage;
+ },
+ messageDescription: function() {
+ if (expected === null) {
+ return '';
+ } else if (expected instanceof RegExp) {
+ return ' with a message matching ' + j$.pp(expected);
} else {
- if (checkForAnErrorType(arguments[1])) {
- errorType = arguments[1];
- } else {
- throw new Error('Expected error type is not an Error.');
- }
-
- if (arguments[2] instanceof RegExp) {
- regexp = arguments[2];
- } else if (typeof arguments[2] == 'string') {
- message = arguments[2];
- } else {
- throw new Error('Expected error message is not a string or RegExp.');
- }
+ return ' with message ' + j$.pp(expected);
}
+ },
+ hasNoSpecifics: function() {
+ return expected === null && errorType === null;
+ },
+ matches: function(error) {
+ return (errorType === null || error instanceof errorType) &&
+ (expected === null || messageMatch(error.message));
}
+ };
+ }
- function checkForAnErrorType(type) {
- if (typeof type !== 'function') {
- return false;
- }
+ function isStringOrRegExp(potential) {
+ return potential instanceof RegExp || (typeof potential == 'string');
+ }
- var Surrogate = function() {};
- Surrogate.prototype = type.prototype;
- return (new Surrogate()) instanceof Error;
- }
+ function isAnErrorType(type) {
+ if (typeof type !== 'function') {
+ return false;
}
- };
+
+ var Surrogate = function() {};
+ Surrogate.prototype = type.prototype;
+ return (new Surrogate()) instanceof Error;
+ }
}
return toThrowError;
};
+getJasmineRequireObj().interface = function(jasmine, env) {
+ var jasmineInterface = {
+ describe: function(description, specDefinitions) {
+ return env.describe(description, specDefinitions);
+ },
+
+ xdescribe: function(description, specDefinitions) {
+ return env.xdescribe(description, specDefinitions);
+ },
+
+ fdescribe: function(description, specDefinitions) {
+ return env.fdescribe(description, specDefinitions);
+ },
+
+ it: function() {
+ return env.it.apply(env, arguments);
+ },
+
+ xit: function() {
+ return env.xit.apply(env, arguments);
+ },
+
+ fit: function() {
+ return env.fit.apply(env, arguments);
+ },
+
+ beforeEach: function() {
+ return env.beforeEach.apply(env, arguments);
+ },
+
+ afterEach: function() {
+ return env.afterEach.apply(env, arguments);
+ },
+
+ beforeAll: function() {
+ return env.beforeAll.apply(env, arguments);
+ },
+
+ afterAll: function() {
+ return env.afterAll.apply(env, arguments);
+ },
+
+ expect: function(actual) {
+ return env.expect(actual);
+ },
+
+ pending: function() {
+ return env.pending.apply(env, arguments);
+ },
+
+ fail: function() {
+ return env.fail.apply(env, arguments);
+ },
+
+ spyOn: function(obj, methodName) {
+ return env.spyOn(obj, methodName);
+ },
+
+ jsApiReporter: new jasmine.JsApiReporter({
+ timer: new jasmine.Timer()
+ }),
+
+ jasmine: jasmine
+ };
+
+ jasmine.addCustomEqualityTester = function(tester) {
+ env.addCustomEqualityTester(tester);
+ };
+
+ jasmine.addMatchers = function(matchers) {
+ return env.addMatchers(matchers);
+ };
+
+ jasmine.clock = function() {
+ return env.clock;
+ };
+
+ return jasmineInterface;
+};
+
getJasmineRequireObj().version = function() {
- return '2.0.1';
+ return '2.4.1';
};
diff --git a/spec/lib/jasmine-2.0.1/jasmine_favicon.png b/spec/lib/jasmine-2.4.1/jasmine_favicon.png
index 3b84583..3b84583 100644
--- a/spec/lib/jasmine-2.0.1/jasmine_favicon.png
+++ b/spec/lib/jasmine-2.4.1/jasmine_favicon.png
Binary files differ
diff --git a/spec/spec/element.js b/spec/spec/element.js
index 96a5ff7..1f96120 100644
--- a/spec/spec/element.js
+++ b/spec/spec/element.js
@@ -645,7 +645,7 @@ describe('Element', function() {
it('creates a point from screen coordinates transformed in the elements space', function(){
var rect = draw.rect(100,100)
expect(rect.point(2,5).x).toBeCloseTo(-6)
- expect(rect.point(2,5).y).toBeCloseTo(-3)
+ expect(rect.point(2,5).y).toBeCloseTo(-21)
})
})
})
diff --git a/spec/spec/event.js b/spec/spec/event.js
index 9c945d0..5390472 100644
--- a/spec/spec/event.js
+++ b/spec/spec/event.js
@@ -297,30 +297,32 @@ describe('Event', function() {
})
it('attaches multiple handlers on different element', function() {
var listenerCnt = SVG.listeners.length
-
+
var rect2 = draw.rect(100,100);
var rect3 = draw.rect(100,100);
-
+
rect.on('event', action)
rect2.on('event', action)
rect3.on('event', function(){ butter = 'melting' })
rect3.on('event', action)
-
+
expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['*']).length).toBe(1) // 1 listener on rect
+ expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect2.node)]['event']['*']).length).toBe(1) // 1 listener on rect2
expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect3.node)]['event']['*']).length).toBe(2) // 2 listener on rect3
+
expect(SVG.listeners.length).toBe(listenerCnt + 3) // added listeners on 3 different elements
})
if('attaches a handler to a namespaced event', function(){
var listenerCnt = SVG.listeners.length
-
+
var rect2 = draw.rect(100,100);
var rect3 = draw.rect(100,100);
-
+
rect.on('event.namespace1', action)
rect2.on('event.namespace2', action)
rect3.on('event.namespace3', function(){ butter = 'melting' })
rect3.on('event', action)
-
+
expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['*'])).toBeUndefined() // no global listener on rect
expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['namespace1']).length).toBe( 1) // 1 namespaced listener on rect
expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect2.node)]['event']['namespace2']).length).toBe(1) // 1 namespaced listener on rect
@@ -338,7 +340,7 @@ describe('Event', function() {
})
it('stores the listener for future reference', function() {
rect.on('event', action)
- expect(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['*'][action]).not.toBeUndefined()
+ expect(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['*'][action._svgjsListenerId]).not.toBeUndefined()
})
it('returns the called element', function() {
expect(rect.on('event', action)).toBe(rect)
@@ -355,47 +357,47 @@ describe('Event', function() {
it('detaches a specific event listener, all other still working', function() {
rect2 = draw.rect(100,100);
rect3 = draw.rect(100,100);
-
+
rect.on('event', action)
rect2.on('event', action)
rect3.on('event', function(){ butter = 'melting' })
-
+
rect.off('event', action)
-
+
expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['*']).length).toBe(0)
-
+
dispatchEvent(rect, 'event')
expect(toast).toBeNull()
-
+
dispatchEvent(rect2, 'event')
expect(toast).toBe('ready')
-
+
dispatchEvent(rect3, 'event')
expect(butter).toBe('melting')
-
+
expect(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['*'][action]).toBeUndefined()
})
it('detaches a specific namespaced event listener, all other still working', function() {
rect2 = draw.rect(100,100);
rect3 = draw.rect(100,100);
-
+
rect.on('event.namespace', action)
rect2.on('event.namespace', action)
rect3.on('event.namespace', function(){ butter = 'melting' })
-
+
rect.off('event.namespace', action)
-
+
expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['namespace']).length).toBe(0)
-
+
dispatchEvent(rect, 'event')
expect(toast).toBeNull()
-
+
dispatchEvent(rect2, 'event')
expect(toast).toBe('ready')
-
+
dispatchEvent(rect3, 'event')
expect(butter).toBe('melting')
-
+
expect(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['namespace'][action]).toBeUndefined()
})
it('detaches all listeners for a specific namespace', function() {
@@ -459,7 +461,7 @@ describe('Event', function() {
expect(toast).toBe('ready')
})
})
-
+
})
diff --git a/spec/spec/fx.js b/spec/spec/fx.js
index f1171f5..cc08731 100644
--- a/spec/spec/fx.js
+++ b/spec/spec/fx.js
@@ -5,32 +5,273 @@ describe('FX', function() {
rect = draw.rect(100,100).move(100,100)
fx = rect.animate(500)
})
+
+ afterEach(function() {
+ //fx.finish()
+ //rect.off('.fx')
+ })
- it('creates an instance of SVG.FX', function() {
+ it('creates an instance of SVG.FX and sets parameter', function() {
expect(fx instanceof SVG.FX).toBe(true)
+ expect(fx._target).toBe(rect)
+ expect(fx.pos).toBe(0)
+ expect(fx.lastPos).toBe(0)
+ expect(fx.paused).toBeFalsy()
+ expect(fx.active).toBeFalsy()
+ expect(fx.situations).toEqual([])
+ expect(fx.current.init).toBeFalsy()
+ expect(fx.current.reversed).toBeFalsy()
+ expect(fx.current.duration).toBe(500)
+ expect(fx.current.delay).toBe(0)
+ expect(fx.current.animations).toEqual({})
+ expect(fx.current.attrs).toEqual({})
+ expect(fx.current.styles).toEqual({})
+ expect(fx.current.transforms).toEqual([])
+ expect(fx.current.once).toEqual({})
+ })
+
+ describe('target()', function(){
+ it('returns the current fx object', function(){
+ expect(fx.target()).toBe(rect)
+ })
+ })
+
+ describe('timeToPos()', function() {
+ it('converts a timestamp to a progress', function() {
+ expect(fx.timeToPos(fx.current.start+fx.current.duration/2)).toBe(0.5)
+ })
+ })
+
+ describe('posToTime()', function() {
+ it('converts a progress to a timestamp', function() {
+ expect(fx.posToTime(0.5)).toBe(fx.current.start+fx.current.duration/2)
+ })
+ })
+
+ describe('seek()', function() {
+ it('sets the progress to the specified position', function() {
+ var start = fx.current.start
+ expect(fx.seek(0.5).pos).toBe(0.5)
+ // time is running so we cant compare it directly
+ expect(fx.current.start).toBeLessThan(start - fx.current.duration * 0.5 + 1)
+ expect(fx.current.start).toBeGreaterThan(start - fx.current.duration * 0.5 - 10)
+ })
+ })
+
+ describe('start()', function(){
+ it('starts the animation', function(done) {
+ fx.start()
+ expect(fx.active).toBe(true)
+ expect(fx.timeout).not.toBe(0)
+ setTimeout(function(){
+ expect(fx.pos).toBeGreaterThan(0)
+ done()
+ }, 200)
+ })
+ })
+
+ describe('pause()', function() {
+ it('pause the animation', function() {
+ expect(fx.pause().paused).toBe(true)
+ })
+ })
+
+ describe('play()', function() {
+ it('unpause the animation', function(done) {
+ var start = fx.start().pause().current.start
+ setTimeout(function(){
+ expect(fx.play().paused).toBe(false)
+ expect(fx.current.start).not.toBe(start)
+ done()
+ }, 200)
+ })
+ })
+
+ describe('speed()', function() {
+ it('speeds up the animation by the given factor', function(){
+
+ expect(fx.speed(2).current.duration).toBe(250)
+ expect(fx.speed(0.5).current.duration).toBe(500)
+ expect(fx.seek(0.2).speed(2).current.duration).toBe(0.2 * 500 + 0.8 * 500 / 2)
+ })
+ })
+
+ describe('reverse()', function() {
+ it('toggles the direction of the animation without a parameter', function() {
+ expect(fx.reverse().current.reversed).toBe(true)
+ })
+ })
+
+ describe('reverse()', function() {
+ it('sets the direction to backwards with true given', function() {
+ expect(fx.reverse(true).current.reversed).toBe(true)
+ })
+ })
+
+ describe('reverse()', function() {
+ it('sets the direction to forwards with false given', function() {
+ expect(fx.reverse(false).current.reversed).toBe(false)
+ })
+ })
+
+ describe('stop()', function() {
+ it('stops the animation immediately without a parameter', function() {
+ fx.animate(500)
+ expect(fx.stop().current).toBeNull()
+ expect(fx.active).toBeFalsy()
+ expect(fx.situations.length).toBe(1)
+ })
+ })
+
+ describe('stop()', function() {
+ it('stops the animation immediately and fullfill it if first parameter true', function() {
+ fx.animate(500)
+ expect(fx.stop(true).current).toBeNull()
+ expect(fx.active).toBeFalsy()
+ expect(fx.pos).toBe(1)
+ expect(fx.situations.length).toBe(1)
+ })
+ })
+
+ describe('stop()', function() {
+ it('stops the animation immediately and remove all items from queue when second parameter true', function() {
+ fx.animate(500)
+ expect(fx.stop(false, true).current).toBeNull()
+ expect(fx.active).toBeFalsy()
+ expect(fx.situations.length).toBe(0)
+ })
+ })
+
+ describe('finish()', function() {
+ it('finish the whole animation by fullfilling every single one', function() {
+ fx.animate(500)
+ expect(fx.finish().pos).toBe(1)
+ expect(fx.situations.length).toBe(0)
+ expect(fx.current).toBeNull()
+ })
+ })
+
+ describe('progress()', function() {
+ it('returns the current position', function() {
+ expect(fx.progress()).toBe(0)
+ expect(fx.progress()).toBe(fx.pos)
+ })
+ })
+
+ describe('after()', function() {
+ it('adds a callback which is called when the current animation is finished', function(done) {
+ fx.start().after(function(situation){
+ expect(fx.current).toBe(situation)
+ expect(fx.pos).toBe(1)
+ done()
+ })
+ })
+ })
+
+ describe('afterAll()', function() {
+ it('adds a callback which is called when the current animation is finished', function(done) {
+ fx.start().after(function(){
+ expect(fx.pos).toBe(1)
+ expect(fx.situations.length).toBe(0)
+ done()
+ })
+ })
+ })
+
+ describe('during()', function() {
+ it('adds a callback which is called on every animation step', function(done) {
+
+ fx.start().during(function(pos, eased, situation){
+
+ expect(fx.current).toBe(situation)
+
+ if(fx.pos > 0.9){
+ rect.off('.fx')
+ fx.stop()
+
+ done()
+ }
+ })
+ })
+ })
+
+ describe('duringAll()', function() {
+ it('adds a callback which is called on every animation step for the whole chain', function(done) {
+
+ fx.finish()
+ rect.off('.fx')
+
+ fx.animate(500).start().animate(500)
+
+ var sit = null
+
+ var pos1 = false
+ var pos2 = false
+
+ setTimeout(function(){
+ pos1 = true
+ }, 300)
+
+ setTimeout(function(){
+ pos2 = true
+ }, 800)
+
+ fx.duringAll(function(pos, eased, fx2, situation){
+
+ if(pos1){
+ pos1 = false
+ sit = situation
+ expect(fx2.pos).toBeGreaterThan(0.5)
+ }
+
+ if(pos2){
+ console.log('asd')
+ pos2 = null
+ expect(situation).not.toBe(sit)
+ expect(fx2.pos).toBeGreaterThan(0.5)
+ done()
+ }
+ })
+
+ setTimeout(function(){
+ if(pos2 === null) return
+ fail('Not enough situations called')
+ done()
+ }, 1200)
+ })
+ })
+
+ describe('once()', function() {
+ it('adds a callback which is called once at the specified position', function(done) {
+
+ fx.start().once(0.5, function(pos, eased){
+ expect(true).toBe(true)
+ done()
+ })
+ })
})
it('animates the x/y-attr', function(done) {
-
+
fx.move(200,200).after(function(){
-
+
expect(rect.x()).toBe(200)
expect(rect.y()).toBe(200)
done()
-
+
});
-
+
setTimeout(function(){
expect(rect.x()).toBeGreaterThan(100)
expect(rect.y()).toBeGreaterThan(100)
}, 250)
})
-
+
it('animates matrix', function(done) {
-
+
fx.transform({a:0.8, b:0.4, c:-0.15, d:0.7, e: 90.3, f: 27.07}).after(function(){
-
+
var ctm = rect.ctm()
expect(ctm.a).toBeCloseTo(0.8)
expect(ctm.b).toBeCloseTo(0.4)
@@ -38,13 +279,13 @@ describe('FX', function() {
expect(ctm.d).toBeCloseTo(0.7)
expect(ctm.e).toBeCloseTo(90.3)
expect(ctm.f).toBeCloseTo(27.07)
-
+
done()
-
+
})
-
+
setTimeout(function(){
-
+
var ctm = rect.ctm();
expect(ctm.a).toBeLessThan(1)
expect(ctm.b).toBeGreaterThan(0)
@@ -53,7 +294,7 @@ describe('FX', function() {
expect(ctm.e).toBeGreaterThan(0)
expect(ctm.f).toBeGreaterThan(0)
}, 250)
-
+
})
}) \ No newline at end of file
diff --git a/spec/spec/sugar.js b/spec/spec/sugar.js
new file mode 100644
index 0000000..9133032
--- /dev/null
+++ b/spec/spec/sugar.js
@@ -0,0 +1,679 @@
+describe('Sugar', function() {
+
+ var rect
+
+ beforeEach(function() {
+ draw.attr('viewBox', null)
+ })
+
+ afterEach(function() {
+ draw.clear()
+ })
+
+ describe('fill()', function() {
+ it('returns the node reference', function() {
+ rect = draw.rect(100,100)
+ expect(rect.fill('red')).toBe(rect)
+ })
+
+ it('returns the value with no argument given', function() {
+ rect = draw.rect(100,100).fill('red')
+ expect(rect.fill('red')).toBe('red')
+ })
+
+ describe('color, opacity, rule', function() {
+
+ ['color', 'opacity', 'rule'].forEach(function(a){
+
+ describe('fill-'+a+'()', function(){
+
+ rect = draw.rect(100,100).fill('red')
+
+ })
+
+ })
+
+ })
+ })
+
+ describe('()', function() {
+ it('returns the node reference', function() {
+ rect = draw.rect(100,100)
+ expect(rect.fill('red')).toBe(rect)
+ })
+
+ it('returns the value with no argument given', function() {
+ rect = draw.rect(100,100).fill('red')
+ expect(rect.fill('red')).toBe('red')
+ })
+ })
+
+ describe('attr()', function() {
+ var rect
+
+ beforeEach(function() {
+ rect = draw.rect(100,100)
+ })
+
+ afterEach(function() {
+ rect.remove()
+ })
+
+ it('sets one attribute when two arguments are given', function() {
+ rect.attr('fill', '#ff0066')
+ expect(rect.node.getAttribute('fill')).toBe('#ff0066')
+ })
+ it('sets various attributes when an object is given', function() {
+ rect.attr({ fill: '#00ff66', stroke: '#ff2233', 'stroke-width': 10 })
+ expect(rect.node.getAttribute('fill')).toBe('#00ff66')
+ expect(rect.node.getAttribute('stroke')).toBe('#ff2233')
+ expect(rect.node.getAttribute('stroke-width')).toBe('10')
+ })
+ it('gets the value of the string value given as first argument', function() {
+ rect.attr('fill', '#ff0066')
+ expect(rect.attr('fill')).toEqual('#ff0066')
+ })
+ it('gets an object with all attributes without any arguments', function() {
+ rect.attr({ fill: '#00ff66', stroke: '#ff2233' })
+ var attr = rect.attr()
+ expect(attr.fill).toBe('#00ff66')
+ expect(attr.stroke).toBe('#ff2233')
+ })
+ it('removes an attribute if the second argument is explicitly set to null', function() {
+ rect.attr('stroke-width', 10)
+ expect(rect.node.getAttribute('stroke-width')).toBe('10')
+ rect.attr('stroke-width', null)
+ expect(rect.node.getAttribute('stroke-width')).toBe(null)
+ })
+ it('correctly parses numeric values as a getter', function() {
+ rect.attr('stroke-width', 11)
+ expect(rect.node.getAttribute('stroke-width')).toBe('11')
+ expect(rect.attr('stroke-width')).toBe(11)
+ })
+ it('correctly parses negative numeric values as a getter', function() {
+ rect.attr('x', -120)
+ expect(rect.node.getAttribute('x')).toBe('-120')
+ expect(rect.attr('x')).toBe(-120)
+ })
+ it('falls back on default values if attribute is not present', function() {
+ expect(rect.attr('stroke-linejoin')).toBe('miter')
+ })
+ it('gets the "style" attribute as a string', function() {
+ rect.style('cursor', 'pointer')
+ expect(rect.node.style.cursor).toBe('pointer')
+ })
+ it('redirects to the style() method when setting a style string', function() {
+ rect.attr('style', 'cursor:move;')
+ expect(rect.node.style.cursor).toBe('move')
+ })
+ it('removes style attribute on node if the style is empty', function() {
+ rect.style('cursor', 'move')
+ rect.style('cursor', '')
+ expect(rect.style.cursor).toBe(undefined)
+ })
+ it('acts as a global getter when no arguments are given', function() {
+ rect.fill('#ff0066')
+ expect(rect.attr().fill).toBe('#ff0066')
+ })
+ it('correctly parses numeric values as a global getter', function() {
+ rect.stroke({ width: 20 })
+ expect(rect.attr()['stroke-width']).toBe(20)
+ })
+ it('correctly parses negative numeric values as a global getter', function() {
+ rect.x(-30)
+ expect(rect.attr().x).toBe(-30)
+ })
+ it('leaves unit values alone as a global getter', function() {
+ rect.attr('x', '69%')
+ expect(rect.attr().x).toBe('69%')
+ })
+ })
+
+ describe('id()', function() {
+ var rect
+
+ beforeEach(function() {
+ rect = draw.rect(100,100)
+ })
+
+ it('gets the value if the id attribute without an argument', function() {
+ expect(rect.id()).toBe(rect.attr('id'))
+ })
+ it('sets the value of the id', function() {
+ rect.id('new_id')
+ expect(rect.attr('id')).toBe('new_id')
+ })
+ })
+
+ describe('style()', function() {
+ it('sets the style with key and value arguments', function() {
+ var rect = draw.rect(100,100).style('cursor', 'crosshair')
+ expect(stripped(rect.node.style.cssText)).toBe('cursor:crosshair;')
+ })
+ it('sets multiple styles with an object as the first argument', function() {
+ var rect = draw.rect(100,100).style({ cursor: 'help', display: 'block' })
+ expect(stripped(rect.node.style.cssText)).toMatch(/cursor:help;/)
+ expect(stripped(rect.node.style.cssText)).toMatch(/display:block;/)
+ expect(stripped(rect.node.style.cssText).length).toBe(('display:block;cursor:help;').length)
+ })
+ it('gets a style with a string key as the fists argument', function() {
+ var rect = draw.rect(100,100).style({ cursor: 'progress', display: 'block' })
+ expect(rect.style('cursor')).toBe('progress')
+ })
+ it('gets a style with a string key as the fists argument', function() {
+ var rect = draw.rect(100,100).style({ cursor: 's-resize', display: 'none' })
+ expect(stripped(rect.style())).toMatch(/display:none;/)
+ expect(stripped(rect.style())).toMatch(/cursor:s-resize;/)
+ expect(stripped(rect.style()).length).toBe(('cursor:s-resize;display:none;').length)
+ })
+ it('removes a style if the value is an empty string', function() {
+ var rect = draw.rect(100,100).style({ cursor: 'n-resize', display: '' })
+ expect(stripped(rect.style())).toBe('cursor:n-resize;')
+ })
+ it('removes a style if the value explicitly set to null', function() {
+ var rect = draw.rect(100,100).style('cursor', 'w-resize')
+ expect(stripped(rect.style())).toBe('cursor:w-resize;')
+ rect.style('cursor', null)
+ expect(rect.style()).toBe('')
+ })
+ })
+
+ describe('transform()', function() {
+ var rect, ctm
+
+ beforeEach(function() {
+ rect = draw.rect(100,100)
+ })
+
+ it('gets the current transformations', function() {
+ expect(rect.transform()).toEqual(new SVG.Matrix(rect).extract())
+ })
+ it('sets the translation of and element', function() {
+ rect.transform({ x: 10, y: 11 })
+ expect(rect.node.getAttribute('transform')).toBe('matrix(1,0,0,1,10,11)')
+ })
+ it('performs an absolute translation', function() {
+ rect.transform({ x: 10, y: 11 }).transform({ x: 20, y: 21 })
+ expect(rect.node.getAttribute('transform')).toBe('matrix(1,0,0,1,20,21)')
+ })
+ it('performs a relative translation when relative is set to true', function() {
+ rect.transform({ x: 10, y: 11 }).transform({ x: 20, y: 21, relative: true })
+ expect(rect.node.getAttribute('transform')).toBe('matrix(1,0,0,1,30,32)')
+ })
+ it('performs a relative translation with relative flag', function() {
+ rect.transform({ x: 10, y: 11 }).transform({ x: 20, y: 21 }, true)
+ expect(rect.node.getAttribute('transform')).toBe('matrix(1,0,0,1,30,32)')
+ })
+ it('sets the scaleX and scaleY of and element', function() {
+ rect.transform({ scaleX: 0.5, scaleY: 2 })
+ expect(rect.node.getAttribute('transform')).toBe('matrix(0.5,0,0,2,25,-50)')
+ })
+ it('performs a uniform scale with scale given', function() {
+ rect.transform({ scale: 3 })
+ expect(rect.node.getAttribute('transform')).toBe('matrix(3,0,0,3,-100,-100)')
+ })
+ it('performs an absolute scale by default', function() {
+ rect.transform({ scale: 3 }).transform({ scale: 0.5 })
+ expect(rect.node.getAttribute('transform')).toBe('matrix(0.5,0,0,0.5,25,25)')
+ })
+ it('performs a relative scale with a relative flag', function() {
+ rect.transform({ scaleX: 0.5, scaleY: 2 }).transform({ scaleX: 3, scaleY: 4 }, true)
+ expect(rect.node.getAttribute('transform')).toBe('matrix(1.5,0,0,8,-25,-350)')
+ })
+ it('sets the skewX of and element with center on the element', function() {
+ ctm = rect.transform({ skewX: 10 }).ctm()
+ expect(ctm.a).toBe(1)
+ expect(ctm.b).toBe(0)
+ expect(ctm.c).toBeCloseTo(0.17632698070846498)
+ expect(ctm.d).toBe(1)
+ expect(ctm.e).toBeCloseTo(-8.81634903542325)
+ expect(ctm.f).toBe(0)
+ })
+ it('sets the skewX of and element with given center', function() {
+ ctm = rect.transform({ skewX: 10, cx: 0, cy: 0 }).ctm()
+ expect(ctm.a).toBe(1)
+ expect(ctm.b).toBe(0)
+ expect(ctm.c).toBeCloseTo(0.17632698070846498)
+ expect(ctm.d).toBe(1)
+ expect(ctm.e).toBe(0)
+ expect(ctm.f).toBe(0)
+ })
+ it('sets the skewY of and element', function() {
+ ctm = rect.transform({ skewY: -10, cx: 0, cy: 0 }).ctm()
+ expect(ctm.a).toBe(1)
+ expect(ctm.b).toBeCloseTo(-0.17632698070846498)
+ expect(ctm.c).toBe(0)
+ expect(ctm.d).toBe(1)
+ expect(ctm.e).toBe(0)
+ expect(ctm.f).toBe(0)
+ })
+ it('rotates the element around its centre if no rotation point is given', function() {
+ ctm = rect.center(100, 100).transform({ rotation: 45 }).ctm()
+ expect(ctm.a).toBeCloseTo(0.7071068286895752)
+ expect(ctm.b).toBeCloseTo(0.7071068286895752)
+ expect(ctm.c).toBeCloseTo(-0.7071068286895752)
+ expect(ctm.d).toBeCloseTo(0.7071068286895752)
+ expect(ctm.e).toBeCloseTo(100)
+ expect(ctm.f).toBeCloseTo(-41.421356201171875)
+ expect(rect.transform('rotation')).toBe(45)
+ })
+ it('rotates the element around the given rotation point', function() {
+ ctm = rect.transform({ rotation: 55, cx: 80, cy:2 }).ctm()
+ expect(ctm.a).toBeCloseTo(0.5735765099525452)
+ expect(ctm.b).toBeCloseTo(0.8191521167755127)
+ expect(ctm.c).toBeCloseTo(-0.8191521167755127)
+ expect(ctm.d).toBeCloseTo(0.5735765099525452)
+ expect(ctm.e).toBeCloseTo(35.75218963623047)
+ expect(ctm.f).toBeCloseTo(-64.67931365966797)
+ })
+ it('transforms element using a matrix', function() {
+ rect.transform({ a: 0.5, c: 0.5 })
+ expect(rect.node.getAttribute('transform')).toBe('matrix(0.5,0,0.5,1,0,0)')
+ })
+ })
+
+ describe('untransform()', function() {
+ var circle
+
+ beforeEach(function() {
+ circle = draw.circle(100).translate(50, 100)
+ })
+
+ it('removes the transform attribute', function() {
+ expect(circle.node.getAttribute('transform')).toBe('matrix(1,0,0,1,50,100)')
+ circle.untransform()
+ expect(circle.node.getAttribute('transform')).toBeNull()
+ })
+ it('resets the current transform matix', function() {
+ expect(circle.ctm()).toEqual(new SVG.Matrix(1,0,0,1,50,100))
+ circle.untransform()
+ expect(circle.ctm()).toEqual(new SVG.Matrix)
+ })
+ })
+
+ describe('ctm()', function() {
+ var rect
+
+ beforeEach(function() {
+ rect = draw.rect(100, 100)
+ })
+
+ it('gets the current transform matrix of the element', function() {
+ rect.translate(10, 20)
+ expect(rect.ctm().toString()).toBe('matrix(1,0,0,1,10,20)')
+ })
+ it('returns an instance of SVG.Matrix', function() {
+ expect(rect.ctm() instanceof SVG.Matrix).toBeTruthy()
+ })
+ })
+
+ describe('data()', function() {
+ it('sets a data attribute and convert value to json', function() {
+ var rect = draw.rect(100,100).data('test', 'value')
+ expect(rect.node.getAttribute('data-test')).toBe('value')
+ })
+ it('sets a data attribute and not convert value to json if flagged raw', function() {
+ var rect = draw.rect(100,100).data('test', 'value', true)
+ expect(rect.node.getAttribute('data-test')).toBe('value')
+ })
+ it('sets multiple data attributes and convert values to json when an object is passed', function() {
+ var rect = draw.rect(100,100).data({
+ forbidden: 'fruit'
+ , multiple: {
+ values: 'in'
+ , an: 'object'
+ }
+ })
+ expect(rect.node.getAttribute('data-forbidden')).toBe('fruit')
+ expect(rect.node.getAttribute('data-multiple')).toEqual('{"values":"in","an":"object"}')
+ })
+ it('gets data value if only one argument is passed', function() {
+ var rect = draw.rect(100,100).data('test', 101)
+ expect(rect.data('test')).toBe(101)
+ })
+ it('maintains data type for a number', function() {
+ var rect = draw.rect(100,100).data('test', 101)
+ expect(typeof rect.data('test')).toBe('number')
+ })
+ it('maintains data type for an object', function() {
+ var rect = draw.rect(100,100).data('test', { string: 'value', array: [1,2,3] })
+ expect(typeof rect.data('test')).toBe('object')
+ expect(Array.isArray(rect.data('test').array)).toBe(true)
+ })
+ })
+
+ describe('remove()', function() {
+ it('removes an element and return it', function() {
+ var rect = draw.rect(100,100)
+ expect(rect.remove()).toBe(rect)
+ })
+ it('removes an element from its parent', function() {
+ var rect = draw.rect(100,100)
+ rect.remove()
+ expect(draw.has(rect)).toBe(false)
+ })
+ })
+
+ describe('addTo()', function() {
+ it('adds an element to a given parent and returns itself', function() {
+ var rect = draw.rect(100,100)
+ , group = draw.group()
+
+ expect(rect.addTo(group)).toBe(rect)
+ expect(rect.parent()).toBe(group)
+ })
+ })
+
+ describe('putIn()', function() {
+ it('adds an element to a given parent and returns parent', function() {
+ var rect = draw.rect(100,100)
+ , group = draw.group()
+
+ expect(rect.putIn(group)).toBe(group)
+ expect(rect.parent()).toBe(group)
+ })
+ })
+
+ describe('rbox()', function() {
+ it('returns an instance of SVG.RBox', function() {
+ var rect = draw.rect(100,100)
+ expect(rect.rbox() instanceof SVG.RBox).toBe(true)
+ })
+ it('returns the correct rectangular box', function() {
+ var rect = draw.size(200, 150).viewbox(0, 0, 200, 150).rect(105, 210).move(2, 12)
+ var box = rect.rbox()
+ expect(box.x).toBeCloseTo(2,0)
+ expect(box.y).toBeCloseTo(12)
+ expect(box.cx).toBeCloseTo(54.5)
+ expect(box.cy).toBeCloseTo(117)
+ expect(box.width).toBe(105)
+ expect(box.height).toBe(210)
+ })
+ it('returns the correct rectangular box within a viewbox', function() {
+ var rect = draw.size(200,150).viewbox(0,0,100,75).rect(105,210).move(2,12)
+ var box = rect.rbox()
+ expect(box.x).toBeCloseTo(4)
+ expect(box.y).toBeCloseTo(24)
+ expect(box.cx).toBeCloseTo(56.5)
+ expect(box.cy).toBeCloseTo(129)
+ expect(box.width).toBe(105)
+ expect(box.height).toBe(210)
+ })
+ })
+
+ describe('doc()', function() {
+ it('returns the parent document', function() {
+ var rect = draw.rect(100,100)
+ expect(rect.doc()).toBe(draw)
+ })
+ })
+
+ describe('parent()', function() {
+ it('contains the parent svg', function() {
+ var rect = draw.rect(100,100)
+ expect(rect.parent()).toBe(draw)
+ })
+ it('contains the parent group when in a group', function() {
+ var group = draw.group()
+ , rect = group.rect(100,100)
+ expect(rect.parent()).toBe(group)
+ })
+ it('contains the parent which matches type', function() {
+ var group = draw.group()
+ , rect = group.rect(100,100)
+ expect(rect.parent(SVG.Doc)).toBe(draw)
+ })
+ it('contains the parent which matches selector', function() {
+ var group1 = draw.group().addClass('test')
+ , group2 = group1.group()
+ , rect = group2.rect(100,100)
+ expect(rect.parent('.test')).toBe(group1)
+ })
+ })
+
+ describe('parents()', function() {
+ it('returns array of parent up to but not including the dom element filtered by type', function() {
+ var group1 = draw.group().addClass('test')
+ , group2 = group1.group()
+ , rect = group2.rect(100,100)
+
+ expect(rect.parents('.test')[0]).toBe(group1)
+ expect(rect.parents(SVG.G)[0]).toBe(group2)
+ expect(rect.parents(SVG.G)[1]).toBe(group1)
+ expect(rect.parents().length).toBe(3)
+ })
+ })
+
+ describe('clone()', function() {
+ var rect, group, circle
+
+ beforeEach(function() {
+ rect = draw.rect(100,100).center(321,567).fill('#f06')
+ group = draw.group().add(rect)
+ circle = group.circle(100)
+ })
+
+ it('makes an exact copy of the element', function() {
+ clone = rect.clone()
+ expect(clone.attr('id', null).attr()).toEqual(rect.attr('id', null).attr())
+ })
+ it('assigns a new id to the cloned element', function() {
+ clone = rect.clone()
+ expect(clone.attr('id')).not.toBe(rect.attr('id'))
+ })
+ it('copies all child nodes as well', function() {
+ clone = group.clone()
+ expect(clone.children().length).toBe(group.children().length)
+ })
+ it('assigns a new id to cloned child elements', function() {
+ clone = group.clone()
+ expect(clone.attr('id')).not.toEqual(group.attr('id'))
+ expect(clone.get(0).attr('id')).not.toBe(group.get(0).attr('id'))
+ expect(clone.get(1).attr('id')).not.toBe(group.get(1).attr('id'))
+ })
+ it('inserts the clone after the cloned element', function() {
+ clone = rect.clone()
+ expect(rect.next()).toBe(clone)
+ })
+ })
+
+ describe('toString()', function() {
+ it('returns the element id', function() {
+ var rect = draw.rect(100,100).center(321,567).fill('#f06')
+ expect(rect + '').toBe(rect.attr('id'))
+ })
+ })
+
+ describe('replace()', function() {
+ it('replaces the original element by another given element', function() {
+ var rect = draw.rect(100,100).center(321,567).fill('#f06')
+ var circle = draw.circle(200)
+ var rectIndex = draw.children().indexOf(rect)
+
+ rect.replace(circle)
+
+ expect(rectIndex).toBe(draw.children().indexOf(circle))
+ })
+ it('removes the original element', function() {
+ var rect = draw.rect(100,100).center(321,567).fill('#f06')
+
+ rect.replace(draw.circle(200))
+
+ expect(draw.has(rect)).toBe(false)
+ })
+ it('returns the new element', function() {
+ var circle = draw.circle(200)
+ var element = draw.rect(100,100).center(321,567).fill('#f06').replace(circle)
+
+ expect(element).toBe(circle)
+ })
+ })
+
+ describe('classes()', function() {
+ it('returns an array of classes on the node', function() {
+ var element = draw.rect(100,100)
+ element.node.setAttribute('class', 'one two')
+ expect(element.classes()).toEqual(['one', 'two'])
+ })
+ })
+
+ describe('hasClass()', function() {
+ it('returns true if the node has the class', function() {
+ var element = draw.rect(100,100)
+ element.node.setAttribute('class', 'one')
+ expect(element.hasClass('one')).toBeTruthy()
+ })
+
+ it('returns false if the node does not have the class', function() {
+ var element = draw.rect(100,100)
+ element.node.setAttribute('class', 'one')
+ expect(element.hasClass('two')).toBeFalsy()
+ })
+ })
+
+ describe('addClass()', function() {
+ it('adds the class to the node', function() {
+ var element = draw.rect(100,100)
+ element.addClass('one')
+ expect(element.hasClass('one')).toBeTruthy()
+ })
+
+ it('does not add duplicate classes', function() {
+ var element = draw.rect(100,100)
+ element.addClass('one')
+ element.addClass('one')
+ expect(element.node.getAttribute('class')).toEqual('one')
+ })
+
+ it('returns the svg instance', function() {
+ var element = draw.rect(100,100)
+ expect(element.addClass('one')).toEqual(element)
+ })
+ })
+
+ describe('removeClass()', function() {
+ it('removes the class from the node when the class exists', function() {
+ var element = draw.rect(100,100)
+ element.addClass('one')
+ element.removeClass('one')
+ expect(element.hasClass('one')).toBeFalsy()
+ })
+
+ it('does nothing when the class does not exist', function() {
+ var element = draw.rect(100,100)
+ element.removeClass('one')
+ expect(element.hasClass('one')).toBeFalsy()
+ })
+
+ it('returns the element', function() {
+ var element = draw.rect(100,100)
+ expect(element.removeClass('one')).toEqual(element)
+ })
+ })
+
+ describe('toggleClass()', function() {
+ it('adds the class when it does not already exist', function(){
+ var element = draw.rect(100,100)
+ element.toggleClass('one')
+ expect(element.hasClass('one')).toBeTruthy()
+ })
+ it('removes the class when it already exists', function(){
+ var element = draw.rect(100,100)
+ element.addClass('one')
+ element.toggleClass('one')
+ expect(element.hasClass('one')).toBeFalsy()
+ })
+ it('returns the svg instance', function() {
+ var element = draw.rect(100,100)
+ expect(element.toggleClass('one')).toEqual(element)
+ })
+ })
+
+ describe('reference()', function() {
+ it('gets a referenced element from a given attribute', function() {
+ var rect = draw.defs().rect(100, 100)
+ , use = draw.use(rect)
+ , mark = draw.marker(10, 10)
+ , path = draw.path(svgPath).marker('end', mark)
+
+ expect(use.reference('href')).toBe(rect)
+ expect(path.reference('marker-end')).toBe(mark)
+ })
+ })
+
+ describe('svg()', function() {
+ describe('without an argument', function() {
+ it('returns full raw svg when called on the main svg doc', function() {
+ draw.size(100,100).rect(100,100).id(null)
+ draw.circle(100).fill('#f06').id(null)
+ expect(draw.svg()).toBe('<svg id="SvgjsSvg1000" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" width="100" height="100"><rect width="100" height="100"></rect><circle r="50" cx="50" cy="50" fill="#ff0066"></circle></svg>')
+ })
+ it('returns partial raw svg when called on a sub group', function() {
+ var group = draw.group().id(null)
+ group.rect(100,100).id(null)
+ group.circle(100).fill('#f06').id(null)
+ expect(group.svg()).toBe('<g><rect width="100" height="100"></rect><circle r="50" cx="50" cy="50" fill="#ff0066"></circle></g>')
+ })
+ it('returns a single element when called on an element', function() {
+ var group = draw.group().id(null)
+ group.rect(100,100).id(null)
+ var circle = group.circle(100).fill('#f06').id(null)
+ expect(circle.svg()).toBe('<circle r="50" cx="50" cy="50" fill="#ff0066"></circle>')
+ })
+ })
+ describe('with raw svg given', function() {
+ it('imports a full svg document', function() {
+ draw.svg('<svg id="SvgjsSvg1000" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100" viewBox="0 0 50 50"><rect id="SvgjsRect1183" width="100" height="100"></rect><circle id="SvgjsCircle1184" r="50" cx="25" cy="25" fill="#ff0066"></circle></svg>')
+ expect(draw.get(0).type).toBe('svg')
+ expect(draw.get(0).children().length).toBe(2)
+ expect(draw.get(0).get(0).type).toBe('rect')
+ expect(draw.get(0).get(1).type).toBe('circle')
+ expect(draw.get(0).get(1).attr('fill')).toBe('#ff0066')
+ })
+ it('imports partial svg content', function() {
+ draw.svg('<g id="SvgjsG1185"><rect id="SvgjsRect1186" width="100" height="100"></rect><circle id="SvgjsCircle1187" r="50" cx="25" cy="25" fill="#ff0066"></circle></g>')
+ expect(draw.get(0).type).toBe('g')
+ expect(draw.get(0).get(0).type).toBe('rect')
+ expect(draw.get(0).get(1).type).toBe('circle')
+ expect(draw.get(0).get(1).attr('fill')).toBe('#ff0066')
+ })
+ it('does not import on single elements, even with an argument it acts as a getter', function() {
+ var rect = draw.rect(100,100).id(null)
+ , result = rect.svg('<circle r="300"></rect>')
+ expect(result).toBe('<rect width="100" height="100"></rect>')
+ })
+ })
+ })
+
+ describe('writeDataToDom()', function() {
+ it('set all properties in el.dom to the svgjs:data attribute', function(){
+ var rect = draw.rect(100,100)
+ rect.dom.foo = 'bar'
+ rect.dom.number = new SVG.Number('3px')
+
+ rect.writeDataToDom()
+
+ expect(rect.attr('svgjs:data')).toBe('{"foo":"bar","number":"3px"}')
+ })
+ })
+
+ describe('setData()', function() {
+ it('read all data from the svgjs:data attribute and assign it to el.dom', function(){
+ var rect = draw.rect(100,100)
+
+ rect.attr('svgjs:data', '{"foo":"bar","number":"3px"}')
+ rect.setData(JSON.parse(rect.attr('svgjs:data')))
+
+ expect(rect.dom.foo).toBe('bar')
+ expect(rect.dom.number).toBe('3px')
+ })
+ })
+
+ describe('point()', function() {
+ it('creates a point from screen coordinates transformed in the elements space', function(){
+ var rect = draw.rect(100,100)
+ expect(rect.point(2,5).x).toBeCloseTo(-6)
+ expect(rect.point(2,5).y).toBeCloseTo(-3)
+ })
+ })
+})
diff --git a/src/element.js b/src/element.js
index 75d77dc..552fd5b 100644
--- a/src/element.js
+++ b/src/element.js
@@ -256,5 +256,8 @@ SVG.Element = SVG.invent({
this.dom = o
return this
}
+ , is: function(obj){
+ return is(this, obj)
+ }
}
})
diff --git a/src/event.js b/src/event.js
index 66cf6c3..e94b6e1 100644
--- a/src/event.js
+++ b/src/event.js
@@ -30,6 +30,7 @@
// Initialize listeners stack
SVG.listeners = []
SVG.handlerMap = []
+SVG.listenerId = 0
// Add event binder in the SVG namespace
SVG.on = function(node, event, listener, binding) {
@@ -45,8 +46,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)
@@ -59,8 +63,11 @@ SVG.off = function(node, event, listener) {
, ns = event && event.split('.')[1]
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
diff --git a/src/fx.js b/src/fx.js
index 758ce50..7514a40 100644
--- a/src/fx.js
+++ b/src/fx.js
@@ -1,444 +1,641 @@
+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}
+}
+
+var someVar = 0
+
+SVG.Situation = SVG.invent({
+
+ create: function(o){
+ this.init = false
+ this.reversed = false
+
+ this.duration = o.duration
+ this.delay = o.delay
+
+ this.start = +new Date() + this.delay
+ this.end = this.start + this.duration
+ this.easing = o.easing
+
+ 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 of the form:
+ // [A, B, C] or, [A, [25, 0, 0]] where ABC are matrixes and the array represents a rotation
+ ]
+
+ this.once = {
+ // functions to fire at a specific position
+ // e.g. "0.5": function foo(){}
+ }
+
+ }
+
+})
+
+SVG.Delay = function(delay){
+ this.delay = delay
+}
+
SVG.FX = SVG.invent({
- // Initialize FX object
+
create: function(element) {
- // store target element
- this.target = element
+ this._target = element
+ this.situations = []
+ this.active = false
+ this.current = null
+ this.paused = false
+ this.lastPos = 0
+ this.pos = 0
}
- // Add class methods
, extend: {
- // Add animation parameters and start animation
- animate: function(d, ease, delay) {
- var akeys, skeys, key
- , element = this.target
- , fx = this
-
- // dissect object if one is passed
- if (typeof d == 'object') {
- delay = d.delay
- ease = d.ease
- d = d.duration
+
+ /**
+ * 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
}
- // ensure default duration and easing
- d = d == '=' ? d : d == null ? 1000 : new SVG.Number(d).valueOf()
- ease = ease || '<>'
-
- // process values
- fx.at = function(pos) {
- var i
-
- // normalise pos
- pos = pos < 0 ? 0 : pos > 1 ? 1 : pos
-
- // collect attribute keys
- if (akeys == null) {
- akeys = []
- for (key in fx.attrs)
- akeys.push(key)
-
- // make sure morphable elements are scaled, translated and morphed all together
- if (element.morphArray && (fx.destination.plot || akeys.indexOf('points') > -1)) {
- // get destination
- var box
- , p = new element.morphArray(fx.destination.plot || fx.attrs.points || element.array())
-
- // add size
- if (fx.destination.size)
- p.size(fx.destination.size.width.to, fx.destination.size.height.to)
-
- // add movement
- box = p.bbox()
- if (fx.destination.x)
- p.move(fx.destination.x.to, box.y)
- else if (fx.destination.cx)
- p.move(fx.destination.cx.to - box.width / 2, box.y)
-
- box = p.bbox()
- if (fx.destination.y)
- p.move(box.x, fx.destination.y.to)
- else if (fx.destination.cy)
- p.move(box.x, fx.destination.cy.to - box.height / 2)
-
- // reset destination values
- fx.destination = {
- plot: element.array().morph(p)
- }
- }
- }
+ var situation = new SVG.Situation({
+ duration: o || 1000,
+ delay: delay || 0,
+ easing: SVG.easing[easing || '-'] || easing
+ })
- // collect style keys
- if (skeys == null) {
- skeys = []
- for (key in fx.styles)
- skeys.push(key)
- }
+ this.queue(situation)
- // apply easing
- pos = ease == '<>' ?
- (-Math.cos(pos * Math.PI) / 2) + 0.5 :
- ease == '>' ?
- Math.sin(pos * Math.PI / 2) :
- ease == '<' ?
- -Math.cos(pos * Math.PI / 2) + 1 :
- ease == '-' ?
- pos :
- typeof ease == 'function' ?
- ease(pos) :
- pos
-
- // run plot function
- if (fx.destination.plot) {
- element.plot(fx.destination.plot.at(pos))
-
- } else {
- // run all x-position properties
- if (fx.destination.x)
- element.x(fx.destination.x.at(pos))
- else if (fx.destination.cx)
- element.cx(fx.destination.cx.at(pos))
-
- // run all y-position properties
- if (fx.destination.y)
- element.y(fx.destination.y.at(pos))
- else if (fx.destination.cy)
- element.cy(fx.destination.cy.at(pos))
-
- // run all size properties
- if (fx.destination.size)
- element.size(fx.destination.size.width.at(pos), fx.destination.size.height.at(pos))
- }
+ return this
+ }
- // run all viewbox properties
- if (fx.destination.viewbox)
- element.viewbox(
- fx.destination.viewbox.x.at(pos)
- , fx.destination.viewbox.y.at(pos)
- , fx.destination.viewbox.width.at(pos)
- , fx.destination.viewbox.height.at(pos)
- )
-
- // run leading property
- if (fx.destination.leading)
- element.leading(fx.destination.leading.at(pos))
-
- // animate attributes
- for (i = akeys.length - 1; i >= 0; i--)
- element.attr(akeys[i], at(fx.attrs[akeys[i]], pos))
-
- // animate styles
- for (i = skeys.length - 1; i >= 0; i--)
- element.style(skeys[i], at(fx.styles[skeys[i]], pos))
-
- // callback for each keyframe
- if (fx.situation.during)
- fx.situation.during.call(element, pos, function(from, to) {
- return at({ from: from, to: to }, pos)
- })
- }
-
- if (typeof d === 'number') {
- // delay animation
- this.timeout = setTimeout(function() {
- var start = new Date().getTime()
-
- // initialize situation object
- fx.situation.start = start
- fx.situation.play = true
- fx.situation.finish = start + d
- fx.situation.duration = d
- fx.situation.ease = ease
-
- // render function
- fx.render = function() {
-
- if (fx.situation.play === true) {
- // calculate pos
- var time = new Date().getTime()
- , pos = time > fx.situation.finish ? 1 : (time - fx.situation.start) / d
-
- // reverse pos if animation is reversed
- if (fx.situation.reversing)
- pos = -pos + 1
-
- // process values
- fx.at(pos)
-
- // finish off animation
- if (time > fx.situation.finish) {
- if (fx.destination.plot)
- element.plot(new SVG.PointArray(fx.destination.plot.destination).settle())
-
- if (fx.situation.loop === true || (typeof fx.situation.loop == 'number' && fx.situation.loop > 0)) {
- // register reverse
- if (fx.situation.reverse)
- fx.situation.reversing = !fx.situation.reversing
-
- if (typeof fx.situation.loop == 'number') {
- // reduce loop count
- if (!fx.situation.reverse || fx.situation.reversing)
- --fx.situation.loop
-
- // remove last loop if reverse is disabled
- if (!fx.situation.reverse && fx.situation.loop == 1)
- --fx.situation.loop
- }
-
- fx.animate(d, ease, delay)
- } else {
- fx.situation.after ? fx.situation.after.apply(element, [fx]) : fx.stop()
- }
-
- } else {
- fx.animationFrame = requestAnimationFrame(fx.render)
- }
- } else {
- fx.animationFrame = requestAnimationFrame(fx.render)
- }
-
- }
+ /**
+ * 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)
+
+ return this.queue(delay)
+ }
- // start animation
- fx.render()
-
- }, new SVG.Number(delay).valueOf())
+ /**
+ * 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 this
+
+ return this._target
}
- // Get bounding box of target element
- , bbox: function() {
- return this.target.bbox()
- }
- // Add animatable attributes
- , attr: function(a, v) {
- // 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.target.attr(a)
-
- // detect format
- if (a == 'transform') {
- // merge given transformation with an existing one
- if (this.attrs[a])
- v = this.attrs[a].destination.multiply(v)
-
- // prepare matrix for morphing
- this.attrs[a] = (new SVG.Matrix(this.target)).morph(v)
-
- // 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 {
- this.attrs[a] = SVG.Color.isColor(v) ?
- // prepare color for morphing
- new SVG.Color(from).morph(v) :
- SVG.regex.numberAndUnit.test(v) ?
- // prepare number for morphing
- new SVG.Number(from).morph(v) :
- // prepare for plain morphing
- { from: from, to: v }
- }
+ // returns the position at a given time
+ , timeToPos: function(timestamp){
+ return (timestamp - this.current.start) / (this.current.duration)
+ }
+
+ // returns the timestamp from a given positon
+ , posToTime: function(pos){
+ 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))
+ }
+
+ // cancels the animationframe
+ // TODO: remove this in favour of the oneliner
+ , stopAnimFrame: function(){
+ cancelAnimationFrame(this.animationFrame)
+ }
+
+ // 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
}
- // Add animatable styles
- , style: function(s, v) {
- if (typeof s == 'object')
- for (var key in s)
- this.style(key, s[key])
-
- else
- this.styles[s] = { from: this.target.style(s), to: v }
-
+
+ /**
+ * 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
}
- // Animatable x-axis
- , x: function(x) {
- this.destination.x = new SVG.Number(this.target.x()).morph(x)
-
+
+ /**
+ * pulls next element from the queue and execute it
+ * @return this
+ */
+ , dequeue: function(){
+ // stop current animation
+ this.current && this.current.stop && this.current.stop()
+
+ // 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()
+ }
+
+ }
+
return this
}
- // Animatable y-axis
- , y: function(y) {
- this.destination.y = new SVG.Number(this.target.y()).morph(y)
-
+
+ // 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
+ 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 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{
+ s.attrs[i].value = this.target().attr(i)// + s.attrs[i].value
+ }
+ }
+
+ for(i in s.styles){
+ s.styles[i].value = this.target().style(i)
+ }
+
+ s.transformations = this.target().matrixify()
+
+ s.init = true
return this
}
- // Animatable center x-axis
- , cx: function(x) {
- this.destination.cx = new SVG.Number(this.target.cx()).morph(x)
-
+ , clearQueue: function(){
+ this.situations = []
return this
}
- // Animatable center y-axis
- , cy: function(y) {
- this.destination.cy = new SVG.Number(this.target.cy()).morph(y)
-
+ , clearCurrent: function(){
+ this.current = null
return this
}
- // 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 = this.target.bbox()
-
- this.destination.size = {
- width: new SVG.Number(box.width).morph(width)
- , height: new SVG.Number(box.height).morph(height)
- }
+ /** 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()
}
-
- return this
+
+ this.active = false
+
+ if(jumpToEnd){
+ this.seek(1)
+ }
+
+ this.stopAnimFrame()
+ clearTimeout(this.timeout)
+
+ return this.clearCurrent()
}
- // Add animatable plot
- , plot: function(p) {
- this.destination.plot = p
+ /** 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
}
- // Add leading method
- , leading: function(value) {
- if (this.target.destination.leading)
- this.destination.leading = new SVG.Number(this.target.destination.leading).morph(value)
- 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().current && this.stop(true, false));
+
+ return this.clearQueue().clearCurrent()
}
- // Add animatable viewbox
- , viewbox: function(x, y, width, height) {
- if (this.target instanceof SVG.Container) {
- var box = this.target.viewbox()
-
- this.destination.viewbox = {
- x: new SVG.Number(box.x).morph(x)
- , y: new SVG.Number(box.y).morph(y)
- , width: new SVG.Number(box.width).morph(width)
- , height: new SVG.Number(box.height).morph(height)
- }
- }
-
+
+ // set the internal animation pointer to the specified position and updates the visualisation
+ , seek: function(pos){
+ this.pos = pos
+ 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.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
}
- // Add animateable gradient update
- , update: function(o) {
- if (this.target instanceof SVG.Stop) {
- 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', new SVG.Number(o.offset))
- }
+ // pauses the animation
+ , pause: function(){
+ this.paused = true
+ this.stopAnimFrame()
+ clearTimeout(this.timeout)
return this
}
- // Add callback for each keyframe
- , during: function(during) {
- this.situation.during = during
-
+
+ // unpause the animation
+ , play: function(){
+ if(!this.paused) return this
+ this.paused = false
+ return this.seek(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
}
- // Callback after animation
- , after: function(after) {
- this.situation.after = after
-
+
+
+ /**
+ * 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 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()
+ , wrapper = 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
}
- // Make loopable
- , loop: function(times, reverse) {
- // store current loop and total loops
- this.situation.loop = this.situation.loops = times || true
- // make reversable
- this.situation.reverse = !!reverse
+ // adds a callback which is called whenever one animation step is performed
+ , during: function(fn){
+ var c = this.last()
+ , wrapper = function(e){
+ 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)
+
+ return this.after(function(){
+ this.off('during.fx', wrapper)
+ })
+ }
+
+ // 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
}
- // Stop running animation
- , stop: function(fulfill) {
- // fulfill animation
- if (fulfill === true) {
- this.animate(0)
+ // calls on every animation step for all animations
+ , duringAll: function(fn){
+ var wrapper = function(e){
+ fn.call(this, e.detail.pos, e.detail.eased, e.detail.fx, e.detail.situation)
+ }
- if (this.situation.after)
- this.situation.after.apply(this.target, [this])
+ this.target().off('during.fx', wrapper).on('during.fx', wrapper)
- } else {
- // stop current animation
- clearTimeout(this.timeout)
- cancelAnimationFrame(this.animationFrame);
+ return this.afterAll(function(){
+ this.off('during.fx', wrapper)
+ })
+ }
- // reset storage for properties
- this.attrs = {}
- this.styles = {}
- this.situation = {}
- this.destination = {}
+ /**
+ * 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
+
+ for(var i = 0, len = this.situations.length; i < len; ++i){
+ end += (situations[i].duration || 0) + (situations[i].delay || 0)
}
-
+
+ return (this.pos * this.current.duration + this.start - start) / (end - start)
+ }
+
+ , last: function(){
+ return this.situations.length ? this.situations[this.situations.length-1] : this.current
+ }
+
+ // 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
}
- // Pause running animation
- , pause: function() {
- if (this.situation.play === true) {
- this.situation.play = false
- this.situation.pause = new Date().getTime()
+
+ /** 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 position
+ if(!ignoreTime) this.pos = this.timeToPos(+new Date)
+
+ 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
+
+ // apply easing
+ var eased = this.current.easing(this.pos)
+
+ // call once-callbacks
+ for(var i in this.current.once){
+ if(i > this.lastPos && i <= eased){
+ 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, situation: this.current})
+
+ // apply the actual animation to every property
+ this.eachAt()
+
+ // do final code when situation is finished
+ if((this.pos == 1 && !this.current.reversed) || (this.current.reversed && this.pos == 0)){
+
+ // stop animation callback
+ cancelAnimationFrame(this.animationFrame)
+
+ // fire finished callback with current situation as parameter
+ this.target().fire('finished', {fx:this, situation: this.current})
+
+ if(!this.situations.length && !this.current && this.active){
+ this.target().fire('allfinished')
+ this.target().off('.fx')
+ 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
+
}
- // Play running animation
- , play: function() {
- if (this.situation.play === false) {
- var pause = new Date().getTime() - this.situation.pause
-
- this.situation.finish += pause
- this.situation.start += pause
- this.situation.play = true
+
+ // 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(), c = this.current
+
+ // apply animations which can be called trough a method
+ for(i in c.animations){
+
+ 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)
+
+ }
+
+ // apply animation which has to be applied with attr()
+ for(i in c.attrs){
+
+ 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)
+
+ }
+
+ // apply animation which has to be applied with style()
+ for(i in c.styles){
+
+ 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)
+
}
+ // animate transformations which has to be chained
+ 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)
+ }
+
+ return this
+
+ }
+
+
+ // adds an once-callback which is called at a specific position and never again
+ , once: function(pos, fn, isEased){
+
+ if(!isEased)pos = this.current.easing(pos)
+
+ this.current.once[pos] = fn
+
return this
}
-
+
}
- // Define parent class
, 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(d, ease, delay) {
- return (this.fx || (this.fx = new SVG.FX(this))).stop().animate(d, ease, delay)
+ 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))).delay(delay)
}
- // Stop current animation; this is an alias to the fx instance
- , stop: function(fulfill) {
+ , stop: function(jumpToEnd, clearQueue) {
if (this.fx)
- this.fx.stop(fulfill)
-
+ this.fx.stop(jumpToEnd, clearQueue)
+
return this
}
// Pause current animation
@@ -455,6 +652,162 @@ SVG.FX = SVG.invent({
return this
}
-
}
+
+})
+
+// MorphObj is used whenever no morphable object is given
+SVG.MorphObj = SVG.invent({
+
+ create: function(to){
+ // prepare color for morphing
+ if(SVG.Color.isColor(to)) return new SVG.Color().morph(to)
+ // prepare number for morphing
+ if(SVG.regex.numberAndUnit.test(to)) return new SVG.Number().morph(to)
+
+ // prepare for plain morphing
+ this.value = 0
+ this.destination = to
+ }
+
+, extend: {
+ at: function(pos, real){
+ return real < 1 ? this.value : this.destination
+ },
+
+ valueOf: function(){
+ return this.value
+ }
+ }
+
})
+
+SVG.extend(SVG.FX, {
+ // 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 {
+ // the MorphObj takes care about the right function used
+ this.add(a, new SVG.MorphObj(v), 'attrs')
+ }
+
+ return this
+ }
+ // Add animatable styles
+, style: function(s, v) {
+ if (typeof s == 'object')
+ for (var key in s)
+ this.style(key, s[key])
+
+ else
+ this.add(s, new SVG.MorphObj(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().morph(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().morph(y)
+ num.relative = relative
+ return this.add('y', num)
+ }
+ // Animatable center x-axis
+, cx: function(x) {
+ return this.add('cx', new SVG.Number().morph(x))
+ }
+ // Animatable center y-axis
+, cy: function(y) {
+ return this.add('cy', new SVG.Number().morph(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().morph(width))
+ .add('height', new SVG.Number().morph(height))
+
+ }
+
+ return this
+ }
+ // Add animatable plot
+, plot: function(p) {
+ return this.add('plot', this.target().array().morph(p))
+ }
+ // Add leading method
+, leading: function(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) {
+ 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
+ }
+}) \ No newline at end of file
diff --git a/src/group.js b/src/group.js
index 05d144c..9ec89f2 100644
--- a/src/group.js
+++ b/src/group.js
@@ -17,11 +17,11 @@ SVG.G = SVG.invent({
}
// 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() {
diff --git a/src/helpers.js b/src/helpers.js
index ee86dc7..4de813f 100644
--- a/src/helpers.js
+++ b/src/helpers.js
@@ -1,3 +1,7 @@
+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);
diff --git a/src/matrix.js b/src/matrix.js
index 9b3c28e..e727b2f 100644
--- a/src/matrix.js
+++ b/src/matrix.js
@@ -14,7 +14,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]]
}
@@ -32,6 +32,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)
@@ -46,6 +48,7 @@ SVG.Matrix = SVG.invent({
, d: this.d
, e: this.e
, f: this.f
+ , matrix: new SVG.Matrix(this)
}
}
// Clone matrix
diff --git a/src/sugar.js b/src/sugar.js
index 2a0404c..6e1bbf4 100644
--- a/src/sugar.js
+++ b/src/sugar.js
@@ -61,11 +61,11 @@ SVG.extend(SVG.Element, SVG.FX, {
}
// Relative move over x axis
, dx: function(x) {
- return this.x((this.target || this).x() + x)
+ return this.x((this instanceof SVG.FX ? 0 : this.x()) + x, true)
}
// Relative move over y axis
, dy: function(y) {
- return this.y((this.target || this).y() + y)
+ return this.y((this instanceof SVG.FX ? 0 : this.y()) + y, true)
}
// Relative move over x and y axes
, dmove: function(x, y) {
diff --git a/src/text.js b/src/text.js
index debc1fb..6dd4dee 100644
--- a/src/text.js
+++ b/src/text.js
@@ -112,8 +112,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)
})
diff --git a/src/textpath.js b/src/textpath.js
index 03dfbee..f091de9 100644
--- a/src/textpath.js
+++ b/src/textpath.js
@@ -3,7 +3,7 @@ SVG.TextPath = SVG.invent({
create: 'textPath'
// Inherit from
-, inherit: SVG.Element
+, inherit: SVG.Parent
// Define parent class
, parent: SVG.Text
diff --git a/src/transform.js b/src/transform.js
index 0e7a5a0..e6ad9ae 100644
--- a/src/transform.js
+++ b/src/transform.js
@@ -1,8 +1,8 @@
-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
@@ -10,20 +10,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
@@ -41,24 +32,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
-
// 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) {
@@ -114,7 +93,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
}
})
@@ -161,3 +217,133 @@ 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'
+ }
+
+})
diff --git a/src/viewbox.js b/src/viewbox.js
index ce9c902..bb106b0 100644
--- a/src/viewbox.js
+++ b/src/viewbox.js
@@ -1,63 +1,115 @@
-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]
+ }
+
+
}
-
-}
-
-//
-SVG.extend(SVG.ViewBox, {
- // Parse viewbox to string
- toString: function() {
- return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height
+
+, 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
+ ])
+
+ }
+
}
-
+
}) \ No newline at end of file