diff options
-rw-r--r-- | .gitignore | 6 | ||||
-rwxr-xr-x[-rw-r--r--] | CHANGELOG.md | 75 | ||||
-rw-r--r-- | Gemfile | 4 | ||||
-rw-r--r-- | Gemfile.lock | 17 | ||||
-rwxr-xr-x[-rw-r--r--] | LICENSE.txt (renamed from MIT-LICENSE) | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | README.md | 940 | ||||
-rw-r--r-- | Rakefile | 160 | ||||
-rwxr-xr-x[-rw-r--r--] | bower.json | 2 | ||||
-rwxr-xr-x[-rw-r--r--] | component.json | 2 | ||||
-rwxr-xr-x[-rw-r--r--] | dist/svg.js | 7335 | ||||
-rwxr-xr-x[-rw-r--r--] | dist/svg.min.js | 4 | ||||
-rw-r--r-- | gulpfile.js | 147 | ||||
-rw-r--r-- | package.json | 80 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/index.html | 105 | ||||
-rw-r--r-- | spec/lib/jasmine-1.3.1/MIT.LICENSE | 20 | ||||
-rw-r--r-- | spec/lib/jasmine-1.3.1/jasmine-html.js | 681 | ||||
-rw-r--r-- | spec/lib/jasmine-1.3.1/jasmine.css | 82 | ||||
-rw-r--r-- | spec/lib/jasmine-1.3.1/jasmine.js | 2600 | ||||
-rwxr-xr-x | spec/lib/jasmine-2.0.1/boot.js | 181 | ||||
-rwxr-xr-x | spec/lib/jasmine-2.0.1/console.js | 165 | ||||
-rwxr-xr-x | spec/lib/jasmine-2.0.1/jasmine-html.js | 390 | ||||
-rwxr-xr-x | spec/lib/jasmine-2.0.1/jasmine.css | 59 | ||||
-rwxr-xr-x | spec/lib/jasmine-2.0.1/jasmine.js | 2516 | ||||
-rwxr-xr-x | spec/lib/jasmine-2.0.1/jasmine_favicon.png | bin | 0 -> 1486 bytes | |||
-rw-r--r-- | spec/spec/adopter.js | 61 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/arrange.js | 1 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/array.js | 0 | ||||
-rw-r--r-- | spec/spec/bare.js | 40 | ||||
-rw-r--r-- | spec/spec/bbox.js | 43 | ||||
-rwxr-xr-x | spec/spec/boxes.js | 206 | ||||
-rw-r--r-- | spec/spec/circle.js | 185 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/clip.js | 10 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/color.js | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/container.js | 32 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/doc.js | 17 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/element.js | 307 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/ellipse.js | 43 | ||||
-rw-r--r-- | spec/spec/event.js | 88 | ||||
-rw-r--r-- | spec/spec/fx.js | 96 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/gradient.js | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/group.js | 28 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/helper.js | 21 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/hyperlink.js | 6 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/image.js | 6 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/line.js | 10 | ||||
-rw-r--r-- | spec/spec/marker.js | 4 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/mask.js | 8 | ||||
-rw-r--r-- | spec/spec/matrix.js | 298 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/memory.js | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/number.js | 68 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/path.js | 57 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/pattern.js | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/polygon.js | 53 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/polyline.js | 53 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/rect.js | 6 | ||||
-rw-r--r-- | spec/spec/regex.js | 27 | ||||
-rw-r--r-- | spec/spec/selector.js | 34 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/set.js | 13 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/svg.js | 2 | ||||
-rw-r--r-- | spec/spec/symbol.js | 16 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/text.js | 50 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/textpath.js | 52 | ||||
-rwxr-xr-x[-rw-r--r--] | spec/spec/use.js | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | src/arrange.js | 36 | ||||
-rwxr-xr-x[-rw-r--r--] | src/array.js | 5 | ||||
-rw-r--r-- | src/attr.js | 83 | ||||
-rw-r--r-- | src/bare.js | 45 | ||||
-rw-r--r-- | src/bbox.js | 58 | ||||
-rwxr-xr-x | src/boxes.js | 159 | ||||
-rwxr-xr-x[-rw-r--r--] | src/clip.js | 8 | ||||
-rwxr-xr-x[-rw-r--r--] | src/color.js | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | src/container.js | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | src/data.js | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | src/default.js | 30 | ||||
-rwxr-xr-x[-rw-r--r--] | src/defs.js | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | src/doc.js | 108 | ||||
-rwxr-xr-x[-rw-r--r--] | src/element.js | 353 | ||||
-rwxr-xr-x[-rw-r--r--] | src/ellipse.js | 86 | ||||
-rwxr-xr-x[-rw-r--r--] | src/event.js | 3 | ||||
-rwxr-xr-x[-rw-r--r--] | src/fx.js | 327 | ||||
-rwxr-xr-x[-rw-r--r--] | src/gradient.js | 37 | ||||
-rwxr-xr-x[-rw-r--r--] | src/group.js | 8 | ||||
-rw-r--r-- | src/helpers.js | 110 | ||||
-rwxr-xr-x[-rw-r--r--] | src/hyperlink.js | 2 | ||||
-rwxr-xr-x[-rw-r--r--] | src/image.js | 12 | ||||
-rw-r--r-- | src/inventor.js | 23 | ||||
-rwxr-xr-x[-rw-r--r--] | src/line.js | 69 | ||||
-rwxr-xr-x[-rw-r--r--] | src/mask.js | 4 | ||||
-rw-r--r-- | src/matrix.js | 168 | ||||
-rwxr-xr-x[-rw-r--r--] | src/memory.js | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | src/nested.js | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | src/number.js | 172 | ||||
-rwxr-xr-x[-rw-r--r--] | src/parent.js | 39 | ||||
-rwxr-xr-x[-rw-r--r--] | src/path.js | 14 | ||||
-rwxr-xr-x[-rw-r--r--] | src/patharray.js | 4 | ||||
-rwxr-xr-x[-rw-r--r--] | src/pattern.js | 0 | ||||
-rwxr-xr-x[-rw-r--r--] | src/pointarray.js | 13 | ||||
-rw-r--r-- | src/pointed.js | 25 | ||||
-rwxr-xr-x[-rw-r--r--] | src/poly.js | 32 | ||||
-rw-r--r-- | src/rbox.js | 76 | ||||
-rwxr-xr-x[-rw-r--r--] | src/rect.js | 20 | ||||
-rwxr-xr-x[-rw-r--r--] | src/regex.js | 53 | ||||
-rw-r--r-- | src/relative.js | 16 | ||||
-rw-r--r-- | src/selector.js | 21 | ||||
-rwxr-xr-x[-rw-r--r--] | src/set.js | 44 | ||||
-rwxr-xr-x[-rw-r--r--] | src/shape.js | 0 | ||||
-rw-r--r-- | src/style.js | 33 | ||||
-rwxr-xr-x[-rw-r--r--] | src/sugar.js | 73 | ||||
-rwxr-xr-x[-rw-r--r--] | src/svg.js | 95 | ||||
-rw-r--r-- | src/symbol.js | 17 | ||||
-rwxr-xr-x[-rw-r--r--] | src/text.js | 91 | ||||
-rwxr-xr-x[-rw-r--r--] | src/textpath.js | 43 | ||||
-rw-r--r-- | src/transform.js | 126 | ||||
-rwxr-xr-x[-rw-r--r--] | src/use.js | 4 | ||||
-rw-r--r-- | src/utilities.js | 23 | ||||
-rwxr-xr-x[-rw-r--r--] | src/viewbox.js | 8 |
116 files changed, 11310 insertions, 8978 deletions
@@ -1,8 +1,10 @@ .DS_Store +.idea public site/ bleed/ +docs/ obsolete/ test/ -docs/ -src/index.js
\ No newline at end of file +src/index.js +node_modules/
\ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 43d914e..f92e5d1 100644..100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,61 @@ -# 1.1.0 (25/5/2015) - -- fixed file permission -- fixed event-handling -- added namespaced events -- fixed removing root and nested SVGs [thanks @zachrbrown] -- fix: animating rotation takes rotation-center into accound -- added: use-element now can point to an external file - -# 1.0.1 (06/09/2014) - -- added UMD support - -# 1.0.0 (05/09/2014) - -- fixed event module -- removed `mouseenter` and `mouseleave` events because they are not supported by IE +# 2.0.0-rc.2 (?/11/2014) + +- added `morph()` method to `SVG.PathArray` -> __TODO!__ +- added `rotate()` method to linear gradients -> __TODO!__ +- added `'random'` option and `randomize()` method to `SVG.Color` -> __TODO!__ +- added `parents()` method to get an array of all parenting elements -> __TODO!__ +- added support for css selectors with the `parent()` method -> __TODO!__ +- added `enqueue()` method to `SVG.FX` -> __TODO!__ +- added `ungroup()` method -> __TODO!__ [thanks to Peter Uithoven] + +# 2.0.0-rc.1 (?/10/2014) + +- added specs for `SVG.FX` -> __TODO!__ +- fixed a bug in clipping and masking where empty nodes persists after removal -> __TODO!__ +- fixed a bug in IE11 with `mouseenter` and `mouseleave` -> __TODO!__ +- added `precision()` method to round numeric element attributes -> __TODO!__ + +# 1.0.0 (?/09/2014) + +- implemented an SVG adoption system to be able to manipulate existing SVG's not created with svg.js +- changed `parent` reference on elements to `parent()` method +- using `CustomEvent` instead of `Event` to be able to fire events with a `detail` object [thanks @Fuzzyma] +- added polyfill for IE9 and IE10 custom events [thanks @Fuzzyma] +- added DOM query selector with the `select()` method globally or on parent elements +- added the intentionally neglected `SVG.Circle` element +- fixed bug in `radius()` method when `y` value equals `0` +- renamed `SVG.TSpan` class to `SVG.Tspan` to play nice with the adoption system +- added `rx()` and `ry()` to `SVG.Rect`, `SVG.Circle`, `SVG.Ellispe` and `SVG.FX` +- changed `array` reference to `array()` method on `SVG.Polyline`, `SVG.Polygon` and `SVG.Path` +- completely reworked `clone()` method to use the adoption system +- added support to clone manually built text elements +- added `svg.wiml.js` plugin to plugins list +- added `ctm()` method to for matrix-centric transformations +- added `morph()` method to `SVG.Matrix` +- added support for new matrix system to `SVG.FX` +- completely reworked transformations to be chainable and more true to their nature +- changed `lines` reference to `lines()` on `SVG.Text` +- changed `track` reference to `track()` on `SVG.Text` +- changed `textPath` reference to `textPath()` on `SVG.Text` +- added raw svg import functionality with the `svg()` method +- reworked sup-pixel offset implementation to be more compact +- added `native()` method to elements and matrix to get to the native api +- added `untransform()` method to remove all transformations +- switched from Ruby's `rake` to Node's `gulp` for building [thanks to Alex Ewerlöf] +- added coding style description to README +- changed `to()` method to `at()` method in `SVG.FX` +- added reverse functionality for animations +- documented the `situation` object in `SVG.FX` +- renamed `SVG.SetFX` to `SVG.FX.Set` +- added distinction between relative and absolute matrix transformations +- implemented the `element()` method using the `SVG.Bare` class to create elements that are not described by SVG.js +- removed `SVG.Symbol` but kept the `symbol()` method using the new `element()` method +- reworked `SVG.Number` to return new instances with calculations rather than itself +- added `w` and `h` properties as shorthand for `width` and `height` to `SVG.BBox` +- added `SVG.TBox` to get a bounding box that is affected by transformation values +- reworked animatable matrix rotations +- fixed a bug where events are not detached properly +- added event-based or complete detaching of event listeners in `off()` method # 1.0.0-rc.9 (17/06/2014) diff --git a/Gemfile b/Gemfile deleted file mode 100644 index dd15469..0000000 --- a/Gemfile +++ /dev/null @@ -1,4 +0,0 @@ -source :rubygems - -gem 'rake' -gem 'uglifier'
\ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index d39262a..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,17 +0,0 @@ -GEM - remote: http://rubygems.org/ - specs: - execjs (1.4.0) - multi_json (~> 1.0) - multi_json (1.5.0) - rake (10.0.3) - uglifier (1.3.0) - execjs (>= 0.3.0) - multi_json (~> 1.0, >= 1.0.2) - -PLATFORMS - ruby - -DEPENDENCIES - rake - uglifier diff --git a/MIT-LICENSE b/LICENSE.txt index 92730ed..92730ed 100644..100755 --- a/MIT-LICENSE +++ b/LICENSE.txt diff --git a/README.md b/README.md index daae4ec..4ad0358 100644..100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# svg.js +# SVG.js A lightweight library for manipulating and animating SVG. @@ -6,7 +6,9 @@ Svg.js has no dependencies and aims to be as small as possible. Svg.js is licensed under the terms of the MIT License. -See [svgjs.com](http://svgjs.com) for an introduction, [documentation](http://documentup.com/wout/svg.js) and [some action](http://svgjs.com/test). +See [svgjs.com](http://svgjs.com) for an introduction, [documentation](http://documentup.com/wout/SVG.js) and [some action](http://svgjs.com/test). + +[](https://www.gittip.com/wout/) ## Usage @@ -65,17 +67,23 @@ Svg.js also works outside of the HTML DOM, inside an SVG document for example: </svg> ``` -### Sub pixel offset fix -By default sub pixel offset won't be corrected. To enable it, call the `fixSubPixelOffset()` method: +### Sub-pixel offset fix +Call the `spof()` method to fix sub-pixel offset: + +```javascript +var draw = SVG('drawing').spof() +``` + +To enable automatic sub-pixel offset correction when the window is resized: ```javascript -var draw = SVG('drawing').fixSubPixelOffset() +SVG.on(window, 'resize', function() { draw.spof() }) ``` ## Parent elements ### Main svg document -The main svg.js initializer function creates a root svg node in the given element and returns an instance of `SVG.Doc`: +The main SVG.js initializer function creates a root svg node in the given element and returns an instance of `SVG.Doc`: ```javascript var draw = SVG('drawing') @@ -99,7 +107,7 @@ __`returns`: `SVG.Nested`__ _Javascript inheritance stack: `SVG.Nested` < `SVG.Container` < `SVG.Parent`_ ### Groups -Grouping elements is useful if you want to transform a set of elements as if it were one. All element within a group maintain their position relative to the group they belong to. A group has all the same element methods as the root svg document: +Grouping elements is useful if you want to transform a set of elements as if it were one. All element within a group maintain their position relative to the group they belong to. A group has all the same element methods as the root svg document: ```javascript var group = draw.group() @@ -112,6 +120,8 @@ Existing elements from the svg document can also be added to a group: group.add(rect) ``` +__Note:__ Groups do not have a geometry of their own, it's inherited from their content. Therefore groups do not listen to `x`, `y`, `width` and `height` attributes. If that is what you are looking for, use a `nested()` svg instead. + __`returns`: `SVG.G`__ _Javascript inheritance stack: `SVG.G` < `SVG.Container` < `SVG.Parent`_ @@ -205,56 +215,55 @@ rect.radius(10, 20) __`returns`: `itself`__ - -## Ellipse -Ellipses, like rects, have two arguments, their `width` and `height`: +## Circle +The only argument necessary for a circle is the diameter: ```javascript -var ellipse = draw.ellipse(200, 100) +var circle = draw.circle(100) ``` -__`returns`: `SVG.Ellipse`__ +__`returns`: `SVG.Circle`__ -_Javascript inheritance stack: `SVG.Ellipse` < `SVG.Shape` < `SVG.Element`_ +_Javascript inheritance stack: `SVG.Circle` < `SVG.Shape` < `SVG.Element`_ ### radius() -Ellipses can also be redefined by their radii: +Circles can also be redefined by their radius: ```javascript -ellipse.radius(75, 50) +rect.radius(75) ``` __`returns`: `itself`__ -## Circle -The only argument necessary for a circle is the diameter: +## Ellipse +Ellipses, like rects, have two arguments, their `width` and `height`: ```javascript -var circle = draw.circle(100) +var ellipse = draw.ellipse(200, 100) ``` __`returns`: `SVG.Ellipse`__ _Javascript inheritance stack: `SVG.Ellipse` < `SVG.Shape` < `SVG.Element`_ -_Note that this generates an `<ellipse>` element instead of a `<circle>`. This choice has been made to keep the size of the library down._ - ### radius() -Circles can also be redefined by their radius: +Ellipses can also be redefined by their radii: ```javascript -circle.radius(75) +rect.radius(75, 50) ``` __`returns`: `itself`__ ## Line -The line element always takes four arguments, `x1`, `y1`, `x2` and `y2`: +Create a line from point A to point B: ```javascript var line = draw.line(0, 0, 100, 150).stroke({ width: 1 }) ``` +Creating a line element can be done in four ways. Look at the `plot()` method to see all the possiblilities. + __`returns`: `SVG.Line`__ _Javascript inheritance stack: `SVG.Line` < `SVG.Shape` < `SVG.Element`_ @@ -266,8 +275,37 @@ Updating a line is done with the `plot()` method: line.plot(50, 30, 100, 150) ``` +Alternatively it also accepts a point string: + +```javascript +line.plot('0,0 100,150') +``` + +Or a point array: + +```javascript +line.plot([[0, 0], [100, 150]]) +``` + +Or an instance of `SVG.PointArray`: + +```javascript +var array = new SVG.PointArray([[0, 0], [100, 150]]) +line.plot(array) +``` + __`returns`: `itself`__ +### array() +References the `SVG.PointArray` instance. This method is rather intended for internal use: + +```javascript +polyline.array() +``` + +__`returns`: `SVG.PointArray`__ + + ## Polyline The polyline element defines a set of connected straight line segments. Typically, polyline elements define open shapes: @@ -304,6 +342,15 @@ polyline.animate(3000).plot([[0,0], [100,50], [50,100], [150,50], [200,50], [250 __`returns`: `itself`__ +### array() +References the `SVG.PointArray` instance. This method is rather intended for internal use: + +```javascript +polyline.array() +``` + +__`returns`: `SVG.PointArray`__ + ## Polygon The polygon element, unlike the polyline element, defines a closed shape consisting of a set of connected straight line segments: @@ -333,6 +380,15 @@ polygon.animate(3000).plot([[0,0], [100,50], [50,100], [150,50], [200,50], [250, __`returns`: `itself`__ +### array() +References the `SVG.PointArray` instance. This method is rather intended for internal use: + +```javascript +polygon.array() +``` + +__`returns`: `SVG.PointArray`__ + ## Path The path string is similar to the polygon string but much more complex in order to support curves: @@ -356,6 +412,15 @@ path.plot('M100,200L300,400') __`returns`: `itself`__ +### array() +References the `SVG.PathArray` instance. This method is rather intended for internal use: + +```javascript +path.array() +``` + +__`returns`: `SVG.PathArray`__ + ## Image Creating images is as you might expect: @@ -401,7 +466,7 @@ __`returns`: `itself`__ ## Text -Unlike html, text in svg is much harder to tame. There is no way to create flowing text, so newlines should be entered manually. In svg.js there are two ways to create text elements. +Unlike html, text in svg is much harder to tame. There is no way to create flowing text, so newlines should be entered manually. In SVG.js there are two ways to create text elements. The first and easiest method is to provide a string of text, split by newlines: @@ -460,7 +525,7 @@ Just adding one tspan is also possible: text.tspan(' on a train...').fill('#f06') ``` -__`returns`: `SVG.TSpan`__ +__`returns`: `SVG.Tspan`__ ### plain() If the content of the element doesn't need any stying or multiple lines, it might be sufficient to just add some plain text: @@ -486,7 +551,7 @@ text.font({ __`returns`: `itself`__ ### leading() -As opposed to html, where leading is defined by `line-height`, svg does not have a natural leading equivalent. In svg, lines are not defined naturally. They are defined by `<tspan>` nodes with a `dy` attribute defining the line height and a `x` value resetting the line to the `x` position of the parent text element. But you can also have many nodes in one line defining a different `y`, `dy`, `x` or even `dx` value. This gives us a lot of freedom, but also a lot more responsibility. We have to decide when a new line is defined, where it starts, what its offset is and what it's height is. The `leading()` method in svg.js tries to ease the pain by giving you behaviour that is much closer to html. In combination with newline separated text, it works just like html: +As opposed to html, where leading is defined by `line-height`, svg does not have a natural leading equivalent. In svg, lines are not defined naturally. They are defined by `<tspan>` nodes with a `dy` attribute defining the line height and a `x` value resetting the line to the `x` position of the parent text element. But you can also have many nodes in one line defining a different `y`, `dy`, `x` or even `dx` value. This gives us a lot of freedom, but also a lot more responsibility. We have to decide when a new line is defined, where it starts, what its offset is and what it's height is. The `leading()` method in SVG.js tries to ease the pain by giving you behaviour that is much closer to html. In combination with newline separated text, it works just like html: ```javascript var text = draw.text("Lorem ipsum dolor sit amet consectetur.\nCras sodales imperdiet auctor.") @@ -547,8 +612,16 @@ text.length() __`returns`: `number`__ -### lines -All added tspans are stored in the `lines` reference, which is an instance of `SVG.Set`. +### lines() +All first level tspans can be referenced with the `lines()` method: + +```javascript +text.lines() +``` + +This will return an intance of `SVG.Set` including all `tspan` elements. + +__`returns`: `SVG.Set`__ ### events The text element has one event. It is fired every time the `rebuild()` method is called: @@ -559,10 +632,10 @@ text.on('rebuild', function() { }) ``` -## TSpan -The tspan elements are only available inside text elements or inside other tspan elements. In svg.js they have a class of their own: +## Tspan +The tspan elements are only available inside text elements or inside other tspan elements. In SVG.js they have a class of their own: -_Javascript inheritance stack: `SVG.TSpan` < `SVG.Shape` < `SVG.Element`_ +_Javascript inheritance stack: `SVG.Tspan` < `SVG.Shape` < `SVG.Element`_ ### text() Update the content of the tspan. This can be done by either passing a string: @@ -595,7 +668,7 @@ Add a nested tspan: tspan.tspan('I am a child of my parent').fill('#f06') ``` -__`returns`: `SVG.TSpan`__ +__`returns`: `SVG.Tspan`__ ### plain() Just adds some plain text: @@ -681,26 +754,36 @@ text.plot('M 300 500 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 Attributes specific to the `<textPath>` element can be applied to the textPath instance itself: ```javascript -text.textPath.attr('startOffset', 0.5) +text.textPath().attr('startOffset', 0.5) ``` And they can be animated as well of course: ```javascript -text.textPath.animate(3000).attr('startOffset', 0.8) +text.textPath().animate(3000).attr('startOffset', 0.8) ``` __`returns`: `SVG.TextPath`__ _Javascript inheritance stack: `SVG.TextPath` < `SVG.Element`_ -### track +### textPath() +Referencing the textPath node directly: + +```javascript +var textPath = text.textPath() +``` + +__`returns`: `SVG.TextPath`__ + +### track() Referencing the linked path element directly: ```javascript -var path = text.track +var path = text.track() ``` +__`returns`: `SVG.Path`__ ## Use The use element simply emulates another existing element. Any changes on the master element will be reflected on all the `use` instances. The usage of `use()` is very straightforward: @@ -735,7 +818,6 @@ _Javascript inheritance stack: `SVG.Use` < `SVG.Shape` < `SVG.Element`_ ## Symbol Not unlike the `group` element, the `symbol` element is a container element. The only difference between symbols and groups is that symbols are not rendered. Therefore a `symbol` element is ideal in combination with the `use` element: - ```javascript var symbol = draw.symbol() symbol.rect(100, 100).fill('#f09') @@ -743,15 +825,45 @@ symbol.rect(100, 100).fill('#f09') var use = draw.use(symbol).move(200, 200) ``` -__`returns`: `SVG.Symbol`__ +__`returns`: `SVG.Bare`__ + +_Javascript inheritance stack: `SVG.Bare` < `SVG.Element` [with a shallow inheritance from `SVG.Parent`]_ + +## Bare +For all SVG elements that are not described by SVG.js, the `SVG.Bare` class comes in handy. This class inherits directly from `SVG.Element` and makes it possible to add custom methods in a separate namespace without polluting the main `SVG.Element` namespace. Consider it your personal playground. + +### element() +The `SVG.Bare` class can be instantiated with the `element()` method on any parent element: + +```javascript +var element = draw.element('title') +``` +The string value passed as the first argument is the node name that should be generated. + +Additionally any existing class name can be passed as the second argument to define from which class the element should inherit: + +```javascript +var element = draw.element('symbol', SVG.Parent) +``` -_Javascript inheritance stack: `SVG.Use` < `SVG.Container` < `SVG.Symbol`_ +This gives you as the user a lot of power. But remember, with great power comes great responsibility. +__`returns`: `SVG.Bare`__ + +### words() +The `SVG.Bare` instance carries an additional method to add plain text: + +```javascript +var element = draw.element('title').words('This is a title.') +//-> <title>This is a title.</title> +``` + +__`returns`: `itself`__ ## Referencing elements ### By id -If you want to get an element created by svg.js by its id, you can use the `SVG.get()` method: +If you want to get an element created by SVG.js by its id, you can use the `SVG.get()` method: ```javascript var element = SVG.get('my_element') @@ -759,15 +871,30 @@ var element = SVG.get('my_element') element.fill('#f06') ``` -### By class name -There is no DOM querying system built into svg.js but [jQuery](http://jquery.com/) or [Zepto](http://zeptojs.com/) will help you achieve this. Here is an example: +### Using CSS selectors +There are two ways to select elements using CSS selectors. + +The first is to search globally. This will search in all svg elements in a document and return them in an instance of `SVG.Set`: + +```javascript +var elements = SVG.select('rect.my-class').fill('#f06') +``` + +The second is to search within a parent element: + +```javascript +var elements = group.select('rect.my-class').fill('#f06') +``` + +### Using jQuery or Zepto +Another way is to use [jQuery](http://jquery.com/) or [Zepto](http://zeptojs.com/). Here is an example: ```javascript /* add elements */ var draw = SVG('drawing') -var group = draw.group().attr('class', 'my-group') -var rect = group.rect(100,100).attr('class', 'my-element') -var circle = group.circle(100).attr('class', 'my-element').move(100, 100) +var group = draw.group().addClass('my-group') +var rect = group.rect(100,100).addClass('my-element') +var circle = group.circle(100).addClass('my-element').move(100, 100) /* get elements in group */ var elements = $('#drawing g.my-group .my-element').each(function() { @@ -776,7 +903,7 @@ var elements = $('#drawing g.my-group .my-element').each(function() { ``` ## Circular reference -Every element instance within svg.js has a reference to the actual `node`: +Every element instance within SVG.js has a reference to the actual `node`: ### node ```javascript @@ -784,8 +911,16 @@ element.node ``` __`returns`: `node`__ +### native() +The same can be achieved with the `native()` method: +```javascript +element.native() +``` +__`returns`: `node`__ + + ### instance -Similarly, the node carries a reference to the svg.js `instance`: +Similar, the node carries a reference to the SVG.js `instance`: ```javascript node.instance @@ -793,12 +928,12 @@ node.instance __`returns`: `element`__ ## Parent reference -Every element has a reference to its parent: +Every element has a reference to its parent with the `parent()` method: -### parent +### parent() ```javascript -element.parent +element.parent() ``` __`returns`: `element`__ @@ -808,10 +943,10 @@ Even the main svg document: ```javascript var draw = SVG('drawing') -draw.parent //-> returns the wrappig html element with id 'drawing' +draw.parent() //-> returns the wrappig html element with id 'drawing' ``` -__`returns`: `node`__ +__`returns`: `HTMLNode`__ ### doc() @@ -945,6 +1080,28 @@ rect.reference('fill') //-> returns gradient or pattern instance for example circle.reference('clip-path') //-> returns clip instance ``` +## Import / export SVG + +### svg() +Exporting the full generated SVG, or a part of it, can be done with the `svg()` method: + +```javascript +draw.svg() +``` + +Exporting works on all elements. + +Importing is done with the same method: + +```javascript +draw.svg('<g><rect width="100" height="50" fill="#f06"></rect></g>') +``` + +Importing works on any element that inherits from `SVG.Parent`, which is basically every element that can contain other elements. + +`getter`__`returns`: `string`__ + +`setter`__`returns`: `itself`__ ## Manipulating elements @@ -987,58 +1144,66 @@ rect.attr('fill', null) ### transform() -With the `transform()` method elements can be scaled, rotated, translated and skewed: + +The `transform()` method acts as a full getter without an argument: ```javascript -rect.transform({ - rotation: 45 -, cx: 100 -, cy: 100 -}) +element.transform() ``` -You can also provide two arguments as property and value: +The returned __`object`__ contains the following values: + +- `x` (translation on the x-axis) +- `y` (translation on the y-axis) +- `skewX` (calculated skew on x-axis) +- `skewY` (calculated skew on y-axis) +- `scaleX` (calculated scale on x-axis) +- `scaleY` (calculated scale on y-axis) +- `rotation` (calculated rotation) +- `cx` (last used rotation centre on x-axis) +- `cy` (last used rotation centre on y-axis) + +Additionally a string value for the required property can be passed: ```javascript -rect.transform('matrix', '1,0.5,0.5,1,0,0') +element.transform('rotation') ``` -All available transformations are: +In this case the returned value is a __`number`__. -```javascript -rect.transform({ - x: [translation on x-axis] -, y: [translation on y-axis] -, rotation: [degrees] -, cx: [x rotation point] -, cy: [y rotation point] +As a setter it has two ways of working. By default transformations are absolute. For example, if you call: + +```javascript +element.transform({ rotation: 125 }).transform({ rotation: 37.5 }) +``` -, scaleX: [scaling on x-axis] -, scaleY: [scaling on y-axis] +The resulting rotation will be `37.5` and not the sum of the two transformations. But if that's what you want there is a way out by adding the `relative` parameter. That would be: -, skewX: [skewing on x-axis] -, skewY: [skewing on y-axis] -, matrix: [a 6-digit matrix string; e.g. '1,0,0,1,0,0'] -, a: [the first matrix digit] -, b: [the second matrix digit] -, c: [the third matrix digit] -, d: [the fourth matrix digit] -, e: [the fifth matrix digit] -, f: [the sixth matrix digit] -}) +```javascript +element.transform({ rotation: 125 }).transform({ rotation: 37.5, relative: true }) ``` -Note that you can also apply transformations directly using the `attr()` method: +Alternatively a relative flag can be passed as the second argument: ```javascript -rect.attr('transform', 'matrix(1,0.5,0.5,1,0,0)') +element.transform({ rotation: 125 }).transform({ rotation: 37.5 }, true) ``` -Although that would mean you can't use the `transform()` method because it would overwrite any manually applied transformations. You should only go down this route if you know exactly what you are doing and you want to achieve an effect that is not achievable with the `transform()` method. +Available transformations are: -`getter`__`returns`: `number`__ +- `rotation` with optional `cx` and `cy` +- `scale` with optional `cx` and `cy` +- `scaleX` with optional `cx` and `cy` +- `scaleY` with optional `cx` and `cy` +- `skewX` with optional `cx` and `cy` +- `skewY` with optional `cx` and `cy` +- `x` +- `y` +- `a`, `b`, `c`, `d`, `e` and/or `f` or an existing matrix instead of the object + +`getter`__`returns`: `value`__ `setter`__`returns`: `itself`__ @@ -1061,7 +1226,7 @@ Or a css string: rect.style('cursor:pointer;fill:#f03;') ``` -Similar to `attr()`, the `style()` method can also act as a getter: +Similar to `attr()` the `style()` method can also act as a getter: ```javascript rect.style('cursor') @@ -1444,37 +1609,92 @@ If the size of the viewbox equals the size of the svg drawing, the zoom value wi `setter`__`returns`: `itself`__ ### bbox() +Get the bounding box of an element. This is a wrapper for the native `getBBox()` method but adds more values: ```javascript path.bbox() ``` -This will return an instance of `SVG.BBox` containing the following values: -```javascript -{ width: 20, height: 20, x: 10, y: 20, cx: 20, cy: 30, x2: 30, y2: 40 } -``` +This will return an instance of `SVG.BBox` containing the following values: -As opposed to the native `getBBox()` method any translations used with the `transform()` method will be taken into account. +- `width` (value from native `getBBox`) +- `height` (value from native `getBBox`) +- `w` (shorthand for `width`) +- `h` (shorthand for `height`) +- `x` (value from native `getBBox`) +- `y` (value from native `getBBox`) +- `cx` (center `x` of the bounding box) +- `cy` (center `y` of the bounding box) +- `x2` (lower right `x` of the bounding box) +- `y2` (lower right `y` of the bounding box) The `SVG.BBox` has one other nifty little feature, enter the `merge()` method. With `merge()` two `SVG.BBox` instances can be merged into one new instance, basically being the bounding box of the two original bounding boxes: ```javascript -var box1 = draw.rect(100,100).move(50,50).bbox() -var box2 = draw.rect(100,100).move(200,200).bbox() +var box1 = draw.rect(100,100).move(50,50) +var box2 = draw.rect(100,100).move(200,200) var box3 = box1.merge(box2) ``` __`returns`: `SVG.BBox`__ +### tbox() +Where `bbox()` returns a bounding box mindless of any transformations, the `tbox()` method does take transformations into account. So any translation or scale will be applied to the resulting values to get closer to the actual visual representation: + +```javascript +path.tbox() +``` + +This will return an instance of `SVG.TBox` containing the following values: + +- `width` (value from native getBBox influenced by the `scaleX` of the current matrix) +- `height` (value from native getBBox influenced by the `scaleY` of the current matrix) +- `w` (shorthand for `width`) +- `h` (shorthand for `height`) +- `x` (value from native getBBox influenced by the `x` of the current matrix) +- `y` (value from native getBBox influenced by the `y` of the current matrix) +- `cx` (center `x` of the bounding box) +- `cy` (center `y` of the bounding box) +- `x2` (lower right `x` of the bounding box) +- `y2` (lower right `y` of the bounding box) + +Note that the rotation of the element will not be added to the calculation. + +__`returns`: `SVG.TBox`__ + ### rbox() -Is similar to `bbox()` but will give you the box around the exact representation of the element, taking all transformations into account. +Is similar to `bbox()` but will give you the box around the exact visual representation of the element, taking all transformations into account. ```javascript path.rbox() ``` +This will return an instance of `SVG.RBox` containing the following values: + +- `width` (the actual visual width) +- `height` (the actual visual height) +- `w` (shorthand for `width`) +- `h` (shorthand for `height`) +- `x` (the actual visual position on the x-axis) +- `y` (the actual visual position on the y-axis) +- `cx` (center `x` of the bounding box) +- `cy` (center `y` of the bounding box) +- `x2` (lower right `x` of the bounding box) +- `y2` (lower right `y` of the bounding box) + +__Important__: Mozilla browsers include stroke widths where other browsers do not. Therefore the resulting box might be different in Mozulla browsers. It is very hard to modify this behavior so for the time being this is an inconvenience we have to live with. + __`returns`: `SVG.RBox`__ +### ctm() +Retreives the current transform matrix of the element: + +```javascript +path.ctm() +``` + +__`returns`: `SVG.Matrix`__ + ### inside() To check if a given point is inside the bounding box of an element you can use the `inside()` method: @@ -1651,13 +1871,13 @@ To make things easier a morphing function is passed as the second argument. This var ellipse = draw.ellipse(100, 100).attr('cx', '20%').fill('#333') rect.animate(3000).move(100, 100).during(function(pos, morph) { - /* numeric values */ + // numeric values ellipse.size(morph(100, 200), morph(100, 50)) - /* unit strings */ + // unit strings ellipse.attr('cx', morph('20%', '80%')) - /* hex color strings */ + // hex color strings ellipse.fill(morph('#333', '#ff0066')) }) ``` @@ -1674,9 +1894,19 @@ rect.animate(3000).move(100, 100).loop() But the loop can also be a predefined number of times: ```javascript -rect.animate(3000).move(100, 100).loop(5) +rect.animate(3000).move(100, 100).loop(3) +``` + +Loops go from beginning to end and start over again (`0->1.0->1.0->1.`). + +There is also a reverse flag that should be passed as the second argument: + +```javascript +rect.animate(3000).move(100, 100).loop(3, true) ``` +Loops will then be completely reversed before starting over (`0->1->0->1->0->1.`). + __`returns`: `SVG.FX`__ ### after() @@ -1692,24 +1922,48 @@ Note that the `after()` method will never be called if the animation is looping __`returns`: `SVG.FX`__ -### to() -Say you want to control the position of an animation with an external event, then the `to()` method will prove to be very useful: +### at() +Say you want to control the position of an animation with an external event, then the `at()` method will proove very useful: ```javascript -var animate = draw.rect(100, 100).move(50, 50).animate('=').move(200, 200) +var animation = draw.rect(100, 100).move(50, 50).animate('=').move(200, 200) document.onmousemove = function(event) { - animate.to(event.clientX / 1000) + animation.at(event.clientX / 1000) } ``` -In order to be able use the `to()` method the duration of the animation should be set to `'='`. The value passed as the first argument of `to()` should be a number between `0` and `1`, `0` being the beginning of the animation and `1` being the end. Note that any values below `0` and above `1` will be normalized. +In order to be able to use the `at()` method, the duration of the animation should be set to `'='`. The value passed as the first argument of `at()` should be a number between `0` and `1`, `0` being the beginning of the animation and `1` being the end. Note that any values below `0` and above `1` will be normalized. _This functionality requires the fx.js module which is included in the default distribution._ __`returns`: `SVG.FX`__ +### situation +The current situation of an animation is stored in the `situation` object: + +```javascript +rect.animate(3000).move(100, 100) +rect.fx.situation //-> everything is in here +``` + +Available values are: + +- `start` (start time as a number in milliseconds) +- `play` (animation playing or not; `true` or `false`) +- `pause` (time when the animation was last paused) +- `duration` (the chosen duration of the animation) +- `ease` (the chosen easing calculation) +- `finish` (start + duration) +- `loop` (the current loop; counting down if a number; `true`, `false` or a number) +- `loops` (if a number, the total number loops; `true`, `false` or a number) +- `reverse` (whether or not the loop should be reversed; `true` or `false`) +- `reversing` (`true` if the loop is currently reversing, otherwise `false`) +- `during` (the function that should be called on every keyframe) +- `after` (the function that should be called after completion) + + ## Syntax sugar Fill and stroke are used quite often. Therefore two convenience methods are provided: @@ -1934,7 +2188,7 @@ var clip = draw.clip().add(text).add(ellipse) rect.clipWith(clip) ``` -__`returns`: `SVG.Clip`__ +__`returns`: `SVG.ClipPath`__ ### unclip() Unclipping the elements can be done with the `unclip()` method: @@ -2619,6 +2873,18 @@ Unbinding events is just as easy: rect.off('click', click) ``` +Or to unbind all listeners for a given event: + +```javascript +rect.off('click') +``` + +Or even unbind all listeners for all events: + +```javascript +rect.off() +``` + __`returns`: `itself`__ But there is more to event listeners. You can bind events to html elements as well: @@ -2686,7 +2952,7 @@ _Important: always make sure you namespace your event to avoid conflicts. Prefer ## Numbers -Numbers in svg.js have a dedicated number class to be able to process string values. Creating a new number is simple: +Numbers in SVG.js have a dedicated number class to be able to process string values. Creating a new number is simple: ```javascript var number = new SVG.Number('78%') @@ -2703,7 +2969,7 @@ Addition: number.plus('3%') ``` -__`returns`: `itself`__ +__`returns`: `SVG.Number`__ ### minus() Subtraction: @@ -2712,7 +2978,7 @@ Subtraction: number.minus('3%') ``` -__`returns`: `itself`__ +__`returns`: `SVG.Number`__ ### times() Multiplication: @@ -2721,7 +2987,7 @@ Multiplication: number.times(2) ``` -__`returns`: `itself`__ +__`returns`: `SVG.Number`__ ### divide() Division: @@ -2730,7 +2996,7 @@ Division: number.divide('3%') ``` -__`returns`: `itself`__ +__`returns`: `SVG.Number`__ ### to() Change number to another unit: @@ -2739,7 +3005,7 @@ Change number to another unit: number.to('px') ``` -__`returns`: `itself`__ +__`returns`: `SVG.Number`__ ### morph() Make a number morphable: @@ -2824,7 +3090,7 @@ __`returns`: `SVG.Color`__ ## Arrays -In svg.js every value list string can be cast and passed as an array. This makes writing them more convenient but also adds a lot of key functionality to them. +In SVG.js every value list string can be cast and passed as an array. This makes writing them more convenient but also adds a lot of key functionality to them. ### SVG.Array Is for simple, whitespace separated value strings: @@ -2870,7 +3136,7 @@ new SVG.PointArray([ Note that every instance of `SVG.Polyline` and `SVG.Polygon` carries a reference to the `SVG.PointArray` instance: ```javascript -polygon.array //-> returns the SVG.PointArray instance +polygon.array() //-> returns the SVG.PointArray instance ``` _Javascript inheritance stack: `SVG.PointArray` < `SVG.Array`_ @@ -2905,44 +3171,44 @@ new SVG.PathArray([ Note that every instance of `SVG.Path` carries a reference to the `SVG.PathArray` instance: ```javascript -path.array //-> returns the SVG.PathArray instance +path.array() //-> returns the SVG.PathArray instance ``` #### Syntax The syntax for patharrays is very predictable. They are basically literal representations in the form of two dimentional arrays. ##### Move To -Original syntax is `M0 0` or `m0 0`. The svg.js syntax `['M',0,0]` or `['m',0,0]`. +Original syntax is `M0 0` or `m0 0`. The SVG.js syntax `['M',0,0]` or `['m',0,0]`. ##### Line To -Original syntax is `L100 100` or `l100 100`. The svg.js syntax `['L',100,100]` or `['l',100,100]`. +Original syntax is `L100 100` or `l100 100`. The SVG.js syntax `['L',100,100]` or `['l',100,100]`. ##### Horizontal line -Original syntax is `H200` or `h200`. The svg.js syntax `['H',200]` or `['h',200]`. +Original syntax is `H200` or `h200`. The SVG.js syntax `['H',200]` or `['h',200]`. ##### Vertical line -Original syntax is `V300` or `v300`. The svg.js syntax `['V',300]` or `['v',300]`. +Original syntax is `V300` or `v300`. The SVG.js syntax `['V',300]` or `['v',300]`. ##### Bezier curve -Original syntax is `C20 20 40 20 50 10` or `c20 20 40 20 50 10`. The svg.js syntax `['C',20,20,40,20,50,10]` or `['c',20,20,40,20,50,10]`. +Original syntax is `C20 20 40 20 50 10` or `c20 20 40 20 50 10`. The SVG.js syntax `['C',20,20,40,20,50,10]` or `['c',20,20,40,20,50,10]`. Or mirrored with `S`: -Original syntax is `S40 20 50 10` or `s40 20 50 10`. The svg.js syntax `['S',40,20,50,10]` or `['s',40,20,50,10]`. +Original syntax is `S40 20 50 10` or `s40 20 50 10`. The SVG.js syntax `['S',40,20,50,10]` or `['s',40,20,50,10]`. Or quadratic with `Q`: -Original syntax is `Q20 20 50 10` or `q20 20 50 10`. The svg.js syntax `['Q',20,20,50,10]` or `['q',20,20,50,10]`. +Original syntax is `Q20 20 50 10` or `q20 20 50 10`. The SVG.js syntax `['Q',20,20,50,10]` or `['q',20,20,50,10]`. Or a complete shortcut with `T`: -Original syntax is `T50 10` or `t50 10`. The svg.js syntax `['T',50,10]` or `['t',50,10]`. +Original syntax is `T50 10` or `t50 10`. The SVG.js syntax `['T',50,10]` or `['t',50,10]`. ##### Arc -Original syntax is `A 30 50 0 0 1 162 163` or `a 30 50 0 0 1 162 163`. The svg.js syntax `['A',30,50,0,0,1,162,163]` or `['a',30,50,0,0,1,162,163]`. +Original syntax is `A 30 50 0 0 1 162 163` or `a 30 50 0 0 1 162 163`. The SVG.js syntax `['A',30,50,0,0,1,162,163]` or `['a',30,50,0,0,1,162,163]`. ##### Close -Original syntax is `Z` or `z`. The svg.js syntax `['Z']` or `['z']`. +Original syntax is `Z` or `z`. The SVG.js syntax `['Z']` or `['z']`. The best documentation on paths can be found at https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths. @@ -2950,7 +3216,7 @@ The best documentation on paths can be found at https://developer.mozilla.org/en _Javascript inheritance stack: `SVG.PathArray` < `SVG.Array`_ ### morph() -In order to animate array values the `morph()` method lets you pass a destination value. This can be either the string value, a plain array or an instance of the same type of svg.js array: +In order to animate array values the `morph()` method lets you pass a destination value. This can be either the string value, a plain array or an instance of the same type of SVG.js array: ```javascript var array = new SVG.PointArray([[0, 0], [100, 100]]) @@ -2986,10 +3252,10 @@ Note that this method is currently not available on `SVG.PathArray` but will be __`returns`: `itself`__ ### move() -Moves geometry to a given `x` and `y` position by its upper left corner: +Moves geometry of the array with the given `x` and `y` values: ```javascript -var array = new SVG.PointArray([[10, 10], [100, 100]]) +var array = new SVG.PointArray([[0, 0], [100, 100]]) array.move(33,75) array.toString() //-> returns '33,75 133,175' ``` @@ -3034,10 +3300,258 @@ Note that this method is only available on `SVG.PointArray` and `SVG.PathArray` __`returns`: `object`__ +## Matrices +Matrices in SVG.js have their own class `SVG.Matrix`, wrapping the native `SVGMatrix`. They add a lot of functionality like extracting transform values, matrix morphing and improvements on the native methods. + +### SVG.Matrix +In SVG.js matrices accept various values on initialization. + +Without a value: + +```javascript +var matrix = new SVG.Matrix +matrix.toString() //-> returns matrix(1,0,0,1,0,0) +``` + +Six arguments: + +```javascript +var matrix = new SVG.Matrix(1, 0, 0, 1, 100, 150) +matrix.toString() //-> returns matrix(1,0,0,1,100,150) +``` + +A string value: + +```javascript +var matrix = new SVG.Matrix('1,0,0,1,100,150') +matrix.toString() //-> returns matrix(1,0,0,1,100,150) +``` + +An object value: + +```javascript +var matrix = new SVG.Matrix({ a: 1, b: 0, c: 0, d: 1, e: 100, f: 150 }) +matrix.toString() //-> returns matrix(1,0,0,1,100,150) +``` + +A native `SVGMatrix`: + +```javascript +var svgMatrix = svgElement.getCTM() +var matrix = new SVG.Matrix(svgMatrix) +matrix.toString() //-> returns matrix(1,0,0,1,0,0) +``` + +Even an instance of `SVG.Element`: + +```javascript +var rect = draw.rect(50, 25) +var matrix = new SVG.Matrix(rect) +matrix.toString() //-> returns matrix(1,0,0,1,0,0) +``` + +### extract() +Gets the calculated values of the matrix as an object: + +```javascript +matrix.extract() +``` + +The returned object contains the following values: + +- `x` (translation on the x-axis) +- `y` (translation on the y-axis) +- `skewX` (calculated skew on x-axis) +- `skewY` (calculated skew on y-axis) +- `scaleX` (calculated scale on x-axis) +- `scaleY` (calculated scale on y-axis) +- `rotation` (calculated rotation) + +__`returns`: `object`__ + +### clone() +Returns an exact copy of the matrix: + +```javascript +matrix.clone() +``` + +__`returns`: `SVG.Matrix`__ + +### morph() +In order to animate matrices the `morph()` method lets you pass a destination matrix. This can be any value a `SVG.Matrix` would accept on initialization: + +```javascript +matrix.morph('matrix(2,0,0,2,100,150)') +``` + +__`returns`: `itself`__ + +### at() +This method will morph the matrix to a given position between `0` and `1`: + +```javascript +matrix.at(0.27) +``` + +This will only work when a destination matirx is defined using the `morph()` method. + +__`returns`: `SVG.Matrix`__ + +### multiply() +Multiplies by another given matrix: + +```javascript +matrix.matrix(matrix2) +``` + +__`returns`: `SVG.Matrix`__ + +### inverse() +Creates an inverted matix: + +```javascript +matrix.inverse() +``` + +__`returns`: `SVG.Matrix`__ + +### translate() +Translates matrix by a given x and y value: + +```javascript +matrix.translate(10, 20) +``` + +__`returns`: `SVG.Matrix`__ + +### scale() +Scales matrix uniformal with one value: + +```javascript +// scale +matrix.scale(2) +``` + +Scales matrix non-uniformal with two values: + +```javascript +// scaleX, scaleY +matrix.scale(2, 3) +``` + +Scales matrix uniformal on a given center point with three values: + +```javascript +// scale, cx, cy +matrix.scale(2, 100, 150) +``` + +Scales matrix non-uniformal on a given center point with four values: + +```javascript +// scaleX, scaleY, cx, cy +matrix.scale(2, 3, 100, 150) +``` + +__`returns`: `SVG.Matrix`__ + +### rotate() +Rotates matrix by degrees with one value given: + +```javascript +// degrees +matrix.rotate(45) +``` + +Rotates a matrix by degrees around a given point with three values: + +```javascript +// degrees, cx, cy +matrix.rotate(45, 100, 150) +``` + +__`returns`: `SVG.Matrix`__ + +### flip() +Flips matrix over a given axis: + +```javascript +matrix.flip('x') +``` + +or + +```javascript +matrix.flip('y') +``` + +By default elements are flipped over their center point. The flip axis position can be defined with the second argument: + +```javascript +matrix.flip('x', 150) +``` + +or + +```javascript +matrix.flip('y', 100) +``` + +__`returns`: `SVG.Matrix`__ + +### skew() +Skews matrix a given degrees over x and or y axis with two values: + +```javascript +// degreesX, degreesY +matrix.skew(0, 45) +``` + +Skews matrix a given degrees over x and or y axis on a given point with four values: + +```javascript +// degreesX, degreesY, cx, cy +matrix.skew(0, 45, 150, 100) +``` + +__`returns`: `SVG.Matrix`__ + +### around() +Performs a given matrix transformation around a given center point: + +```javascript +// cx, cy, matrix +matrix.around(100, 150, new SVG.Matrix().skew(0, 45)) +``` + +The matrix passed as the third argument will be used to multiply. + +__`returns`: `SVG.Matrix`__ + +### native() +Returns a native `SVGMatrix` extracted from the `SVG.Matrix` instance: + +```javascript +matrix.native() +``` + +__`returns`: `SVGMatrix`__ + +### toString() +Converts the matrix to a transform string: + +```javascript +matrix.toString() +// -> matrix(1,0,0,1,0,0) +``` + +__`returns`: `string`__ + ## Extending functionality ### SVG.invent() -Creating your own custom elements with svg.js is a piece of cake thanks to the `SVG.invent` function. For the sake of this example, let's "invent" a shape. We want a `rect` with rounded corners that are always proportional to the height of the element. The new shape lives in the `SVG` namespace and is called `Rounded`. Here is how we achieve that. +Creating your own custom elements with SVG.js is a piece of cake thanks to the `SVG.invent` function. For the sake of this example, lets "invent" a shape. We want a `rect` with rounded corners that are always proportional to the height of the element. The new shape lives in the `SVG` namespace and is called `Rounded`. Here is how we achieve that. ```javascript SVG.Rounded = SVG.invent({ @@ -3083,16 +3597,16 @@ That's it, the invention is now ready to be used! The `SVG.invent()` function always expects an object. The object can have the following configuration values: - `create`: can be either a string with the node name (e.g. `rect`, `ellipse`, ...) or a custom initializer function; `[required]` -- `inherit`: the desired svg.js class to inherit from (e.g. `SVG.Shape`, `SVG.Element`, `SVG.Container`, `SVG.Rect`, ...); `[optional but recommended]` +- `inherit`: the desired SVG.js class to inherit from (e.g. `SVG.Shape`, `SVG.Element`, `SVG.Container`, `SVG.Rect`, ...); `[optional but recommended]` - `extend`: an object with the methods that should be applied to the element's prototype; `[optional]` - `construct`: an object with the methods to create the element on the parent element; `[optional]` -- `parent`: an svg.js parent class on which the methods in the passed `construct` object should be available; `[optional]` +- `parent`: an SVG.js parent class on which the methods in the passed `construct` object should be available; `[optional]` Svg.js uses the `SVG.invent()` function to create all internal elements, so have a look at the source to see how this function is used in various ways. ### SVG.extend() -Svg.js has a modular structure. It is very easy to add your own methods at different levels. Let's say we want to add a method to all shape types then we would add our method to SVG.Shape: +SVG.js has a modular structure. It is very easy to add you own methods at different levels. Let's say we want to add a method to all shape types then we would add our method to SVG.Shape: ```javascript SVG.extend(SVG.Shape, { @@ -3139,10 +3653,12 @@ SVG.extend(SVG.Ellipse, SVG.Path, SVG.Polygon, { ## Plugins -Here are a few nice plugins that are available for svg.js: +Here are a few nice plugins that are available for SVG.js: + +** Caution: Not tested for SVG.js 2.0 ** ### absorb -[svg.absorb.js](https://github.com/wout/svg.absorb.js) absorb raw SVG data into an svg.js instance. +[svg.absorb.js](https://github.com/wout/svg.absorb.js) absorb raw SVG data into an SVG.js instance. ### draggable [svg.draggable.js](https://github.com/wout/svg.draggable.js) to make elements draggable. @@ -3179,6 +3695,12 @@ Here are a few nice plugins that are available for svg.js: ### topath [svg.topath.js](https://github.com/wout/svg.topath.js) to convert any other shape to a path. +### topoly +[svg.topoly.js](https://github.com/wout/svg.topoly.js) to convert a path to polygon or polyline. + +### wiml +[svg.wiml.js](https://github.com/wout/svg.wiml.js) a templating language for svg output. + ### comic [comic.js](https://github.com/balint42/comic.js) to cartoonize any given svg. @@ -3192,62 +3714,140 @@ Here are a few nice plugins that are available for svg.js: [svg.resize.js](https://github.com/fuzzyma/svg.resize.js) to resize elements with your mouse ## Contributing -All contributions are very welcome but please make sure you: +We love contributions. Yes indeed, we used the word LOVE! But please make sure you follow the same coding style. Here are some guidelines. -- maintain the coding style - - __indentation__ of 2 spaces - - no tailing __semicolons__ - - single __quotes__ - - use one line __comments__ to describe any additions - - look around and you'll know what to do -- write at least one spec example per implementation or modification +### Indentation +We do it with __two spaces__. Make sure you don't start using tabs because then things get messy. -Before running the specs you will need to build the library. -Be aware that pull requests without specs will be declined. +### Avoid hairy code +We like to keep things simple and clean, don't write anything you don't need. So use __single quotes__ where possible and __avoid semicolons__, we're not writing PHP here. +__Good__: +```javascript +var text = draw.text('with single quotes here') + , nest = draw.nested().attr('x', '50%') -## Building -Starting out with the default distribution of svg.js is good. Although you might want to remove some modules to keep the size at minimum. +for (var i = 0; i < 5; i++) + if (i != 3) + nest.circle(i * 100) +``` -You will need ruby, RubyGems, and rake installed on your system. +__Bad__: +```javascript +var text = draw.text("with single quotes here"); +var nest = draw.nested().attr("x", "50%"); -``` sh -# dependencies: -$ ruby -v -$ gem -v -$ rake -V +for (var i = 0; i < 5; i++) { + if (i != 3) { + nest.circle(100); + }; +}; +``` + +### Minimize variable declarations +All local variables should be declared at the beginning of a function or method unless there is ony one variable to declare. Although it is not required to assign them at the same moment. When if statements are involved, requiring some variables only to be present in the statement, the necessary variables should be declared right after the if statement. + +__Good__: +```javascript +function reading_board() { + var aap, noot, mies + + aap = 1 + noot = 2 + mies = aap + noot +} +``` -# required to generate the minified version: -$ gem install uglifier +__Bad__: +```javascript +function reading_board() { + var aap = 1 + var noot = 2 + var mies = aap + noot +} ``` -Build svg.js by running `rake`: +### Let your code breathe people! +Don't try to be a code compressor yourself, they do way a better job anyway. Give your code some spaces and newlines. -``` sh -$ rake -Original version: 32.165k -Minified: 14.757k -Minified and gzipped: 4.413k, compression factor 7.289 +__Good__: +```javascript +var nest = draw.nested().attr({ + x: 10 +, y: 20 +, width: 200 +, height: 300 +}) + +for (var i = 0; i < 5; i++) + nest.circle(100) ``` -The resulting files are: +__Bad__: +```javascript +var nest=draw.nested().attr({x:10,y:20,width:200,height:300}); +for(var i=0;i<5;i++)nest.circle(100); +``` -1. `dist/svg.js` -2. `dist/svg.min.js` +### It won't hurt to add a few comments +Where necessary tell us what you are doing but be concise. We only use single-line comments. Also keep your variable and method names short while maintaining readability. -To include optional modules and remove default ones, use the `concat` task. In -this example, 'clip' is removed, but 'group' and 'arrange' are added: +__Good__: +```javascript +// Adds orange-specific methods +SVG.extend(SVG.Rect, { + // Add a nice, transparent orange + orangify: function() { + // fill with orange color + this.fill('orange') + + // add a slight opacity + return this.opacity(0.85) + } +}) +``` + +__Bad__: +```javascript +/* + * + * does something with orange and opacity + * + */ +SVG.extend(SVG.Rect, { + orgf: function() { + return this.fill('orange').opacity(0.85) + } +}) +``` + +### Refactor your code +Once your implementation is ready, revisit and rework it. We like to keep it [DRY](http://en.wikipedia.org/wiki/Don't_repeat_yourself). + +### Test. Your. Code. +It's not that hard to write at least one example per implementation, although we prefer more. Your code might seem to work by quickly testing it in your brwoser but more than often you can't forsee everything. + +Before running the specs you will need to build the library. Be aware that pull requests without specs will be declined. + + +## Building +After contributing you probably want to build the library to run some specs. Make sure you have Node.js installed on your system, `cd` to the svg.js directory and run: ``` sh -$ rake concat[-clip:group:arrange] dist +$ npm install ``` -To build the base library only including shapes: +Build SVG.js by running `gulp`: ``` sh -rake concat[-fx:-event:-group:-arrange:-mask:-gradient:-nested:-sugar] dist +$ gulp ``` +The resulting files are: + +1. `dist/svg.js` +2. `dist/svg.min.js` + ## Compatibility @@ -3256,7 +3856,7 @@ rake concat[-fx:-event:-group:-arrange:-mask:-gradient:-nested:-sugar] dist - Chrome 4+ - Safari 3.2+ - Opera 9+ -- IE9 + +- IE9+ ### Mobile - iOS Safari 3.2+ @@ -3265,6 +3865,10 @@ rake concat[-fx:-event:-group:-arrange:-mask:-gradient:-nested:-sugar] dist - Chrome for Android 18+ - Firefox for Android 15+ -Visit the [svg.js test page](http://svgjs.com/test) if you want to check compatibility with different browsers. +Visit the [SVG.js test page](http://svgjs.com/test) if you want to check compatibility with different browsers. + +## Acknowledgements & Thanks + +Documentation kindly provided by [DocumentUp](http://documentup.com) -Important: this library is still in beta, therefore the API might be subject to change in the course of development. +SVG.js and its documentation is released under the terms of the MIT license. diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 9ddc500..0000000 --- a/Rakefile +++ /dev/null @@ -1,160 +0,0 @@ -SVGJS_VERSION = '1.1.0' - -# all available modules in the correct loading order -MODULES = %w[ svg selector inventor polyfill regex default color array pointarray patharray number viewbox bbox rbox element parent container fx relative event defs group arrange mask clip gradient pattern doc shape symbol use rect ellipse line poly path image text textpath nested hyperlink marker sugar set data memory helpers ] - -# how many bytes in a "kilobyte" -KILO = 1024 - -# define default task -task :default => :dist - -# module-aware file task -class BuildTask < Rake::FileTask - - def modules - prerequisites.map { |f| File.basename(f, '.js') } - end - - def remove_prerequisites to_remove - @prerequisites -= to_remove - return self - end - - def needed?() super or modules_mismatch? end - - def modules_mismatch? - previous_modules != modules - end - - def previous_modules - first_line =~ / - ([\w,\s]+) - / && $1.split(/\W+/) - end - - def first_line - File.open(name, 'r') { |f| f.gets } - end - -end - -BuildTask.define_task 'dist/svg.js' => MODULES.map {|m| "src/#{ m }.js" } do |task| - mkdir_p 'dist', :verbose => false - - svgjs = '' - - task.prerequisites.each do |src| - # bring in source files one by one, but without copyright info - copyright = true - File.open(src).each_line do |line| - copyright = false if copyright and line !~ %r{^(/|\s*$)} - svgjs << " #{ line }" unless copyright - end - svgjs << "\n\n" - end - - File.open(task.name, 'w') do |file| - file.puts "/* svg.js %s - %s - svgjs.com/license */" % [version_string, task.modules.join(' ')] - file.puts ";(function(root, factory) {" - file.puts " if (typeof define === 'function' && define.amd) {" - file.puts " define(factory);" - file.puts " } else if (typeof exports === 'object') {" - file.puts " module.exports = factory();" - file.puts " } else {" - file.puts " root.SVG = factory();" - file.puts " }" - file.puts "}(this, function() {" - file.puts "\n" - file.puts svgjs - file.puts " return SVG" - file.puts "}));" - end - -end - -file 'dist/svg.min.js' => 'dist/svg.js' do |task| - require 'rubygems' - begin require 'uglifier' - rescue LoadError; fail "Uglifier not available: #{$!}" - else - File.open(task.name, 'w') do |min| - min << Uglifier.new.compile(File.read(task.prerequisites.first)) - end - end -end - -file 'dist/svg.min.gz' => 'dist/svg.min.js' do |task| - verbose false do - tmp_file = task.name.sub('.gz', '') - cp task.prerequisites.first, tmp_file - sh 'gzip', '--best', tmp_file - end -end - -desc "Concatenate source files to build svg.js" -task :concat, [:modules] do |task, args| - modules = args[:modules].to_s.split(':') - toattrsdd, to_exclude = modules.partition {|m| m.sub!(/^(-)?(.+)/, 'src/\2.js'); !$1 } - - Rake::Task['dist/svg.js']. - remove_prerequisites(to_exclude).enhance(toattrsdd). - invoke -end - -desc "Generate svg.js distribution files and report size statistics" -task :dist => ['dist/svg.js', 'dist/svg.min.js', 'dist/svg.min.gz'] do |task| - orig_size, min_size, gz_size = task.prerequisites.map {|f| File.size(f) } - - puts "Original version: %.3fk" % (orig_size.to_f / KILO) - puts "Minified: %.3fk" % (min_size.to_f / KILO) - puts "Minified and gzipped: %.3fk, compression factor %.3f" % [gz_size.to_f / KILO, orig_size.to_f / gz_size] - - rm_f 'dist/svg.min.gz', :verbose => false -end - -desc "List available modules" -task :modules do - Dir['src/**/*.js'].each do |file| - name = file.gsub(/^src\//,'').gsub(/.js$/,'') - puts name + (MODULES.include?(name) ? '*' : '') - end - puts "\n*included in default build" -end - -task(:clean) { rm_rf 'dist' } - -desc "Run tests with PhantomJS" -task :test do - sh 'script/test' - Rake::Task[:check_whitespace].invoke -end - -desc "Strip trailing whitespace and ensure each file ends with a newline" -task :whitespace do - verbose false do - files = Dir['{src,test,examples}/**/*.{js,html}'] - ruby(*%w'-p -i -e $_.sub!(/\s*\Z/,"\n")'.concat(files)) - end -end - -desc "Checks for trailing whitespace in source files and tests" -task :check_whitespace do - flunked = false - flunk = lambda {|file, num| flunked = true; puts "#{file}:#{num}" } - Dir['{src,test,examples}/**/*.{js,html}'].each do |file| - File.open(file, 'r') {|f| f.each_with_index {|ln, num| flunk.call(file, num + 1) if ln.chomp =~ /\s+$/ } } - end - fail if flunked -end - -# svg.js version number + git sha if available -def version_string - desc = `git describe --tags HEAD 2>&1`.chomp - if $?.success? - desc - else - suffix, dir = '', File.basename(Dir.pwd) - # detect git sha from directory name of GitHub zip/tarball - suffix = "-g#{$1}" if dir =~ /^wout-svg.js-([a-f0-9]{7,40})$/ - SVGJS_VERSION + suffix - end -end
\ No newline at end of file diff --git a/bower.json b/bower.json index 5aec152..a2fc3d8 100644..100755 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "svg.js", - "version":"1.1.0", + "version":"2.0.0-rc.2", "homepage": "http://svgjs.com/", "authors": [ "Wout Fierens <wout@impinc.co.uk>" diff --git a/component.json b/component.json index 822b1f0..4cd508a 100644..100755 --- a/component.json +++ b/component.json @@ -2,7 +2,7 @@ "name": "svg.js", "repo": "wout/svg.js", "description": "A lightweight library for manipulating and animating SVG", - "version": "1.1.0", + "version": "2.0.0-rc.1", "keywords": ["svg"], "author": "Wout Fierens <wout@impinc.co.uk>", "main": "dist/svg.js", diff --git a/dist/svg.js b/dist/svg.js index 1d74c18..5f41862 100644..100755 --- a/dist/svg.js +++ b/dist/svg.js @@ -1,784 +1,787 @@ -/* svg.js 1.1.0 - svg selector inventor polyfill regex default color array pointarray patharray number viewbox bbox rbox element parent container fx relative event defs group arrange mask clip gradient pattern doc shape symbol use rect ellipse line poly path image text textpath nested hyperlink marker sugar set data memory helpers - svgjs.com/license */ -;(function(root, factory) { +/*! +* SVG.js - A lightweight library for manipulating and animating SVG. +* @version 2.0.0-rc.2 +* http://www.svgjs.com +* +* @copyright Wout Fierens <wout@impinc.co.uk> +* @license MIT +* +* BUILT: Thu Jun 11 2015 00:40:21 GMT+0200 (Mitteleuropäische Sommerzeit) +*/; + +(function(root, factory) { if (typeof define === 'function' && define.amd) { define(factory); } else if (typeof exports === 'object') { - module.exports = factory(); + module.exports = factory(require, exports, module); } else { root.SVG = factory(); } -}(this, function() { +}(this, function(require, exports, module) { + +// The main wrapping element +var SVG = this.SVG = function(element) { + if (SVG.supported) { + element = new SVG.Doc(element) + + if (!SVG.parser) + SVG.prepare(element) - var SVG = this.SVG = function(element) { - if (SVG.supported) { - element = new SVG.Doc(element) - - if (!SVG.parser) - SVG.prepare(element) - - return element - } - } - - // Default namespaces - SVG.ns = 'http://www.w3.org/2000/svg' - SVG.xmlns = 'http://www.w3.org/2000/xmlns/' - SVG.xlink = 'http://www.w3.org/1999/xlink' - - // Element id sequence - SVG.did = 1000 - - // Get next named element id - SVG.eid = function(name) { - return 'Svgjs' + name.charAt(0).toUpperCase() + name.slice(1) + (SVG.did++) - } - - // Method for element creation - SVG.create = function(name) { - /* create element */ - var element = document.createElementNS(this.ns, name) - - /* apply unique id */ - element.setAttribute('id', this.eid(name)) - return element } +} + +// Default namespaces +SVG.ns = 'http://www.w3.org/2000/svg' +SVG.xmlns = 'http://www.w3.org/2000/xmlns/' +SVG.xlink = 'http://www.w3.org/1999/xlink' + +// Svg support test +SVG.supported = (function() { + return !! document.createElementNS && + !! document.createElementNS(SVG.ns,'svg').createSVGRect +})() + +// Don't bother to continue if SVG is not supported +if (!SVG.supported) return false + +// Element id sequence +SVG.did = 1000 + +// Get next named element id +SVG.eid = function(name) { + return 'Svgjs' + capitalize(name) + (SVG.did++) +} + +// Method for element creation +SVG.create = function(name) { + // create element + var element = document.createElementNS(this.ns, name) - // Method for extending objects - SVG.extend = function() { - var modules, methods, key, i - - /* get list of modules */ - modules = [].slice.call(arguments) - - /* get object with extensions */ - methods = modules.pop() - - for (i = modules.length - 1; i >= 0; i--) - if (modules[i]) - for (key in methods) - modules[i].prototype[key] = methods[key] + // apply unique id + element.setAttribute('id', this.eid(name)) - /* make sure SVG.Set inherits any newly added methods */ - if (SVG.Set && SVG.Set.inherit) - SVG.Set.inherit() - } + return element +} + +// Method for extending objects +SVG.extend = function() { + var modules, methods, key, i - // Initialize parsing element - SVG.prepare = function(element) { - /* select document body and create invisible svg element */ - var body = document.getElementsByTagName('body')[0] - , draw = (body ? new SVG.Doc(body) : element.nested()).size(2, 0) - , path = SVG.create('path') + // Get list of modules + modules = [].slice.call(arguments) - /* insert parsers */ - draw.node.appendChild(path) + // Get object with extensions + methods = modules.pop() - /* create parser object */ - SVG.parser = { - body: body || element.parent - , draw: draw.style('opacity:0;position:fixed;left:100%;top:100%;overflow:hidden') - , poly: draw.polyline().node - , path: path + for (i = modules.length - 1; i >= 0; i--) + if (modules[i]) + for (key in methods) + modules[i].prototype[key] = methods[key] + + // Make sure SVG.Set inherits any newly added methods + if (SVG.Set && SVG.Set.inherit) + SVG.Set.inherit() +} + +// Invent new element +SVG.invent = function(config) { + // Create element initializer + var initializer = typeof config.create == 'function' ? + config.create : + function() { + this.constructor.call(this, SVG.create(config.create)) } - } - - // svg support test - SVG.supported = (function() { - return !! document.createElementNS && - !! document.createElementNS(SVG.ns,'svg').createSVGRect - })() - - if (!SVG.supported) return false + // Inherit prototype + if (config.inherit) + initializer.prototype = new config.inherit - SVG.get = function(id) { - var node = document.getElementById(idFromReference(id) || id) - if (node) return node.instance - } + // Extend with methods + if (config.extend) + SVG.extend(initializer, config.extend) + + // Attach construct method to parent + if (config.construct) + SVG.extend(config.parent || SVG.Container, config.construct) + + return initializer +} + +// Adopt existing svg elements +SVG.adopt = function(node) { + // make sure a node isn't already adopted + if (node.instance) return node.instance + + // initialize variables + var element + + // adopt with element-specific settings + if (node.nodeName == 'svg') + element = node.parentNode instanceof SVGElement ? new SVG.Nested : new SVG.Doc + else if (node.nodeName == 'lineairGradient') // lineair? + element = new SVG.Gradient('lineair') + else if (node.nodeName == 'radialGradient') + element = new SVG.Gradient('radial') + else if (SVG[capitalize(node.nodeName)]) + element = new SVG[capitalize(node.nodeName)] + else + element = new SVG.Element(node) + + // ensure references + element.type = node.nodeName + element.node = node + node.instance = element - SVG.invent = function(config) { - /* create element initializer */ - var initializer = typeof config.create == 'function' ? - config.create : - function() { - this.constructor.call(this, SVG.create(config.create)) - } + // SVG.Class specific preparations + if (element instanceof SVG.Doc) + element.namespace().defs() + + return element +} + +// Initialize parsing element +SVG.prepare = function(element) { + // Select document body and create invisible svg element + var body = document.getElementsByTagName('body')[0] + , draw = (body ? new SVG.Doc(body) : element.nested()).size(2, 0) + , path = SVG.create('path') + + // Insert parsers + draw.node.appendChild(path) + + // Create parser object + SVG.parser = { + body: body || element.parent() + , draw: draw.style('opacity:0;position:fixed;left:100%;top:100%;overflow:hidden') + , poly: draw.polyline().node + , path: path + } +} +// Storage for regular expressions +SVG.regex = { + // Parse unit value + unit: /^(-?[\d\.]+)([a-z%]{0,2})$/ - /* inherit prototype */ - if (config.inherit) - initializer.prototype = new config.inherit + // Parse hex value +, hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i - /* extend with methods */ - if (config.extend) - SVG.extend(initializer, config.extend) + // Parse rgb value +, rgb: /rgb\((\d+),(\d+),(\d+)\)/ - /* attach construct method to parent */ - if (config.construct) - SVG.extend(config.parent || SVG.Container, config.construct) + // Parse reference id +, reference: /#([a-z0-9\-_]+)/i - return initializer - } + // Parse matrix wrapper +, matrix: /matrix\(|\)/g + + // Whitespace +, whitespace: /\s/g - if (typeof CustomEvent !== 'function') { - // Code from: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent - var CustomEvent = function(event, options) { - options = options || { bubbles: false, cancelable: false, detail: undefined } - var e = document.createEvent('CustomEvent') - e.initCustomEvent(event, options.bubbles, options.cancelable, options.detail) - return e - } + // Test hex value +, isHex: /^#[a-f0-9]{3,6}$/i - CustomEvent.prototype = window.Event.prototype + // Test rgb value +, isRgb: /^rgb\(/ - window.CustomEvent = CustomEvent - } + // Test css declaration +, isCss: /[^:]+:[^;]+;?/ - // requestAnimationFrame / cancelAnimationFrame Polyfill with fallback based on Paul Irish - (function(w) { - var lastTime = 0 - var vendors = ['moz', 'webkit'] - - for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { - w.requestAnimationFrame = w[vendors[x] + 'RequestAnimationFrame'] - w.cancelAnimationFrame = w[vendors[x] + 'CancelAnimationFrame'] || - w[vendors[x] + 'CancelRequestAnimationFrame'] - } - - w.requestAnimationFrame = w.requestAnimationFrame || - function(callback) { - var currTime = new Date().getTime() - var timeToCall = Math.max(0, 16 - (currTime - lastTime)) - - var id = w.setTimeout(function() { - callback(currTime + timeToCall) - }, timeToCall) - - lastTime = currTime + timeToCall - return id - } - - w.cancelAnimationFrame = w.cancelAnimationFrame || w.clearTimeout; + // Test for blank string +, isBlank: /^(\s+)?$/ - }(window)) + // Test for numeric string +, isNumber: /^-?[\d\.]+$/ - SVG.regex = { - /* parse unit value */ - unit: /^(-?[\d\.]+)([a-z%]{0,2})$/ - - /* parse hex value */ - , hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i - - /* parse rgb value */ - , rgb: /rgb\((\d+),(\d+),(\d+)\)/ + // Test for percent value +, isPercent: /^-?[\d\.]+%$/ + + // Test for image url +, isImage: /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i + +} +SVG.utils = { + // Map function + map: function(array, block) { + var i + , il = array.length + , result = [] + + for (i = 0; i < il; i++) + result.push(block(array[i])) - /* parse reference id */ - , reference: /#([a-z0-9\-_]+)/i + return result + } + + // Degrees to radians +, radians: function(d) { + return d % 360 * Math.PI / 180 + } + // Radians to degrees +, degrees: function(r) { + return r * 180 / Math.PI % 360 + } + +} + +SVG.defaults = { + // Default attribute values + attrs: { + /* fill and stroke */ + 'fill-opacity': 1 + , 'stroke-opacity': 1 + , 'stroke-width': 0 + , 'stroke-linejoin': 'miter' + , 'stroke-linecap': 'butt' + , fill: '#000000' + , stroke: '#000000' + , opacity: 1 + /* position */ + , x: 0 + , y: 0 + , cx: 0 + , cy: 0 + /* size */ + , width: 0 + , height: 0 + /* radius */ + , r: 0 + , rx: 0 + , ry: 0 + /* gradient */ + , offset: 0 + , 'stop-opacity': 1 + , 'stop-color': '#000000' + /* text */ + , 'font-size': 16 + , 'font-family': 'Helvetica, Arial, sans-serif' + , 'text-anchor': 'start' + } - /* test hex value */ - , isHex: /^#[a-f0-9]{3,6}$/i - - /* test rgb value */ - , isRgb: /^rgb\(/ - - /* test css declaration */ - , isCss: /[^:]+:[^;]+;?/ +} +// Module for color convertions +SVG.Color = function(color) { + var match + + /* initialize defaults */ + this.r = 0 + this.g = 0 + this.b = 0 + + /* parse color */ + if (typeof color === 'string') { + if (SVG.regex.isRgb.test(color)) { + /* get rgb values */ + match = SVG.regex.rgb.exec(color.replace(/\s/g,'')) + + /* parse numeric values */ + this.r = parseInt(match[1]) + this.g = parseInt(match[2]) + this.b = parseInt(match[3]) + + } else if (SVG.regex.isHex.test(color)) { + /* get hex values */ + match = SVG.regex.hex.exec(fullHex(color)) + + /* parse numeric values */ + this.r = parseInt(match[1], 16) + this.g = parseInt(match[2], 16) + this.b = parseInt(match[3], 16) + + } - /* test for blank string */ - , isBlank: /^(\s+)?$/ + } else if (typeof color === 'object') { + this.r = color.r + this.g = color.g + this.b = color.b - /* test for numeric string */ - , isNumber: /^-?[\d\.]+$/ - - /* test for percent value */ - , isPercent: /^-?[\d\.]+%$/ - - /* test for image url */ - , isImage: /\.(jpg|jpeg|png|gif)(\?[^=]+.*)?/i + } - /* test for namespaced event */ - , isEvent: /^[\w]+:[\w]+$/ - +} + +SVG.extend(SVG.Color, { + // Default to hex conversion + toString: function() { + return this.toHex() + } + // Build hex value +, toHex: function() { + return '#' + + compToHex(this.r) + + compToHex(this.g) + + compToHex(this.b) } + // Build rgb value +, toRgb: function() { + return 'rgb(' + [this.r, this.g, this.b].join() + ')' + } + // Calculate true brightness +, brightness: function() { + return (this.r / 255 * 0.30) + + (this.g / 255 * 0.59) + + (this.b / 255 * 0.11) + } + // Make color morphable +, morph: function(color) { + this.destination = new SVG.Color(color) - SVG.defaults = { - // Default matrix - matrix: '1 0 0 1 0 0' - - // Default attribute values - , attrs: { - /* fill and stroke */ - 'fill-opacity': 1 - , 'stroke-opacity': 1 - , 'stroke-width': 0 - , 'stroke-linejoin': 'miter' - , 'stroke-linecap': 'butt' - , fill: '#000000' - , stroke: '#000000' - , opacity: 1 - /* position */ - , x: 0 - , y: 0 - , cx: 0 - , cy: 0 - /* size */ - , width: 0 - , height: 0 - /* radius */ - , r: 0 - , rx: 0 - , ry: 0 - /* gradient */ - , offset: 0 - , 'stop-opacity': 1 - , 'stop-color': '#000000' - /* text */ - , 'font-size': 16 - , 'font-family': 'Helvetica, Arial, sans-serif' - , 'text-anchor': 'start' - } - - // Default transformation values - , trans: function() { - return { - /* translate */ - x: 0 - , y: 0 - /* scale */ - , scaleX: 1 - , scaleY: 1 - /* rotate */ - , rotation: 0 - /* skew */ - , skewX: 0 - , skewY: 0 - /* matrix */ - , matrix: this.matrix - , a: 1 - , b: 0 - , c: 0 - , d: 1 - , e: 0 - , f: 0 - } - } - + return this } + // Get morphed color at given position +, at: function(pos) { + /* make sure a destination is defined */ + if (!this.destination) return this - SVG.Color = function(color) { - var match - - /* initialize defaults */ - this.r = 0 - this.g = 0 - this.b = 0 - - /* parse color */ - if (typeof color === 'string') { - if (SVG.regex.isRgb.test(color)) { - /* get rgb values */ - match = SVG.regex.rgb.exec(color.replace(/\s/g,'')) - - /* parse numeric values */ - this.r = parseInt(match[1]) - this.g = parseInt(match[2]) - this.b = parseInt(match[3]) - - } else if (SVG.regex.isHex.test(color)) { - /* get hex values */ - match = SVG.regex.hex.exec(fullHex(color)) - - /* parse numeric values */ - this.r = parseInt(match[1], 16) - this.g = parseInt(match[2], 16) - this.b = parseInt(match[3], 16) - - } - - } else if (typeof color === 'object') { - this.r = color.r - this.g = color.g - this.b = color.b - - } - + /* normalise pos */ + pos = pos < 0 ? 0 : pos > 1 ? 1 : pos + + /* generate morphed color */ + return new SVG.Color({ + r: ~~(this.r + (this.destination.r - this.r) * pos) + , g: ~~(this.g + (this.destination.g - this.g) * pos) + , b: ~~(this.b + (this.destination.b - this.b) * pos) + }) } - SVG.extend(SVG.Color, { - // Default to hex conversion - toString: function() { - return this.toHex() - } - // Build hex value - , toHex: function() { - return '#' - + compToHex(this.r) - + compToHex(this.g) - + compToHex(this.b) - } - // Build rgb value - , toRgb: function() { - return 'rgb(' + [this.r, this.g, this.b].join() + ')' - } - // Calculate true brightness - , brightness: function() { - return (this.r / 255 * 0.30) - + (this.g / 255 * 0.59) - + (this.b / 255 * 0.11) - } - // Make color morphable - , morph: function(color) { - this.destination = new SVG.Color(color) - - return this - } - // Get morphed color at given position - , at: function(pos) { - /* make sure a destination is defined */ - if (!this.destination) return this - - /* normalise pos */ - pos = pos < 0 ? 0 : pos > 1 ? 1 : pos - - /* generate morphed color */ - return new SVG.Color({ - r: ~~(this.r + (this.destination.r - this.r) * pos) - , g: ~~(this.g + (this.destination.g - this.g) * pos) - , b: ~~(this.b + (this.destination.b - this.b) * pos) - }) +}) + +// Testers + +// Test if given value is a color string +SVG.Color.test = function(color) { + color += '' + return SVG.regex.isHex.test(color) + || SVG.regex.isRgb.test(color) +} + +// Test if given value is a rgb object +SVG.Color.isRgb = function(color) { + return color && typeof color.r == 'number' + && typeof color.g == 'number' + && typeof color.b == 'number' +} + +// Test if given value is a color +SVG.Color.isColor = function(color) { + return SVG.Color.isRgb(color) || SVG.Color.test(color) +} +// Module for array conversion +SVG.Array = function(array, fallback) { + array = (array || []).valueOf() + + /* if array is empty and fallback is provided, use fallback */ + if (array.length == 0 && fallback) + array = fallback.valueOf() + + /* parse array */ + this.value = this.parse(array) +} + +SVG.extend(SVG.Array, { + // Make array morphable + morph: function(array) { + this.destination = this.parse(array) + + /* normalize length of arrays */ + if (this.value.length != this.destination.length) { + var lastValue = this.value[this.value.length - 1] + , lastDestination = this.destination[this.destination.length - 1] + + while(this.value.length > this.destination.length) + this.destination.push(lastDestination) + while(this.value.length < this.destination.length) + this.value.push(lastValue) } - }) - - // Testers - - // Test if given value is a color string - SVG.Color.test = function(color) { - color += '' - return SVG.regex.isHex.test(color) - || SVG.regex.isRgb.test(color) + return this } - - // Test if given value is a rgb object - SVG.Color.isRgb = function(color) { - return color && typeof color.r == 'number' - && typeof color.g == 'number' - && typeof color.b == 'number' + // Clean up any duplicate points +, settle: function() { + /* find all unique values */ + for (var i = 0, il = this.value.length, seen = []; i < il; i++) + if (seen.indexOf(this.value[i]) == -1) + seen.push(this.value[i]) + + /* set new value */ + return this.value = seen } - - // Test if given value is a color - SVG.Color.isColor = function(color) { - return SVG.Color.isRgb(color) || SVG.Color.test(color) + // Get morphed array at given position +, at: function(pos) { + /* make sure a destination is defined */ + if (!this.destination) return this + + /* generate morphed array */ + for (var i = 0, il = this.value.length, array = []; i < il; i++) + array.push(this.value[i] + (this.destination[i] - this.value[i]) * pos) + + return new SVG.Array(array) } + // Convert array to string +, toString: function() { + return this.value.join(' ') + } + // Real value +, valueOf: function() { + return this.value + } + // Parse whitespace separated string +, parse: function(array) { + array = array.valueOf() - SVG.Array = function(array, fallback) { - array = (array || []).valueOf() - - /* if array is empty and fallback is provided, use fallback */ - if (array.length == 0 && fallback) - array = fallback.valueOf() - - /* parse array */ - this.value = this.parse(array) + /* if already is an array, no need to parse it */ + if (Array.isArray(array)) return array + + return this.split(array) + } + // Strip unnecessary whitespace +, split: function(string) { + return string.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g,'').split(' ') + } + // Reverse array +, reverse: function() { + this.value.reverse() + + return this } - - SVG.extend(SVG.Array, { - // Make array morphable - morph: function(array) { - this.destination = this.parse(array) - - /* normalize length of arrays */ - if (this.value.length != this.destination.length) { - var lastValue = this.value[this.value.length - 1] - , lastDestination = this.destination[this.destination.length - 1] - - while(this.value.length > this.destination.length) - this.destination.push(lastDestination) - while(this.value.length < this.destination.length) - this.value.push(lastValue) - } - - return this - } - // Clean up any duplicate points - , settle: function() { - /* find all unique values */ - for (var i = 0, il = this.value.length, seen = []; i < il; i++) - if (seen.indexOf(this.value[i]) == -1) - seen.push(this.value[i]) - - /* set new value */ - return this.value = seen - } - // Get morphed array at given position - , at: function(pos) { - /* make sure a destination is defined */ - if (!this.destination) return this - - /* generate morphed array */ - for (var i = 0, il = this.value.length, array = []; i < il; i++) - array.push(this.value[i] + (this.destination[i] - this.value[i]) * pos) - - return new SVG.Array(array) - } - // Convert array to string - , toString: function() { - return this.value.join(' ') - } - // Real value - , valueOf: function() { - return this.value - } - // Parse whitespace separated string - , parse: function(array) { - array = array.valueOf() - - /* if already is an array, no need to parse it */ - if (Array.isArray(array)) return array - - return this.split(array) - } - // Strip unnecessary whitespace - , split: function(string) { - return string.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g,'').split(' ') - } - // Reverse array - , reverse: function() { - this.value.reverse() - - return this - } - - }) - +}) +// Poly points array +SVG.PointArray = function(array, fallback) { + this.constructor.call(this, array, fallback || [[0,0]]) +} - SVG.PointArray = function() { - this.constructor.apply(this, arguments) +// Inherit from SVG.Array +SVG.PointArray.prototype = new SVG.Array + +SVG.extend(SVG.PointArray, { + // Convert array to string + toString: function() { + /* convert to a poly point string */ + for (var i = 0, il = this.value.length, array = []; i < il; i++) + array.push(this.value[i].join(',')) + + return array.join(' ') } - - // Inherit from SVG.Array - SVG.PointArray.prototype = new SVG.Array - - SVG.extend(SVG.PointArray, { - // Convert array to string - toString: function() { - /* convert to a poly point string */ - for (var i = 0, il = this.value.length, array = []; i < il; i++) - array.push(this.value[i].join(',')) - - return array.join(' ') - } - // Get morphed array at given position - , at: function(pos) { - /* make sure a destination is defined */ - if (!this.destination) return this - - /* generate morphed point string */ - for (var i = 0, il = this.value.length, array = []; i < il; i++) - array.push([ - this.value[i][0] + (this.destination[i][0] - this.value[i][0]) * pos - , this.value[i][1] + (this.destination[i][1] - this.value[i][1]) * pos - ]) - - return new SVG.PointArray(array) - } - // Parse point string - , parse: function(array) { - array = array.valueOf() - - /* if already is an array, no need to parse it */ - if (Array.isArray(array)) return array - - /* split points */ - array = this.split(array) - - /* parse points */ - for (var i = 0, il = array.length, p, points = []; i < il; i++) { - p = array[i].split(',') - points.push([parseFloat(p[0]), parseFloat(p[1])]) - } - - return points - } - // Move point string - , move: function(x, y) { - var box = this.bbox() - - /* get relative offset */ - x -= box.x - y -= box.y - - /* move every point */ - if (!isNaN(x) && !isNaN(y)) - for (var i = this.value.length - 1; i >= 0; i--) - this.value[i] = [this.value[i][0] + x, this.value[i][1] + y] - - return this - } - // Resize poly string - , size: function(width, height) { - var i, box = this.bbox() - - /* recalculate position of all points according to new size */ - for (i = this.value.length - 1; i >= 0; i--) { - this.value[i][0] = ((this.value[i][0] - box.x) * width) / box.width + box.x - this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y - } - - return this - } - // Get bounding box of points - , bbox: function() { - SVG.parser.poly.setAttribute('points', this.toString()) - - return SVG.parser.poly.getBBox() + // Convert array to line object +, toLine: function() { + return { + x1: this.value[0][0] + , y1: this.value[0][1] + , x2: this.value[1][0] + , y2: this.value[1][1] } - - }) + } + // Get morphed array at given position +, at: function(pos) { + /* make sure a destination is defined */ + if (!this.destination) return this + + /* generate morphed point string */ + for (var i = 0, il = this.value.length, array = []; i < il; i++) + array.push([ + this.value[i][0] + (this.destination[i][0] - this.value[i][0]) * pos + , this.value[i][1] + (this.destination[i][1] - this.value[i][1]) * pos + ]) - SVG.PathArray = function(array, fallback) { - this.constructor.call(this, array, fallback) + return new SVG.PointArray(array) } - - // Inherit from SVG.Array - SVG.PathArray.prototype = new SVG.Array - - SVG.extend(SVG.PathArray, { - // Convert array to string - toString: function() { - return arrayToString(this.value) + // Parse point string +, parse: function(array) { + array = array.valueOf() + + /* if already is an array, no need to parse it */ + if (Array.isArray(array)) return array + + /* split points */ + array = this.split(array) + + /* parse points */ + for (var i = 0, il = array.length, p, points = []; i < il; i++) { + p = array[i].split(',') + points.push([parseFloat(p[0]), parseFloat(p[1])]) } - // Move path string - , move: function(x, y) { - /* get bounding box of current situation */ - var box = this.bbox() - - /* get relative offset */ - x -= box.x - y -= box.y - - if (!isNaN(x) && !isNaN(y)) { - /* move every point */ - for (var l, i = this.value.length - 1; i >= 0; i--) { - l = this.value[i][0] - - if (l == 'M' || l == 'L' || l == 'T') { - this.value[i][1] += x - this.value[i][2] += y - - } else if (l == 'H') { - this.value[i][1] += x - - } else if (l == 'V') { - this.value[i][1] += y - - } else if (l == 'C' || l == 'S' || l == 'Q') { - this.value[i][1] += x - this.value[i][2] += y - this.value[i][3] += x - this.value[i][4] += y - - if (l == 'C') { - this.value[i][5] += x - this.value[i][6] += y - } - - } else if (l == 'A') { - this.value[i][6] += x - this.value[i][7] += y - } - - } - } - - return this + + return points + } + // Move point string +, move: function(x, y) { + var box = this.bbox() + + /* get relative offset */ + x -= box.x + y -= box.y + + /* move every point */ + if (!isNaN(x) && !isNaN(y)) + for (var i = this.value.length - 1; i >= 0; i--) + this.value[i] = [this.value[i][0] + x, this.value[i][1] + y] + + return this + } + // Resize poly string +, size: function(width, height) { + var i, box = this.bbox() + + /* recalculate position of all points according to new size */ + for (i = this.value.length - 1; i >= 0; i--) { + this.value[i][0] = ((this.value[i][0] - box.x) * width) / box.width + box.x + this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y } - // Resize path string - , size: function(width, height) { - /* get bounding box of current situation */ - var i, l, box = this.bbox() - - /* recalculate position of all points according to new size */ - for (i = this.value.length - 1; i >= 0; i--) { + + return this + } + // Get bounding box of points +, bbox: function() { + SVG.parser.poly.setAttribute('points', this.toString()) + + return SVG.parser.poly.getBBox() + } + +}) +// Path points array +SVG.PathArray = function(array, fallback) { + this.constructor.call(this, array, fallback || [['M', 0, 0]]) +} + +// Inherit from SVG.Array +SVG.PathArray.prototype = new SVG.Array + +SVG.extend(SVG.PathArray, { + // Convert array to string + toString: function() { + return arrayToString(this.value) + } + // Move path string +, move: function(x, y) { + /* get bounding box of current situation */ + var box = this.bbox() + + /* get relative offset */ + x -= box.x + y -= box.y + + if (!isNaN(x) && !isNaN(y)) { + /* move every point */ + for (var l, i = this.value.length - 1; i >= 0; i--) { l = this.value[i][0] - + if (l == 'M' || l == 'L' || l == 'T') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x - this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y - + this.value[i][1] += x + this.value[i][2] += y + } else if (l == 'H') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x - + this.value[i][1] += x + } else if (l == 'V') { - this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y - + this.value[i][1] += y + } else if (l == 'C' || l == 'S' || l == 'Q') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x - this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y - this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x - this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y - + this.value[i][1] += x + this.value[i][2] += y + this.value[i][3] += x + this.value[i][4] += y + if (l == 'C') { - this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x - this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y + this.value[i][5] += x + this.value[i][6] += y } - + } else if (l == 'A') { - /* resize radii */ - this.value[i][1] = (this.value[i][1] * width) / box.width - this.value[i][2] = (this.value[i][2] * height) / box.height - - /* move position values */ - this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x - this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y + this.value[i][6] += x + this.value[i][7] += y } - + } - - return this } - // Absolutize and parse path to array - , parse: function(array) { - /* if it's already is a patharray, no need to parse it */ - if (array instanceof SVG.PathArray) return array.valueOf() - - /* prepare for parsing */ - var i, il, x0, y0, x1, y1, x2, y2, s, seg, segs - , x = 0 - , y = 0 - - /* populate working path */ - SVG.parser.path.setAttribute('d', typeof array === 'string' ? array : arrayToString(array)) - - /* get segments */ - segs = SVG.parser.path.pathSegList - - for (i = 0, il = segs.numberOfItems; i < il; ++i) { - seg = segs.getItem(i) - s = seg.pathSegTypeAsLetter - - /* yes, this IS quite verbose but also about 30 times faster than .test() with a precompiled regex */ - if (s == 'M' || s == 'L' || s == 'H' || s == 'V' || s == 'C' || s == 'S' || s == 'Q' || s == 'T' || s == 'A') { - if ('x' in seg) x = seg.x - if ('y' in seg) y = seg.y - - } else { - if ('x1' in seg) x1 = x + seg.x1 - if ('x2' in seg) x2 = x + seg.x2 - if ('y1' in seg) y1 = y + seg.y1 - if ('y2' in seg) y2 = y + seg.y2 - if ('x' in seg) x += seg.x - if ('y' in seg) y += seg.y - - if (s == 'm') - segs.replaceItem(SVG.parser.path.createSVGPathSegMovetoAbs(x, y), i) - else if (s == 'l') - segs.replaceItem(SVG.parser.path.createSVGPathSegLinetoAbs(x, y), i) - else if (s == 'h') - segs.replaceItem(SVG.parser.path.createSVGPathSegLinetoHorizontalAbs(x), i) - else if (s == 'v') - segs.replaceItem(SVG.parser.path.createSVGPathSegLinetoVerticalAbs(y), i) - else if (s == 'c') - segs.replaceItem(SVG.parser.path.createSVGPathSegCurvetoCubicAbs(x, y, x1, y1, x2, y2), i) - else if (s == 's') - segs.replaceItem(SVG.parser.path.createSVGPathSegCurvetoCubicSmoothAbs(x, y, x2, y2), i) - else if (s == 'q') - segs.replaceItem(SVG.parser.path.createSVGPathSegCurvetoQuadraticAbs(x, y, x1, y1), i) - else if (s == 't') - segs.replaceItem(SVG.parser.path.createSVGPathSegCurvetoQuadraticSmoothAbs(x, y), i) - else if (s == 'a') - segs.replaceItem(SVG.parser.path.createSVGPathSegArcAbs(x, y, seg.r1, seg.r2, seg.angle, seg.largeArcFlag, seg.sweepFlag), i) - else if (s == 'z' || s == 'Z') { - x = x0 - y = y0 - } + + return this + } + // Resize path string +, size: function(width, height) { + /* get bounding box of current situation */ + var i, l, box = this.bbox() + + /* recalculate position of all points according to new size */ + for (i = this.value.length - 1; i >= 0; i--) { + l = this.value[i][0] + + if (l == 'M' || l == 'L' || l == 'T') { + this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x + this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y + + } else if (l == 'H') { + this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x + + } else if (l == 'V') { + this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y + + } else if (l == 'C' || l == 'S' || l == 'Q') { + this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x + this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y + this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x + this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y + + if (l == 'C') { + this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x + this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y } - - /* record the start of a subpath */ - if (s == 'M' || s == 'm') { - x0 = x - y0 = y + + } else if (l == 'A') { + /* resize radii */ + this.value[i][1] = (this.value[i][1] * width) / box.width + this.value[i][2] = (this.value[i][2] * height) / box.height + + /* move position values */ + this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x + this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y + } + + } + + return this + } + // Absolutize and parse path to array +, parse: function(array) { + /* if it's already is a patharray, no need to parse it */ + if (array instanceof SVG.PathArray) return array.valueOf() + + /* prepare for parsing */ + var i, il, x0, y0, x1, y1, x2, y2, s, seg, segs + , x = 0 + , y = 0 + + /* populate working path */ + SVG.parser.path.setAttribute('d', typeof array === 'string' ? array : arrayToString(array)) + + /* get segments */ + segs = SVG.parser.path.pathSegList + + for (i = 0, il = segs.numberOfItems; i < il; ++i) { + seg = segs.getItem(i) + s = seg.pathSegTypeAsLetter + + /* yes, this IS quite verbose but also about 30 times faster than .test() with a precompiled regex */ + if (s == 'M' || s == 'L' || s == 'H' || s == 'V' || s == 'C' || s == 'S' || s == 'Q' || s == 'T' || s == 'A') { + if ('x' in seg) x = seg.x + if ('y' in seg) y = seg.y + + } else { + if ('x1' in seg) x1 = x + seg.x1 + if ('x2' in seg) x2 = x + seg.x2 + if ('y1' in seg) y1 = y + seg.y1 + if ('y2' in seg) y2 = y + seg.y2 + if ('x' in seg) x += seg.x + if ('y' in seg) y += seg.y + + if (s == 'm') + segs.replaceItem(SVG.parser.path.createSVGPathSegMovetoAbs(x, y), i) + else if (s == 'l') + segs.replaceItem(SVG.parser.path.createSVGPathSegLinetoAbs(x, y), i) + else if (s == 'h') + segs.replaceItem(SVG.parser.path.createSVGPathSegLinetoHorizontalAbs(x), i) + else if (s == 'v') + segs.replaceItem(SVG.parser.path.createSVGPathSegLinetoVerticalAbs(y), i) + else if (s == 'c') + segs.replaceItem(SVG.parser.path.createSVGPathSegCurvetoCubicAbs(x, y, x1, y1, x2, y2), i) + else if (s == 's') + segs.replaceItem(SVG.parser.path.createSVGPathSegCurvetoCubicSmoothAbs(x, y, x2, y2), i) + else if (s == 'q') + segs.replaceItem(SVG.parser.path.createSVGPathSegCurvetoQuadraticAbs(x, y, x1, y1), i) + else if (s == 't') + segs.replaceItem(SVG.parser.path.createSVGPathSegCurvetoQuadraticSmoothAbs(x, y), i) + else if (s == 'a') + segs.replaceItem(SVG.parser.path.createSVGPathSegArcAbs(x, y, seg.r1, seg.r2, seg.angle, seg.largeArcFlag, seg.sweepFlag), i) + else if (s == 'z' || s == 'Z') { + x = x0 + y = y0 } } - - /* build internal representation */ - array = [] - segs = SVG.parser.path.pathSegList - - for (i = 0, il = segs.numberOfItems; i < il; ++i) { - seg = segs.getItem(i) - s = seg.pathSegTypeAsLetter - x = [s] - - if (s == 'M' || s == 'L' || s == 'T') - x.push(seg.x, seg.y) - else if (s == 'H') - x.push(seg.x) - else if (s == 'V') - x.push(seg.y) - else if (s == 'C') - x.push(seg.x1, seg.y1, seg.x2, seg.y2, seg.x, seg.y) - else if (s == 'S') - x.push(seg.x2, seg.y2, seg.x, seg.y) - else if (s == 'Q') - x.push(seg.x1, seg.y1, seg.x, seg.y) - else if (s == 'A') - x.push(seg.r1, seg.r2, seg.angle, seg.largeArcFlag|0, seg.sweepFlag|0, seg.x, seg.y) - - /* store segment */ - array.push(x) + + /* record the start of a subpath */ + if (s == 'M' || s == 'm') { + x0 = x + y0 = y } - - return array } - // Get bounding box of path - , bbox: function() { - SVG.parser.path.setAttribute('d', this.toString()) - - return SVG.parser.path.getBBox() + + /* build internal representation */ + array = [] + segs = SVG.parser.path.pathSegList + + for (i = 0, il = segs.numberOfItems; i < il; ++i) { + seg = segs.getItem(i) + s = seg.pathSegTypeAsLetter + x = [s] + + if (s == 'M' || s == 'L' || s == 'T') + x.push(seg.x, seg.y) + else if (s == 'H') + x.push(seg.x) + else if (s == 'V') + x.push(seg.y) + else if (s == 'C') + x.push(seg.x1, seg.y1, seg.x2, seg.y2, seg.x, seg.y) + else if (s == 'S') + x.push(seg.x2, seg.y2, seg.x, seg.y) + else if (s == 'Q') + x.push(seg.x1, seg.y1, seg.x, seg.y) + else if (s == 'A') + x.push(seg.r1, seg.r2, seg.angle, seg.largeArcFlag | 0, seg.sweepFlag | 0, seg.x, seg.y) + + /* store segment */ + array.push(x) } - - }) + + return array + } + // Get bounding box of path +, bbox: function() { + SVG.parser.path.setAttribute('d', this.toString()) - SVG.Number = function(value) { - - /* initialize defaults */ + return SVG.parser.path.getBBox() + } + +}) +// Module for unit convertions +SVG.Number = SVG.invent({ + // Initialize + create: function(value, unit) { + // initialize defaults this.value = 0 - this.unit = '' - - /* parse value */ + this.unit = unit || '' + + // parse value if (typeof value === 'number') { - /* ensure a valid numeric value */ + // ensure a valid numeric value this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value - + } else if (typeof value === 'string') { - var match = value.match(SVG.regex.unit) - - if (match) { - /* make value numeric */ - this.value = parseFloat(match[1]) + unit = value.match(SVG.regex.unit) + + if (unit) { + // make value numeric + this.value = parseFloat(unit[1]) - /* normalize percent value */ - if (match[2] == '%') + // normalize + if (unit[2] == '%') this.value /= 100 - else if (match[2] == 's') + else if (unit[2] == 's') this.value *= 1000 - /* store unit */ - this.unit = match[2] + // store unit + this.unit = unit[2] } - + } else { if (value instanceof SVG.Number) { - this.value = value.value + this.value = value.valueOf() this.unit = value.unit } } - + } - - SVG.extend(SVG.Number, { + // Add methods +, extend: { // Stringalize toString: function() { return ( @@ -795,9 +798,7 @@ } // Add number , plus: function(number) { - this.value = this + new SVG.Number(number) - - return this + return new SVG.Number(this + new SVG.Number(number), this.unit) } // Subtract number , minus: function(number) { @@ -805,2127 +806,2410 @@ } // Multiply number , times: function(number) { - this.value = this * new SVG.Number(number) - - return this + return new SVG.Number(this * new SVG.Number(number), this.unit) } // Divide number , divide: function(number) { - this.value = this / new SVG.Number(number) - - return this + return new SVG.Number(this / new SVG.Number(number), this.unit) } // Convert to different unit , to: function(unit) { + var number = new SVG.Number(this) + if (typeof unit === 'string') - this.unit = unit - - return this + number.unit = unit + + return number } // Make number morphable , morph: function(number) { this.destination = new SVG.Number(number) - + return this } // Get morphed number at given position , at: function(pos) { - /* make sure a destination is defined */ + // Make sure a destination is defined if (!this.destination) return this - - /* generate new morphed number */ + + // Generate new morphed number return new SVG.Number(this.destination) .minus(this) .times(pos) .plus(this) } - - }) - 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 - } + } +}) + +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]) - /* ensure defaults */ - this.x = box.x - this.y = box.y - this.width = width * wm - this.height = height * hm - this.zoom = 1 + /* 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 - 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.extend(SVG.ViewBox, { + // Parse viewbox to string + toString: function() { + return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height } - // - SVG.extend(SVG.ViewBox, { - // Parse viewbox to string - toString: function() { - return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height +}) + +SVG.Element = SVG.invent({ + // Initialize node + create: function(node) { + // make stroke value accessible dynamically + this._stroke = SVG.defaults.attrs.stroke + + // create circular reference + if (this.node = node) { + this.type = node.nodeName + this.node.instance = this + + // store current attribute value + this._stroke = node.getAttribute('stroke') || this._stroke } - - }) + } - SVG.BBox = function(element) { - var box - - /* initialize zero box */ - this.x = 0 - this.y = 0 - this.width = 0 - this.height = 0 - - /* get values if element is given */ - if (element) { - try { - /* actual, native bounding box */ - box = element.node.getBBox() - } catch(e) { - /* fallback for some browsers */ - box = { - x: element.node.clientLeft - , y: element.node.clientTop - , width: element.node.clientWidth - , height: element.node.clientHeight - } + // Add class methods +, extend: { + // Move over x-axis + x: function(x) { + if (x != null) { + x = new SVG.Number(x) + x.value /= this.transform('scaleX') } - - /* include translations on x an y */ - this.x = box.x + element.trans.x - this.y = box.y + element.trans.y - - /* plain width and height */ - this.width = box.width * element.trans.scaleX - this.height = box.height * element.trans.scaleY + return this.attr('x', x) } - - /* add center, right and bottom */ - boxProperties(this) - - } - - // - SVG.extend(SVG.BBox, { - // merge bounding box with another, return a new instance - merge: function(box) { - var b = new SVG.BBox() - - /* merge box */ - b.x = Math.min(this.x, box.x) - b.y = Math.min(this.y, box.y) - b.width = Math.max(this.x + this.width, box.x + box.width) - b.x - b.height = Math.max(this.y + this.height, box.y + box.height) - b.y - - /* add center, right and bottom */ - boxProperties(b) - - return b + // Move over y-axis + , y: function(y) { + if (y != null) { + y = new SVG.Number(y) + y.value /= this.transform('scaleY') + } + return this.attr('y', y) } - - }) + // Move by center over x-axis + , cx: function(x) { + return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2) + } + // Move by center over y-axis + , cy: function(y) { + return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2) + } + // Move element to given x and y values + , move: function(x, y) { + return this.x(x).y(y) + } + // Move element by its center + , center: function(x, y) { + return this.cx(x).cy(y) + } + // Set width of element + , width: function(width) { + return this.attr('width', width) + } + // Set height of element + , height: function(height) { + return this.attr('height', height) + } + // Set element size to given width and height + , size: function(width, height) { + var p = proportionalSize(this.bbox(), width, height) - SVG.RBox = function(element) { - var e, zoom - , box = {} - - /* initialize zero box */ - this.x = 0 - this.y = 0 - this.width = 0 - this.height = 0 - - if (element) { - e = element.doc().parent - zoom = element.doc().viewbox().zoom - - /* actual, native bounding box */ - box = element.node.getBoundingClientRect() + return this + .width(new SVG.Number(p.width)) + .height(new SVG.Number(p.height)) + } + // Clone element + , clone: function() { + // clone element and assign new id + var clone = assignNewId(this.node.cloneNode(true)) + + // insert the clone after myself + this.after(clone) - /* get screen offset */ - this.x = box.left - this.y = box.top + return clone + } + // Remove element + , remove: function() { + if (this.parent()) + this.parent().removeElement(this) - /* subtract parent offset */ - this.x -= e.offsetLeft - this.y -= e.offsetTop + return this + } + // Replace element + , replace: function(element) { + this.after(element).remove() + + return element + } + // Add element to given container and return self + , addTo: function(parent) { + return parent.put(this) + } + // Add element to given container and return container + , putIn: function(parent) { + return parent.add(this) + } + // Get / set id + , id: function(id) { + return this.attr('id', id) + } + // Checks whether the given point inside the bounding box of the element + , inside: function(x, y) { + var box = this.bbox() - while (e = e.offsetParent) { - this.x -= e.offsetLeft - this.y -= e.offsetTop + return x > box.x + && y > box.y + && x < box.x + box.width + && y < box.y + box.height + } + // Show element + , show: function() { + return this.style('display', '') + } + // Hide element + , hide: function() { + return this.style('display', 'none') + } + // Is element visible? + , visible: function() { + return this.style('display') != 'none' + } + // Return id on string conversion + , toString: function() { + return this.attr('id') + } + // Return array of classes on the node + , classes: function() { + var attr = this.attr('class') + + return attr == null ? [] : attr.trim().split(/\s+/) + } + // Return true if class exists on the node, false otherwise + , hasClass: function(name) { + return this.classes().indexOf(name) != -1 + } + // Add class to the node + , addClass: function(name) { + if (!this.hasClass(name)) { + var array = this.classes() + array.push(name) + this.attr('class', array.join(' ')) } - - /* calculate cumulative zoom from svg documents */ - e = element - while (e = e.parent) { - if (e.type == 'svg' && e.viewbox) { - zoom *= e.viewbox().zoom - this.x -= e.x() || 0 - this.y -= e.y() || 0 - } + + return this + } + // Remove class from the node + , removeClass: function(name) { + if (this.hasClass(name)) { + this.attr('class', this.classes().filter(function(c) { + return c != name + }).join(' ')) } + + return this } - - /* recalculate viewbox distortion */ - this.x /= zoom - this.y /= zoom - this.width = box.width /= zoom - this.height = box.height /= zoom - - /* offset by window scroll position, because getBoundingClientRect changes when window is scrolled */ - this.x += typeof window.scrollX === 'number' ? window.scrollX : window.pageXOffset - this.y += typeof window.scrollY === 'number' ? window.scrollY : window.pageYOffset - - /* add center, right and bottom */ - boxProperties(this) - - } - - // - SVG.extend(SVG.RBox, { - // merge rect box with another, return a new instance - merge: function(box) { - var b = new SVG.RBox() - - /* merge box */ - b.x = Math.min(this.x, box.x) - b.y = Math.min(this.y, box.y) - b.width = Math.max(this.x + this.width, box.x + box.width) - b.x - b.height = Math.max(this.y + this.height, box.y + box.height) - b.y - - /* add center, right and bottom */ - boxProperties(b) - - return b + // Toggle the presence of a class on the node + , toggleClass: function(name) { + return this.hasClass(name) ? this.removeClass(name) : this.addClass(name) } - - }) + // Get referenced element form attribute value + , reference: function(attr) { + return SVG.get(this.attr(attr)) + } + // Returns the parent element instance + , parent: function(type) { + if (this.node.parentNode) { + // get parent element + var parent = SVG.adopt(this.node.parentNode) + // if a specific type is given, find a parent with that class + if (type) + while (!(parent instanceof type) && parent.node.parentNode instanceof SVGElement) + parent = SVG.adopt(parent.node.parentNode) - SVG.Element = SVG.invent({ - // Initialize node - create: function(node) { - /* make stroke value accessible dynamically */ - this._stroke = SVG.defaults.attrs.stroke - - /* initialize transformation store with defaults */ - this.trans = SVG.defaults.trans() - - /* create circular reference */ - if (this.node = node) { - this.type = node.nodeName - this.node.instance = this + return parent } } - - // Add class methods - , extend: { - // Move over x-axis - x: function(x) { - if (x != null) { - x = new SVG.Number(x) - x.value /= this.trans.scaleX - } - return this.attr('x', x) - } - // Move over y-axis - , y: function(y) { - if (y != null) { - y = new SVG.Number(y) - y.value /= this.trans.scaleY - } - return this.attr('y', y) - } - // Move by center over x-axis - , cx: function(x) { - return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2) - } - // Move by center over y-axis - , cy: function(y) { - return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2) - } - // Move element to given x and y values - , move: function(x, y) { - return this.x(x).y(y) - } - // Move element by its center - , center: function(x, y) { - return this.cx(x).cy(y) - } - // Set width of element - , width: function(width) { - return this.attr('width', width) - } - // Set height of element - , height: function(height) { - return this.attr('height', height) - } - // Set element size to given width and height - , size: function(width, height) { - var p = proportionalSize(this.bbox(), width, height) - - return this - .width(new SVG.Number(p.width)) - .height(new SVG.Number(p.height)) - } - // Clone element - , clone: function() { - var clone , attr - , type = this.type - - /* invoke shape method with shape-specific arguments */ - clone = type == 'rect' || type == 'ellipse' ? - this.parent[type](0,0) : - type == 'line' ? - this.parent[type](0,0,0,0) : - type == 'image' ? - this.parent[type](this.src) : - type == 'text' ? - this.parent[type](this.content) : - type == 'path' ? - this.parent[type](this.attr('d')) : - type == 'polyline' || type == 'polygon' ? - this.parent[type](this.attr('points')) : - type == 'g' ? - this.parent.group() : - this.parent[type]() - - /* apply attributes attributes */ - attr = this.attr() - delete attr.id - clone.attr(attr) - - /* copy transformations */ - clone.trans = this.trans - - /* apply attributes and translations */ - return clone.transform({}) - } - // Remove element - , remove: function() { - if (this.parent) - this.parent.removeElement(this) - - return this - } - // Replace element - , replace: function(element) { - this.after(element).remove() - - return element - } - // Add element to given container and return self - , addTo: function(parent) { - return parent.put(this) - } - // Add element to given container and return container - , putIn: function(parent) { - return parent.add(this) - } - // Get parent document - , doc: function(type) { - return this._parent(type || SVG.Doc) - } - // Set svg element attribute - , attr: function(a, v, n) { - if (a == null) { - /* get an object of attributes */ - a = {} - v = this.node.attributes - for (n = v.length - 1; n >= 0; n--) - a[v[n].nodeName] = SVG.regex.isNumber.test(v[n].nodeValue) ? parseFloat(v[n].nodeValue) : v[n].nodeValue - - return a - - } else if (typeof a == 'object') { - /* apply every attribute individually if an object is passed */ - for (v in a) this.attr(v, a[v]) - - } else if (v === null) { - /* remove value */ - this.node.removeAttribute(a) - - } else if (v == null) { - /* act as a getter if the first and only argument is not an object */ - v = this.node.attributes[a] - return v == null ? - SVG.defaults.attrs[a] : - SVG.regex.isNumber.test(v.nodeValue) ? - parseFloat(v.nodeValue) : v.nodeValue - - } else if (a == 'style') { - /* redirect to the style method */ - return this.style(v) - - } else { - /* BUG FIX: some browsers will render a stroke if a color is given even though stroke width is 0 */ - if (a == 'stroke-width') - this.attr('stroke', parseFloat(v) > 0 ? this._stroke : null) - else if (a == 'stroke') - this._stroke = v - - /* convert image fill and stroke to patterns */ - if (a == 'fill' || a == 'stroke') { - if (SVG.regex.isImage.test(v)) - v = this.doc().defs().image(v, 0, 0) - - if (v instanceof SVG.Image) - v = this.doc().defs().pattern(0, 0, function() { - this.add(v) - }) - } - - /* ensure correct numeric values (also accepts NaN and Infinity) */ - if (typeof v === 'number') - v = new SVG.Number(v) - - /* ensure full hex color */ - else if (SVG.Color.isColor(v)) - v = new SVG.Color(v) - - /* parse array values */ - else if (Array.isArray(v)) - v = new SVG.Array(v) - - /* if the passed attribute is leading... */ - if (a == 'leading') { - /* ... call the leading method instead */ - if (this.leading) - this.leading(v) - } else { - /* set given attribute on node */ - typeof n === 'string' ? - this.node.setAttributeNS(n, a, v.toString()) : - this.node.setAttribute(a, v.toString()) - } - - /* rebuild if required */ - if (this.rebuild && (a == 'font-size' || a == 'x')) - this.rebuild(a, v) - } - - return this + // Get parent document + , doc: function(type) { + return this.parent(type || SVG.Doc) + } + // Returns the svg node to call native svg methods on it + , native: function() { + return this.node + } + // Import raw svg + , svg: function(svg) { + // create temporary holder + var well = document.createElement('svg') + + // act as a setter if svg is given + if (svg && this instanceof SVG.Parent) { + // dump raw svg + well.innerHTML = '<svg>' + svg.replace(/\n/, '').replace(/<(\w+)([^<]+?)\/>/g, '<$1$2></$1>') + '</svg>' + + // transplant nodes + for (var i = 0, il = well.firstChild.childNodes.length; i < il; i++) + this.node.appendChild(well.firstChild.childNodes[i]) + + // otherwise act as a getter + } else { + // create a wrapping svg element in case of partial content + well.appendChild(svg = document.createElement('svg')) + + // insert a copy of this node + svg.appendChild(this.node.cloneNode(true)) + + // return target element + return well.innerHTML.replace(/^<svg>/, '').replace(/<\/svg>$/, '') } - // Manage transformations - , transform: function(o, v) { - - if (arguments.length == 0) { - /* act as a getter if no argument is given */ - return this.trans - - } else if (typeof o === 'string') { - /* act as a getter if only one string argument is given */ - if (arguments.length < 2) - return this.trans[o] - - /* apply transformations as object if key value arguments are given*/ - var transform = {} - transform[o] = v - - return this.transform(transform) - } - - /* ... otherwise continue as a setter */ - var transform = [] - - /* parse matrix */ - o = parseMatrix(o) - - /* merge values */ - for (v in o) - if (o[v] != null) - this.trans[v] = o[v] - - /* compile matrix */ - this.trans.matrix = this.trans.a - + ' ' + this.trans.b - + ' ' + this.trans.c - + ' ' + this.trans.d - + ' ' + this.trans.e - + ' ' + this.trans.f - - /* alias current transformations */ - o = this.trans - - /* add matrix */ - if (o.matrix != SVG.defaults.matrix) - transform.push('matrix(' + o.matrix + ')') - - /* add rotation */ - if (o.rotation != 0) - transform.push('rotate(' + o.rotation + ' ' + (o.cx == null ? this.bbox().cx : o.cx) + ' ' + (o.cy == null ? this.bbox().cy : o.cy) + ')') - - /* add scale */ - if (o.scaleX != 1 || o.scaleY != 1) - transform.push('scale(' + o.scaleX + ' ' + o.scaleY + ')') - - /* add skew on x axis */ - if (o.skewX != 0) - transform.push('skewX(' + o.skewX + ')') - - /* add skew on y axis */ - if (o.skewY != 0) - transform.push('skewY(' + o.skewY + ')') - - /* add translation */ - if (o.x != 0 || o.y != 0) - transform.push('translate(' + new SVG.Number(o.x / o.scaleX) + ' ' + new SVG.Number(o.y / o.scaleY) + ')') - - /* update transformations, even if there are none */ - if (transform.length == 0) - this.node.removeAttribute('transform') - else - this.node.setAttribute('transform', transform.join(' ')) - - return this + + return this + } + } +}) + +SVG.FX = SVG.invent({ + // Initialize FX object + create: function(element) { + // store target element + this.target = element + } + + // 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 } - // Dynamic style generator - , style: function(s, v) { - if (arguments.length == 0) { - /* get full style */ - return this.node.style.cssText || '' - - } else if (arguments.length < 2) { - /* apply every style individually if an object is passed */ - if (typeof s == 'object') { - for (v in s) this.style(v, s[v]) - - } else if (SVG.regex.isCss.test(s)) { - /* parse css string */ - s = s.split(';') - - /* apply every definition individually */ - for (var i = 0; i < s.length; i++) { - v = s[i].split(':') - this.style(v[0].replace(/\s+/g, ''), v[1]) + + // 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) } - } else { - /* act as a getter if the first and only argument is not an object */ - return this.node.style[camelCase(s)] } - - } else { - this.node.style[camelCase(s)] = v === null || SVG.regex.isBlank.test(v) ? '' : v } - - return this - } - // Get / set id - , id: function(id) { - return this.attr('id', id) - } - // Get bounding box - , bbox: function() { - return new SVG.BBox(this) - } - // Get rect box - , rbox: function() { - return new SVG.RBox(this) - } - // Checks whether the given point inside the bounding box of the element - , inside: function(x, y) { - var box = this.bbox() - - return x > box.x - && y > box.y - && x < box.x + box.width - && y < box.y + box.height - } - // Show element - , show: function() { - return this.style('display', '') - } - // Hide element - , hide: function() { - return this.style('display', 'none') - } - // Is element visible? - , visible: function() { - return this.style('display') != 'none' - } - // Return id on string conversion - , toString: function() { - return this.attr('id') - } - // Return array of classes on the node - , classes: function() { - var classAttr = this.node.getAttribute('class') - if (classAttr === null) { - return [] - } else { - return classAttr.trim().split(/\s+/) - } - } - // Return true if class exists on the node, false otherwise - , hasClass: function(className) { - return this.classes().indexOf(className) != -1 - } - // Add class to the node - , addClass: function(className) { - var classArray - if (!(this.hasClass(className))) { - classArray = this.classes() - classArray.push(className) - this.node.setAttribute('class', classArray.join(' ')) - } - return this - } - // Remove class from the node - , removeClass: function(className) { - var classArray - if (this.hasClass(className)) { - classArray = this.classes().filter(function(c) { - return c != className - }) - this.node.setAttribute('class', classArray.join(' ')) - } - return this - } - // Toggle the presence of a class on the node - , toggleClass: function(className) { - if (this.hasClass(className)) { - this.removeClass(className) - } else { - this.addClass(className) + + // collect style keys + if (skeys == null) { + skeys = [] + for (key in fx.styles) + skeys.push(key) } - return this - } - // Get referenced element form attribute value - , reference: function(attr) { - return SVG.get(this.attr()[attr]) - } - // Private: find svg parent by instance - , _parent: function(parent) { - var element = this + + // 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 - while (element != null && !(element instanceof parent)) - element = element.parent - - return element - } - } - }) + // 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)) - SVG.Parent = SVG.invent({ - // Initialize node - create: function(element) { - this.constructor.call(this, element) - } - - // Inherit from - , inherit: SVG.Element - - // Add class methods - , extend: { - // Returns all child elements - children: function() { - return this._children || (this._children = []) - } - // Add given element at a position - , add: function(element, i) { - if (!this.has(element)) { - /* define insertion index if none given */ - i = i == null ? this.children().length : i - - /* remove references from previous parent */ - if (element.parent) - element.parent.children().splice(element.parent.index(element), 1) - - /* add element references */ - this.children().splice(i, 0, element) - this.node.insertBefore(element.node, this.node.childNodes[i] || null) - element.parent = this - } - - /* reposition defs */ - if (this._defs) { - this.node.removeChild(this._defs.node) - this.node.appendChild(this._defs.node) + // 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 - } - // Basically does the same as `add()` but returns the added element instead - , put: function(element, i) { - this.add(element, i) - return element - } - // Checks if the given element is a child - , has: function(element) { - return this.index(element) >= 0 - } - // Gets index of given element - , index: function(element) { - return this.children().indexOf(element) - } - // Get a element at the given index - , get: function(i) { - return this.children()[i] - } - // Get first child, skipping the defs node - , first: function() { - return this.children()[0] - } - // Get the last child - , last: function() { - return this.children()[this.children().length - 1] + + // 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) + }) } - // Iterates over all children and invokes a given block - , each: function(block, deep) { - var i, il - , children = this.children() - - for (i = 0, il = children.length; i < il; i++) { - if (children[i] instanceof SVG.Element) - block.apply(children[i], [i, children]) - - if (deep && (children[i] instanceof SVG.Container)) - children[i].each(block, deep) - } - return this - } - // Remove a child element at a position - , removeElement: function(element) { - this.children().splice(this.index(element), 1) - this.node.removeChild(element.node) - element.parent = null - - return this - } - // Remove all elements in this container - , clear: function() { - /* remove children */ - for (var i = this.children().length - 1; i >= 0; i--) - this.removeElement(this.children()[i]) - - /* remove defs node */ - if (this._defs) - this._defs.clear() - - return this - } - , // Get defs - defs: function() { - return this.doc().defs() - } - } - - }) + 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 - SVG.Container = SVG.invent({ - // Initialize node - create: function(element) { - this.constructor.call(this, element) - } - - // Inherit from - , inherit: SVG.Parent - - // Add class methods - , extend: { - // Get the viewBox and calculate the zoom value - viewbox: function(v) { - if (arguments.length == 0) - /* act as a getter if there are no arguments */ - return new SVG.ViewBox(this) - - /* otherwise act as a setter */ - v = arguments.length == 1 ? - [v.x, v.y, v.width, v.height] : - [].slice.call(arguments) - - return this.attr('viewBox', v) - } - } - - }) + // 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 - SVG.FX = SVG.invent({ - // Initialize FX object - create: function(element) { - /* store target element */ - this.target = element - } - - // Add class methods - , extend: { - // Add animation parameters and start animation - animate: function(d, ease, delay) { - var akeys, tkeys, 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 - } - - /* ensure default duration and easing */ - d = d == '=' ? d : d == null ? 1000 : new SVG.Number(d).valueOf() - ease = ease || '<>' - - /* process values */ - fx.to = 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._plot || akeys.indexOf('points') > -1)) { - /* get destination */ - var box - , p = new element.morphArray(fx._plot || fx.attrs.points || element.array) - - /* add size */ - if (fx._size) p.size(fx._size.width.to, fx._size.height.to) - - /* add movement */ - box = p.bbox() - if (fx._x) p.move(fx._x.to, box.y) - else if (fx._cx) p.move(fx._cx.to - box.width / 2, box.y) - - box = p.bbox() - if (fx._y) p.move(box.x, fx._y.to) - else if (fx._cy) p.move(box.x, fx._cy.to - box.height / 2) - - /* delete element oriented changes */ - delete fx._x - delete fx._y - delete fx._cx - delete fx._cy - delete fx._size - - fx._plot = element.array.morph(p) - } - } - - /* collect transformation keys */ - if (tkeys == null) { - tkeys = [] - for (key in fx.trans) - tkeys.push(key) - } - - /* collect style keys */ - if (skeys == null) { - skeys = [] - for (key in fx.styles) - skeys.push(key) - } - - /* 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._plot) { - element.plot(fx._plot.at(pos)) - - } else { - /* run all x-position properties */ - if (fx._x) - element.x(fx._x.at(pos)) - else if (fx._cx) - element.cx(fx._cx.at(pos)) - - /* run all y-position properties */ - if (fx._y) - element.y(fx._y.at(pos)) - else if (fx._cy) - element.cy(fx._cy.at(pos)) - - /* run all size properties */ - if (fx._size) - element.size(fx._size.width.at(pos), fx._size.height.at(pos)) - } - - /* run all viewbox properties */ - if (fx._viewbox) - element.viewbox( - fx._viewbox.x.at(pos) - , fx._viewbox.y.at(pos) - , fx._viewbox.width.at(pos) - , fx._viewbox.height.at(pos) - ) - - /* run leading property */ - if (fx._leading) - element.leading(fx._leading.at(pos)) - - /* animate attributes */ - for (i = akeys.length - 1; i >= 0; i--) - element.attr(akeys[i], at(fx.attrs[akeys[i]], pos)) - - /* animate transformations */ - for (i = tkeys.length - 1; i >= 0; i--) - element.transform(tkeys[i], at(fx.trans[tkeys[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._during) - fx._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 = { - interval: 1000 / 60 - , start: start - , play: true - , finish: start + d - , duration: d - } - - /* render function */ - fx.render = function() { + // reverse pos if animation is reversed + if (fx.situation.reversing) + pos = -pos + 1 + + // process values + fx.at(pos) - if (fx.situation.play === true) { - // This code was borrowed from the emile.js micro framework by Thomas Fuchs, aka MadRobby. - var time = new Date().getTime() - , pos = time > fx.situation.finish ? 1 : (time - fx.situation.start) / d - - /* process values */ - fx.to(pos) - - /* finish off animation */ - if (time > fx.situation.finish) { - if (fx._plot) - element.plot(new SVG.PointArray(fx._plot.destination).settle()) - - if (fx._loop === true || (typeof fx._loop == 'number' && fx._loop > 1)) { - if (typeof fx._loop == 'number') - --fx._loop - fx.animate(d, ease, delay) - } else { - fx._after ? fx._after.apply(element, [fx]) : fx.stop() + // 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.animationFrame = requestAnimationFrame(fx.render) + fx.situation.after ? fx.situation.after.apply(element, [fx]) : fx.stop() } + } else { fx.animationFrame = requestAnimationFrame(fx.render) } - + } else { + fx.animationFrame = requestAnimationFrame(fx.render) } - - /* start animation */ - fx.render() - }, new SVG.Number(delay).valueOf()) - } - - return this - } - // Get bounding box of target element - , bbox: function() { - return this.target.bbox() + } + + // start animation + fx.render() + + }, new SVG.Number(delay).valueOf()) } - // Add animatable attributes - , attr: function(a, v) { - if (typeof a == 'object') { - for (var key in a) - this.attr(key, a[key]) - + + return this + } + // 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] = this.target.ctm().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 { - var from = this.target.attr(a) - - this.attrs[a] = SVG.Color.isColor(from) ? + this.attrs[a] = SVG.Color.isColor(v) ? + // prepare color for morphing new SVG.Color(from).morph(v) : - SVG.regex.unit.test(from) ? + SVG.regex.unit.test(v) ? + // prepare number for morphing new SVG.Number(from).morph(v) : + // prepare for plain morphing { from: from, to: v } } - - return this - } - // Add animatable transformations - , transform: function(o, v) { - if (arguments.length == 1) { - /* parse matrix string */ - o = parseMatrix(o) - - /* dlete matrixstring from object */ - delete o.matrix - - /* add rotation-center to transformations */ - this.target.trans.cx = o.cx || null - this.target.trans.cy = o.cy || null - - delete o.cx - delete o.cy - - /* store matrix values */ - for (v in o) - this.trans[v] = { from: this.target.trans[v], to: o[v] } - - } else { - /* apply transformations as object if key value arguments are given*/ - var transform = {} - transform[o] = v - - this.transform(transform) - } - - 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 } - - return this - } - // Animatable x-axis - , x: function(x) { - this._x = new SVG.Number(this.target.x()).morph(x) - - return this - } - // Animatable y-axis - , y: function(y) { - this._y = new SVG.Number(this.target.y()).morph(y) - - return this } - // Animatable center x-axis - , cx: function(x) { - this._cx = new SVG.Number(this.target.cx()).morph(x) - - return this - } - // Animatable center y-axis - , cy: function(y) { - this._cy = new SVG.Number(this.target.cy()).morph(y) - - 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._size = { - width: new SVG.Number(box.width).morph(width) - , height: new SVG.Number(box.height).morph(height) - } - } - - return this - } - // Add animatable plot - , plot: function(p) { - this._plot = p - - return this - } - // Add leading method - , leading: function(value) { - if (this.target._leading) - this._leading = new SVG.Number(this.target._leading).morph(value) - - return this - } - // Add animatable viewbox - , viewbox: function(x, y, width, height) { - if (this.target instanceof SVG.Container) { - var box = this.target.viewbox() - - this._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) - } - } + + 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 } + + return this + } + // Animatable x-axis + , x: function(x) { + this.destination.x = new SVG.Number(this.target.x()).morph(x) + + return this + } + // Animatable y-axis + , y: function(y) { + this.destination.y = new SVG.Number(this.target.y()).morph(y) + + return this + } + // Animatable center x-axis + , cx: function(x) { + this.destination.cx = new SVG.Number(this.target.cx()).morph(x) + + return this + } + // Animatable center y-axis + , cy: function(y) { + this.destination.cy = new SVG.Number(this.target.cy()).morph(y) + + 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) - 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)) + } 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) } - - return this - } - // Add callback for each keyframe - , during: function(during) { - this._during = during - - return this } - // Callback after animation - , after: function(after) { - this._after = after + + return this + } + // Add animatable plot + , plot: function(p) { + this.destination.plot = p + + 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 + } + // Add animatable viewbox + , viewbox: function(x, y, width, height) { + if (this.target instanceof SVG.Container) { + var box = this.target.viewbox() - return this - } - // Make loopable - , loop: function(times) { - this._loop = times || true - - return this - } - // Stop running animation - , stop: function(fulfill) { - /* fulfill animation */ - if (fulfill === true) { - - this.animate(0) - - if (this._after) - this._after.apply(this.target, [this]) - - } else { - /* stop current animation */ - clearTimeout(this.timeout) - cancelAnimationFrame(this.animationFrame); - - /* reset storage for properties that need animation */ - this.attrs = {} - this.trans = {} - this.styles = {} - this.situation = {} - - /* delete destinations */ - delete this._x - delete this._y - delete this._cx - delete this._cy - delete this._size - delete this._plot - delete this._loop - delete this._after - delete this._during - delete this._leading - delete this._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) } - - return this } - // Pause running animation - , pause: function() { - if (this.situation.play === true) { - this.situation.play = false - this.situation.pause = new Date().getTime() - } - - return this + + 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)) } - // 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 - } - - return this + + return this + } + // Add callback for each keyframe + , during: function(during) { + this.situation.during = during + + return this + } + // Callback after animation + , after: function(after) { + this.situation.after = after + + 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 + + return this + } + // Stop running animation + , stop: function(fulfill) { + // fulfill animation + if (fulfill === true) { + + this.animate(0) + + if (this.situation.after) + this.situation.after.apply(this.target, [this]) + + } else { + // stop current animation + clearTimeout(this.timeout) + cancelAnimationFrame(this.animationFrame); + + // reset storage for properties + this.attrs = {} + this.styles = {} + this.situation = {} + this.destination = {} } + 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) + // Pause running animation + , pause: function() { + if (this.situation.play === true) { + this.situation.play = false + this.situation.pause = new Date().getTime() } - // Stop current animation; this is an alias to the fx instance - , stop: function(fulfill) { - if (this.fx) - this.fx.stop(fulfill) + + return this + } + // Play running animation + , play: function() { + if (this.situation.play === false) { + var pause = new Date().getTime() - this.situation.pause - return this - } - // Pause current animation - , pause: function() { - if (this.fx) - this.fx.pause() - - return this + this.situation.finish += pause + this.situation.start += pause + this.situation.play = true } - // Play paused current animation - , play: function() { - if (this.fx) - this.fx.play() - - return this + + 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) + } + // Stop current animation; this is an alias to the fx instance + , stop: function(fulfill) { + if (this.fx) + this.fx.stop(fulfill) + + return this + } + // Pause current animation + , pause: function() { + if (this.fx) + this.fx.pause() + + return this + } + // Play paused current animation + , play: function() { + if (this.fx) + this.fx.play() + + return this + } + + } +}) + +SVG.BBox = SVG.invent({ + // Initialize + create: function(element) { + // get values if element is given + if (element) { + var box + + // yes this is ugly, but Firefox can be a bitch when it comes to elements that are not yet rendered + try { + // find native bbox + box = element.node.getBBox() + } catch(e) { + // mimic bbox + box = { + x: element.node.clientLeft + , y: element.node.clientTop + , width: element.node.clientWidth + , height: element.node.clientHeight + } } + // plain x and y + this.x = box.x + this.y = box.y + + // plain width and height + this.width = box.width + this.height = box.height } - }) + // add center, right and bottom + fullBox(this) + } + + // Define Parent +, parent: SVG.Element - SVG.extend(SVG.Element, SVG.FX, { - // Relative move over x axis - dx: function(x) { - return this.x((this.target || this).x() + x) + // Constructor +, construct: { + // Get bounding box + bbox: function() { + return new SVG.BBox(this) } - // Relative move over y axis - , dy: function(y) { - return this.y((this.target || this).y() + y) + } + +}) + +SVG.TBox = SVG.invent({ + // Initialize + create: function(element) { + // get values if element is given + if (element) { + var t = element.ctm().extract() + , box = element.bbox() + + // width and height including transformations + this.width = box.width * t.scaleX + this.height = box.height * t.scaleY + + // x and y including transformations + this.x = box.x + t.x + this.y = box.y + t.y } - // Relative move over x and y axes - , dmove: function(x, y) { - return this.dx(x).dy(y) + + // add center, right and bottom + fullBox(this) + } + + // Define Parent +, parent: SVG.Element + + // Constructor +, construct: { + // Get transformed bounding box + tbox: function() { + return new SVG.TBox(this) } - - }) + } - ;[ 'click' - , 'dblclick' - , 'mousedown' - , 'mouseup' - , 'mouseover' - , 'mouseout' - , 'mousemove' - // , 'mouseenter' -> not supported by IE - // , 'mouseleave' -> not supported by IE - , 'touchstart' - , 'touchmove' - , 'touchleave' - , 'touchend' - , 'touchcancel' ].forEach(function(event) { - - /* add event to SVG.Element */ - SVG.Element.prototype[event] = function(f) { - var self = this +}) + + +SVG.RBox = SVG.invent({ + // Initialize + create: function(element) { + if (element) { + var e = element.doc().parent() + , box = element.node.getBoundingClientRect() + , zoom = 1 - /* bind event to element rather than element node */ - this.node['on' + event] = typeof f == 'function' ? - function() { return f.apply(self, arguments) } : null + // get screen offset + this.x = box.left + this.y = box.top - return this + // subtract parent offset + this.x -= e.offsetLeft + this.y -= e.offsetTop + + while (e = e.offsetParent) { + this.x -= e.offsetLeft + this.y -= e.offsetTop + } + + // calculate cumulative zoom from svg documents + e = element + while (e.parent && (e = e.parent())) { + if (e.viewbox) { + zoom *= e.viewbox().zoom + this.x -= e.x() || 0 + this.y -= e.y() || 0 + } + } + + // recalculate viewbox distortion + this.width = box.width /= zoom + this.height = box.height /= zoom } - }) - - // Initialize listeners stack - SVG.listeners = [] - SVG.handlerMap = [] - - // Only kept for consistency of API - SVG.registerEvent = function(){}; - - // Add event binder in the SVG namespace - SVG.on = function(node, event, listener) { - // create listener, get object-index - var l = listener.bind(node.instance || node) - , index = (SVG.handlerMap.indexOf(node) + 1 || SVG.handlerMap.push(node)) - 1 - , ev = event.split('.')[0] - , ns = event.split('.')[1] || '*' + // add center, right and bottom + fullBox(this) + + // offset by window scroll position, because getBoundingClientRect changes when window is scrolled + this.x += window.scrollX + this.y += window.scrollY + } + + // define Parent +, parent: SVG.Element + + // Constructor +, construct: { + // Get rect box + rbox: function() { + return new SVG.RBox(this) + } + } + +}) + +// Add universal merge method +;[SVG.BBox, SVG.TBox, SVG.RBox].forEach(function(c) { + + SVG.extend(c, { + // Merge rect box with another, return a new instance + merge: function(box) { + var b = new c() + + // merge boxes + b.x = Math.min(this.x, box.x) + b.y = Math.min(this.y, box.y) + b.width = Math.max(this.x + this.width, box.x + box.width) - b.x + b.height = Math.max(this.y + this.height, box.y + box.height) - b.y - - // ensure valid object - SVG.listeners[index] = SVG.listeners[index] || {} - SVG.listeners[index][ev] = SVG.listeners[index][ev] || {} - SVG.listeners[index][ev][ns] = SVG.listeners[index][ev][ns] || {} - - // reference listener - SVG.listeners[index][ev][ns][listener] = l - - // add listener - node.addEventListener(ev, l, false) + return fullBox(b) + } + + }) + +}) + +SVG.Matrix = SVG.invent({ + // Initialize + create: function(source) { + var i, base = arrayToMatrix([1, 0, 0, 1, 0, 0]) + + // ensure source as object + source = source && source.node && source.node.getCTM ? + source.node.getCTM() : + typeof source === 'string' ? + stringToMatrix(source) : + arguments.length == 6 ? + arrayToMatrix([].slice.call(arguments)) : + typeof source === 'object' ? + source : base + + // merge source + for (i = abcdef.length - 1; i >= 0; i--) + this[abcdef[i]] = source && typeof source[abcdef[i]] === 'number' ? + source[abcdef[i]] : base[abcdef[i]] } - // Add event unbinder in the SVG namespace - SVG.off = function(node, event, listener) { - var index = SVG.handlerMap.indexOf(node) - , ev = event && event.split('.')[0] - , ns = event && event.split('.')[1] - - if(index == -1) return - - if (listener) { - // remove listener reference - if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns || '*']) { - // remove listener - node.removeEventListener(ev, SVG.listeners[index][ev][ns || '*'][listener], false) - - delete SVG.listeners[index][ev][ns || '*'][listener] - } - - } else if (ns) { - // remove all listeners for the namespaced event - if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns]) { - for (listener in SVG.listeners[index][ev][ns]) - SVG.off(node, [ev, ns].join('.'), listener) + // Add methods +, extend: { + // Extract individual transformations + extract: function() { + // find delta transform points + var px = deltaTransformPoint(this, 0, 1) + , py = deltaTransformPoint(this, 1, 0) + , skewX = 180 / Math.PI * Math.atan2(px.y, px.x) - 90 - delete SVG.listeners[index][ev][ns] + return { + // translation + x: this.e + , y: this.f + // skew + , skewX: -skewX + , skewY: 180 / Math.PI * Math.atan2(py.y, py.x) + // scale + , scaleX: Math.sqrt(this.a * this.a + this.b * this.b) + , scaleY: Math.sqrt(this.c * this.c + this.d * this.d) + // rotation + , rotation: skewX + } + } + // Clone matrix + , clone: function() { + return new SVG.Matrix(this) + } + // Morph one matrix into another + , morph: function(matrix) { + // store new destination + this.destination = new SVG.Matrix(matrix) + + return this + } + // Get morphed matrix at a given position + , at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this + + // calculate morphed matrix at a given position + var matrix = new SVG.Matrix({ + a: this.a + (this.destination.a - this.a) * pos + , b: this.b + (this.destination.b - this.b) * pos + , c: this.c + (this.destination.c - this.c) * pos + , d: this.d + (this.destination.d - this.d) * pos + , e: this.e + (this.destination.e - this.e) * pos + , f: this.f + (this.destination.f - this.f) * pos + }) + + // process parametric rotation if present + if (this.param && this.param.to) { + // calculate current parametric position + var param = { + rotation: this.param.from.rotation + (this.param.to.rotation - this.param.from.rotation) * pos + , cx: this.param.from.cx + , cy: this.param.from.cy + } + + // rotate matrix + matrix = matrix.rotate( + (this.param.to.rotation - this.param.from.rotation * 2) * pos + , param.cx + , param.cy + ) + + // store current parametric values + matrix.param = param } - - } else if (ev) { - // remove all listeners for the event - if (SVG.listeners[index][ev]) { - for (namespace in SVG.listeners[index][ev]) - SVG.off(node, [ev, namespace].join('.')) - - delete SVG.listeners[index][ev] + + return matrix + } + // Multiplies by given matrix + , multiply: function(matrix) { + return new SVG.Matrix(this.native().multiply(parseMatrix(matrix).native())) + } + // Inverses matrix + , inverse: function() { + return new SVG.Matrix(this.native().inverse()) + } + // Translate matrix + , translate: function(x, y) { + return new SVG.Matrix(this.native().translate(x || 0, y || 0)) + } + // Scale matrix + , scale: function(x, y, cx, cy) { + // support universal scale + if (arguments.length == 1 || arguments.length == 3) + y = x + if (arguments.length == 3) { + cy = cx + cx = y } - - } else { - // remove all listeners on a given node - for (event in SVG.listeners[index]) - SVG.off(node, event) - - delete SVG.listeners[index] - + + return this.around(cx, cy, new SVG.Matrix(x, 0, 0, y, 0, 0)) } - } - - // - SVG.extend(SVG.Element, { - // Bind given event to listener - on: function(event, listener) { - SVG.on(this.node, event, listener) + // Rotate matrix + , rotate: function(r, cx, cy) { + // convert degrees to radians + r = SVG.utils.radians(r) - return this + return this.around(cx, cy, new SVG.Matrix(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0)) } - // Unbind event from listener - , off: function(event, listener) { - SVG.off(this.node, event, listener) - - return this + // Flip matrix on x or y, at a given offset + , flip: function(a, o) { + return a == 'x' ? this.scale(-1, 1, o, 0) : this.scale(1, -1, 0, o) } - // Fire given event - , fire: function(event, data) { - - // Dispatch event - this.node.dispatchEvent(new CustomEvent(event, {detail:data})) - + // Skew + , skew: function(x, y, cx, cy) { + return this.around(cx, cy, this.native().skewX(x || 0).skewY(y || 0)) + } + // Transform around a center point + , around: function(cx, cy, matrix) { return this + .multiply(new SVG.Matrix(1, 0, 0, 1, cx || 0, cy || 0)) + .multiply(matrix) + .multiply(new SVG.Matrix(1, 0, 0, 1, -cx || 0, -cy || 0)) + } + // Convert to native SVGMatrix + , native: function() { + // create new matrix + var matrix = SVG.parser.draw.node.createSVGMatrix() + + // update with current values + for (var i = abcdef.length - 1; i >= 0; i--) + matrix[abcdef[i]] = this[abcdef[i]] + + return matrix } - }) + // Convert matrix to string + , toString: function() { + return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')' + } + } - SVG.Defs = SVG.invent({ - // Initialize node - create: 'defs' + // Define parent +, parent: SVG.Element + + // Add parent method +, construct: { + // Get current matrix + ctm: function() { + return new SVG.Matrix(this) + } - // Inherit from - , inherit: SVG.Container + } + +}) +SVG.extend(SVG.Element, { + // Set svg element attribute + attr: function(a, v, n) { + // act as full getter + if (a == null) { + // get an object of attributes + a = {} + v = this.node.attributes + for (n = v.length - 1; n >= 0; n--) + a[v[n].nodeName] = SVG.regex.isNumber.test(v[n].nodeValue) ? parseFloat(v[n].nodeValue) : v[n].nodeValue + + return a + + } else if (typeof a == 'object') { + // apply every attribute individually if an object is passed + for (v in a) this.attr(v, a[v]) + + } else if (v === null) { + // remove value + this.node.removeAttribute(a) + + } else if (v == null) { + // act as a getter if the first and only argument is not an object + v = this.node.getAttribute(a) + return v == null ? + SVG.defaults.attrs[a] : + SVG.regex.isNumber.test(v) ? + parseFloat(v) : v - }) + } else { + // bUG FIX: some browsers will render a stroke if a color is given even though stroke width is 0 + if (a == 'stroke-width') + this.attr('stroke', parseFloat(v) > 0 ? this._stroke : null) + else if (a == 'stroke') + this._stroke = v - SVG.G = SVG.invent({ - // Initialize node - create: 'g' - - // Inherit from - , inherit: SVG.Container + // convert image fill and stroke to patterns + if (a == 'fill' || a == 'stroke') { + if (SVG.regex.isImage.test(v)) + v = this.doc().defs().image(v, 0, 0) + + if (v instanceof SVG.Image) + v = this.doc().defs().pattern(0, 0, function() { + this.add(v) + }) + } + + // ensure correct numeric values (also accepts NaN and Infinity) + if (typeof v === 'number') + v = new SVG.Number(v) + + // ensure full hex color + else if (SVG.Color.isColor(v)) + v = new SVG.Color(v) + + // parse array values + else if (Array.isArray(v)) + v = new SVG.Array(v) + + // store parametric transformation values locally + else if (v instanceof SVG.Matrix && v.param) + this.param = v.param + + // if the passed attribute is leading... + if (a == 'leading') { + // ... call the leading method instead + if (this.leading) + this.leading(v) + } else { + // set given attribute on node + typeof n === 'string' ? + this.node.setAttributeNS(n, a, v.toString()) : + this.node.setAttribute(a, v.toString()) + } + + // rebuild if required + if (this.rebuild && (a == 'font-size' || a == 'x')) + this.rebuild(a, v) + } - // Add class methods - , extend: { - // Move over x-axis - x: function(x) { - return x == null ? this.trans.x : this.transform('x', x) + return this + } +}) +SVG.extend(SVG.Element, SVG.FX, { + // Add transformations + transform: function(o, relative) { + // get target in case of the fx module, otherwise reference this + var target = this.target || this + , matrix + + // act as a getter + if (typeof o !== 'object') { + // get current matrix + matrix = target.ctm().extract() + + // add parametric rotation + if (typeof this.param === 'object') { + matrix.rotation = this.param.rotation + matrix.cx = this.param.cx + matrix.cy = this.param.cy } - // Move over y-axis - , y: function(y) { - return y == null ? this.trans.y : this.transform('y', y) + + 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) + + // ensure relative flag + relative = !!relative || !!o.relative + + // act on matrix + if (o.a != null) { + matrix = relative ? + // relative + matrix.multiply(new SVG.Matrix(o)) : + // absolute + new SVG.Matrix(o) + + // act on rotation + } else if (o.rotation != null) { + // 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 } - // Move by center over x-axis - , cx: function(x) { - return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2) + + // store parametric values + this.param = o + + // apply transformation + if (this instanceof SVG.Element) { + matrix = relative ? + // relative + target.attr('transform', matrix + ' rotate(' + [o.rotation, o.cx, o.cy].join() + ')').ctm() : + // absolute + matrix.rotate(o.rotation - matrix.extract().rotation, o.cx, o.cy) } - // Move by center over y-axis - , cy: function(y) { - return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2) + + // 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 + + if (!relative) { + // absolute; multiply inversed values + var e = matrix.extract() + o.scaleX = o.scaleX * 1 / e.scaleX + o.scaleY = o.scaleY * 1 / e.scaleY + } + + matrix = matrix.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 + + if (!relative) { + // absolute; reset skew values + var e = matrix.extract() + matrix = matrix.multiply(new SVG.Matrix().skew(e.skewX, e.skewY, o.cx, o.cy).inverse()) + } + + matrix = matrix.skew(o.skewX, o.skewY, o.cx, o.cy) + + // act on flip + } else if (o.flip) { + matrix = 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) { + if (relative) { + // relative + matrix = matrix.translate(o.x, o.y) + } else { + // absolute + if (o.x != null) matrix.e = o.x + if (o.y != null) matrix.f = o.y } } - // Add parent method - , construct: { - // Create a group element - group: function() { - return this.put(new SVG.G) + return this.attr('transform', matrix) + } +}) + +SVG.extend(SVG.Element, { + // Reset all transformations + untransform: function() { + return this.attr('transform', null) + } +}) +SVG.extend(SVG.Element, { + // Dynamic style generator + style: function(s, v) { + if (arguments.length == 0) { + /* get full style */ + return this.node.style.cssText || '' + + } else if (arguments.length < 2) { + /* apply every style individually if an object is passed */ + if (typeof s == 'object') { + for (v in s) this.style(v, s[v]) + + } else if (SVG.regex.isCss.test(s)) { + /* parse css string */ + s = s.split(';') + + /* apply every definition individually */ + for (var i = 0; i < s.length; i++) { + v = s[i].split(':') + this.style(v[0].replace(/\s+/g, ''), v[1]) + } + } else { + /* act as a getter if the first and only argument is not an object */ + return this.node.style[camelCase(s)] } + + } else { + this.node.style[camelCase(s)] = v === null || SVG.regex.isBlank.test(v) ? '' : v } - }) + + return this + } +}) +SVG.Parent = SVG.invent({ + // Initialize node + create: function(element) { + this.constructor.call(this, element) + } + + // Inherit from +, inherit: SVG.Element - SVG.extend(SVG.Element, { - // Get all siblings, including myself - siblings: function() { - return this.parent.children() + // Add class methods +, extend: { + // Returns all child elements + children: function() { + return SVG.utils.map(this.node.childNodes, function(node) { + return SVG.adopt(node) + }) } - // Get the curent position siblings - , position: function() { - return this.parent.index(this) + // Add given element at a position + , add: function(element, i) { + if (!this.has(element)) { + // define insertion index if none given + i = i == null ? this.children().length : i + + // add element references + this.node.insertBefore(element.node, this.node.childNodes[i] || null) + } + + return this } - // Get the next element (will return null if there is none) - , next: function() { - return this.siblings()[this.position() + 1] + // Basically does the same as `add()` but returns the added element instead + , put: function(element, i) { + this.add(element, i) + return element } - // Get the next element (will return null if there is none) - , previous: function() { - return this.siblings()[this.position() - 1] + // Checks if the given element is a child + , has: function(element) { + return this.index(element) >= 0 } - // Send given element one step forward - , forward: function() { - var i = this.position() - return this.parent.removeElement(this).put(this, i + 1) + // Gets index of given element + , index: function(element) { + return this.children().indexOf(element) } - // Send given element one step backward - , backward: function() { - var i = this.position() - - if (i > 0) - this.parent.removeElement(this).add(this, i - 1) - - return this + // Get a element at the given index + , get: function(i) { + return this.children()[i] } - // Send given element all the way to the front - , front: function() { - return this.parent.removeElement(this).put(this) + // Get first child, skipping the defs node + , first: function() { + return this.children()[0] } - // Send given element all the way to the back - , back: function() { - if (this.position() > 0) - this.parent.removeElement(this).add(this, 0) + // Get the last child + , last: function() { + return this.children()[this.children().length - 1] + } + // Iterates over all children and invokes a given block + , each: function(block, deep) { + var i, il + , children = this.children() + for (i = 0, il = children.length; i < il; i++) { + if (children[i] instanceof SVG.Element) + block.apply(children[i], [i, children]) + + if (deep && (children[i] instanceof SVG.Container)) + children[i].each(block, deep) + } + return this } - // Inserts a given element before the targeted element - , before: function(element) { - element.remove() - - var i = this.position() + // Remove a given child + , removeElement: function(element) { + this.node.removeChild(element.node) - this.parent.add(element, i) - return this } - // Insters a given element after the targeted element - , after: function(element) { - element.remove() - - var i = this.position() + // Remove all elements in this container + , clear: function() { + // remove children + while(this.node.hasChildNodes()) + this.node.removeChild(this.node.lastChild) - this.parent.add(element, i + 1) - + // remove defs reference + delete this._defs + return this } + , // Get defs + defs: function() { + return this.doc().defs() + } + } - }) +}) - SVG.Mask = SVG.invent({ - // Initialize node - create: function() { - this.constructor.call(this, SVG.create('mask')) - - /* keep references to masked elements */ - this.targets = [] +SVG.Container = SVG.invent({ + // Initialize node + create: function(element) { + this.constructor.call(this, element) + } + + // Inherit from +, inherit: SVG.Parent + + // Add class methods +, extend: { + // Get the viewBox and calculate the zoom value + viewbox: function(v) { + if (arguments.length == 0) + /* act as a getter if there are no arguments */ + return new SVG.ViewBox(this) + + /* otherwise act as a setter */ + v = arguments.length == 1 ? + [v.x, v.y, v.width, v.height] : + [].slice.call(arguments) + + return this.attr('viewBox', v) } + } - // Inherit from - , inherit: SVG.Container - - // Add class methods - , extend: { - // Unmask all masked elements and remove itself - remove: function() { - /* unmask all targets */ - for (var i = this.targets.length - 1; i >= 0; i--) - if (this.targets[i]) - this.targets[i].unmask() - delete this.targets - - /* remove mask from parent */ - this.parent.removeElement(this) - - return this - } - } +}) +// Add events to elements +;[ 'click' + , 'dblclick' + , 'mousedown' + , 'mouseup' + , 'mouseover' + , 'mouseout' + , 'mousemove' + // , 'mouseenter' -> not supported by IE + // , 'mouseleave' -> not supported by IE + , 'touchstart' + , 'touchmove' + , 'touchleave' + , 'touchend' + , 'touchcancel' ].forEach(function(event) { + + /* add event to SVG.Element */ + SVG.Element.prototype[event] = function(f) { + var self = this - // Add parent method - , construct: { - // Create masking element - mask: function() { - return this.defs().put(new SVG.Mask) - } - } - }) + /* bind event to element rather than element node */ + this.node['on' + event] = typeof f == 'function' ? + function() { return f.apply(self, arguments) } : null + + return this + } +}) + +// Initialize listeners stack +SVG.listeners = [] +SVG.handlerMap = [] + +// Add event binder in the SVG namespace +SVG.on = function(node, event, listener) { + // create listener, get object-index + var l = listener.bind(node.instance || node) + , index = (SVG.handlerMap.indexOf(node) + 1 || SVG.handlerMap.push(node)) - 1 + , ev = event.split('.')[0] + , ns = event.split('.')[1] || '*' + - SVG.extend(SVG.Element, { - // Distribute mask to svg element - maskWith: function(element) { - /* use given mask or create a new one */ - this.masker = element instanceof SVG.Mask ? element : this.parent.mask().add(element) + // ensure valid object + SVG.listeners[index] = SVG.listeners[index] || {} + SVG.listeners[index][ev] = SVG.listeners[index][ev] || {} + SVG.listeners[index][ev][ns] = SVG.listeners[index][ev][ns] || {} + + // reference listener + SVG.listeners[index][ev][ns][listener] = l + + // add listener + node.addEventListener(ev, l, false) +} + +// Add event unbinder in the SVG namespace +SVG.off = function(node, event, listener) { + var index = SVG.handlerMap.indexOf(node) + , ev = event && event.split('.')[0] + , ns = event && event.split('.')[1] + + if(index == -1) return - /* store reverence on self in mask */ - this.masker.targets.push(this) - - /* apply mask */ - return this.attr('mask', 'url("#' + this.masker.attr('id') + '")') + if (listener) { + // remove listener reference + if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns || '*']) { + // remove listener + node.removeEventListener(ev, SVG.listeners[index][ev][ns || '*'][listener], false) + + delete SVG.listeners[index][ev][ns || '*'][listener] } - // Unmask element - , unmask: function() { - delete this.masker - return this.attr('mask', null) + + } else if (ns) { + // remove all listeners for the namespaced event + if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns]) { + for (listener in SVG.listeners[index][ev][ns]) + SVG.off(node, [ev, ns].join('.'), listener) + + delete SVG.listeners[index][ev][ns] } - - }) + } else if (ev) { + // remove all listeners for the event + if (SVG.listeners[index][ev]) { + for (namespace in SVG.listeners[index][ev]) + SVG.off(node, [ev, namespace].join('.')) - SVG.Clip = SVG.invent({ - // Initialize node - create: function() { - this.constructor.call(this, SVG.create('clipPath')) - - /* keep references to clipped elements */ - this.targets = [] + delete SVG.listeners[index][ev] } + + } else { + // remove all listeners on a given node + for (event in SVG.listeners[index]) + SVG.off(node, event) + + delete SVG.listeners[index] + + } +} + +// +SVG.extend(SVG.Element, { + // Bind given event to listener + on: function(event, listener) { + SVG.on(this.node, event, listener) + + return this + } + // Unbind event from listener +, off: function(event, listener) { + SVG.off(this.node, event, listener) + + return this + } + // Fire given event +, fire: function(event, data) { + + // Dispatch event + this.node.dispatchEvent(new CustomEvent(event, {detail:data})) + + return this + } +}) + +SVG.Defs = SVG.invent({ + // Initialize node + create: 'defs' + + // Inherit from +, inherit: SVG.Container - // Inherit from - , inherit: SVG.Container +}) +SVG.G = SVG.invent({ + // Initialize node + create: 'g' + + // Inherit from +, inherit: SVG.Container - // Add class methods - , extend: { - // Unclip all clipped elements and remove itself - remove: function() { - /* unclip all targets */ - for (var i = this.targets.length - 1; i >= 0; i--) - if (this.targets[i]) - this.targets[i].unclip() - delete this.targets + // Add class methods +, extend: { + // Move over x-axis + x: function(x) { + return x == null ? this.transform('x') : this.transform({ x: -this.x() + x }, true) + } + // Move over y-axis + , y: function(y) { + return y == null ? this.transform('y') : this.transform({ y: -this.y() + y }, true) + } + // Move by center over x-axis + , cx: function(x) { + return x == null ? this.tbox().cx : this.x(x - this.tbox().width / 2) + } + // Move by center over y-axis + , cy: function(y) { + return y == null ? this.tbox().cy : this.y(y - this.tbox().height / 2) + } + } - /* remove clipPath from parent */ - this.parent.removeElement(this) - - return this - } + // Add parent method +, construct: { + // Create a group element + group: function() { + return this.put(new SVG.G) } + } +}) +// ### This module adds backward / forward functionality to elements. + +// +SVG.extend(SVG.Element, { + // Get all siblings, including myself + siblings: function() { + return this.parent().children() + } + // Get the curent position siblings +, position: function() { + return this.parent().index(this) + } + // Get the next element (will return null if there is none) +, next: function() { + return this.siblings()[this.position() + 1] + } + // Get the next element (will return null if there is none) +, previous: function() { + return this.siblings()[this.position() - 1] + } + // Send given element one step forward +, forward: function() { + var i = this.position() + 1 + , p = this.parent() + + // move node one step forward + p.removeElement(this).add(this, i) + + // make sure defs node is always at the top + if (p instanceof SVG.Doc) + p.node.appendChild(p.defs().node) + + return this + } + // Send given element one step backward +, backward: function() { + var i = this.position() - // Add parent method - , construct: { - // Create clipping element - clip: function() { - return this.defs().put(new SVG.Clip) - } + if (i > 0) + this.parent().removeElement(this).add(this, i - 1) + + return this + } + // Send given element all the way to the front +, front: function() { + var p = this.parent() + + // Move node forward + p.node.appendChild(this.node) + + // Make sure defs node is always at the top + if (p instanceof SVG.Doc) + p.node.appendChild(p.defs().node) + + return this + } + // Send given element all the way to the back +, back: function() { + if (this.position() > 0) + this.parent().removeElement(this).add(this, 0) + + return this + } + // Inserts a given element before the targeted element +, before: function(element) { + element.remove() + + var i = this.position() + + this.parent().add(element, i) + + return this + } + // Insters a given element after the targeted element +, after: function(element) { + element.remove() + + var i = this.position() + + this.parent().add(element, i + 1) + + return this + } + +}) +SVG.Mask = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, SVG.create('mask')) + + /* keep references to masked elements */ + this.targets = [] + } + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Unmask all masked elements and remove itself + remove: function() { + /* unmask all targets */ + for (var i = this.targets.length - 1; i >= 0; i--) + if (this.targets[i]) + this.targets[i].unmask() + delete this.targets + + /* remove mask from parent */ + this.parent().removeElement(this) + + return this } - }) + } - // - SVG.extend(SVG.Element, { - // Distribute clipPath to svg element - clipWith: function(element) { - /* use given clip or create a new one */ - this.clipper = element instanceof SVG.Clip ? element : this.parent.clip().add(element) + // Add parent method +, construct: { + // Create masking element + mask: function() { + return this.defs().put(new SVG.Mask) + } + } +}) + + +SVG.extend(SVG.Element, { + // Distribute mask to svg element + maskWith: function(element) { + /* use given mask or create a new one */ + this.masker = element instanceof SVG.Mask ? element : this.parent().mask().add(element) + + /* store reverence on self in mask */ + this.masker.targets.push(this) + + /* apply mask */ + return this.attr('mask', 'url("#' + this.masker.attr('id') + '")') + } + // Unmask element +, unmask: function() { + delete this.masker + return this.attr('mask', null) + } - /* store reverence on self in mask */ - this.clipper.targets.push(this) +}) + +SVG.ClipPath = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, SVG.create('clipPath')) + + /* keep references to clipped elements */ + this.targets = [] + } + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Unclip all clipped elements and remove itself + remove: function() { + /* unclip all targets */ + for (var i = this.targets.length - 1; i >= 0; i--) + if (this.targets[i]) + this.targets[i].unclip() + delete this.targets + + /* remove clipPath from parent */ + this.parent().removeElement(this) - /* apply mask */ - return this.attr('clip-path', 'url("#' + this.clipper.attr('id') + '")') + return this } - // Unclip element - , unclip: function() { - delete this.clipper - return this.attr('clip-path', null) + } + + // Add parent method +, construct: { + // Create clipping element + clip: function() { + return this.defs().put(new SVG.ClipPath) } + } +}) + +// +SVG.extend(SVG.Element, { + // Distribute clipPath to svg element + clipWith: function(element) { + /* use given clip or create a new one */ + this.clipper = element instanceof SVG.ClipPath ? element : this.parent().clip().add(element) + + /* store reverence on self in mask */ + this.clipper.targets.push(this) - }) + /* apply mask */ + return this.attr('clip-path', 'url("#' + this.clipper.attr('id') + '")') + } + // Unclip element +, unclip: function() { + delete this.clipper + return this.attr('clip-path', null) + } + +}) +SVG.Gradient = SVG.invent({ + // Initialize node + create: function(type) { + this.constructor.call(this, SVG.create(type + 'Gradient')) + + /* store type */ + this.type = type + } - SVG.Gradient = SVG.invent({ - // Initialize node - create: function(type) { - this.constructor.call(this, SVG.create(type + 'Gradient')) + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Add a color stop + at: function(offset, color, opacity) { + return this.put(new SVG.Stop).update(offset, color, opacity) + } + // Update gradient + , update: function(block) { + /* remove all stops */ + this.clear() - /* store type */ - this.type = type + /* invoke passed block */ + if (typeof block == 'function') + block.call(this, this) + + return this } - - // Inherit from - , inherit: SVG.Container - - // Add class methods - , extend: { - // From position - from: function(x, y) { - return this.type == 'radial' ? - this.attr({ fx: new SVG.Number(x), fy: new SVG.Number(y) }) : - this.attr({ x1: new SVG.Number(x), y1: new SVG.Number(y) }) - } - // To position - , to: function(x, y) { - return this.type == 'radial' ? - this.attr({ cx: new SVG.Number(x), cy: new SVG.Number(y) }) : - this.attr({ x2: new SVG.Number(x), y2: new SVG.Number(y) }) - } - // Radius for radial gradient - , radius: function(r) { - return this.type == 'radial' ? - this.attr({ r: new SVG.Number(r) }) : - this - } - // Add a color stop - , at: function(offset, color, opacity) { - return this.put(new SVG.Stop).update(offset, color, opacity) - } - // Update gradient - , update: function(block) { - /* remove all stops */ - this.clear() - - /* invoke passed block */ - if (typeof block == 'function') - block.call(this, this) - - return this - } - // Return the fill id - , fill: function() { - return 'url(#' + this.id() + ')' - } - // Alias string convertion to fill - , toString: function() { - return this.fill() - } + // Return the fill id + , fill: function() { + return 'url(#' + this.id() + ')' } - - // Add parent method - , construct: { - // Create gradient element in defs - gradient: function(type, block) { - return this.defs().gradient(type, block) - } + // Alias string convertion to fill + , toString: function() { + return this.fill() } - }) + } - SVG.extend(SVG.Defs, { - // define gradient + // Add parent method +, construct: { + // Create gradient element in defs gradient: function(type, block) { - return this.put(new SVG.Gradient(type)).update(block) + return this.defs().gradient(type, block) } - - }) + } +}) + +// Add animatable methods to both gradient and fx module +SVG.extend(SVG.Gradient, SVG.FX, { + // From position + from: function(x, y) { + return (this.target || this).type == 'radial' ? + this.attr({ fx: new SVG.Number(x), fy: new SVG.Number(y) }) : + this.attr({ x1: new SVG.Number(x), y1: new SVG.Number(y) }) + } + // To position +, to: function(x, y) { + return (this.target || this).type == 'radial' ? + this.attr({ cx: new SVG.Number(x), cy: new SVG.Number(y) }) : + this.attr({ x2: new SVG.Number(x), y2: new SVG.Number(y) }) + } +}) + +// Base gradient generation +SVG.extend(SVG.Defs, { + // define gradient + gradient: function(type, block) { + return this.put(new SVG.Gradient(type)).update(block) + } - SVG.Stop = SVG.invent({ - // Initialize node - create: 'stop' - - // Inherit from - , inherit: SVG.Element - - // Add class methods - , extend: { - // add color stops - update: function(o) { - if (typeof o == 'number' || o instanceof SVG.Number) { - o = { - offset: arguments[0] - , color: arguments[1] - , opacity: arguments[2] - } +}) + +SVG.Stop = SVG.invent({ + // Initialize node + create: 'stop' + + // Inherit from +, inherit: SVG.Element + + // Add class methods +, extend: { + // add color stops + update: function(o) { + if (typeof o == 'number' || o instanceof SVG.Number) { + o = { + offset: arguments[0] + , color: arguments[1] + , opacity: arguments[2] } - - /* set attributes */ - 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 } - } - - }) + /* set attributes */ + 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)) - SVG.Pattern = SVG.invent({ - // Initialize node - create: 'pattern' - - // Inherit from - , inherit: SVG.Container - - // Add class methods - , extend: { - // Return the fill id - fill: function() { - return 'url(#' + this.id() + ')' - } - // Update pattern by rebuilding - , update: function(block) { - /* remove content */ - this.clear() - - /* invoke passed block */ - if (typeof block == 'function') - block.call(this, this) - - return this - } - // Alias string convertion to fill - , toString: function() { - return this.fill() - } - } - - // Add parent method - , construct: { - // Create pattern element in defs - pattern: function(width, height, block) { - return this.defs().pattern(width, height, block) - } - } - }) - - SVG.extend(SVG.Defs, { - // Define gradient - pattern: function(width, height, block) { - return this.put(new SVG.Pattern).update(block).attr({ - x: 0 - , y: 0 - , width: width - , height: height - , patternUnits: 'userSpaceOnUse' - }) + return this } + } + +}) + +SVG.Pattern = SVG.invent({ + // Initialize node + create: 'pattern' + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Return the fill id + fill: function() { + return 'url(#' + this.id() + ')' + } + // Update pattern by rebuilding + , update: function(block) { + /* remove content */ + this.clear() + + /* invoke passed block */ + if (typeof block == 'function') + block.call(this, this) + + return this + } + // Alias string convertion to fill + , toString: function() { + return this.fill() + } + } - }) + // Add parent method +, construct: { + // Create pattern element in defs + pattern: function(width, height, block) { + return this.defs().pattern(width, height, block) + } + } +}) - SVG.Doc = SVG.invent({ - // Initialize node - create: function(element) { - /* ensure the presence of a html element */ - this.parent = typeof element == 'string' ? +SVG.extend(SVG.Defs, { + // Define gradient + pattern: function(width, height, block) { + return this.put(new SVG.Pattern).update(block).attr({ + x: 0 + , y: 0 + , width: width + , height: height + , patternUnits: 'userSpaceOnUse' + }) + } + +}) +SVG.Doc = SVG.invent({ + // Initialize node + create: function(element) { + if (element) { + /* ensure the presence of a dom element */ + element = typeof element == 'string' ? document.getElementById(element) : element /* If the target is an svg element, use that element as the main wrapper. This allows svg.js to work with svg documents as well. */ - this.constructor - .call(this, this.parent.nodeName == 'svg' ? this.parent : SVG.create('svg')) - - /* set svg element attributes */ - this - .attr({ xmlns: SVG.ns, version: '1.1', width: '100%', height: '100%' }) - .attr('xmlns:xlink', SVG.xlink, SVG.xmlns) - - /* create the <defs> node */ - this._defs = new SVG.Defs - this._defs.parent = this - this.node.appendChild(this._defs.node) - - /* turn off sub pixel offset by default */ - this.doSpof = false - - /* ensure correct rendering */ - if (this.parent != this.node) - this.stage() - } - - // Inherit from - , inherit: SVG.Container - - // Add class methods - , extend: { - /* enable drawing */ - stage: function() { - var element = this - - /* insert element */ - this.parent.appendChild(this.node) - - /* fix sub-pixel offset */ - element.spof() - - /* make sure sub-pixel offset is fixed every time the window is resized */ - SVG.on(window, 'resize', function() { - element.spof() - }) - - return this - } - - // Creates and returns defs element - , defs: function() { - return this._defs - } - - // Fix for possible sub-pixel offset. See: - // https://bugzilla.mozilla.org/show_bug.cgi?id=608812 - , spof: function() { - if (this.doSpof) { - var pos = this.node.getScreenCTM() - - if (pos) - this - .style('left', (-pos.e % 1) + 'px') - .style('top', (-pos.f % 1) + 'px') - } - - return this - } - - // Enable sub-pixel offset - , fixSubPixelOffset: function() { - this.doSpof = true - - return this + if (element.nodeName == 'svg') { + this.constructor.call(this, element) + } else { + this.constructor.call(this, SVG.create('svg')) + element.appendChild(this.node) } - // Removes the doc from the DOM - , remove: function() { - if(this.parent) { - this.parent.removeChild(this.node); - this.parent = null; - } - - return this; - } + /* set svg element attributes and ensure defs node */ + this.namespace().size('100%', '100%').defs() } - - }) + } + // Inherit from +, inherit: SVG.Container - SVG.Shape = SVG.invent({ - // Initialize node - create: function(element) { - this.constructor.call(this, element) - } - - // Inherit from - , inherit: SVG.Element - - }) + // Add class methods +, extend: { + // Add namespaces + namespace: function() { + return this + .attr({ xmlns: SVG.ns, version: '1.1' }) + .attr('xmlns:xlink', SVG.xlink, SVG.xmlns) + } + // Creates and returns defs element + , defs: function() { + if (!this._defs) { + var defs - SVG.Symbol = SVG.invent({ - // Initialize node - create: 'symbol' - - // Inherit from - , inherit: SVG.Container - - // Add parent method - , construct: { - // Create a new symbol - symbol: function() { - return this.defs().put(new SVG.Symbol) + // Find or create a defs element in this instance + if (defs = this.node.getElementsByTagName('defs')[0]) + this._defs = SVG.adopt(defs) + else + this._defs = new SVG.Defs + + // Make sure the defs node is at the end of the stack + this.node.appendChild(this._defs.node) } + + return this._defs + } + // custom parent method + , parent: function() { + return this.node.parentNode.nodeName == '#document' ? null : this.node.parentNode + } + // Fix for possible sub-pixel offset. See: + // https://bugzilla.mozilla.org/show_bug.cgi?id=608812 + , spof: function(spof) { + var pos = this.node.getScreenCTM() + + if (pos) + this + .style('left', (-pos.e % 1) + 'px') + .style('top', (-pos.f % 1) + 'px') + + return this } - }) + // Removes the doc from the DOM + , remove: function() { + if(this.parent()) { + this.parent().removeChild(this.node); + } - SVG.Use = SVG.invent({ - // Initialize node - create: 'use' - - // Inherit from - , inherit: SVG.Shape + return this; + } + } - // Add class methods - , extend: { - // Use element as a reference - element: function(element, file) { - /* store target element */ - this.target = element +}) + +SVG.Shape = SVG.invent({ + // Initialize node + create: function(element) { + this.constructor.call(this, element) + } + + // Inherit from +, inherit: SVG.Element + +}) + +SVG.Bare = SVG.invent({ + // Initialize + create: function(element, inherit) { + // construct element + this.constructor.call(this, SVG.create(element)) + + // inherit custom methods + if (inherit) + for (var method in inherit.prototype) + if (typeof inherit.prototype[method] === 'function') + element[method] = inherit.prototype[method] + } + + // Inherit from +, inherit: SVG.Element + + // Add methods +, extend: { + // Insert some plain text + words: function(text) { + // remove contents + while (this.node.hasChildNodes()) + this.node.removeChild(this.node.lastChild) + + // create text node + this.node.appendChild(document.createTextNode(text)) + + return this + } + } +}) + + +SVG.extend(SVG.Parent, { + // Create an element that is not described by SVG.js + element: function(element, inherit) { + return this.put(new SVG.Bare(element, inherit)) + } + // Add symbol element +, symbol: function() { + return this.defs().element('symbol', SVG.Container) + } + +}) +SVG.Use = SVG.invent({ + // Initialize node + create: 'use' + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // Use element as a reference + element: function(element, file) { + /* Store target element */ + this.target = element + + /* Set lined element */ + return this.attr('href', (file || '') + '#' + element, SVG.xlink) + } + } - /* set lined element */ - return this.attr('href', (file || '') + '#' + element, SVG.xlink) - } + // Add parent method +, construct: { + // Create a use element + use: function(element, file) { + return this.put(new SVG.Use).element(element, file) } + } +}) +SVG.Rect = SVG.invent({ + // Initialize node + create: 'rect' + + // Inherit from +, inherit: SVG.Shape - // Add parent method - , construct: { - // Create a use element - use: function(element, file) { - return this.put(new SVG.Use).element(element, file) - } + // Add parent method +, construct: { + // Create a rect element + rect: function(width, height) { + return this.put(new SVG.Rect().size(width, height)) } - }) + } +}) +SVG.Circle = SVG.invent({ + // Initialize node + create: 'circle' - SVG.Rect = SVG.invent({ - // Initialize node - create: 'rect' - - // Inherit from - , inherit: SVG.Shape - - // Add parent method - , construct: { - // Create a rect element - rect: function(width, height) { - return this.put(new SVG.Rect().size(width, height)) - } - - } - - }) + // Inherit from +, inherit: SVG.Shape - SVG.Ellipse = SVG.invent({ - // Initialize node - create: 'ellipse' - - // Inherit from - , inherit: SVG.Shape - - // Add class methods - , extend: { - // Move over x-axis - x: function(x) { - return x == null ? this.cx() - this.attr('rx') : this.cx(x + this.attr('rx')) - } - // Move over y-axis - , y: function(y) { - return y == null ? this.cy() - this.attr('ry') : this.cy(y + this.attr('ry')) - } - // Move by center over x-axis - , cx: function(x) { - return x == null ? this.attr('cx') : this.attr('cx', new SVG.Number(x).divide(this.trans.scaleX)) - } - // Move by center over y-axis - , cy: function(y) { - return y == null ? this.attr('cy') : this.attr('cy', new SVG.Number(y).divide(this.trans.scaleY)) - } - // Set width of element - , width: function(width) { - return width == null ? this.attr('rx') * 2 : this.attr('rx', new SVG.Number(width).divide(2)) - } - // Set height of element - , height: function(height) { - return height == null ? this.attr('ry') * 2 : this.attr('ry', new SVG.Number(height).divide(2)) - } - // Custom size function - , size: function(width, height) { - var p = proportionalSize(this.bbox(), width, height) - - return this.attr({ - rx: new SVG.Number(p.width).divide(2) - , ry: new SVG.Number(p.height).divide(2) - }) - } - + // Add parent method +, construct: { + // Create circle element, based on ellipse + circle: function(size) { + return this.put(new SVG.Circle).rx(new SVG.Number(size).divide(2)).move(0, 0) } - - // Add parent method - , construct: { - // Create circle element, based on ellipse - circle: function(size) { - return this.ellipse(size, size) - } - // Create an ellipse - , ellipse: function(width, height) { - return this.put(new SVG.Ellipse).size(width, height).move(0, 0) - } - + } +}) + +SVG.extend(SVG.Circle, SVG.FX, { + // Radius x value + rx: function(rx) { + return this.attr('r', rx) + } + // Alias radius x value +, ry: function(ry) { + return this.rx(ry) + } +}) + +SVG.Ellipse = SVG.invent({ + // Initialize node + create: 'ellipse' + + // Inherit from +, inherit: SVG.Shape + + // Add parent method +, construct: { + // Create an ellipse + ellipse: function(width, height) { + return this.put(new SVG.Ellipse).size(width, height).move(0, 0) } - - }) + } +}) - SVG.Line = SVG.invent({ - // Initialize node - create: 'line' - - // Inherit from - , inherit: SVG.Shape - - // Add class methods - , extend: { - // Move over x-axis - x: function(x) { - var b = this.bbox() - - return x == null ? b.x : this.attr({ - x1: this.attr('x1') - b.x + x - , x2: this.attr('x2') - b.x + x - }) - } - // Move over y-axis - , y: function(y) { - var b = this.bbox() - - return y == null ? b.y : this.attr({ - y1: this.attr('y1') - b.y + y - , y2: this.attr('y2') - b.y + y - }) - } - // Move by center over x-axis - , cx: function(x) { - var half = this.bbox().width / 2 - return x == null ? this.x() + half : this.x(x - half) - } - // Move by center over y-axis - , cy: function(y) { - var half = this.bbox().height / 2 - return y == null ? this.y() + half : this.y(y - half) - } - // Set width of element - , width: function(width) { - var b = this.bbox() - - return width == null ? b.width : this.attr(this.attr('x1') < this.attr('x2') ? 'x2' : 'x1', b.x + width) - } - // Set height of element - , height: function(height) { - var b = this.bbox() - - return height == null ? b.height : this.attr(this.attr('y1') < this.attr('y2') ? 'y2' : 'y1', b.y + height) - } - // Set line size by width and height - , size: function(width, height) { - var p = proportionalSize(this.bbox(), width, height) - - return this.width(p.width).height(p.height) - } - // Set path data - , plot: function(x1, y1, x2, y2) { - return this.attr({ - x1: x1 - , y1: y1 - , x2: x2 - , y2: y2 - }) - } +SVG.extend(SVG.Ellipse, SVG.Rect, SVG.FX, { + // Radius x value + rx: function(rx) { + return this.attr('rx', rx) + } + // Radius y value +, ry: function(ry) { + return this.attr('ry', ry) + } +}) + +// Add common method +SVG.extend(SVG.Circle, SVG.Ellipse, { + // Move over x-axis + x: function(x) { + return x == null ? this.cx() - this.rx() : this.cx(x + this.rx()) } - - // Add parent method - , construct: { - // Create a line element - line: function(x1, y1, x2, y2) { - return this.put(new SVG.Line().plot(x1, y1, x2, y2)) - } + // Move over y-axis + , y: function(y) { + return y == null ? this.cy() - this.ry() : this.cy(y + this.ry()) } - }) + // Move by center over x-axis + , cx: function(x) { + return x == null ? this.attr('cx') : this.attr('cx', new SVG.Number(x).divide(this.transform('scaleX'))) + } + // Move by center over y-axis + , cy: function(y) { + return y == null ? this.attr('cy') : this.attr('cy', new SVG.Number(y).divide(this.transform('scaleY'))) + } + // Set width of element + , width: function(width) { + return width == null ? this.rx() * 2 : this.rx(new SVG.Number(width).divide(2)) + } + // Set height of element + , height: function(height) { + return height == null ? this.ry() * 2 : this.ry(new SVG.Number(height).divide(2)) + } + // Custom size function + , size: function(width, height) { + var p = proportionalSize(this.bbox(), width, height) + return this + .rx(new SVG.Number(p.width).divide(2)) + .ry(new SVG.Number(p.height).divide(2)) + } +}) +SVG.Line = SVG.invent({ + // Initialize node + create: 'line' - SVG.Polyline = SVG.invent({ - // Initialize node - create: 'polyline' - - // Inherit from - , inherit: SVG.Shape - - // Add parent method - , construct: { - // Create a wrapped polyline element - polyline: function(p) { - return this.put(new SVG.Polyline).plot(p) - } + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // Get array + array: function() { + return new SVG.PointArray([ + [ this.attr('x1'), this.attr('y1') ] + , [ this.attr('x2'), this.attr('y2') ] + ]) + } + // Overwrite native plot() method + , plot: function(x1, y1, x2, y2) { + if (arguments.length == 4) + x1 = { x1: x1, y1: y1, x2: x2, y2: y2 } + else + x1 = new SVG.PointArray(x1).toLine() + + return this.attr(x1) } - }) + // Move by left top corner + , move: function(x, y) { + return this.attr(this.array().move(x, y).toLine()) + } + // Set element size to given width and height + , size: function(width, height) { + var p = proportionalSize(this.bbox(), width, height) + + return this.attr(this.array().size(p.width, p.height).toLine()) + } + } - SVG.Polygon = SVG.invent({ - // Initialize node - create: 'polygon' + // Add parent method +, construct: { + // Create a line element + line: function(x1, y1, x2, y2) { + return this.put(new SVG.Line).plot(x1, y1, x2, y2) + } + } +}) + +SVG.Polyline = SVG.invent({ + // Initialize node + create: 'polyline' + + // Inherit from +, inherit: SVG.Shape - // Inherit from - , inherit: SVG.Shape - - // Add parent method - , construct: { - // Create a wrapped polygon element - polygon: function(p) { - return this.put(new SVG.Polygon).plot(p) - } + // Add parent method +, construct: { + // Create a wrapped polyline element + polyline: function(p) { + return this.put(new SVG.Polyline).plot(p) } - }) + } +}) + +SVG.Polygon = SVG.invent({ + // Initialize node + create: 'polygon' + + // Inherit from +, inherit: SVG.Shape - // Add polygon-specific functions - SVG.extend(SVG.Polyline, SVG.Polygon, { + // Add parent method +, construct: { + // Create a wrapped polygon element + polygon: function(p) { + return this.put(new SVG.Polygon).plot(p) + } + } +}) + +// Add polygon-specific functions +SVG.extend(SVG.Polyline, SVG.Polygon, { + // Get array + array: function() { + return this._array || (this._array = new SVG.PointArray(this.attr('points'))) + } + // Plot new path +, plot: function(p) { + return this.attr('points', (this._array = new SVG.PointArray(p))) + } + // Move by left top corner +, move: function(x, y) { + return this.attr('points', this.array().move(x, y)) + } + // Set element size to given width and height +, size: function(width, height) { + var p = proportionalSize(this.bbox(), width, height) + + return this.attr('points', this.array().size(p.width, p.height)) + } + +}) +// unify all point to point elements +SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, { + // Define morphable array + morphArray: SVG.PointArray + // Move by left top corner over x-axis +, x: function(x) { + return x == null ? this.bbox().x : this.move(x, this.bbox().y) + } + // Move by left top corner over y-axis +, y: function(y) { + return y == null ? this.bbox().y : this.move(this.bbox().x, y) + } + // Set width of element +, width: function(width) { + var b = this.bbox() + + return width == null ? b.width : this.size(width, b.height) + } + // Set height of element +, height: function(height) { + var b = this.bbox() + + return height == null ? b.height : this.size(b.width, height) + } +}) +SVG.Path = SVG.invent({ + // Initialize node + create: 'path' + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { // Define morphable array - morphArray: SVG.PointArray - // Plot new path + morphArray: SVG.PathArray + // Get array + , array: function() { + return this._array || (this._array = new SVG.PathArray(this.attr('d'))) + } + // Plot new poly points , plot: function(p) { - return this.attr('points', (this.array = new SVG.PointArray(p, [[0,0]]))) + return this.attr('d', (this._array = new SVG.PathArray(p))) } // Move by left top corner , move: function(x, y) { - return this.attr('points', this.array.move(x, y)) + return this.attr('d', this.array().move(x, y)) } // Move by left top corner over x-axis , x: function(x) { @@ -2935,999 +3219,1086 @@ , y: function(y) { return y == null ? this.bbox().y : this.move(this.bbox().x, y) } + // Set element size to given width and height + , size: function(width, height) { + var p = proportionalSize(this.bbox(), width, height) + + return this.attr('d', this.array().size(p.width, p.height)) + } // Set width of element , width: function(width) { - var b = this.bbox() - - return width == null ? b.width : this.size(width, b.height) + return width == null ? this.bbox().width : this.size(width, this.bbox().height) } // Set height of element , height: function(height) { - var b = this.bbox() - - return height == null ? b.height : this.size(b.width, height) + return height == null ? this.bbox().height : this.size(this.bbox().width, height) } - // Set element size to given width and height - , size: function(width, height) { - var p = proportionalSize(this.bbox(), width, height) + + } - return this.attr('points', this.array.size(p.width, p.height)) + // Add parent method +, construct: { + // Create a wrapped path element + path: function(d) { + return this.put(new SVG.Path).plot(d) } - - }) + } +}) +SVG.Image = SVG.invent({ + // Initialize node + create: 'image' - SVG.Path = SVG.invent({ - // Initialize node - create: 'path' - - // Inherit from - , inherit: SVG.Shape - - // Add class methods - , extend: { - // Plot new poly points - plot: function(p) { - return this.attr('d', (this.array = new SVG.PathArray(p, [['M', 0, 0]]))) - } - // Move by left top corner - , move: function(x, y) { - return this.attr('d', this.array.move(x, y)) - } - // Move by left top corner over x-axis - , x: function(x) { - return x == null ? this.bbox().x : this.move(x, this.bbox().y) - } - // Move by left top corner over y-axis - , y: function(y) { - return y == null ? this.bbox().y : this.move(this.bbox().x, y) - } - // Set element size to given width and height - , size: function(width, height) { - var p = proportionalSize(this.bbox(), width, height) - - return this.attr('d', this.array.size(p.width, p.height)) - } - // Set width of element - , width: function(width) { - return width == null ? this.bbox().width : this.size(width, this.bbox().height) - } - // Set height of element - , height: function(height) { - return height == null ? this.bbox().height : this.size(this.bbox().width, height) - } + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // (re)load image + load: function(url) { + if (!url) return this + + var self = this + , img = document.createElement('img') - } - - // Add parent method - , construct: { - // Create a wrapped path element - path: function(d) { - return this.put(new SVG.Path).plot(d) - } - } - }) + // preload image + img.onload = function() { + var p = self.doc(SVG.Pattern) - SVG.Image = SVG.invent({ - // Initialize node - create: 'image' - - // Inherit from - , inherit: SVG.Shape - - // Add class methods - , extend: { - // (re)load image - load: function(url) { - if (!url) return this - - var self = this - , img = document.createElement('img') + // ensure image size + if (self.width() == 0 && self.height() == 0) + self.size(img.width, img.height) + + // ensure pattern size if not set + if (p && p.width() == 0 && p.height() == 0) + p.size(self.width(), self.height()) - /* preload image */ - img.onload = function() { - var p = self.doc(SVG.Pattern) - - /* ensure image size */ - if (self.width() == 0 && self.height() == 0) - self.size(img.width, img.height) - - /* ensure pattern size if not set */ - if (p && p.width() == 0 && p.height() == 0) - p.size(self.width(), self.height()) - - /* callback */ - if (typeof self._loaded === 'function') - self._loaded.call(self, { - width: img.width - , height: img.height - , ratio: img.width / img.height - , url: url - }) - } - - return this.attr('href', (img.src = this.src = url), SVG.xlink) - } - // Add loade callback - , loaded: function(loaded) { - this._loaded = loaded - return this + // callback + if (typeof self._loaded === 'function') + self._loaded.call(self, { + width: img.width + , height: img.height + , ratio: img.width / img.height + , url: url + }) } + + return this.attr('href', (img.src = this.src = url), SVG.xlink) } - - // Add parent method - , construct: { - // Create image element, load image and set its size - image: function(source, width, height) { - return this.put(new SVG.Image).load(source).size(width || 0, height || width || 0) - } + // Add loaded callback + , loaded: function(loaded) { + this._loaded = loaded + return this } + } - }) + // Add parent method +, construct: { + // create image element, load image and set its size + image: function(source, width, height) { + return this.put(new SVG.Image).load(source).size(width || 0, height || width || 0) + } + } + +}) +SVG.Text = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, SVG.create('text')) + + this._leading = new SVG.Number(1.3) // store leading value for rebuilding + this._rebuild = true // enable automatic updating of dy values + this._build = false // disable build mode for adding multiple lines + + // set default font + this.attr('font-family', SVG.defaults.attrs['font-family']) + } - SVG.Text = SVG.invent({ - // Initialize node - create: function() { - this.constructor.call(this, SVG.create('text')) + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // Move over x-axis + x: function(x) { + // act as getter + if (x == null) + return this.attr('x') - this._leading = new SVG.Number(1.3) /* store leading value for rebuilding */ - this._rebuild = true /* enable automatic updating of dy values */ - this._build = false /* disable build mode for adding multiple lines */ - - /* set default font */ - this.attr('font-family', SVG.defaults.attrs['font-family']) + // move lines as well if no textPath is present + if (!this.textPath) + this.lines().each(function() { if (this.newLined) this.x(x) }) + + return this.attr('x', x) } - - // Inherit from - , inherit: SVG.Shape - - // Add class methods - , extend: { - // Move over x-axis - x: function(x) { - /* act as getter */ - if (x == null) - return this.attr('x') - - /* move lines as well if no textPath is present */ - if (!this.textPath) - this.lines.each(function() { if (this.newLined) this.x(x) }) - - return this.attr('x', x) - } - // Move over y-axis - , y: function(y) { - var oy = this.attr('y') - , o = typeof oy === 'number' ? oy - this.bbox().y : 0 - - /* act as getter */ - if (y == null) - return typeof oy === 'number' ? oy - o : oy - - return this.attr('y', typeof y === 'number' ? y + o : y) - } - // Move center over x-axis - , cx: function(x) { - return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2) - } - // Move center over y-axis - , cy: function(y) { - return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2) - } - // Set the text content - , text: function(text) { - /* act as getter */ - if (typeof text === 'undefined') return this.content - - /* remove existing content */ - this.clear().build(true) - - if (typeof text === 'function') { - /* call block */ - text.call(this, this) - - } else { - /* store text and make sure text is not blank */ - text = (this.content = text).split('\n') - - /* build new lines */ - for (var i = 0, il = text.length; i < il; i++) - this.tspan(text[i]).newLine() - } + // Move over y-axis + , y: function(y) { + var oy = this.attr('y') + , o = typeof oy === 'number' ? oy - this.bbox().y : 0 + + // act as getter + if (y == null) + return typeof oy === 'number' ? oy - o : oy + + return this.attr('y', typeof y === 'number' ? y + o : y) + } + // Move center over x-axis + , cx: function(x) { + return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2) + } + // Move center over y-axis + , cy: function(y) { + return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2) + } + // Set the text content + , text: function(text) { + // act as getter + if (typeof text === 'undefined') return this.content + + // remove existing content + this.clear().build(true) + + if (typeof text === 'function') { + // call block + text.call(this, this) + + } else { + // store text and make sure text is not blank + text = (this.content = text).split('\n') - /* disable build mode and rebuild lines */ - return this.build(false).rebuild() + // build new lines + for (var i = 0, il = text.length; i < il; i++) + this.tspan(text[i]).newLine() } - // Set font size - , size: function(size) { - return this.attr('font-size', size).rebuild() - } - // Set / get leading - , leading: function(value) { - /* act as getter */ - if (value == null) - return this._leading - - /* act as setter */ - this._leading = new SVG.Number(value) + + // disable build mode and rebuild lines + return this.build(false).rebuild() + } + // Set font size + , size: function(size) { + return this.attr('font-size', size).rebuild() + } + // Set / get leading + , leading: function(value) { + // act as getter + if (value == null) + return this._leading + + // act as setter + this._leading = new SVG.Number(value) + + return this.rebuild() + } + // Get all the first level lines + , lines: function() { + // filter tspans and map them to SVG.js instances + for (var i = 0, il = this.node.childNodes.length, lines = []; i < il; i++) + if (this.node.childNodes[i] instanceof SVGElement) + lines.push(SVG.adopt(this.node.childNodes[i])) + + // return an instance of SVG.set + return new SVG.Set(lines) + } + // Rebuild appearance type + , rebuild: function(rebuild) { + // store new rebuild flag if given + if (typeof rebuild == 'boolean') + this._rebuild = rebuild + + // define position of all lines + if (this._rebuild) { + var self = this - return this.rebuild() - } - // Rebuild appearance type - , rebuild: function(rebuild) { - /* store new rebuild flag if given */ - if (typeof rebuild == 'boolean') - this._rebuild = rebuild - - /* define position of all lines */ - if (this._rebuild) { - var self = this - - this.lines.each(function() { - if (this.newLined) { - if (!this.textPath) - this.attr('x', self.attr('x')) - this.attr('dy', self._leading * new SVG.Number(self.attr('font-size'))) - } - }) - - this.fire('rebuild') - } - - return this - } - // Enable / disable build mode - , build: function(build) { - this._build = !!build - return this + this.lines().each(function() { + if (this.newLined) { + if (!this.textPath) + this.attr('x', self.attr('x')) + + this.attr('dy', self._leading * new SVG.Number(self.attr('font-size'))) + } + }) + + this.fire('rebuild') } + + return this } - - // Add parent method - , construct: { - // Create text element - text: function(text) { - return this.put(new SVG.Text).text(text) - } - // Create plain text element - , plain: function(text) { - return this.put(new SVG.Text).plain(text) - } + // Enable / disable build mode + , build: function(build) { + this._build = !!build + return this } + } - }) - - SVG.TSpan = SVG.invent({ - // Initialize node - create: 'tspan' - - // Inherit from - , inherit: SVG.Shape - - // Add class methods - , extend: { - // Set text content - text: function(text) { - typeof text === 'function' ? text.call(this, this) : this.plain(text) - - return this - } - // Shortcut dx - , dx: function(dx) { - return this.attr('dx', dx) - } - // Shortcut dy - , dy: function(dy) { - return this.attr('dy', dy) - } - // Create new line - , newLine: function() { - /* fetch text parent */ - var t = this.doc(SVG.Text) - - /* mark new line */ - this.newLined = true - - /* apply new hy¡n */ - return this.dy(t._leading * t.attr('font-size')).attr('x', t.x()) - } + // Add parent method +, construct: { + // Create text element + text: function(text) { + return this.put(new SVG.Text).text(text) } - - }) - - SVG.extend(SVG.Text, SVG.TSpan, { - // Create plain text node - plain: function(text) { - /* clear if build mode is disabled */ - if (this._build === false) - this.clear() - - /* create text node */ - this.node.appendChild(document.createTextNode((this.content = text))) - + // Create plain text element + , plain: function(text) { + return this.put(new SVG.Text).plain(text) + } + } + +}) + +SVG.Tspan = SVG.invent({ + // Initialize node + create: 'tspan' + + // Inherit from +, inherit: SVG.Shape + + // Add class methods +, extend: { + // Set text content + text: function(text) { + typeof text === 'function' ? text.call(this, this) : this.plain(text) + return this } - // Create a tspan - , tspan: function(text) { - var node = (this.textPath || this).node - , tspan = new SVG.TSpan - - /* clear if build mode is disabled */ - if (this._build === false) - this.clear() - - /* add new tspan and reference */ - node.appendChild(tspan.node) - tspan.parent = this - - /* only first level tspans are considered to be "lines" */ - if (this instanceof SVG.Text) - this.lines.add(tspan) - - return tspan.text(text) + // Shortcut dx + , dx: function(dx) { + return this.attr('dx', dx) } - // Clear all lines - , clear: function() { - var node = (this.textPath || this).node + // Shortcut dy + , dy: function(dy) { + return this.attr('dy', dy) + } + // Create new line + , newLine: function() { + // fetch text parent + var t = this.doc(SVG.Text) + + // mark new line + this.newLined = true + + // apply new hy¡n + return this.dy(t._leading * t.attr('font-size')).attr('x', t.x()) + } + } - /* remove existing child nodes */ - while (node.hasChildNodes()) - node.removeChild(node.lastChild) - - /* reset content references */ - if (this instanceof SVG.Text) { - delete this.lines - this.lines = new SVG.Set - this.content = '' - } - +}) + +SVG.extend(SVG.Text, SVG.Tspan, { + // Create plain text node + plain: function(text) { + // clear if build mode is disabled + if (this._build === false) + this.clear() + + // create text node + this.node.appendChild(document.createTextNode((this.content = text))) + + return this + } + // Create a tspan +, tspan: function(text) { + var node = (this.textPath() || this).node + , tspan = new SVG.Tspan + + // clear if build mode is disabled + if (this._build === false) + this.clear() + + // add new tspan + node.appendChild(tspan.node) + + // only first level tspans are considered to be "lines" + // that doenst make sence. A line is added to a SVG.Set which is never used or returned. + // So why bother adding it? + // Also: lines() reads all children so it already has this tspan in it because we added it before + if (this instanceof SVG.Text) + this.lines().add(tspan) + + return tspan.text(text) + } + // Clear all lines +, clear: function() { + var node = (this.textPath() || this).node + + // remove existing child nodes + while (node.hasChildNodes()) + node.removeChild(node.lastChild) + + // reset content references + if (this instanceof SVG.Text) + this.content = '' + + return this + } + // Get length of text element +, length: function() { + return this.node.getComputedTextLength() + } +}) + +SVG.TextPath = SVG.invent({ + // Initialize node + create: 'textPath' + + // Inherit from +, inherit: SVG.Element + + // Define parent class +, parent: SVG.Text + + // Add parent method +, construct: { + // Create path for text to run on + path: function(d) { + // create textPath element + var path = new SVG.TextPath + , track = this.doc().defs().path(d) + + // move lines to textpath + while (this.node.hasChildNodes()) + path.node.appendChild(this.node.firstChild) + + // add textPath element as child node + this.node.appendChild(path.node) + + // link textPath to path and add content + path.attr('href', '#' + track, SVG.xlink) + return this } - // Get length of text element - , length: function() { - return this.node.getComputedTextLength() - } - }) + // Plot path if any + , plot: function(d) { + var track = this.track() + if (track) + track.plot(d) - SVG.TextPath = SVG.invent({ - // Initialize node - create: 'textPath' - - // Inherit from - , inherit: SVG.Element - - // Define parent class - , parent: SVG.Text - - // Add parent method - , construct: { - // Create path for text to run on - path: function(d) { - /* create textPath element */ - this.textPath = new SVG.TextPath - - /* move lines to textpath */ - while(this.node.hasChildNodes()) - this.textPath.node.appendChild(this.node.firstChild) - - /* add textPath element as child node */ - this.node.appendChild(this.textPath.node) - - /* create path in defs */ - this.track = this.doc().defs().path(d) - - /* create circular reference */ - this.textPath.parent = this - - /* link textPath to path and add content */ - this.textPath.attr('href', '#' + this.track, SVG.xlink) - - return this - } - // Plot path if any - , plot: function(d) { - if (this.track) this.track.plot(d) - return this - } + return this } - }) + // Get the path track element + , track: function() { + var path = this.textPath() - SVG.Nested = SVG.invent({ - // Initialize node - create: function() { - this.constructor.call(this, SVG.create('svg')) - - this.style('overflow', 'visible') + if (path) + return path.reference('href') } - - // Inherit from - , inherit: SVG.Container - - // Add parent method - , construct: { - // Create nested svg document - nested: function() { - return this.put(new SVG.Nested) - } + // Get the textPath child + , textPath: function() { + if (this.node.firstChild && this.node.firstChild.nodeName == 'textPath') + return SVG.adopt(this.node.firstChild) } - }) + } +}) +SVG.Nested = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, SVG.create('svg')) + + this.style('overflow', 'visible') + } - SVG.A = SVG.invent({ - // Initialize node - create: 'a' - - // Inherit from - , inherit: SVG.Container + // Inherit from +, inherit: SVG.Container - // Add class methods - , extend: { - // Link url - to: function(url) { - return this.attr('href', url, SVG.xlink) - } - // Link show attribute - , show: function(target) { - return this.attr('show', target, SVG.xlink) - } - // Link target attribute - , target: function(target) { - return this.attr('target', target) - } + // Add parent method +, construct: { + // Create nested svg document + nested: function() { + return this.put(new SVG.Nested) } - - // Add parent method - , construct: { - // Create a hyperlink element - link: function(url) { - return this.put(new SVG.A).to(url) - } + } +}) +SVG.A = SVG.invent({ + // Initialize node + create: 'a' + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Link url + to: function(url) { + return this.attr('href', url, SVG.xlink) } - }) + // Link show attribute + , show: function(target) { + return this.attr('show', target, SVG.xlink) + } + // Link target attribute + , target: function(target) { + return this.attr('target', target) + } + } - SVG.extend(SVG.Element, { + // Add parent method +, construct: { // Create a hyperlink element - linkTo: function(url) { - var link = new SVG.A - - if (typeof url == 'function') - url.call(link, link) - else - link.to(url) - - return this.parent.put(link).put(this) + link: function(url) { + return this.put(new SVG.A).to(url) } - - }) + } +}) - SVG.Marker = SVG.invent({ - // Initialize node - create: 'marker' - - // Inherit from - , inherit: SVG.Container +SVG.extend(SVG.Element, { + // Create a hyperlink element + linkTo: function(url) { + var link = new SVG.A + + if (typeof url == 'function') + url.call(link, link) + else + link.to(url) + + return this.parent().put(link).put(this) + } - // Add class methods - , extend: { - // Set width of element - width: function(width) { - return this.attr('markerWidth', width) - } - // Set height of element - , height: function(height) { - return this.attr('markerHeight', height) - } - // Set marker refX and refY - , ref: function(x, y) { - return this.attr('refX', x).attr('refY', y) - } - // Update marker - , update: function(block) { - /* remove all content */ - this.clear() - - /* invoke passed block */ - if (typeof block == 'function') - block.call(this, this) - - return this - } - // Return the fill id - , toString: function() { - return 'url(#' + this.id() + ')' - } +}) +SVG.Marker = SVG.invent({ + // Initialize node + create: 'marker' + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Set width of element + width: function(width) { + return this.attr('markerWidth', width) } - - // Add parent method - , construct: { - marker: function(width, height, block) { - // Create marker element in defs - return this.defs().marker(width, height, block) - } + // Set height of element + , height: function(height) { + return this.attr('markerHeight', height) } - - }) - - SVG.extend(SVG.Defs, { - // Create marker - marker: function(width, height, block) { - // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto - return this.put(new SVG.Marker) - .size(width, height) - .ref(width / 2, height / 2) - .viewbox(0, 0, width, height) - .attr('orient', 'auto') - .update(block) + // Set marker refX and refY + , ref: function(x, y) { + return this.attr('refX', x).attr('refY', y) } - - }) - - SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, SVG.Path, { - // Create and attach markers - marker: function(marker, width, height, block) { - var attr = ['marker'] - - // Build attribute name - if (marker != 'all') attr.push(marker) - attr = attr.join('-') - - // Set marker attribute - marker = arguments[1] instanceof SVG.Marker ? - arguments[1] : - this.doc().marker(width, height, block) + // Update marker + , update: function(block) { + /* remove all content */ + this.clear() + + /* invoke passed block */ + if (typeof block == 'function') + block.call(this, this) - return this.attr(attr, marker) + return this } - - }) + // Return the fill id + , toString: function() { + return 'url(#' + this.id() + ')' + } + } - var sugar = { - stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'] - , fill: ['color', 'opacity', 'rule'] - , prefix: function(t, a) { - return a == 'color' ? t : t + '-' + a + // Add parent method +, construct: { + marker: function(width, height, block) { + // Create marker element in defs + return this.defs().marker(width, height, block) } } + +}) + +SVG.extend(SVG.Defs, { + // Create marker + marker: function(width, height, block) { + // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto + return this.put(new SVG.Marker) + .size(width, height) + .ref(width / 2, height / 2) + .viewbox(0, 0, width, height) + .attr('orient', 'auto') + .update(block) + } - /* Add sugar for fill and stroke */ - ;['fill', 'stroke'].forEach(function(m) { - var i, extension = {} +}) + +SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, SVG.Path, { + // Create and attach markers + marker: function(marker, width, height, block) { + var attr = ['marker'] + + // Build attribute name + if (marker != 'all') attr.push(marker) + attr = attr.join('-') + + // Set marker attribute + marker = arguments[1] instanceof SVG.Marker ? + arguments[1] : + this.doc().marker(width, height, block) - extension[m] = function(o) { - if (typeof o == 'string' || SVG.Color.isRgb(o) || (o && typeof o.fill === 'function')) - this.attr(m, o) + return this.attr(attr, marker) + } - else - /* set all attributes from sugar.fill and sugar.stroke list */ - for (i = sugar[m].length - 1; i >= 0; i--) - if (o[sugar[m][i]] != null) - this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]) +}) +// Define list of available attributes for stroke and fill +var sugar = { + stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'] +, fill: ['color', 'opacity', 'rule'] +, prefix: function(t, a) { + return a == 'color' ? t : t + '-' + a + } +} + +/* Add sugar for fill and stroke */ +;['fill', 'stroke'].forEach(function(m) { + var i, extension = {} + + extension[m] = function(o) { + if (typeof o == 'string' || SVG.Color.isRgb(o) || (o && typeof o.fill === 'function')) + this.attr(m, o) + + else + /* set all attributes from sugar.fill and sugar.stroke list */ + for (i = sugar[m].length - 1; i >= 0; i--) + if (o[sugar[m][i]] != null) + this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]) + + return this + } + + SVG.extend(SVG.Element, SVG.FX, extension) + +}) + +SVG.extend(SVG.Element, SVG.FX, { + // Map rotation to transform + rotate: function(d, cx, cy) { + return this.transform({ rotation: d, cx: cx, cy: cy }) + } + // Map skew to transform +, skew: function(x, y, cx, cy) { + return this.transform({ skewX: x, skewY: y, cx: cx, cy: cy }) + } + // Map scale to transform +, scale: function(x, y, cx, cy) { + return arguments.length == 1 || arguments.length == 3 ? + this.transform({ scale: x, cx: y, cy: cx }) : + this.transform({ scaleX: x, scaleY: y, cx: cx, cy: cy }) + } + // Map translate to transform +, translate: function(x, y) { + return this.transform({ x: x, y: y }) + } + // Map flip to transform +, flip: function(a, o) { + return this.transform({ flip: a, offset: o }) + } + // Map matrix to transform +, matrix: function(m) { + return this.attr('transform', new SVG.Matrix(m)) + } + // Opacity +, opacity: function(value) { + return this.attr('opacity', value) + } + // Relative move over x axis +, dx: function(x) { + return this.x((this.target || this).x() + x) + } + // Relative move over y axis +, dy: function(y) { + return this.y((this.target || this).y() + y) + } + // Relative move over x and y axes +, dmove: function(x, y) { + return this.dx(x).dy(y) + } +}) + +SVG.extend(SVG.Rect, SVG.Ellipse, SVG.Circle, SVG.Gradient, SVG.FX, { + // Add x and y radius + radius: function(x, y) { + return (this.target || this).type == 'radial' ? + this.attr({ r: new SVG.Number(x) }) : + this.rx(x).ry(y == null ? x : y) + } +}) + +SVG.extend(SVG.Path, { + // Get path length + length: function() { + return this.node.getTotalLength() + } + // Get point at length +, pointAt: function(length) { + return this.node.getPointAtLength(length) + } +}) + +SVG.extend(SVG.Parent, SVG.Text, SVG.FX, { + // Set font + font: function(o) { + for (var k in o) + k == 'leading' ? + this.leading(o[k]) : + k == 'anchor' ? + this.attr('text-anchor', o[k]) : + k == 'size' || k == 'family' || k == 'weight' || k == 'stretch' || k == 'variant' || k == 'style' ? + this.attr('font-'+ k, o[k]) : + this.attr(k, o[k]) + + return this + } +}) + + +SVG.Set = SVG.invent({ + // Initialize + create: function(members) { + // Set initial state + Array.isArray(members) ? this.members = members : this.clear() + } + + // Add class methods +, extend: { + // Add element to set + add: function() { + var i, il, elements = [].slice.call(arguments) + + for (i = 0, il = elements.length; i < il; i++) + this.members.push(elements[i]) return this } - - SVG.extend(SVG.Element, SVG.FX, extension) - - }) - - SVG.extend(SVG.Element, SVG.FX, { - // Rotation - rotate: function(deg, x, y) { - return this.transform({ - rotation: deg || 0 - , cx: x - , cy: y - }) + // Remove element from set + , remove: function(element) { + var i = this.index(element) + + // remove given child + if (i > -1) + this.members.splice(i, 1) + + return this } - // Skew - , skew: function(x, y) { - return this.transform({ - skewX: x || 0 - , skewY: y || 0 - }) + // Iterate over all members + , each: function(block) { + for (var i = 0, il = this.members.length; i < il; i++) + block.apply(this.members[i], [i, this.members]) + + return this } - // Scale - , scale: function(x, y) { - return this.transform({ - scaleX: x - , scaleY: y == null ? x : y - }) + // Restore to defaults + , clear: function() { + // initialize store + this.members = [] + + return this } - // Translate - , translate: function(x, y) { - return this.transform({ - x: x - , y: y - }) + // Get the length of a set + , length: function() { + return this.members.length } - // Matrix - , matrix: function(m) { - return this.transform({ matrix: m }) + // Checks if a given element is present in set + , has: function(element) { + return this.index(element) >= 0 } - // Opacity - , opacity: function(value) { - return this.attr('opacity', value) + // retuns index of given element in set + , index: function(element) { + return this.members.indexOf(element) } - - }) - - SVG.extend(SVG.Rect, SVG.Ellipse, SVG.FX, { - // Add x and y radius - radius: function(x, y) { - return this.attr({ rx: x, ry: y || x }) + // Get member at given index + , get: function(i) { + return this.members[i] } - - }) - - SVG.extend(SVG.Path, { - // Get path length - length: function() { - return this.node.getTotalLength() + // Get first member + , first: function() { + return this.get(0) } - // Get point at length - , pointAt: function(length) { - return this.node.getPointAtLength(length) + // Get last member + , last: function() { + return this.get(this.members.length - 1) } - - }) - - SVG.extend(SVG.Parent, SVG.Text, SVG.FX, { - // Set font - font: function(o) { - for (var k in o) - k == 'leading' ? - this.leading(o[k]) : - k == 'anchor' ? - this.attr('text-anchor', o[k]) : - k == 'size' || k == 'family' || k == 'weight' || k == 'stretch' || k == 'variant' || k == 'style' ? - this.attr('font-'+ k, o[k]) : - this.attr(k, o[k]) - - return this + // Default value + , valueOf: function() { + return this.members } - - }) - + // Get the bounding box of all members included or empty box if set has no items + , bbox: function(){ + var box = new SVG.BBox() + + // return an empty box of there are no members + if (this.members.length == 0) + return box + // get the first rbox and update the target bbox + var rbox = this.members[0].rbox() + box.x = rbox.x + box.y = rbox.y + box.width = rbox.width + box.height = rbox.height - SVG.Set = SVG.invent({ - // Initialize - create: function() { - /* set initial state */ - this.clear() + this.each(function() { + // user rbox for correct position and visual representation + box = box.merge(this.rbox()) + }) + + return box } + } - // Add class methods - , extend: { - // Add element to set - add: function() { - var i, il, elements = [].slice.call(arguments) - - for (i = 0, il = elements.length; i < il; i++) - this.members.push(elements[i]) - - return this - } - // Remove element from set - , remove: function(element) { - var i = this.index(element) - - /* remove given child */ - if (i > -1) - this.members.splice(i, 1) - - return this - } - // Iterate over all members - , each: function(block) { - for (var i = 0, il = this.members.length; i < il; i++) - block.apply(this.members[i], [i, this.members]) - - return this - } - // Restore to defaults - , clear: function() { - /* initialize store */ - this.members = [] - - return this - } - // Checks if a given element is present in set - , has: function(element) { - return this.index(element) >= 0 - } - // retuns index of given element in set - , index: function(element) { - return this.members.indexOf(element) - } - // Get member at given index - , get: function(i) { - return this.members[i] - } - // Get first member - , first: function() { - return this.get(0) - } - // Get last member - , last: function() { - return this.get(this.members.length - 1) - } - // Default value - , valueOf: function() { - return this.members - } - // Get the bounding box of all members included or empty box if set has no items - , bbox: function(){ - var box = new SVG.BBox() - - /* return an empty box of there are no members */ - if (this.members.length == 0) - return box - - /* get the first rbox and update the target bbox */ - var rbox = this.members[0].rbox() - box.x = rbox.x - box.y = rbox.y - box.width = rbox.width - box.height = rbox.height - - this.each(function() { - /* user rbox for correct position and visual representation */ - box = box.merge(this.rbox()) - }) - - return box - } + // Add parent method +, construct: { + // Create a new set + set: function(members) { + return new SVG.Set(members) } - - // Add parent method - , construct: { - // Create a new set - set: function() { - return new SVG.Set - } + } +}) + +SVG.FX.Set = SVG.invent({ + // Initialize node + create: function(set) { + // store reference to set + this.set = set + } + +}) + +// Alias methods +SVG.Set.inherit = function() { + var m + , methods = [] + + // gather shape methods + for(var m in SVG.Shape.prototype) + if (typeof SVG.Shape.prototype[m] == 'function' && typeof SVG.Set.prototype[m] != 'function') + methods.push(m) + + // apply shape aliasses + methods.forEach(function(method) { + SVG.Set.prototype[method] = function() { + for (var i = 0, il = this.members.length; i < il; i++) + if (this.members[i] && typeof this.members[i][method] == 'function') + this.members[i][method].apply(this.members[i], arguments) + + return method == 'animate' ? (this.fx || (this.fx = new SVG.FX.Set(this))) : this } }) - - SVG.SetFX = SVG.invent({ - // Initialize node - create: function(set) { - /* store reference to set */ - this.set = set + + // clear methods for the next round + methods = [] + + // gather fx methods + for(var m in SVG.FX.prototype) + if (typeof SVG.FX.prototype[m] == 'function' && typeof SVG.FX.Set.prototype[m] != 'function') + methods.push(m) + + // apply fx aliasses + methods.forEach(function(method) { + SVG.FX.Set.prototype[method] = function() { + for (var i = 0, il = this.set.members.length; i < il; i++) + this.set.members[i].fx[method].apply(this.set.members[i].fx, arguments) + + return this } - }) - - // Alias methods - SVG.Set.inherit = function() { - var m - , methods = [] - - /* gather shape methods */ - for(var m in SVG.Shape.prototype) - if (typeof SVG.Shape.prototype[m] == 'function' && typeof SVG.Set.prototype[m] != 'function') - methods.push(m) - - /* apply shape aliasses */ - methods.forEach(function(method) { - SVG.Set.prototype[method] = function() { - for (var i = 0, il = this.members.length; i < il; i++) - if (this.members[i] && typeof this.members[i][method] == 'function') - this.members[i][method].apply(this.members[i], arguments) - - return method == 'animate' ? (this.fx || (this.fx = new SVG.SetFX(this))) : this - } - }) - - /* clear methods for the next round */ - methods = [] - - /* gather fx methods */ - for(var m in SVG.FX.prototype) - if (typeof SVG.FX.prototype[m] == 'function' && typeof SVG.SetFX.prototype[m] != 'function') - methods.push(m) - - /* apply fx aliasses */ - methods.forEach(function(method) { - SVG.SetFX.prototype[method] = function() { - for (var i = 0, il = this.set.members.length; i < il; i++) - this.set.members[i].fx[method].apply(this.set.members[i].fx, arguments) - - return this - } - }) - } - - +} - SVG.extend(SVG.Element, { - // Store data values on svg nodes - data: function(a, v, r) { - if (typeof a == 'object') { - for (v in a) - this.data(v, a[v]) - - } else if (arguments.length < 2) { - try { - return JSON.parse(this.attr('data-' + a)) - } catch(e) { - return this.attr('data-' + a) - } - - } else { - this.attr( - 'data-' + a - , v === null ? - null : - r === true || typeof v === 'string' || typeof v === 'number' ? - v : - JSON.stringify(v) - ) + +// +SVG.extend(SVG.Element, { + // Store data values on svg nodes + data: function(a, v, r) { + if (typeof a == 'object') { + for (v in a) + this.data(v, a[v]) + + } else if (arguments.length < 2) { + try { + return JSON.parse(this.attr('data-' + a)) + } catch(e) { + return this.attr('data-' + a) } - return this + } else { + this.attr( + 'data-' + a + , v === null ? + null : + r === true || typeof v === 'string' || typeof v === 'number' ? + v : + JSON.stringify(v) + ) } - }) + + return this + } +}) +SVG.extend(SVG.Element, { + // Remember arbitrary data + remember: function(k, v) { + /* remember every item in an object individually */ + if (typeof arguments[0] == 'object') + for (var v in k) + this.remember(v, k[v]) - SVG.extend(SVG.Element, { - // Remember arbitrary data - remember: function(k, v) { - /* remember every item in an object individually */ - if (typeof arguments[0] == 'object') - for (var v in k) - this.remember(v, k[v]) - - /* retrieve memory */ - else if (arguments.length == 1) - return this.memory()[k] - - /* store memory */ - else - this.memory()[k] = v - - return this - } - - // Erase a given memory - , forget: function() { - if (arguments.length == 0) - this._memory = {} - else - for (var i = arguments.length - 1; i >= 0; i--) - delete this.memory()[arguments[i]] - - return this - } - - // Initialize or return local memory object - , memory: function() { - return this._memory || (this._memory = {}) - } - - }) + /* retrieve memory */ + else if (arguments.length == 1) + return this.memory()[k] - function camelCase(s) { - return s.toLowerCase().replace(/-(.)/g, function(m, g) { - return g.toUpperCase() - }) + /* store memory */ + else + this.memory()[k] = v + + return this } - - // Ensure to six-based hex - function fullHex(hex) { - return hex.length == 4 ? - [ '#', - hex.substring(1, 2), hex.substring(1, 2) - , hex.substring(2, 3), hex.substring(2, 3) - , hex.substring(3, 4), hex.substring(3, 4) - ].join('') : hex + + // Erase a given memory +, forget: function() { + if (arguments.length == 0) + this._memory = {} + else + for (var i = arguments.length - 1; i >= 0; i--) + delete this.memory()[arguments[i]] + + return this } - - // Component to hex value - function compToHex(comp) { - var hex = comp.toString(16) - return hex.length == 1 ? '0' + hex : hex + + // Initialize or return local memory object +, memory: function() { + return this._memory || (this._memory = {}) } - - // Calculate proportional width and height values when necessary - function proportionalSize(box, width, height) { - if (width == null || height == null) { - if (height == null) - height = box.height / box.width * width - else if (width == null) - width = box.width / box.height * height - } - - return { - width: width - , height: height - } + +}) +// Method for getting an element by id +SVG.get = function(id) { + var node = document.getElementById(idFromReference(id) || id) + if (node) return SVG.adopt(node) +} + +// Select elements by query string +SVG.select = function(query, parent) { + return new SVG.Set( + SVG.utils.map((parent || document).querySelectorAll(query), function(node) { + return SVG.adopt(node) + }) + ) +} + +SVG.extend(SVG.Parent, { + // Scoped select method + select: function(query) { + return SVG.select(query, this.node) } - - // Calculate position according to from and to - function at(o, pos) { - /* number recalculation (don't bother converting to SVG.Number for performance reasons) */ - return typeof o.from == 'number' ? - o.from + (o.to - o.from) * pos : - - /* instance recalculation */ - o instanceof SVG.Color || o instanceof SVG.Number ? o.at(pos) : - - /* for all other values wait until pos has reached 1 to return the final value */ - pos < 1 ? o.from : o.to + +}) +// Convert dash-separated-string to camelCase +function camelCase(s) { + return s.toLowerCase().replace(/-(.)/g, function(m, g) { + return g.toUpperCase() + }) +} + +// Capitalize first letter of a string +function capitalize(s) { + return s.charAt(0).toUpperCase() + s.slice(1) +} + +// Ensure to six-based hex +function fullHex(hex) { + return hex.length == 4 ? + [ '#', + hex.substring(1, 2), hex.substring(1, 2) + , hex.substring(2, 3), hex.substring(2, 3) + , hex.substring(3, 4), hex.substring(3, 4) + ].join('') : hex +} + +// Component to hex value +function compToHex(comp) { + var hex = comp.toString(16) + return hex.length == 1 ? '0' + hex : hex +} + +// Calculate proportional width and height values when necessary +function proportionalSize(box, width, height) { + if (height == null) + height = box.height / box.width * width + else if (width == null) + width = box.width / box.height * height + + return { + width: width + , height: height } +} + +// Delta transform point +function deltaTransformPoint(matrix, x, y) { + return { + x: x * matrix.a + y * matrix.c + 0 + , y: x * matrix.b + y * matrix.d + 0 + } +} + +// Map matrix array to object +function arrayToMatrix(a) { + return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] } +} + +// Parse matrix if required +function parseMatrix(matrix) { + if (!(matrix instanceof SVG.Matrix)) + matrix = new SVG.Matrix(matrix) - // PathArray Helpers - function arrayToString(a) { - for (var i = 0, il = a.length, s = ''; i < il; i++) { - s += a[i][0] - - if (a[i][1] != null) { - s += a[i][1] - - if (a[i][2] != null) { + return matrix +} + +// Add centre point to transform object +function ensureCentre(o, target) { + o.cx = o.cx == null ? target.bbox().cx : o.cx + o.cy = o.cy == null ? target.bbox().cy : o.cy +} + +// Convert string to matrix +function stringToMatrix(source) { + // remove matrix wrapper and split to individual numbers + source = source + .replace(SVG.regex.whitespace, '') + .replace(SVG.regex.matrix, '') + .split(',') + + // convert string values to floats and convert to a matrix-formatted object + return arrayToMatrix( + SVG.utils.map(source, function(n) { + return parseFloat(n) + }) + ) +} + +// Calculate position according to from and to +function at(o, pos) { + // number recalculation (don't bother converting to SVG.Number for performance reasons) + return typeof o.from == 'number' ? + o.from + (o.to - o.from) * pos : + + // instance recalculation + o instanceof SVG.Color || o instanceof SVG.Number || o instanceof SVG.Matrix ? o.at(pos) : + + // for all other values wait until pos has reached 1 to return the final value + pos < 1 ? o.from : o.to +} + +// PathArray Helpers +function arrayToString(a) { + for (var i = 0, il = a.length, s = ''; i < il; i++) { + s += a[i][0] + + if (a[i][1] != null) { + s += a[i][1] + + if (a[i][2] != null) { + s += ' ' + s += a[i][2] + + if (a[i][3] != null) { s += ' ' - s += a[i][2] - - if (a[i][3] != null) { + s += a[i][3] + s += ' ' + s += a[i][4] + + if (a[i][5] != null) { s += ' ' - s += a[i][3] + s += a[i][5] s += ' ' - s += a[i][4] - - if (a[i][5] != null) { - s += ' ' - s += a[i][5] + s += a[i][6] + + if (a[i][7] != null) { s += ' ' - s += a[i][6] - - if (a[i][7] != null) { - s += ' ' - s += a[i][7] - } + s += a[i][7] } } } } } - - return s + ' ' - } - - // Add more bounding box properties - function boxProperties(b) { - b.x2 = b.x + b.width - b.y2 = b.y + b.height - b.cx = b.x + b.width / 2 - b.cy = b.y + b.height / 2 - } - - // Parse a matrix string - function parseMatrix(o) { - if (o.matrix) { - /* split matrix string */ - var m = o.matrix.replace(/\s/g, '').split(',') - - /* pasrse values */ - if (m.length == 6) { - o.a = parseFloat(m[0]) - o.b = parseFloat(m[1]) - o.c = parseFloat(m[2]) - o.d = parseFloat(m[3]) - o.e = parseFloat(m[4]) - o.f = parseFloat(m[5]) - } - } - - return o } - // Get id from reference string - function idFromReference(url) { - var m = url.toString().match(SVG.regex.reference) - - if (m) return m[1] + return s + ' ' +} + +// Deep new id assignment +function assignNewId(node) { + // do the same for SVG child nodes as well + for (var i = node.childNodes.length - 1; i >= 0; i--) + if (node.childNodes[i] instanceof SVGElement) + assignNewId(node.childNodes[i]) + + return SVG.adopt(node).id(SVG.eid(node.nodeName)) +} + +// Add more bounding box properties +function fullBox(b) { + if (b.x == null) { + b.x = 0 + b.y = 0 + b.width = 0 + b.height = 0 + } + + b.w = b.width + b.h = b.height + b.x2 = b.x + b.width + b.y2 = b.y + b.height + b.cx = b.x + b.width / 2 + b.cy = b.y + b.height / 2 + + return b +} + +// Get id from reference string +function idFromReference(url) { + var m = url.toString().match(SVG.regex.reference) + + if (m) return m[1] +} + +// Create matrix array for looping +var abcdef = 'abcdef'.split('') +// Add CustomEvent to IE9 and IE10 +if (typeof CustomEvent !== 'function') { + // Code from: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent + var CustomEvent = function(event, options) { + options = options || { bubbles: false, cancelable: false, detail: undefined } + var e = document.createEvent('CustomEvent') + e.initCustomEvent(event, options.bubbles, options.cancelable, options.detail) + return e } + CustomEvent.prototype = window.Event.prototype + + window.CustomEvent = CustomEvent +} + +// requestAnimationFrame / cancelAnimationFrame Polyfill with fallback based on Paul Irish +(function(w) { + var lastTime = 0 + var vendors = ['moz', 'webkit'] + + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + w.requestAnimationFrame = w[vendors[x] + 'RequestAnimationFrame'] + w.cancelAnimationFrame = w[vendors[x] + 'CancelAnimationFrame'] || + w[vendors[x] + 'CancelRequestAnimationFrame'] + } + + w.requestAnimationFrame = w.requestAnimationFrame || + function(callback) { + var currTime = new Date().getTime() + var timeToCall = Math.max(0, 16 - (currTime - lastTime)) + + var id = w.setTimeout(function() { + callback(currTime + timeToCall) + }, timeToCall) + + lastTime = currTime + timeToCall + return id + } + + w.cancelAnimationFrame = w.cancelAnimationFrame || w.clearTimeout; + +}(window)) +return SVG; - return SVG })); diff --git a/dist/svg.min.js b/dist/svg.min.js index 0f22d69..ff1a2e1 100644..100755 --- a/dist/svg.min.js +++ b/dist/svg.min.js @@ -1,2 +1,2 @@ -!function(t,e){"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=e():t.SVG=e()}(this,function(){function t(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function e(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 i(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function n(t,e,i){return(null==e||null==i)&&(null==i?i=t.height/t.width*e:null==e&&(e=t.width/t.height*i)),{width:e,height:i}}function r(t,e){return"number"==typeof t.from?t.from+(t.to-t.from)*e:t instanceof u.Color||t instanceof u.Number?t.at(e):1>e?t.from:t.to}function s(t){for(var e=0,i=t.length,n="";i>e;e++)n+=t[e][0],null!=t[e][1]&&(n+=t[e][1],null!=t[e][2]&&(n+=" ",n+=t[e][2],null!=t[e][3]&&(n+=" ",n+=t[e][3],n+=" ",n+=t[e][4],null!=t[e][5]&&(n+=" ",n+=t[e][5],n+=" ",n+=t[e][6],null!=t[e][7]&&(n+=" ",n+=t[e][7])))));return n+" "}function h(t){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}function o(t){if(t.matrix){var e=t.matrix.replace(/\s/g,"").split(",");6==e.length&&(t.a=parseFloat(e[0]),t.b=parseFloat(e[1]),t.c=parseFloat(e[2]),t.d=parseFloat(e[3]),t.e=parseFloat(e[4]),t.f=parseFloat(e[5]))}return t}function a(t){var e=t.toString().match(u.regex.reference);return e?e[1]:void 0}var u=this.SVG=function(t){return u.supported?(t=new u.Doc(t),u.parser||u.prepare(t),t):void 0};if(u.ns="http://www.w3.org/2000/svg",u.xmlns="http://www.w3.org/2000/xmlns/",u.xlink="http://www.w3.org/1999/xlink",u.did=1e3,u.eid=function(t){return"Svgjs"+t.charAt(0).toUpperCase()+t.slice(1)+u.did++},u.create=function(t){var e=document.createElementNS(this.ns,t);return e.setAttribute("id",this.eid(t)),e},u.extend=function(){var t,e,i,n;for(t=[].slice.call(arguments),e=t.pop(),n=t.length-1;n>=0;n--)if(t[n])for(i in e)t[n].prototype[i]=e[i];u.Set&&u.Set.inherit&&u.Set.inherit()},u.prepare=function(t){var e=document.getElementsByTagName("body")[0],i=(e?new u.Doc(e):t.nested()).size(2,0),n=u.create("path");i.node.appendChild(n),u.parser={body:e||t.parent,draw:i.style("opacity:0;position:fixed;left:100%;top:100%;overflow:hidden"),poly:i.polyline().node,path:n}},u.supported=function(){return!!document.createElementNS&&!!document.createElementNS(u.ns,"svg").createSVGRect}(),!u.supported)return!1;if(u.get=function(t){var e=document.getElementById(a(t)||t);return e?e.instance:void 0},u.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,u.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&u.extend(e,t.extend),t.construct&&u.extend(t.parent||u.Container,t.construct),e},"function"!=typeof l){var l=function(t,e){e=e||{bubbles:!1,cancelable:!1,detail:void 0};var i=document.createEvent("CustomEvent");return i.initCustomEvent(t,e.bubbles,e.cancelable,e.detail),i};l.prototype=window.Event.prototype,window.CustomEvent=l}!function(t){for(var e=0,i=["moz","webkit"],n=0;n<i.length&&!window.requestAnimationFrame;++n)t.requestAnimationFrame=t[i[n]+"RequestAnimationFrame"],t.cancelAnimationFrame=t[i[n]+"CancelAnimationFrame"]||t[i[n]+"CancelRequestAnimationFrame"];t.requestAnimationFrame=t.requestAnimationFrame||function(i){var n=(new Date).getTime(),r=Math.max(0,16-(n-e)),s=t.setTimeout(function(){i(n+r)},r);return e=n+r,s},t.cancelAnimationFrame=t.cancelAnimationFrame||t.clearTimeout}(window),u.regex={unit:/^(-?[\d\.]+)([a-z%]{0,2})$/,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^-?[\d\.]+$/,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif)(\?[^=]+.*)?/i,isEvent:/^[\w]+:[\w]+$/},u.defaults={matrix:"1 0 0 1 0 0",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"},trans:function(){return{x:0,y:0,scaleX:1,scaleY:1,rotation:0,skewX:0,skewY:0,matrix:this.matrix,a:1,b:0,c:0,d:1,e:0,f:0}}},u.Color=function(t){var i;this.r=0,this.g=0,this.b=0,"string"==typeof t?u.regex.isRgb.test(t)?(i=u.regex.rgb.exec(t.replace(/\s/g,"")),this.r=parseInt(i[1]),this.g=parseInt(i[2]),this.b=parseInt(i[3])):u.regex.isHex.test(t)&&(i=u.regex.hex.exec(e(t)),this.r=parseInt(i[1],16),this.g=parseInt(i[2],16),this.b=parseInt(i[3],16)):"object"==typeof t&&(this.r=t.r,this.g=t.g,this.b=t.b)},u.extend(u.Color,{toString:function(){return this.toHex()},toHex:function(){return"#"+i(this.r)+i(this.g)+i(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 u.Color(t),this},at:function(t){return this.destination?(t=0>t?0:t>1?1:t,new u.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}}),u.Color.test=function(t){return t+="",u.regex.isHex.test(t)||u.regex.isRgb.test(t)},u.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},u.Color.isColor=function(t){return u.Color.isRgb(t)||u.Color.test(t)},u.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},u.extend(u.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],i=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(i);for(;this.value.length<this.destination.length;)this.value.push(e)}return this},settle:function(){for(var t=0,e=this.value.length,i=[];e>t;t++)-1==i.indexOf(this.value[t])&&i.push(this.value[t]);return this.value=i},at:function(t){if(!this.destination)return this;for(var e=0,i=this.value.length,n=[];i>e;e++)n.push(this.value[e]+(this.destination[e]-this.value[e])*t);return new u.Array(n)},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.replace(/\s+/g," ").replace(/^\s+|\s+$/g,"").split(" ")},reverse:function(){return this.value.reverse(),this}}),u.PointArray=function(){this.constructor.apply(this,arguments)},u.PointArray.prototype=new u.Array,u.extend(u.PointArray,{toString:function(){for(var t=0,e=this.value.length,i=[];e>t;t++)i.push(this.value[t].join(","));return i.join(" ")},at:function(t){if(!this.destination)return this;for(var e=0,i=this.value.length,n=[];i>e;e++)n.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 u.PointArray(n)},parse:function(t){if(t=t.valueOf(),Array.isArray(t))return t;t=this.split(t);for(var e,i=0,n=t.length,r=[];n>i;i++)e=t[i].split(","),r.push([parseFloat(e[0]),parseFloat(e[1])]);return r},move:function(t,e){var i=this.bbox();if(t-=i.x,e-=i.y,!isNaN(t)&&!isNaN(e))for(var n=this.value.length-1;n>=0;n--)this.value[n]=[this.value[n][0]+t,this.value[n][1]+e];return this},size:function(t,e){var i,n=this.bbox();for(i=this.value.length-1;i>=0;i--)this.value[i][0]=(this.value[i][0]-n.x)*t/n.width+n.x,this.value[i][1]=(this.value[i][1]-n.y)*e/n.height+n.y;return this},bbox:function(){return u.parser.poly.setAttribute("points",this.toString()),u.parser.poly.getBBox()}}),u.PathArray=function(t,e){this.constructor.call(this,t,e)},u.PathArray.prototype=new u.Array,u.extend(u.PathArray,{toString:function(){return s(this.value)},move:function(t,e){var i=this.bbox();if(t-=i.x,e-=i.y,!isNaN(t)&&!isNaN(e))for(var n,r=this.value.length-1;r>=0;r--)n=this.value[r][0],"M"==n||"L"==n||"T"==n?(this.value[r][1]+=t,this.value[r][2]+=e):"H"==n?this.value[r][1]+=t:"V"==n?this.value[r][1]+=e:"C"==n||"S"==n||"Q"==n?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"==n&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"==n&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var i,n,r=this.bbox();for(i=this.value.length-1;i>=0;i--)n=this.value[i][0],"M"==n||"L"==n||"T"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y):"H"==n?this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x:"V"==n?this.value[i][1]=(this.value[i][1]-r.y)*e/r.height+r.y:"C"==n||"S"==n||"Q"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y,this.value[i][3]=(this.value[i][3]-r.x)*t/r.width+r.x,this.value[i][4]=(this.value[i][4]-r.y)*e/r.height+r.y,"C"==n&&(this.value[i][5]=(this.value[i][5]-r.x)*t/r.width+r.x,this.value[i][6]=(this.value[i][6]-r.y)*e/r.height+r.y)):"A"==n&&(this.value[i][1]=this.value[i][1]*t/r.width,this.value[i][2]=this.value[i][2]*e/r.height,this.value[i][6]=(this.value[i][6]-r.x)*t/r.width+r.x,this.value[i][7]=(this.value[i][7]-r.y)*e/r.height+r.y);return this},parse:function(t){if(t instanceof u.PathArray)return t.valueOf();var e,i,n,r,h,o,a,l,c,f,d,p=0,m=0;for(u.parser.path.setAttribute("d","string"==typeof t?t:s(t)),d=u.parser.path.pathSegList,e=0,i=d.numberOfItems;i>e;++e)f=d.getItem(e),c=f.pathSegTypeAsLetter,"M"==c||"L"==c||"H"==c||"V"==c||"C"==c||"S"==c||"Q"==c||"T"==c||"A"==c?("x"in f&&(p=f.x),"y"in f&&(m=f.y)):("x1"in f&&(h=p+f.x1),"x2"in f&&(a=p+f.x2),"y1"in f&&(o=m+f.y1),"y2"in f&&(l=m+f.y2),"x"in f&&(p+=f.x),"y"in f&&(m+=f.y),"m"==c?d.replaceItem(u.parser.path.createSVGPathSegMovetoAbs(p,m),e):"l"==c?d.replaceItem(u.parser.path.createSVGPathSegLinetoAbs(p,m),e):"h"==c?d.replaceItem(u.parser.path.createSVGPathSegLinetoHorizontalAbs(p),e):"v"==c?d.replaceItem(u.parser.path.createSVGPathSegLinetoVerticalAbs(m),e):"c"==c?d.replaceItem(u.parser.path.createSVGPathSegCurvetoCubicAbs(p,m,h,o,a,l),e):"s"==c?d.replaceItem(u.parser.path.createSVGPathSegCurvetoCubicSmoothAbs(p,m,a,l),e):"q"==c?d.replaceItem(u.parser.path.createSVGPathSegCurvetoQuadraticAbs(p,m,h,o),e):"t"==c?d.replaceItem(u.parser.path.createSVGPathSegCurvetoQuadraticSmoothAbs(p,m),e):"a"==c?d.replaceItem(u.parser.path.createSVGPathSegArcAbs(p,m,f.r1,f.r2,f.angle,f.largeArcFlag,f.sweepFlag),e):("z"==c||"Z"==c)&&(p=n,m=r)),("M"==c||"m"==c)&&(n=p,r=m);for(t=[],d=u.parser.path.pathSegList,e=0,i=d.numberOfItems;i>e;++e)f=d.getItem(e),c=f.pathSegTypeAsLetter,p=[c],"M"==c||"L"==c||"T"==c?p.push(f.x,f.y):"H"==c?p.push(f.x):"V"==c?p.push(f.y):"C"==c?p.push(f.x1,f.y1,f.x2,f.y2,f.x,f.y):"S"==c?p.push(f.x2,f.y2,f.x,f.y):"Q"==c?p.push(f.x1,f.y1,f.x,f.y):"A"==c&&p.push(f.r1,f.r2,f.angle,0|f.largeArcFlag,0|f.sweepFlag,f.x,f.y),t.push(p);return t},bbox:function(){return u.parser.path.setAttribute("d",this.toString()),u.parser.path.getBBox()}}),u.Number=function(t){if(this.value=0,this.unit="","number"==typeof t)this.value=isNaN(t)?0:isFinite(t)?t:0>t?-3.4e38:3.4e38;else if("string"==typeof t){var e=t.match(u.regex.unit);e&&(this.value=parseFloat(e[1]),"%"==e[2]?this.value/=100:"s"==e[2]&&(this.value*=1e3),this.unit=e[2])}else t instanceof u.Number&&(this.value=t.value,this.unit=t.unit)},u.extend(u.Number,{toString:function(){return("%"==this.unit?~~(1e8*this.value)/1e6:"s"==this.unit?this.value/1e3:this.value)+this.unit},valueOf:function(){return this.value},plus:function(t){return this.value=this+new u.Number(t),this},minus:function(t){return this.plus(-new u.Number(t))},times:function(t){return this.value=this*new u.Number(t),this},divide:function(t){return this.value=this/new u.Number(t),this},to:function(t){return"string"==typeof t&&(this.unit=t),this},morph:function(t){return this.destination=new u.Number(t),this},at:function(t){return this.destination?new u.Number(this.destination).minus(this).times(t).plus(this):this}}),u.ViewBox=function(t){var e,i,n,r,s=1,h=1,o=t.bbox(),a=(t.attr("viewBox")||"").match(/-?[\d\.]+/g),l=t,c=t;for(n=new u.Number(t.width()),r=new u.Number(t.height());"%"==n.unit;)s*=n.value,n=new u.Number(l instanceof u.Doc?l.parent.offsetWidth:l.parent.width()),l=l.parent;for(;"%"==r.unit;)h*=r.value,r=new u.Number(c instanceof u.Doc?c.parent.offsetHeight:c.parent.height()),c=c.parent;this.x=o.x,this.y=o.y,this.width=n*s,this.height=r*h,this.zoom=1,a&&(e=parseFloat(a[0]),i=parseFloat(a[1]),n=parseFloat(a[2]),r=parseFloat(a[3]),this.zoom=this.width/this.height>n/r?this.height/r:this.width/n,this.x=e,this.y=i,this.width=n,this.height=r)},u.extend(u.ViewBox,{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height}}),u.BBox=function(t){var e;if(this.x=0,this.y=0,this.width=0,this.height=0,t){try{e=t.node.getBBox()}catch(i){e={x:t.node.clientLeft,y:t.node.clientTop,width:t.node.clientWidth,height:t.node.clientHeight}}this.x=e.x+t.trans.x,this.y=e.y+t.trans.y,this.width=e.width*t.trans.scaleX,this.height=e.height*t.trans.scaleY}h(this)},u.extend(u.BBox,{merge:function(t){var e=new u.BBox;return e.x=Math.min(this.x,t.x),e.y=Math.min(this.y,t.y),e.width=Math.max(this.x+this.width,t.x+t.width)-e.x,e.height=Math.max(this.y+this.height,t.y+t.height)-e.y,h(e),e}}),u.RBox=function(t){var e,i,n={};if(this.x=0,this.y=0,this.width=0,this.height=0,t){for(e=t.doc().parent,i=t.doc().viewbox().zoom,n=t.node.getBoundingClientRect(),this.x=n.left,this.y=n.top,this.x-=e.offsetLeft,this.y-=e.offsetTop;e=e.offsetParent;)this.x-=e.offsetLeft,this.y-=e.offsetTop;for(e=t;e=e.parent;)"svg"==e.type&&e.viewbox&&(i*=e.viewbox().zoom,this.x-=e.x()||0,this.y-=e.y()||0)}this.x/=i,this.y/=i,this.width=n.width/=i,this.height=n.height/=i,this.x+="number"==typeof window.scrollX?window.scrollX:window.pageXOffset,this.y+="number"==typeof window.scrollY?window.scrollY:window.pageYOffset,h(this)},u.extend(u.RBox,{merge:function(t){var e=new u.RBox;return e.x=Math.min(this.x,t.x),e.y=Math.min(this.y,t.y),e.width=Math.max(this.x+this.width,t.x+t.width)-e.x,e.height=Math.max(this.y+this.height,t.y+t.height)-e.y,h(e),e}}),u.Element=u.invent({create:function(t){this._stroke=u.defaults.attrs.stroke,this.trans=u.defaults.trans(),(this.node=t)&&(this.type=t.nodeName,this.node.instance=this)},extend:{x:function(t){return null!=t&&(t=new u.Number(t),t.value/=this.trans.scaleX),this.attr("x",t)},y:function(t){return null!=t&&(t=new u.Number(t),t.value/=this.trans.scaleY),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 i=n(this.bbox(),t,e);return this.width(new u.Number(i.width)).height(new u.Number(i.height))},clone:function(){var t,e,i=this.type;return t="rect"==i||"ellipse"==i?this.parent[i](0,0):"line"==i?this.parent[i](0,0,0,0):"image"==i?this.parent[i](this.src):"text"==i?this.parent[i](this.content):"path"==i?this.parent[i](this.attr("d")):"polyline"==i||"polygon"==i?this.parent[i](this.attr("points")):"g"==i?this.parent.group():this.parent[i](),e=this.attr(),delete e.id,t.attr(e),t.trans=this.trans,t.transform({})},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)},doc:function(t){return this._parent(t||u.Doc)},attr:function(t,e,i){if(null==t){for(t={},e=this.node.attributes,i=e.length-1;i>=0;i--)t[e[i].nodeName]=u.regex.isNumber.test(e[i].nodeValue)?parseFloat(e[i].nodeValue):e[i].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.attributes[t],null==e?u.defaults.attrs[t]:u.regex.isNumber.test(e.nodeValue)?parseFloat(e.nodeValue):e.nodeValue;if("style"==t)return this.style(e);"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),("fill"==t||"stroke"==t)&&(u.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof u.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new u.Number(e):u.Color.isColor(e)?e=new u.Color(e):Array.isArray(e)&&(e=new u.Array(e)),"leading"==t?this.leading&&this.leading(e):"string"==typeof i?this.node.setAttributeNS(i,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this},transform:function(t,e){if(0==arguments.length)return this.trans;if("string"==typeof t){if(arguments.length<2)return this.trans[t];var i={};return i[t]=e,this.transform(i)}var i=[];t=o(t);for(e in t)null!=t[e]&&(this.trans[e]=t[e]);return this.trans.matrix=this.trans.a+" "+this.trans.b+" "+this.trans.c+" "+this.trans.d+" "+this.trans.e+" "+this.trans.f,t=this.trans,t.matrix!=u.defaults.matrix&&i.push("matrix("+t.matrix+")"),0!=t.rotation&&i.push("rotate("+t.rotation+" "+(null==t.cx?this.bbox().cx:t.cx)+" "+(null==t.cy?this.bbox().cy:t.cy)+")"),(1!=t.scaleX||1!=t.scaleY)&&i.push("scale("+t.scaleX+" "+t.scaleY+")"),0!=t.skewX&&i.push("skewX("+t.skewX+")"),0!=t.skewY&&i.push("skewY("+t.skewY+")"),(0!=t.x||0!=t.y)&&i.push("translate("+new u.Number(t.x/t.scaleX)+" "+new u.Number(t.y/t.scaleY)+")"),0==i.length?this.node.removeAttribute("transform"):this.node.setAttribute("transform",i.join(" ")),this},style:function(e,i){if(0==arguments.length)return this.node.style.cssText||"";if(arguments.length<2)if("object"==typeof e)for(i in e)this.style(i,e[i]);else{if(!u.regex.isCss.test(e))return this.node.style[t(e)];e=e.split(";");for(var n=0;n<e.length;n++)i=e[n].split(":"),this.style(i[0].replace(/\s+/g,""),i[1])}else this.node.style[t(e)]=null===i||u.regex.isBlank.test(i)?"":i;return this},id:function(t){return this.attr("id",t)},bbox:function(){return new u.BBox(this)},rbox:function(){return new u.RBox(this)},inside:function(t,e){var i=this.bbox();return t>i.x&&e>i.y&&t<i.x+i.width&&e<i.y+i.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.node.getAttribute("class");return null===t?[]:t.trim().split(/\s+/)},hasClass:function(t){return-1!=this.classes().indexOf(t)},addClass:function(t){var e;return this.hasClass(t)||(e=this.classes(),e.push(t),this.node.setAttribute("class",e.join(" "))),this},removeClass:function(t){var e;return this.hasClass(t)&&(e=this.classes().filter(function(e){return e!=t}),this.node.setAttribute("class",e.join(" "))),this},toggleClass:function(t){return this.hasClass(t)?this.removeClass(t):this.addClass(t),this},reference:function(t){return u.get(this.attr()[t])},_parent:function(t){for(var e=this;null!=e&&!(e instanceof t);)e=e.parent;return e}}}),u.Parent=u.invent({create:function(t){this.constructor.call(this,t)},inherit:u.Element,extend:{children:function(){return this._children||(this._children=[])},add:function(t,e){return this.has(t)||(e=null==e?this.children().length:e,t.parent&&t.parent.children().splice(t.parent.index(t),1),this.children().splice(e,0,t),this.node.insertBefore(t.node,this.node.childNodes[e]||null),t.parent=this),this._defs&&(this.node.removeChild(this._defs.node),this.node.appendChild(this._defs.node)),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 i,n,r=this.children();for(i=0,n=r.length;n>i;i++)r[i]instanceof u.Element&&t.apply(r[i],[i,r]),e&&r[i]instanceof u.Container&&r[i].each(t,e);return this},removeElement:function(t){return this.children().splice(this.index(t),1),this.node.removeChild(t.node),t.parent=null,this},clear:function(){for(var t=this.children().length-1;t>=0;t--)this.removeElement(this.children()[t]);return this._defs&&this._defs.clear(),this},defs:function(){return this.doc().defs()}}}),u.Container=u.invent({create:function(t){this.constructor.call(this,t)},inherit:u.Parent,extend:{viewbox:function(t){return 0==arguments.length?new u.ViewBox(this):(t=1==arguments.length?[t.x,t.y,t.width,t.height]:[].slice.call(arguments),this.attr("viewBox",t))}}}),u.FX=u.invent({create:function(t){this.target=t},extend:{animate:function(t,e,i){var n,s,h,o,a=this.target,l=this;return"object"==typeof t&&(i=t.delay,e=t.ease,t=t.duration),t="="==t?t:null==t?1e3:new u.Number(t).valueOf(),e=e||"<>",l.to=function(t){var i;if(t=0>t?0:t>1?1:t,null==n){n=[];for(o in l.attrs)n.push(o);if(a.morphArray&&(l._plot||n.indexOf("points")>-1)){var u,c=new a.morphArray(l._plot||l.attrs.points||a.array);l._size&&c.size(l._size.width.to,l._size.height.to),u=c.bbox(),l._x?c.move(l._x.to,u.y):l._cx&&c.move(l._cx.to-u.width/2,u.y),u=c.bbox(),l._y?c.move(u.x,l._y.to):l._cy&&c.move(u.x,l._cy.to-u.height/2),delete l._x,delete l._y,delete l._cx,delete l._cy,delete l._size,l._plot=a.array.morph(c)}}if(null==s){s=[];for(o in l.trans)s.push(o)}if(null==h){h=[];for(o in l.styles)h.push(o)}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,l._plot?a.plot(l._plot.at(t)):(l._x?a.x(l._x.at(t)):l._cx&&a.cx(l._cx.at(t)),l._y?a.y(l._y.at(t)):l._cy&&a.cy(l._cy.at(t)),l._size&&a.size(l._size.width.at(t),l._size.height.at(t))),l._viewbox&&a.viewbox(l._viewbox.x.at(t),l._viewbox.y.at(t),l._viewbox.width.at(t),l._viewbox.height.at(t)),l._leading&&a.leading(l._leading.at(t)),i=n.length-1;i>=0;i--)a.attr(n[i],r(l.attrs[n[i]],t));for(i=s.length-1;i>=0;i--)a.transform(s[i],r(l.trans[s[i]],t));for(i=h.length-1;i>=0;i--)a.style(h[i],r(l.styles[h[i]],t));l._during&&l._during.call(a,t,function(e,i){return r({from:e,to:i},t)})},"number"==typeof t&&(this.timeout=setTimeout(function(){var n=(new Date).getTime();l.situation={interval:1e3/60,start:n,play:!0,finish:n+t,duration:t},l.render=function(){if(l.situation.play===!0){var n=(new Date).getTime(),r=n>l.situation.finish?1:(n-l.situation.start)/t;l.to(r),n>l.situation.finish?(l._plot&&a.plot(new u.PointArray(l._plot.destination).settle()),l._loop===!0||"number"==typeof l._loop&&l._loop>1?("number"==typeof l._loop&&--l._loop,l.animate(t,e,i)):l._after?l._after.apply(a,[l]):l.stop()):l.animationFrame=requestAnimationFrame(l.render)}else l.animationFrame=requestAnimationFrame(l.render)},l.render()},new u.Number(i).valueOf())),this},bbox:function(){return this.target.bbox()},attr:function(t,e){if("object"==typeof t)for(var i in t)this.attr(i,t[i]);else{var n=this.target.attr(t);this.attrs[t]=u.Color.isColor(n)?new u.Color(n).morph(e):u.regex.unit.test(n)?new u.Number(n).morph(e):{from:n,to:e}}return this},transform:function(t,e){if(1==arguments.length){t=o(t),delete t.matrix,this.target.trans.cx=t.cx||null,this.target.trans.cy=t.cy||null,delete t.cx,delete t.cy;for(e in t)this.trans[e]={from:this.target.trans[e],to:t[e]}}else{var i={};i[t]=e,this.transform(i)}return this},style:function(t,e){if("object"==typeof t)for(var i in t)this.style(i,t[i]);else this.styles[t]={from:this.target.style(t),to:e};return this},x:function(t){return this._x=new u.Number(this.target.x()).morph(t),this},y:function(t){return this._y=new u.Number(this.target.y()).morph(t),this},cx:function(t){return this._cx=new u.Number(this.target.cx()).morph(t),this},cy:function(t){return this._cy=new u.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 u.Text)this.attr("font-size",t);else{var i=this.target.bbox();this._size={width:new u.Number(i.width).morph(t),height:new u.Number(i.height).morph(e)}}return this},plot:function(t){return this._plot=t,this},leading:function(t){return this.target._leading&&(this._leading=new u.Number(this.target._leading).morph(t)),this},viewbox:function(t,e,i,n){if(this.target instanceof u.Container){var r=this.target.viewbox();this._viewbox={x:new u.Number(r.x).morph(t),y:new u.Number(r.y).morph(e),width:new u.Number(r.width).morph(i),height:new u.Number(r.height).morph(n)}}return this},update:function(t){return this.target instanceof u.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 u.Number(t.offset))),this},during:function(t){return this._during=t,this},after:function(t){return this._after=t,this},loop:function(t){return this._loop=t||!0,this},stop:function(t){return t===!0?(this.animate(0),this._after&&this._after.apply(this.target,[this])):(clearTimeout(this.timeout),cancelAnimationFrame(this.animationFrame),this.attrs={},this.trans={},this.styles={},this.situation={},delete this._x,delete this._y,delete this._cx,delete this._cy,delete this._size,delete this._plot,delete this._loop,delete this._after,delete this._during,delete this._leading,delete this._viewbox),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:u.Element,construct:{animate:function(t,e,i){return(this.fx||(this.fx=new u.FX(this))).stop().animate(t,e,i)},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}}}),u.extend(u.Element,u.FX,{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)}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){u.Element.prototype[t]=function(e){var i=this;return this.node["on"+t]="function"==typeof e?function(){return e.apply(i,arguments)}:null,this}}),u.listeners=[],u.handlerMap=[],u.registerEvent=function(){},u.on=function(t,e,i){var n=i.bind(t.instance||t),r=(u.handlerMap.indexOf(t)+1||u.handlerMap.push(t))-1,s=e.split(".")[0],h=e.split(".")[1]||"*";u.listeners[r]=u.listeners[r]||{},u.listeners[r][s]=u.listeners[r][s]||{},u.listeners[r][s][h]=u.listeners[r][s][h]||{},u.listeners[r][s][h][i]=n,t.addEventListener(s,n,!1)},u.off=function(t,e,i){var n=u.handlerMap.indexOf(t),r=e&&e.split(".")[0],s=e&&e.split(".")[1];if(-1!=n)if(i)u.listeners[n][r]&&u.listeners[n][r][s||"*"]&&(t.removeEventListener(r,u.listeners[n][r][s||"*"][i],!1),delete u.listeners[n][r][s||"*"][i]);else if(s){if(u.listeners[n][r]&&u.listeners[n][r][s]){for(i in u.listeners[n][r][s])u.off(t,[r,s].join("."),i);delete u.listeners[n][r][s]}}else if(r){if(u.listeners[n][r]){for(namespace in u.listeners[n][r])u.off(t,[r,namespace].join("."));delete u.listeners[n][r]}}else{for(e in u.listeners[n])u.off(t,e);delete u.listeners[n]}},u.extend(u.Element,{on:function(t,e){return u.on(this.node,t,e),this},off:function(t,e){return u.off(this.node,t,e),this},fire:function(t,e){return this.node.dispatchEvent(new l(t,{detail:e})),this}}),u.Defs=u.invent({create:"defs",inherit:u.Container}),u.G=u.invent({create:"g",inherit:u.Container,extend:{x:function(t){return null==t?this.trans.x:this.transform("x",t)},y:function(t){return null==t?this.trans.y:this.transform("y",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)}},construct:{group:function(){return this.put(new u.G)}}}),u.extend(u.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();return this.parent.removeElement(this).put(this,t+1)},backward:function(){var t=this.position();return t>0&&this.parent.removeElement(this).add(this,t-1),this},front:function(){return this.parent.removeElement(this).put(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}}),u.Mask=u.invent({create:function(){this.constructor.call(this,u.create("mask")),this.targets=[]},inherit:u.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return delete this.targets,this.parent.removeElement(this),this}},construct:{mask:function(){return this.defs().put(new u.Mask)}}}),u.extend(u.Element,{maskWith:function(t){return this.masker=t instanceof u.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)}}),u.Clip=u.invent({create:function(){this.constructor.call(this,u.create("clipPath")),this.targets=[]},inherit:u.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return delete this.targets,this.parent.removeElement(this),this}},construct:{clip:function(){return this.defs().put(new u.Clip)}}}),u.extend(u.Element,{clipWith:function(t){return this.clipper=t instanceof u.Clip?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)}}),u.Gradient=u.invent({create:function(t){this.constructor.call(this,u.create(t+"Gradient")),this.type=t},inherit:u.Container,extend:{from:function(t,e){return this.attr("radial"==this.type?{fx:new u.Number(t),fy:new u.Number(e)}:{x1:new u.Number(t),y1:new u.Number(e)})},to:function(t,e){return this.attr("radial"==this.type?{cx:new u.Number(t),cy:new u.Number(e)}:{x2:new u.Number(t),y2:new u.Number(e)})},radius:function(t){return"radial"==this.type?this.attr({r:new u.Number(t)}):this},at:function(t,e,i){return this.put(new u.Stop).update(t,e,i)},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()}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),u.extend(u.Defs,{gradient:function(t,e){return this.put(new u.Gradient(t)).update(e)}}),u.Stop=u.invent({create:"stop",inherit:u.Element,extend:{update:function(t){return("number"==typeof t||t instanceof u.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 u.Number(t.offset)),this}}}),u.Pattern=u.invent({create:"pattern",inherit:u.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()}},construct:{pattern:function(t,e,i){return this.defs().pattern(t,e,i)}}}),u.extend(u.Defs,{pattern:function(t,e,i){return this.put(new u.Pattern).update(i).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),u.Doc=u.invent({create:function(t){this.parent="string"==typeof t?document.getElementById(t):t,this.constructor.call(this,"svg"==this.parent.nodeName?this.parent:u.create("svg")),this.attr({xmlns:u.ns,version:"1.1",width:"100%",height:"100%"}).attr("xmlns:xlink",u.xlink,u.xmlns),this._defs=new u.Defs,this._defs.parent=this,this.node.appendChild(this._defs.node),this.doSpof=!1,this.parent!=this.node&&this.stage()},inherit:u.Container,extend:{stage:function(){var t=this;return this.parent.appendChild(this.node),t.spof(),u.on(window,"resize",function(){t.spof()}),this},defs:function(){return this._defs},spof:function(){if(this.doSpof){var t=this.node.getScreenCTM();t&&this.style("left",-t.e%1+"px").style("top",-t.f%1+"px")}return this},fixSubPixelOffset:function(){return this.doSpof=!0,this},remove:function(){return this.parent&&(this.parent.removeChild(this.node),this.parent=null),this}}}),u.Shape=u.invent({create:function(t){this.constructor.call(this,t)},inherit:u.Element}),u.Symbol=u.invent({create:"symbol",inherit:u.Container,construct:{symbol:function(){return this.defs().put(new u.Symbol) -}}}),u.Use=u.invent({create:"use",inherit:u.Shape,extend:{element:function(t,e){return this.target=t,this.attr("href",(e||"")+"#"+t,u.xlink)}},construct:{use:function(t,e){return this.put(new u.Use).element(t,e)}}}),u.Rect=u.invent({create:"rect",inherit:u.Shape,construct:{rect:function(t,e){return this.put((new u.Rect).size(t,e))}}}),u.Ellipse=u.invent({create:"ellipse",inherit:u.Shape,extend:{x:function(t){return null==t?this.cx()-this.attr("rx"):this.cx(t+this.attr("rx"))},y:function(t){return null==t?this.cy()-this.attr("ry"):this.cy(t+this.attr("ry"))},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",new u.Number(t).divide(this.trans.scaleX))},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",new u.Number(t).divide(this.trans.scaleY))},width:function(t){return null==t?2*this.attr("rx"):this.attr("rx",new u.Number(t).divide(2))},height:function(t){return null==t?2*this.attr("ry"):this.attr("ry",new u.Number(t).divide(2))},size:function(t,e){var i=n(this.bbox(),t,e);return this.attr({rx:new u.Number(i.width).divide(2),ry:new u.Number(i.height).divide(2)})}},construct:{circle:function(t){return this.ellipse(t,t)},ellipse:function(t,e){return this.put(new u.Ellipse).size(t,e).move(0,0)}}}),u.Line=u.invent({create:"line",inherit:u.Shape,extend:{x:function(t){var e=this.bbox();return null==t?e.x:this.attr({x1:this.attr("x1")-e.x+t,x2:this.attr("x2")-e.x+t})},y:function(t){var e=this.bbox();return null==t?e.y:this.attr({y1:this.attr("y1")-e.y+t,y2:this.attr("y2")-e.y+t})},cx:function(t){var e=this.bbox().width/2;return null==t?this.x()+e:this.x(t-e)},cy:function(t){var e=this.bbox().height/2;return null==t?this.y()+e:this.y(t-e)},width:function(t){var e=this.bbox();return null==t?e.width:this.attr(this.attr("x1")<this.attr("x2")?"x2":"x1",e.x+t)},height:function(t){var e=this.bbox();return null==t?e.height:this.attr(this.attr("y1")<this.attr("y2")?"y2":"y1",e.y+t)},size:function(t,e){var i=n(this.bbox(),t,e);return this.width(i.width).height(i.height)},plot:function(t,e,i,n){return this.attr({x1:t,y1:e,x2:i,y2:n})}},construct:{line:function(t,e,i,n){return this.put((new u.Line).plot(t,e,i,n))}}}),u.Polyline=u.invent({create:"polyline",inherit:u.Shape,construct:{polyline:function(t){return this.put(new u.Polyline).plot(t)}}}),u.Polygon=u.invent({create:"polygon",inherit:u.Shape,construct:{polygon:function(t){return this.put(new u.Polygon).plot(t)}}}),u.extend(u.Polyline,u.Polygon,{morphArray:u.PointArray,plot:function(t){return this.attr("points",this.array=new u.PointArray(t,[[0,0]]))},move:function(t,e){return this.attr("points",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)},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)},size:function(t,e){var i=n(this.bbox(),t,e);return this.attr("points",this.array.size(i.width,i.height))}}),u.Path=u.invent({create:"path",inherit:u.Shape,extend:{plot:function(t){return this.attr("d",this.array=new u.PathArray(t,[["M",0,0]]))},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 i=n(this.bbox(),t,e);return this.attr("d",this.array.size(i.width,i.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 u.Path).plot(t)}}}),u.Image=u.invent({create:"image",inherit:u.Shape,extend:{load:function(t){if(!t)return this;var e=this,i=document.createElement("img");return i.onload=function(){var n=e.doc(u.Pattern);0==e.width()&&0==e.height()&&e.size(i.width,i.height),n&&0==n.width()&&0==n.height()&&n.size(e.width(),e.height()),"function"==typeof e._loaded&&e._loaded.call(e,{width:i.width,height:i.height,ratio:i.width/i.height,url:t})},this.attr("href",i.src=this.src=t,u.xlink)},loaded:function(t){return this._loaded=t,this}},construct:{image:function(t,e,i){return this.put(new u.Image).load(t).size(e||0,i||e||0)}}}),u.Text=u.invent({create:function(){this.constructor.call(this,u.create("text")),this._leading=new u.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",u.defaults.attrs["font-family"])},inherit:u.Shape,extend:{x:function(t){return null==t?this.attr("x"):(this.textPath||this.lines.each(function(){this.newLined&&this.x(t)}),this.attr("x",t))},y:function(t){var e=this.attr("y"),i="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-i:e:this.attr("y","number"==typeof t?t+i: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)return this.content;if(this.clear().build(!0),"function"==typeof t)t.call(this,this);else{t=(this.content=t).split("\n");for(var e=0,i=t.length;i>e;e++)this.tspan(t[e]).newLine()}return this.build(!1).rebuild()},size:function(t){return this.attr("font-size",t).rebuild()},leading:function(t){return null==t?this._leading:(this._leading=new u.Number(t),this.rebuild())},rebuild:function(t){if("boolean"==typeof t&&(this._rebuild=t),this._rebuild){var e=this;this.lines.each(function(){this.newLined&&(this.textPath||this.attr("x",e.attr("x")),this.attr("dy",e._leading*new u.Number(e.attr("font-size"))))}),this.fire("rebuild")}return this},build:function(t){return this._build=!!t,this}},construct:{text:function(t){return this.put(new u.Text).text(t)},plain:function(t){return this.put(new u.Text).plain(t)}}}),u.TSpan=u.invent({create:"tspan",inherit:u.Shape,extend:{text:function(t){return"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.doc(u.Text);return this.newLined=!0,this.dy(t._leading*t.attr("font-size")).attr("x",t.x())}}}),u.extend(u.Text,u.TSpan,{plain:function(t){return this._build===!1&&this.clear(),this.node.appendChild(document.createTextNode(this.content=t)),this},tspan:function(t){var e=(this.textPath||this).node,i=new u.TSpan;return this._build===!1&&this.clear(),e.appendChild(i.node),i.parent=this,this instanceof u.Text&&this.lines.add(i),i.text(t)},clear:function(){for(var t=(this.textPath||this).node;t.hasChildNodes();)t.removeChild(t.lastChild);return this instanceof u.Text&&(delete this.lines,this.lines=new u.Set,this.content=""),this},length:function(){return this.node.getComputedTextLength()}}),u.TextPath=u.invent({create:"textPath",inherit:u.Element,parent:u.Text,construct:{path:function(t){for(this.textPath=new u.TextPath;this.node.hasChildNodes();)this.textPath.node.appendChild(this.node.firstChild);return this.node.appendChild(this.textPath.node),this.track=this.doc().defs().path(t),this.textPath.parent=this,this.textPath.attr("href","#"+this.track,u.xlink),this},plot:function(t){return this.track&&this.track.plot(t),this}}}),u.Nested=u.invent({create:function(){this.constructor.call(this,u.create("svg")),this.style("overflow","visible")},inherit:u.Container,construct:{nested:function(){return this.put(new u.Nested)}}}),u.A=u.invent({create:"a",inherit:u.Container,extend:{to:function(t){return this.attr("href",t,u.xlink)},show:function(t){return this.attr("show",t,u.xlink)},target:function(t){return this.attr("target",t)}},construct:{link:function(t){return this.put(new u.A).to(t)}}}),u.extend(u.Element,{linkTo:function(t){var e=new u.A;return"function"==typeof t?t.call(e,e):e.to(t),this.parent.put(e).put(this)}}),u.Marker=u.invent({create:"marker",inherit:u.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,i){return this.defs().marker(t,e,i)}}}),u.extend(u.Defs,{marker:function(t,e,i){return this.put(new u.Marker).size(t,e).ref(t/2,e/2).viewbox(0,0,t,e).attr("orient","auto").update(i)}}),u.extend(u.Line,u.Polyline,u.Polygon,u.Path,{marker:function(t,e,i,n){var r=["marker"];return"all"!=t&&r.push(t),r=r.join("-"),t=arguments[1]instanceof u.Marker?arguments[1]:this.doc().marker(e,i,n),this.attr(r,t)}});var c={stroke:["color","width","opacity","linecap","linejoin","miterlimit","dasharray","dashoffset"],fill:["color","opacity","rule"],prefix:function(t,e){return"color"==e?t:t+"-"+e}};return["fill","stroke"].forEach(function(t){var e,i={};i[t]=function(i){if("string"==typeof i||u.Color.isRgb(i)||i&&"function"==typeof i.fill)this.attr(t,i);else for(e=c[t].length-1;e>=0;e--)null!=i[c[t][e]]&&this.attr(c.prefix(t,c[t][e]),i[c[t][e]]);return this},u.extend(u.Element,u.FX,i)}),u.extend(u.Element,u.FX,{rotate:function(t,e,i){return this.transform({rotation:t||0,cx:e,cy:i})},skew:function(t,e){return this.transform({skewX:t||0,skewY:e||0})},scale:function(t,e){return this.transform({scaleX:t,scaleY:null==e?t:e})},translate:function(t,e){return this.transform({x:t,y:e})},matrix:function(t){return this.transform({matrix:t})},opacity:function(t){return this.attr("opacity",t)}}),u.extend(u.Rect,u.Ellipse,u.FX,{radius:function(t,e){return this.attr({rx:t,ry:e||t})}}),u.extend(u.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),u.extend(u.Parent,u.Text,u.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}}),u.Set=u.invent({create:function(){this.clear()},extend:{add:function(){var t,e,i=[].slice.call(arguments);for(t=0,e=i.length;e>t;t++)this.members.push(i[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,i=this.members.length;i>e;e++)t.apply(this.members[e],[e,this.members]);return this},clear:function(){return this.members=[],this},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 u.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(){return new u.Set}}}),u.SetFX=u.invent({create:function(t){this.set=t}}),u.Set.inherit=function(){var t,e=[];for(var t in u.Shape.prototype)"function"==typeof u.Shape.prototype[t]&&"function"!=typeof u.Set.prototype[t]&&e.push(t);e.forEach(function(t){u.Set.prototype[t]=function(){for(var e=0,i=this.members.length;i>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 u.SetFX(this)):this}}),e=[];for(var t in u.FX.prototype)"function"==typeof u.FX.prototype[t]&&"function"!=typeof u.SetFX.prototype[t]&&e.push(t);e.forEach(function(t){u.SetFX.prototype[t]=function(){for(var e=0,i=this.set.members.length;i>e;e++)this.set.members[e].fx[t].apply(this.set.members[e].fx,arguments);return this}})},u.extend(u.Element,{data:function(t,e,i){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(n){return this.attr("data-"+t)}else this.attr("data-"+t,null===e?null:i===!0||"string"==typeof e||"number"==typeof e?e:JSON.stringify(e));return this}}),u.extend(u.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={})}}),u});
\ No newline at end of file +/*! SVG.js v2.0.0-rc.2 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=e(require,exports,module):t.SVG=e()}(this,function(){function t(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function e(t){return t.charAt(0).toUpperCase()+t.slice(1)}function i(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 n(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function r(t,e,i){return null==i?i=t.height/t.width*e:null==e&&(e=t.width/t.height*i),{width:e,height:i}}function s(t,e,i){return{x:e*t.a+i*t.c+0,y:e*t.b+i*t.d+0}}function o(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function a(t){return t instanceof m.Matrix||(t=new m.Matrix(t)),t}function h(t,e){t.cx=null==t.cx?e.bbox().cx:t.cx,t.cy=null==t.cy?e.bbox().cy:t.cy}function u(t){return t=t.replace(m.regex.whitespace,"").replace(m.regex.matrix,"").split(","),o(m.utils.map(t,function(t){return parseFloat(t)}))}function l(t,e){return"number"==typeof t.from?t.from+(t.to-t.from)*e:t instanceof m.Color||t instanceof m.Number||t instanceof m.Matrix?t.at(e):1>e?t.from:t.to}function c(t){for(var e=0,i=t.length,n="";i>e;e++)n+=t[e][0],null!=t[e][1]&&(n+=t[e][1],null!=t[e][2]&&(n+=" ",n+=t[e][2],null!=t[e][3]&&(n+=" ",n+=t[e][3],n+=" ",n+=t[e][4],null!=t[e][5]&&(n+=" ",n+=t[e][5],n+=" ",n+=t[e][6],null!=t[e][7]&&(n+=" ",n+=t[e][7])))));return n+" "}function f(t){for(var e=t.childNodes.length-1;e>=0;e--)t.childNodes[e]instanceof SVGElement&&f(t.childNodes[e]);return m.adopt(t).id(m.eid(t.nodeName))}function d(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 p(t){var e=t.toString().match(m.regex.reference);return e?e[1]:void 0}var m=this.SVG=function(t){return m.supported?(t=new m.Doc(t),m.parser||m.prepare(t),t):void 0};if(m.ns="http://www.w3.org/2000/svg",m.xmlns="http://www.w3.org/2000/xmlns/",m.xlink="http://www.w3.org/1999/xlink",m.supported=function(){return!!document.createElementNS&&!!document.createElementNS(m.ns,"svg").createSVGRect}(),!m.supported)return!1;m.did=1e3,m.eid=function(t){return"Svgjs"+e(t)+m.did++},m.create=function(t){var e=document.createElementNS(this.ns,t);return e.setAttribute("id",this.eid(t)),e},m.extend=function(){var t,e,i,n;for(t=[].slice.call(arguments),e=t.pop(),n=t.length-1;n>=0;n--)if(t[n])for(i in e)t[n].prototype[i]=e[i];m.Set&&m.Set.inherit&&m.Set.inherit()},m.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,m.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&m.extend(e,t.extend),t.construct&&m.extend(t.parent||m.Container,t.construct),e},m.adopt=function(t){if(t.instance)return t.instance;var i;return i="svg"==t.nodeName?t.parentNode instanceof SVGElement?new m.Nested:new m.Doc:"lineairGradient"==t.nodeName?new m.Gradient("lineair"):"radialGradient"==t.nodeName?new m.Gradient("radial"):m[e(t.nodeName)]?new(m[e(t.nodeName)]):new m.Element(t),i.type=t.nodeName,i.node=t,t.instance=i,i instanceof m.Doc&&i.namespace().defs(),i},m.prepare=function(t){var e=document.getElementsByTagName("body")[0],i=(e?new m.Doc(e):t.nested()).size(2,0),n=m.create("path");i.node.appendChild(n),m.parser={body:e||t.parent(),draw:i.style("opacity:0;position:fixed;left:100%;top:100%;overflow:hidden"),poly:i.polyline().node,path:n}},m.regex={unit:/^(-?[\d\.]+)([a-z%]{0,2})$/,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,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^-?[\d\.]+$/,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i},m.utils={map:function(t,e){var i,n=t.length,r=[];for(i=0;n>i;i++)r.push(e(t[i]));return r},radians:function(t){return t%360*Math.PI/180},degrees:function(t){return 180*t/Math.PI%360}},m.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"}},m.Color=function(t){var e;this.r=0,this.g=0,this.b=0,"string"==typeof t?m.regex.isRgb.test(t)?(e=m.regex.rgb.exec(t.replace(/\s/g,"")),this.r=parseInt(e[1]),this.g=parseInt(e[2]),this.b=parseInt(e[3])):m.regex.isHex.test(t)&&(e=m.regex.hex.exec(i(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)},m.extend(m.Color,{toString:function(){return this.toHex()},toHex:function(){return"#"+n(this.r)+n(this.g)+n(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 m.Color(t),this},at:function(t){return this.destination?(t=0>t?0:t>1?1:t,new m.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}}),m.Color.test=function(t){return t+="",m.regex.isHex.test(t)||m.regex.isRgb.test(t)},m.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},m.Color.isColor=function(t){return m.Color.isRgb(t)||m.Color.test(t)},m.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},m.extend(m.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],i=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(i);for(;this.value.length<this.destination.length;)this.value.push(e)}return this},settle:function(){for(var t=0,e=this.value.length,i=[];e>t;t++)-1==i.indexOf(this.value[t])&&i.push(this.value[t]);return this.value=i},at:function(t){if(!this.destination)return this;for(var e=0,i=this.value.length,n=[];i>e;e++)n.push(this.value[e]+(this.destination[e]-this.value[e])*t);return new m.Array(n)},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.replace(/\s+/g," ").replace(/^\s+|\s+$/g,"").split(" ")},reverse:function(){return this.value.reverse(),this}}),m.PointArray=function(t,e){this.constructor.call(this,t,e||[[0,0]])},m.PointArray.prototype=new m.Array,m.extend(m.PointArray,{toString:function(){for(var t=0,e=this.value.length,i=[];e>t;t++)i.push(this.value[t].join(","));return i.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,i=this.value.length,n=[];i>e;e++)n.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 m.PointArray(n)},parse:function(t){if(t=t.valueOf(),Array.isArray(t))return t;t=this.split(t);for(var e,i=0,n=t.length,r=[];n>i;i++)e=t[i].split(","),r.push([parseFloat(e[0]),parseFloat(e[1])]);return r},move:function(t,e){var i=this.bbox();if(t-=i.x,e-=i.y,!isNaN(t)&&!isNaN(e))for(var n=this.value.length-1;n>=0;n--)this.value[n]=[this.value[n][0]+t,this.value[n][1]+e];return this},size:function(t,e){var i,n=this.bbox();for(i=this.value.length-1;i>=0;i--)this.value[i][0]=(this.value[i][0]-n.x)*t/n.width+n.x,this.value[i][1]=(this.value[i][1]-n.y)*e/n.height+n.y;return this},bbox:function(){return m.parser.poly.setAttribute("points",this.toString()),m.parser.poly.getBBox()}}),m.PathArray=function(t,e){this.constructor.call(this,t,e||[["M",0,0]])},m.PathArray.prototype=new m.Array,m.extend(m.PathArray,{toString:function(){return c(this.value)},move:function(t,e){var i=this.bbox();if(t-=i.x,e-=i.y,!isNaN(t)&&!isNaN(e))for(var n,r=this.value.length-1;r>=0;r--)n=this.value[r][0],"M"==n||"L"==n||"T"==n?(this.value[r][1]+=t,this.value[r][2]+=e):"H"==n?this.value[r][1]+=t:"V"==n?this.value[r][1]+=e:"C"==n||"S"==n||"Q"==n?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"==n&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"==n&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var i,n,r=this.bbox();for(i=this.value.length-1;i>=0;i--)n=this.value[i][0],"M"==n||"L"==n||"T"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y):"H"==n?this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x:"V"==n?this.value[i][1]=(this.value[i][1]-r.y)*e/r.height+r.y:"C"==n||"S"==n||"Q"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y,this.value[i][3]=(this.value[i][3]-r.x)*t/r.width+r.x,this.value[i][4]=(this.value[i][4]-r.y)*e/r.height+r.y,"C"==n&&(this.value[i][5]=(this.value[i][5]-r.x)*t/r.width+r.x,this.value[i][6]=(this.value[i][6]-r.y)*e/r.height+r.y)):"A"==n&&(this.value[i][1]=this.value[i][1]*t/r.width,this.value[i][2]=this.value[i][2]*e/r.height,this.value[i][6]=(this.value[i][6]-r.x)*t/r.width+r.x,this.value[i][7]=(this.value[i][7]-r.y)*e/r.height+r.y);return this},parse:function(t){if(t instanceof m.PathArray)return t.valueOf();var e,i,n,r,s,o,a,h,u,l,f,d=0,p=0;for(m.parser.path.setAttribute("d","string"==typeof t?t:c(t)),f=m.parser.path.pathSegList,e=0,i=f.numberOfItems;i>e;++e)l=f.getItem(e),u=l.pathSegTypeAsLetter,"M"==u||"L"==u||"H"==u||"V"==u||"C"==u||"S"==u||"Q"==u||"T"==u||"A"==u?("x"in l&&(d=l.x),"y"in l&&(p=l.y)):("x1"in l&&(s=d+l.x1),"x2"in l&&(a=d+l.x2),"y1"in l&&(o=p+l.y1),"y2"in l&&(h=p+l.y2),"x"in l&&(d+=l.x),"y"in l&&(p+=l.y),"m"==u?f.replaceItem(m.parser.path.createSVGPathSegMovetoAbs(d,p),e):"l"==u?f.replaceItem(m.parser.path.createSVGPathSegLinetoAbs(d,p),e):"h"==u?f.replaceItem(m.parser.path.createSVGPathSegLinetoHorizontalAbs(d),e):"v"==u?f.replaceItem(m.parser.path.createSVGPathSegLinetoVerticalAbs(p),e):"c"==u?f.replaceItem(m.parser.path.createSVGPathSegCurvetoCubicAbs(d,p,s,o,a,h),e):"s"==u?f.replaceItem(m.parser.path.createSVGPathSegCurvetoCubicSmoothAbs(d,p,a,h),e):"q"==u?f.replaceItem(m.parser.path.createSVGPathSegCurvetoQuadraticAbs(d,p,s,o),e):"t"==u?f.replaceItem(m.parser.path.createSVGPathSegCurvetoQuadraticSmoothAbs(d,p),e):"a"==u?f.replaceItem(m.parser.path.createSVGPathSegArcAbs(d,p,l.r1,l.r2,l.angle,l.largeArcFlag,l.sweepFlag),e):("z"==u||"Z"==u)&&(d=n,p=r)),("M"==u||"m"==u)&&(n=d,r=p);for(t=[],f=m.parser.path.pathSegList,e=0,i=f.numberOfItems;i>e;++e)l=f.getItem(e),u=l.pathSegTypeAsLetter,d=[u],"M"==u||"L"==u||"T"==u?d.push(l.x,l.y):"H"==u?d.push(l.x):"V"==u?d.push(l.y):"C"==u?d.push(l.x1,l.y1,l.x2,l.y2,l.x,l.y):"S"==u?d.push(l.x2,l.y2,l.x,l.y):"Q"==u?d.push(l.x1,l.y1,l.x,l.y):"A"==u&&d.push(l.r1,l.r2,l.angle,0|l.largeArcFlag,0|l.sweepFlag,l.x,l.y),t.push(d);return t},bbox:function(){return m.parser.path.setAttribute("d",this.toString()),m.parser.path.getBBox()}}),m.Number=m.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(m.regex.unit),e&&(this.value=parseFloat(e[1]),"%"==e[2]?this.value/=100:"s"==e[2]&&(this.value*=1e3),this.unit=e[2])):t instanceof m.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},valueOf:function(){return this.value},plus:function(t){return new m.Number(this+new m.Number(t),this.unit)},minus:function(t){return this.plus(-new m.Number(t))},times:function(t){return new m.Number(this*new m.Number(t),this.unit)},divide:function(t){return new m.Number(this/new m.Number(t),this.unit)},to:function(t){var e=new m.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new m.Number(t),this},at:function(t){return this.destination?new m.Number(this.destination).minus(this).times(t).plus(this):this}}}),m.ViewBox=function(t){var e,i,n,r,s=1,o=1,a=t.bbox(),h=(t.attr("viewBox")||"").match(/-?[\d\.]+/g),u=t,l=t;for(n=new m.Number(t.width()),r=new m.Number(t.height());"%"==n.unit;)s*=n.value,n=new m.Number(u instanceof m.Doc?u.parent().offsetWidth:u.parent().width()),u=u.parent();for(;"%"==r.unit;)o*=r.value,r=new m.Number(l instanceof m.Doc?l.parent().offsetHeight:l.parent().height()),l=l.parent();this.x=a.x,this.y=a.y,this.width=n*s,this.height=r*o,this.zoom=1,h&&(e=parseFloat(h[0]),i=parseFloat(h[1]),n=parseFloat(h[2]),r=parseFloat(h[3]),this.zoom=this.width/this.height>n/r?this.height/r:this.width/n,this.x=e,this.y=i,this.width=n,this.height=r)},m.extend(m.ViewBox,{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height}}),m.Element=m.invent({create:function(t){this._stroke=m.defaults.attrs.stroke,(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke)},extend:{x:function(t){return null!=t&&(t=new m.Number(t),t.value/=this.transform("scaleX")),this.attr("x",t)},y:function(t){return null!=t&&(t=new m.Number(t),t.value/=this.transform("scaleY")),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 i=r(this.bbox(),t,e);return this.width(new m.Number(i.width)).height(new m.Number(i.height))},clone:function(){var t=f(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 i=this.bbox();return t>i.x&&e>i.y&&t<i.x+i.width&&e<i.y+i.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 m.get(this.attr(t))},parent:function(t){if(this.node.parentNode){var e=m.adopt(this.node.parentNode);if(t)for(;!(e instanceof t)&&e.node.parentNode instanceof SVGElement;)e=m.adopt(e.node.parentNode);return e}},doc:function(t){return this.parent(t||m.Doc)},"native":function(){return this.node},svg:function(t){var e=document.createElement("svg");if(!(t&&this instanceof m.Parent))return e.appendChild(t=document.createElement("svg")),t.appendChild(this.node.cloneNode(!0)),e.innerHTML.replace(/^<svg>/,"").replace(/<\/svg>$/,"");e.innerHTML="<svg>"+t.replace(/\n/,"").replace(/<(\w+)([^<]+?)\/>/g,"<$1$2></$1>")+"</svg>";for(var i=0,n=e.firstChild.childNodes.length;n>i;i++)this.node.appendChild(e.firstChild.childNodes[i]);return this}}}),m.FX=m.invent({create:function(t){this.target=t},extend:{animate:function(t,e,i){var n,r,s,o=this.target,a=this;return"object"==typeof t&&(i=t.delay,e=t.ease,t=t.duration),t="="==t?t:null==t?1e3:new m.Number(t).valueOf(),e=e||"<>",a.at=function(t){var i;if(t=0>t?0:t>1?1:t,null==n){n=[];for(s in a.attrs)n.push(s);if(o.morphArray&&(a.destination.plot||n.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)),i=n.length-1;i>=0;i--)o.attr(n[i],l(a.attrs[n[i]],t));for(i=r.length-1;i>=0;i--)o.style(r[i],l(a.styles[r[i]],t));a.situation.during&&a.situation.during.call(o,t,function(e,i){return l({from:e,to:i},t)})},"number"==typeof t&&(this.timeout=setTimeout(function(){var n=(new Date).getTime();a.situation.start=n,a.situation.play=!0,a.situation.finish=n+t,a.situation.duration=t,a.situation.ease=e,a.render=function(){if(a.situation.play===!0){var n=(new Date).getTime(),r=n>a.situation.finish?1:(n-a.situation.start)/t;a.situation.reversing&&(r=-r+1),a.at(r),n>a.situation.finish?(a.destination.plot&&o.plot(new m.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,i)):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 m.Number(i).valueOf())),this},bbox:function(){return this.target.bbox()},attr:function(t,e){if("object"==typeof t)for(var i in t)this.attr(i,t[i]);else{var n=this.target.attr(t);"transform"==t?(this.attrs[t]&&(e=this.attrs[t].destination.multiply(e)),this.attrs[t]=this.target.ctm().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]=m.Color.isColor(e)?new m.Color(n).morph(e):m.regex.unit.test(e)?new m.Number(n).morph(e):{from:n,to:e}}return this},style:function(t,e){if("object"==typeof t)for(var i in t)this.style(i,t[i]);else this.styles[t]={from:this.target.style(t),to:e};return this},x:function(t){return this.destination.x=new m.Number(this.target.x()).morph(t),this},y:function(t){return this.destination.y=new m.Number(this.target.y()).morph(t),this},cx:function(t){return this.destination.cx=new m.Number(this.target.cx()).morph(t),this},cy:function(t){return this.destination.cy=new m.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 m.Text)this.attr("font-size",t);else{var i=this.target.bbox();this.destination.size={width:new m.Number(i.width).morph(t),height:new m.Number(i.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 m.Number(this.target.destination.leading).morph(t)),this},viewbox:function(t,e,i,n){if(this.target instanceof m.Container){var r=this.target.viewbox();this.destination.viewbox={x:new m.Number(r.x).morph(t),y:new m.Number(r.y).morph(e),width:new m.Number(r.width).morph(i),height:new m.Number(r.height).morph(n)}}return this},update:function(t){return this.target instanceof m.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 m.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:m.Element,construct:{animate:function(t,e,i){return(this.fx||(this.fx=new m.FX(this))).stop().animate(t,e,i)},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}}}),m.BBox=m.invent({create:function(t){if(t){var e;try{e=t.node.getBBox()}catch(i){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}d(this)},parent:m.Element,construct:{bbox:function(){return new m.BBox(this)}}}),m.TBox=m.invent({create:function(t){if(t){var e=t.ctm().extract(),i=t.bbox();this.width=i.width*e.scaleX,this.height=i.height*e.scaleY,this.x=i.x+e.x,this.y=i.y+e.y}d(this)},parent:m.Element,construct:{tbox:function(){return new m.TBox(this)}}}),m.RBox=m.invent({create:function(t){if(t){var e=t.doc().parent(),i=t.node.getBoundingClientRect(),n=1;for(this.x=i.left,this.y=i.top,this.x-=e.offsetLeft,this.y-=e.offsetTop;e=e.offsetParent;)this.x-=e.offsetLeft,this.y-=e.offsetTop;for(e=t;e.parent&&(e=e.parent());)e.viewbox&&(n*=e.viewbox().zoom,this.x-=e.x()||0,this.y-=e.y()||0);this.width=i.width/=n,this.height=i.height/=n}d(this),this.x+=window.scrollX,this.y+=window.scrollY},parent:m.Element,construct:{rbox:function(){return new m.RBox(this)}}}),[m.BBox,m.TBox,m.RBox].forEach(function(t){m.extend(t,{merge:function(e){var i=new t;return i.x=Math.min(this.x,e.x),i.y=Math.min(this.y,e.y),i.width=Math.max(this.x+this.width,e.x+e.width)-i.x,i.height=Math.max(this.y+this.height,e.y+e.height)-i.y,d(i)}})}),m.Matrix=m.invent({create:function(t){var e,i=o([1,0,0,1,0,0]);for(t=t&&t.node&&t.node.getCTM?t.node.getCTM():"string"==typeof t?u(t):6==arguments.length?o([].slice.call(arguments)):"object"==typeof t?t:i,e=g.length-1;e>=0;e--)this[g[e]]=t&&"number"==typeof t[g[e]]?t[g[e]]:i[g[e]]},extend:{extract:function(){var t=s(this,0,1),e=s(this,1,0),i=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,skewX:-i,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:i}},clone:function(){return new m.Matrix(this)},morph:function(t){return this.destination=new m.Matrix(t),this},at:function(t){if(!this.destination)return this;var e=new m.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 i={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,i.cx,i.cy),e.param=i}return e},multiply:function(t){return new m.Matrix(this.native().multiply(a(t).native()))},inverse:function(){return new m.Matrix(this.native().inverse())},translate:function(t,e){return new m.Matrix(this.native().translate(t||0,e||0))},scale:function(t,e,i,n){return(1==arguments.length||3==arguments.length)&&(e=t),3==arguments.length&&(n=i,i=e),this.around(i,n,new m.Matrix(t,0,0,e,0,0))},rotate:function(t,e,i){return t=m.utils.radians(t),this.around(e,i,new m.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,i,n){return this.around(i,n,this.native().skewX(t||0).skewY(e||0))},around:function(t,e,i){return this.multiply(new m.Matrix(1,0,0,1,t||0,e||0)).multiply(i).multiply(new m.Matrix(1,0,0,1,-t||0,-e||0))},"native":function(){for(var t=m.parser.draw.node.createSVGMatrix(),e=g.length-1;e>=0;e--)t[g[e]]=this[g[e]];return t},toString:function(){return"matrix("+this.a+","+this.b+","+this.c+","+this.d+","+this.e+","+this.f+")"}},parent:m.Element,construct:{ctm:function(){return new m.Matrix(this)}}}),m.extend(m.Element,{attr:function(t,e,i){if(null==t){for(t={},e=this.node.attributes,i=e.length-1;i>=0;i--)t[e[i].nodeName]=m.regex.isNumber.test(e[i].nodeValue)?parseFloat(e[i].nodeValue):e[i].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?m.defaults.attrs[t]:m.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)&&(m.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof m.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new m.Number(e):m.Color.isColor(e)?e=new m.Color(e):Array.isArray(e)?e=new m.Array(e):e instanceof m.Matrix&&e.param&&(this.param=e.param),"leading"==t?this.leading&&this.leading(e):"string"==typeof i?this.node.setAttributeNS(i,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this}}),m.extend(m.Element,m.FX,{transform:function(t,e){var i,n=this.target||this;if("object"!=typeof t)return i=n.ctm().extract(),"object"==typeof this.param&&(i.rotation=this.param.rotation,i.cx=this.param.cx,i.cy=this.param.cy),"string"==typeof t?i[t]:i;if(i=this instanceof m.FX&&this.attrs.transform?this.attrs.transform:new m.Matrix(n),e=!!e||!!t.relative,null!=t.a)i=e?i.multiply(new m.Matrix(t)):new m.Matrix(t);else if(null!=t.rotation)h(t,n),e&&(t.rotation+=this.param&&null!=this.param.rotation?this.param.rotation:i.extract().rotation),this.param=t,this instanceof m.Element&&(i=e?n.attr("transform",i+" rotate("+[t.rotation,t.cx,t.cy].join()+")").ctm():i.rotate(t.rotation-i.extract().rotation,t.cx,t.cy));else if(null!=t.scale||null!=t.scaleX||null!=t.scaleY){if(h(t,n),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=i.extract();t.scaleX=1*t.scaleX/r.scaleX,t.scaleY=1*t.scaleY/r.scaleY}i=i.scale(t.scaleX,t.scaleY,t.cx,t.cy)}else if(null!=t.skewX||null!=t.skewY){if(h(t,n),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,!e){var r=i.extract();i=i.multiply((new m.Matrix).skew(r.skewX,r.skewY,t.cx,t.cy).inverse())}i=i.skew(t.skewX,t.skewY,t.cx,t.cy)}else t.flip?i=i.flip(t.flip,null==t.offset?n.bbox()["c"+t.flip]:t.offset):(null!=t.x||null!=t.y)&&(e?i=i.translate(t.x,t.y):(null!=t.x&&(i.e=t.x),null!=t.y&&(i.f=t.y)));return this.attr("transform",i)}}),m.extend(m.Element,{untransform:function(){return this.attr("transform",null)}}),m.extend(m.Element,{style:function(e,i){if(0==arguments.length)return this.node.style.cssText||"";if(arguments.length<2)if("object"==typeof e)for(i in e)this.style(i,e[i]);else{if(!m.regex.isCss.test(e))return this.node.style[t(e)];e=e.split(";");for(var n=0;n<e.length;n++)i=e[n].split(":"),this.style(i[0].replace(/\s+/g,""),i[1])}else this.node.style[t(e)]=null===i||m.regex.isBlank.test(i)?"":i;return this}}),m.Parent=m.invent({create:function(t){this.constructor.call(this,t)},inherit:m.Element,extend:{children:function(){return m.utils.map(this.node.childNodes,function(t){return m.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 i,n,r=this.children();for(i=0,n=r.length;n>i;i++)r[i]instanceof m.Element&&t.apply(r[i],[i,r]),e&&r[i]instanceof m.Container&&r[i].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()}}}),m.Container=m.invent({create:function(t){this.constructor.call(this,t)},inherit:m.Parent,extend:{viewbox:function(t){return 0==arguments.length?new m.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){m.Element.prototype[t]=function(e){var i=this;return this.node["on"+t]="function"==typeof e?function(){return e.apply(i,arguments)}:null,this}}),m.listeners=[],m.handlerMap=[],m.on=function(t,e,i){var n=i.bind(t.instance||t),r=(m.handlerMap.indexOf(t)+1||m.handlerMap.push(t))-1,s=e.split(".")[0],o=e.split(".")[1]||"*";m.listeners[r]=m.listeners[r]||{},m.listeners[r][s]=m.listeners[r][s]||{},m.listeners[r][s][o]=m.listeners[r][s][o]||{},m.listeners[r][s][o][i]=n,t.addEventListener(s,n,!1)},m.off=function(t,e,i){var n=m.handlerMap.indexOf(t),r=e&&e.split(".")[0],s=e&&e.split(".")[1];if(-1!=n)if(i)m.listeners[n][r]&&m.listeners[n][r][s||"*"]&&(t.removeEventListener(r,m.listeners[n][r][s||"*"][i],!1),delete m.listeners[n][r][s||"*"][i]);else if(s){if(m.listeners[n][r]&&m.listeners[n][r][s]){for(i in m.listeners[n][r][s])m.off(t,[r,s].join("."),i);delete m.listeners[n][r][s]}}else if(r){if(m.listeners[n][r]){for(namespace in m.listeners[n][r])m.off(t,[r,namespace].join("."));delete m.listeners[n][r]}}else{for(e in m.listeners[n])m.off(t,e);delete m.listeners[n]}},m.extend(m.Element,{on:function(t,e){return m.on(this.node,t,e),this},off:function(t,e){return m.off(this.node,t,e),this},fire:function(t,e){return this.node.dispatchEvent(new y(t,{detail:e})),this}}),m.Defs=m.invent({create:"defs",inherit:m.Container}),m.G=m.invent({create:"g",inherit:m.Container,extend:{x:function(t){return null==t?this.transform("x"):this.transform({x:-this.x()+t},!0)},y:function(t){return null==t?this.transform("y"):this.transform({y:-this.y()+t},!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)}},construct:{group:function(){return this.put(new m.G)}}}),m.extend(m.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 m.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 m.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}}),m.Mask=m.invent({create:function(){this.constructor.call(this,m.create("mask")),this.targets=[]},inherit:m.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return delete this.targets,this.parent().removeElement(this),this}},construct:{mask:function(){return this.defs().put(new m.Mask)}}}),m.extend(m.Element,{maskWith:function(t){return this.masker=t instanceof m.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)}}),m.ClipPath=m.invent({create:function(){this.constructor.call(this,m.create("clipPath")),this.targets=[]},inherit:m.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return delete this.targets,this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new m.ClipPath)}}}),m.extend(m.Element,{clipWith:function(t){return this.clipper=t instanceof m.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)}}),m.Gradient=m.invent({create:function(t){this.constructor.call(this,m.create(t+"Gradient")),this.type=t},inherit:m.Container,extend:{at:function(t,e,i){return this.put(new m.Stop).update(t,e,i)},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()}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),m.extend(m.Gradient,m.FX,{from:function(t,e){return"radial"==(this.target||this).type?this.attr({fx:new m.Number(t),fy:new m.Number(e)}):this.attr({x1:new m.Number(t),y1:new m.Number(e)})},to:function(t,e){return"radial"==(this.target||this).type?this.attr({cx:new m.Number(t),cy:new m.Number(e)}):this.attr({x2:new m.Number(t),y2:new m.Number(e)})}}),m.extend(m.Defs,{gradient:function(t,e){return this.put(new m.Gradient(t)).update(e)}}),m.Stop=m.invent({create:"stop",inherit:m.Element,extend:{update:function(t){return("number"==typeof t||t instanceof m.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 m.Number(t.offset)),this}}}),m.Pattern=m.invent({create:"pattern",inherit:m.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()}},construct:{pattern:function(t,e,i){return this.defs().pattern(t,e,i)}}}),m.extend(m.Defs,{pattern:function(t,e,i){return this.put(new m.Pattern).update(i).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),m.Doc=m.invent({create:function(t){t&&(t="string"==typeof t?document.getElementById(t):t,"svg"==t.nodeName?this.constructor.call(this,t):(this.constructor.call(this,m.create("svg")),t.appendChild(this.node)),this.namespace().size("100%","100%").defs())},inherit:m.Container,extend:{namespace:function(){return this.attr({xmlns:m.ns,version:"1.1"}).attr("xmlns:xlink",m.xlink,m.xmlns)},defs:function(){if(!this._defs){var t;this._defs=(t=this.node.getElementsByTagName("defs")[0])?m.adopt(t):new m.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}}}),m.Shape=m.invent({create:function(t){this.constructor.call(this,t)},inherit:m.Element}),m.Bare=m.invent({create:function(t,e){if(this.constructor.call(this,m.create(t)),e)for(var i in e.prototype)"function"==typeof e.prototype[i]&&(t[i]=e.prototype[i])},inherit:m.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(document.createTextNode(t)),this}}}),m.extend(m.Parent,{element:function(t,e){return this.put(new m.Bare(t,e))},symbol:function(){return this.defs().element("symbol",m.Container)}}),m.Use=m.invent({create:"use",inherit:m.Shape,extend:{element:function(t,e){return this.target=t,this.attr("href",(e||"")+"#"+t,m.xlink)}},construct:{use:function(t,e){return this.put(new m.Use).element(t,e)}}}),m.Rect=m.invent({create:"rect",inherit:m.Shape,construct:{rect:function(t,e){return this.put((new m.Rect).size(t,e))}}}),m.Circle=m.invent({create:"circle",inherit:m.Shape,construct:{circle:function(t){return this.put(new m.Circle).rx(new m.Number(t).divide(2)).move(0,0)}}}),m.extend(m.Circle,m.FX,{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),m.Ellipse=m.invent({create:"ellipse",inherit:m.Shape,construct:{ellipse:function(t,e){return this.put(new m.Ellipse).size(t,e).move(0,0)}}}),m.extend(m.Ellipse,m.Rect,m.FX,{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),m.extend(m.Circle,m.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",new m.Number(t).divide(this.transform("scaleX")))},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",new m.Number(t).divide(this.transform("scaleY")))},width:function(t){return null==t?2*this.rx():this.rx(new m.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new m.Number(t).divide(2))},size:function(t,e){var i=r(this.bbox(),t,e);return this.rx(new m.Number(i.width).divide(2)).ry(new m.Number(i.height).divide(2))}}),m.Line=m.invent({create:"line",inherit:m.Shape,extend:{array:function(){return new m.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,i,n){return t=4==arguments.length?{x1:t,y1:e,x2:i,y2:n}:new m.PointArray(t).toLine(),this.attr(t)},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var i=r(this.bbox(),t,e);return this.attr(this.array().size(i.width,i.height).toLine())}},construct:{line:function(t,e,i,n){return this.put(new m.Line).plot(t,e,i,n)}}}),m.Polyline=m.invent({create:"polyline",inherit:m.Shape,construct:{polyline:function(t){return this.put(new m.Polyline).plot(t)}}}),m.Polygon=m.invent({create:"polygon",inherit:m.Shape,construct:{polygon:function(t){return this.put(new m.Polygon).plot(t)}}}),m.extend(m.Polyline,m.Polygon,{array:function(){return this._array||(this._array=new m.PointArray(this.attr("points")))},plot:function(t){return this.attr("points",this._array=new m.PointArray(t))},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var i=r(this.bbox(),t,e);return this.attr("points",this.array().size(i.width,i.height))}}),m.extend(m.Line,m.Polyline,m.Polygon,{morphArray:m.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)}}),m.Path=m.invent({create:"path",inherit:m.Shape,extend:{morphArray:m.PathArray,array:function(){return this._array||(this._array=new m.PathArray(this.attr("d")))},plot:function(t){return this.attr("d",this._array=new m.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 i=r(this.bbox(),t,e);return this.attr("d",this.array().size(i.width,i.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 m.Path).plot(t)}}}),m.Image=m.invent({create:"image",inherit:m.Shape,extend:{load:function(t){if(!t)return this;var e=this,i=document.createElement("img");return i.onload=function(){var n=e.doc(m.Pattern);0==e.width()&&0==e.height()&&e.size(i.width,i.height),n&&0==n.width()&&0==n.height()&&n.size(e.width(),e.height()),"function"==typeof e._loaded&&e._loaded.call(e,{width:i.width,height:i.height,ratio:i.width/i.height,url:t})},this.attr("href",i.src=this.src=t,m.xlink)},loaded:function(t){return this._loaded=t,this}},construct:{image:function(t,e,i){return this.put(new m.Image).load(t).size(e||0,i||e||0)}}}),m.Text=m.invent({create:function(){this.constructor.call(this,m.create("text")),this._leading=new m.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",m.defaults.attrs["font-family"])},inherit:m.Shape,extend:{x:function(t){return null==t?this.attr("x"):(this.textPath||this.lines().each(function(){this.newLined&&this.x(t)}),this.attr("x",t))},y:function(t){var e=this.attr("y"),i="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-i:e:this.attr("y","number"==typeof t?t+i: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)return this.content;if(this.clear().build(!0),"function"==typeof t)t.call(this,this);else{t=(this.content=t).split("\n");for(var e=0,i=t.length;i>e;e++)this.tspan(t[e]).newLine()}return this.build(!1).rebuild()},size:function(t){return this.attr("font-size",t).rebuild()},leading:function(t){return null==t?this._leading:(this._leading=new m.Number(t),this.rebuild())},lines:function(){for(var t=0,e=this.node.childNodes.length,i=[];e>t;t++)this.node.childNodes[t]instanceof SVGElement&&i.push(m.adopt(this.node.childNodes[t]));return new m.Set(i)},rebuild:function(t){if("boolean"==typeof t&&(this._rebuild=t),this._rebuild){var e=this;this.lines().each(function(){this.newLined&&(this.textPath||this.attr("x",e.attr("x")),this.attr("dy",e._leading*new m.Number(e.attr("font-size"))))}),this.fire("rebuild")}return this},build:function(t){return this._build=!!t,this}},construct:{text:function(t){return this.put(new m.Text).text(t)},plain:function(t){return this.put(new m.Text).plain(t)}}}),m.Tspan=m.invent({create:"tspan",inherit:m.Shape,extend:{text:function(t){return"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.doc(m.Text);return this.newLined=!0,this.dy(t._leading*t.attr("font-size")).attr("x",t.x())}}}),m.extend(m.Text,m.Tspan,{plain:function(t){return this._build===!1&&this.clear(),this.node.appendChild(document.createTextNode(this.content=t)),this},tspan:function(t){var e=(this.textPath()||this).node,i=new m.Tspan;return this._build===!1&&this.clear(),e.appendChild(i.node),this instanceof m.Text&&this.lines().add(i),i.text(t)},clear:function(){for(var t=(this.textPath()||this).node;t.hasChildNodes();)t.removeChild(t.lastChild);return this instanceof m.Text&&(this.content=""),this},length:function(){return this.node.getComputedTextLength()}}),m.TextPath=m.invent({create:"textPath",inherit:m.Element,parent:m.Text,construct:{path:function(t){for(var e=new m.TextPath,i=this.doc().defs().path(t);this.node.hasChildNodes();)e.node.appendChild(this.node.firstChild);return this.node.appendChild(e.node),e.attr("href","#"+i,m.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?m.adopt(this.node.firstChild):void 0}}}),m.Nested=m.invent({create:function(){this.constructor.call(this,m.create("svg")),this.style("overflow","visible")},inherit:m.Container,construct:{nested:function(){return this.put(new m.Nested)}}}),m.A=m.invent({create:"a",inherit:m.Container,extend:{to:function(t){return this.attr("href",t,m.xlink)},show:function(t){return this.attr("show",t,m.xlink)},target:function(t){return this.attr("target",t)}},construct:{link:function(t){return this.put(new m.A).to(t)}}}),m.extend(m.Element,{linkTo:function(t){var e=new m.A;return"function"==typeof t?t.call(e,e):e.to(t),this.parent().put(e).put(this)}}),m.Marker=m.invent({create:"marker",inherit:m.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,i){return this.defs().marker(t,e,i)}}}),m.extend(m.Defs,{marker:function(t,e,i){return this.put(new m.Marker).size(t,e).ref(t/2,e/2).viewbox(0,0,t,e).attr("orient","auto").update(i)}}),m.extend(m.Line,m.Polyline,m.Polygon,m.Path,{marker:function(t,e,i,n){var r=["marker"];return"all"!=t&&r.push(t),r=r.join("-"),t=arguments[1]instanceof m.Marker?arguments[1]:this.doc().marker(e,i,n),this.attr(r,t)}});var x={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,i={};i[t]=function(i){if("string"==typeof i||m.Color.isRgb(i)||i&&"function"==typeof i.fill)this.attr(t,i);else for(e=x[t].length-1;e>=0;e--)null!=i[x[t][e]]&&this.attr(x.prefix(t,x[t][e]),i[x[t][e]]);return this},m.extend(m.Element,m.FX,i)}),m.extend(m.Element,m.FX,{rotate:function(t,e,i){return this.transform({rotation:t,cx:e,cy:i})},skew:function(t,e,i,n){return this.transform({skewX:t,skewY:e,cx:i,cy:n})},scale:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({scale:t,cx:e,cy:i}):this.transform({scaleX:t,scaleY:e,cx:i,cy:n})},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 m.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)}}),m.extend(m.Rect,m.Ellipse,m.Circle,m.Gradient,m.FX,{radius:function(t,e){return"radial"==(this.target||this).type?this.attr({r:new m.Number(t)}):this.rx(t).ry(null==e?t:e)}}),m.extend(m.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),m.extend(m.Parent,m.Text,m.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}}),m.Set=m.invent({create:function(t){Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){var t,e,i=[].slice.call(arguments);for(t=0,e=i.length;e>t;t++)this.members.push(i[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,i=this.members.length;i>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 m.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 m.Set(t)}}}),m.FX.Set=m.invent({create:function(t){this.set=t}}),m.Set.inherit=function(){var t,e=[];for(var t in m.Shape.prototype)"function"==typeof m.Shape.prototype[t]&&"function"!=typeof m.Set.prototype[t]&&e.push(t);e.forEach(function(t){m.Set.prototype[t]=function(){for(var e=0,i=this.members.length;i>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 m.FX.Set(this)):this}}),e=[];for(var t in m.FX.prototype)"function"==typeof m.FX.prototype[t]&&"function"!=typeof m.FX.Set.prototype[t]&&e.push(t);e.forEach(function(t){m.FX.Set.prototype[t]=function(){for(var e=0,i=this.set.members.length;i>e;e++)this.set.members[e].fx[t].apply(this.set.members[e].fx,arguments);return this}})},m.extend(m.Element,{data:function(t,e,i){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(n){return this.attr("data-"+t)}else this.attr("data-"+t,null===e?null:i===!0||"string"==typeof e||"number"==typeof e?e:JSON.stringify(e));return this}}),m.extend(m.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={})}}),m.get=function(t){var e=document.getElementById(p(t)||t);return e?m.adopt(e):void 0},m.select=function(t,e){return new m.Set(m.utils.map((e||document).querySelectorAll(t),function(t){return m.adopt(t)}))},m.extend(m.Parent,{select:function(t){return m.select(t,this.node)}});var g="abcdef".split("");if("function"!=typeof y){var y=function(t,e){e=e||{bubbles:!1,cancelable:!1,detail:void 0};var i=document.createEvent("CustomEvent");return i.initCustomEvent(t,e.bubbles,e.cancelable,e.detail),i};y.prototype=window.Event.prototype,window.CustomEvent=y}return function(t){for(var e=0,i=["moz","webkit"],n=0;n<i.length&&!window.requestAnimationFrame;++n)t.requestAnimationFrame=t[i[n]+"RequestAnimationFrame"],t.cancelAnimationFrame=t[i[n]+"CancelAnimationFrame"]||t[i[n]+"CancelRequestAnimationFrame"];t.requestAnimationFrame=t.requestAnimationFrame||function(i){var n=(new Date).getTime(),r=Math.max(0,16-(n-e)),s=t.setTimeout(function(){i(n+r)},r);return e=n+r,s},t.cancelAnimationFrame=t.cancelAnimationFrame||t.clearTimeout}(window),m});
\ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..fe8b0b3 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,147 @@ +var del = require('del')
+ , gulp = require('gulp')
+ , concat = require('gulp-concat')
+ , header = require('gulp-header')
+ , jasmine = require('gulp-jasmine')
+ , rename = require('gulp-rename')
+ , size = require('gulp-size')
+ , uglify = require('gulp-uglify')
+ , wrapUmd = require('gulp-wrap-umd')
+ , wrapper = require('gulp-wrapper')
+ , request = require('request')
+ , fs = require('fs')
+ , pkg = require('./package.json')
+
+
+var headerLong = ['/*!'
+ , '* <%= pkg.name %> - <%= pkg.description %>'
+ , '* @version <%= pkg.version %>'
+ , '* <%= pkg.homepage %>'
+ , '*'
+ , '* @copyright <%= pkg.author %>'
+ , '* @license <%= pkg.license %>'
+ , '*'
+ , '* BUILT: <%= pkg.buildDate %>'
+ , '*/;'
+ , ''].join('\n')
+
+var headerShort = '/*! <%= pkg.name %> v<%= pkg.version %> <%= pkg.license %>*/;'
+
+// all files in the right order (currently we don't use any dependency management system)
+var parts = [
+ 'src/svg.js'
+, 'src/regex.js'
+, 'src/utilities.js'
+, 'src/default.js'
+, 'src/color.js'
+, 'src/array.js'
+, 'src/pointarray.js'
+, 'src/patharray.js'
+, 'src/number.js'
+, 'src/viewbox.js'
+, 'src/element.js'
+, 'src/fx.js'
+, 'src/boxes.js'
+, 'src/matrix.js'
+, 'src/attr.js'
+, 'src/transform.js'
+, 'src/style.js'
+, 'src/parent.js'
+, 'src/container.js'
+, 'src/event.js'
+, 'src/defs.js'
+, 'src/group.js'
+, 'src/arrange.js'
+, 'src/mask.js'
+, 'src/clip.js'
+, 'src/gradient.js'
+, 'src/pattern.js'
+, 'src/doc.js'
+, 'src/shape.js'
+, 'src/bare.js'
+, 'src/use.js'
+, 'src/rect.js'
+, 'src/ellipse.js'
+, 'src/line.js'
+, 'src/poly.js'
+, 'src/pointed.js'
+, 'src/path.js'
+, 'src/image.js'
+, 'src/text.js'
+, 'src/textpath.js'
+, 'src/nested.js'
+, 'src/hyperlink.js'
+, 'src/marker.js'
+, 'src/sugar.js'
+, 'src/set.js'
+, 'src/data.js'
+, 'src/memory.js'
+, 'src/selector.js'
+, 'src/helpers.js'
+, 'src/polyfill.js'
+]
+
+gulp.task('clean', function(cb) {
+ del([ 'dist/*' ], cb);
+})
+
+/**
+ * Compile everything in /src to one unified file in the order defined in the MODULES constant
+ * wrap the whole thing in a UMD wrapper (@see https://github.com/umdjs/umd)
+ * add the license information to the header plus the build time stamp
+ */
+gulp.task('unify', ['clean'], function() {
+ pkg.buildDate = Date()
+ return gulp.src(parts)
+ .pipe(concat('svg.js', { newLine: '\n' }))
+ // wrap the whole thing in an immediate function call
+ .pipe(wrapUmd({
+ namespace: 'SVG',
+ exports: 'SVG'
+ }))
+ .pipe(header(headerLong, { pkg: pkg }))
+ .pipe(gulp.dest('dist'))
+ .pipe(size({ showFiles: true, title: 'Full' }))
+})
+
+/**
+ * uglify the file and show the size of the result
+ * add the license info
+ * show the gzipped file size
+ */
+gulp.task('minify', ['unify'], function() {
+ return gulp.src('dist/svg.js')
+ .pipe(uglify())
+ .pipe(rename({ suffix:'.min' }))
+ .pipe(size({ showFiles: true, title: 'Minified' }))
+ .pipe(header(headerShort, { pkg: pkg }))
+ .pipe(gulp.dest('dist'))
+ .pipe(size({ showFiles: true, gzip: true, title: 'Gzipped' }))
+})
+
+/**
+ * rebuild documentation using documentup
+ */
+
+gulp.task('docs', function() {
+ fs.readFile('README.md', 'utf8', function (err, data) {
+ request.post(
+ 'http://documentup.com/compiled'
+ , { form: { content: data, name: 'SVG.js', theme: 'v1' } }
+ , function (error, response, body) {
+ // Replace stylesheet
+ body = body.replace('//documentup.com/stylesheets/themes/v1.css', 'svgjs.css')
+
+ // Write file
+ fs.writeFile('docs/index.html', body, function(err) {})
+ }
+ )
+ })
+})
+
+gulp.task('default', ['clean', 'unify', 'minify'], function() {})
+
+
+
+
+
diff --git a/package.json b/package.json index a443233..5b07d3d 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,63 @@ { - "name": "svg.js" -, "description": "A lightweight library for manipulating and animating SVG." -, "url": "http://svgjs.com" -, "homepage": "http://svgjs.com" -, "keywords": ["svg", "vector", "graphics", "animation"] -, "author": "Wout Fierens <wout@impinc.co.uk>" -, "main": "dist/svg.js" -, "version": "1.1.0" -, "jam": { + "name": "SVG.js", + "version": "2.0.0-rc.2", + "description": "A lightweight library for manipulating and animating SVG.", + "url": "http://svgjs.com", + "homepage": "http://www.svgjs.com", + "keywords": [ + "svg", + "vector", + "graphics", + "animation" + ], + "author": "Wout Fierens <wout@impinc.co.uk>", + "main": "dist/svg.js", + "jam": { "include": [ - "dist/svg.js" - , "README.md" - , "MIT-LICENSE" + "dist/svg.js", + "README.md", + "LICENSE.txt" ] - } -, "maintainers": [ + }, + "maintainers": [ + { + "name": "Wout Fierens", + "email": "wout@svgjs.com", + "web": "http://svgjs.com" + }, + { + "name": "Alex Ewerlöf", + "email": "alex@userpixel.com", + "web": "http://www.ewerlof.name" + }, { - "name": "Wout Fierens" - , "email": "wout@woutfierens.com" - , "web": "http://woutfierens.com" + "name": "Ulrich-Matthias Schäfer", + "email": "ulima.ums@googlemail.com" } - ] -, "licenses": [ + ], + "licenses": [ { - "type": "MIT" - , "url": "http://www.opensource.org/licenses/mit-license.php" + "type": "MIT", + "url": "http://www.opensource.org/licenses/mit-license.php" } - ] -, "repository": { - "type": "git" - , "url": "https://github.com/wout/svg.js.git" + ], + "repository": { + "type": "git", + "url": "https://github.com/wout/svg.js.git" + }, + "github": "https://github.com/wout/svg.js", + "license": "MIT", + "devDependencies": { + "del": "^1.2.0", + "gulp": "^3.8.6", + "gulp-concat": "^2.3.3", + "gulp-header": "^1.0.5", + "gulp-jasmine": "^0.3.0", + "gulp-rename": "^1.2.0", + "gulp-size": "^0.4.0", + "gulp-uglify": "^0.3.1", + "gulp-wrap-umd": "^0.2.1", + "gulp-wrapper": "^0.1.42", + "request": "^2.37.0" } -, "github": "https://github.com/wout/svg.js" } diff --git a/spec/index.html b/spec/index.html index a40e8d2..526cda6 100644..100755 --- a/spec/index.html +++ b/spec/index.html @@ -2,10 +2,10 @@ "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> - <title>Jasmine Spec Runner</title> - - <link rel="shortcut icon" type="image/png" href="lib/jasmine-1.3.1/jasmine_favicon.png"> - <link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css"> + <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 { @@ -19,49 +19,72 @@ </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 type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script> -<script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script> +<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" type="text/javascript" charset="utf-8"></script> +<script src="../dist/svg.js" charset="utf-8"></script> <!-- include spec files here... --> -<script type="text/javascript" src="spec/svg.js"></script> -<script type="text/javascript" src="spec/selector.js"></script> -<script type="text/javascript" src="spec/regex.js"></script> -<script type="text/javascript" src="spec/container.js"></script> -<script type="text/javascript" src="spec/element.js"></script> -<script type="text/javascript" src="spec/memory.js"></script> -<script type="text/javascript" src="spec/arrange.js"></script> -<script type="text/javascript" src="spec/event.js"></script> -<script type="text/javascript" src="spec/bbox.js"></script> -<script type="text/javascript" src="spec/rect.js"></script> -<script type="text/javascript" src="spec/ellipse.js"></script> -<script type="text/javascript" src="spec/line.js"></script> -<script type="text/javascript" src="spec/polyline.js"></script> -<script type="text/javascript" src="spec/polygon.js"></script> -<script type="text/javascript" src="spec/path.js"></script> -<script type="text/javascript" src="spec/marker.js"></script> -<script type="text/javascript" src="spec/image.js"></script> -<script type="text/javascript" src="spec/text.js"></script> -<script type="text/javascript" src="spec/textpath.js"></script> -<script type="text/javascript" src="spec/doc.js"></script> -<script type="text/javascript" src="spec/defs.js"></script> -<script type="text/javascript" src="spec/group.js"></script> -<script type="text/javascript" src="spec/set.js"></script> -<script type="text/javascript" src="spec/gradient.js"></script> -<script type="text/javascript" src="spec/pattern.js"></script> -<script type="text/javascript" src="spec/use.js"></script> -<script type="text/javascript" src="spec/symbol.js"></script> -<script type="text/javascript" src="spec/mask.js"></script> -<script type="text/javascript" src="spec/clip.js"></script> -<script type="text/javascript" src="spec/color.js"></script> -<script type="text/javascript" src="spec/number.js"></script> -<script type="text/javascript" src="spec/array.js"></script> -<script type="text/javascript" src="spec/hyperlink.js"></script> -<script type="text/javascript" src="spec/fx.js"></script> +<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() { diff --git a/spec/lib/jasmine-1.3.1/MIT.LICENSE b/spec/lib/jasmine-1.3.1/MIT.LICENSE deleted file mode 100644 index 7c435ba..0000000 --- a/spec/lib/jasmine-1.3.1/MIT.LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2008-2011 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-1.3.1/jasmine-html.js b/spec/lib/jasmine-1.3.1/jasmine-html.js deleted file mode 100644 index 543d569..0000000 --- a/spec/lib/jasmine-1.3.1/jasmine-html.js +++ /dev/null @@ -1,681 +0,0 @@ -jasmine.HtmlReporterHelpers = {}; - -jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { - var el = document.createElement(type); - - for (var i = 2; i < arguments.length; i++) { - var child = arguments[i]; - - if (typeof child === 'string') { - el.appendChild(document.createTextNode(child)); - } else { - if (child) { - el.appendChild(child); - } - } - } - - for (var attr in attrs) { - if (attr == "className") { - el[attr] = attrs[attr]; - } else { - el.setAttribute(attr, attrs[attr]); - } - } - - return el; -}; - -jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { - var results = child.results(); - var status = results.passed() ? 'passed' : 'failed'; - if (results.skipped) { - status = 'skipped'; - } - - return status; -}; - -jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { - var parentDiv = this.dom.summary; - var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; - var parent = child[parentSuite]; - - if (parent) { - if (typeof this.views.suites[parent.id] == 'undefined') { - this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); - } - parentDiv = this.views.suites[parent.id].element; - } - - parentDiv.appendChild(childElement); -}; - - -jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { - for(var fn in jasmine.HtmlReporterHelpers) { - ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; - } -}; - -jasmine.HtmlReporter = function(_doc) { - var self = this; - var doc = _doc || window.document; - - var reporterView; - - var dom = {}; - - // Jasmine Reporter Public Interface - self.logRunningSpecs = false; - - self.reportRunnerStarting = function(runner) { - var specs = runner.specs() || []; - - if (specs.length == 0) { - return; - } - - createReporterDom(runner.env.versionString()); - doc.body.appendChild(dom.reporter); - setExceptionHandling(); - - reporterView = new jasmine.HtmlReporter.ReporterView(dom); - reporterView.addSpecs(specs, self.specFilter); - }; - - self.reportRunnerResults = function(runner) { - reporterView && reporterView.complete(); - }; - - self.reportSuiteResults = function(suite) { - reporterView.suiteComplete(suite); - }; - - self.reportSpecStarting = function(spec) { - if (self.logRunningSpecs) { - self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); - } - }; - - self.reportSpecResults = function(spec) { - reporterView.specComplete(spec); - }; - - self.log = function() { - var console = jasmine.getGlobal().console; - if (console && console.log) { - if (console.log.apply) { - console.log.apply(console, arguments); - } else { - console.log(arguments); // ie fix: console.log.apply doesn't exist on ie - } - } - }; - - self.specFilter = function(spec) { - if (!focusedSpecName()) { - return true; - } - - return spec.getFullName().indexOf(focusedSpecName()) === 0; - }; - - return self; - - function focusedSpecName() { - var specName; - - (function memoizeFocusedSpec() { - if (specName) { - return; - } - - var paramMap = []; - var params = jasmine.HtmlReporter.parameters(doc); - - for (var i = 0; i < params.length; i++) { - var p = params[i].split('='); - paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); - } - - specName = paramMap.spec; - })(); - - return specName; - } - - function createReporterDom(version) { - dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, - dom.banner = self.createDom('div', { className: 'banner' }, - self.createDom('span', { className: 'title' }, "Jasmine "), - self.createDom('span', { className: 'version' }, version)), - - dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), - dom.alert = self.createDom('div', {className: 'alert'}, - self.createDom('span', { className: 'exceptions' }, - self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'), - self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))), - dom.results = self.createDom('div', {className: 'results'}, - dom.summary = self.createDom('div', { className: 'summary' }), - dom.details = self.createDom('div', { id: 'details' })) - ); - } - - function noTryCatch() { - return window.location.search.match(/catch=false/); - } - - function searchWithCatch() { - var params = jasmine.HtmlReporter.parameters(window.document); - var removed = false; - var i = 0; - - while (!removed && i < params.length) { - if (params[i].match(/catch=/)) { - params.splice(i, 1); - removed = true; - } - i++; - } - if (jasmine.CATCH_EXCEPTIONS) { - params.push("catch=false"); - } - - return params.join("&"); - } - - function setExceptionHandling() { - var chxCatch = document.getElementById('no_try_catch'); - - if (noTryCatch()) { - chxCatch.setAttribute('checked', true); - jasmine.CATCH_EXCEPTIONS = false; - } - chxCatch.onclick = function() { - window.location.search = searchWithCatch(); - }; - } -}; -jasmine.HtmlReporter.parameters = function(doc) { - var paramStr = doc.location.search.substring(1); - var params = []; - - if (paramStr.length > 0) { - params = paramStr.split('&'); - } - return params; -} -jasmine.HtmlReporter.sectionLink = function(sectionName) { - var link = '?'; - var params = []; - - if (sectionName) { - params.push('spec=' + encodeURIComponent(sectionName)); - } - if (!jasmine.CATCH_EXCEPTIONS) { - params.push("catch=false"); - } - if (params.length > 0) { - link += params.join("&"); - } - - return link; -}; -jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); -jasmine.HtmlReporter.ReporterView = function(dom) { - this.startedAt = new Date(); - this.runningSpecCount = 0; - this.completeSpecCount = 0; - this.passedCount = 0; - this.failedCount = 0; - this.skippedCount = 0; - - this.createResultsMenu = function() { - this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, - this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), - ' | ', - this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); - - this.summaryMenuItem.onclick = function() { - dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); - }; - - this.detailsMenuItem.onclick = function() { - showDetails(); - }; - }; - - this.addSpecs = function(specs, specFilter) { - this.totalSpecCount = specs.length; - - this.views = { - specs: {}, - suites: {} - }; - - for (var i = 0; i < specs.length; i++) { - var spec = specs[i]; - this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); - if (specFilter(spec)) { - this.runningSpecCount++; - } - } - }; - - this.specComplete = function(spec) { - this.completeSpecCount++; - - if (isUndefined(this.views.specs[spec.id])) { - this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); - } - - var specView = this.views.specs[spec.id]; - - switch (specView.status()) { - case 'passed': - this.passedCount++; - break; - - case 'failed': - this.failedCount++; - break; - - case 'skipped': - this.skippedCount++; - break; - } - - specView.refresh(); - this.refresh(); - }; - - this.suiteComplete = function(suite) { - var suiteView = this.views.suites[suite.id]; - if (isUndefined(suiteView)) { - return; - } - suiteView.refresh(); - }; - - this.refresh = function() { - - if (isUndefined(this.resultsMenu)) { - this.createResultsMenu(); - } - - // currently running UI - if (isUndefined(this.runningAlert)) { - this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" }); - dom.alert.appendChild(this.runningAlert); - } - this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); - - // skipped specs UI - if (isUndefined(this.skippedAlert)) { - this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" }); - } - - this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; - - if (this.skippedCount === 1 && isDefined(dom.alert)) { - dom.alert.appendChild(this.skippedAlert); - } - - // passing specs UI - if (isUndefined(this.passedAlert)) { - this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" }); - } - this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); - - // failing specs UI - if (isUndefined(this.failedAlert)) { - this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); - } - this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); - - if (this.failedCount === 1 && isDefined(dom.alert)) { - dom.alert.appendChild(this.failedAlert); - dom.alert.appendChild(this.resultsMenu); - } - - // summary info - this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); - this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; - }; - - this.complete = function() { - dom.alert.removeChild(this.runningAlert); - - this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; - - if (this.failedCount === 0) { - dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); - } else { - showDetails(); - } - - dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); - }; - - return this; - - function showDetails() { - if (dom.reporter.className.search(/showDetails/) === -1) { - dom.reporter.className += " showDetails"; - } - } - - function isUndefined(obj) { - return typeof obj === 'undefined'; - } - - function isDefined(obj) { - return !isUndefined(obj); - } - - function specPluralizedFor(count) { - var str = count + " spec"; - if (count > 1) { - str += "s" - } - return str; - } - -}; - -jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); - - -jasmine.HtmlReporter.SpecView = function(spec, dom, views) { - this.spec = spec; - this.dom = dom; - this.views = views; - - this.symbol = this.createDom('li', { className: 'pending' }); - this.dom.symbolSummary.appendChild(this.symbol); - - this.summary = this.createDom('div', { className: 'specSummary' }, - this.createDom('a', { - className: 'description', - href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()), - title: this.spec.getFullName() - }, this.spec.description) - ); - - this.detail = this.createDom('div', { className: 'specDetail' }, - this.createDom('a', { - className: 'description', - href: '?spec=' + encodeURIComponent(this.spec.getFullName()), - title: this.spec.getFullName() - }, this.spec.getFullName()) - ); -}; - -jasmine.HtmlReporter.SpecView.prototype.status = function() { - return this.getSpecStatus(this.spec); -}; - -jasmine.HtmlReporter.SpecView.prototype.refresh = function() { - this.symbol.className = this.status(); - - switch (this.status()) { - case 'skipped': - break; - - case 'passed': - this.appendSummaryToSuiteDiv(); - break; - - case 'failed': - this.appendSummaryToSuiteDiv(); - this.appendFailureDetail(); - break; - } -}; - -jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { - this.summary.className += ' ' + this.status(); - this.appendToSummary(this.spec, this.summary); -}; - -jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { - this.detail.className += ' ' + this.status(); - - var resultItems = this.spec.results().getItems(); - var messagesDiv = this.createDom('div', { className: 'messages' }); - - for (var i = 0; i < resultItems.length; i++) { - var result = resultItems[i]; - - if (result.type == 'log') { - messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); - } else if (result.type == 'expect' && result.passed && !result.passed()) { - messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); - - if (result.trace.stack) { - messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); - } - } - } - - if (messagesDiv.childNodes.length > 0) { - this.detail.appendChild(messagesDiv); - this.dom.details.appendChild(this.detail); - } -}; - -jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { - this.suite = suite; - this.dom = dom; - this.views = views; - - this.element = this.createDom('div', { className: 'suite' }, - this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description) - ); - - this.appendToSummary(this.suite, this.element); -}; - -jasmine.HtmlReporter.SuiteView.prototype.status = function() { - return this.getSpecStatus(this.suite); -}; - -jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { - this.element.className += " " + this.status(); -}; - -jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); - -/* @deprecated Use jasmine.HtmlReporter instead - */ -jasmine.TrivialReporter = function(doc) { - this.document = doc || document; - this.suiteDivs = {}; - this.logRunningSpecs = false; -}; - -jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { - var el = document.createElement(type); - - for (var i = 2; i < arguments.length; i++) { - var child = arguments[i]; - - if (typeof child === 'string') { - el.appendChild(document.createTextNode(child)); - } else { - if (child) { el.appendChild(child); } - } - } - - for (var attr in attrs) { - if (attr == "className") { - el[attr] = attrs[attr]; - } else { - el.setAttribute(attr, attrs[attr]); - } - } - - return el; -}; - -jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { - var showPassed, showSkipped; - - this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, - this.createDom('div', { className: 'banner' }, - this.createDom('div', { className: 'logo' }, - this.createDom('span', { className: 'title' }, "Jasmine"), - this.createDom('span', { className: 'version' }, runner.env.versionString())), - this.createDom('div', { className: 'options' }, - "Show ", - showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), - this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), - showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), - this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") - ) - ), - - this.runnerDiv = this.createDom('div', { className: 'runner running' }, - this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), - this.runnerMessageSpan = this.createDom('span', {}, "Running..."), - this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) - ); - - this.document.body.appendChild(this.outerDiv); - - var suites = runner.suites(); - for (var i = 0; i < suites.length; i++) { - var suite = suites[i]; - var suiteDiv = this.createDom('div', { className: 'suite' }, - this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), - this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); - this.suiteDivs[suite.id] = suiteDiv; - var parentDiv = this.outerDiv; - if (suite.parentSuite) { - parentDiv = this.suiteDivs[suite.parentSuite.id]; - } - parentDiv.appendChild(suiteDiv); - } - - this.startedAt = new Date(); - - var self = this; - showPassed.onclick = function(evt) { - if (showPassed.checked) { - self.outerDiv.className += ' show-passed'; - } else { - self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); - } - }; - - showSkipped.onclick = function(evt) { - if (showSkipped.checked) { - self.outerDiv.className += ' show-skipped'; - } else { - self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); - } - }; -}; - -jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { - var results = runner.results(); - var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; - this.runnerDiv.setAttribute("class", className); - //do it twice for IE - this.runnerDiv.setAttribute("className", className); - var specs = runner.specs(); - var specCount = 0; - for (var i = 0; i < specs.length; i++) { - if (this.specFilter(specs[i])) { - specCount++; - } - } - var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); - message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; - this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); - - this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); -}; - -jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { - var results = suite.results(); - var status = results.passed() ? 'passed' : 'failed'; - if (results.totalCount === 0) { // todo: change this to check results.skipped - status = 'skipped'; - } - this.suiteDivs[suite.id].className += " " + status; -}; - -jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { - if (this.logRunningSpecs) { - this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); - } -}; - -jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { - var results = spec.results(); - var status = results.passed() ? 'passed' : 'failed'; - if (results.skipped) { - status = 'skipped'; - } - var specDiv = this.createDom('div', { className: 'spec ' + status }, - this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), - this.createDom('a', { - className: 'description', - href: '?spec=' + encodeURIComponent(spec.getFullName()), - title: spec.getFullName() - }, spec.description)); - - - var resultItems = results.getItems(); - var messagesDiv = this.createDom('div', { className: 'messages' }); - for (var i = 0; i < resultItems.length; i++) { - var result = resultItems[i]; - - if (result.type == 'log') { - messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); - } else if (result.type == 'expect' && result.passed && !result.passed()) { - messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); - - if (result.trace.stack) { - messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); - } - } - } - - if (messagesDiv.childNodes.length > 0) { - specDiv.appendChild(messagesDiv); - } - - this.suiteDivs[spec.suite.id].appendChild(specDiv); -}; - -jasmine.TrivialReporter.prototype.log = function() { - var console = jasmine.getGlobal().console; - if (console && console.log) { - if (console.log.apply) { - console.log.apply(console, arguments); - } else { - console.log(arguments); // ie fix: console.log.apply doesn't exist on ie - } - } -}; - -jasmine.TrivialReporter.prototype.getLocation = function() { - return this.document.location; -}; - -jasmine.TrivialReporter.prototype.specFilter = function(spec) { - var paramMap = {}; - var params = this.getLocation().search.substring(1).split('&'); - for (var i = 0; i < params.length; i++) { - var p = params[i].split('='); - paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); - } - - if (!paramMap.spec) { - return true; - } - return spec.getFullName().indexOf(paramMap.spec) === 0; -}; diff --git a/spec/lib/jasmine-1.3.1/jasmine.css b/spec/lib/jasmine-1.3.1/jasmine.css deleted file mode 100644 index 8c008dc..0000000 --- a/spec/lib/jasmine-1.3.1/jasmine.css +++ /dev/null @@ -1,82 +0,0 @@ -body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } - -#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } -#HTMLReporter a { text-decoration: none; } -#HTMLReporter a:hover { text-decoration: underline; } -#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } -#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } -#HTMLReporter #jasmine_content { position: fixed; right: 100%; } -#HTMLReporter .version { color: #aaaaaa; } -#HTMLReporter .banner { margin-top: 14px; } -#HTMLReporter .duration { color: #aaaaaa; float: right; } -#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } -#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } -#HTMLReporter .symbolSummary li.passed { font-size: 14px; } -#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } -#HTMLReporter .symbolSummary li.failed { line-height: 9px; } -#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } -#HTMLReporter .symbolSummary li.skipped { font-size: 14px; } -#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } -#HTMLReporter .symbolSummary li.pending { line-height: 11px; } -#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } -#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } -#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } -#HTMLReporter .runningAlert { background-color: #666666; } -#HTMLReporter .skippedAlert { background-color: #aaaaaa; } -#HTMLReporter .skippedAlert:first-child { background-color: #333333; } -#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } -#HTMLReporter .passingAlert { background-color: #a6b779; } -#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } -#HTMLReporter .failingAlert { background-color: #cf867e; } -#HTMLReporter .failingAlert:first-child { background-color: #b03911; } -#HTMLReporter .results { margin-top: 14px; } -#HTMLReporter #details { display: none; } -#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } -#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } -#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } -#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } -#HTMLReporter.showDetails .summary { display: none; } -#HTMLReporter.showDetails #details { display: block; } -#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } -#HTMLReporter .summary { margin-top: 14px; } -#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } -#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } -#HTMLReporter .summary .specSummary.failed a { color: #b03911; } -#HTMLReporter .description + .suite { margin-top: 0; } -#HTMLReporter .suite { margin-top: 14px; } -#HTMLReporter .suite a { color: #333333; } -#HTMLReporter #details .specDetail { margin-bottom: 28px; } -#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } -#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } -#HTMLReporter .resultMessage span.result { display: block; } -#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } - -#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } -#TrivialReporter a:visited, #TrivialReporter a { color: #303; } -#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } -#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } -#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } -#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } -#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } -#TrivialReporter .runner.running { background-color: yellow; } -#TrivialReporter .options { text-align: right; font-size: .8em; } -#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } -#TrivialReporter .suite .suite { margin: 5px; } -#TrivialReporter .suite.passed { background-color: #dfd; } -#TrivialReporter .suite.failed { background-color: #fdd; } -#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } -#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } -#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } -#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } -#TrivialReporter .spec.skipped { background-color: #bbb; } -#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } -#TrivialReporter .passed { background-color: #cfc; display: none; } -#TrivialReporter .failed { background-color: #fbb; } -#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } -#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } -#TrivialReporter .resultMessage .mismatch { color: black; } -#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } -#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } -#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } -#TrivialReporter #jasmine_content { position: fixed; right: 100%; } -#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } diff --git a/spec/lib/jasmine-1.3.1/jasmine.js b/spec/lib/jasmine-1.3.1/jasmine.js deleted file mode 100644 index 476d4b8..0000000 --- a/spec/lib/jasmine-1.3.1/jasmine.js +++ /dev/null @@ -1,2600 +0,0 @@ -var isCommonJS = typeof window == "undefined" && typeof exports == "object"; - -/** - * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. - * - * @namespace - */ -var jasmine = {}; -if (isCommonJS) exports.jasmine = jasmine; -/** - * @private - */ -jasmine.unimplementedMethod_ = function() { - throw new Error("unimplemented method"); -}; - -/** - * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just - * a plain old variable and may be redefined by somebody else. - * - * @private - */ -jasmine.undefined = jasmine.___undefined___; - -/** - * Show diagnostic messages in the console if set to true - * - */ -jasmine.VERBOSE = false; - -/** - * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. - * - */ -jasmine.DEFAULT_UPDATE_INTERVAL = 250; - -/** - * Maximum levels of nesting that will be included when an object is pretty-printed - */ -jasmine.MAX_PRETTY_PRINT_DEPTH = 40; - -/** - * Default timeout interval in milliseconds for waitsFor() blocks. - */ -jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; - -/** - * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite. - * Set to false to let the exception bubble up in the browser. - * - */ -jasmine.CATCH_EXCEPTIONS = true; - -jasmine.getGlobal = function() { - function getGlobal() { - return this; - } - - return getGlobal(); -}; - -/** - * Allows for bound functions to be compared. Internal use only. - * - * @ignore - * @private - * @param base {Object} bound 'this' for the function - * @param name {Function} function to find - */ -jasmine.bindOriginal_ = function(base, name) { - var original = base[name]; - if (original.apply) { - return function() { - return original.apply(base, arguments); - }; - } else { - // IE support - return jasmine.getGlobal()[name]; - } -}; - -jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); -jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); -jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); -jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); - -jasmine.MessageResult = function(values) { - this.type = 'log'; - this.values = values; - this.trace = new Error(); // todo: test better -}; - -jasmine.MessageResult.prototype.toString = function() { - var text = ""; - for (var i = 0; i < this.values.length; i++) { - if (i > 0) text += " "; - if (jasmine.isString_(this.values[i])) { - text += this.values[i]; - } else { - text += jasmine.pp(this.values[i]); - } - } - return text; -}; - -jasmine.ExpectationResult = function(params) { - this.type = 'expect'; - this.matcherName = params.matcherName; - this.passed_ = params.passed; - this.expected = params.expected; - this.actual = params.actual; - this.message = this.passed_ ? 'Passed.' : params.message; - - var trace = (params.trace || new Error(this.message)); - this.trace = this.passed_ ? '' : trace; -}; - -jasmine.ExpectationResult.prototype.toString = function () { - return this.message; -}; - -jasmine.ExpectationResult.prototype.passed = function () { - return this.passed_; -}; - -/** - * Getter for the Jasmine environment. Ensures one gets created - */ -jasmine.getEnv = function() { - var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); - return env; -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isArray_ = function(value) { - return jasmine.isA_("Array", value); -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isString_ = function(value) { - return jasmine.isA_("String", value); -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isNumber_ = function(value) { - return jasmine.isA_("Number", value); -}; - -/** - * @ignore - * @private - * @param {String} typeName - * @param value - * @returns {Boolean} - */ -jasmine.isA_ = function(typeName, value) { - return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; -}; - -/** - * Pretty printer for expecations. Takes any object and turns it into a human-readable string. - * - * @param value {Object} an object to be outputted - * @returns {String} - */ -jasmine.pp = function(value) { - var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); - stringPrettyPrinter.format(value); - return stringPrettyPrinter.string; -}; - -/** - * Returns true if the object is a DOM Node. - * - * @param {Object} obj object to check - * @returns {Boolean} - */ -jasmine.isDomNode = function(obj) { - return obj.nodeType > 0; -}; - -/** - * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. - * - * @example - * // don't care about which function is passed in, as long as it's a function - * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); - * - * @param {Class} clazz - * @returns matchable object of the type clazz - */ -jasmine.any = function(clazz) { - return new jasmine.Matchers.Any(clazz); -}; - -/** - * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the - * attributes on the object. - * - * @example - * // don't care about any other attributes than foo. - * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"}); - * - * @param sample {Object} sample - * @returns matchable object for the sample - */ -jasmine.objectContaining = function (sample) { - return new jasmine.Matchers.ObjectContaining(sample); -}; - -/** - * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. - * - * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine - * expectation syntax. Spies can be checked if they were called or not and what the calling params were. - * - * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). - * - * Spies are torn down at the end of every spec. - * - * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. - * - * @example - * // a stub - * var myStub = jasmine.createSpy('myStub'); // can be used anywhere - * - * // spy example - * var foo = { - * not: function(bool) { return !bool; } - * } - * - * // actual foo.not will not be called, execution stops - * spyOn(foo, 'not'); - - // foo.not spied upon, execution will continue to implementation - * spyOn(foo, 'not').andCallThrough(); - * - * // fake example - * var foo = { - * not: function(bool) { return !bool; } - * } - * - * // foo.not(val) will return val - * spyOn(foo, 'not').andCallFake(function(value) {return value;}); - * - * // mock example - * foo.not(7 == 7); - * expect(foo.not).toHaveBeenCalled(); - * expect(foo.not).toHaveBeenCalledWith(true); - * - * @constructor - * @see spyOn, jasmine.createSpy, jasmine.createSpyObj - * @param {String} name - */ -jasmine.Spy = function(name) { - /** - * The name of the spy, if provided. - */ - this.identity = name || 'unknown'; - /** - * Is this Object a spy? - */ - this.isSpy = true; - /** - * The actual function this spy stubs. - */ - this.plan = function() { - }; - /** - * Tracking of the most recent call to the spy. - * @example - * var mySpy = jasmine.createSpy('foo'); - * mySpy(1, 2); - * mySpy.mostRecentCall.args = [1, 2]; - */ - this.mostRecentCall = {}; - - /** - * Holds arguments for each call to the spy, indexed by call count - * @example - * var mySpy = jasmine.createSpy('foo'); - * mySpy(1, 2); - * mySpy(7, 8); - * mySpy.mostRecentCall.args = [7, 8]; - * mySpy.argsForCall[0] = [1, 2]; - * mySpy.argsForCall[1] = [7, 8]; - */ - this.argsForCall = []; - this.calls = []; -}; - -/** - * Tells a spy to call through to the actual implemenatation. - * - * @example - * var foo = { - * bar: function() { // do some stuff } - * } - * - * // defining a spy on an existing property: foo.bar - * spyOn(foo, 'bar').andCallThrough(); - */ -jasmine.Spy.prototype.andCallThrough = function() { - this.plan = this.originalValue; - return this; -}; - -/** - * For setting the return value of a spy. - * - * @example - * // defining a spy from scratch: foo() returns 'baz' - * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); - * - * // defining a spy on an existing property: foo.bar() returns 'baz' - * spyOn(foo, 'bar').andReturn('baz'); - * - * @param {Object} value - */ -jasmine.Spy.prototype.andReturn = function(value) { - this.plan = function() { - return value; - }; - return this; -}; - -/** - * For throwing an exception when a spy is called. - * - * @example - * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' - * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); - * - * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' - * spyOn(foo, 'bar').andThrow('baz'); - * - * @param {String} exceptionMsg - */ -jasmine.Spy.prototype.andThrow = function(exceptionMsg) { - this.plan = function() { - throw exceptionMsg; - }; - return this; -}; - -/** - * Calls an alternate implementation when a spy is called. - * - * @example - * var baz = function() { - * // do some stuff, return something - * } - * // defining a spy from scratch: foo() calls the function baz - * var foo = jasmine.createSpy('spy on foo').andCall(baz); - * - * // defining a spy on an existing property: foo.bar() calls an anonymnous function - * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); - * - * @param {Function} fakeFunc - */ -jasmine.Spy.prototype.andCallFake = function(fakeFunc) { - this.plan = fakeFunc; - return this; -}; - -/** - * Resets all of a spy's the tracking variables so that it can be used again. - * - * @example - * spyOn(foo, 'bar'); - * - * foo.bar(); - * - * expect(foo.bar.callCount).toEqual(1); - * - * foo.bar.reset(); - * - * expect(foo.bar.callCount).toEqual(0); - */ -jasmine.Spy.prototype.reset = function() { - this.wasCalled = false; - this.callCount = 0; - this.argsForCall = []; - this.calls = []; - this.mostRecentCall = {}; -}; - -jasmine.createSpy = function(name) { - - var spyObj = function() { - spyObj.wasCalled = true; - spyObj.callCount++; - var args = jasmine.util.argsToArray(arguments); - spyObj.mostRecentCall.object = this; - spyObj.mostRecentCall.args = args; - spyObj.argsForCall.push(args); - spyObj.calls.push({object: this, args: args}); - return spyObj.plan.apply(this, arguments); - }; - - var spy = new jasmine.Spy(name); - - for (var prop in spy) { - spyObj[prop] = spy[prop]; - } - - spyObj.reset(); - - return spyObj; -}; - -/** - * Determines whether an object is a spy. - * - * @param {jasmine.Spy|Object} putativeSpy - * @returns {Boolean} - */ -jasmine.isSpy = function(putativeSpy) { - return putativeSpy && putativeSpy.isSpy; -}; - -/** - * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something - * large in one call. - * - * @param {String} baseName name of spy class - * @param {Array} methodNames array of names of methods to make spies - */ -jasmine.createSpyObj = function(baseName, methodNames) { - if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { - throw new Error('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]] = jasmine.createSpy(baseName + '.' + methodNames[i]); - } - return obj; -}; - -/** - * All parameters are pretty-printed and concatenated together, then written to the current spec's output. - * - * Be careful not to leave calls to <code>jasmine.log</code> in production code. - */ -jasmine.log = function() { - var spec = jasmine.getEnv().currentSpec; - spec.log.apply(spec, arguments); -}; - -/** - * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. - * - * @example - * // spy example - * var foo = { - * not: function(bool) { return !bool; } - * } - * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops - * - * @see jasmine.createSpy - * @param obj - * @param methodName - * @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods - */ -var spyOn = function(obj, methodName) { - return jasmine.getEnv().currentSpec.spyOn(obj, methodName); -}; -if (isCommonJS) exports.spyOn = spyOn; - -/** - * Creates a Jasmine spec that will be added to the current suite. - * - * // TODO: pending tests - * - * @example - * it('should be true', function() { - * expect(true).toEqual(true); - * }); - * - * @param {String} desc description of this specification - * @param {Function} func defines the preconditions and expectations of the spec - */ -var it = function(desc, func) { - return jasmine.getEnv().it(desc, func); -}; -if (isCommonJS) exports.it = it; - -/** - * Creates a <em>disabled</em> Jasmine spec. - * - * A convenience method that allows existing specs to be disabled temporarily during development. - * - * @param {String} desc description of this specification - * @param {Function} func defines the preconditions and expectations of the spec - */ -var xit = function(desc, func) { - return jasmine.getEnv().xit(desc, func); -}; -if (isCommonJS) exports.xit = xit; - -/** - * Starts a chain for a Jasmine expectation. - * - * It is passed an Object that is the actual value and should chain to one of the many - * jasmine.Matchers functions. - * - * @param {Object} actual Actual value to test against and expected value - * @return {jasmine.Matchers} - */ -var expect = function(actual) { - return jasmine.getEnv().currentSpec.expect(actual); -}; -if (isCommonJS) exports.expect = expect; - -/** - * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. - * - * @param {Function} func Function that defines part of a jasmine spec. - */ -var runs = function(func) { - jasmine.getEnv().currentSpec.runs(func); -}; -if (isCommonJS) exports.runs = runs; - -/** - * Waits a fixed time period before moving to the next block. - * - * @deprecated Use waitsFor() instead - * @param {Number} timeout milliseconds to wait - */ -var waits = function(timeout) { - jasmine.getEnv().currentSpec.waits(timeout); -}; -if (isCommonJS) exports.waits = waits; - -/** - * Waits for the latchFunction to return true before proceeding to the next block. - * - * @param {Function} latchFunction - * @param {String} optional_timeoutMessage - * @param {Number} optional_timeout - */ -var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { - jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); -}; -if (isCommonJS) exports.waitsFor = waitsFor; - -/** - * A function that is called before each spec in a suite. - * - * Used for spec setup, including validating assumptions. - * - * @param {Function} beforeEachFunction - */ -var beforeEach = function(beforeEachFunction) { - jasmine.getEnv().beforeEach(beforeEachFunction); -}; -if (isCommonJS) exports.beforeEach = beforeEach; - -/** - * A function that is called after each spec in a suite. - * - * Used for restoring any state that is hijacked during spec execution. - * - * @param {Function} afterEachFunction - */ -var afterEach = function(afterEachFunction) { - jasmine.getEnv().afterEach(afterEachFunction); -}; -if (isCommonJS) exports.afterEach = afterEach; - -/** - * Defines a suite of specifications. - * - * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared - * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization - * of setup in some tests. - * - * @example - * // TODO: a simple suite - * - * // TODO: a simple suite with a nested describe block - * - * @param {String} description A string, usually the class under test. - * @param {Function} specDefinitions function that defines several specs. - */ -var describe = function(description, specDefinitions) { - return jasmine.getEnv().describe(description, specDefinitions); -}; -if (isCommonJS) exports.describe = describe; - -/** - * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. - * - * @param {String} description A string, usually the class under test. - * @param {Function} specDefinitions function that defines several specs. - */ -var xdescribe = function(description, specDefinitions) { - return jasmine.getEnv().xdescribe(description, specDefinitions); -}; -if (isCommonJS) exports.xdescribe = xdescribe; - - -// Provide the XMLHttpRequest class for IE 5.x-6.x: -jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { - function tryIt(f) { - try { - return f(); - } catch(e) { - } - return null; - } - - var xhr = tryIt(function() { - return new ActiveXObject("Msxml2.XMLHTTP.6.0"); - }) || - tryIt(function() { - return new ActiveXObject("Msxml2.XMLHTTP.3.0"); - }) || - tryIt(function() { - return new ActiveXObject("Msxml2.XMLHTTP"); - }) || - tryIt(function() { - return new ActiveXObject("Microsoft.XMLHTTP"); - }); - - if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); - - return xhr; -} : XMLHttpRequest; -/** - * @namespace - */ -jasmine.util = {}; - -/** - * Declare that a child class inherit it's prototype from the parent class. - * - * @private - * @param {Function} childClass - * @param {Function} parentClass - */ -jasmine.util.inherit = function(childClass, parentClass) { - /** - * @private - */ - var subclass = function() { - }; - subclass.prototype = parentClass.prototype; - childClass.prototype = new subclass(); -}; - -jasmine.util.formatException = function(e) { - var lineNumber; - if (e.line) { - lineNumber = e.line; - } - else if (e.lineNumber) { - lineNumber = e.lineNumber; - } - - var file; - - if (e.sourceURL) { - file = e.sourceURL; - } - else if (e.fileName) { - file = e.fileName; - } - - var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); - - if (file && lineNumber) { - message += ' in ' + file + ' (line ' + lineNumber + ')'; - } - - return message; -}; - -jasmine.util.htmlEscape = function(str) { - if (!str) return str; - return str.replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>'); -}; - -jasmine.util.argsToArray = function(args) { - var arrayOfArgs = []; - for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); - return arrayOfArgs; -}; - -jasmine.util.extend = function(destination, source) { - for (var property in source) destination[property] = source[property]; - return destination; -}; - -/** - * Environment for Jasmine - * - * @constructor - */ -jasmine.Env = function() { - this.currentSpec = null; - this.currentSuite = null; - this.currentRunner_ = new jasmine.Runner(this); - - this.reporter = new jasmine.MultiReporter(); - - this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; - this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; - this.lastUpdate = 0; - this.specFilter = function() { - return true; - }; - - this.nextSpecId_ = 0; - this.nextSuiteId_ = 0; - this.equalityTesters_ = []; - - // wrap matchers - this.matchersClass = function() { - jasmine.Matchers.apply(this, arguments); - }; - jasmine.util.inherit(this.matchersClass, jasmine.Matchers); - - jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); -}; - - -jasmine.Env.prototype.setTimeout = jasmine.setTimeout; -jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; -jasmine.Env.prototype.setInterval = jasmine.setInterval; -jasmine.Env.prototype.clearInterval = jasmine.clearInterval; - -/** - * @returns an object containing jasmine version build info, if set. - */ -jasmine.Env.prototype.version = function () { - if (jasmine.version_) { - return jasmine.version_; - } else { - throw new Error('Version not set'); - } -}; - -/** - * @returns string containing jasmine version build info, if set. - */ -jasmine.Env.prototype.versionString = function() { - if (!jasmine.version_) { - return "version unknown"; - } - - var version = this.version(); - var versionString = version.major + "." + version.minor + "." + version.build; - if (version.release_candidate) { - versionString += ".rc" + version.release_candidate; - } - versionString += " revision " + version.revision; - return versionString; -}; - -/** - * @returns a sequential integer starting at 0 - */ -jasmine.Env.prototype.nextSpecId = function () { - return this.nextSpecId_++; -}; - -/** - * @returns a sequential integer starting at 0 - */ -jasmine.Env.prototype.nextSuiteId = function () { - return this.nextSuiteId_++; -}; - -/** - * Register a reporter to receive status updates from Jasmine. - * @param {jasmine.Reporter} reporter An object which will receive status updates. - */ -jasmine.Env.prototype.addReporter = function(reporter) { - this.reporter.addReporter(reporter); -}; - -jasmine.Env.prototype.execute = function() { - this.currentRunner_.execute(); -}; - -jasmine.Env.prototype.describe = function(description, specDefinitions) { - var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); - - var parentSuite = this.currentSuite; - if (parentSuite) { - parentSuite.add(suite); - } else { - this.currentRunner_.add(suite); - } - - this.currentSuite = suite; - - var declarationError = null; - try { - specDefinitions.call(suite); - } catch(e) { - declarationError = e; - } - - if (declarationError) { - this.it("encountered a declaration exception", function() { - throw declarationError; - }); - } - - this.currentSuite = parentSuite; - - return suite; -}; - -jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { - if (this.currentSuite) { - this.currentSuite.beforeEach(beforeEachFunction); - } else { - this.currentRunner_.beforeEach(beforeEachFunction); - } -}; - -jasmine.Env.prototype.currentRunner = function () { - return this.currentRunner_; -}; - -jasmine.Env.prototype.afterEach = function(afterEachFunction) { - if (this.currentSuite) { - this.currentSuite.afterEach(afterEachFunction); - } else { - this.currentRunner_.afterEach(afterEachFunction); - } - -}; - -jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { - return { - execute: function() { - } - }; -}; - -jasmine.Env.prototype.it = function(description, func) { - var spec = new jasmine.Spec(this, this.currentSuite, description); - this.currentSuite.add(spec); - this.currentSpec = spec; - - if (func) { - spec.runs(func); - } - - return spec; -}; - -jasmine.Env.prototype.xit = function(desc, func) { - return { - id: this.nextSpecId(), - runs: function() { - } - }; -}; - -jasmine.Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) { - if (a.source != b.source) - mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/"); - - if (a.ignoreCase != b.ignoreCase) - mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier"); - - if (a.global != b.global) - mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier"); - - if (a.multiline != b.multiline) - mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier"); - - if (a.sticky != b.sticky) - mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier"); - - return (mismatchValues.length === 0); -}; - -jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { - if (a.__Jasmine_offseteen_here_offsetefore__ === b && b.__Jasmine_offseteen_here_offsetefore__ === a) { - return true; - } - - a.__Jasmine_offseteen_here_offsetefore__ = b; - b.__Jasmine_offseteen_here_offsetefore__ = a; - - var hasKey = function(obj, keyName) { - return obj !== null && obj[keyName] !== jasmine.undefined; - }; - - for (var property in b) { - if (!hasKey(a, property) && hasKey(b, property)) { - mismatchKeys.push("expected has key '" + property + "', but missing from actual."); - } - } - for (property in a) { - if (!hasKey(b, property) && hasKey(a, property)) { - mismatchKeys.push("expected missing key '" + property + "', but present in actual."); - } - } - for (property in b) { - if (property == '__Jasmine_offseteen_here_offsetefore__') continue; - if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { - mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); - } - } - - if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { - mismatchValues.push("arrays were not the same length"); - } - - delete a.__Jasmine_offseteen_here_offsetefore__; - delete b.__Jasmine_offseteen_here_offsetefore__; - return (mismatchKeys.length === 0 && mismatchValues.length === 0); -}; - -jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { - mismatchKeys = mismatchKeys || []; - mismatchValues = mismatchValues || []; - - for (var i = 0; i < this.equalityTesters_.length; i++) { - var equalityTester = this.equalityTesters_[i]; - var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); - if (result !== jasmine.undefined) return result; - } - - if (a === b) return true; - - if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { - return (a == jasmine.undefined && b == jasmine.undefined); - } - - if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { - return a === b; - } - - if (a instanceof Date && b instanceof Date) { - return a.getTime() == b.getTime(); - } - - if (a.jasmineMatches) { - return a.jasmineMatches(b); - } - - if (b.jasmineMatches) { - return b.jasmineMatches(a); - } - - if (a instanceof jasmine.Matchers.ObjectContaining) { - return a.matches(b); - } - - if (b instanceof jasmine.Matchers.ObjectContaining) { - return b.matches(a); - } - - if (jasmine.isString_(a) && jasmine.isString_(b)) { - return (a == b); - } - - if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { - return (a == b); - } - - if (a instanceof RegExp && b instanceof RegExp) { - return this.compareRegExps_(a, b, mismatchKeys, mismatchValues); - } - - if (typeof a === "object" && typeof b === "object") { - return this.compareObjects_(a, b, mismatchKeys, mismatchValues); - } - - //Straight check - return (a === b); -}; - -jasmine.Env.prototype.contains_ = function(haystack, needle) { - if (jasmine.isArray_(haystack)) { - for (var i = 0; i < haystack.length; i++) { - if (this.equals_(haystack[i], needle)) return true; - } - return false; - } - return haystack.indexOf(needle) >= 0; -}; - -jasmine.Env.prototype.addEqualityTester = function(equalityTester) { - this.equalityTesters_.push(equalityTester); -}; -/** No-op base class for Jasmine reporters. - * - * @constructor - */ -jasmine.Reporter = function() { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportRunnerResults = function(runner) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSuiteResults = function(suite) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSpecStarting = function(spec) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSpecResults = function(spec) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.log = function(str) { -}; - -/** - * Blocks are functions with executable code that make up a spec. - * - * @constructor - * @param {jasmine.Env} env - * @param {Function} func - * @param {jasmine.Spec} spec - */ -jasmine.Block = function(env, func, spec) { - this.env = env; - this.func = func; - this.spec = spec; -}; - -jasmine.Block.prototype.execute = function(onComplete) { - if (!jasmine.CATCH_EXCEPTIONS) { - this.func.apply(this.spec); - } - else { - try { - this.func.apply(this.spec); - } catch (e) { - this.spec.fail(e); - } - } - onComplete(); -}; -/** JavaScript API reporter. - * - * @constructor - */ -jasmine.JsApiReporter = function() { - this.started = false; - this.finished = false; - this.suites_ = []; - this.results_ = {}; -}; - -jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { - this.started = true; - var suites = runner.topLevelSuites(); - for (var i = 0; i < suites.length; i++) { - var suite = suites[i]; - this.suites_.push(this.summarize_(suite)); - } -}; - -jasmine.JsApiReporter.prototype.suites = function() { - return this.suites_; -}; - -jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { - var isSuite = suiteOrSpec instanceof jasmine.Suite; - var summary = { - id: suiteOrSpec.id, - name: suiteOrSpec.description, - type: isSuite ? 'suite' : 'spec', - children: [] - }; - - if (isSuite) { - var children = suiteOrSpec.children(); - for (var i = 0; i < children.length; i++) { - summary.children.push(this.summarize_(children[i])); - } - } - return summary; -}; - -jasmine.JsApiReporter.prototype.results = function() { - return this.results_; -}; - -jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { - return this.results_[specId]; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { - this.finished = true; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { - this.results_[spec.id] = { - messages: spec.results().getItems(), - result: spec.results().failedCount > 0 ? "failed" : "passed" - }; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.log = function(str) { -}; - -jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ - var results = {}; - for (var i = 0; i < specIds.length; i++) { - var specId = specIds[i]; - results[specId] = this.summarizeResult_(this.results_[specId]); - } - return results; -}; - -jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ - var summaryMessages = []; - var messagesLength = result.messages.length; - for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { - var resultMessage = result.messages[messageIndex]; - summaryMessages.push({ - text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, - passed: resultMessage.passed ? resultMessage.passed() : true, - type: resultMessage.type, - message: resultMessage.message, - trace: { - stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined - } - }); - } - - return { - result : result.result, - messages : summaryMessages - }; -}; - -/** - * @constructor - * @param {jasmine.Env} env - * @param actual - * @param {jasmine.Spec} spec - */ -jasmine.Matchers = function(env, actual, spec, opt_isNot) { - this.env = env; - this.actual = actual; - this.spec = spec; - this.isNot = opt_isNot || false; - this.reportWasCalled_ = false; -}; - -// todo: @deprecated as of Jasmine 0.11, remove soon [xw] -jasmine.Matchers.pp = function(str) { - throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); -}; - -// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] -jasmine.Matchers.prototype.report = function(result, failing_message, details) { - throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); -}; - -jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { - for (var methodName in prototype) { - if (methodName == 'report') continue; - var orig = prototype[methodName]; - matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); - } -}; - -jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { - return function() { - var matcherArgs = jasmine.util.argsToArray(arguments); - var result = matcherFunction.apply(this, arguments); - - if (this.isNot) { - result = !result; - } - - if (this.reportWasCalled_) return result; - - var message; - if (!result) { - if (this.message) { - message = this.message.apply(this, arguments); - if (jasmine.isArray_(message)) { - message = message[this.isNot ? 1 : 0]; - } - } else { - var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); - message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; - if (matcherArgs.length > 0) { - for (var i = 0; i < matcherArgs.length; i++) { - if (i > 0) message += ","; - message += " " + jasmine.pp(matcherArgs[i]); - } - } - message += "."; - } - } - var expectationResult = new jasmine.ExpectationResult({ - matcherName: matcherName, - passed: result, - expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], - actual: this.actual, - message: message - }); - this.spec.addMatcherResult(expectationResult); - return jasmine.undefined; - }; -}; - - - - -/** - * toBe: compares the actual to the expected using === - * @param expected - */ -jasmine.Matchers.prototype.toBe = function(expected) { - return this.actual === expected; -}; - -/** - * toNotBe: compares the actual to the expected using !== - * @param expected - * @deprecated as of 1.0. Use not.toBe() instead. - */ -jasmine.Matchers.prototype.toNotBe = function(expected) { - return this.actual !== expected; -}; - -/** - * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. - * - * @param expected - */ -jasmine.Matchers.prototype.toEqual = function(expected) { - return this.env.equals_(this.actual, expected); -}; - -/** - * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual - * @param expected - * @deprecated as of 1.0. Use not.toEqual() instead. - */ -jasmine.Matchers.prototype.toNotEqual = function(expected) { - return !this.env.equals_(this.actual, expected); -}; - -/** - * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes - * a pattern or a String. - * - * @param expected - */ -jasmine.Matchers.prototype.toMatch = function(expected) { - return new RegExp(expected).test(this.actual); -}; - -/** - * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch - * @param expected - * @deprecated as of 1.0. Use not.toMatch() instead. - */ -jasmine.Matchers.prototype.toNotMatch = function(expected) { - return !(new RegExp(expected).test(this.actual)); -}; - -/** - * Matcher that compares the actual to jasmine.undefined. - */ -jasmine.Matchers.prototype.toBeDefined = function() { - return (this.actual !== jasmine.undefined); -}; - -/** - * Matcher that compares the actual to jasmine.undefined. - */ -jasmine.Matchers.prototype.toBeUndefined = function() { - return (this.actual === jasmine.undefined); -}; - -/** - * Matcher that compares the actual to null. - */ -jasmine.Matchers.prototype.toBeNull = function() { - return (this.actual === null); -}; - -/** - * Matcher that compares the actual to NaN. - */ -jasmine.Matchers.prototype.toBeNaN = function() { - this.message = function() { - return [ "Expected " + jasmine.pp(this.actual) + " to be NaN." ]; - }; - - return (this.actual !== this.actual); -}; - -/** - * Matcher that boolean not-nots the actual. - */ -jasmine.Matchers.prototype.toBeTruthy = function() { - return !!this.actual; -}; - - -/** - * Matcher that boolean nots the actual. - */ -jasmine.Matchers.prototype.toBeFalsy = function() { - return !this.actual; -}; - - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was called. - */ -jasmine.Matchers.prototype.toHaveBeenCalled = function() { - if (arguments.length > 0) { - throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); - } - - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy " + this.actual.identity + " to have been called.", - "Expected spy " + this.actual.identity + " not to have been called." - ]; - }; - - return this.actual.wasCalled; -}; - -/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ -jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was not called. - * - * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead - */ -jasmine.Matchers.prototype.wasNotCalled = function() { - if (arguments.length > 0) { - throw new Error('wasNotCalled does not take arguments'); - } - - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy " + this.actual.identity + " to not have been called.", - "Expected spy " + this.actual.identity + " to have been called." - ]; - }; - - return !this.actual.wasCalled; -}; - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. - * - * @example - * - */ -jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { - var expectedArgs = jasmine.util.argsToArray(arguments); - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - this.message = function() { - var invertedMessage = "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."; - var positiveMessage = ""; - if (this.actual.callCount === 0) { - positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called."; - } else { - positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but actual calls were " + jasmine.pp(this.actual.argsForCall).replace(/^\[ | \]$/g, '') - } - return [positiveMessage, invertedMessage]; - }; - - return this.env.contains_(this.actual.argsForCall, expectedArgs); -}; - -/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ -jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; - -/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ -jasmine.Matchers.prototype.wasNotCalledWith = function() { - var expectedArgs = jasmine.util.argsToArray(arguments); - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", - "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" - ]; - }; - - return !this.env.contains_(this.actual.argsForCall, expectedArgs); -}; - -/** - * Matcher that checks that the expected item is an element in the actual Array. - * - * @param {Object} expected - */ -jasmine.Matchers.prototype.toContain = function(expected) { - return this.env.contains_(this.actual, expected); -}; - -/** - * Matcher that checks that the expected item is NOT an element in the actual Array. - * - * @param {Object} expected - * @deprecated as of 1.0. Use not.toContain() instead. - */ -jasmine.Matchers.prototype.toNotContain = function(expected) { - return !this.env.contains_(this.actual, expected); -}; - -jasmine.Matchers.prototype.toBeLessThan = function(expected) { - return this.actual < expected; -}; - -jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { - return this.actual > expected; -}; - -/** - * Matcher that checks that the expected item is equal to the actual item - * up to a given level of decimal precision (default 2). - * - * @param {Number} expected - * @param {Number} precision, as number of decimal places - */ -jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { - if (!(precision === 0)) { - precision = precision || 2; - } - return Math.abs(expected - this.actual) < (Math.pow(10, -precision) / 2); -}; - -/** - * Matcher that checks that the expected exception was thrown by the actual. - * - * @param {String} [expected] - */ -jasmine.Matchers.prototype.toThrow = function(expected) { - var result = false; - var exception; - if (typeof this.actual != 'function') { - throw new Error('Actual is not a function'); - } - try { - this.actual(); - } catch (e) { - exception = e; - } - if (exception) { - result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); - } - - var not = this.isNot ? "not " : ""; - - this.message = function() { - if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { - return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); - } else { - return "Expected function to throw an exception."; - } - }; - - return result; -}; - -jasmine.Matchers.Any = function(expectedClass) { - this.expectedClass = expectedClass; -}; - -jasmine.Matchers.Any.prototype.jasmineMatches = function(other) { - if (this.expectedClass == String) { - return typeof other == 'string' || other instanceof String; - } - - if (this.expectedClass == Number) { - return typeof other == 'number' || other instanceof Number; - } - - if (this.expectedClass == Function) { - return typeof other == 'function' || other instanceof Function; - } - - if (this.expectedClass == Object) { - return typeof other == 'object'; - } - - return other instanceof this.expectedClass; -}; - -jasmine.Matchers.Any.prototype.jasmineToString = function() { - return '<jasmine.any(' + this.expectedClass + ')>'; -}; - -jasmine.Matchers.ObjectContaining = function (sample) { - this.sample = sample; -}; - -jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) { - mismatchKeys = mismatchKeys || []; - mismatchValues = mismatchValues || []; - - var env = jasmine.getEnv(); - - var hasKey = function(obj, keyName) { - return obj != null && obj[keyName] !== jasmine.undefined; - }; - - 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 (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) { - mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual."); - } - } - - return (mismatchKeys.length === 0 && mismatchValues.length === 0); -}; - -jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () { - return "<jasmine.objectContaining(" + jasmine.pp(this.sample) + ")>"; -}; -// Mock setTimeout, clearTimeout -// Contributed by Pivotal Computer Systems, www.pivotalsf.com - -jasmine.FakeTimer = function() { - this.reset(); - - var self = this; - self.setTimeout = function(funcToCall, millis) { - self.timeoutsMade++; - self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); - return self.timeoutsMade; - }; - - self.setInterval = function(funcToCall, millis) { - self.timeoutsMade++; - self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); - return self.timeoutsMade; - }; - - self.clearTimeout = function(timeoutKey) { - self.scheduledFunctions[timeoutKey] = jasmine.undefined; - }; - - self.clearInterval = function(timeoutKey) { - self.scheduledFunctions[timeoutKey] = jasmine.undefined; - }; - -}; - -jasmine.FakeTimer.prototype.reset = function() { - this.timeoutsMade = 0; - this.scheduledFunctions = {}; - this.nowMillis = 0; -}; - -jasmine.FakeTimer.prototype.tick = function(millis) { - var oldMillis = this.nowMillis; - var newMillis = oldMillis + millis; - this.runFunctionsWithinRange(oldMillis, newMillis); - this.nowMillis = newMillis; -}; - -jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { - var scheduledFunc; - var funcsToRun = []; - for (var timeoutKey in this.scheduledFunctions) { - scheduledFunc = this.scheduledFunctions[timeoutKey]; - if (scheduledFunc != jasmine.undefined && - scheduledFunc.runAtMillis >= oldMillis && - scheduledFunc.runAtMillis <= nowMillis) { - funcsToRun.push(scheduledFunc); - this.scheduledFunctions[timeoutKey] = jasmine.undefined; - } - } - - if (funcsToRun.length > 0) { - funcsToRun.sort(function(a, b) { - return a.runAtMillis - b.runAtMillis; - }); - for (var i = 0; i < funcsToRun.length; ++i) { - try { - var funcToRun = funcsToRun[i]; - this.nowMillis = funcToRun.runAtMillis; - funcToRun.funcToCall(); - if (funcToRun.recurring) { - this.scheduleFunction(funcToRun.timeoutKey, - funcToRun.funcToCall, - funcToRun.millis, - true); - } - } catch(e) { - } - } - this.runFunctionsWithinRange(oldMillis, nowMillis); - } -}; - -jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { - this.scheduledFunctions[timeoutKey] = { - runAtMillis: this.nowMillis + millis, - funcToCall: funcToCall, - recurring: recurring, - timeoutKey: timeoutKey, - millis: millis - }; -}; - -/** - * @namespace - */ -jasmine.Clock = { - defaultFakeTimer: new jasmine.FakeTimer(), - - reset: function() { - jasmine.Clock.assertInstalled(); - jasmine.Clock.defaultFakeTimer.reset(); - }, - - tick: function(millis) { - jasmine.Clock.assertInstalled(); - jasmine.Clock.defaultFakeTimer.tick(millis); - }, - - runFunctionsWithinRange: function(oldMillis, nowMillis) { - jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); - }, - - scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { - jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); - }, - - useMock: function() { - if (!jasmine.Clock.isInstalled()) { - var spec = jasmine.getEnv().currentSpec; - spec.after(jasmine.Clock.uninstallMock); - - jasmine.Clock.installMock(); - } - }, - - installMock: function() { - jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; - }, - - uninstallMock: function() { - jasmine.Clock.assertInstalled(); - jasmine.Clock.installed = jasmine.Clock.real; - }, - - real: { - setTimeout: jasmine.getGlobal().setTimeout, - clearTimeout: jasmine.getGlobal().clearTimeout, - setInterval: jasmine.getGlobal().setInterval, - clearInterval: jasmine.getGlobal().clearInterval - }, - - assertInstalled: function() { - if (!jasmine.Clock.isInstalled()) { - throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); - } - }, - - isInstalled: function() { - return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; - }, - - installed: null -}; -jasmine.Clock.installed = jasmine.Clock.real; - -//else for IE support -jasmine.getGlobal().setTimeout = function(funcToCall, millis) { - if (jasmine.Clock.installed.setTimeout.apply) { - return jasmine.Clock.installed.setTimeout.apply(this, arguments); - } else { - return jasmine.Clock.installed.setTimeout(funcToCall, millis); - } -}; - -jasmine.getGlobal().setInterval = function(funcToCall, millis) { - if (jasmine.Clock.installed.setInterval.apply) { - return jasmine.Clock.installed.setInterval.apply(this, arguments); - } else { - return jasmine.Clock.installed.setInterval(funcToCall, millis); - } -}; - -jasmine.getGlobal().clearTimeout = function(timeoutKey) { - if (jasmine.Clock.installed.clearTimeout.apply) { - return jasmine.Clock.installed.clearTimeout.apply(this, arguments); - } else { - return jasmine.Clock.installed.clearTimeout(timeoutKey); - } -}; - -jasmine.getGlobal().clearInterval = function(timeoutKey) { - if (jasmine.Clock.installed.clearTimeout.apply) { - return jasmine.Clock.installed.clearInterval.apply(this, arguments); - } else { - return jasmine.Clock.installed.clearInterval(timeoutKey); - } -}; - -/** - * @constructor - */ -jasmine.MultiReporter = function() { - this.subReporters_ = []; -}; -jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); - -jasmine.MultiReporter.prototype.addReporter = function(reporter) { - this.subReporters_.push(reporter); -}; - -(function() { - var functionNames = [ - "reportRunnerStarting", - "reportRunnerResults", - "reportSuiteResults", - "reportSpecStarting", - "reportSpecResults", - "log" - ]; - for (var i = 0; i < functionNames.length; i++) { - var functionName = functionNames[i]; - jasmine.MultiReporter.prototype[functionName] = (function(functionName) { - return function() { - for (var j = 0; j < this.subReporters_.length; j++) { - var subReporter = this.subReporters_[j]; - if (subReporter[functionName]) { - subReporter[functionName].apply(subReporter, arguments); - } - } - }; - })(functionName); - } -})(); -/** - * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults - * - * @constructor - */ -jasmine.NestedResults = function() { - /** - * The total count of results - */ - this.totalCount = 0; - /** - * Number of passed results - */ - this.passedCount = 0; - /** - * Number of failed results - */ - this.failedCount = 0; - /** - * Was this suite/spec skipped? - */ - this.skipped = false; - /** - * @ignore - */ - this.items_ = []; -}; - -/** - * Roll up the result counts. - * - * @param result - */ -jasmine.NestedResults.prototype.rollupCounts = function(result) { - this.totalCount += result.totalCount; - this.passedCount += result.passedCount; - this.failedCount += result.failedCount; -}; - -/** - * Adds a log message. - * @param values Array of message parts which will be concatenated later. - */ -jasmine.NestedResults.prototype.log = function(values) { - this.items_.push(new jasmine.MessageResult(values)); -}; - -/** - * Getter for the results: message & results. - */ -jasmine.NestedResults.prototype.getItems = function() { - return this.items_; -}; - -/** - * Adds a result, tracking counts (total, passed, & failed) - * @param {jasmine.ExpectationResult|jasmine.NestedResults} result - */ -jasmine.NestedResults.prototype.addResult = function(result) { - if (result.type != 'log') { - if (result.items_) { - this.rollupCounts(result); - } else { - this.totalCount++; - if (result.passed()) { - this.passedCount++; - } else { - this.failedCount++; - } - } - } - this.items_.push(result); -}; - -/** - * @returns {Boolean} True if <b>everything</b> below passed - */ -jasmine.NestedResults.prototype.passed = function() { - return this.passedCount === this.totalCount; -}; -/** - * Base class for pretty printing for expectation results. - */ -jasmine.PrettyPrinter = function() { - this.ppNestLevel_ = 0; -}; - -/** - * Formats a value in a nice, human-readable string. - * - * @param value - */ -jasmine.PrettyPrinter.prototype.format = function(value) { - this.ppNestLevel_++; - try { - if (value === jasmine.undefined) { - this.emitScalar('undefined'); - } else if (value === null) { - this.emitScalar('null'); - } else if (value === jasmine.getGlobal()) { - this.emitScalar('<global>'); - } else if (value.jasmineToString) { - this.emitScalar(value.jasmineToString()); - } else if (typeof value === 'string') { - this.emitString(value); - } else if (jasmine.isSpy(value)) { - this.emitScalar("spy on " + value.identity); - } else if (value instanceof RegExp) { - this.emitScalar(value.toString()); - } else if (typeof value === 'function') { - this.emitScalar('Function'); - } else if (typeof value.nodeType === 'number') { - this.emitScalar('HTMLNode'); - } else if (value instanceof Date) { - this.emitScalar('Date(' + value + ')'); - } else if (value.__Jasmine_offseteen_here_offsetefore__) { - this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>'); - } else if (jasmine.isArray_(value) || typeof value == 'object') { - value.__Jasmine_offseteen_here_offsetefore__ = true; - if (jasmine.isArray_(value)) { - this.emitArray(value); - } else { - this.emitObject(value); - } - delete value.__Jasmine_offseteen_here_offsetefore__; - } else { - this.emitScalar(value.toString()); - } - } finally { - this.ppNestLevel_--; - } -}; - -jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { - for (var property in obj) { - if (!obj.hasOwnProperty(property)) continue; - if (property == '__Jasmine_offseteen_here_offsetefore__') continue; - fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && - obj.__lookupGetter__(property) !== null) : false); - } -}; - -jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; - -jasmine.StringPrettyPrinter = function() { - jasmine.PrettyPrinter.call(this); - - this.string = ''; -}; -jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); - -jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { - this.append(value); -}; - -jasmine.StringPrettyPrinter.prototype.emitString = function(value) { - this.append("'" + value + "'"); -}; - -jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { - if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { - this.append("Array"); - return; - } - - this.append('[ '); - for (var i = 0; i < array.length; i++) { - if (i > 0) { - this.append(', '); - } - this.format(array[i]); - } - this.append(' ]'); -}; - -jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { - if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) { - this.append("Object"); - return; - } - - var self = this; - this.append('{ '); - var first = true; - - this.iterateObject(obj, function(property, isGetter) { - if (first) { - first = false; - } else { - self.append(', '); - } - - self.append(property); - self.append(' : '); - if (isGetter) { - self.append('<getter>'); - } else { - self.format(obj[property]); - } - }); - - this.append(' }'); -}; - -jasmine.StringPrettyPrinter.prototype.append = function(value) { - this.string += value; -}; -jasmine.Queue = function(env) { - this.env = env; - - // parallel to blocks. each true value in this array means the block will - // get executed even if we abort - this.ensured = []; - this.blocks = []; - this.running = false; - this.index = 0; - this.offset = 0; - this.abort = false; -}; - -jasmine.Queue.prototype.addBefore = function(block, ensure) { - if (ensure === jasmine.undefined) { - ensure = false; - } - - this.blocks.unshift(block); - this.ensured.unshift(ensure); -}; - -jasmine.Queue.prototype.add = function(block, ensure) { - if (ensure === jasmine.undefined) { - ensure = false; - } - - this.blocks.push(block); - this.ensured.push(ensure); -}; - -jasmine.Queue.prototype.insertNext = function(block, ensure) { - if (ensure === jasmine.undefined) { - ensure = false; - } - - this.ensured.splice((this.index + this.offset + 1), 0, ensure); - this.blocks.splice((this.index + this.offset + 1), 0, block); - this.offset++; -}; - -jasmine.Queue.prototype.start = function(onComplete) { - this.running = true; - this.onComplete = onComplete; - this.next_(); -}; - -jasmine.Queue.prototype.isRunning = function() { - return this.running; -}; - -jasmine.Queue.LOOP_DONT_RECURSE = true; - -jasmine.Queue.prototype.next_ = function() { - var self = this; - var goAgain = true; - - while (goAgain) { - goAgain = false; - - if (self.index < self.blocks.length && !(this.abort && !this.ensured[self.index])) { - var calledSynchronously = true; - var completedSynchronously = false; - - var onComplete = function () { - if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { - completedSynchronously = true; - return; - } - - if (self.blocks[self.index].abort) { - self.abort = true; - } - - self.offset = 0; - self.index++; - - var now = new Date().getTime(); - if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { - self.env.lastUpdate = now; - self.env.setTimeout(function() { - self.next_(); - }, 0); - } else { - if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { - goAgain = true; - } else { - self.next_(); - } - } - }; - self.blocks[self.index].execute(onComplete); - - calledSynchronously = false; - if (completedSynchronously) { - onComplete(); - } - - } else { - self.running = false; - if (self.onComplete) { - self.onComplete(); - } - } - } -}; - -jasmine.Queue.prototype.results = function() { - var results = new jasmine.NestedResults(); - for (var i = 0; i < this.blocks.length; i++) { - if (this.blocks[i].results) { - results.addResult(this.blocks[i].results()); - } - } - return results; -}; - - -/** - * Runner - * - * @constructor - * @param {jasmine.Env} env - */ -jasmine.Runner = function(env) { - var self = this; - self.env = env; - self.queue = new jasmine.Queue(env); - self.before_ = []; - self.after_ = []; - self.suites_ = []; -}; - -jasmine.Runner.prototype.execute = function() { - var self = this; - if (self.env.reporter.reportRunnerStarting) { - self.env.reporter.reportRunnerStarting(this); - } - self.queue.start(function () { - self.finishCallback(); - }); -}; - -jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { - beforeEachFunction.typeName = 'beforeEach'; - this.before_.splice(0,0,beforeEachFunction); -}; - -jasmine.Runner.prototype.afterEach = function(afterEachFunction) { - afterEachFunction.typeName = 'afterEach'; - this.after_.splice(0,0,afterEachFunction); -}; - - -jasmine.Runner.prototype.finishCallback = function() { - this.env.reporter.reportRunnerResults(this); -}; - -jasmine.Runner.prototype.addSuite = function(suite) { - this.suites_.push(suite); -}; - -jasmine.Runner.prototype.add = function(block) { - if (block instanceof jasmine.Suite) { - this.addSuite(block); - } - this.queue.add(block); -}; - -jasmine.Runner.prototype.specs = function () { - var suites = this.suites(); - var specs = []; - for (var i = 0; i < suites.length; i++) { - specs = specs.concat(suites[i].specs()); - } - return specs; -}; - -jasmine.Runner.prototype.suites = function() { - return this.suites_; -}; - -jasmine.Runner.prototype.topLevelSuites = function() { - var topLevelSuites = []; - for (var i = 0; i < this.suites_.length; i++) { - if (!this.suites_[i].parentSuite) { - topLevelSuites.push(this.suites_[i]); - } - } - return topLevelSuites; -}; - -jasmine.Runner.prototype.results = function() { - return this.queue.results(); -}; -/** - * Internal representation of a Jasmine specification, or test. - * - * @constructor - * @param {jasmine.Env} env - * @param {jasmine.Suite} suite - * @param {String} description - */ -jasmine.Spec = function(env, suite, description) { - if (!env) { - throw new Error('jasmine.Env() required'); - } - if (!suite) { - throw new Error('jasmine.Suite() required'); - } - var spec = this; - spec.id = env.nextSpecId ? env.nextSpecId() : null; - spec.env = env; - spec.suite = suite; - spec.description = description; - spec.queue = new jasmine.Queue(env); - - spec.afterCallbacks = []; - spec.spies_ = []; - - spec.results_ = new jasmine.NestedResults(); - spec.results_.description = description; - spec.matchersClass = null; -}; - -jasmine.Spec.prototype.getFullName = function() { - return this.suite.getFullName() + ' ' + this.description + '.'; -}; - - -jasmine.Spec.prototype.results = function() { - return this.results_; -}; - -/** - * All parameters are pretty-printed and concatenated together, then written to the spec's output. - * - * Be careful not to leave calls to <code>jasmine.log</code> in production code. - */ -jasmine.Spec.prototype.log = function() { - return this.results_.log(arguments); -}; - -jasmine.Spec.prototype.runs = function (func) { - var block = new jasmine.Block(this.env, func, this); - this.addToQueue(block); - return this; -}; - -jasmine.Spec.prototype.addToQueue = function (block) { - if (this.queue.isRunning()) { - this.queue.insertNext(block); - } else { - this.queue.add(block); - } -}; - -/** - * @param {jasmine.ExpectationResult} result - */ -jasmine.Spec.prototype.addMatcherResult = function(result) { - this.results_.addResult(result); -}; - -jasmine.Spec.prototype.expect = function(actual) { - var positive = new (this.getMatchersClass_())(this.env, actual, this); - positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); - return positive; -}; - -/** - * Waits a fixed time period before moving to the next block. - * - * @deprecated Use waitsFor() instead - * @param {Number} timeout milliseconds to wait - */ -jasmine.Spec.prototype.waits = function(timeout) { - var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); - this.addToQueue(waitsFunc); - return this; -}; - -/** - * Waits for the latchFunction to return true before proceeding to the next block. - * - * @param {Function} latchFunction - * @param {String} optional_timeoutMessage - * @param {Number} optional_timeout - */ -jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { - var latchFunction_ = null; - var optional_timeoutMessage_ = null; - var optional_timeout_ = null; - - for (var i = 0; i < arguments.length; i++) { - var arg = arguments[i]; - switch (typeof arg) { - case 'function': - latchFunction_ = arg; - break; - case 'string': - optional_timeoutMessage_ = arg; - break; - case 'number': - optional_timeout_ = arg; - break; - } - } - - var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); - this.addToQueue(waitsForFunc); - return this; -}; - -jasmine.Spec.prototype.fail = function (e) { - var expectationResult = new jasmine.ExpectationResult({ - passed: false, - message: e ? jasmine.util.formatException(e) : 'Exception', - trace: { stack: e.stack } - }); - this.results_.addResult(expectationResult); -}; - -jasmine.Spec.prototype.getMatchersClass_ = function() { - return this.matchersClass || this.env.matchersClass; -}; - -jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { - var parent = this.getMatchersClass_(); - var newMatchersClass = function() { - parent.apply(this, arguments); - }; - jasmine.util.inherit(newMatchersClass, parent); - jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); - this.matchersClass = newMatchersClass; -}; - -jasmine.Spec.prototype.finishCallback = function() { - this.env.reporter.reportSpecResults(this); -}; - -jasmine.Spec.prototype.finish = function(onComplete) { - this.removeAllSpies(); - this.finishCallback(); - if (onComplete) { - onComplete(); - } -}; - -jasmine.Spec.prototype.after = function(doAfter) { - if (this.queue.isRunning()) { - this.queue.add(new jasmine.Block(this.env, doAfter, this), true); - } else { - this.afterCallbacks.unshift(doAfter); - } -}; - -jasmine.Spec.prototype.execute = function(onComplete) { - var spec = this; - if (!spec.env.specFilter(spec)) { - spec.results_.skipped = true; - spec.finish(onComplete); - return; - } - - this.env.reporter.reportSpecStarting(this); - - spec.env.currentSpec = spec; - - spec.addBeforesAndAftersToQueue(); - - spec.queue.start(function () { - spec.finish(onComplete); - }); -}; - -jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { - var runner = this.env.currentRunner(); - var i; - - for (var suite = this.suite; suite; suite = suite.parentSuite) { - for (i = 0; i < suite.before_.length; i++) { - this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); - } - } - for (i = 0; i < runner.before_.length; i++) { - this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); - } - for (i = 0; i < this.afterCallbacks.length; i++) { - this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this), true); - } - for (suite = this.suite; suite; suite = suite.parentSuite) { - for (i = 0; i < suite.after_.length; i++) { - this.queue.add(new jasmine.Block(this.env, suite.after_[i], this), true); - } - } - for (i = 0; i < runner.after_.length; i++) { - this.queue.add(new jasmine.Block(this.env, runner.after_[i], this), true); - } -}; - -jasmine.Spec.prototype.explodes = function() { - throw 'explodes function should not have been called'; -}; - -jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { - if (obj == jasmine.undefined) { - throw "spyOn could not find an object to spy upon for " + methodName + "()"; - } - - if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { - throw methodName + '() method does not exist'; - } - - if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { - throw new Error(methodName + ' has already been spied upon'); - } - - var spyObj = jasmine.createSpy(methodName); - - this.spies_.push(spyObj); - spyObj.baseObj = obj; - spyObj.methodName = methodName; - spyObj.originalValue = obj[methodName]; - - obj[methodName] = spyObj; - - return spyObj; -}; - -jasmine.Spec.prototype.removeAllSpies = function() { - for (var i = 0; i < this.spies_.length; i++) { - var spy = this.spies_[i]; - spy.baseObj[spy.methodName] = spy.originalValue; - } - this.spies_ = []; -}; - -/** - * Internal representation of a Jasmine suite. - * - * @constructor - * @param {jasmine.Env} env - * @param {String} description - * @param {Function} specDefinitions - * @param {jasmine.Suite} parentSuite - */ -jasmine.Suite = function(env, description, specDefinitions, parentSuite) { - var self = this; - self.id = env.nextSuiteId ? env.nextSuiteId() : null; - self.description = description; - self.queue = new jasmine.Queue(env); - self.parentSuite = parentSuite; - self.env = env; - self.before_ = []; - self.after_ = []; - self.children_ = []; - self.suites_ = []; - self.specs_ = []; -}; - -jasmine.Suite.prototype.getFullName = function() { - var fullName = this.description; - for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { - fullName = parentSuite.description + ' ' + fullName; - } - return fullName; -}; - -jasmine.Suite.prototype.finish = function(onComplete) { - this.env.reporter.reportSuiteResults(this); - this.finished = true; - if (typeof(onComplete) == 'function') { - onComplete(); - } -}; - -jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { - beforeEachFunction.typeName = 'beforeEach'; - this.before_.unshift(beforeEachFunction); -}; - -jasmine.Suite.prototype.afterEach = function(afterEachFunction) { - afterEachFunction.typeName = 'afterEach'; - this.after_.unshift(afterEachFunction); -}; - -jasmine.Suite.prototype.results = function() { - return this.queue.results(); -}; - -jasmine.Suite.prototype.add = function(suiteOrSpec) { - this.children_.push(suiteOrSpec); - if (suiteOrSpec instanceof jasmine.Suite) { - this.suites_.push(suiteOrSpec); - this.env.currentRunner().addSuite(suiteOrSpec); - } else { - this.specs_.push(suiteOrSpec); - } - this.queue.add(suiteOrSpec); -}; - -jasmine.Suite.prototype.specs = function() { - return this.specs_; -}; - -jasmine.Suite.prototype.suites = function() { - return this.suites_; -}; - -jasmine.Suite.prototype.children = function() { - return this.children_; -}; - -jasmine.Suite.prototype.execute = function(onComplete) { - var self = this; - this.queue.start(function () { - self.finish(onComplete); - }); -}; -jasmine.WaitsBlock = function(env, timeout, spec) { - this.timeout = timeout; - jasmine.Block.call(this, env, null, spec); -}; - -jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); - -jasmine.WaitsBlock.prototype.execute = function (onComplete) { - if (jasmine.VERBOSE) { - this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); - } - this.env.setTimeout(function () { - onComplete(); - }, this.timeout); -}; -/** - * A block which waits for some condition to become true, with timeout. - * - * @constructor - * @extends jasmine.Block - * @param {jasmine.Env} env The Jasmine environment. - * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. - * @param {Function} latchFunction A function which returns true when the desired condition has been met. - * @param {String} message The message to display if the desired condition hasn't been met within the given time period. - * @param {jasmine.Spec} spec The Jasmine spec. - */ -jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { - this.timeout = timeout || env.defaultTimeoutInterval; - this.latchFunction = latchFunction; - this.message = message; - this.totalTimeSpentWaitingForLatch = 0; - jasmine.Block.call(this, env, null, spec); -}; -jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); - -jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; - -jasmine.WaitsForBlock.prototype.execute = function(onComplete) { - if (jasmine.VERBOSE) { - this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); - } - var latchFunctionResult; - try { - latchFunctionResult = this.latchFunction.apply(this.spec); - } catch (e) { - this.spec.fail(e); - onComplete(); - return; - } - - if (latchFunctionResult) { - onComplete(); - } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { - var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); - this.spec.fail({ - name: 'timeout', - message: message - }); - - this.abort = true; - onComplete(); - } else { - this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; - var self = this; - this.env.setTimeout(function() { - self.execute(onComplete); - }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); - } -}; - -jasmine.version_= { - "major": 1, - "minor": 3, - "build": 1, - "revision": 1354556913 -}; diff --git a/spec/lib/jasmine-2.0.1/boot.js b/spec/lib/jasmine-2.0.1/boot.js new file mode 100755 index 0000000..ec8baa0 --- /dev/null +++ b/spec/lib/jasmine-2.0.1/boot.js @@ -0,0 +1,181 @@ +/** + 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. + + 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. + + The location of `boot.js` can be specified and/or overridden in `jasmine.yml`. + + [jasmine-gem]: http://github.com/pivotal/jasmine-gem + */ + +(function() { + + /** + * ## Require & Instantiate + * + * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. + */ + window.jasmine = jasmineRequire.core(jasmineRequire); + + /** + * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference. + */ + jasmineRequire.html(jasmine); + + /** + * Create the Jasmine environment. This is used to run all specs in a project. + */ + var env = jasmine.getEnv(); + + /** + * ## The Global Interface + * + * 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); + }; + + /** + * Expose the interface for adding custom expectation matchers + */ + jasmine.addMatchers = function(matchers) { + return env.addMatchers(matchers); + }; + + /** + * Expose the mock interface for the JavaScript timeout functions + */ + jasmine.clock = function() { + return env.clock; + }; + + /** + * ## Runner Parameters + * + * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface. + */ + + var queryString = new jasmine.QueryString({ + getWindowLocation: function() { return window.location; } + }); + + var catchingExceptions = queryString.getParam("catch"); + env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); + + /** + * ## 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()); }, + getContainer: function() { return document.body; }, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); }, + timer: new jasmine.Timer() + }); + + /** + * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. + */ + env.addReporter(jasmineInterface.jsApiReporter); + env.addReporter(htmlReporter); + + /** + * Filter which specs will be run by matching the start of the full name against the `spec` query param. + */ + var specFilter = new jasmine.HtmlSpecFilter({ + filterString: function() { return queryString.getParam("spec"); } + }); + + env.specFilter = function(spec) { + return specFilter.matches(spec.getFullName()); + }; + + /** + * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack. + */ + window.setTimeout = window.setTimeout; + window.setInterval = window.setInterval; + window.clearTimeout = window.clearTimeout; + window.clearInterval = window.clearInterval; + + /** + * ## Execution + * + * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded. + */ + var currentWindowOnload = window.onload; + + window.onload = function() { + if (currentWindowOnload) { + currentWindowOnload(); + } + htmlReporter.initialize(); + env.execute(); + }; + + /** + * Helper function for readability above. + */ + function extend(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; + } + +}()); diff --git a/spec/lib/jasmine-2.0.1/console.js b/spec/lib/jasmine-2.0.1/console.js new file mode 100755 index 0000000..c54f72d --- /dev/null +++ b/spec/lib/jasmine-2.0.1/console.js @@ -0,0 +1,165 @@ +/* +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. +*/ +function getJasmineRequireObj() { + if (typeof module !== 'undefined' && module.exports) { + return exports; + } else { + window.jasmineRequire = window.jasmineRequire || {}; + return window.jasmineRequire; + } +} + +getJasmineRequireObj().console = function(jRequire, j$) { + j$.ConsoleReporter = jRequire.ConsoleReporter(); +}; + +getJasmineRequireObj().ConsoleReporter = function() { + + var noopTimer = { + start: function(){}, + elapsed: function(){ return 0; } + }; + + function ConsoleReporter(options) { + var print = options.print, + showColors = options.showColors || false, + onComplete = options.onComplete || function() {}, + timer = options.timer || noopTimer, + specCount, + failureCount, + failedSpecs = [], + pendingCount, + ansi = { + green: '\x1B[32m', + red: '\x1B[31m', + yellow: '\x1B[33m', + none: '\x1B[0m' + }; + + this.jasmineStarted = function() { + specCount = 0; + failureCount = 0; + pendingCount = 0; + print('Started'); + printNewline(); + timer.start(); + }; + + this.jasmineDone = function() { + printNewline(); + for (var i = 0; i < failedSpecs.length; i++) { + specFailureDetails(failedSpecs[i]); + } + + if(specCount > 0) { + printNewline(); + + var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' + + failureCount + ' ' + plural('failure', failureCount); + + if (pendingCount) { + specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount); + } + + print(specCounts); + } else { + print('No specs found'); + } + + printNewline(); + var seconds = timer.elapsed() / 1000; + print('Finished in ' + seconds + ' ' + plural('second', seconds)); + + printNewline(); + + onComplete(failureCount === 0); + }; + + this.specDone = function(result) { + specCount++; + + if (result.status == 'pending') { + pendingCount++; + print(colored('yellow', '*')); + return; + } + + if (result.status == 'passed') { + print(colored('green', '.')); + return; + } + + if (result.status == 'failed') { + failureCount++; + failedSpecs.push(result); + print(colored('red', 'F')); + } + }; + + return this; + + function printNewline() { + print('\n'); + } + + function colored(color, str) { + return showColors ? (ansi[color] + str + ansi.none) : str; + } + + function plural(str, count) { + return count == 1 ? str : str + 's'; + } + + function repeat(thing, times) { + var arr = []; + for (var i = 0; i < times; i++) { + arr.push(thing); + } + return arr; + } + + function indent(str, spaces) { + var lines = (str || '').split('\n'); + var newArr = []; + for (var i = 0; i < lines.length; i++) { + newArr.push(repeat(' ', spaces).join('') + lines[i]); + } + return newArr.join('\n'); + } + + function specFailureDetails(result) { + printNewline(); + print(result.fullName); + + for (var i = 0; i < result.failedExpectations.length; i++) { + var failedExpectation = result.failedExpectations[i]; + printNewline(); + print(indent(failedExpectation.stack, 2)); + } + + printNewline(); + } + } + + return ConsoleReporter; +}; diff --git a/spec/lib/jasmine-2.0.1/jasmine-html.js b/spec/lib/jasmine-2.0.1/jasmine-html.js new file mode 100755 index 0000000..9d95903 --- /dev/null +++ b/spec/lib/jasmine-2.0.1/jasmine-html.js @@ -0,0 +1,390 @@ +/* +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. +*/ +jasmineRequire.html = function(j$) { + j$.ResultsNode = jasmineRequire.ResultsNode(); + j$.HtmlReporter = jasmineRequire.HtmlReporter(j$); + j$.QueryString = jasmineRequire.QueryString(); + j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter(); +}; + +jasmineRequire.HtmlReporter = function(j$) { + + var noopTimer = { + start: function() {}, + elapsed: function() { return 0; } + }; + + function HtmlReporter(options) { + var env = options.env || {}, + getContainer = options.getContainer, + createElement = options.createElement, + createTextNode = options.createTextNode, + onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, + timer = options.timer || noopTimer, + results = [], + specsExecuted = 0, + failureCount = 0, + pendingSpecCount = 0, + htmlReporterMain, + symbols; + + 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('ul', {className: 'symbol-summary'}), + createDom('div', {className: 'alert'}), + createDom('div', {className: 'results'}, + createDom('div', {className: 'failures'}) + ) + ); + getContainer().appendChild(htmlReporterMain); + + symbols = find('.symbol-summary'); + }; + + var totalSpecsDefined; + this.jasmineStarted = function(options) { + totalSpecsDefined = options.totalSpecsDefined || 0; + timer.start(); + }; + + var summary = createDom('div', {className: 'summary'}); + + var topResults = new j$.ResultsNode({}, '', null), + currentParent = topResults; + + this.suiteStarted = function(result) { + currentParent.addChild(result, 'suite'); + currentParent = currentParent.last(); + }; + + this.suiteDone = function(result) { + if (currentParent == topResults) { + return; + } + + currentParent = currentParent.parent; + }; + + this.specStarted = function(result) { + currentParent.addChild(result, 'spec'); + }; + + var failures = []; + this.specDone = function(result) { + if(noExpectations(result) && console && console.error) { + console.error('Spec \'' + result.fullName + '\' has no expectations.'); + } + + if (result.status != 'disabled') { + specsExecuted++; + } + + symbols.appendChild(createDom('li', { + className: noExpectations(result) ? 'empty' : result.status, + id: 'spec_' + result.id, + title: result.fullName + } + )); + + if (result.status == 'failed') { + failureCount++; + + var failure = + createDom('div', {className: 'spec-detail failed'}, + createDom('div', {className: 'description'}, + createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName) + ), + createDom('div', {className: '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)); + } + + failures.push(failure); + } + + if (result.status == 'pending') { + pendingSpecCount++; + } + }; + + this.jasmineDone = function() { + var banner = find('.banner'); + banner.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's')); + + var alert = find('.alert'); + + 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'); + + checkbox.checked = !env.catchingExceptions(); + checkbox.onclick = onRaiseExceptionsClick; + + if (specsExecuted < totalSpecsDefined) { + var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all'; + alert.appendChild( + createDom('span', {className: 'bar skipped'}, + createDom('a', {href: '?', title: 'Run all specs'}, skippedMessage) + ) + ); + } + var statusBarMessage = ''; + var statusBarClassName = 'bar '; + + if (totalSpecsDefined > 0) { + statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); + if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); } + statusBarClassName += (failureCount > 0) ? 'failed' : 'passed'; + } else { + statusBarClassName += 'skipped'; + statusBarMessage += 'No specs found'; + } + + alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage)); + + var results = find('.results'); + results.appendChild(summary); + + summaryList(topResults, summary); + + function summaryList(resultsTree, domParent) { + var specListNode; + 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'}, + createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description) + ) + ); + + summaryList(resultNode, suiteListNode); + domParent.appendChild(suiteListNode); + } + if (resultNode.type == 'spec') { + if (domParent.getAttribute('class') != 'specs') { + specListNode = createDom('ul', {className: 'specs'}); + domParent.appendChild(specListNode); + } + var specDescription = resultNode.result.description; + if(noExpectations(resultNode.result)) { + specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription; + } + specListNode.appendChild( + createDom('li', { + className: resultNode.result.status, + id: 'spec-' + resultNode.result.id + }, + createDom('a', {href: specHref(resultNode.result)}, specDescription) + ) + ); + } + } + } + + if (failures.length) { + alert.appendChild( + createDom('span', {className: 'menu bar spec-list'}, + createDom('span', {}, 'Spec List | '), + createDom('a', {className: 'failures-menu', href: '#'}, 'Failures'))); + alert.appendChild( + createDom('span', {className: 'menu bar failure-list'}, + createDom('a', {className: 'spec-list-menu', href: '#'}, 'Spec List'), + createDom('span', {}, ' | Failures '))); + + find('.failures-menu').onclick = function() { + setMenuModeTo('failure-list'); + }; + find('.spec-list-menu').onclick = function() { + setMenuModeTo('spec-list'); + }; + + setMenuModeTo('failure-list'); + + var failureNode = find('.failures'); + for (var i = 0; i < failures.length; i++) { + failureNode.appendChild(failures[i]); + } + } + }; + + return this; + + function find(selector) { + return getContainer().querySelector('.jasmine_html-reporter ' + selector); + } + + function clearPrior() { + // return the reporter + var oldReporter = find(''); + + if(oldReporter) { + getContainer().removeChild(oldReporter); + } + } + + function createDom(type, attrs, childrenVarArgs) { + var el = createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(createTextNode(child)); + } else { + if (child) { + el.appendChild(child); + } + } + } + + for (var attr in attrs) { + if (attr == 'className') { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; + } + + function pluralize(singular, count) { + var word = (count == 1 ? singular : singular + 's'); + + return '' + count + ' ' + word; + } + + function specHref(result) { + return '?spec=' + encodeURIComponent(result.fullName); + } + + function setMenuModeTo(mode) { + htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode); + } + + function noExpectations(result) { + return (result.failedExpectations.length + result.passedExpectations.length) === 0 && + result.status === 'passed'; + } + } + + return HtmlReporter; +}; + +jasmineRequire.HtmlSpecFilter = function() { + function HtmlSpecFilter(options) { + var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); + var filterPattern = new RegExp(filterString); + + this.matches = function(specName) { + return filterPattern.test(specName); + }; + } + + return HtmlSpecFilter; +}; + +jasmineRequire.ResultsNode = function() { + function ResultsNode(result, type, parent) { + this.result = result; + this.type = type; + this.parent = parent; + + this.children = []; + + this.addChild = function(result, type) { + this.children.push(new ResultsNode(result, type, this)); + }; + + this.last = function() { + return this.children[this.children.length - 1]; + }; + } + + return ResultsNode; +}; + +jasmineRequire.QueryString = function() { + function QueryString(options) { + + this.setParam = function(key, value) { + var paramMap = queryStringToParamMap(); + paramMap[key] = value; + options.getWindowLocation().search = toQueryString(paramMap); + }; + + this.getParam = function(key) { + return queryStringToParamMap()[key]; + }; + + return this; + + function toQueryString(paramMap) { + var qStrPairs = []; + for (var prop in paramMap) { + qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])); + } + return '?' + qStrPairs.join('&'); + } + + function queryStringToParamMap() { + var paramStr = options.getWindowLocation().search.substring(1), + params = [], + paramMap = {}; + + if (paramStr.length > 0) { + params = paramStr.split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + var value = decodeURIComponent(p[1]); + if (value === 'true' || value === 'false') { + value = JSON.parse(value); + } + paramMap[decodeURIComponent(p[0])] = value; + } + } + + return paramMap; + } + + } + + return QueryString; +}; diff --git a/spec/lib/jasmine-2.0.1/jasmine.css b/spec/lib/jasmine-2.0.1/jasmine.css new file mode 100755 index 0000000..c54ff30 --- /dev/null +++ b/spec/lib/jasmine-2.0.1/jasmine.css @@ -0,0 +1,59 @@ +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.0.1/jasmine.js b/spec/lib/jasmine-2.0.1/jasmine.js new file mode 100755 index 0000000..c943db1 --- /dev/null +++ b/spec/lib/jasmine-2.0.1/jasmine.js @@ -0,0 +1,2516 @@ +/* +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. +*/ +function getJasmineRequireObj() { + if (typeof module !== 'undefined' && module.exports) { + return exports; + } else { + window.jasmineRequire = window.jasmineRequire || {}; + return window.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$; +}; + +getJasmineRequireObj().requireMatchers = function(jRequire, j$) { + var availableMatchers = [ + 'toBe', + 'toBeCloseTo', + 'toBeDefined', + 'toBeFalsy', + 'toBeGreaterThan', + 'toBeLessThan', + 'toBeNaN', + 'toBeNull', + 'toBeTruthy', + 'toBeUndefined', + 'toContain', + 'toEqual', + 'toHaveBeenCalled', + 'toHaveBeenCalledWith', + 'toMatch', + 'toThrow', + 'toThrowError' + ], + matchers = {}; + + for (var i = 0; i < availableMatchers.length; i++) { + var name = availableMatchers[i]; + matchers[name] = jRequire[name](j$); + } + + return matchers; +}; + +getJasmineRequireObj().base = (function (jasmineGlobal) { + if (typeof module !== 'undefined' && module.exports) { + jasmineGlobal = global; + } + + 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$.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$.isArray_ = function(value) { + return j$.isA_('Array', value); + }; + + j$.isString_ = function(value) { + return j$.isA_('String', value); + }; + + j$.isNumber_ = function(value) { + return j$.isA_('Number', value); + }; + + j$.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; + }; + + j$.isDomNode = function(obj) { + return obj.nodeType > 0; + }; + + j$.any = function(clazz) { + return new j$.Any(clazz); + }; + + j$.objectContaining = function(sample) { + return new j$.ObjectContaining(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() { + callTracker.track({ + object: this, + args: Array.prototype.slice.apply(arguments) + }); + return spyStrategy.exec.apply(this, 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'); + } + + spy[prop] = originalFn[prop]; + } + + spy.and = spyStrategy; + spy.calls = callTracker; + + return spy; + }; + + j$.isSpy = function(putativeSpy) { + if (!putativeSpy) { + return false; + } + return putativeSpy.and instanceof j$.SpyStrategy && + putativeSpy.calls instanceof j$.CallTracker; + }; + + 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; + }; + }; +})(this); + +getJasmineRequireObj().util = function() { + + var util = {}; + + util.inherit = function(childClass, parentClass) { + var Subclass = function() { + }; + Subclass.prototype = parentClass.prototype; + childClass.prototype = new Subclass(); + }; + + util.htmlEscape = function(str) { + if (!str) { + return str; + } + return str.replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>'); + }; + + util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) { + arrayOfArgs.push(args[i]); + } + return arrayOfArgs; + }; + + util.isUndefined = function(obj) { + return obj === void 0; + }; + + util.arrayContains = function(array, search) { + var i = array.length; + while (i--) { + if (array[i] == search) { + return true; + } + } + return false; + }; + + return util; +}; + +getJasmineRequireObj().Spec = function(j$) { + function Spec(attrs) { + this.expectationFactory = attrs.expectationFactory; + 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.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; }; + + if (!this.fn) { + this.pend(); + } + + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [], + passedExpectations: [] + }; + } + + Spec.prototype.addExpectationResult = function(passed, data) { + var expectationResult = this.expectationResultFactory(data); + if (passed) { + this.result.passedExpectations.push(expectationResult); + } else { + this.result.failedExpectations.push(expectationResult); + } + }; + + Spec.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + + Spec.prototype.execute = function(onComplete) { + var self = this; + + this.onStart(this); + + if (this.markedPending || this.disabled) { + complete(); + return; + } + + var allFns = this.beforeFns().concat(this.fn).concat(this.afterFns()); + + this.queueRunnerFactory({ + fns: allFns, + onException: onException, + onComplete: complete, + enforceTimeout: function() { return true; } + }); + + 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(); + self.resultCallback(self.result); + + if (onComplete) { + onComplete(); + } + } + }; + + Spec.prototype.disable = function() { + this.disabled = true; + }; + + Spec.prototype.pend = function() { + this.markedPending = true; + }; + + Spec.prototype.status = function() { + if (this.disabled) { + return 'disabled'; + } + + if (this.markedPending) { + return 'pending'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'passed'; + } + }; + + Spec.prototype.getFullName = function() { + return this.getSpecName(this); + }; + + Spec.pendingSpecExceptionMessage = '=> marked Pending'; + + Spec.isPendingSpecException = function(e) { + return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1); + }; + + return Spec; +}; + +if (typeof window == void 0 && typeof exports == 'object') { + exports.Spec = jasmineRequire.Spec; +} + +getJasmineRequireObj().Env = function(j$) { + function Env(options) { + options = options || {}; + + var self = this; + var global = options.global || j$.getGlobal(); + + var totalSpecsDefined = 0; + + var catchExceptions = true; + + var realSetTimeout = j$.getGlobal().setTimeout; + var realClearTimeout = j$.getGlobal().clearTimeout; + this.clock = new j$.Clock(global, new j$.DelayedFunctionScheduler(), new j$.MockDate(global)); + + var runnableLookupTable = {}; + + var spies = []; + + var currentSpec = null; + var currentSuite = null; + + var reporter = new j$.ReportDispatcher([ + 'jasmineStarted', + 'jasmineDone', + 'suiteStarted', + 'suiteDone', + 'specStarted', + 'specDone' + ]); + + this.specFilter = function() { + return true; + }; + + var equalityTesters = []; + + var customEqualityTesters = []; + this.addCustomEqualityTester = function(tester) { + customEqualityTesters.push(tester); + }; + + j$.Expectation.addCoreMatchers(j$.matchers); + + var nextSpecId = 0; + var getNextSpecId = function() { + return 'spec' + nextSpecId++; + }; + + var nextSuiteId = 0; + var getNextSuiteId = function() { + return 'suite' + nextSuiteId++; + }; + + var expectationFactory = function(actual, spec) { + return j$.Expectation.Factory({ + util: j$.matchersUtil, + customEqualityTesters: customEqualityTesters, + actual: actual, + addExpectationResult: addExpectationResult + }); + + function addExpectationResult(passed, result) { + return spec.addExpectationResult(passed, result); + } + }; + + var specStarted = function(spec) { + currentSpec = spec; + reporter.specStarted(spec.result); + }; + + var beforeFns = function(suite) { + return function() { + var befores = []; + while(suite) { + befores = befores.concat(suite.beforeFns); + suite = suite.parentSuite; + } + return befores.reverse(); + }; + }; + + var afterFns = function(suite) { + return function() { + var afters = []; + while(suite) { + afters = afters.concat(suite.afterFns); + suite = suite.parentSuite; + } + return afters; + }; + }; + + var getSpecName = function(spec, suite) { + return suite.getFullName() + ' ' + spec.description; + }; + + // TODO: we may just be able to pass in the fn instead of wrapping here + var buildExpectationResult = j$.buildExpectationResult, + exceptionFormatter = new j$.ExceptionFormatter(), + expectationResultFactory = function(attrs) { + attrs.messageFormatter = exceptionFormatter.message; + attrs.stackFormatter = exceptionFormatter.stack; + + return buildExpectationResult(attrs); + }; + + // TODO: fix this naming, and here's where the value comes in + this.catchExceptions = function(value) { + catchExceptions = !!value; + return catchExceptions; + }; + + this.catchingExceptions = function() { + return catchExceptions; + }; + + var maximumSpecCallbackDepth = 20; + var currentSpecCallbackDepth = 0; + + function clearStack(fn) { + currentSpecCallbackDepth++; + if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { + currentSpecCallbackDepth = 0; + realSetTimeout(fn, 0); + } else { + fn(); + } + } + + var catchException = function(e) { + return j$.Spec.isPendingSpecException(e) || catchExceptions; + }; + + var queueRunnerFactory = function(options) { + options.catchException = catchException; + options.clearStack = options.clearStack || clearStack; + options.timer = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; + + new j$.QueueRunner(options).execute(); + }; + + var topSuite = new j$.Suite({ + env: this, + id: getNextSuiteId(), + description: 'Jasmine__TopLevel__Suite', + queueRunner: queueRunnerFactory, + resultCallback: function() {} // TODO - hook this up + }); + runnableLookupTable[topSuite.id] = topSuite; + currentSuite = topSuite; + + this.topSuite = function() { + return topSuite; + }; + + this.execute = function(runnablesToRun) { + runnablesToRun = runnablesToRun || [topSuite.id]; + + 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)); + } + + reporter.jasmineStarted({ + totalSpecsDefined: totalSpecsDefined + }); + + queueRunnerFactory({fns: allFns, onComplete: reporter.jasmineDone}); + }; + + 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 spy = j$.createSpy(methodName, obj[methodName]); + + spies.push({ + spy: spy, + baseObj: obj, + methodName: methodName, + originalValue: obj[methodName] + }); + + obj[methodName] = spy; + + return spy; + }; + + var suiteFactory = function(description) { + var suite = new j$.Suite({ + env: self, + id: getNextSuiteId(), + description: description, + parentSuite: currentSuite, + queueRunner: queueRunnerFactory, + onStart: suiteStarted, + resultCallback: function(attrs) { + reporter.suiteDone(attrs); + } + }); + + runnableLookupTable[suite.id] = suite; + return suite; + }; + + this.describe = function(description, specDefinitions) { + var suite = suiteFactory(description); + + var parentSuite = currentSuite; + parentSuite.addChild(suite); + currentSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch (e) { + declarationError = e; + } + + if (declarationError) { + this.it('encountered a declaration exception', function() { + throw declarationError; + }); + } + + currentSuite = parentSuite; + + return suite; + }; + + this.xdescribe = function(description, specDefinitions) { + var suite = this.describe(description, specDefinitions); + suite.disable(); + return suite; + }; + + var specFactory = function(description, fn, suite) { + totalSpecsDefined++; + + var spec = new j$.Spec({ + id: getNextSpecId(), + beforeFns: beforeFns(suite), + afterFns: afterFns(suite), + expectationFactory: expectationFactory, + exceptionFormatter: exceptionFormatter, + resultCallback: specResultCallback, + getSpecName: function(spec) { + return getSpecName(spec, suite); + }, + onStart: specStarted, + description: description, + expectationResultFactory: expectationResultFactory, + queueRunnerFactory: queueRunnerFactory, + fn: fn + }); + + runnableLookupTable[spec.id] = spec; + + if (!self.specFilter(spec)) { + spec.disable(); + } + + 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 = []; + currentSpec = null; + reporter.specDone(result); + } + }; + + var suiteStarted = function(suite) { + reporter.suiteStarted(suite.result); + }; + + this.it = function(description, fn) { + var spec = specFactory(description, fn, currentSuite); + currentSuite.addChild(spec); + return spec; + }; + + this.xit = function(description, fn) { + var spec = this.it(description, fn); + spec.pend(); + return spec; + }; + + this.expect = function(actual) { + if (!currentSpec) { + 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); + }; + + this.beforeEach = function(beforeEachFunction) { + currentSuite.beforeEach(beforeEachFunction); + }; + + this.afterEach = function(afterEachFunction) { + currentSuite.afterEach(afterEachFunction); + }; + + this.pending = function() { + throw j$.Spec.pendingSpecExceptionMessage; + }; + } + + return Env; +}; + +getJasmineRequireObj().JsApiReporter = function() { + + var noopTimer = { + start: function(){}, + elapsed: function(){ return 0; } + }; + + function JsApiReporter(options) { + var timer = options.timer || noopTimer, + status = 'loaded'; + + this.started = false; + this.finished = false; + + this.jasmineStarted = function() { + this.started = true; + status = 'started'; + timer.start(); + }; + + var executionTime; + + this.jasmineDone = function() { + this.finished = true; + executionTime = timer.elapsed(); + status = 'done'; + }; + + this.status = function() { + return status; + }; + + var suites = {}; + + this.suiteStarted = function(result) { + storeSuite(result); + }; + + this.suiteDone = function(result) { + storeSuite(result); + }; + + function storeSuite(result) { + suites[result.id] = result; + } + + this.suites = function() { + return suites; + }; + + var specs = []; + this.specStarted = function(result) { }; + + this.specDone = function(result) { + specs.push(result); + }; + + this.specResults = function(index, length) { + return specs.slice(index, index + length); + }; + + this.specs = function() { + return specs; + }; + + this.executionTime = function() { + return executionTime; + }; + + } + + 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() { + var calls = []; + + this.track = function(context) { + calls.push(context); + }; + + this.any = function() { + return !!calls.length; + }; + + this.count = function() { + return calls.length; + }; + + this.argsFor = function(index) { + var call = calls[index]; + return call ? call.args : []; + }; + + this.all = function() { + return calls; + }; + + this.allArgs = function() { + var callArgs = []; + for(var i = 0; i < calls.length; i++){ + callArgs.push(calls[i].args); + } + + return callArgs; + }; + + this.first = function() { + return calls[0]; + }; + + this.mostRecent = function() { + return calls[calls.length - 1]; + }; + + this.reset = function() { + calls = []; + }; + } + + return CallTracker; +}; + +getJasmineRequireObj().Clock = function() { + function Clock(global, delayedFunctionScheduler, mockDate) { + var self = this, + realTimingFunctions = { + setTimeout: global.setTimeout, + clearTimeout: global.clearTimeout, + setInterval: global.setInterval, + clearInterval: global.clearInterval + }, + fakeTimingFunctions = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval + }, + installed = false, + timer; + + + self.install = function() { + replace(global, fakeTimingFunctions); + timer = fakeTimingFunctions; + installed = true; + + return self; + }; + + self.uninstall = function() { + delayedFunctionScheduler.reset(); + mockDate.uninstall(); + replace(global, realTimingFunctions); + + timer = realTimingFunctions; + installed = false; + }; + + self.mockDate = function(initialDate) { + mockDate.install(initialDate); + }; + + self.setTimeout = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); + } + return timer.setTimeout(fn, delay); + } + return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); + }; + + self.setInterval = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); + } + return timer.setInterval(fn, delay); + } + return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); + }; + + self.clearTimeout = function(id) { + return Function.prototype.call.apply(timer.clearTimeout, [global, id]); + }; + + self.clearInterval = function(id) { + return Function.prototype.call.apply(timer.clearInterval, [global, id]); + }; + + self.tick = function(millis) { + if (installed) { + mockDate.tick(millis); + delayedFunctionScheduler.tick(millis); + } else { + throw new Error('Mock clock is not installed, use jasmine.clock().install()'); + } + }; + + return self; + + function legacyIE() { + //if these methods are polyfilled, apply will be present + return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; + } + + function replace(dest, source) { + for (var prop in source) { + dest[prop] = source[prop]; + } + } + + function setTimeout(fn, delay) { + return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2)); + } + + function clearTimeout(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function setInterval(fn, interval) { + return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); + } + + function clearInterval(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function argSlice(argsObj, n) { + return Array.prototype.slice.call(argsObj, n); + } + } + + return Clock; +}; + +getJasmineRequireObj().DelayedFunctionScheduler = function() { + function DelayedFunctionScheduler() { + var self = this; + var scheduledLookup = []; + var scheduledFunctions = {}; + var currentTime = 0; + var delayedFnCount = 0; + + self.tick = function(millis) { + millis = millis || 0; + var endTime = currentTime + millis; + + runScheduledFunctions(endTime); + currentTime = endTime; + }; + + self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { + var f; + if (typeof(funcToCall) === 'string') { + /* jshint evil: true */ + f = function() { return eval(funcToCall); }; + /* jshint evil: false */ + } else { + f = funcToCall; + } + + millis = millis || 0; + timeoutKey = timeoutKey || ++delayedFnCount; + runAtMillis = runAtMillis || (currentTime + millis); + + var funcToSchedule = { + runAtMillis: runAtMillis, + funcToCall: f, + recurring: recurring, + params: params, + timeoutKey: timeoutKey, + millis: millis + }; + + if (runAtMillis in scheduledFunctions) { + scheduledFunctions[runAtMillis].push(funcToSchedule); + } else { + scheduledFunctions[runAtMillis] = [funcToSchedule]; + scheduledLookup.push(runAtMillis); + scheduledLookup.sort(function (a, b) { + return a - b; + }); + } + + return timeoutKey; + }; + + self.removeFunctionWithId = function(timeoutKey) { + for (var runAtMillis in scheduledFunctions) { + var funcs = scheduledFunctions[runAtMillis]; + var i = indexOfFirstToPass(funcs, function (func) { + return func.timeoutKey === timeoutKey; + }); + + if (i > -1) { + if (funcs.length === 1) { + delete scheduledFunctions[runAtMillis]; + deleteFromLookup(runAtMillis); + } else { + funcs.splice(i, 1); + } + + // intervals get rescheduled when executed, so there's never more + // than a single scheduled function with a given timeoutKey + break; + } + } + }; + + self.reset = function() { + currentTime = 0; + scheduledLookup = []; + scheduledFunctions = {}; + delayedFnCount = 0; + }; + + return self; + + function indexOfFirstToPass(array, testFn) { + var index = -1; + + for (var i = 0; i < array.length; ++i) { + if (testFn(array[i])) { + index = i; + break; + } + } + + return index; + } + + function deleteFromLookup(key) { + var value = Number(key); + var i = indexOfFirstToPass(scheduledLookup, function (millis) { + return millis === value; + }); + + if (i > -1) { + scheduledLookup.splice(i, 1); + } + } + + function reschedule(scheduledFn) { + self.scheduleFunction(scheduledFn.funcToCall, + scheduledFn.millis, + scheduledFn.params, + true, + scheduledFn.timeoutKey, + scheduledFn.runAtMillis + scheduledFn.millis); + } + + function runScheduledFunctions(endTime) { + if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { + return; + } + + do { + currentTime = scheduledLookup.shift(); + + 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 || []); + + if (funcToRun.recurring) { + reschedule(funcToRun); + } + } + } while (scheduledLookup.length > 0 && + // checking first if we're out of time prevents setTimeout(0) + // scheduled in a funcToRun from forcing an extra iteration + currentTime !== endTime && + scheduledLookup[0] <= endTime); + } + } + + return DelayedFunctionScheduler; +}; + +getJasmineRequireObj().ExceptionFormatter = function() { + function ExceptionFormatter() { + this.message = function(error) { + var message = ''; + + if (error.name && error.message) { + message += error.name + ': ' + error.message; + } else { + message += error.toString() + ' thrown'; + } + + if (error.fileName || error.sourceURL) { + message += ' in ' + (error.fileName || error.sourceURL); + } + + if (error.line || error.lineNumber) { + message += ' (line ' + (error.line || error.lineNumber) + ')'; + } + + return message; + }; + + this.stack = function(error) { + return error ? error.stack : null; + }; + } + + return ExceptionFormatter; +}; + +getJasmineRequireObj().Expectation = function() { + + var matchers = {}; + + function Expectation(options) { + this.util = options.util || { buildFailureMessage: function() {} }; + this.customEqualityTesters = options.customEqualityTesters || []; + this.actual = options.actual; + this.addExpectationResult = options.addExpectationResult || function(){}; + this.isNot = options.isNot; + + for (var matcherName in matchers) { + this[matcherName] = matchers[matcherName]; + } + } + + Expectation.prototype.wrapCompare = function(name, matcherFactory) { + return function() { + var args = Array.prototype.slice.call(arguments, 0), + expected = args.slice(0), + message = ''; + + args.unshift(this.actual); + + var matcher = matcherFactory(this.util, this.customEqualityTesters), + matcherCompare = matcher.compare; + + function defaultNegativeCompare() { + var result = matcher.compare.apply(null, args); + result.pass = !result.pass; + return result; + } + + if (this.isNot) { + matcherCompare = matcher.negativeCompare || defaultNegativeCompare; + } + + var result = matcherCompare.apply(null, args); + + if (!result.pass) { + if (!result.message) { + args.unshift(this.isNot); + args.unshift(name); + message = this.util.buildFailureMessage.apply(null, args); + } else { + if (Object.prototype.toString.apply(result.message) === '[object Function]') { + message = result.message(); + } else { + message = result.message; + } + } + } + + if (expected.length == 1) { + expected = expected[0]; + } + + // TODO: how many of these params are needed? + this.addExpectationResult( + result.pass, + { + matcherName: name, + passed: result.pass, + message: message, + actual: this.actual, + expected: expected // TODO: this may need to be arrayified/sliced + } + ); + }; + }; + + Expectation.addCoreMatchers = function(matchers) { + var prototype = Expectation.prototype; + for (var matcherName in matchers) { + var matcher = matchers[matcherName]; + prototype[matcherName] = prototype.wrapCompare(matcherName, matcher); + } + }; + + 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 || {}; + + var expect = new Expectation(options); + + // TODO: this would be nice as its own Object - NegativeExpectation + // TODO: copy instead of mutate options + options.isNot = true; + expect.not = new Expectation(options); + + return expect; + }; + + return Expectation; +}; + +//TODO: expectation result may make more sense as a presentation of an expectation. +getJasmineRequireObj().buildExpectationResult = function() { + function buildExpectationResult(options) { + var messageFormatter = options.messageFormatter || function() {}, + stackFormatter = options.stackFormatter || function() {}; + + return { + matcherName: options.matcherName, + expected: options.expected, + actual: options.actual, + message: message(), + stack: stack(), + passed: options.passed + }; + + function message() { + if (options.passed) { + return 'Passed.'; + } else if (options.message) { + return options.message; + } else if (options.error) { + return messageFormatter(options.error); + } + return ''; + } + + function stack() { + if (options.passed) { + return ''; + } + + var error = options.error; + if (!error) { + try { + throw new Error(message()); + } catch (e) { + error = e; + } + } + return stackFormatter(error); + } + } + + return buildExpectationResult; +}; + +getJasmineRequireObj().MockDate = function() { + function MockDate(global) { + var self = this; + var currentTime = 0; + + if (!global || !global.Date) { + self.install = function() {}; + self.tick = function() {}; + self.uninstall = function() {}; + return self; + } + + var GlobalDate = global.Date; + + self.install = function(mockDate) { + if (mockDate instanceof GlobalDate) { + currentTime = mockDate.getTime(); + } else { + currentTime = new GlobalDate().getTime(); + } + + global.Date = FakeDate; + }; + + self.tick = function(millis) { + millis = millis || 0; + currentTime = currentTime + millis; + }; + + self.uninstall = function() { + currentTime = 0; + global.Date = GlobalDate; + }; + + createDateProperties(); + + 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]); + } + } + + function createDateProperties() { + + FakeDate.now = function() { + if (GlobalDate.now) { + return currentTime; + } else { + throw new Error('Browser does not support Date.now()'); + } + }; + + FakeDate.toSource = GlobalDate.toSource; + FakeDate.toString = GlobalDate.toString; + FakeDate.parse = GlobalDate.parse; + FakeDate.UTC = GlobalDate.UTC; + } + } + + 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() { + this.ppNestLevel_ = 0; + this.seen = []; + } + + PrettyPrinter.prototype.format = function(value) { + this.ppNestLevel_++; + try { + if (j$.util.isUndefined(value)) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === 0 && 1/value === -Infinity) { + this.emitScalar('-0'); + } else if (value === j$.getGlobal()) { + this.emitScalar('<global>'); + } else if (value.jasmineToString) { + this.emitScalar(value.jasmineToString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (j$.isSpy(value)) { + this.emitScalar('spy on ' + value.and.identity()); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } 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)) { + this.seen.push(value); + if (j$.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + this.seen.pop(); + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } + }; + + PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; } + fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && + obj.__lookupGetter__(property) !== null) : false); + } + }; + + PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; + + function StringPrettyPrinter() { + PrettyPrinter.call(this); + + this.string = ''; + } + + j$.util.inherit(StringPrettyPrinter, PrettyPrinter); + + StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); + }; + + StringPrettyPrinter.prototype.emitString = function(value) { + this.append('\'' + value + '\''); + }; + + StringPrettyPrinter.prototype.emitArray = function(array) { + if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { + this.append('Array'); + return; + } + var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); + this.append('[ '); + for (var i = 0; i < length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + if(array.length > length){ + this.append(', ...'); + } + this.append(' ]'); + }; + + StringPrettyPrinter.prototype.emitObject = function(obj) { + if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { + this.append('Object'); + return; + } + + var self = this; + this.append('{ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.append(property); + self.append(': '); + if (isGetter) { + self.append('<getter>'); + } else { + self.format(obj[property]); + } + }); + + this.append(' }'); + }; + + StringPrettyPrinter.prototype.append = function(value) { + this.string += value; + }; + + return function(value) { + var stringPrettyPrinter = new StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; + }; +}; + +getJasmineRequireObj().QueueRunner = function(j$) { + + function once(fn) { + var called = false; + return function() { + if (!called) { + called = true; + fn(); + } + }; + } + + function QueueRunner(attrs) { + this.fns = attrs.fns || []; + 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}; + } + + QueueRunner.prototype.execute = function() { + this.run(this.fns, 0); + }; + + QueueRunner.prototype.run = function(fns, recursiveIndex) { + var length = fns.length, + self = this, + iterativeIndex; + + for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { + var fn = fns[iterativeIndex]; + if (fn.length > 0) { + return attemptAsync(fn); + } else { + attemptSync(fn); + } + } + + var runnerDone = iterativeIndex >= length; + + if (runnerDone) { + this.clearStack(this.onComplete); + } + + function attemptSync(fn) { + try { + fn.call(self.userContext); + } catch (e) { + handleException(e); + } + } + + function attemptAsync(fn) { + var clearTimeout = function () { + Function.prototype.apply.apply(self.timer.clearTimeout, [j$.getGlobal(), [timeoutId]]); + }, + next = once(function () { + clearTimeout(timeoutId); + self.run(fns, 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(); + }, j$.DEFAULT_TIMEOUT_INTERVAL]]); + } + + try { + fn.call(self.userContext, next); + } catch (e) { + handleException(e); + next(); + } + } + + function handleException(e) { + self.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.. + throw e; + } + } + }; + + return QueueRunner; +}; + +getJasmineRequireObj().ReportDispatcher = function() { + function ReportDispatcher(methods) { + + var dispatchedMethods = methods || []; + + for (var i = 0; i < dispatchedMethods.length; i++) { + var method = dispatchedMethods[i]; + this[method] = (function(m) { + return function() { + dispatch(m, arguments); + }; + }(method)); + } + + var reporters = []; + + this.addReporter = function(reporter) { + reporters.push(reporter); + }; + + return this; + + function dispatch(method, args) { + for (var i = 0; i < reporters.length; i++) { + var reporter = reporters[i]; + if (reporter[method]) { + reporter[method].apply(reporter, args); + } + } + } + } + + return ReportDispatcher; +}; + + +getJasmineRequireObj().SpyStrategy = function() { + + function SpyStrategy(options) { + options = options || {}; + + var identity = options.name || 'unknown', + originalFn = options.fn || function() {}, + getSpy = options.getSpy || function() {}, + plan = function() {}; + + this.identity = function() { + return identity; + }; + + this.exec = function() { + return plan.apply(this, arguments); + }; + + this.callThrough = function() { + plan = originalFn; + return getSpy(); + }; + + this.returnValue = function(value) { + plan = function() { + return value; + }; + return getSpy(); + }; + + this.throwError = function(something) { + var error = (something instanceof Error) ? something : new Error(something); + plan = function() { + throw error; + }; + return getSpy(); + }; + + this.callFake = function(fn) { + plan = fn; + return getSpy(); + }; + + this.stub = function(fn) { + plan = function() {}; + return getSpy(); + }; + } + + return SpyStrategy; +}; + +getJasmineRequireObj().Suite = function() { + 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.beforeFns = []; + this.afterFns = []; + this.queueRunner = attrs.queueRunner || function() {}; + this.disabled = false; + + this.children = []; + + this.result = { + id: this.id, + status: this.disabled ? 'disabled' : '', + description: this.description, + fullName: this.getFullName() + }; + } + + Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + if (parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + } + return fullName; + }; + + Suite.prototype.disable = function() { + this.disabled = true; + }; + + Suite.prototype.beforeEach = function(fn) { + this.beforeFns.unshift(fn); + }; + + Suite.prototype.afterEach = function(fn) { + this.afterFns.unshift(fn); + }; + + Suite.prototype.addChild = function(child) { + this.children.push(child); + }; + + Suite.prototype.execute = function(onComplete) { + var self = this; + if (this.disabled) { + complete(); + return; + } + + var allFns = []; + + for (var i = 0; i < this.children.length; i++) { + allFns.push(wrapChildAsAsync(this.children[i])); + } + + this.onStart(this); + + this.queueRunner({ + fns: allFns, + onComplete: complete + }); + + function complete() { + self.resultCallback(self.result); + + if (onComplete) { + onComplete(); + } + } + + function wrapChildAsAsync(child) { + return function(done) { child.execute(done); }; + } + }; + + return Suite; +}; + +if (typeof window == void 0 && typeof exports == 'object') { + exports.Suite = jasmineRequire.Suite; +} + +getJasmineRequireObj().Timer = function() { + var defaultNow = (function(Date) { + return function() { return new Date().getTime(); }; + })(Date); + + function Timer(options) { + options = options || {}; + + var now = options.now || defaultNow, + startTime; + + this.start = function() { + startTime = now(); + }; + + this.elapsed = function() { + return now() - startTime; + }; + } + + return Timer; +}; + +getJasmineRequireObj().matchersUtil = function(j$) { + // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? + + return { + equals: function(a, b, customTesters) { + customTesters = customTesters || []; + + return eq(a, b, [], [], customTesters); + }, + + contains: function(haystack, needle, customTesters) { + customTesters = customTesters || []; + + if (Object.prototype.toString.apply(haystack) === '[object Array]') { + for (var i = 0; i < haystack.length; i++) { + if (eq(haystack[i], needle, [], [], customTesters)) { + return true; + } + } + return false; + } + return !!haystack && haystack.indexOf(needle) >= 0; + }, + + buildFailureMessage: function() { + var args = Array.prototype.slice.call(arguments, 0), + matcherName = args[0], + isNot = args[1], + actual = args[2], + expected = args.slice(3), + englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + + var message = 'Expected ' + + j$.pp(actual) + + (isNot ? ' not ' : ' ') + + englishyPredicate; + + if (expected.length > 0) { + for (var i = 0; i < expected.length; i++) { + if (i > 0) { + message += ','; + } + message += ' ' + j$.pp(expected[i]); + } + } + + return message + '.'; + } + }; + + // Equality function lovingly adapted from isEqual in + // [Underscore](http://underscorejs.org) + function eq(a, b, aStack, bStack, customTesters) { + var result = true; + + for (var i = 0; i < customTesters.length; i++) { + var customTesterResult = customTesters[i](a, b); + if (!j$.util.isUndefined(customTesterResult)) { + return customTesterResult; + } + } + + 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; + } + + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) { return a !== 0 || 1 / a == 1 / b; } + // A strict comparison is necessary because `null == undefined`. + if (a === null || b === null) { return a === b; } + var className = Object.prototype.toString.call(a); + if (className != Object.prototype.toString.call(b)) { return false; } + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') { 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; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) { return bStack[length] == b; } + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + 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 { + // 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; + } + // Deep compare objects. + for (var key in a) { + if (has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; } + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (has(b, key) && !(size--)) { break; } + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + + return result; + + function has(obj, key) { + return obj.hasOwnProperty(key); + } + + function isFunction(obj) { + return typeof obj === 'function'; + } + } +}; + +getJasmineRequireObj().toBe = function() { + function toBe() { + return { + compare: function(actual, expected) { + return { + pass: actual === expected + }; + } + }; + } + + return toBe; +}; + +getJasmineRequireObj().toBeCloseTo = function() { + + function toBeCloseTo() { + return { + compare: function(actual, expected, precision) { + if (precision !== 0) { + precision = precision || 2; + } + + return { + pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2) + }; + } + }; + } + + return toBeCloseTo; +}; + +getJasmineRequireObj().toBeDefined = function() { + function toBeDefined() { + return { + compare: function(actual) { + return { + pass: (void 0 !== actual) + }; + } + }; + } + + return toBeDefined; +}; + +getJasmineRequireObj().toBeFalsy = function() { + function toBeFalsy() { + return { + compare: function(actual) { + return { + pass: !!!actual + }; + } + }; + } + + return toBeFalsy; +}; + +getJasmineRequireObj().toBeGreaterThan = function() { + + function toBeGreaterThan() { + return { + compare: function(actual, expected) { + return { + pass: actual > expected + }; + } + }; + } + + return toBeGreaterThan; +}; + + +getJasmineRequireObj().toBeLessThan = function() { + function toBeLessThan() { + return { + + compare: function(actual, expected) { + return { + pass: actual < expected + }; + } + }; + } + + return toBeLessThan; +}; +getJasmineRequireObj().toBeNaN = function(j$) { + + function toBeNaN() { + return { + compare: function(actual) { + var result = { + pass: (actual !== actual) + }; + + if (result.pass) { + result.message = 'Expected actual not to be NaN.'; + } else { + result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; + } + + return result; + } + }; + } + + return toBeNaN; +}; + +getJasmineRequireObj().toBeNull = function() { + + function toBeNull() { + return { + compare: function(actual) { + return { + pass: actual === null + }; + } + }; + } + + return toBeNull; +}; + +getJasmineRequireObj().toBeTruthy = function() { + + function toBeTruthy() { + return { + compare: function(actual) { + return { + pass: !!actual + }; + } + }; + } + + return toBeTruthy; +}; + +getJasmineRequireObj().toBeUndefined = function() { + + function toBeUndefined() { + return { + compare: function(actual) { + return { + pass: void 0 === actual + }; + } + }; + } + + return toBeUndefined; +}; + +getJasmineRequireObj().toContain = function() { + function toContain(util, customEqualityTesters) { + customEqualityTesters = customEqualityTesters || []; + + return { + compare: function(actual, expected) { + + return { + pass: util.contains(actual, expected, customEqualityTesters) + }; + } + }; + } + + return toContain; +}; + +getJasmineRequireObj().toEqual = function() { + + function toEqual(util, customEqualityTesters) { + customEqualityTesters = customEqualityTesters || []; + + return { + compare: function(actual, expected) { + var result = { + pass: false + }; + + result.pass = util.equals(actual, expected, customEqualityTesters); + + return result; + } + }; + } + + return toEqual; +}; + +getJasmineRequireObj().toHaveBeenCalled = function(j$) { + + function toHaveBeenCalled() { + return { + compare: function(actual) { + var result = {}; + + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } + + if (arguments.length > 1) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + result.pass = actual.calls.any(); + + result.message = result.pass ? + 'Expected spy ' + actual.and.identity() + ' not to have been called.' : + 'Expected spy ' + actual.and.identity() + ' to have been called.'; + + return result; + } + }; + } + + return toHaveBeenCalled; +}; + +getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { + + function toHaveBeenCalledWith(util, customEqualityTesters) { + return { + compare: function() { + var args = Array.prototype.slice.call(arguments, 0), + actual = args[0], + expectedArgs = args.slice(1), + result = { pass: false }; + + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } + + if (!actual.calls.any()) { + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; + return result; + } + + if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { + result.pass = true; + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; + } else { + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; + } + + return result; + } + }; + } + + return toHaveBeenCalledWith; +}; + +getJasmineRequireObj().toMatch = function() { + + function toMatch() { + return { + compare: function(actual, expected) { + var regexp = new RegExp(expected); + + return { + pass: regexp.test(actual) + }; + } + }; + } + + return toMatch; +}; + +getJasmineRequireObj().toThrow = function(j$) { + + function toThrow(util) { + return { + compare: function(actual, expected) { + var result = { pass: false }, + threw = false, + thrown; + + if (typeof actual != 'function') { + throw new Error('Actual is not a Function'); + } + + try { + actual(); + } catch (e) { + threw = true; + thrown = e; + } + + if (!threw) { + result.message = 'Expected function to throw an exception.'; + return result; + } + + if (arguments.length == 1) { + result.pass = true; + result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; + + return result; + } + + if (util.equals(thrown, expected)) { + result.pass = true; + result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; + } else { + result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; + } + + return result; + } + }; + } + + return toThrow; +}; + +getJasmineRequireObj().toThrowError = function(j$) { + function toThrowError (util) { + return { + compare: function(actual) { + var threw = false, + pass = {pass: true}, + fail = {pass: false}, + thrown, + errorType, + message, + regexp, + name, + constructorName; + + if (typeof actual != 'function') { + throw new Error('Actual is not a Function'); + } + + extractExpectedParams.apply(null, arguments); + + try { + actual(); + } catch (e) { + threw = true; + thrown = e; + } + + if (!threw) { + fail.message = 'Expected function to throw an Error.'; + return fail; + } + + if (!(thrown instanceof Error)) { + fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; + return fail; + } + + if (arguments.length == 1) { + pass.message = 'Expected function not to throw an Error, but it threw ' + fnNameFor(thrown) + '.'; + return pass; + } + + if (errorType) { + name = fnNameFor(errorType); + constructorName = fnNameFor(thrown.constructor); + } + + 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; + } + } + + 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 (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; + } + } + + 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 (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 fnNameFor(func) { + return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1]; + } + + function extractExpectedParams() { + if (arguments.length == 1) { + return; + } + + 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.'); + } + } 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.'); + } + } + } + + function checkForAnErrorType(type) { + if (typeof type !== 'function') { + return false; + } + + var Surrogate = function() {}; + Surrogate.prototype = type.prototype; + return (new Surrogate()) instanceof Error; + } + } + }; + } + + return toThrowError; +}; + +getJasmineRequireObj().version = function() { + return '2.0.1'; +}; diff --git a/spec/lib/jasmine-2.0.1/jasmine_favicon.png b/spec/lib/jasmine-2.0.1/jasmine_favicon.png Binary files differnew file mode 100755 index 0000000..3b84583 --- /dev/null +++ b/spec/lib/jasmine-2.0.1/jasmine_favicon.png diff --git a/spec/spec/adopter.js b/spec/spec/adopter.js new file mode 100644 index 0000000..0136d0e --- /dev/null +++ b/spec/spec/adopter.js @@ -0,0 +1,61 @@ +describe('Adopter', function() { + var path + + beforeEach(function() { + path = SVG.get('lineAB') + polyline = SVG.get('inlineSVG').select('polyline').first() + polygon = SVG.get('inlineSVG').select('polygon').first() + }) + + describe('with SVG.Doc instance', function() { + it('adopts the main svg document when parent() method is called on first level children', function() { + expect(path.parent() instanceof SVG.Doc).toBeTruthy() + }) + it('defines a xmlns attribute', function() { + expect(path.parent().node.getAttribute('xmlns')).toBe(SVG.ns) + }) + it('defines a version attribute', function() { + expect(path.parent().node.getAttribute('version')).toBe('1.1') + }) + it('defines a xmlns:xlink attribute', function() { + expect(path.parent().node.getAttribute('xmlns:xlink')).toBe(SVG.xlink) + }) + it('initializes a defs node', function() { + expect(path.parent()._defs).toBe(path.parent().defs()) + }) + }) + + describe('with SVG.Path instance', function() { + it('adopts an exiting path element', function() { + expect(path instanceof SVG.Path).toBeTruthy() + }) + it('modifies an adopted element', function() { + path.fill('#f06') + expect(path.node.getAttribute('fill')).toBe('#ff0066') + }) + it('parses d attribute to SVG.PathArray', function() { + expect(path.array() instanceof SVG.PathArray).toBeTruthy() + }) + }) + + describe('with SVG.Polyline instance', function() { + it('parses points attribute to SVG.PointArray', function() { + expect(polyline.array() instanceof SVG.PointArray).toBeTruthy() + }) + }) + + describe('with SVG.Polygon instance', function() { + it('parses points attribute to SVG.PointArray', function() { + expect(polygon.array() instanceof SVG.PointArray).toBeTruthy() + }) + }) + + describe('with node that has no matching svg.js class', function() { + it('wraps the node in the base SVG.Element class', function() { + var desc = SVG.get('inlineSVG').select('desc').first() + expect(desc instanceof SVG.Element).toBeTruthy() + }) + }) + + +})
\ No newline at end of file diff --git a/spec/spec/arrange.js b/spec/spec/arrange.js index 195c322..07f87d1 100644..100755 --- a/spec/spec/arrange.js +++ b/spec/spec/arrange.js @@ -56,6 +56,7 @@ describe('Arrange', function() { expect(e3.position()).toBe(2) }) it('keeps the defs on top of the stack', function() { + draw.defs() e3.forward() expect(draw.node.childNodes[2]).toBe(e3.node) expect(draw.node.childNodes[3]).toBe(draw.defs().node) diff --git a/spec/spec/array.js b/spec/spec/array.js index 05ea837..05ea837 100644..100755 --- a/spec/spec/array.js +++ b/spec/spec/array.js diff --git a/spec/spec/bare.js b/spec/spec/bare.js new file mode 100644 index 0000000..a27dbc3 --- /dev/null +++ b/spec/spec/bare.js @@ -0,0 +1,40 @@ +describe('Bare', function() { + + describe('element()', function() { + var element + + beforeEach(function() { + element = draw.element('rect') + }) + + it('creates an instance of SVG.Bare', function() { + expect(element instanceof SVG.Bare).toBeTruthy() + }) + it('creates element in called parent', function() { + expect(element.parent()).toBe(draw) + }) + }) + + describe('symbol()', function() { + var symbol + + beforeEach(function() { + symbol = draw.symbol() + }) + + it('creates an instance of SVG.Bare', function() { + expect(symbol instanceof SVG.Bare).toBeTruthy() + }) + it('creates symbol in defs', function() { + expect(symbol.parent() instanceof SVG.Defs).toBeTruthy() + }) + }) + + describe('words()', function() { + it('inserts plain text in a node', function() { + var element = draw.element('title').words('These are some words.').id(null) + expect(element.svg()).toBe('<title>These are some words.</title>') + }) + }) + +})
\ No newline at end of file diff --git a/spec/spec/bbox.js b/spec/spec/bbox.js deleted file mode 100644 index 44c1eb8..0000000 --- a/spec/spec/bbox.js +++ /dev/null @@ -1,43 +0,0 @@ -describe('BBox', function() { - - afterEach(function() { - draw.clear() - }) - - it('creates a new instance without passing an element', function() { - var box = new SVG.BBox - expect(box.x).toBe(0) - expect(box.y).toBe(0) - expect(box.cx).toBe(0) - expect(box.cy).toBe(0) - expect(box.width).toBe(0) - expect(box.height).toBe(0) - }) - - describe('merge()', function() { - it('merges various bounding boxes', function() { - var box1 = draw.rect(100,100).move(50,50).bbox() - var box2 = draw.rect(100,100).move(300,400).bbox() - var box3 = draw.rect(100,100).move(500,100).bbox() - var merged = box1.merge(box2).merge(box3) - expect(merged.x).toBe(50) - expect(merged.y).toBe(50) - expect(merged.cx).toBe(325) - expect(merged.cy).toBe(275) - expect(merged.width).toBe(550) - expect(merged.height).toBe(450) - }) - it('returns a new bbox instance', function() { - var box1 = draw.rect(100,100).move(50,50).bbox() - var box2 = draw.rect(100,100).move(300,400).bbox() - var merged = box1.merge(box2) - expect(box1).not.toBe(merged) - expect(box2).not.toBe(merged) - expect(box1.x).toBe(50) - expect(box1.y).toBe(50) - expect(box2.x).toBe(300) - expect(box2.y).toBe(400) - }) - }) - -})
\ No newline at end of file diff --git a/spec/spec/boxes.js b/spec/spec/boxes.js new file mode 100755 index 0000000..6afef0d --- /dev/null +++ b/spec/spec/boxes.js @@ -0,0 +1,206 @@ +describe('BBox', function() { + + afterEach(function() { + draw.clear() + }) + + it('creates a new instance without passing an element', function() { + var box = new SVG.BBox + expect(box.x).toBe(0) + expect(box.y).toBe(0) + expect(box.cx).toBe(0) + expect(box.cy).toBe(0) + expect(box.width).toBe(0) + expect(box.height).toBe(0) + }) + + describe('merge()', function() { + it('merges various bounding boxes', function() { + var box1 = draw.rect(100,100).move(50,50).bbox() + var box2 = draw.rect(100,100).move(300,400).bbox() + var box3 = draw.rect(100,100).move(500,100).bbox() + var merged = box1.merge(box2).merge(box3) + expect(merged.x).toBe(50) + expect(merged.y).toBe(50) + expect(merged.cx).toBe(325) + expect(merged.cy).toBe(275) + expect(merged.width).toBe(550) + expect(merged.height).toBe(450) + }) + it('returns a new bbox instance', function() { + var box1 = draw.rect(100,100).move(50,50).bbox() + var box2 = draw.rect(100,100).move(300,400).bbox() + var merged = box1.merge(box2) + expect(box1).not.toBe(merged) + expect(box2).not.toBe(merged) + expect(box1.x).toBe(50) + expect(box1.y).toBe(50) + expect(box2.x).toBe(300) + expect(box2.y).toBe(400) + }) + }) + +}) + +describe('TBox', function() { + + afterEach(function() { + draw.clear() + }) + + it('creates a new instance without passing an element', function() { + var box = new SVG.TBox + expect(box.x).toBe(0) + expect(box.y).toBe(0) + expect(box.cx).toBe(0) + expect(box.cy).toBe(0) + expect(box.width).toBe(0) + expect(box.height).toBe(0) + }) + + describe('merge()', function() { + it('merges various bounding boxes', function() { + var box1 = draw.rect(100,100).move(50,50).bbox() + var box2 = draw.rect(100,100).move(300,400).bbox() + var box3 = draw.rect(100,100).move(500,100).bbox() + var merged = box1.merge(box2).merge(box3) + expect(merged.x).toBe(50) + expect(merged.y).toBe(50) + expect(merged.cx).toBe(325) + expect(merged.cy).toBe(275) + expect(merged.width).toBe(550) + expect(merged.height).toBe(450) + }) + it('returns a new bbox instance', function() { + var box1 = draw.rect(100,100).move(50,50).bbox() + var box2 = draw.rect(100,100).move(300,400).bbox() + var merged = box1.merge(box2) + expect(box1).not.toBe(merged) + expect(box2).not.toBe(merged) + expect(box1.x).toBe(50) + expect(box1.y).toBe(50) + expect(box2.x).toBe(300) + expect(box2.y).toBe(400) + }) + }) + +}) + +describe('RBox', function() { + + afterEach(function() { + draw.clear() + }) + + it('creates a new instance without passing an element', function() { + var box = new SVG.RBox + expect(box.x).toBe(0) + expect(box.y).toBe(0) + expect(box.cx).toBe(0) + expect(box.cy).toBe(0) + expect(box.width).toBe(0) + expect(box.height).toBe(0) + }) + + describe('merge()', function() { + it('merges various bounding boxes', function() { + var box1 = draw.rect(100,100).move(50,50).bbox() + var box2 = draw.rect(100,100).move(300,400).bbox() + var box3 = draw.rect(100,100).move(500,100).bbox() + var merged = box1.merge(box2).merge(box3) + expect(merged.x).toBe(50) + expect(merged.y).toBe(50) + expect(merged.cx).toBe(325) + expect(merged.cy).toBe(275) + expect(merged.width).toBe(550) + expect(merged.height).toBe(450) + }) + it('returns a new bbox instance', function() { + var box1 = draw.rect(100,100).move(50,50).bbox() + var box2 = draw.rect(100,100).move(300,400).bbox() + var merged = box1.merge(box2) + expect(box1).not.toBe(merged) + expect(box2).not.toBe(merged) + expect(box1.x).toBe(50) + expect(box1.y).toBe(50) + expect(box2.x).toBe(300) + expect(box2.y).toBe(400) + }) + }) + +}) + +describe('Boxes', function() { + var rect + + beforeEach(function() { + rect = draw.rect(50, 180).move(25, 90).scale(2, 3, 25, 90).translate(10, 11) + }) + afterEach(function() { + draw.clear() + }) + + describe('bbox()', function() { + it('returns an instance of SVG.BBox', function() { + expect(rect.bbox() instanceof SVG.BBox).toBeTruthy() + }) + it('matches the size of the target element, ignoring transformations', function() { + var box = rect.bbox() + expect(box.x).toBe(25) + expect(box.y).toBe(90) + expect(box.cx).toBe(50) + expect(box.cy).toBe(180) + expect(box.width).toBe(50) + expect(box.height).toBe(180) + expect(box.w).toBe(50) + expect(box.h).toBe(180) + expect(box.x2).toBe(75) + expect(box.y2).toBe(270) + }) + }) + + describe('tbox()', function() { + it('returns an instance of SVG.TBox', function() { + expect(rect.tbox() instanceof SVG.TBox).toBeTruthy() + }) + it('matches the size of the target element, including transformations', function() { + var box = rect.tbox() + expect(box.x).toBe(35) + expect(box.y).toBe(101) + expect(box.cx).toBe(85) + expect(box.cy).toBe(371) + expect(box.width).toBe(100) + expect(box.height).toBe(540) + expect(box.w).toBe(100) + expect(box.h).toBe(540) + expect(box.x2).toBe(135) + expect(box.y2).toBe(641) + }) + }) + + describe('rbox()', function() { + it('returns an instance of SVG.RBox', function() { + expect(rect.rbox() instanceof SVG.RBox).toBeTruthy() + }) + it('matches the size of the target element, including transformations', function() { + var box = rect.rbox() + expect(box.x).toBe(60) + expect(box.y).toBe(281) + expect(box.cx).toBe(110) + expect(box.cy).toBe(551) + expect(box.width).toBe(100) + expect(box.height).toBe(540) + expect(box.w).toBe(100) + expect(box.h).toBe(540) + expect(box.x2).toBe(160) + expect(box.y2).toBe(821) + }) + }) + +}) + + + + + + diff --git a/spec/spec/circle.js b/spec/spec/circle.js new file mode 100644 index 0000000..be64e27 --- /dev/null +++ b/spec/spec/circle.js @@ -0,0 +1,185 @@ +describe('Circle', function() { + var circle + + beforeEach(function() { + circle = draw.circle(240) + }) + + afterEach(function() { + draw.clear() + }) + + describe('x()', function() { + it('returns the value of x without an argument', function() { + expect(circle.x()).toBe(0) + }) + it('sets the value of x with the first argument', function() { + circle.x(123) + var box = circle.bbox() + expect(box.x).toBe(123) + }) + }) + + describe('y()', function() { + it('returns the value of y without an argument', function() { + expect(circle.y()).toBe(0) + }) + it('sets the value of cy with the first argument', function() { + circle.y(345) + var box = circle.bbox() + expect(box.y).toBe(345) + }) + }) + + describe('cx()', function() { + it('returns the value of cx without an argument', function() { + expect(circle.cx()).toBe(120) + }) + it('sets the value of cx with the first argument', function() { + circle.cx(123) + var box = circle.bbox() + expect(box.cx).toBe(123) + }) + }) + + describe('cy()', function() { + it('returns the value of cy without an argument', function() { + expect(circle.cy()).toBe(120) + }) + it('sets the value of cy with the first argument', function() { + circle.cy(345) + var box = circle.bbox() + expect(box.cy).toBe(345) + }) + }) + + describe('radius()', function() { + it('sets the r attribute with the first argument', function() { + circle.radius(10) + expect(circle.node.getAttribute('r')).toBe('10') + }) + }) + + describe('rx()', function() { + it('sets the r attribute with the first argument', function() { + circle.rx(11) + expect(circle.node.getAttribute('r')).toBe('11') + }) + it('gets the r attribute without and argument', function() { + circle.rx() + expect(circle.node.getAttribute('r')).toBe('120') + }) + }) + + describe('ry()', function() { + it('sets the r attribute with the first argument', function() { + circle.ry(12) + expect(circle.node.getAttribute('r')).toBe('12') + }) + it('gets the r attribute without and argument', function() { + circle.ry() + expect(circle.node.getAttribute('r')).toBe('120') + }) + }) + + describe('move()', function() { + it('sets the x and y position', function() { + circle.move(123, 456) + var box = circle.bbox() + expect(box.x).toBe(123) + expect(box.y).toBe(456) + }) + }) + + describe('dx()', function() { + it('moves the x positon of the element relative to the current position', function() { + circle.move(50, 60) + circle.dx(100) + expect(circle.node.getAttribute('cx')).toBe('270') + }) + }) + + describe('dy()', function() { + it('moves the y positon of the element relative to the current position', function() { + circle.move(50, 60) + circle.dy(120) + expect(circle.node.getAttribute('cy')).toBe('300') + }) + }) + + describe('dmove()', function() { + it('moves the x and y positon of the element relative to the current position', function() { + circle.move(50,60) + circle.dmove(80, 25) + expect(circle.node.getAttribute('cx')).toBe('250') + expect(circle.node.getAttribute('cy')).toBe('205') + }) + }) + + describe('center()', function() { + it('sets the cx and cy position', function() { + circle.center(321,567) + var box = circle.bbox() + expect(box.cx).toBe(321) + expect(box.cy).toBe(567) + }) + }) + + describe('width()', function() { + it('sets the width and height of the element', function() { + circle.width(82) + expect(circle.node.getAttribute('r')).toBe('41') + }) + it('gets the width and height of the element if the argument is null', function() { + expect((circle.width() / 2).toString()).toBe(circle.node.getAttribute('r')) + }) + }) + + describe('height()', function() { + it('sets the height and width of the element', function() { + circle.height(1236) + expect(circle.node.getAttribute('r')).toBe('618') + }) + it('gets the height and width of the element if the argument is null', function() { + expect((circle.height() / 2).toString()).toBe(circle.node.getAttribute('r')) + }) + }) + + describe('size()', function() { + it('defines the r of the element', function() { + circle.size(987) + expect(circle.node.getAttribute('r')).toBe((987 / 2).toString()) + }) + }) + + describe('scale()', function() { + it('should scale the element universally with one argument', function() { + var box = circle.scale(2).tbox() + + expect(box.width).toBe(circle.attr('r') * 2 * 2) + expect(box.height).toBe(circle.attr('r') * 2 * 2) + }) + it('should scale the element over individual x and y axes with two arguments', function() { + var box = circle.scale(2, 3.5).tbox() + + expect(box.width).toBe(circle.attr('r') * 2 * 2) + expect(box.height).toBe(circle.attr('r') * 2 * 3.5) + }) + }) + + describe('translate()', function() { + it('sets the translation of an element', function() { + circle.transform({ x: 12, y: 12 }) + expect(circle.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') + }) + }) + +}) + + + + + + + + diff --git a/spec/spec/clip.js b/spec/spec/clip.js index fb673f0..eba8df4 100644..100755 --- a/spec/spec/clip.js +++ b/spec/spec/clip.js @@ -11,20 +11,20 @@ describe('ClipPath', function() { draw.clear() }) - it('moves the masking element to a new clip node', function() { - expect(circle.parent instanceof SVG.Clip).toBe(true) + it('moves the clipping element to a new clip node', function() { + expect(circle.parent() instanceof SVG.ClipPath).toBe(true) }) it('creates the clip node in the defs node', function() { - expect(circle.parent.parent).toBe(draw.defs()) + expect(circle.parent().parent()).toBe(draw.defs()) }) it('sets the "clip-path" attribute on the cliped element with the clip id', function() { - expect(rect.attr('clip-path')).toBe('url("#' + circle.parent.attr('id') + '")') + expect(rect.attr('clip-path')).toBe('url("#' + circle.parent().attr('id') + '")') }) it('references the clip element in the masked element', function() { - expect(rect.clipper).toBe(circle.parent) + expect(rect.clipper).toBe(circle.parent()) }) it('references the clipped element in the clipPath target list', function() { diff --git a/spec/spec/color.js b/spec/spec/color.js index 6bae4c8..6bae4c8 100644..100755 --- a/spec/spec/color.js +++ b/spec/spec/color.js diff --git a/spec/spec/container.js b/spec/spec/container.js index f72ec17..d300fcd 100644..100755 --- a/spec/spec/container.js +++ b/spec/spec/container.js @@ -50,11 +50,11 @@ describe('Container', function() { draw.circle(100) expect(draw.children().length).toBe(initial + 1) }) - it('should create an ellipse', function() { - expect(draw.circle(100).type).toBe('ellipse') + it('should create an circle', function() { + expect(draw.circle(100).type).toBe('circle') }) - it('should create an instance of SVG.Ellipse', function() { - expect(draw.circle(100) instanceof SVG.Ellipse).toBe(true) + it('should create an instance of SVG.Circle', function() { + expect(draw.circle(100) instanceof SVG.Circle).toBe(true) }) it('should be an instance of SVG.Shape', function() { expect(draw.circle(100) instanceof SVG.Shape).toBe(true) @@ -210,11 +210,11 @@ describe('Container', function() { draw.clear() expect(draw.children().length).toBe(0) }) - it('keeps the defs node', function() { + it('creates a new defs node', function() { var oldDefs = draw.defs() draw.rect(100,100).maskWith(draw.circle(100, 100)) draw.clear() - expect(draw.defs()).toBe(oldDefs) + expect(draw.defs()).not.toBe(oldDefs) }) it('clears all children in the defs node', function() { draw.rect(100,100).maskWith(draw.circle(100, 100)) @@ -273,21 +273,26 @@ describe('Container', function() { }) describe('viewbox()', function() { + + beforeEach(function() { + draw.attr('viewBox', null) + }) + it('should set the viewbox when four arguments are provided', function() { draw.viewbox(0,0,100,100) expect(draw.node.getAttribute('viewBox')).toBe('0 0 100 100') }) it('should set the viewbox when an object is provided as first argument', function() { - draw.viewbox({ x: 0, y: 0, width: 50, height: 50, zoom: 1 }) + draw.viewbox({ x: 0, y: 0, width: 50, height: 50 }) expect(draw.node.getAttribute('viewBox')).toBe('0 0 50 50') }) it('should accept negative values', function() { - draw.size(100,100).viewbox(-100,-100,50,50) + draw.size(100,100).viewbox(-100, -100, 50, 50) expect(draw.node.getAttribute('viewBox')).toEqual('-100 -100 50 50') }) it('should get the viewbox if no arguments are given', function() { - draw.viewbox(0,0,100,100) - expect(draw.viewbox()).toEqual({ x: 0, y: 0, width: 100, height: 100, zoom: 1 }) + draw.viewbox(0, 0, 100, 100) + expect(draw.viewbox()).toEqual(new SVG.ViewBox(draw)) }) it('should define the zoom of the viewbox in relation to the canvas size', function() { draw.size(100,100).viewbox(0,0,50,50) @@ -336,6 +341,13 @@ describe('Container', function() { }) }) + describe('parent()', function() { + it('returns the parent element instance', function() { + var rect = draw.rect(100,100) + expect(rect.parent()).toBe(rect.node.parentNode.instance) + }) + }) + }) diff --git a/spec/spec/doc.js b/spec/spec/doc.js index beedff7..96e8cf5 100644..100755 --- a/spec/spec/doc.js +++ b/spec/spec/doc.js @@ -3,13 +3,13 @@ describe('Doc', function() { it('is an instance of SVG.Container', function() { expect(draw instanceof SVG.Container).toBe(true) }) - - it('has a defs element', function() { - expect(draw._defs instanceof SVG.Defs).toBe(true) + + it('is an instance of SVG.Doc', function() { + expect(draw instanceof SVG.Doc).toBe(true) }) - it('has itself as doc', function() { - expect(draw.doc()).toBe(draw); + it('has a defs element', function() { + expect(draw.defs() instanceof SVG.Defs).toBe(true) }) describe('defs()', function() { @@ -17,17 +17,16 @@ describe('Doc', function() { expect(draw.defs()).toBe(draw._defs) }) it('references parent node', function(){ - expect(draw.defs().parent).toBe(draw) + expect(draw.defs().parent()).toBe(draw) }) }) describe('remove()', function() { it('removes the doc from the dom', function() { draw.remove() - expect(draw.parent).toBeNull() - expect(document.getElementsByTagName('body')[0].querySelectorAll('svg').length).toBe(1) - draw = SVG(drawing).size(100,100); expect(document.getElementsByTagName('body')[0].querySelectorAll('svg').length).toBe(2) + draw = SVG(drawing).size(100,100); + expect(document.getElementsByTagName('body')[0].querySelectorAll('svg').length).toBe(3) }) }) diff --git a/spec/spec/element.js b/spec/spec/element.js index 969d755..1c722cd 100644..100755 --- a/spec/spec/element.js +++ b/spec/spec/element.js @@ -1,5 +1,9 @@ describe('Element', function() { + beforeEach(function() { + draw.attr('viewBox', null) + }) + afterEach(function() { draw.clear() }) @@ -8,6 +12,13 @@ describe('Element', function() { var rect = draw.rect(100,100) expect(rect.node.instance).toBe(rect) }) + + describe('native()', function() { + it('returns the node reference', function() { + var rect = draw.rect(100,100) + expect(rect.native()).toBe(rect.node) + }) + }) describe('attr()', function() { var rect @@ -15,34 +26,38 @@ describe('Element', function() { beforeEach(function() { rect = draw.rect(100,100) }) - - it('should set one attribute when two arguments are given', function() { + + 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('should set various attributes when an object is given', function() { + 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('should get the value of the string value given as first argument', function() { + it('gets the value of the string value given as first argument', function() { rect.attr('fill', '#ff0066') expect(rect.attr('fill')).toEqual('#ff0066') }) - it('should get an object with all attributes without any arguments', function() { + 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('should remove an attribute if the second argument is explicitly set to null', function() { + 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('should correctly parse numeric values as a getter', function() { + 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) @@ -63,24 +78,24 @@ describe('Element', function() { rect.attr('style', 'cursor:move;') expect(rect.node.style.cursor).toBe('move') }) - it('should remove style attribute on node if the style is empty', function() { + 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('should act as a global getter when no arguments are given', function() { + it('acts as a global getter when no arguments are given', function() { rect.fill('#ff0066') expect(rect.attr().fill).toBe('#ff0066') }) - it('should correctly parse numeric values as a global getter', function() { + it('correctly parses numeric values as a global getter', function() { rect.stroke({ width: 20 }) expect(rect.attr()['stroke-width']).toBe(20) }) - it('should correctly parse negative numeric values as a global getter', function() { + it('correctly parses negative numeric values as a global getter', function() { rect.x(-30) expect(rect.attr().x).toBe(-30) }) - it('should leave unit values alone as a global getter', function() { + it('leaves unit values alone as a global getter', function() { rect.attr('x', '69%') expect(rect.attr().x).toBe('69%') }) @@ -93,7 +108,7 @@ describe('Element', function() { rect = draw.rect(100,100) }) - it('gets the value of the id attribute without an argument', function() { + 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() { @@ -103,31 +118,31 @@ describe('Element', function() { }) describe('style()', function() { - it('should set the style with key and value arguments', 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('should set multiple styles with an object as the first argument', function() { + 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('should get a style with a string key as the fists argument', function() { + 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('should get a style with a string key as the fists argument', function() { + 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('should remove a style if the value is an empty string', function() { + 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('should remove a style if the value explicitly set to null', function() { + 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) @@ -136,41 +151,131 @@ describe('Element', function() { }) describe('transform()', function() { - it('should get the current transformations', function() { - var rect = draw.rect(100,100) - expect(rect.transform()).toEqual(SVG.defaults.trans()) - }) - it('should set the translation of and element', function() { - var rect = draw.rect(100,100).transform({ x: 10, y: 10 }) - expect(rect.node.getAttribute('transform')).toBe('translate(10 10)') + var rect, ctm + + beforeEach(function() { + rect = draw.rect(100,100) }) - it('should set the scaleX of and element', function() { - var rect = draw.rect(100,100).transform({ scaleX: 0.1 }) - expect(rect.node.getAttribute('transform')).toBe('scale(0.1 1)') + + 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)') }) - it('should set the scaleY of and element', function() { - var rect = draw.rect(100,100).transform({ scaleY: 10 }) - expect(rect.node.getAttribute('transform')).toBe('scale(1 10)') + }) + + describe('untransform()', function() { + var circle + + beforeEach(function() { + circle = draw.circle(100).translate(50, 100) }) - it('should set the skewX of and element', function() { - var rect = draw.rect(100,100).transform({ skewX: 0.1 }) - expect(rect.node.getAttribute('transform')).toBe('skewX(0.1)') + + 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('should set the skewY of and element', function() { - var rect = draw.rect(100,100).transform({ skewY: 10 }) - expect(rect.node.getAttribute('transform')).toBe('skewY(10)') + 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) }) - it('should rotate the element around its centre if no rotation point is given', function() { - var rect = draw.rect(100,100).transform({ rotation: 45 }) - expect(rect.node.getAttribute('transform')).toBe('rotate(45 50 50)') + }) + + describe('ctm()', function() { + var rect + + beforeEach(function() { + rect = draw.rect(100, 100) }) - it('should rotate the element around the given rotation point', function() { - var rect = draw.rect(100,100).transform({ rotation: 55, cx: 80, cy:2 }) - expect(rect.node.getAttribute('transform')).toBe('rotate(55 80 2)') + + 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('should transform element using a matrix', function() { - var rect = draw.rect(100,100).transform({ a: 0.5, c: 0.5 }) - expect(rect.node.getAttribute('transform')).toBe('matrix(0.5 0 0.5 1 0 0)') + it('returns an instance of SVG.Matrix', function() { + expect(rect.ctm() instanceof SVG.Matrix).toBeTruthy() }) }) @@ -210,11 +315,11 @@ describe('Element', function() { }) describe('remove()', function() { - it('should remove an element and return it', function() { + it('removes an element and return it', function() { var rect = draw.rect(100,100) expect(rect.remove()).toBe(rect) }) - it('should remove an element from its parent', function() { + it('removes an element from its parent', function() { var rect = draw.rect(100,100) rect.remove() expect(draw.has(rect)).toBe(false) @@ -227,7 +332,7 @@ describe('Element', function() { , group = draw.group() expect(rect.addTo(group)).toBe(rect) - expect(rect.parent).toBe(group) + expect(rect.parent()).toBe(group) }) }) @@ -237,7 +342,7 @@ describe('Element', function() { , group = draw.group() expect(rect.putIn(group)).toBe(group) - expect(rect.parent).toBe(group) + expect(rect.parent()).toBe(group) }) }) @@ -247,24 +352,24 @@ describe('Element', function() { 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 rect = draw.size(200, 150).viewbox(0, 0, 200, 150).rect(105, 210).move(2, 12) var box = rect.rbox() expect(box.x).toBe(2) expect(box.y).toBe(12) expect(box.cx).toBe(54.5) expect(box.cy).toBe(117) - expect(box.width).toBeCloseTo(105) - expect(box.height).toBeCloseTo(210) + 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(approximately(box.x)).toBe(approximately(1)) - expect(approximately(box.y)).toBe(approximately(6)) - expect(approximately(box.cx)).toBe(approximately(27.25)) - expect(approximately(box.cy)).toBe(approximately(58.5)) - expect(approximately(box.width)).toBe(approximately(52.5)) - expect(approximately(box.height)).toBe(approximately(105)) + expect(box.x).toBe(2) + expect(box.y).toBe(12) + expect(box.cx).toBe(54.5) + expect(box.cy).toBe(117) + expect(box.width).toBe(105) + expect(box.height).toBe(210) }) }) @@ -278,25 +383,45 @@ describe('Element', function() { describe('parent', function() { it('contains the parent svg', function() { var rect = draw.rect(100,100) - expect(rect.parent).toBe(draw) + 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) + expect(rect.parent()).toBe(group) }) }) describe('clone()', function() { - it('makes an exact copy of the element', function() { - var rect = draw.rect(100,100).center(321,567).fill('#f06') + 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(rect.attr('id', null).attr()).toEqual(clone.attr('id', null).attr()) + expect(clone.attr('id', null).attr()).toEqual(rect.attr('id', null).attr()) }) - it('assigns a new id to the cloned element', function() { - var rect = draw.rect(100,100).center(321,567).fill('#f06') + it('assigns a new id to the cloned element', function() { clone = rect.clone() - expect(rect.attr('id')).not.toEqual(clone.attr('id')) + 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) }) }) @@ -418,9 +543,53 @@ describe('Element', function() { , 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" 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>') + }) + }) + }) }) diff --git a/spec/spec/ellipse.js b/spec/spec/ellipse.js index f9e6387..d2a492b 100644..100755 --- a/spec/spec/ellipse.js +++ b/spec/spec/ellipse.js @@ -10,10 +10,10 @@ describe('Ellipse', function() { }) describe('x()', function() { - it('should return the value of x without an argument', function() { + it('returns the value of x without an argument', function() { expect(ellipse.x()).toBe(0) }) - it('should set the value of x with the first argument', function() { + it('sets the value of x with the first argument', function() { ellipse.x(123) var box = ellipse.bbox() expect(box.x).toBe(123) @@ -21,10 +21,10 @@ describe('Ellipse', function() { }) describe('y()', function() { - it('should return the value of y without an argument', function() { + it('returns the value of y without an argument', function() { expect(ellipse.y()).toBe(0) }) - it('should set the value of cy with the first argument', function() { + it('sets the value of cy with the first argument', function() { ellipse.y(345) var box = ellipse.bbox() expect(box.y).toBe(345) @@ -32,10 +32,10 @@ describe('Ellipse', function() { }) describe('cx()', function() { - it('should return the value of cx without an argument', function() { + it('returns the value of cx without an argument', function() { expect(ellipse.cx()).toBe(120) }) - it('should set the value of cx with the first argument', function() { + it('sets the value of cx with the first argument', function() { ellipse.cx(123) var box = ellipse.bbox() expect(box.cx).toBe(123) @@ -43,10 +43,10 @@ describe('Ellipse', function() { }) describe('cy()', function() { - it('should return the value of cy without an argument', function() { + it('returns the value of cy without an argument', function() { expect(ellipse.cy()).toBe(45) }) - it('should set the value of cy with the first argument', function() { + it('sets the value of cy with the first argument', function() { ellipse.cy(345) var box = ellipse.bbox() expect(box.cy).toBe(345) @@ -54,21 +54,26 @@ describe('Ellipse', function() { }) describe('radius()', function() { - it('should set the rx and ry', function() { - ellipse.radius(10,20) + it('sets the rx and ry', function() { + ellipse.radius(10, 20) expect(ellipse.node.getAttribute('rx')).toBe('10') expect(ellipse.node.getAttribute('ry')).toBe('20') }) - it('should set the rx and ry if only rx given', function() { + it('sets the rx and ry if only rx given', function() { ellipse.radius(30) expect(ellipse.node.getAttribute('rx')).toBe('30') expect(ellipse.node.getAttribute('ry')).toBe('30') }) + it('sets the rx and ry value correctly when given 0', function() { + ellipse.radius(11, 0) + expect(ellipse.node.getAttribute('rx')).toBe('11') + expect(ellipse.node.getAttribute('ry')).toBe('0') + }) }) describe('move()', function() { - it('should set the x and y position', function() { - ellipse.move(123,456) + it('sets the x and y position', function() { + ellipse.move(123, 456) var box = ellipse.bbox() expect(box.x).toBe(123) expect(box.y).toBe(456) @@ -77,7 +82,7 @@ describe('Ellipse', function() { describe('dx()', function() { it('moves the x positon of the element relative to the current position', function() { - ellipse.move(50,60) + ellipse.move(50, 60) ellipse.dx(100) expect(ellipse.node.getAttribute('cx')).toBe('270') }) @@ -85,7 +90,7 @@ describe('Ellipse', function() { describe('dy()', function() { it('moves the y positon of the element relative to the current position', function() { - ellipse.move(50,60) + ellipse.move(50, 60) ellipse.dy(120) expect(ellipse.node.getAttribute('cy')).toBe('225') }) @@ -151,13 +156,13 @@ describe('Ellipse', function() { describe('scale()', function() { it('should scale the element universally with one argument', function() { - var box = ellipse.scale(2).bbox() + var box = ellipse.scale(2).tbox() expect(box.width).toBe(ellipse.attr('rx') * 2 * 2) expect(box.height).toBe(ellipse.attr('ry') * 2 * 2) }) it('should scale the element over individual x and y axes with two arguments', function() { - var box = ellipse.scale(2, 3.5).bbox() + var box = ellipse.scale(2, 3.5).tbox() expect(box.width).toBe(ellipse.attr('rx') * 2 * 2) expect(box.height).toBe(ellipse.attr('ry') * 2 * 3.5) @@ -165,9 +170,9 @@ describe('Ellipse', function() { }) describe('translate()', function() { - it('should set the translation of an element', function() { + it('sets the translation of an element', function() { ellipse.transform({ x: 12, y: 12 }) - expect(ellipse.node.getAttribute('transform')).toBe('translate(12 12)') + expect(ellipse.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') }) }) diff --git a/spec/spec/event.js b/spec/spec/event.js index eb167d5..cb6c4ef 100644 --- a/spec/spec/event.js +++ b/spec/spec/event.js @@ -151,45 +151,43 @@ describe('Event', function() { }) }) - // NOT SUPPORTED BY IE - // describe('mouseenter()', function() { - // it('attaches an onmouseenter event to the node of the element', function() { - // expect(typeof rect.node.onmouseenter).not.toBe('function') - // rect.mouseenter(action) - // expect(typeof rect.node.onmouseenter).toBe('function') - // }) - // it('fires the event on mouseenter', function() { - // dispatchEvent(rect.mouseenter(action), 'mouseenter') - // expect(toast).toBe('ready') - // }) - // it('applies the element as context', function() { - // dispatchEvent(rect.mouseenter(action), 'mouseenter') - // expect(context).toBe(rect) - // }) - // it('returns the called element', function() { - // expect(rect.mouseenter(action)).toBe(rect) - // }) - // }) - - // NOT SUPPORTED BY IE - // describe('mouseleave()', function() { - // it('attaches an onmouseleave event to the node of the element', function() { - // expect(typeof rect.node.onmouseleave).not.toBe('function') - // rect.mouseleave(action) - // expect(typeof rect.node.onmouseleave).toBe('function') - // }) - // it('fires the event on mouseleave', function() { - // dispatchEvent(rect.mouseleave(action), 'mouseleave') - // expect(toast).toBe('ready') - // }) - // it('applies the element as context', function() { - // dispatchEvent(rect.mouseleave(action), 'mouseleave') - // expect(context).toBe(rect) - // }) - // it('returns the called element', function() { - // expect(rect.mouseleave(action)).toBe(rect) - // }) - // }) + /*describe('mouseenter()', function() { + it('attaches an onmouseenter event to the node of the element', function() { + expect(typeof rect.node.onmouseenter).not.toBe('function') + rect.mouseenter(action) + expect(typeof rect.node.onmouseenter).toBe('function') + }) + it('fires the event on mouseenter', function() { + dispatchEvent(rect.mouseenter(action), 'mouseenter') + expect(toast).toBe('ready') + }) + it('applies the element as context', function() { + dispatchEvent(rect.mouseenter(action), 'mouseenter') + expect(context).toBe(rect) + }) + it('returns the called element', function() { + expect(rect.mouseenter(action)).toBe(rect) + }) + }) + + describe('mouseleave()', function() { + it('attaches an onmouseleave event to the node of the element', function() { + expect(typeof rect.node.onmouseleave).not.toBe('function') + rect.mouseleave(action) + expect(typeof rect.node.onmouseleave).toBe('function') + }) + it('fires the event on mouseleave', function() { + dispatchEvent(rect.mouseleave(action), 'mouseleave') + expect(toast).toBe('ready') + }) + it('applies the element as context', function() { + dispatchEvent(rect.mouseleave(action), 'mouseleave') + expect(context).toBe(rect) + }) + it('returns the called element', function() { + expect(rect.mouseleave(action)).toBe(rect) + }) + })*/ } else { @@ -289,16 +287,8 @@ describe('Event', function() { }) } -/* This function is no longer needed and only exists for compatibility issues */ -/* - describe('registerEvent()', function() { - it('creates a new custom event and stores it in the events object', function() { - expect(SVG.events['event']).toBeUndefined() - SVG.registerEvent('event') - expect(SVG.events['event'] instanceof CustomEvent).toBeTruthy() - }) - }) -*/ + + describe('on()', function() { it('attaches and event to the element', function() { diff --git a/spec/spec/fx.js b/spec/spec/fx.js index e1b15a2..2ae372d 100644 --- a/spec/spec/fx.js +++ b/spec/spec/fx.js @@ -1,88 +1,60 @@ describe('FX', function() { - var rect, fx, flag1 = flag2 = false + var rect, fx; beforeEach(function() { rect = draw.rect(100,100).move(100,100) fx = rect.animate(500) - flag1 = flag2 = false }) it('creates an instance of SVG.FX', function() { expect(fx instanceof SVG.FX).toBe(true) }) - it('animates the x/y-attr', function() { + it('animates the x/y-attr', function(done) { - runs(function(){ - fx.move(200,200) - - setTimeout(function(){ - expect(rect.x()).toBeGreaterThan(100) - expect(rect.y()).toBeGreaterThan(100) - flag1 = true - }, 250) - - setTimeout(function(){ - expect(rect.x()).toBe(200) - expect(rect.y()).toBe(200) - flag2 = true - }, 600) - }) + fx.move(200,200).after(function(){ + + expect(rect.x()).toBe(200) + expect(rect.y()).toBe(200) + done() - waitsFor(function() { - return flag1; - }, "x/y should be animated", 300); + }); - waitsFor(function() { - return flag2; - }, "x/y should be animated", 700); + setTimeout(function(){ + expect(rect.x()).toBeGreaterThan(100) + expect(rect.y()).toBeGreaterThan(100) + }, 250) }) - it('animates transformations / sets rotation-center', function() { + it('animates matrix', function(done) { - runs(function(){ + fx.transform({a:0.8, b:0.4, c:-0.15, d:0.7, e: 90.3, f: 27.07}).after(function(){ - fx.transform({ - rotation: 30, - cx: 10, - cy: 10, - x: 100, - scaleX: 0.8, - skewX: 1.2 - }) + var ctm = rect.ctm() + expect(ctm.a).toBeCloseTo(0.8) + expect(ctm.b).toBeCloseTo(0.4) + expect(ctm.c).toBeCloseTo(-0.15) + expect(ctm.d).toBeCloseTo(0.7) + expect(ctm.e).toBeCloseTo(90.3) + expect(ctm.f).toBeCloseTo(27.07) - setTimeout(function(){ - var trans = rect.transform() - expect(trans.rotation).toBeGreaterThan(0) - expect(trans.cx).toBe(10) - expect(trans.cy).toBe(10) - expect(trans.x).toBeGreaterThan(0) - expect(trans.scaleX).toBeLessThan(1) - expect(trans.skewX).toBeGreaterThan(0) - flag1 = true - }, 250) - - setTimeout(function(){ - var trans = rect.transform() - expect(trans.rotation).toBe(30) - expect(trans.cx).toBe(10) - expect(trans.cy).toBe(10) - expect(trans.x).toBe(100) - expect(trans.scaleX).toBe(0.8) - expect(trans.skewX).toBe(1.2) - flag2 = true - }, 600) + done() + }) - waitsFor(function() { - return flag1; - }, "transformation should be animated", 300); + setTimeout(function(){ + + var ctm = rect.ctm(); + console.log(ctm); + expect(ctm.a).toBeLessThan(1) + expect(ctm.b).toBeGreaterThan(0) + expect(ctm.c).toBeLessThan(0) + expect(ctm.d).toBeGreaterThan(0) + expect(ctm.e).toBeGreaterThan(0) + expect(ctm.f).toBeGreatherThan(0) + }, 250) - waitsFor(function() { - return flag2; - }, "transformation should be animated", 700); - }) })
\ No newline at end of file diff --git a/spec/spec/gradient.js b/spec/spec/gradient.js index b84ccb9..b84ccb9 100644..100755 --- a/spec/spec/gradient.js +++ b/spec/spec/gradient.js diff --git a/spec/spec/group.js b/spec/spec/group.js index dd56ed5..3187a3f 100644..100755 --- a/spec/spec/group.js +++ b/spec/spec/group.js @@ -16,9 +16,14 @@ describe('Group', function() { }) it('sets the value of x with the first argument', function() { group.x(123) - var box = group.bbox() + var box = group.tbox() expect(box.x).toBe(123) }) + it('sets the value of x correctly when called multiple times', function() { + group.x(10).x(100).x(13) + var box = group.tbox() + expect(box.x).toBe(13) + }) }) describe('y()', function() { @@ -27,9 +32,14 @@ describe('Group', function() { }) it('sets the value of y with the first argument', function() { group.y(345) - var box = group.bbox() + var box = group.tbox() expect(box.y).toBe(345) }) + it('sets the value of y correctly when called multiple times', function() { + group.y(1).y(10).y(15) + var box = group.tbox() + expect(box.y).toBe(15) + }) }) describe('cx()', function() { @@ -38,7 +48,7 @@ describe('Group', function() { }) it('sets the value of cx with the first argument', function() { group.cx(123) - var box = group.bbox() + var box = group.tbox() expect(box.cx).toBe(123) }) }) @@ -49,7 +59,7 @@ describe('Group', function() { }) it('sets the value of cy with the first argument', function() { group.cy(345) - var box = group.bbox() + var box = group.tbox() expect(box.cy).toBe(345) }) }) @@ -57,14 +67,14 @@ describe('Group', function() { describe('move()', function() { it('sets the x and y position', function() { group.move(123,456) - expect(group.node.getAttribute('transform')).toBe('translate(123 456)') + expect(group.node.getAttribute('transform')).toBe('matrix(1,0,0,1,123,456)') }) }) describe('center()', function() { it('sets the cx and cy position', function() { group.center(321,567) - var box = group.bbox() + var box = group.tbox() expect(box.cx).toBe(321) expect(box.cy).toBe(567) }) @@ -74,7 +84,7 @@ describe('Group', function() { it('moves the x positon of the element relative to the current position', function() { group.move(50,60) group.dx(100) - expect(group.node.getAttribute('transform')).toBe('translate(150 60)') + expect(group.node.getAttribute('transform')).toBe('matrix(1,0,0,1,150,60)') }) }) @@ -82,7 +92,7 @@ describe('Group', function() { it('moves the y positon of the element relative to the current position', function() { group.move(50,60) group.dy(120) - expect(group.node.getAttribute('transform')).toBe('translate(50 180)') + expect(group.node.getAttribute('transform')).toBe('matrix(1,0,0,1,50,180)') }) }) @@ -90,7 +100,7 @@ describe('Group', function() { it('moves the x and y positon of the element relative to the current position', function() { group.move(50, 60) group.dmove(80, 25) - expect(group.node.getAttribute('transform')).toBe('translate(130 85)') + expect(group.node.getAttribute('transform')).toBe('matrix(1,0,0,1,130,85)') }) }) diff --git a/spec/spec/helper.js b/spec/spec/helper.js index 8b46076..9cd88cd 100644..100755 --- a/spec/spec/helper.js +++ b/spec/spec/helper.js @@ -1,34 +1,27 @@ -/* create canavs */ +// create canavs var drawing = document.createElement('div') drawing.id = 'drawing' document.getElementsByTagName('body')[0].appendChild(drawing) draw = SVG(drawing).size(100,100) -/* raw path data */ +// raw path data svgPath = 'M88.006,61.994c3.203,0,6.216-1.248,8.481-3.514C98.752,56.215,100,53.203,100,50c0-3.204-1.248-6.216-3.513-8.481 c-2.266-2.265-5.278-3.513-8.481-3.513c-2.687,0-5.237,0.877-7.327,2.496h-7.746l5.479-5.479 c5.891-0.757,10.457-5.803,10.457-11.896c0-6.614-5.381-11.995-11.994-11.995c-6.093,0-11.14,4.567-11.896,10.457l-5.479,5.479 v-7.747c1.618-2.089,2.495-4.641,2.495-7.327c0-3.204-1.247-6.216-3.513-8.481C56.216,1.248,53.204,0,50,0 c-3.204,0-6.216,1.248-8.481,3.513c-2.265,2.265-3.513,5.277-3.513,8.481c0,2.686,0.877,5.237,2.495,7.327v7.747l-5.479-5.479 c-0.757-5.89-5.803-10.457-11.896-10.457c-6.614,0-11.995,5.381-11.995,11.995c0,6.093,4.567,11.139,10.458,11.896l5.479,5.479 h-7.747c-2.089-1.619-4.641-2.496-7.327-2.496c-3.204,0-6.216,1.248-8.481,3.513C1.248,43.784,0,46.796,0,50 c0,3.203,1.248,6.216,3.513,8.48c2.265,2.266,5.277,3.514,8.481,3.514c2.686,0,5.237-0.877,7.327-2.496h7.747l-5.479,5.479 c-5.891,0.757-10.458,5.804-10.458,11.896c0,6.614,5.381,11.994,11.995,11.994c6.093,0,11.139-4.566,11.896-10.457l5.479-5.479 v7.749c-3.63,4.7-3.291,11.497,1.018,15.806C43.784,98.752,46.796,100,50,100c3.204,0,6.216-1.248,8.481-3.514 c4.309-4.309,4.647-11.105,1.018-15.806v-7.749l5.479,5.479c0.757,5.891,5.804,10.457,11.896,10.457 c6.613,0,11.994-5.38,11.994-11.994c0-6.093-4.566-11.14-10.457-11.896l-5.479-5.479h7.746 C82.769,61.117,85.319,61.994,88.006,61.994z M76.874,68.354c4.705,0,8.52,3.814,8.52,8.521c0,4.705-3.814,8.52-8.52,8.52 s-8.52-3.814-8.52-8.52l-12.33-12.33V81.98c3.327,3.328,3.327,8.723,0,12.049c-3.327,3.328-8.722,3.328-12.049,0 c-3.327-3.326-3.327-8.721,0-12.049V64.544l-12.33,12.33c0,4.705-3.814,8.52-8.52,8.52s-8.52-3.814-8.52-8.52 c0-4.706,3.814-8.521,8.52-8.521l12.33-12.33H18.019c-3.327,3.328-8.722,3.328-12.049,0c-3.327-3.326-3.327-8.721,0-12.048 s8.722-3.327,12.049,0h17.438l-12.33-12.33c-4.706,0-8.52-3.814-8.52-8.52c0-4.706,3.814-8.52,8.52-8.52s8.52,3.814,8.52,8.52 l12.33,12.33V18.019c-3.327-3.327-3.327-8.722,0-12.049s8.722-3.327,12.049,0s3.327,8.722,0,12.049v17.438l12.33-12.33 c0-4.706,3.814-8.52,8.52-8.52s8.52,3.814,8.52,8.52c0,4.705-3.814,8.52-8.52,8.52l-12.33,12.33h17.438 c3.327-3.327,8.722-3.327,12.049,0s3.327,8.722,0,12.048c-3.327,3.328-8.722,3.328-12.049,0H64.544L76.874,68.354z' -/* image url */ +// image url imageUrl = '' -/* lorem ipsum text */ +// lorem ipsum text loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sodales\n imperdiet auctor. Nunc ultrices lectus at erat dictum pharetra\n elementum ante posuere. Duis turpis risus, blandit nec elementum et,\n posuere eget lacus. Aliquam et risus magna, eu aliquet nibh. Fusce\n consequat mi quis purus varius sagittis euismod urna interdum.\n Curabitur aliquet orci quis felis semper vulputate. Vestibulum ac nisi\n magna, id dictum diam. Proin sed metus vel magna blandit\n sodales. Pellentesque at neque ultricies nunc euismod rutrum ut in\n lorem. Mauris euismod tellus in tellus tempus interdum. Phasellus\n mattis sapien et leo feugiat dictum. Vestibulum at volutpat velit.' -/* test for touch device */ +// test for touch device window.isTouchDevice = 'ontouchstart' in document.documentElement -/* approximately helper */ -function approximately(number, precision) { - precision = precision == null ? 1.5 : precision - - return Math.round(number / precision) * precision -} - -/* strip spaces from result */ +// strip spaces from result function stripped(string) { return string.replace(/\s+/g, '') } -/* dispatch an event */ +// dispatch an event function dispatchEvent(element, name) { var e diff --git a/spec/spec/hyperlink.js b/spec/spec/hyperlink.js index 3fe78f8..affef6d 100644..100755 --- a/spec/spec/hyperlink.js +++ b/spec/spec/hyperlink.js @@ -46,14 +46,14 @@ describe('Hyperlink', function() { describe('linkTo()', function() { it('wraps the called element in a link with given url', function() { element.linkTo(url) - expect(element.parent.attr('href')).toBe(url) + expect(element.parent().attr('href')).toBe(url) }) it('wraps the called element in a link with given block', function() { element.linkTo(function(link) { link.to(url).target('_blank') }) - expect(element.parent.attr('href')).toBe(url) - expect(element.parent.attr('target')).toBe('_blank') + expect(element.parent().attr('href')).toBe(url) + expect(element.parent().attr('target')).toBe('_blank') }) }) }) diff --git a/spec/spec/image.js b/spec/spec/image.js index 91cfff6..a764265 100644..100755 --- a/spec/spec/image.js +++ b/spec/spec/image.js @@ -137,13 +137,13 @@ describe('Image', function() { describe('scale()', function() { it('should scale the element universally with one argument', function() { - var box = image.scale(2).bbox() + var box = image.scale(2).tbox() expect(box.width).toBe(image.attr('width') * 2) expect(box.height).toBe(image.attr('height') * 2) }) it('should scale the element over individual x and y axes with two arguments', function() { - var box = image.scale(2, 3.5).bbox() + var box = image.scale(2, 3.5).tbox() expect(box.width).toBe(image.attr('width') * 2) expect(box.height).toBe(image.attr('height') * 3.5) @@ -153,7 +153,7 @@ describe('Image', function() { describe('translate()', function() { it('should set the translation of an element', function() { image.transform({ x: 12, y: 12 }) - expect(image.node.getAttribute('transform')).toBe('translate(12 12)') + expect(image.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') }) }) diff --git a/spec/spec/line.js b/spec/spec/line.js index 97490ad..8e19014 100644..100755 --- a/spec/spec/line.js +++ b/spec/spec/line.js @@ -164,15 +164,15 @@ describe('Line', function() { describe('scale()', function() { it('should scale the element universally with one argument', function() { - var box1 = line.bbox() - , box2 = line.scale(2).bbox() + var box1 = line.tbox() + , box2 = line.scale(2).tbox() expect(box2.width).toBe(box1.width * 2) expect(box2.height).toBe(box1.height * 2) }) it('should scale the element over individual x and y axes with two arguments', function() { - var box1 = line.bbox() - , box2 = line.scale(2,3.5).bbox() + var box1 = line.tbox() + , box2 = line.scale(2,3.5).tbox() expect(box2.width).toBe(box1.width * 2) expect(box2.height).toBe(box1.height * 3.5) @@ -182,7 +182,7 @@ describe('Line', function() { describe('translate()', function() { it('should set the translation of an element', function() { line.transform({ x: 12, y: 12 }) - expect(line.node.getAttribute('transform')).toBe('translate(12 12)') + expect(line.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') }) }) diff --git a/spec/spec/marker.js b/spec/spec/marker.js index 8d18c85..7b902d4 100644 --- a/spec/spec/marker.js +++ b/spec/spec/marker.js @@ -16,7 +16,7 @@ describe('Marker', function() { }) it('creates marker in defs', function() { - expect(marker.parent instanceof SVG.Defs).toBeTruthy() + expect(marker.parent() instanceof SVG.Defs).toBeTruthy() }) describe('marker()', function() { @@ -47,7 +47,7 @@ describe('Marker', function() { this.ref(5, 6) }) - marker = path.marker('mid') + marker = path.marker('mid', 10, 10) }) it('creates an instance of SVG.Marker', function() { diff --git a/spec/spec/mask.js b/spec/spec/mask.js index a13d842..ab367a0 100644..100755 --- a/spec/spec/mask.js +++ b/spec/spec/mask.js @@ -12,19 +12,19 @@ describe('Mask', function() { }) it('moves the masking element to a new mask node', function() { - expect(circle.parent instanceof SVG.Mask).toBe(true) + expect(circle.parent() instanceof SVG.Mask).toBe(true) }) it('creates the mask node in the defs node', function() { - expect(circle.parent.parent).toBe(draw.defs()) + expect(circle.parent().parent()).toBe(draw.defs()) }) it('sets the "mask" attribute on the masked element with the mask id', function() { - expect(rect.attr('mask')).toBe('url("#' + circle.parent.attr('id') + '")') + expect(rect.attr('mask')).toBe('url("#' + circle.parent().attr('id') + '")') }) it('references the mask element in the masked element', function() { - expect(rect.masker).toBe(circle.parent) + expect(rect.masker).toBe(circle.parent()) }) it('references the masked element in the mask target list', function() { diff --git a/spec/spec/matrix.js b/spec/spec/matrix.js new file mode 100644 index 0000000..5c4776b --- /dev/null +++ b/spec/spec/matrix.js @@ -0,0 +1,298 @@ +describe('Matrix', function() { + var matrix + + describe('initialization', function() { + + describe('without a source', function() { + + beforeEach(function() { + matrix = new SVG.Matrix + }) + + it('creates a new matrix with default values', function() { + expect(matrix.a).toBe(1) + expect(matrix.b).toBe(0) + expect(matrix.c).toBe(0) + expect(matrix.d).toBe(1) + expect(matrix.e).toBe(0) + expect(matrix.f).toBe(0) + }) + + describe('extract()', function() { + var extract + + beforeEach(function() { + extract = matrix.extract() + }) + + it('parses translation values', function() { + expect(extract.x).toBe(0) + expect(extract.y).toBe(0) + }) + it('parses skew values', function() { + expect(extract.skewX).toBe(0) + expect(extract.skewY).toBe(0) + }) + it('parses scale values', function() { + expect(extract.scaleX).toBe(1) + expect(extract.scaleY).toBe(1) + }) + it('parses rotatoin value', function() { + expect(extract.rotation).toBe(0) + }) + }) + + describe('toString()' , function() { + it('exports correctly to a string', function() { + expect(matrix.toString()).toBe('matrix(1,0,0,1,0,0)') + }) + }) + }) + + describe('with an element given', function() { + var rect + + beforeEach(function() { + rect = draw.rect(100, 100) + .transform({ rotation: -10 }, true) + .transform({ x: 40, y: 50 }, true) + .transform({ scale: 2 }, true) + + matrix = new SVG.Matrix(rect) + }) + + it('parses the current transform matrix from an element', function() { + expect(matrix.a).toBeCloseTo(1.9696155786514282) + expect(matrix.b).toBeCloseTo(-0.3472963869571686) + expect(matrix.c).toBeCloseTo(0.3472963869571686) + expect(matrix.d).toBeCloseTo(1.9696155786514282) + expect(matrix.e).toBeCloseTo(-17.770875930786133) + expect(matrix.f).toBeCloseTo(11.178505897521973) + }) + + describe('extract()', function() { + + it('parses translation values', function() { + var extract = new SVG.Matrix(draw.rect(100, 100).translate(40, 50)).extract() + expect(extract.x).toBeCloseTo(40) + expect(extract.y).toBeCloseTo(50) + }) + it('parses skewX value', function() { + var extract = new SVG.Matrix(draw.rect(100, 100).skew(25, 0)).extract() + expect(extract.skewX).toBeCloseTo(25) + }) + it('parses skewY value', function() { + var extract = new SVG.Matrix(draw.rect(100, 100).skew(0, 20)).extract() + expect(extract.skewY).toBeCloseTo(20) + }) + it('parses scale values', function() { + var extract = new SVG.Matrix(draw.rect(100, 100).scale(2, 3)).extract() + expect(extract.scaleX).toBeCloseTo(2) + expect(extract.scaleY).toBeCloseTo(3) + }) + it('parses rotatoin value', function() { + var extract = new SVG.Matrix(draw.rect(100, 100).rotate(-100)).extract() + expect(extract.rotation).toBeCloseTo(-100) + }) + + }) + + }) + + describe('with a string given', function() { + it('parses the string value correctly', function() { + var matrix = new SVG.Matrix('2, 0, 0, 2, 100, 50') + + expect(matrix.a).toBe(2) + expect(matrix.b).toBe(0) + expect(matrix.c).toBe(0) + expect(matrix.d).toBe(2) + expect(matrix.e).toBe(100) + expect(matrix.f).toBe(50) + }) + }) + + }) + + describe('morph()', function() { + it('stores a given matrix for morphing', function() { + var matrix1 = new SVG.Matrix(2, 0, 0, 5, 0, 0) + , matrix2 = new SVG.Matrix(1, 0, 0, 1, 4, 3) + + matrix1.morph(matrix2) + + expect(matrix1.destination).toEqual(matrix2) + }) + it('stores a clone, not the given matrix itself', function() { + var matrix1 = new SVG.Matrix(2, 0, 0, 5, 0, 0) + , matrix2 = new SVG.Matrix(1, 0, 0, 1, 4, 3) + + matrix1.morph(matrix2) + + expect(matrix1.destination).not.toBe(matrix2) + }) + }) + + describe('at()', function() { + it('returns a morphed array at a given position', function() { + var matrix1 = new SVG.Matrix(2, 0, 0, 5, 0, 0) + , matrix2 = new SVG.Matrix(1, 0, 0, 1, 4, 3) + , matrix3 = matrix1.morph(matrix2).at(0.5) + + expect(matrix1.toString()).toBe('matrix(2,0,0,5,0,0)') + expect(matrix2.toString()).toBe('matrix(1,0,0,1,4,3)') + expect(matrix3.toString()).toBe('matrix(1.5,0,0,3,2,1.5)') + }) + }) + + describe('multiply()', function() { + it('multiplies two matices', function() { + var matrix1 = new SVG.Matrix(2, 0, 0, 5, 0, 0) + , matrix2 = new SVG.Matrix(1, 0, 0, 1, 4, 3) + , matrix3 = matrix1.multiply(matrix2) + + expect(matrix1.toString()).toBe('matrix(2,0,0,5,0,0)') + expect(matrix2.toString()).toBe('matrix(1,0,0,1,4,3)') + expect(matrix3.toString()).toBe('matrix(2,0,0,5,8,15)') + }) + it('accepts matrices in any form', function() { + var matrix1 = new SVG.Matrix(2, 0, 0, 5, 0, 0) + , matrix2 = matrix1.multiply('1,0,0,1,4,3') + + expect(matrix1.toString()).toBe('matrix(2,0,0,5,0,0)') + expect(matrix2.toString()).toBe('matrix(2,0,0,5,8,15)') + }) + }) + + describe('inverse()', function() { + it('inverses matrix', function() { + var matrix1 = new SVG.Matrix(2, 0, 0, 5, 4, 3) + , matrix2 = matrix1.inverse() + + expect(matrix1.toString()).toBe('matrix(2,0,0,5,4,3)') + expect(matrix2.toString()).toBe('matrix(0.5,0,0,0.2,-2,-0.6)') + }) + }) + + describe('translate()', function() { + it('translates matrix by given x and y values', function() { + var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).translate(10, 12.5) + + expect(matrix.e).toBe(14) + expect(matrix.f).toBe(15.5) + }) + }) + + describe('scale()', function() { + it('performs a uniformal scale with one value', function() { + var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).scale(3) + + expect(matrix.a).toBe(3) + expect(matrix.d).toBe(3) + expect(matrix.e).toBe(4) + expect(matrix.f).toBe(3) + }) + it('performs a non-uniformal scale with two values', function() { + var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).scale(2.5, 3.5) + + expect(matrix.a).toBe(2.5) + expect(matrix.d).toBe(3.5) + expect(matrix.e).toBe(4) + expect(matrix.f).toBe(3) + }) + it('performs a uniformal scale at a given center point with three values', function() { + var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).scale(3, 150, 100) + + expect(matrix.a).toBe(3) + expect(matrix.d).toBe(3) + expect(matrix.e).toBe(-2) + expect(matrix.f).toBe(-197) + }) + it('performs a non-uniformal scale at a given center point with our values', function() { + var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).scale(3, 2, 150, 100) + + expect(matrix.a).toBe(3) + expect(matrix.d).toBe(2) + expect(matrix.e).toBe(-296) + expect(matrix.f).toBe(-97) + }) + }) + + describe('rotate()', function() { + it('performs a rotation with one argument', function() { + var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).rotate(30) + + expect(matrix.a).toBeCloseTo(0.8660254037844387) + expect(matrix.d).toBeCloseTo(0.8660254037844387) + expect(matrix.e).toBe(4) + expect(matrix.f).toBe(3) + }) + it('performs a rotation on a given point with three arguments', function() { + var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).rotate(30, 150, 100) + + expect(matrix.a).toBeCloseTo(0.8660254037844387) + expect(matrix.d).toBeCloseTo(0.8660254037844387) + expect(matrix.e).toBeCloseTo(74.0961894323342) + expect(matrix.f).toBeCloseTo(-58.60254037844388) + }) + }) + + describe('flip()', function() { + describe('with x given', function() { + it('performs a flip over the horizontal axis with one argument', function() { + var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).flip('x') + + expect(matrix.a).toBe(-1) + expect(matrix.d).toBe(1) + expect(matrix.e).toBe(4) + expect(matrix.f).toBe(3) + }) + it('performs a flip over the horizontal axis over a given point with two arguments', function() { + var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).flip('x', 150) + + expect(matrix.a).toBe(-1) + expect(matrix.d).toBe(1) + expect(matrix.e).toBe(304) + expect(matrix.f).toBe(3) + }) + }) + describe('with y given', function() { + it('performs a flip over the vertical axis with one argument', function() { + var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).flip('y') + + expect(matrix.a).toBe(1) + expect(matrix.d).toBe(-1) + expect(matrix.e).toBe(4) + expect(matrix.f).toBe(3) + }) + it('performs a flip over the vertical axis over a given point with two arguments', function() { + var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).flip('y', 100) + + expect(matrix.a).toBe(1) + expect(matrix.d).toBe(-1) + expect(matrix.e).toBe(4) + expect(matrix.f).toBe(203) + }) + }) + }) + + describe('skew()', function() { + it('performs a skew two arguments', function() { + var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3)//.skew(0, 0) + + expect(matrix.a).toBe(1) + expect(matrix.b).toBe(0) + expect(matrix.c).toBe(0) + expect(matrix.d).toBe(1) + expect(matrix.e).toBe(4) + expect(matrix.f).toBe(3) + }) + }) + + describe('native()', function() { + it('returns the node reference', function() { + expect(new SVG.Matrix().native() instanceof SVGMatrix).toBeTruthy() + }) + }) + +})
\ No newline at end of file diff --git a/spec/spec/memory.js b/spec/spec/memory.js index c7bb942..c7bb942 100644..100755 --- a/spec/spec/memory.js +++ b/spec/spec/memory.js diff --git a/spec/spec/number.js b/spec/spec/number.js index f35be5f..de00481 100644..100755 --- a/spec/spec/number.js +++ b/spec/spec/number.js @@ -12,6 +12,11 @@ describe('Number', function() { it('has a blank unit', function() { expect(number.unit).toBe('') }) + it('accepts the unit as a second argument', function() { + number = new SVG.Number(30, '%') + expect(number.value).toBe(30) + expect(number.unit).toBe('%') + }) it('parses a pixel value', function() { number = new SVG.Number('20px') expect(number.value).toBe(20) @@ -91,86 +96,85 @@ describe('Number', function() { describe('to()', function() { beforeEach(function() { - number.plus(4) + number = number.plus(4) + }) + it('returns a new instance', function() { + expect(number.to('em')).not.toBe(number) + expect(number.to('em') instanceof SVG.Number).toBeTruthy() }) it('changes the unit value', function() { - number.to('%') + number = number.to('%') expect(number.unit).toBe('%') }) it('changes the output value', function() { var oldNumber = number.valueOf() - number.to('%') + number = number.to('%') expect(number.toString()).toBe('400%') }) }) describe('plus()', function() { + it('returns a new instance', function() { + expect(number.plus(4.5)).not.toBe(number) + expect(number.plus(4.5) instanceof SVG.Number).toBeTruthy() + }) it('adds a given number', function() { - number.plus(3.5) - expect(number.valueOf()).toBe(3.5) + expect(number.plus(3.5).valueOf()).toBe(3.5) }) it('adds a given percentage value', function() { - number.plus('225%') - expect(number.valueOf()).toBe(2.25) + expect(number.plus('225%').valueOf()).toBe(2.25) }) it('adds a given pixel value', function() { - number.plus('83px') - expect(number.valueOf()).toBe(83) + expect(number.plus('83px').valueOf()).toBe(83) }) }) describe('minus()', function() { it('subtracts a given number', function() { - number.minus(3.7) - expect(number.valueOf()).toBe(-3.7) + expect(number.minus(3.7).valueOf()).toBe(-3.7) }) it('subtracts a given percentage value', function() { - number.minus('223%') - expect(number.valueOf()).toBe(-2.23) + expect(number.minus('223%').valueOf()).toBe(-2.23) }) it('subtracts a given pixel value', function() { - number.minus('85px') - expect(number.valueOf()).toBe(-85) + expect(number.minus('85px').valueOf()).toBe(-85) }) }) describe('times()', function() { beforeEach(function() { - number.plus(4) + number = number.plus(4) }) it('multiplies with a given number', function() { - number.times(3) - expect(number.valueOf()).toBe(12) + expect(number.times(3).valueOf()).toBe(12) }) it('multiplies with a given percentage value', function() { - number.times('110%') - expect(number.valueOf()).toBe(4.4) + expect(number.times('110%').valueOf()).toBe(4.4) }) it('multiplies with a given pixel value', function() { - number.times('85px') - expect(number.valueOf()).toBe(340) + expect(number.times('85px').valueOf()).toBe(340) }) }) describe('divide()', function() { beforeEach(function() { - number.plus(90) + number = number.plus(90) }) it('divides by a given number', function() { - number.divide(3) - expect(number.valueOf()).toBe(30) + expect(number.divide(3).valueOf()).toBe(30) }) it('divides by a given percentage value', function() { - number.divide('3000%') - expect(number.valueOf()).toBe(3) + expect(number.divide('3000%').valueOf()).toBe(3) }) it('divides by a given pixel value', function() { - number.divide('45px') - expect(number.valueOf()).toBe(2) + expect(number.divide('45px').valueOf()).toBe(2) }) }) describe('morph()', function() { + it('returns itself', function() { + expect(number.morph(new SVG.Number)).toBe(number) + }) it('prepares the color for morphing', function() { var destination = new SVG.Number number.morph(destination) @@ -179,6 +183,12 @@ describe('Number', function() { }) describe('at()', function() { + it('returns a new instance', function() { + var destination = new SVG.Number(200) + var morphed = number.morph(destination).at(0.4) + expect(morphed).not.toBe(number) + expect(morphed).not.toBe(destination) + }) it('morphes number to a given position', function() { var destination = new SVG.Number(200) var morphed = number.morph(destination).at(0.4) diff --git a/spec/spec/path.js b/spec/spec/path.js index 6ca9cfe..b333002 100644..100755 --- a/spec/spec/path.js +++ b/spec/spec/path.js @@ -8,12 +8,21 @@ describe('Path', function() { afterEach(function() { draw.clear() }) + + describe('array()', function() { + it('returns an instance of SVG.PathArray', function() { + expect(path.array() instanceof SVG.PathArray).toBeTruthy() + }) + it('returns the value stored in the private variable _array', function() { + expect(path.array()).toBe(path._array) + }) + }) describe('x()', function() { - it('should return the value of x without an argument', function() { + it('returns the value of x without an argument', function() { expect(path.x()).toBe(0) }) - it('should set the value of x with the first argument', function() { + it('sets the value of x with the first argument', function() { path.x(123) var box = path.bbox() expect(box.x).toBe(123) @@ -21,10 +30,10 @@ describe('Path', function() { }) describe('y()', function() { - it('should return the value of y without an argument', function() { + it('returns the value of y without an argument', function() { expect(path.y()).toBe(0) }) - it('should set the value of y with the first argument', function() { + it('sets the value of y with the first argument', function() { path.y(345) var box = path.bbox() expect(box.y).toBe(345) @@ -32,10 +41,10 @@ describe('Path', function() { }) describe('cx()', function() { - it('should return the value of cx without an argument', function() { + it('returns the value of cx without an argument', function() { expect(path.cx()).toBe(50) }) - it('should set the value of cx with the first argument', function() { + it('sets the value of cx with the first argument', function() { path.cx(123) var box = path.bbox() expect(box.cx).toBe(123) @@ -43,10 +52,10 @@ describe('Path', function() { }) describe('cy()', function() { - it('should return the value of cy without an argument', function() { + it('returns the value of cy without an argument', function() { expect(path.cy()).toBe(50) }) - it('should set the value of cy with the first argument', function() { + it('sets the value of cy with the first argument', function() { path.cy(345) var box = path.bbox() expect(box.cy).toBe(345) @@ -54,14 +63,14 @@ describe('Path', function() { }) describe('move()', function() { - it('should set the x and y position', function() { + it('sets the x and y position', function() { path.move(123,456) var box = path.bbox() expect(box.x).toBe(123) expect(box.y).toBe(456) }) - it('should set the x and y position when scaled to half its size', function() { - path.scale(0.5).move(123,456) + it('sets the x and y position when scaled to half its size', function() { + path.scale(0.5, 0, 0).move(123,456) var box = path.bbox() expect(box.x).toBe(123) expect(box.y).toBe(456) @@ -97,7 +106,7 @@ describe('Path', function() { }) describe('center()', function() { - it('should set the cx and cy position', function() { + it('sets the cx and cy position', function() { path.center(321,567) var box = path.bbox() expect(box.x).toBe(271) @@ -109,11 +118,11 @@ describe('Path', function() { it('sets the width of the element', function() { path.width(234) var box = path.bbox() - expect(approximately(box.width, 0.1)).toBe(234) + expect(box.width).toBeCloseTo(234) }) it('gets the width of the element aithout an agrument', function() { path.width(456) - expect(approximately(path.width(), 0.1)).toBe(456) + expect(path.width()).toBeCloseTo(456) }) }) @@ -121,11 +130,11 @@ describe('Path', function() { it('sets the height of the element', function() { path.height(654) var box = path.bbox() - expect(approximately(box.height, 0.1)).toBe(654) + expect(box.height).toBeCloseTo(654) }) it('gets the height of the element aithout an agrument', function() { path.height(321) - expect(approximately(path.height(), 0.1)).toBe(321) + expect(path.height()).toBeCloseTo(321) }) }) @@ -133,8 +142,8 @@ describe('Path', function() { it('defines the width and height of the element', function() { path.size(987,654) var box = path.bbox() - expect(approximately(box.width, 0.1)).toBe(987) - expect(approximately(box.height, 0.1)).toBe(654) + expect(box.width).toBeCloseTo(987) + expect(box.height).toBeCloseTo(654) }) it('defines the width and height proportionally with only the width value given', function() { var box = path.bbox() @@ -152,15 +161,15 @@ describe('Path', function() { describe('scale()', function() { it('should scale the element universally with one argument', function() { - var box1 = path.bbox() - , box2 = path.scale(2).bbox() + var box1 = path.tbox() + , box2 = path.scale(2).tbox() expect(box1.width * 2).toBe(box2.width) expect(box1.height * 2).toBe(box2.height) }) it('should scale the element over individual x and y axes with two arguments', function() { - var box1 = path.bbox() - , box2 = path.scale(2, 3.5).bbox() + var box1 = path.tbox() + , box2 = path.scale(2, 3.5).tbox() expect(box1.width * 2).toBe(box2.width) expect(box1.height * 3.5).toBe(box2.height) @@ -168,9 +177,9 @@ describe('Path', function() { }) describe('translate()', function() { - it('should set the translation of an element', function() { + it('sets the translation of an element', function() { path.transform({ x: 12, y: 12 }) - expect(path.node.getAttribute('transform')).toBe('translate(12 12)') + expect(path.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') }) }) diff --git a/spec/spec/pattern.js b/spec/spec/pattern.js index 4abdadc..4abdadc 100644..100755 --- a/spec/spec/pattern.js +++ b/spec/spec/pattern.js diff --git a/spec/spec/polygon.js b/spec/spec/polygon.js index 88899de..b6dc40d 100644..100755 --- a/spec/spec/polygon.js +++ b/spec/spec/polygon.js @@ -8,12 +8,21 @@ describe('Polygon', function() { afterEach(function() { draw.clear() }) + + describe('array()', function() { + it('returns an instance of SVG.PointArray', function() { + expect(polygon.array() instanceof SVG.PointArray).toBeTruthy() + }) + it('returns the value stored in the private variable _array', function() { + expect(polygon.array()).toBe(polygon._array) + }) + }) describe('x()', function() { - it('should return the value of x without an argument', function() { + it('returns the value of x without an argument', function() { expect(polygon.x()).toBe(0) }) - it('should set the value of x with the first argument', function() { + it('sets the value of x with the first argument', function() { polygon.x(123) var box = polygon.bbox() expect(box.x).toBe(123) @@ -21,10 +30,10 @@ describe('Polygon', function() { }) describe('y()', function() { - it('should return the value of y without an argument', function() { + it('returns the value of y without an argument', function() { expect(polygon.y()).toBe(0) }) - it('should set the value of y with the first argument', function() { + it('sets the value of y with the first argument', function() { polygon.y(345) var box = polygon.bbox() expect(box.y).toBe(345) @@ -32,10 +41,10 @@ describe('Polygon', function() { }) describe('cx()', function() { - it('should return the value of cx without an argument', function() { + it('returns the value of cx without an argument', function() { expect(polygon.cx()).toBe(50) }) - it('should set the value of cx with the first argument', function() { + it('sets the value of cx with the first argument', function() { polygon.cx(123) var box = polygon.bbox() expect(box.cx).toBe(123) @@ -43,10 +52,10 @@ describe('Polygon', function() { }) describe('cy()', function() { - it('should return the value of cy without an argument', function() { + it('returns the value of cy without an argument', function() { expect(polygon.cy()).toBe(50) }) - it('should set the value of cy with the first argument', function() { + it('sets the value of cy with the first argument', function() { polygon.cy(345) var box = polygon.bbox() expect(box.cy).toBe(345) @@ -54,7 +63,7 @@ describe('Polygon', function() { }) describe('move()', function() { - it('should set the x and y position', function() { + it('sets the x and y position', function() { polygon.move(123,456) var box = polygon.bbox() expect(box.x).toBe(123) @@ -91,7 +100,7 @@ describe('Polygon', function() { }) describe('center()', function() { - it('should set the cx and cy position', function() { + it('sets the cx and cy position', function() { polygon.center(321,567) var box = polygon.bbox() expect(box.x).toBe(271) @@ -103,11 +112,11 @@ describe('Polygon', function() { it('sets the width and height of the element', function() { polygon.width(987) var box = polygon.bbox() - expect(approximately(box.width, 0.1)).toBe(987) + expect(box.width).toBeCloseTo(987) }) it('gets the width and height of the element without an argument', function() { polygon.width(789) - expect(approximately(polygon.width(), 0.1)).toBe(789) + expect(polygon.width()).toBeCloseTo(789) }) }) @@ -115,11 +124,11 @@ describe('Polygon', function() { it('sets the height and height of the element', function() { polygon.height(987) var box = polygon.bbox() - expect(approximately(box.height, 0.1)).toBe(987) + expect(box.height).toBeCloseTo(987) }) it('gets the height and height of the element without an argument', function() { polygon.height(789) - expect(approximately(polygon.height(), 0.1)).toBe(789) + expect(polygon.height()).toBeCloseTo(789) }) }) @@ -127,8 +136,8 @@ describe('Polygon', function() { it('should define the width and height of the element', function() { polygon.size(987,654) var box = polygon.bbox() - expect(approximately(box.width, 0.1)).toBe(987) - expect(approximately(box.height, 0.1)).toBe(654) + expect(box.width).toBeCloseTo(987) + expect(box.height).toBeCloseTo(654) }) it('defines the width and height proportionally with only the width value given', function() { var box = polygon.bbox() @@ -146,15 +155,15 @@ describe('Polygon', function() { describe('scale()', function() { it('should scale the element universally with one argument', function() { - var box1 = polygon.bbox() - , box2 = polygon.scale(2).bbox() + var box1 = polygon.tbox() + , box2 = polygon.scale(2).tbox() expect(box2.width).toBe(box1.width * 2) expect(box2.height).toBe(box1.height * 2) }) it('should scale the element over individual x and y axes with two arguments', function() { - var box1 = polygon.bbox() - , box2 = polygon.scale(2, 3.5).bbox() + var box1 = polygon.tbox() + , box2 = polygon.scale(2, 3.5).tbox() expect(box2.width).toBe(box1.width * 2) expect(box2.height).toBe(box1.height * 3.5) @@ -162,9 +171,9 @@ describe('Polygon', function() { }) describe('translate()', function() { - it('should set the translation of an element', function() { + it('sets the translation of an element', function() { polygon.transform({ x: 12, y: 12 }) - expect(polygon.node.getAttribute('transform')).toBe('translate(12 12)') + expect(polygon.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') }) }) diff --git a/spec/spec/polyline.js b/spec/spec/polyline.js index 99dce1a..eeb761f 100644..100755 --- a/spec/spec/polyline.js +++ b/spec/spec/polyline.js @@ -8,12 +8,21 @@ describe('Polyline', function() { afterEach(function() { draw.clear() }) + + describe('array()', function() { + it('returns an instance of SVG.PointArray', function() { + expect(polyline.array() instanceof SVG.PointArray).toBeTruthy() + }) + it('returns the value stored in the private variable _array', function() { + expect(polyline.array()).toBe(polyline._array) + }) + }) describe('x()', function() { - it('should return the value of x without an argument', function() { + it('returns the value of x without an argument', function() { expect(polyline.x()).toBe(0) }) - it('should set the value of x with the first argument', function() { + it('sets the value of x with the first argument', function() { polyline.x(123) var box = polyline.bbox() expect(box.x).toBe(123) @@ -21,10 +30,10 @@ describe('Polyline', function() { }) describe('y()', function() { - it('should return the value of y without an argument', function() { + it('returns the value of y without an argument', function() { expect(polyline.y()).toBe(0) }) - it('should set the value of y with the first argument', function() { + it('sets the value of y with the first argument', function() { polyline.y(345) var box = polyline.bbox() expect(box.y).toBe(345) @@ -32,10 +41,10 @@ describe('Polyline', function() { }) describe('cx()', function() { - it('should return the value of cx without an argument', function() { + it('returns the value of cx without an argument', function() { expect(polyline.cx()).toBe(50) }) - it('should set the value of cx with the first argument', function() { + it('sets the value of cx with the first argument', function() { polyline.cx(123) var box = polyline.bbox() expect(box.cx).toBe(123) @@ -43,10 +52,10 @@ describe('Polyline', function() { }) describe('cy()', function() { - it('should return the value of cy without an argument', function() { + it('returns the value of cy without an argument', function() { expect(polyline.cy()).toBe(50) }) - it('should set the value of cy with the first argument', function() { + it('sets the value of cy with the first argument', function() { polyline.cy(345) var box = polyline.bbox() expect(box.cy).toBe(345) @@ -54,7 +63,7 @@ describe('Polyline', function() { }) describe('move()', function() { - it('should set the x and y position', function() { + it('sets the x and y position', function() { polyline.move(123,456) var box = polyline.bbox() expect(box.x).toBe(123) @@ -91,7 +100,7 @@ describe('Polyline', function() { }) describe('center()', function() { - it('should set the cx and cy position', function() { + it('sets the cx and cy position', function() { polyline.center(321,567) var box = polyline.bbox() expect(box.x).toBe(271) @@ -103,11 +112,11 @@ describe('Polyline', function() { it('sets the width and height of the element', function() { polyline.width(987) var box = polyline.bbox() - expect(approximately(box.width, 0.1)).toBe(987) + expect(box.width).toBeCloseTo(987, 1) }) it('gets the width and height of the element without an argument', function() { polyline.width(789) - expect(approximately(polyline.width(), 0.1)).toBe(789) + expect(polyline.width()).toBeCloseTo(789) }) }) @@ -115,11 +124,11 @@ describe('Polyline', function() { it('sets the height and height of the element', function() { polyline.height(987) var box = polyline.bbox() - expect(approximately(box.height, 0.1)).toBe(987) + expect(box.height).toBeCloseTo(987) }) it('gets the height and height of the element without an argument', function() { polyline.height(789) - expect(approximately(polyline.height(), 0.1)).toBe(789) + expect(polyline.height()).toBeCloseTo(789) }) }) @@ -127,8 +136,8 @@ describe('Polyline', function() { it('should define the width and height of the element', function() { polyline.size(987,654) var box = polyline.bbox() - expect(approximately(box.width, 0.1)).toBe(987) - expect(approximately(box.height, 0.1)).toBe(654) + expect(box.width).toBeCloseTo(987) + expect(box.height).toBeCloseTo(654) }) it('defines the width and height proportionally with only the width value given', function() { var box = polyline.bbox() @@ -146,15 +155,15 @@ describe('Polyline', function() { describe('scale()', function() { it('should scale the element universally with one argument', function() { - var box1 = polyline.bbox() - , box2 = polyline.scale(2).bbox() + var box1 = polyline.tbox() + , box2 = polyline.scale(2).tbox() expect(box2.width).toBe(box1.width * 2) expect(box2.height).toBe(box1.height * 2) }) it('should scale the element over individual x and y axes with two arguments', function() { - var box1 = polyline.bbox() - , box2 = polyline.scale(2, 3.5).bbox() + var box1 = polyline.tbox() + , box2 = polyline.scale(2, 3.5).tbox() expect(box2.width).toBe(box1.width * 2) expect(box2.height).toBe(box1.height * 3.5) @@ -162,9 +171,9 @@ describe('Polyline', function() { }) describe('translate()', function() { - it('should set the translation of an element', function() { + it('sets the translation of an element', function() { polyline.transform({ x: 12, y: 12 }) - expect(polyline.node.getAttribute('transform')).toBe('translate(12 12)') + expect(polyline.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') }) }) diff --git a/spec/spec/rect.js b/spec/spec/rect.js index 938bc46..cd82d39 100644..100755 --- a/spec/spec/rect.js +++ b/spec/spec/rect.js @@ -150,13 +150,13 @@ describe('Rect', function() { describe('scale()', function() { it('should scale the element universally with one argument', function() { - var box = rect.scale(2).bbox() + var box = rect.scale(2).tbox() expect(box.width).toBe(rect.attr('width') * 2) expect(box.height).toBe(rect.attr('height') * 2) }) it('should scale the element over individual x and y axes with two arguments', function() { - var box = rect.scale(2, 3.5).bbox() + var box = rect.scale(2, 3.5).tbox() expect(box.width).toBe(rect.attr('width') * 2) expect(box.height).toBe(rect.attr('height') * 3.5) @@ -166,7 +166,7 @@ describe('Rect', function() { describe('translate()', function() { it('should set the translation of an element', function() { rect.transform({ x: 12, y: 12 }) - expect(rect.node.getAttribute('transform')).toBe('translate(12 12)') + expect(rect.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') }) }) diff --git a/spec/spec/regex.js b/spec/spec/regex.js index 4b36175..f017059 100644 --- a/spec/spec/regex.js +++ b/spec/spec/regex.js @@ -22,17 +22,30 @@ describe('Regex', function() { }) describe('testers', function() { - describe('isEvent', function() { - it('is true with a namespaced and lowercase name', function() { - expect(SVG.regex.isEvent.test('my:event')).toBeTruthy() + + describe('isHex', function() { + it('is true with a three based hex', function() { + expect(SVG.regex.isHex.test('#f09')).toBeTruthy() + }) + it('is true with a six based hex', function() { + expect(SVG.regex.isHex.test('#fe0198')).toBeTruthy() + }) + it('is false with a faulty hex', function() { + expect(SVG.regex.isHex.test('###')).toBeFalsy() + expect(SVG.regex.isHex.test('#0')).toBeFalsy() + expect(SVG.regex.isHex.test('f06')).toBeFalsy() }) - it('is true with a namespaced and camelCase name', function() { - expect(SVG.regex.isEvent.test('mt:fabulousEvent')).toBeTruthy() + }) + + describe('isRgb', function() { + it('is true with an rgb value', function() { + expect(SVG.regex.isRgb.test('rgb(255,66,100)')).toBeTruthy() }) - it('is false without a namespace', function() { - expect(SVG.regex.isEvent.test('idontlinkenamespaces')).toBeFalsy() + it('is false with a non-rgb value', function() { + expect(SVG.regex.isRgb.test('hsb(255, 100, 100)')).toBeFalsy() }) }) + }) })
\ No newline at end of file diff --git a/spec/spec/selector.js b/spec/spec/selector.js index eb3d6bf..b28213d 100644 --- a/spec/spec/selector.js +++ b/spec/spec/selector.js @@ -10,7 +10,6 @@ describe('Selector', function() { var element = draw.group() , got = SVG.get(element.attr('id')) - expect(got.transform()).toEqual(SVG.defaults.trans()) expect(got.attr()).toEqual(element.attr()) }) it('gets a referenced element by attribute value', function() { @@ -19,9 +18,40 @@ describe('Selector', function() { , mark = draw.marker(10, 10) , path = draw.path(svgPath).marker('end', mark) - expect(SVG.get(use.attr().href)).toBe(rect) + expect(SVG.get(use.attr('href'))).toBe(rect) expect(SVG.get(path.attr('marker-end'))).toBe(mark) }) }) + + describe('select()', function() { + var e1, e2, e3, e4 ,e5 + + beforeEach(function() { + e1 = draw.rect(100, 100).addClass('selectable-element') + e2 = draw.rect(100, 100).addClass('unselectable-element') + e3 = draw.rect(100, 100).addClass('selectable-element') + e4 = draw.rect(100, 100).addClass('unselectable-element') + e5 = draw.rect(100, 100).addClass('selectable-element') + }) + it('gets all elements with a given class name', function() { + expect(SVG.select('rect.selectable-element').valueOf()).toEqual([e1, e3, e5]) + }) + it('returns an instance of SVG.Set', function() { + expect(SVG.select('rect.selectable-element') instanceof SVG.Set).toBe(true) + }) + }) + + describe('Parent#select()', function() { + it('gets all elements with a given class name inside a given element', function() { + var group = draw.group() + , e1 = draw.rect(100, 100).addClass('selectable-element') + , e2 = draw.rect(100, 100).addClass('unselectable-element') + , e3 = group.rect(100, 100).addClass('selectable-element') + , e4 = draw.rect(100, 100).addClass('unselectable-element') + , e5 = group.rect(100, 100).addClass('selectable-element') + + expect(group.select('rect.selectable-element').valueOf()).toEqual([e3, e5]) + }) + }) })
\ No newline at end of file diff --git a/spec/spec/set.js b/spec/spec/set.js index 1238ee7..1e75363 100644..100755 --- a/spec/spec/set.js +++ b/spec/spec/set.js @@ -19,6 +19,12 @@ describe('Set', function() { expect(draw.set() instanceof SVG.Set).toBeTruthy() }) + it('creates a set with initial value', function() { + var members = [1, 2, 4] + + expect(draw.set(members).valueOf()).toBe(members) + }) + describe('add()', function() { it('returns the set instance', function() { expect(set.add(e1)).toBe(set) @@ -102,6 +108,13 @@ describe('Set', function() { }) }) + describe('length()', function() { + it('gets the length of the set', function() { + set.add(e1).add(e2).add(e3).add(e4).add(e5) + expect(set.length()).toBe(5) + }) + }) + describe('index()', function() { it('returns the index of a given element within the set', function() { set.add(e1).add(e2).add(e3).add(e5) diff --git a/spec/spec/svg.js b/spec/spec/svg.js index 5f3f1d1..cc9f7be 100644..100755 --- a/spec/spec/svg.js +++ b/spec/spec/svg.js @@ -48,7 +48,7 @@ describe('SVG', function() { return this.opacity(0.2) } }) - expect(draw.rect(100,100).soft() instanceof SVG.Rect).toBeTruthy() + expect(typeof SVG.Rect.prototype.soft).toBe('function') expect(draw.rect(100,100).soft().attr('opacity')).toBe(0.2) }) diff --git a/spec/spec/symbol.js b/spec/spec/symbol.js deleted file mode 100644 index 39a48db..0000000 --- a/spec/spec/symbol.js +++ /dev/null @@ -1,16 +0,0 @@ -describe('Symbol', function() { - var symbol - - beforeEach(function() { - symbol = draw.symbol() - }) - - it('creates an instance of SVG.Symbol', function() { - expect(symbol instanceof SVG.Symbol).toBeTruthy() - }) - - it('creates symbol in defs', function() { - expect(symbol.parent instanceof SVG.Defs).toBeTruthy() - }) - -})
\ No newline at end of file diff --git a/spec/spec/text.js b/spec/spec/text.js index 9574b4c..9b968e5 100644..100755 --- a/spec/spec/text.js +++ b/spec/spec/text.js @@ -20,12 +20,12 @@ describe('Text', function() { it('sets the value of x with the first argument', function() { text.x(123) var box = text.bbox() - expect(approximately(box.x)).toBe(123) + expect(box.x).toBeCloseTo(123) }) it('sets the value of x based on the anchor with the first argument', function() { text.x(123, true) var box = text.bbox() - expect(approximately(box.x)).toBe(123) + expect(box.x).toBeCloseTo(123) }) }) @@ -50,17 +50,17 @@ describe('Text', function() { describe('cx()', function() { it('returns the value of cx without an argument', function() { var box = text.bbox() - expect(approximately(text.cx())).toBe(approximately(box.width / 2)) + expect(text.cx()).toBeCloseTo(box.width / 2) }) it('sets the value of cx with the first argument', function() { text.cx(123) var box = text.bbox() - expect(approximately(box.cx)).toBe(123) + expect(box.cx).toBeCloseTo(123) }) it('sets the value of cx based on the anchor with the first argument', function() { text.cx(123, true) var box = text.bbox() - expect(approximately(box.cx)).toBe(123) + expect(box.cx).toBeCloseTo(123) }) }) @@ -80,17 +80,17 @@ describe('Text', function() { it('sets the x and y position', function() { text.move(123,456) var box = text.bbox() - expect(approximately(box.x)).toBe(123) - expect(approximately(box.y)).toBe(456) + expect(box.x).toBeCloseTo(123) + expect(box.y).toBeCloseTo(456) }) }) describe('center()', function() { it('sets the cx and cy position', function() { - text.center(321,567) + text.center(321, 567) var box = text.bbox() - expect(approximately(box.cx)).toBe(321) - expect(approximately(box.cy)).toBe(567) + expect(box.cx).toBeCloseTo(321) + expect(box.cy).toBeCloseTo(567, 1) }) }) @@ -104,7 +104,7 @@ describe('Text', function() { describe('translate()', function() { it('sets the translation of an element', function() { text.transform({ x: 12, y: 12 }) - expect(text.node.getAttribute('transform')).toBe('translate(12 12)') + expect(text.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') }) }) @@ -123,11 +123,6 @@ describe('Text', function() { text.text('It is\nJUST\na bear!') expect(text.node.childNodes.length).toBe(3) }) - it('stores a reference to the tspan nodes in a set', function() { - text.text('It is\nJUST\na bear!') - expect(text.lines instanceof SVG.Set).toBe(true) - expect(text.lines.members.length).toBe(3) - }) it('stores the text value in the content reference', function() { text.text('It is\nJUST\na bear!') expect(text.content).toBe('It is\nJUST\na bear!') @@ -190,18 +185,27 @@ describe('Text', function() { text.clear() expect(text.node.childNodes.length).toBe(0) }) - it('initializes a new set for the lines reference', function() { - var lines = text.lines - text.clear() - expect(text.lines instanceof SVG.Set).toBe(true) - expect(text.lines).not.toBe(lines) - }) it('clears the stored content value', function() { text.text('Stored locally.') expect(text.content).toBe('Stored locally.') }) }) + describe('lines()', function() { + it('gets an array of individual lines as an instance of SVG.Set', function() { + var l1, l2, l3 + text.text(function(add) { + l1 = add.tspan('The first.') + l2 = add.tspan('The second.') + l3 = add.tspan('The third.') + }) + expect(text.lines().length()).toBe(3) + expect(text.lines().get(0)).toBe(l1) + expect(text.lines().get(1)).toBe(l2) + expect(text.lines().get(2)).toBe(l3) + }) + }) + describe('length()', function() { it('gets total length of text', function() { text.text(function(add) { @@ -209,7 +213,7 @@ describe('Text', function() { add.tspan('The second.') add.tspan('The third.') }) - expect(text.length()).toBe(text.lines.get(0).length() + text.lines.get(1).length() + text.lines.get(2).length()) + expect(text.length()).toBe(text.lines().get(0).length() + text.lines().get(1).length() + text.lines().get(2).length()) }) }) diff --git a/spec/spec/textpath.js b/spec/spec/textpath.js index 286b07e..362a854 100644..100755 --- a/spec/spec/textpath.js +++ b/spec/spec/textpath.js @@ -1,29 +1,35 @@ describe('TextPath', function() { - var text - , data = 'M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100' + var text + , data = 'M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100' - beforeEach(function() { - text = draw.text('We go up, then we go down, then up again') - }) + beforeEach(function() { + text = draw.text('We go up, then we go down, then up again') + }) - afterEach(function() { - draw.clear() - }) + afterEach(function() { + draw.clear() + }) - describe('path()', function() { - it('returns the text element', function() { - expect(text.path(data)).toBe(text) - }) - it('stores a reference to the path', function() { - expect(text.path(data).track instanceof SVG.Path).toBe(true) - }) - it('creates a textPath node in th text element', function() { - text.path(data) - expect(text.node.firstChild.nodeName).toBe('textPath') - }) - it('stores a reference to the textPath', function() { - expect(text.path(data).textPath instanceof SVG.TextPath).toBe(true) - }) - }) + describe('path()', function() { + it('returns the text element', function() { + expect(text.path(data)).toBe(text) + }) + it('creates a textPath node in the text element', function() { + text.path(data) + expect(text.node.firstChild.nodeName).toBe('textPath') + }) + }) + + describe('textPath()', function() { + it('creates a reference to the textPath', function() { + expect(text.path(data).textPath() instanceof SVG.TextPath).toBe(true) + }) + }) + + describe('track()', function() { + it('creates a reference to the path', function() { + expect(text.path(data).track() instanceof SVG.Path).toBe(true) + }) + }) })
\ No newline at end of file diff --git a/spec/spec/use.js b/spec/spec/use.js index f5ab3a8..f5ab3a8 100644..100755 --- a/spec/spec/use.js +++ b/spec/spec/use.js diff --git a/src/arrange.js b/src/arrange.js index a48ec5d..904381f 100644..100755 --- a/src/arrange.js +++ b/src/arrange.js @@ -4,11 +4,11 @@ SVG.extend(SVG.Element, { // Get all siblings, including myself siblings: function() { - return this.parent.children() + return this.parent().children() } // Get the curent position siblings , position: function() { - return this.parent.index(this) + return this.parent().index(this) } // Get the next element (will return null if there is none) , next: function() { @@ -20,26 +20,44 @@ SVG.extend(SVG.Element, { } // Send given element one step forward , forward: function() { - var i = this.position() - return this.parent.removeElement(this).put(this, i + 1) + var i = this.position() + 1 + , p = this.parent() + + // move node one step forward + p.removeElement(this).add(this, i) + + // make sure defs node is always at the top + if (p instanceof SVG.Doc) + p.node.appendChild(p.defs().node) + + return this } // Send given element one step backward , backward: function() { var i = this.position() if (i > 0) - this.parent.removeElement(this).add(this, i - 1) + this.parent().removeElement(this).add(this, i - 1) return this } // Send given element all the way to the front , front: function() { - return this.parent.removeElement(this).put(this) + var p = this.parent() + + // Move node forward + p.node.appendChild(this.node) + + // Make sure defs node is always at the top + if (p instanceof SVG.Doc) + p.node.appendChild(p.defs().node) + + return this } // Send given element all the way to the back , back: function() { if (this.position() > 0) - this.parent.removeElement(this).add(this, 0) + this.parent().removeElement(this).add(this, 0) return this } @@ -49,7 +67,7 @@ SVG.extend(SVG.Element, { var i = this.position() - this.parent.add(element, i) + this.parent().add(element, i) return this } @@ -59,7 +77,7 @@ SVG.extend(SVG.Element, { var i = this.position() - this.parent.add(element, i + 1) + this.parent().add(element, i + 1) return this } diff --git a/src/array.js b/src/array.js index bc79ceb..e4d2679 100644..100755 --- a/src/array.js +++ b/src/array.js @@ -25,7 +25,7 @@ SVG.extend(SVG.Array, { while(this.value.length < this.destination.length) this.value.push(lastValue) } - + return this } // Clean up any duplicate points @@ -77,5 +77,4 @@ SVG.extend(SVG.Array, { return this } -}) - +})
\ No newline at end of file diff --git a/src/attr.js b/src/attr.js new file mode 100644 index 0000000..2d57390 --- /dev/null +++ b/src/attr.js @@ -0,0 +1,83 @@ +SVG.extend(SVG.Element, { + // Set svg element attribute + attr: function(a, v, n) { + // act as full getter + if (a == null) { + // get an object of attributes + a = {} + v = this.node.attributes + for (n = v.length - 1; n >= 0; n--) + a[v[n].nodeName] = SVG.regex.isNumber.test(v[n].nodeValue) ? parseFloat(v[n].nodeValue) : v[n].nodeValue + + return a + + } else if (typeof a == 'object') { + // apply every attribute individually if an object is passed + for (v in a) this.attr(v, a[v]) + + } else if (v === null) { + // remove value + this.node.removeAttribute(a) + + } else if (v == null) { + // act as a getter if the first and only argument is not an object + v = this.node.getAttribute(a) + return v == null ? + SVG.defaults.attrs[a] : + SVG.regex.isNumber.test(v) ? + parseFloat(v) : v + + } else { + // bUG FIX: some browsers will render a stroke if a color is given even though stroke width is 0 + if (a == 'stroke-width') + this.attr('stroke', parseFloat(v) > 0 ? this._stroke : null) + else if (a == 'stroke') + this._stroke = v + + // convert image fill and stroke to patterns + if (a == 'fill' || a == 'stroke') { + if (SVG.regex.isImage.test(v)) + v = this.doc().defs().image(v, 0, 0) + + if (v instanceof SVG.Image) + v = this.doc().defs().pattern(0, 0, function() { + this.add(v) + }) + } + + // ensure correct numeric values (also accepts NaN and Infinity) + if (typeof v === 'number') + v = new SVG.Number(v) + + // ensure full hex color + else if (SVG.Color.isColor(v)) + v = new SVG.Color(v) + + // parse array values + else if (Array.isArray(v)) + v = new SVG.Array(v) + + // store parametric transformation values locally + else if (v instanceof SVG.Matrix && v.param) + this.param = v.param + + // if the passed attribute is leading... + if (a == 'leading') { + // ... call the leading method instead + if (this.leading) + this.leading(v) + } else { + // set given attribute on node + typeof n === 'string' ? + this.node.setAttributeNS(n, a, v.toString()) : + this.node.setAttribute(a, v.toString()) + } + + // rebuild if required + if (this.rebuild && (a == 'font-size' || a == 'x')) + this.rebuild(a, v) + } + + return this + } +})
\ No newline at end of file diff --git a/src/bare.js b/src/bare.js new file mode 100644 index 0000000..a0b1ee3 --- /dev/null +++ b/src/bare.js @@ -0,0 +1,45 @@ + +SVG.Bare = SVG.invent({ + // Initialize + create: function(element, inherit) { + // construct element + this.constructor.call(this, SVG.create(element)) + + // inherit custom methods + if (inherit) + for (var method in inherit.prototype) + if (typeof inherit.prototype[method] === 'function') + element[method] = inherit.prototype[method] + } + + // Inherit from +, inherit: SVG.Element + + // Add methods +, extend: { + // Insert some plain text + words: function(text) { + // remove contents + while (this.node.hasChildNodes()) + this.node.removeChild(this.node.lastChild) + + // create text node + this.node.appendChild(document.createTextNode(text)) + + return this + } + } +}) + + +SVG.extend(SVG.Parent, { + // Create an element that is not described by SVG.js + element: function(element, inherit) { + return this.put(new SVG.Bare(element, inherit)) + } + // Add symbol element +, symbol: function() { + return this.defs().element('symbol', SVG.Container) + } + +})
\ No newline at end of file diff --git a/src/bbox.js b/src/bbox.js deleted file mode 100644 index c68bec5..0000000 --- a/src/bbox.js +++ /dev/null @@ -1,58 +0,0 @@ - -SVG.BBox = function(element) { - var box - - /* initialize zero box */ - this.x = 0 - this.y = 0 - this.width = 0 - this.height = 0 - - /* get values if element is given */ - if (element) { - try { - /* actual, native bounding box */ - box = element.node.getBBox() - } catch(e) { - /* fallback for some browsers */ - box = { - x: element.node.clientLeft - , y: element.node.clientTop - , width: element.node.clientWidth - , height: element.node.clientHeight - } - } - - /* include translations on x an y */ - this.x = box.x + element.trans.x - this.y = box.y + element.trans.y - - /* plain width and height */ - this.width = box.width * element.trans.scaleX - this.height = box.height * element.trans.scaleY - } - - /* add center, right and bottom */ - boxProperties(this) - -} - -// -SVG.extend(SVG.BBox, { - // merge bounding box with another, return a new instance - merge: function(box) { - var b = new SVG.BBox() - - /* merge box */ - b.x = Math.min(this.x, box.x) - b.y = Math.min(this.y, box.y) - b.width = Math.max(this.x + this.width, box.x + box.width) - b.x - b.height = Math.max(this.y + this.height, box.y + box.height) - b.y - - /* add center, right and bottom */ - boxProperties(b) - - return b - } - -})
\ No newline at end of file diff --git a/src/boxes.js b/src/boxes.js new file mode 100755 index 0000000..9d214e3 --- /dev/null +++ b/src/boxes.js @@ -0,0 +1,159 @@ +SVG.BBox = SVG.invent({ + // Initialize + create: function(element) { + // get values if element is given + if (element) { + var box + + // yes this is ugly, but Firefox can be a bitch when it comes to elements that are not yet rendered + try { + // find native bbox + box = element.node.getBBox() + } catch(e) { + // mimic bbox + box = { + x: element.node.clientLeft + , y: element.node.clientTop + , width: element.node.clientWidth + , height: element.node.clientHeight + } + } + + // plain x and y + this.x = box.x + this.y = box.y + + // plain width and height + this.width = box.width + this.height = box.height + } + + // add center, right and bottom + fullBox(this) + } + + // Define Parent +, parent: SVG.Element + + // Constructor +, construct: { + // Get bounding box + bbox: function() { + return new SVG.BBox(this) + } + } + +}) + +SVG.TBox = SVG.invent({ + // Initialize + create: function(element) { + // get values if element is given + if (element) { + var t = element.ctm().extract() + , box = element.bbox() + + // width and height including transformations + this.width = box.width * t.scaleX + this.height = box.height * t.scaleY + + // x and y including transformations + this.x = box.x + t.x + this.y = box.y + t.y + } + + // add center, right and bottom + fullBox(this) + } + + // Define Parent +, parent: SVG.Element + + // Constructor +, construct: { + // Get transformed bounding box + tbox: function() { + return new SVG.TBox(this) + } + } + +}) + + +SVG.RBox = SVG.invent({ + // Initialize + create: function(element) { + if (element) { + var e = element.doc().parent() + , box = element.node.getBoundingClientRect() + , zoom = 1 + + // get screen offset + this.x = box.left + this.y = box.top + + // subtract parent offset + this.x -= e.offsetLeft + this.y -= e.offsetTop + + while (e = e.offsetParent) { + this.x -= e.offsetLeft + this.y -= e.offsetTop + } + + // calculate cumulative zoom from svg documents + e = element + while (e.parent && (e = e.parent())) { + if (e.viewbox) { + zoom *= e.viewbox().zoom + this.x -= e.x() || 0 + this.y -= e.y() || 0 + } + } + + // recalculate viewbox distortion + this.width = box.width /= zoom + this.height = box.height /= zoom + } + + // add center, right and bottom + fullBox(this) + + // offset by window scroll position, because getBoundingClientRect changes when window is scrolled + this.x += window.scrollX + this.y += window.scrollY + } + + // define Parent +, parent: SVG.Element + + // Constructor +, construct: { + // Get rect box + rbox: function() { + return new SVG.RBox(this) + } + } + +}) + +// Add universal merge method +;[SVG.BBox, SVG.TBox, SVG.RBox].forEach(function(c) { + + SVG.extend(c, { + // Merge rect box with another, return a new instance + merge: function(box) { + var b = new c() + + // merge boxes + b.x = Math.min(this.x, box.x) + b.y = Math.min(this.y, box.y) + b.width = Math.max(this.x + this.width, box.x + box.width) - b.x + b.height = Math.max(this.y + this.height, box.y + box.height) - b.y + + return fullBox(b) + } + + }) + +}) diff --git a/src/clip.js b/src/clip.js index e8a5e35..3fb5daa 100644..100755 --- a/src/clip.js +++ b/src/clip.js @@ -1,4 +1,4 @@ -SVG.Clip = SVG.invent({ +SVG.ClipPath = SVG.invent({ // Initialize node create: function() { this.constructor.call(this, SVG.create('clipPath')) @@ -21,7 +21,7 @@ SVG.Clip = SVG.invent({ delete this.targets /* remove clipPath from parent */ - this.parent.removeElement(this) + this.parent().removeElement(this) return this } @@ -31,7 +31,7 @@ SVG.Clip = SVG.invent({ , construct: { // Create clipping element clip: function() { - return this.defs().put(new SVG.Clip) + return this.defs().put(new SVG.ClipPath) } } }) @@ -41,7 +41,7 @@ SVG.extend(SVG.Element, { // Distribute clipPath to svg element clipWith: function(element) { /* use given clip or create a new one */ - this.clipper = element instanceof SVG.Clip ? element : this.parent.clip().add(element) + this.clipper = element instanceof SVG.ClipPath ? element : this.parent().clip().add(element) /* store reverence on self in mask */ this.clipper.targets.push(this) diff --git a/src/color.js b/src/color.js index 956693c..956693c 100644..100755 --- a/src/color.js +++ b/src/color.js diff --git a/src/container.js b/src/container.js index e63f2ae..e63f2ae 100644..100755 --- a/src/container.js +++ b/src/container.js diff --git a/src/data.js b/src/data.js index 1adbd52..1adbd52 100644..100755 --- a/src/data.js +++ b/src/data.js diff --git a/src/default.js b/src/default.js index b9819a7..eb3d504 100644..100755 --- a/src/default.js +++ b/src/default.js @@ -1,10 +1,7 @@ SVG.defaults = { - // Default matrix - matrix: '1 0 0 1 0 0' - // Default attribute values -, attrs: { + attrs: { /* fill and stroke */ 'fill-opacity': 1 , 'stroke-opacity': 1 @@ -36,29 +33,4 @@ SVG.defaults = { , 'text-anchor': 'start' } - // Default transformation values -, trans: function() { - return { - /* translate */ - x: 0 - , y: 0 - /* scale */ - , scaleX: 1 - , scaleY: 1 - /* rotate */ - , rotation: 0 - /* skew */ - , skewX: 0 - , skewY: 0 - /* matrix */ - , matrix: this.matrix - , a: 1 - , b: 0 - , c: 0 - , d: 1 - , e: 0 - , f: 0 - } - } - }
\ No newline at end of file diff --git a/src/defs.js b/src/defs.js index ad66cc5..ad66cc5 100644..100755 --- a/src/defs.js +++ b/src/defs.js diff --git a/src/doc.js b/src/doc.js index 820a8cd..6efb649 100644..100755 --- a/src/doc.js +++ b/src/doc.js @@ -1,32 +1,24 @@ SVG.Doc = SVG.invent({ // Initialize node create: function(element) { - /* ensure the presence of a html element */ - this.parent = typeof element == 'string' ? - document.getElementById(element) : - element - - /* If the target is an svg element, use that element as the main wrapper. - This allows svg.js to work with svg documents as well. */ - this.constructor - .call(this, this.parent.nodeName == 'svg' ? this.parent : SVG.create('svg')) - - /* set svg element attributes */ - this - .attr({ xmlns: SVG.ns, version: '1.1', width: '100%', height: '100%' }) - .attr('xmlns:xlink', SVG.xlink, SVG.xmlns) - - /* create the <defs> node */ - this._defs = new SVG.Defs - this._defs.parent = this - this.node.appendChild(this._defs.node) - - /* turn off sub pixel offset by default */ - this.doSpof = false - - /* ensure correct rendering */ - if (this.parent != this.node) - this.stage() + if (element) { + /* ensure the presence of a dom element */ + element = typeof element == 'string' ? + document.getElementById(element) : + element + + /* If the target is an svg element, use that element as the main wrapper. + This allows svg.js to work with svg documents as well. */ + if (element.nodeName == 'svg') { + this.constructor.call(this, element) + } else { + this.constructor.call(this, SVG.create('svg')) + element.appendChild(this.node) + } + + /* set svg element attributes and ensure defs node */ + this.namespace().size('100%', '100%').defs() + } } // Inherit from @@ -34,56 +26,50 @@ SVG.Doc = SVG.invent({ // Add class methods , extend: { - /* enable drawing */ - stage: function() { - var element = this - - /* insert element */ - this.parent.appendChild(this.node) - - /* fix sub-pixel offset */ - element.spof() - - /* make sure sub-pixel offset is fixed every time the window is resized */ - SVG.on(window, 'resize', function() { - element.spof() - }) - + // Add namespaces + namespace: function() { return this + .attr({ xmlns: SVG.ns, version: '1.1' }) + .attr('xmlns:xlink', SVG.xlink, SVG.xmlns) } - // Creates and returns defs element , defs: function() { + if (!this._defs) { + var defs + + // Find or create a defs element in this instance + if (defs = this.node.getElementsByTagName('defs')[0]) + this._defs = SVG.adopt(defs) + else + this._defs = new SVG.Defs + + // Make sure the defs node is at the end of the stack + this.node.appendChild(this._defs.node) + } + return this._defs } - + // custom parent method + , parent: function() { + return this.node.parentNode.nodeName == '#document' ? null : this.node.parentNode + } // Fix for possible sub-pixel offset. See: // https://bugzilla.mozilla.org/show_bug.cgi?id=608812 - , spof: function() { - if (this.doSpof) { - var pos = this.node.getScreenCTM() - - if (pos) - this - .style('left', (-pos.e % 1) + 'px') - .style('top', (-pos.f % 1) + 'px') - } + , spof: function(spof) { + var pos = this.node.getScreenCTM() - return this - } - - // Enable sub-pixel offset - , fixSubPixelOffset: function() { - this.doSpof = true + if (pos) + this + .style('left', (-pos.e % 1) + 'px') + .style('top', (-pos.f % 1) + 'px') return this } // Removes the doc from the DOM , remove: function() { - if(this.parent) { - this.parent.removeChild(this.node); - this.parent = null; + if(this.parent()) { + this.parent().removeChild(this.node); } return this; diff --git a/src/element.js b/src/element.js index 8c4166d..d64872a 100644..100755 --- a/src/element.js +++ b/src/element.js @@ -2,16 +2,16 @@ SVG.Element = SVG.invent({ // Initialize node create: function(node) { - /* make stroke value accessible dynamically */ + // make stroke value accessible dynamically this._stroke = SVG.defaults.attrs.stroke - /* initialize transformation store with defaults */ - this.trans = SVG.defaults.trans() - - /* create circular reference */ + // create circular reference if (this.node = node) { this.type = node.nodeName this.node.instance = this + + // store current attribute value + this._stroke = node.getAttribute('stroke') || this._stroke } } @@ -21,7 +21,7 @@ SVG.Element = SVG.invent({ x: function(x) { if (x != null) { x = new SVG.Number(x) - x.value /= this.trans.scaleX + x.value /= this.transform('scaleX') } return this.attr('x', x) } @@ -29,7 +29,7 @@ SVG.Element = SVG.invent({ , y: function(y) { if (y != null) { y = new SVG.Number(y) - y.value /= this.trans.scaleY + y.value /= this.transform('scaleY') } return this.attr('y', y) } @@ -67,41 +67,18 @@ SVG.Element = SVG.invent({ } // Clone element , clone: function() { - var clone , attr - , type = this.type - - /* invoke shape method with shape-specific arguments */ - clone = type == 'rect' || type == 'ellipse' ? - this.parent[type](0,0) : - type == 'line' ? - this.parent[type](0,0,0,0) : - type == 'image' ? - this.parent[type](this.src) : - type == 'text' ? - this.parent[type](this.content) : - type == 'path' ? - this.parent[type](this.attr('d')) : - type == 'polyline' || type == 'polygon' ? - this.parent[type](this.attr('points')) : - type == 'g' ? - this.parent.group() : - this.parent[type]() - - /* apply attributes attributes */ - attr = this.attr() - delete attr.id - clone.attr(attr) - - /* copy transformations */ - clone.trans = this.trans + // clone element and assign new id + var clone = assignNewId(this.node.cloneNode(true)) + + // insert the clone after myself + this.after(clone) - /* apply attributes and translations */ - return clone.transform({}) + return clone } // Remove element , remove: function() { - if (this.parent) - this.parent.removeElement(this) + if (this.parent()) + this.parent().removeElement(this) return this } @@ -119,206 +96,10 @@ SVG.Element = SVG.invent({ , putIn: function(parent) { return parent.add(this) } - // Get parent document - , doc: function(type) { - return this._parent(type || SVG.Doc) - } - // Set svg element attribute - , attr: function(a, v, n) { - if (a == null) { - /* get an object of attributes */ - a = {} - v = this.node.attributes - for (n = v.length - 1; n >= 0; n--) - a[v[n].nodeName] = SVG.regex.isNumber.test(v[n].nodeValue) ? parseFloat(v[n].nodeValue) : v[n].nodeValue - - return a - - } else if (typeof a == 'object') { - /* apply every attribute individually if an object is passed */ - for (v in a) this.attr(v, a[v]) - - } else if (v === null) { - /* remove value */ - this.node.removeAttribute(a) - - } else if (v == null) { - /* act as a getter if the first and only argument is not an object */ - v = this.node.attributes[a] - return v == null ? - SVG.defaults.attrs[a] : - SVG.regex.isNumber.test(v.nodeValue) ? - parseFloat(v.nodeValue) : v.nodeValue - - } else if (a == 'style') { - /* redirect to the style method */ - return this.style(v) - - } else { - /* BUG FIX: some browsers will render a stroke if a color is given even though stroke width is 0 */ - if (a == 'stroke-width') - this.attr('stroke', parseFloat(v) > 0 ? this._stroke : null) - else if (a == 'stroke') - this._stroke = v - - /* convert image fill and stroke to patterns */ - if (a == 'fill' || a == 'stroke') { - if (SVG.regex.isImage.test(v)) - v = this.doc().defs().image(v, 0, 0) - - if (v instanceof SVG.Image) - v = this.doc().defs().pattern(0, 0, function() { - this.add(v) - }) - } - - /* ensure correct numeric values (also accepts NaN and Infinity) */ - if (typeof v === 'number') - v = new SVG.Number(v) - - /* ensure full hex color */ - else if (SVG.Color.isColor(v)) - v = new SVG.Color(v) - - /* parse array values */ - else if (Array.isArray(v)) - v = new SVG.Array(v) - - /* if the passed attribute is leading... */ - if (a == 'leading') { - /* ... call the leading method instead */ - if (this.leading) - this.leading(v) - } else { - /* set given attribute on node */ - typeof n === 'string' ? - this.node.setAttributeNS(n, a, v.toString()) : - this.node.setAttribute(a, v.toString()) - } - - /* rebuild if required */ - if (this.rebuild && (a == 'font-size' || a == 'x')) - this.rebuild(a, v) - } - - return this - } - // Manage transformations - , transform: function(o, v) { - - if (arguments.length == 0) { - /* act as a getter if no argument is given */ - return this.trans - - } else if (typeof o === 'string') { - /* act as a getter if only one string argument is given */ - if (arguments.length < 2) - return this.trans[o] - - /* apply transformations as object if key value arguments are given*/ - var transform = {} - transform[o] = v - - return this.transform(transform) - } - - /* ... otherwise continue as a setter */ - var transform = [] - - /* parse matrix */ - o = parseMatrix(o) - - /* merge values */ - for (v in o) - if (o[v] != null) - this.trans[v] = o[v] - - /* compile matrix */ - this.trans.matrix = this.trans.a - + ' ' + this.trans.b - + ' ' + this.trans.c - + ' ' + this.trans.d - + ' ' + this.trans.e - + ' ' + this.trans.f - - /* alias current transformations */ - o = this.trans - - /* add matrix */ - if (o.matrix != SVG.defaults.matrix) - transform.push('matrix(' + o.matrix + ')') - - /* add rotation */ - if (o.rotation != 0) - transform.push('rotate(' + o.rotation + ' ' + (o.cx == null ? this.bbox().cx : o.cx) + ' ' + (o.cy == null ? this.bbox().cy : o.cy) + ')') - - /* add scale */ - if (o.scaleX != 1 || o.scaleY != 1) - transform.push('scale(' + o.scaleX + ' ' + o.scaleY + ')') - - /* add skew on x axis */ - if (o.skewX != 0) - transform.push('skewX(' + o.skewX + ')') - - /* add skew on y axis */ - if (o.skewY != 0) - transform.push('skewY(' + o.skewY + ')') - - /* add translation */ - if (o.x != 0 || o.y != 0) - transform.push('translate(' + new SVG.Number(o.x / o.scaleX) + ' ' + new SVG.Number(o.y / o.scaleY) + ')') - - /* update transformations, even if there are none */ - if (transform.length == 0) - this.node.removeAttribute('transform') - else - this.node.setAttribute('transform', transform.join(' ')) - - return this - } - // Dynamic style generator - , style: function(s, v) { - if (arguments.length == 0) { - /* get full style */ - return this.node.style.cssText || '' - - } else if (arguments.length < 2) { - /* apply every style individually if an object is passed */ - if (typeof s == 'object') { - for (v in s) this.style(v, s[v]) - - } else if (SVG.regex.isCss.test(s)) { - /* parse css string */ - s = s.split(';') - - /* apply every definition individually */ - for (var i = 0; i < s.length; i++) { - v = s[i].split(':') - this.style(v[0].replace(/\s+/g, ''), v[1]) - } - } else { - /* act as a getter if the first and only argument is not an object */ - return this.node.style[camelCase(s)] - } - - } else { - this.node.style[camelCase(s)] = v === null || SVG.regex.isBlank.test(v) ? '' : v - } - - return this - } // Get / set id , id: function(id) { return this.attr('id', id) } - // Get bounding box - , bbox: function() { - return new SVG.BBox(this) - } - // Get rect box - , rbox: function() { - return new SVG.RBox(this) - } // Checks whether the given point inside the bounding box of the element , inside: function(x, y) { var box = this.bbox() @@ -346,59 +127,91 @@ SVG.Element = SVG.invent({ } // Return array of classes on the node , classes: function() { - var classAttr = this.node.getAttribute('class') - if (classAttr === null) { - return [] - } else { - return classAttr.trim().split(/\s+/) - } + var attr = this.attr('class') + + return attr == null ? [] : attr.trim().split(/\s+/) } // Return true if class exists on the node, false otherwise - , hasClass: function(className) { - return this.classes().indexOf(className) != -1 + , hasClass: function(name) { + return this.classes().indexOf(name) != -1 } // Add class to the node - , addClass: function(className) { - var classArray - if (!(this.hasClass(className))) { - classArray = this.classes() - classArray.push(className) - this.node.setAttribute('class', classArray.join(' ')) + , addClass: function(name) { + if (!this.hasClass(name)) { + var array = this.classes() + array.push(name) + this.attr('class', array.join(' ')) } + return this } // Remove class from the node - , removeClass: function(className) { - var classArray - if (this.hasClass(className)) { - classArray = this.classes().filter(function(c) { - return c != className - }) - this.node.setAttribute('class', classArray.join(' ')) + , removeClass: function(name) { + if (this.hasClass(name)) { + this.attr('class', this.classes().filter(function(c) { + return c != name + }).join(' ')) } + return this } // Toggle the presence of a class on the node - , toggleClass: function(className) { - if (this.hasClass(className)) { - this.removeClass(className) - } else { - this.addClass(className) - } - return this + , toggleClass: function(name) { + return this.hasClass(name) ? this.removeClass(name) : this.addClass(name) } // Get referenced element form attribute value , reference: function(attr) { - return SVG.get(this.attr()[attr]) + return SVG.get(this.attr(attr)) } - // Private: find svg parent by instance - , _parent: function(parent) { - var element = this - - while (element != null && !(element instanceof parent)) - element = element.parent + // Returns the parent element instance + , parent: function(type) { + if (this.node.parentNode) { + // get parent element + var parent = SVG.adopt(this.node.parentNode) - return element + // if a specific type is given, find a parent with that class + if (type) + while (!(parent instanceof type) && parent.node.parentNode instanceof SVGElement) + parent = SVG.adopt(parent.node.parentNode) + + return parent + } + } + // Get parent document + , doc: function(type) { + return this.parent(type || SVG.Doc) + } + // Returns the svg node to call native svg methods on it + , native: function() { + return this.node + } + // Import raw svg + , svg: function(svg) { + // create temporary holder + var well = document.createElement('svg') + + // act as a setter if svg is given + if (svg && this instanceof SVG.Parent) { + // dump raw svg + well.innerHTML = '<svg>' + svg.replace(/\n/, '').replace(/<(\w+)([^<]+?)\/>/g, '<$1$2></$1>') + '</svg>' + + // transplant nodes + for (var i = 0, il = well.firstChild.childNodes.length; i < il; i++) + this.node.appendChild(well.firstChild.childNodes[i]) + + // otherwise act as a getter + } else { + // create a wrapping svg element in case of partial content + well.appendChild(svg = document.createElement('svg')) + + // insert a copy of this node + svg.appendChild(this.node.cloneNode(true)) + + // return target element + return well.innerHTML.replace(/^<svg>/, '').replace(/<\/svg>$/, '') + } + + return this } } }) diff --git a/src/ellipse.js b/src/ellipse.js index 0d23e6e..d64209a 100644..100755 --- a/src/ellipse.js +++ b/src/ellipse.js @@ -1,3 +1,30 @@ +SVG.Circle = SVG.invent({ + // Initialize node + create: 'circle' + + // Inherit from +, inherit: SVG.Shape + + // Add parent method +, construct: { + // Create circle element, based on ellipse + circle: function(size) { + return this.put(new SVG.Circle).rx(new SVG.Number(size).divide(2)).move(0, 0) + } + } +}) + +SVG.extend(SVG.Circle, SVG.FX, { + // Radius x value + rx: function(rx) { + return this.attr('r', rx) + } + // Alias radius x value +, ry: function(ry) { + return this.rx(ry) + } +}) + SVG.Ellipse = SVG.invent({ // Initialize node create: 'ellipse' @@ -5,55 +32,58 @@ SVG.Ellipse = SVG.invent({ // Inherit from , inherit: SVG.Shape - // Add class methods -, extend: { + // Add parent method +, construct: { + // Create an ellipse + ellipse: function(width, height) { + return this.put(new SVG.Ellipse).size(width, height).move(0, 0) + } + } +}) + +SVG.extend(SVG.Ellipse, SVG.Rect, SVG.FX, { + // Radius x value + rx: function(rx) { + return this.attr('rx', rx) + } + // Radius y value +, ry: function(ry) { + return this.attr('ry', ry) + } +}) + +// Add common method +SVG.extend(SVG.Circle, SVG.Ellipse, { // Move over x-axis x: function(x) { - return x == null ? this.cx() - this.attr('rx') : this.cx(x + this.attr('rx')) + return x == null ? this.cx() - this.rx() : this.cx(x + this.rx()) } // Move over y-axis , y: function(y) { - return y == null ? this.cy() - this.attr('ry') : this.cy(y + this.attr('ry')) + return y == null ? this.cy() - this.ry() : this.cy(y + this.ry()) } // Move by center over x-axis , cx: function(x) { - return x == null ? this.attr('cx') : this.attr('cx', new SVG.Number(x).divide(this.trans.scaleX)) + return x == null ? this.attr('cx') : this.attr('cx', new SVG.Number(x).divide(this.transform('scaleX'))) } // Move by center over y-axis , cy: function(y) { - return y == null ? this.attr('cy') : this.attr('cy', new SVG.Number(y).divide(this.trans.scaleY)) + return y == null ? this.attr('cy') : this.attr('cy', new SVG.Number(y).divide(this.transform('scaleY'))) } // Set width of element , width: function(width) { - return width == null ? this.attr('rx') * 2 : this.attr('rx', new SVG.Number(width).divide(2)) + return width == null ? this.rx() * 2 : this.rx(new SVG.Number(width).divide(2)) } // Set height of element , height: function(height) { - return height == null ? this.attr('ry') * 2 : this.attr('ry', new SVG.Number(height).divide(2)) + return height == null ? this.ry() * 2 : this.ry(new SVG.Number(height).divide(2)) } // Custom size function , size: function(width, height) { var p = proportionalSize(this.bbox(), width, height) - return this.attr({ - rx: new SVG.Number(p.width).divide(2) - , ry: new SVG.Number(p.height).divide(2) - }) - } - - } - - // Add parent method -, construct: { - // Create circle element, based on ellipse - circle: function(size) { - return this.ellipse(size, size) + return this + .rx(new SVG.Number(p.width).divide(2)) + .ry(new SVG.Number(p.height).divide(2)) } - // Create an ellipse - , ellipse: function(width, height) { - return this.put(new SVG.Ellipse).size(width, height).move(0, 0) - } - - } - })
\ No newline at end of file diff --git a/src/event.js b/src/event.js index 74f22eb..22a6117 100644..100755 --- a/src/event.js +++ b/src/event.js @@ -31,9 +31,6 @@ SVG.listeners = [] SVG.handlerMap = [] -// Only kept for consistency of API -SVG.registerEvent = function(){}; - // Add event binder in the SVG namespace SVG.on = function(node, event, listener) { // create listener, get object-index diff --git a/src/fx.js b/src/fx.js index 6dceb2a..402bdbd 100644..100755 --- a/src/fx.js +++ b/src/fx.js @@ -1,7 +1,7 @@ SVG.FX = SVG.invent({ // Initialize FX object create: function(element) { - /* store target element */ + // store target element this.target = element } @@ -9,78 +9,72 @@ SVG.FX = SVG.invent({ , extend: { // Add animation parameters and start animation animate: function(d, ease, delay) { - var akeys, tkeys, skeys, key + var akeys, skeys, key , element = this.target , fx = this - /* dissect object if one is passed */ + // dissect object if one is passed if (typeof d == 'object') { delay = d.delay ease = d.ease d = d.duration } - /* ensure default duration and easing */ + // ensure default duration and easing d = d == '=' ? d : d == null ? 1000 : new SVG.Number(d).valueOf() ease = ease || '<>' - /* process values */ - fx.to = function(pos) { + // process values + fx.at = function(pos) { var i - /* normalise pos */ + // normalise pos pos = pos < 0 ? 0 : pos > 1 ? 1 : pos - /* collect attribute keys */ + // 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._plot || akeys.indexOf('points') > -1)) { - /* get destination */ + // 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._plot || fx.attrs.points || element.array) + , p = new element.morphArray(fx.destination.plot || fx.attrs.points || element.array) - /* add size */ - if (fx._size) p.size(fx._size.width.to, fx._size.height.to) + // add size + if (fx.destination.size) + p.size(fx.destination.size.width.to, fx.destination.size.height.to) - /* add movement */ + // add movement box = p.bbox() - if (fx._x) p.move(fx._x.to, box.y) - else if (fx._cx) p.move(fx._cx.to - box.width / 2, box.y) + 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._y) p.move(box.x, fx._y.to) - else if (fx._cy) p.move(box.x, fx._cy.to - box.height / 2) - - /* delete element oriented changes */ - delete fx._x - delete fx._y - delete fx._cx - delete fx._cy - delete fx._size - - fx._plot = element.array.morph(p) + 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) + } } } - /* collect transformation keys */ - if (tkeys == null) { - tkeys = [] - for (key in fx.trans) - tkeys.push(key) - } - - /* collect style keys */ + // collect style keys if (skeys == null) { skeys = [] for (key in fx.styles) skeys.push(key) } - /* apply easing */ + // apply easing pos = ease == '<>' ? (-Math.cos(pos * Math.PI) / 2) + 0.5 : ease == '>' ? @@ -93,96 +87,106 @@ SVG.FX = SVG.invent({ ease(pos) : pos - /* run plot function */ - if (fx._plot) { - element.plot(fx._plot.at(pos)) + // run plot function + if (fx.destination.plot) { + element.plot(fx.destination.plot.at(pos)) } else { - /* run all x-position properties */ - if (fx._x) - element.x(fx._x.at(pos)) - else if (fx._cx) - element.cx(fx._cx.at(pos)) - - /* run all y-position properties */ - if (fx._y) - element.y(fx._y.at(pos)) - else if (fx._cy) - element.cy(fx._cy.at(pos)) - - /* run all size properties */ - if (fx._size) - element.size(fx._size.width.at(pos), fx._size.height.at(pos)) + // 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)) } - /* run all viewbox properties */ - if (fx._viewbox) + // run all viewbox properties + if (fx.destination.viewbox) element.viewbox( - fx._viewbox.x.at(pos) - , fx._viewbox.y.at(pos) - , fx._viewbox.width.at(pos) - , fx._viewbox.height.at(pos) + 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._leading) - element.leading(fx._leading.at(pos)) + // run leading property + if (fx.destination.leading) + element.leading(fx.destination.leading.at(pos)) - /* animate attributes */ + // animate attributes for (i = akeys.length - 1; i >= 0; i--) element.attr(akeys[i], at(fx.attrs[akeys[i]], pos)) - /* animate transformations */ - for (i = tkeys.length - 1; i >= 0; i--) - element.transform(tkeys[i], at(fx.trans[tkeys[i]], pos)) - - /* animate styles */ + // 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._during) - fx._during.call(element, pos, function(from, to) { + // 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 */ + // delay animation this.timeout = setTimeout(function() { var start = new Date().getTime() - /* initialize situation object */ - fx.situation = { - interval: 1000 / 60 - , start: start - , play: true - , finish: start + d - , duration: d - } + // 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 */ + // render function fx.render = function() { if (fx.situation.play === true) { - // This code was borrowed from the emile.js micro framework by Thomas Fuchs, aka MadRobby. + // 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.to(pos) + // process values + fx.at(pos) - /* finish off animation */ + // finish off animation if (time > fx.situation.finish) { - if (fx._plot) - element.plot(new SVG.PointArray(fx._plot.destination).settle()) - - if (fx._loop === true || (typeof fx._loop == 'number' && fx._loop > 1)) { - if (typeof fx._loop == 'number') - --fx._loop + 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._after ? fx._after.apply(element, [fx]) : fx.stop() + fx.situation.after ? fx.situation.after.apply(element, [fx]) : fx.stop() } } else { @@ -194,7 +198,7 @@ SVG.FX = SVG.invent({ } - /* start animation */ + // start animation fx.render() }, new SVG.Number(delay).valueOf()) @@ -208,48 +212,46 @@ SVG.FX = SVG.invent({ } // 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) - this.attrs[a] = SVG.Color.isColor(from) ? - new SVG.Color(from).morph(v) : - SVG.regex.unit.test(from) ? - new SVG.Number(from).morph(v) : - { from: from, to: v } - } - - return this - } - // Add animatable transformations - , transform: function(o, v) { - if (arguments.length == 1) { - /* parse matrix string */ - o = parseMatrix(o) - - /* dlete matrixstring from object */ - delete o.matrix - - /* add rotation-center to transformations */ - this.target.trans.cx = o.cx || null - this.target.trans.cy = o.cy || null - - delete o.cx - delete o.cy - - /* store matrix values */ - for (v in o) - this.trans[v] = { from: this.target.trans[v], to: o[v] } - - } else { - /* apply transformations as object if key value arguments are given*/ - var transform = {} - transform[o] = v - - this.transform(transform) + // 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] = this.target.ctm().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.unit.test(v) ? + // prepare number for morphing + new SVG.Number(from).morph(v) : + // prepare for plain morphing + { from: from, to: v } + } } return this @@ -267,25 +269,25 @@ SVG.FX = SVG.invent({ } // Animatable x-axis , x: function(x) { - this._x = new SVG.Number(this.target.x()).morph(x) + this.destination.x = new SVG.Number(this.target.x()).morph(x) return this } // Animatable y-axis , y: function(y) { - this._y = new SVG.Number(this.target.y()).morph(y) + this.destination.y = new SVG.Number(this.target.y()).morph(y) return this } // Animatable center x-axis , cx: function(x) { - this._cx = new SVG.Number(this.target.cx()).morph(x) + this.destination.cx = new SVG.Number(this.target.cx()).morph(x) return this } // Animatable center y-axis , cy: function(y) { - this._cy = new SVG.Number(this.target.cy()).morph(y) + this.destination.cy = new SVG.Number(this.target.cy()).morph(y) return this } @@ -300,14 +302,14 @@ SVG.FX = SVG.invent({ // Add animatable size , size: function(width, height) { if (this.target instanceof SVG.Text) { - /* animate font size for Text elements */ + // animate font size for Text elements this.attr('font-size', width) } else { - /* animate bbox based size for all other elements */ + // animate bbox based size for all other elements var box = this.target.bbox() - this._size = { + this.destination.size = { width: new SVG.Number(box.width).morph(width) , height: new SVG.Number(box.height).morph(height) } @@ -317,14 +319,14 @@ SVG.FX = SVG.invent({ } // Add animatable plot , plot: function(p) { - this._plot = p + this.destination.plot = p return this } // Add leading method , leading: function(value) { - if (this.target._leading) - this._leading = new SVG.Number(this.target._leading).morph(value) + if (this.target.destination.leading) + this.destination.leading = new SVG.Number(this.target.destination.leading).morph(value) return this } @@ -333,7 +335,7 @@ SVG.FX = SVG.invent({ if (this.target instanceof SVG.Container) { var box = this.target.viewbox() - this._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) @@ -355,55 +357,46 @@ SVG.FX = SVG.invent({ } // Add callback for each keyframe , during: function(during) { - this._during = during + this.situation.during = during return this } // Callback after animation , after: function(after) { - this._after = after + this.situation.after = after return this } // Make loopable - , loop: function(times) { - this._loop = times || true + , loop: function(times, reverse) { + // store current loop and total loops + this.situation.loop = this.situation.loops = times || true + + // make reversable + this.situation.reverse = !!reverse return this } // Stop running animation , stop: function(fulfill) { - /* fulfill animation */ + // fulfill animation if (fulfill === true) { this.animate(0) - if (this._after) - this._after.apply(this.target, [this]) + if (this.situation.after) + this.situation.after.apply(this.target, [this]) } else { - /* stop current animation */ + // stop current animation clearTimeout(this.timeout) cancelAnimationFrame(this.animationFrame); - /* reset storage for properties that need animation */ - this.attrs = {} - this.trans = {} - this.styles = {} - this.situation = {} - - /* delete destinations */ - delete this._x - delete this._y - delete this._cx - delete this._cy - delete this._size - delete this._plot - delete this._loop - delete this._after - delete this._during - delete this._leading - delete this._viewbox + // reset storage for properties + this.attrs = {} + this.styles = {} + this.situation = {} + this.destination = {} } return this diff --git a/src/gradient.js b/src/gradient.js index 56c1376..2c69fd1 100644..100755 --- a/src/gradient.js +++ b/src/gradient.js @@ -12,26 +12,8 @@ SVG.Gradient = SVG.invent({ // Add class methods , extend: { - // From position - from: function(x, y) { - return this.type == 'radial' ? - this.attr({ fx: new SVG.Number(x), fy: new SVG.Number(y) }) : - this.attr({ x1: new SVG.Number(x), y1: new SVG.Number(y) }) - } - // To position - , to: function(x, y) { - return this.type == 'radial' ? - this.attr({ cx: new SVG.Number(x), cy: new SVG.Number(y) }) : - this.attr({ x2: new SVG.Number(x), y2: new SVG.Number(y) }) - } - // Radius for radial gradient - , radius: function(r) { - return this.type == 'radial' ? - this.attr({ r: new SVG.Number(r) }) : - this - } // Add a color stop - , at: function(offset, color, opacity) { + at: function(offset, color, opacity) { return this.put(new SVG.Stop).update(offset, color, opacity) } // Update gradient @@ -64,6 +46,23 @@ SVG.Gradient = SVG.invent({ } }) +// Add animatable methods to both gradient and fx module +SVG.extend(SVG.Gradient, SVG.FX, { + // From position + from: function(x, y) { + return (this.target || this).type == 'radial' ? + this.attr({ fx: new SVG.Number(x), fy: new SVG.Number(y) }) : + this.attr({ x1: new SVG.Number(x), y1: new SVG.Number(y) }) + } + // To position +, to: function(x, y) { + return (this.target || this).type == 'radial' ? + this.attr({ cx: new SVG.Number(x), cy: new SVG.Number(y) }) : + this.attr({ x2: new SVG.Number(x), y2: new SVG.Number(y) }) + } +}) + +// Base gradient generation SVG.extend(SVG.Defs, { // define gradient gradient: function(type, block) { diff --git a/src/group.js b/src/group.js index cfb99ac..1b61371 100644..100755 --- a/src/group.js +++ b/src/group.js @@ -9,19 +9,19 @@ SVG.G = SVG.invent({ , extend: { // Move over x-axis x: function(x) { - return x == null ? this.trans.x : this.transform('x', x) + return x == null ? this.transform('x') : this.transform({ x: -this.x() + x }, true) } // Move over y-axis , y: function(y) { - return y == null ? this.trans.y : this.transform('y', y) + return y == null ? this.transform('y') : this.transform({ y: -this.y() + y }, true) } // Move by center over x-axis , cx: function(x) { - return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2) + return x == null ? this.tbox().cx : this.x(x - this.tbox().width / 2) } // Move by center over y-axis , cy: function(y) { - return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2) + return y == null ? this.tbox().cy : this.y(y - this.tbox().height / 2) } } diff --git a/src/helpers.js b/src/helpers.js index f3f8f26..7fe9f7a 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -5,6 +5,11 @@ function camelCase(s) { }) } +// Capitalize first letter of a string +function capitalize(s) { + return s.charAt(0).toUpperCase() + s.slice(1) +} + // Ensure to six-based hex function fullHex(hex) { return hex.length == 4 ? @@ -23,12 +28,10 @@ function compToHex(comp) { // Calculate proportional width and height values when necessary function proportionalSize(box, width, height) { - if (width == null || height == null) { - if (height == null) - height = box.height / box.width * width - else if (width == null) - width = box.width / box.height * height - } + if (height == null) + height = box.height / box.width * width + else if (width == null) + width = box.width / box.height * height return { width: width @@ -36,16 +39,59 @@ function proportionalSize(box, width, height) { } } +// Delta transform point +function deltaTransformPoint(matrix, x, y) { + return { + x: x * matrix.a + y * matrix.c + 0 + , y: x * matrix.b + y * matrix.d + 0 + } +} + +// Map matrix array to object +function arrayToMatrix(a) { + return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] } +} + +// Parse matrix if required +function parseMatrix(matrix) { + if (!(matrix instanceof SVG.Matrix)) + matrix = new SVG.Matrix(matrix) + + return matrix +} + +// Add centre point to transform object +function ensureCentre(o, target) { + o.cx = o.cx == null ? target.bbox().cx : o.cx + o.cy = o.cy == null ? target.bbox().cy : o.cy +} + +// Convert string to matrix +function stringToMatrix(source) { + // remove matrix wrapper and split to individual numbers + source = source + .replace(SVG.regex.whitespace, '') + .replace(SVG.regex.matrix, '') + .split(',') + + // convert string values to floats and convert to a matrix-formatted object + return arrayToMatrix( + SVG.utils.map(source, function(n) { + return parseFloat(n) + }) + ) +} + // Calculate position according to from and to function at(o, pos) { - /* number recalculation (don't bother converting to SVG.Number for performance reasons) */ + // number recalculation (don't bother converting to SVG.Number for performance reasons) return typeof o.from == 'number' ? o.from + (o.to - o.from) * pos : - /* instance recalculation */ - o instanceof SVG.Color || o instanceof SVG.Number ? o.at(pos) : + // instance recalculation + o instanceof SVG.Color || o instanceof SVG.Number || o instanceof SVG.Matrix ? o.at(pos) : - /* for all other values wait until pos has reached 1 to return the final value */ + // for all other values wait until pos has reached 1 to return the final value pos < 1 ? o.from : o.to } @@ -86,32 +132,33 @@ function arrayToString(a) { return s + ' ' } +// Deep new id assignment +function assignNewId(node) { + // do the same for SVG child nodes as well + for (var i = node.childNodes.length - 1; i >= 0; i--) + if (node.childNodes[i] instanceof SVGElement) + assignNewId(node.childNodes[i]) + + return SVG.adopt(node).id(SVG.eid(node.nodeName)) +} + // Add more bounding box properties -function boxProperties(b) { +function fullBox(b) { + if (b.x == null) { + b.x = 0 + b.y = 0 + b.width = 0 + b.height = 0 + } + + b.w = b.width + b.h = b.height b.x2 = b.x + b.width b.y2 = b.y + b.height b.cx = b.x + b.width / 2 b.cy = b.y + b.height / 2 -} -// Parse a matrix string -function parseMatrix(o) { - if (o.matrix) { - /* split matrix string */ - var m = o.matrix.replace(/\s/g, '').split(',') - - /* pasrse values */ - if (m.length == 6) { - o.a = parseFloat(m[0]) - o.b = parseFloat(m[1]) - o.c = parseFloat(m[2]) - o.d = parseFloat(m[3]) - o.e = parseFloat(m[4]) - o.f = parseFloat(m[5]) - } - } - - return o + return b } // Get id from reference string @@ -120,3 +167,6 @@ function idFromReference(url) { if (m) return m[1] } + +// Create matrix array for looping +var abcdef = 'abcdef'.split('')
\ No newline at end of file diff --git a/src/hyperlink.js b/src/hyperlink.js index 1981d52..a967707 100644..100755 --- a/src/hyperlink.js +++ b/src/hyperlink.js @@ -40,7 +40,7 @@ SVG.extend(SVG.Element, { else link.to(url) - return this.parent.put(link).put(this) + return this.parent().put(link).put(this) } })
\ No newline at end of file diff --git a/src/image.js b/src/image.js index 33f3979..ed44752 100644..100755 --- a/src/image.js +++ b/src/image.js @@ -14,19 +14,19 @@ SVG.Image = SVG.invent({ var self = this , img = document.createElement('img') - /* preload image */ + // preload image img.onload = function() { var p = self.doc(SVG.Pattern) - /* ensure image size */ + // ensure image size if (self.width() == 0 && self.height() == 0) self.size(img.width, img.height) - /* ensure pattern size if not set */ + // ensure pattern size if not set if (p && p.width() == 0 && p.height() == 0) p.size(self.width(), self.height()) - /* callback */ + // callback if (typeof self._loaded === 'function') self._loaded.call(self, { width: img.width @@ -38,7 +38,7 @@ SVG.Image = SVG.invent({ return this.attr('href', (img.src = this.src = url), SVG.xlink) } - // Add loade callback + // Add loaded callback , loaded: function(loaded) { this._loaded = loaded return this @@ -47,7 +47,7 @@ SVG.Image = SVG.invent({ // Add parent method , construct: { - // Create image element, load image and set its size + // create image element, load image and set its size image: function(source, width, height) { return this.put(new SVG.Image).load(source).size(width || 0, height || width || 0) } diff --git a/src/inventor.js b/src/inventor.js deleted file mode 100644 index f8244f5..0000000 --- a/src/inventor.js +++ /dev/null @@ -1,23 +0,0 @@ -// Invent new element -SVG.invent = function(config) { - /* create element initializer */ - var initializer = typeof config.create == 'function' ? - config.create : - function() { - this.constructor.call(this, SVG.create(config.create)) - } - - /* inherit prototype */ - if (config.inherit) - initializer.prototype = new config.inherit - - /* extend with methods */ - if (config.extend) - SVG.extend(initializer, config.extend) - - /* attach construct method to parent */ - if (config.construct) - SVG.extend(config.parent || SVG.Container, config.construct) - - return initializer -}
\ No newline at end of file diff --git a/src/line.js b/src/line.js index c11a734..0fcf63f 100644..100755 --- a/src/line.js +++ b/src/line.js @@ -7,60 +7,31 @@ SVG.Line = SVG.invent({ // Add class methods , extend: { - // Move over x-axis - x: function(x) { - var b = this.bbox() - - return x == null ? b.x : this.attr({ - x1: this.attr('x1') - b.x + x - , x2: this.attr('x2') - b.x + x - }) - } - // Move over y-axis - , y: function(y) { - var b = this.bbox() - - return y == null ? b.y : this.attr({ - y1: this.attr('y1') - b.y + y - , y2: this.attr('y2') - b.y + y - }) - } - // Move by center over x-axis - , cx: function(x) { - var half = this.bbox().width / 2 - return x == null ? this.x() + half : this.x(x - half) - } - // Move by center over y-axis - , cy: function(y) { - var half = this.bbox().height / 2 - return y == null ? this.y() + half : this.y(y - half) - } - // Set width of element - , width: function(width) { - var b = this.bbox() + // Get array + array: function() { + return new SVG.PointArray([ + [ this.attr('x1'), this.attr('y1') ] + , [ this.attr('x2'), this.attr('y2') ] + ]) + } + // Overwrite native plot() method + , plot: function(x1, y1, x2, y2) { + if (arguments.length == 4) + x1 = { x1: x1, y1: y1, x2: x2, y2: y2 } + else + x1 = new SVG.PointArray(x1).toLine() - return width == null ? b.width : this.attr(this.attr('x1') < this.attr('x2') ? 'x2' : 'x1', b.x + width) + return this.attr(x1) } - // Set height of element - , height: function(height) { - var b = this.bbox() - - return height == null ? b.height : this.attr(this.attr('y1') < this.attr('y2') ? 'y2' : 'y1', b.y + height) + // Move by left top corner + , move: function(x, y) { + return this.attr(this.array().move(x, y).toLine()) } - // Set line size by width and height + // Set element size to given width and height , size: function(width, height) { var p = proportionalSize(this.bbox(), width, height) - return this.width(p.width).height(p.height) - } - // Set path data - , plot: function(x1, y1, x2, y2) { - return this.attr({ - x1: x1 - , y1: y1 - , x2: x2 - , y2: y2 - }) + return this.attr(this.array().size(p.width, p.height).toLine()) } } @@ -68,7 +39,7 @@ SVG.Line = SVG.invent({ , construct: { // Create a line element line: function(x1, y1, x2, y2) { - return this.put(new SVG.Line().plot(x1, y1, x2, y2)) + return this.put(new SVG.Line).plot(x1, y1, x2, y2) } } }) diff --git a/src/mask.js b/src/mask.js index 84d75b8..73f29c2 100644..100755 --- a/src/mask.js +++ b/src/mask.js @@ -21,7 +21,7 @@ SVG.Mask = SVG.invent({ delete this.targets /* remove mask from parent */ - this.parent.removeElement(this) + this.parent().removeElement(this) return this } @@ -41,7 +41,7 @@ SVG.extend(SVG.Element, { // Distribute mask to svg element maskWith: function(element) { /* use given mask or create a new one */ - this.masker = element instanceof SVG.Mask ? element : this.parent.mask().add(element) + this.masker = element instanceof SVG.Mask ? element : this.parent().mask().add(element) /* store reverence on self in mask */ this.masker.targets.push(this) diff --git a/src/matrix.js b/src/matrix.js new file mode 100644 index 0000000..cbd053f --- /dev/null +++ b/src/matrix.js @@ -0,0 +1,168 @@ +SVG.Matrix = SVG.invent({ + // Initialize + create: function(source) { + var i, base = arrayToMatrix([1, 0, 0, 1, 0, 0]) + + // ensure source as object + source = source && source.node && source.node.getCTM ? + source.node.getCTM() : + typeof source === 'string' ? + stringToMatrix(source) : + arguments.length == 6 ? + arrayToMatrix([].slice.call(arguments)) : + typeof source === 'object' ? + source : base + + // merge source + for (i = abcdef.length - 1; i >= 0; i--) + this[abcdef[i]] = source && typeof source[abcdef[i]] === 'number' ? + source[abcdef[i]] : base[abcdef[i]] + } + + // Add methods +, extend: { + // Extract individual transformations + extract: function() { + // find delta transform points + var px = deltaTransformPoint(this, 0, 1) + , py = deltaTransformPoint(this, 1, 0) + , skewX = 180 / Math.PI * Math.atan2(px.y, px.x) - 90 + + return { + // translation + x: this.e + , y: this.f + // skew + , skewX: -skewX + , skewY: 180 / Math.PI * Math.atan2(py.y, py.x) + // scale + , scaleX: Math.sqrt(this.a * this.a + this.b * this.b) + , scaleY: Math.sqrt(this.c * this.c + this.d * this.d) + // rotation + , rotation: skewX + } + } + // Clone matrix + , clone: function() { + return new SVG.Matrix(this) + } + // Morph one matrix into another + , morph: function(matrix) { + // store new destination + this.destination = new SVG.Matrix(matrix) + + return this + } + // Get morphed matrix at a given position + , at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this + + // calculate morphed matrix at a given position + var matrix = new SVG.Matrix({ + a: this.a + (this.destination.a - this.a) * pos + , b: this.b + (this.destination.b - this.b) * pos + , c: this.c + (this.destination.c - this.c) * pos + , d: this.d + (this.destination.d - this.d) * pos + , e: this.e + (this.destination.e - this.e) * pos + , f: this.f + (this.destination.f - this.f) * pos + }) + + // process parametric rotation if present + if (this.param && this.param.to) { + // calculate current parametric position + var param = { + rotation: this.param.from.rotation + (this.param.to.rotation - this.param.from.rotation) * pos + , cx: this.param.from.cx + , cy: this.param.from.cy + } + + // rotate matrix + matrix = matrix.rotate( + (this.param.to.rotation - this.param.from.rotation * 2) * pos + , param.cx + , param.cy + ) + + // store current parametric values + matrix.param = param + } + + return matrix + } + // Multiplies by given matrix + , multiply: function(matrix) { + return new SVG.Matrix(this.native().multiply(parseMatrix(matrix).native())) + } + // Inverses matrix + , inverse: function() { + return new SVG.Matrix(this.native().inverse()) + } + // Translate matrix + , translate: function(x, y) { + return new SVG.Matrix(this.native().translate(x || 0, y || 0)) + } + // Scale matrix + , scale: function(x, y, cx, cy) { + // support universal scale + if (arguments.length == 1 || arguments.length == 3) + y = x + if (arguments.length == 3) { + cy = cx + cx = y + } + + return this.around(cx, cy, new SVG.Matrix(x, 0, 0, y, 0, 0)) + } + // Rotate matrix + , rotate: function(r, cx, cy) { + // convert degrees to radians + r = SVG.utils.radians(r) + + return this.around(cx, cy, new SVG.Matrix(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0)) + } + // Flip matrix on x or y, at a given offset + , flip: function(a, o) { + return a == 'x' ? this.scale(-1, 1, o, 0) : this.scale(1, -1, 0, o) + } + // Skew + , skew: function(x, y, cx, cy) { + return this.around(cx, cy, this.native().skewX(x || 0).skewY(y || 0)) + } + // Transform around a center point + , around: function(cx, cy, matrix) { + return this + .multiply(new SVG.Matrix(1, 0, 0, 1, cx || 0, cy || 0)) + .multiply(matrix) + .multiply(new SVG.Matrix(1, 0, 0, 1, -cx || 0, -cy || 0)) + } + // Convert to native SVGMatrix + , native: function() { + // create new matrix + var matrix = SVG.parser.draw.node.createSVGMatrix() + + // update with current values + for (var i = abcdef.length - 1; i >= 0; i--) + matrix[abcdef[i]] = this[abcdef[i]] + + return matrix + } + // Convert matrix to string + , toString: function() { + return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')' + } + } + + // Define parent +, parent: SVG.Element + + // Add parent method +, construct: { + // Get current matrix + ctm: function() { + return new SVG.Matrix(this) + } + + } + +})
\ No newline at end of file diff --git a/src/memory.js b/src/memory.js index 9b6f308..9b6f308 100644..100755 --- a/src/memory.js +++ b/src/memory.js diff --git a/src/nested.js b/src/nested.js index f856e52..f856e52 100644..100755 --- a/src/nested.js +++ b/src/nested.js diff --git a/src/number.js b/src/number.js index 92ff796..0ca8831 100644..100755 --- a/src/number.js +++ b/src/number.js @@ -1,101 +1,99 @@ // Module for unit convertions -SVG.Number = function(value) { +SVG.Number = SVG.invent({ + // Initialize + create: function(value, unit) { + // initialize defaults + this.value = 0 + this.unit = unit || '' - /* initialize defaults */ - this.value = 0 - this.unit = '' + // parse value + if (typeof value === 'number') { + // ensure a valid numeric value + this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value - /* parse value */ - if (typeof value === 'number') { - /* ensure a valid numeric value */ - this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value + } else if (typeof value === 'string') { + unit = value.match(SVG.regex.unit) - } else if (typeof value === 'string') { - var match = value.match(SVG.regex.unit) + if (unit) { + // make value numeric + this.value = parseFloat(unit[1]) + + // normalize + if (unit[2] == '%') + this.value /= 100 + else if (unit[2] == 's') + this.value *= 1000 + + // store unit + this.unit = unit[2] + } - if (match) { - /* make value numeric */ - this.value = parseFloat(match[1]) - - /* normalize percent value */ - if (match[2] == '%') - this.value /= 100 - else if (match[2] == 's') - this.value *= 1000 - - /* store unit */ - this.unit = match[2] + } else { + if (value instanceof SVG.Number) { + this.value = value.valueOf() + this.unit = value.unit + } } - } else { - if (value instanceof SVG.Number) { - this.value = value.value - this.unit = value.unit - } - } - -} - -SVG.extend(SVG.Number, { - // Stringalize - toString: function() { - return ( - this.unit == '%' ? - ~~(this.value * 1e8) / 1e6: - this.unit == 's' ? - this.value / 1e3 : - this.value - ) + this.unit - } -, // Convert to primitive - valueOf: function() { - return this.value - } - // Add number -, plus: function(number) { - this.value = this + new SVG.Number(number) - - return this - } - // Subtract number -, minus: function(number) { - return this.plus(-new SVG.Number(number)) - } - // Multiply number -, times: function(number) { - this.value = this * new SVG.Number(number) - - return this } - // Divide number -, divide: function(number) { - this.value = this / new SVG.Number(number) + // Add methods +, extend: { + // Stringalize + toString: function() { + return ( + this.unit == '%' ? + ~~(this.value * 1e8) / 1e6: + this.unit == 's' ? + this.value / 1e3 : + this.value + ) + this.unit + } + , // Convert to primitive + valueOf: function() { + return this.value + } + // Add number + , plus: function(number) { + return new SVG.Number(this + new SVG.Number(number), this.unit) + } + // Subtract number + , minus: function(number) { + return this.plus(-new SVG.Number(number)) + } + // Multiply number + , times: function(number) { + return new SVG.Number(this * new SVG.Number(number), this.unit) + } + // Divide number + , divide: function(number) { + return new SVG.Number(this / new SVG.Number(number), this.unit) + } + // Convert to different unit + , to: function(unit) { + var number = new SVG.Number(this) + + if (typeof unit === 'string') + number.unit = unit - return this - } - // Convert to different unit -, to: function(unit) { - if (typeof unit === 'string') - this.unit = unit + return number + } + // Make number morphable + , morph: function(number) { + this.destination = new SVG.Number(number) - return this - } - // Make number morphable -, morph: function(number) { - this.destination = new SVG.Number(number) + return this + } + // Get morphed number at given position + , at: function(pos) { + // Make sure a destination is defined + if (!this.destination) return this - return this - } - // Get morphed number at given position -, at: function(pos) { - /* make sure a destination is defined */ - if (!this.destination) return this + // Generate new morphed number + return new SVG.Number(this.destination) + .minus(this) + .times(pos) + .plus(this) + } - /* generate new morphed number */ - return new SVG.Number(this.destination) - .minus(this) - .times(pos) - .plus(this) } - })
\ No newline at end of file diff --git a/src/parent.js b/src/parent.js index 44514c2..c4476c6 100644..100755 --- a/src/parent.js +++ b/src/parent.js @@ -11,30 +11,20 @@ SVG.Parent = SVG.invent({ , extend: { // Returns all child elements children: function() { - return this._children || (this._children = []) + return SVG.utils.map(this.node.childNodes, function(node) { + return SVG.adopt(node) + }) } // Add given element at a position , add: function(element, i) { if (!this.has(element)) { - /* define insertion index if none given */ + // define insertion index if none given i = i == null ? this.children().length : i - /* remove references from previous parent */ - if (element.parent) - element.parent.children().splice(element.parent.index(element), 1) - - /* add element references */ - this.children().splice(i, 0, element) + // add element references this.node.insertBefore(element.node, this.node.childNodes[i] || null) - element.parent = this } - /* reposition defs */ - if (this._defs) { - this.node.removeChild(this._defs.node) - this.node.appendChild(this._defs.node) - } - return this } // Basically does the same as `add()` but returns the added element instead @@ -77,27 +67,24 @@ SVG.Parent = SVG.invent({ return this } - // Remove a child element at a position + // Remove a given child , removeElement: function(element) { - this.children().splice(this.index(element), 1) this.node.removeChild(element.node) - element.parent = null return this } // Remove all elements in this container , clear: function() { - /* remove children */ - for (var i = this.children().length - 1; i >= 0; i--) - this.removeElement(this.children()[i]) - - /* remove defs node */ - if (this._defs) - this._defs.clear() + // remove children + while(this.node.hasChildNodes()) + this.node.removeChild(this.node.lastChild) + + // remove defs reference + delete this._defs return this } - , // Get defs + , // Get defs defs: function() { return this.doc().defs() } diff --git a/src/path.js b/src/path.js index 773b3d8..8282ce7 100644..100755 --- a/src/path.js +++ b/src/path.js @@ -7,13 +7,19 @@ SVG.Path = SVG.invent({ // Add class methods , extend: { + // Define morphable array + morphArray: SVG.PathArray + // Get array + , array: function() { + return this._array || (this._array = new SVG.PathArray(this.attr('d'))) + } // Plot new poly points - plot: function(p) { - return this.attr('d', (this.array = new SVG.PathArray(p, [['M', 0, 0]]))) + , plot: function(p) { + return this.attr('d', (this._array = new SVG.PathArray(p))) } // Move by left top corner , move: function(x, y) { - return this.attr('d', this.array.move(x, y)) + return this.attr('d', this.array().move(x, y)) } // Move by left top corner over x-axis , x: function(x) { @@ -27,7 +33,7 @@ SVG.Path = SVG.invent({ , size: function(width, height) { var p = proportionalSize(this.bbox(), width, height) - return this.attr('d', this.array.size(p.width, p.height)) + return this.attr('d', this.array().size(p.width, p.height)) } // Set width of element , width: function(width) { diff --git a/src/patharray.js b/src/patharray.js index 1a2fdd5..e127576 100644..100755 --- a/src/patharray.js +++ b/src/patharray.js @@ -1,6 +1,6 @@ // Path points array SVG.PathArray = function(array, fallback) { - this.constructor.call(this, array, fallback) + this.constructor.call(this, array, fallback || [['M', 0, 0]]) } // Inherit from SVG.Array @@ -186,7 +186,7 @@ SVG.extend(SVG.PathArray, { else if (s == 'Q') x.push(seg.x1, seg.y1, seg.x, seg.y) else if (s == 'A') - x.push(seg.r1, seg.r2, seg.angle, seg.largeArcFlag|0, seg.sweepFlag|0, seg.x, seg.y) + x.push(seg.r1, seg.r2, seg.angle, seg.largeArcFlag | 0, seg.sweepFlag | 0, seg.x, seg.y) /* store segment */ array.push(x) diff --git a/src/pattern.js b/src/pattern.js index 76190ef..76190ef 100644..100755 --- a/src/pattern.js +++ b/src/pattern.js diff --git a/src/pointarray.js b/src/pointarray.js index e246adf..f43234c 100644..100755 --- a/src/pointarray.js +++ b/src/pointarray.js @@ -1,6 +1,6 @@ // Poly points array -SVG.PointArray = function() { - this.constructor.apply(this, arguments) +SVG.PointArray = function(array, fallback) { + this.constructor.call(this, array, fallback || [[0,0]]) } // Inherit from SVG.Array @@ -15,6 +15,15 @@ SVG.extend(SVG.PointArray, { return array.join(' ') } + // Convert array to line object +, toLine: function() { + return { + x1: this.value[0][0] + , y1: this.value[0][1] + , x2: this.value[1][0] + , y2: this.value[1][1] + } + } // Get morphed array at given position , at: function(pos) { /* make sure a destination is defined */ diff --git a/src/pointed.js b/src/pointed.js new file mode 100644 index 0000000..02ff44e --- /dev/null +++ b/src/pointed.js @@ -0,0 +1,25 @@ +// unify all point to point elements +SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, { + // Define morphable array + morphArray: SVG.PointArray + // Move by left top corner over x-axis +, x: function(x) { + return x == null ? this.bbox().x : this.move(x, this.bbox().y) + } + // Move by left top corner over y-axis +, y: function(y) { + return y == null ? this.bbox().y : this.move(this.bbox().x, y) + } + // Set width of element +, width: function(width) { + var b = this.bbox() + + return width == null ? b.width : this.size(width, b.height) + } + // Set height of element +, height: function(height) { + var b = this.bbox() + + return height == null ? b.height : this.size(b.width, height) + } +})
\ No newline at end of file diff --git a/src/poly.js b/src/poly.js index 1586c77..0f3b1e6 100644..100755 --- a/src/poly.js +++ b/src/poly.js @@ -32,41 +32,23 @@ SVG.Polygon = SVG.invent({ // Add polygon-specific functions SVG.extend(SVG.Polyline, SVG.Polygon, { - // Define morphable array - morphArray: SVG.PointArray + // Get array + array: function() { + return this._array || (this._array = new SVG.PointArray(this.attr('points'))) + } // Plot new path , plot: function(p) { - return this.attr('points', (this.array = new SVG.PointArray(p, [[0,0]]))) + return this.attr('points', (this._array = new SVG.PointArray(p))) } // Move by left top corner , move: function(x, y) { - return this.attr('points', this.array.move(x, y)) - } - // Move by left top corner over x-axis -, x: function(x) { - return x == null ? this.bbox().x : this.move(x, this.bbox().y) - } - // Move by left top corner over y-axis -, y: function(y) { - return y == null ? this.bbox().y : this.move(this.bbox().x, y) - } - // Set width of element -, width: function(width) { - var b = this.bbox() - - return width == null ? b.width : this.size(width, b.height) - } - // Set height of element -, height: function(height) { - var b = this.bbox() - - return height == null ? b.height : this.size(b.width, height) + return this.attr('points', this.array().move(x, y)) } // Set element size to given width and height , size: function(width, height) { var p = proportionalSize(this.bbox(), width, height) - return this.attr('points', this.array.size(p.width, p.height)) + return this.attr('points', this.array().size(p.width, p.height)) } })
\ No newline at end of file diff --git a/src/rbox.js b/src/rbox.js deleted file mode 100644 index 2fc093b..0000000 --- a/src/rbox.js +++ /dev/null @@ -1,76 +0,0 @@ -// Get the rectangular box of a given element -SVG.RBox = function(element) { - var e, zoom - , box = {} - - /* initialize zero box */ - this.x = 0 - this.y = 0 - this.width = 0 - this.height = 0 - - if (element) { - e = element.doc().parent - zoom = element.doc().viewbox().zoom - - /* actual, native bounding box */ - box = element.node.getBoundingClientRect() - - /* get screen offset */ - this.x = box.left - this.y = box.top - - /* subtract parent offset */ - this.x -= e.offsetLeft - this.y -= e.offsetTop - - while (e = e.offsetParent) { - this.x -= e.offsetLeft - this.y -= e.offsetTop - } - - /* calculate cumulative zoom from svg documents */ - e = element - while (e = e.parent) { - if (e.type == 'svg' && e.viewbox) { - zoom *= e.viewbox().zoom - this.x -= e.x() || 0 - this.y -= e.y() || 0 - } - } - } - - /* recalculate viewbox distortion */ - this.x /= zoom - this.y /= zoom - this.width = box.width /= zoom - this.height = box.height /= zoom - - /* offset by window scroll position, because getBoundingClientRect changes when window is scrolled */ - this.x += typeof window.scrollX === 'number' ? window.scrollX : window.pageXOffset - this.y += typeof window.scrollY === 'number' ? window.scrollY : window.pageYOffset - - /* add center, right and bottom */ - boxProperties(this) - -} - -// -SVG.extend(SVG.RBox, { - // merge rect box with another, return a new instance - merge: function(box) { - var b = new SVG.RBox() - - /* merge box */ - b.x = Math.min(this.x, box.x) - b.y = Math.min(this.y, box.y) - b.width = Math.max(this.x + this.width, box.x + box.width) - b.x - b.height = Math.max(this.y + this.height, box.y + box.height) - b.y - - /* add center, right and bottom */ - boxProperties(b) - - return b - } - -}) diff --git a/src/rect.js b/src/rect.js index 313c96d..8f90e90 100644..100755 --- a/src/rect.js +++ b/src/rect.js @@ -1,17 +1,15 @@ SVG.Rect = SVG.invent({ - // Initialize node + // Initialize node create: 'rect' - // Inherit from + // Inherit from , inherit: SVG.Shape - - // Add parent method + + // Add parent method , construct: { - // Create a rect element - rect: function(width, height) { - return this.put(new SVG.Rect().size(width, height)) - } - - } - + // Create a rect element + rect: function(width, height) { + return this.put(new SVG.Rect().size(width, height)) + } + } })
\ No newline at end of file diff --git a/src/regex.js b/src/regex.js index bd59e9c..d08dc7b 100644..100755 --- a/src/regex.js +++ b/src/regex.js @@ -1,39 +1,42 @@ // Storage for regular expressions SVG.regex = { - /* parse unit value */ - unit: /^(-?[\d\.]+)([a-z%]{0,2})$/ + // Parse unit value + unit: /^(-?[\d\.]+)([a-z%]{0,2})$/ - /* parse hex value */ -, hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i + // Parse hex value +, hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i - /* parse rgb value */ -, rgb: /rgb\((\d+),(\d+),(\d+)\)/ + // Parse rgb value +, rgb: /rgb\((\d+),(\d+),(\d+)\)/ - /* parse reference id */ -, reference: /#([a-z0-9\-_]+)/i + // Parse reference id +, reference: /#([a-z0-9\-_]+)/i + + // Parse matrix wrapper +, matrix: /matrix\(|\)/g + + // Whitespace +, whitespace: /\s/g - /* test hex value */ -, isHex: /^#[a-f0-9]{3,6}$/i + // Test hex value +, isHex: /^#[a-f0-9]{3,6}$/i - /* test rgb value */ -, isRgb: /^rgb\(/ + // Test rgb value +, isRgb: /^rgb\(/ - /* test css declaration */ -, isCss: /[^:]+:[^;]+;?/ + // Test css declaration +, isCss: /[^:]+:[^;]+;?/ - /* test for blank string */ -, isBlank: /^(\s+)?$/ + // Test for blank string +, isBlank: /^(\s+)?$/ - /* test for numeric string */ -, isNumber: /^-?[\d\.]+$/ + // Test for numeric string +, isNumber: /^-?[\d\.]+$/ - /* test for percent value */ -, isPercent: /^-?[\d\.]+%$/ + // Test for percent value +, isPercent: /^-?[\d\.]+%$/ - /* test for image url */ -, isImage: /\.(jpg|jpeg|png|gif)(\?[^=]+.*)?/i - - /* test for namespaced event */ -, isEvent: /^[\w]+.[\w]+$/ + // Test for image url +, isImage: /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i }
\ No newline at end of file diff --git a/src/relative.js b/src/relative.js deleted file mode 100644 index 6bb11b7..0000000 --- a/src/relative.js +++ /dev/null @@ -1,16 +0,0 @@ -// -SVG.extend(SVG.Element, SVG.FX, { - // Relative move over x axis - dx: function(x) { - return this.x((this.target || this).x() + x) - } - // Relative move over y axis -, dy: function(y) { - return this.y((this.target || this).y() + y) - } - // Relative move over x and y axes -, dmove: function(x, y) { - return this.dx(x).dy(y) - } - -})
\ No newline at end of file diff --git a/src/selector.js b/src/selector.js index 905e9f9..ec6298a 100644 --- a/src/selector.js +++ b/src/selector.js @@ -1,5 +1,22 @@ // Method for getting an element by id SVG.get = function(id) { var node = document.getElementById(idFromReference(id) || id) - if (node) return node.instance -}
\ No newline at end of file + if (node) return SVG.adopt(node) +} + +// Select elements by query string +SVG.select = function(query, parent) { + return new SVG.Set( + SVG.utils.map((parent || document).querySelectorAll(query), function(node) { + return SVG.adopt(node) + }) + ) +} + +SVG.extend(SVG.Parent, { + // Scoped select method + select: function(query) { + return SVG.select(query, this.node) + } + +})
\ No newline at end of file diff --git a/src/set.js b/src/set.js index 37246fe..41289dd 100644..100755 --- a/src/set.js +++ b/src/set.js @@ -1,8 +1,8 @@ SVG.Set = SVG.invent({ // Initialize - create: function() { - /* set initial state */ - this.clear() + create: function(members) { + // Set initial state + Array.isArray(members) ? this.members = members : this.clear() } // Add class methods @@ -20,7 +20,7 @@ SVG.Set = SVG.invent({ , remove: function(element) { var i = this.index(element) - /* remove given child */ + // remove given child if (i > -1) this.members.splice(i, 1) @@ -35,11 +35,15 @@ SVG.Set = SVG.invent({ } // Restore to defaults , clear: function() { - /* initialize store */ + // initialize store this.members = [] return this } + // Get the length of a set + , length: function() { + return this.members.length + } // Checks if a given element is present in set , has: function(element) { return this.index(element) >= 0 @@ -68,11 +72,11 @@ SVG.Set = SVG.invent({ , bbox: function(){ var box = new SVG.BBox() - /* return an empty box of there are no members */ + // return an empty box of there are no members if (this.members.length == 0) return box - /* get the first rbox and update the target bbox */ + // get the first rbox and update the target bbox var rbox = this.members[0].rbox() box.x = rbox.x box.y = rbox.y @@ -80,7 +84,7 @@ SVG.Set = SVG.invent({ box.height = rbox.height this.each(function() { - /* user rbox for correct position and visual representation */ + // user rbox for correct position and visual representation box = box.merge(this.rbox()) }) @@ -91,16 +95,16 @@ SVG.Set = SVG.invent({ // Add parent method , construct: { // Create a new set - set: function() { - return new SVG.Set + set: function(members) { + return new SVG.Set(members) } } }) -SVG.SetFX = SVG.invent({ +SVG.FX.Set = SVG.invent({ // Initialize node create: function(set) { - /* store reference to set */ + // store reference to set this.set = set } @@ -111,33 +115,33 @@ SVG.Set.inherit = function() { var m , methods = [] - /* gather shape methods */ + // gather shape methods for(var m in SVG.Shape.prototype) if (typeof SVG.Shape.prototype[m] == 'function' && typeof SVG.Set.prototype[m] != 'function') methods.push(m) - /* apply shape aliasses */ + // apply shape aliasses methods.forEach(function(method) { SVG.Set.prototype[method] = function() { for (var i = 0, il = this.members.length; i < il; i++) if (this.members[i] && typeof this.members[i][method] == 'function') this.members[i][method].apply(this.members[i], arguments) - return method == 'animate' ? (this.fx || (this.fx = new SVG.SetFX(this))) : this + return method == 'animate' ? (this.fx || (this.fx = new SVG.FX.Set(this))) : this } }) - /* clear methods for the next round */ + // clear methods for the next round methods = [] - /* gather fx methods */ + // gather fx methods for(var m in SVG.FX.prototype) - if (typeof SVG.FX.prototype[m] == 'function' && typeof SVG.SetFX.prototype[m] != 'function') + if (typeof SVG.FX.prototype[m] == 'function' && typeof SVG.FX.Set.prototype[m] != 'function') methods.push(m) - /* apply fx aliasses */ + // apply fx aliasses methods.forEach(function(method) { - SVG.SetFX.prototype[method] = function() { + SVG.FX.Set.prototype[method] = function() { for (var i = 0, il = this.set.members.length; i < il; i++) this.set.members[i].fx[method].apply(this.set.members[i].fx, arguments) diff --git a/src/shape.js b/src/shape.js index 15c1fa9..15c1fa9 100644..100755 --- a/src/shape.js +++ b/src/shape.js diff --git a/src/style.js b/src/style.js new file mode 100644 index 0000000..a7f737d --- /dev/null +++ b/src/style.js @@ -0,0 +1,33 @@ +SVG.extend(SVG.Element, { + // Dynamic style generator + style: function(s, v) { + if (arguments.length == 0) { + /* get full style */ + return this.node.style.cssText || '' + + } else if (arguments.length < 2) { + /* apply every style individually if an object is passed */ + if (typeof s == 'object') { + for (v in s) this.style(v, s[v]) + + } else if (SVG.regex.isCss.test(s)) { + /* parse css string */ + s = s.split(';') + + /* apply every definition individually */ + for (var i = 0; i < s.length; i++) { + v = s[i].split(':') + this.style(v[0].replace(/\s+/g, ''), v[1]) + } + } else { + /* act as a getter if the first and only argument is not an object */ + return this.node.style[camelCase(s)] + } + + } else { + this.node.style[camelCase(s)] = v === null || SVG.regex.isBlank.test(v) ? '' : v + } + + return this + } +})
\ No newline at end of file diff --git a/src/sugar.js b/src/sugar.js index 4a8fff8..4b7b232 100644..100755 --- a/src/sugar.js +++ b/src/sugar.js @@ -29,52 +29,57 @@ var sugar = { }) SVG.extend(SVG.Element, SVG.FX, { - // Rotation - rotate: function(deg, x, y) { - return this.transform({ - rotation: deg || 0 - , cx: x - , cy: y - }) - } - // Skew -, skew: function(x, y) { - return this.transform({ - skewX: x || 0 - , skewY: y || 0 - }) - } - // Scale -, scale: function(x, y) { - return this.transform({ - scaleX: x - , scaleY: y == null ? x : y - }) - } - // Translate + // Map rotation to transform + rotate: function(d, cx, cy) { + return this.transform({ rotation: d, cx: cx, cy: cy }) + } + // Map skew to transform +, skew: function(x, y, cx, cy) { + return this.transform({ skewX: x, skewY: y, cx: cx, cy: cy }) + } + // Map scale to transform +, scale: function(x, y, cx, cy) { + return arguments.length == 1 || arguments.length == 3 ? + this.transform({ scale: x, cx: y, cy: cx }) : + this.transform({ scaleX: x, scaleY: y, cx: cx, cy: cy }) + } + // Map translate to transform , translate: function(x, y) { - return this.transform({ - x: x - , y: y - }) + return this.transform({ x: x, y: y }) } - // Matrix + // Map flip to transform +, flip: function(a, o) { + return this.transform({ flip: a, offset: o }) + } + // Map matrix to transform , matrix: function(m) { - return this.transform({ matrix: m }) + return this.attr('transform', new SVG.Matrix(m)) } // Opacity , opacity: function(value) { return this.attr('opacity', value) } - + // Relative move over x axis +, dx: function(x) { + return this.x((this.target || this).x() + x) + } + // Relative move over y axis +, dy: function(y) { + return this.y((this.target || this).y() + y) + } + // Relative move over x and y axes +, dmove: function(x, y) { + return this.dx(x).dy(y) + } }) -SVG.extend(SVG.Rect, SVG.Ellipse, SVG.FX, { +SVG.extend(SVG.Rect, SVG.Ellipse, SVG.Circle, SVG.Gradient, SVG.FX, { // Add x and y radius radius: function(x, y) { - return this.attr({ rx: x, ry: y || x }) + return (this.target || this).type == 'radial' ? + this.attr({ r: new SVG.Number(x) }) : + this.rx(x).ry(y == null ? x : y) } - }) SVG.extend(SVG.Path, { @@ -86,7 +91,6 @@ SVG.extend(SVG.Path, { , pointAt: function(length) { return this.node.getPointAtLength(length) } - }) SVG.extend(SVG.Parent, SVG.Text, SVG.FX, { @@ -103,6 +107,5 @@ SVG.extend(SVG.Parent, SVG.Text, SVG.FX, { return this } - }) diff --git a/src/svg.js b/src/svg.js index 16eda3e..20c0637 100644..100755 --- a/src/svg.js +++ b/src/svg.js @@ -15,20 +15,29 @@ SVG.ns = 'http://www.w3.org/2000/svg' SVG.xmlns = 'http://www.w3.org/2000/xmlns/' SVG.xlink = 'http://www.w3.org/1999/xlink' +// Svg support test +SVG.supported = (function() { + return !! document.createElementNS && + !! document.createElementNS(SVG.ns,'svg').createSVGRect +})() + +// Don't bother to continue if SVG is not supported +if (!SVG.supported) return false + // Element id sequence SVG.did = 1000 // Get next named element id SVG.eid = function(name) { - return 'Svgjs' + name.charAt(0).toUpperCase() + name.slice(1) + (SVG.did++) + return 'Svgjs' + capitalize(name) + (SVG.did++) } // Method for element creation SVG.create = function(name) { - /* create element */ + // create element var element = document.createElementNS(this.ns, name) - /* apply unique id */ + // apply unique id element.setAttribute('id', this.eid(name)) return element @@ -38,10 +47,10 @@ SVG.create = function(name) { SVG.extend = function() { var modules, methods, key, i - /* get list of modules */ + // Get list of modules modules = [].slice.call(arguments) - /* get object with extensions */ + // Get object with extensions methods = modules.pop() for (i = modules.length - 1; i >= 0; i--) @@ -49,34 +58,82 @@ SVG.extend = function() { for (key in methods) modules[i].prototype[key] = methods[key] - /* make sure SVG.Set inherits any newly added methods */ + // Make sure SVG.Set inherits any newly added methods if (SVG.Set && SVG.Set.inherit) SVG.Set.inherit() } +// Invent new element +SVG.invent = function(config) { + // Create element initializer + var initializer = typeof config.create == 'function' ? + config.create : + function() { + this.constructor.call(this, SVG.create(config.create)) + } + + // Inherit prototype + if (config.inherit) + initializer.prototype = new config.inherit + + // Extend with methods + if (config.extend) + SVG.extend(initializer, config.extend) + + // Attach construct method to parent + if (config.construct) + SVG.extend(config.parent || SVG.Container, config.construct) + + return initializer +} + +// Adopt existing svg elements +SVG.adopt = function(node) { + // make sure a node isn't already adopted + if (node.instance) return node.instance + + // initialize variables + var element + + // adopt with element-specific settings + if (node.nodeName == 'svg') + element = node.parentNode instanceof SVGElement ? new SVG.Nested : new SVG.Doc + else if (node.nodeName == 'lineairGradient') // lineair? + element = new SVG.Gradient('lineair') + else if (node.nodeName == 'radialGradient') + element = new SVG.Gradient('radial') + else if (SVG[capitalize(node.nodeName)]) + element = new SVG[capitalize(node.nodeName)] + else + element = new SVG.Element(node) + + // ensure references + element.type = node.nodeName + element.node = node + node.instance = element + + // SVG.Class specific preparations + if (element instanceof SVG.Doc) + element.namespace().defs() + + return element +} + // Initialize parsing element SVG.prepare = function(element) { - /* select document body and create invisible svg element */ + // Select document body and create invisible svg element var body = document.getElementsByTagName('body')[0] , draw = (body ? new SVG.Doc(body) : element.nested()).size(2, 0) , path = SVG.create('path') - /* insert parsers */ + // Insert parsers draw.node.appendChild(path) - /* create parser object */ + // Create parser object SVG.parser = { - body: body || element.parent + body: body || element.parent() , draw: draw.style('opacity:0;position:fixed;left:100%;top:100%;overflow:hidden') , poly: draw.polyline().node , path: path } -} - -// svg support test -SVG.supported = (function() { - return !! document.createElementNS && - !! document.createElementNS(SVG.ns,'svg').createSVGRect -})() - -if (!SVG.supported) return false +}
\ No newline at end of file diff --git a/src/symbol.js b/src/symbol.js deleted file mode 100644 index e8907a3..0000000 --- a/src/symbol.js +++ /dev/null @@ -1,17 +0,0 @@ - -SVG.Symbol = SVG.invent({ - // Initialize node - create: 'symbol' - - // Inherit from -, inherit: SVG.Container - - // Add parent method -, construct: { - // Create a new symbol - symbol: function() { - return this.defs().put(new SVG.Symbol) - } - } - -})
\ No newline at end of file diff --git a/src/text.js b/src/text.js index 38d4ecc..389a670 100644..100755 --- a/src/text.js +++ b/src/text.js @@ -1,14 +1,13 @@ - SVG.Text = SVG.invent({ // Initialize node create: function() { this.constructor.call(this, SVG.create('text')) - this._leading = new SVG.Number(1.3) /* store leading value for rebuilding */ - this._rebuild = true /* enable automatic updating of dy values */ - this._build = false /* disable build mode for adding multiple lines */ + this._leading = new SVG.Number(1.3) // store leading value for rebuilding + this._rebuild = true // enable automatic updating of dy values + this._build = false // disable build mode for adding multiple lines - /* set default font */ + // set default font this.attr('font-family', SVG.defaults.attrs['font-family']) } @@ -19,13 +18,13 @@ SVG.Text = SVG.invent({ , extend: { // Move over x-axis x: function(x) { - /* act as getter */ + // act as getter if (x == null) return this.attr('x') - /* move lines as well if no textPath is present */ + // move lines as well if no textPath is present if (!this.textPath) - this.lines.each(function() { if (this.newLined) this.x(x) }) + this.lines().each(function() { if (this.newLined) this.x(x) }) return this.attr('x', x) } @@ -34,7 +33,7 @@ SVG.Text = SVG.invent({ var oy = this.attr('y') , o = typeof oy === 'number' ? oy - this.bbox().y : 0 - /* act as getter */ + // act as getter if (y == null) return typeof oy === 'number' ? oy - o : oy @@ -50,26 +49,26 @@ SVG.Text = SVG.invent({ } // Set the text content , text: function(text) { - /* act as getter */ + // act as getter if (typeof text === 'undefined') return this.content - /* remove existing content */ + // remove existing content this.clear().build(true) if (typeof text === 'function') { - /* call block */ + // call block text.call(this, this) } else { - /* store text and make sure text is not blank */ + // store text and make sure text is not blank text = (this.content = text).split('\n') - /* build new lines */ + // build new lines for (var i = 0, il = text.length; i < il; i++) this.tspan(text[i]).newLine() } - /* disable build mode and rebuild lines */ + // disable build mode and rebuild lines return this.build(false).rebuild() } // Set font size @@ -78,29 +77,40 @@ SVG.Text = SVG.invent({ } // Set / get leading , leading: function(value) { - /* act as getter */ + // act as getter if (value == null) return this._leading - /* act as setter */ + // act as setter this._leading = new SVG.Number(value) return this.rebuild() } + // Get all the first level lines + , lines: function() { + // filter tspans and map them to SVG.js instances + for (var i = 0, il = this.node.childNodes.length, lines = []; i < il; i++) + if (this.node.childNodes[i] instanceof SVGElement) + lines.push(SVG.adopt(this.node.childNodes[i])) + + // return an instance of SVG.set + return new SVG.Set(lines) + } // Rebuild appearance type , rebuild: function(rebuild) { - /* store new rebuild flag if given */ + // store new rebuild flag if given if (typeof rebuild == 'boolean') this._rebuild = rebuild - /* define position of all lines */ + // define position of all lines if (this._rebuild) { var self = this - this.lines.each(function() { + this.lines().each(function() { if (this.newLined) { if (!this.textPath) this.attr('x', self.attr('x')) + this.attr('dy', self._leading * new SVG.Number(self.attr('font-size'))) } }) @@ -131,7 +141,7 @@ SVG.Text = SVG.invent({ }) -SVG.TSpan = SVG.invent({ +SVG.Tspan = SVG.invent({ // Initialize node create: 'tspan' @@ -156,64 +166,63 @@ SVG.TSpan = SVG.invent({ } // Create new line , newLine: function() { - /* fetch text parent */ + // fetch text parent var t = this.doc(SVG.Text) - /* mark new line */ + // mark new line this.newLined = true - /* apply new hy¡n */ + // apply new hy¡n return this.dy(t._leading * t.attr('font-size')).attr('x', t.x()) } } }) -SVG.extend(SVG.Text, SVG.TSpan, { +SVG.extend(SVG.Text, SVG.Tspan, { // Create plain text node plain: function(text) { - /* clear if build mode is disabled */ + // clear if build mode is disabled if (this._build === false) this.clear() - /* create text node */ + // create text node this.node.appendChild(document.createTextNode((this.content = text))) return this } // Create a tspan , tspan: function(text) { - var node = (this.textPath || this).node - , tspan = new SVG.TSpan + var node = (this.textPath() || this).node + , tspan = new SVG.Tspan - /* clear if build mode is disabled */ + // clear if build mode is disabled if (this._build === false) this.clear() - /* add new tspan and reference */ + // add new tspan node.appendChild(tspan.node) - tspan.parent = this - /* only first level tspans are considered to be "lines" */ + // only first level tspans are considered to be "lines" + // that doenst make sence. A line is added to a SVG.Set which is never used or returned. + // So why bother adding it? + // Also: lines() reads all children so it already has this tspan in it because we added it before if (this instanceof SVG.Text) - this.lines.add(tspan) + this.lines().add(tspan) return tspan.text(text) } // Clear all lines , clear: function() { - var node = (this.textPath || this).node + var node = (this.textPath() || this).node - /* remove existing child nodes */ + // remove existing child nodes while (node.hasChildNodes()) node.removeChild(node.lastChild) - /* reset content references */ - if (this instanceof SVG.Text) { - delete this.lines - this.lines = new SVG.Set + // reset content references + if (this instanceof SVG.Text) this.content = '' - } return this } diff --git a/src/textpath.js b/src/textpath.js index 0ee9d77..03dfbee 100644..100755 --- a/src/textpath.js +++ b/src/textpath.js @@ -12,31 +12,42 @@ SVG.TextPath = SVG.invent({ , construct: { // Create path for text to run on path: function(d) { - /* create textPath element */ - this.textPath = new SVG.TextPath + // create textPath element + var path = new SVG.TextPath + , track = this.doc().defs().path(d) - /* move lines to textpath */ - while(this.node.hasChildNodes()) - this.textPath.node.appendChild(this.node.firstChild) + // move lines to textpath + while (this.node.hasChildNodes()) + path.node.appendChild(this.node.firstChild) - /* add textPath element as child node */ - this.node.appendChild(this.textPath.node) + // add textPath element as child node + this.node.appendChild(path.node) - /* create path in defs */ - this.track = this.doc().defs().path(d) - - /* create circular reference */ - this.textPath.parent = this - - /* link textPath to path and add content */ - this.textPath.attr('href', '#' + this.track, SVG.xlink) + // link textPath to path and add content + path.attr('href', '#' + track, SVG.xlink) return this } // Plot path if any , plot: function(d) { - if (this.track) this.track.plot(d) + var track = this.track() + + if (track) + track.plot(d) + return this } + // Get the path track element + , track: function() { + var path = this.textPath() + + if (path) + return path.reference('href') + } + // Get the textPath child + , textPath: function() { + if (this.node.firstChild && this.node.firstChild.nodeName == 'textPath') + return SVG.adopt(this.node.firstChild) + } } })
\ No newline at end of file diff --git a/src/transform.js b/src/transform.js new file mode 100644 index 0000000..43bfb1b --- /dev/null +++ b/src/transform.js @@ -0,0 +1,126 @@ +SVG.extend(SVG.Element, SVG.FX, { + // Add transformations + transform: function(o, relative) { + // get target in case of the fx module, otherwise reference this + var target = this.target || this + , matrix + + // act as a getter + if (typeof o !== 'object') { + // get current matrix + matrix = target.ctm().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) + + // ensure relative flag + relative = !!relative || !!o.relative + + // act on matrix + if (o.a != null) { + matrix = relative ? + // relative + matrix.multiply(new SVG.Matrix(o)) : + // absolute + new SVG.Matrix(o) + + // act on rotation + } else if (o.rotation != null) { + // 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 + target.attr('transform', matrix + ' rotate(' + [o.rotation, o.cx, o.cy].join() + ')').ctm() : + // 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) { + // 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 + + if (!relative) { + // absolute; multiply inversed values + var e = matrix.extract() + o.scaleX = o.scaleX * 1 / e.scaleX + o.scaleY = o.scaleY * 1 / e.scaleY + } + + matrix = matrix.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 + + if (!relative) { + // absolute; reset skew values + var e = matrix.extract() + matrix = matrix.multiply(new SVG.Matrix().skew(e.skewX, e.skewY, o.cx, o.cy).inverse()) + } + + matrix = matrix.skew(o.skewX, o.skewY, o.cx, o.cy) + + // act on flip + } else if (o.flip) { + matrix = 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) { + if (relative) { + // relative + matrix = matrix.translate(o.x, o.y) + } else { + // absolute + if (o.x != null) matrix.e = o.x + if (o.y != null) matrix.f = o.y + } + } + + return this.attr('transform', matrix) + } +}) + +SVG.extend(SVG.Element, { + // Reset all transformations + untransform: function() { + return this.attr('transform', null) + } +})
\ No newline at end of file diff --git a/src/use.js b/src/use.js index 9bea70c..1fe7cb3 100644..100755 --- a/src/use.js +++ b/src/use.js @@ -9,10 +9,10 @@ SVG.Use = SVG.invent({ , extend: { // Use element as a reference element: function(element, file) { - /* store target element */ + /* Store target element */ this.target = element - /* set lined element */ + /* Set lined element */ return this.attr('href', (file || '') + '#' + element, SVG.xlink) } } diff --git a/src/utilities.js b/src/utilities.js new file mode 100644 index 0000000..0c1a0a3 --- /dev/null +++ b/src/utilities.js @@ -0,0 +1,23 @@ +SVG.utils = { + // Map function + map: function(array, block) { + var i + , il = array.length + , result = [] + + for (i = 0; i < il; i++) + result.push(block(array[i])) + + return result + } + + // Degrees to radians +, radians: function(d) { + return d % 360 * Math.PI / 180 + } + // Radians to degrees +, degrees: function(r) { + return r * 180 / Math.PI % 360 + } + +}
\ No newline at end of file diff --git a/src/viewbox.js b/src/viewbox.js index e8736aa..11a7138 100644..100755 --- a/src/viewbox.js +++ b/src/viewbox.js @@ -15,13 +15,13 @@ SVG.ViewBox = function(element) { /* 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 + 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 + height = new SVG.Number(he instanceof SVG.Doc ? he.parent().offsetHeight : he.parent().height()) + he = he.parent() } /* ensure defaults */ |