From 462d2cd3738c904db0be7086878d1fcc17b79553 Mon Sep 17 00:00:00 2001 From: wout Date: Fri, 11 Jul 2014 23:36:17 +0200 Subject: [PATCH] Completely reworked transform system --- CHANGELOG.md | 5 + Gemfile | 2 +- Gemfile.lock | 2 +- README.md | 148 ++++-- Rakefile | 2 +- dist/svg.js | 1025 ++++++++++++++++++++++++---------------- dist/svg.min.js | 5 +- spec/index.html | 1 + spec/spec/container.js | 6 + spec/spec/element.js | 43 +- spec/spec/matrix.js | 61 +++ spec/spec/selector.js | 1 - src/attr.js | 79 ++++ src/bbox.js | 55 --- src/boxes.js | 144 ++++++ src/element.js | 146 +----- src/ellipse.js | 4 +- src/group.js | 4 +- src/helpers.js | 214 +++++---- src/line.js | 8 +- src/matrix.js | 114 ++++- src/patharray.js | 2 +- src/rbox.js | 73 --- src/regex.js | 24 +- src/style.js | 33 ++ src/sugar.js | 27 +- src/transform.js | 47 ++ src/transporter.js | 33 ++ src/utilities.js | 9 + 29 files changed, 1451 insertions(+), 866 deletions(-) create mode 100644 spec/spec/matrix.js create mode 100644 src/attr.js delete mode 100755 src/bbox.js create mode 100755 src/boxes.js delete mode 100755 src/rbox.js create mode 100644 src/style.js create mode 100644 src/transform.js create mode 100644 src/transporter.js diff --git a/CHANGELOG.md b/CHANGELOG.md index e9c9e8e..2e91b8b 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,16 @@ - 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 -> __TODO!__ - completely reworked transformations to be chainable and more true to their nature -> __TODO!__ - changed `lines` reference to `lines()` on `SVG.Text` -> __TODO!__ - changed `track` reference to `track()` on `SVG.Text` -> __TODO!__ - fixed a bug in clipping and masking where empty nodes persists after removal -> __TODO!__ - added raw svg import functionality with the `svg()` method -> __TODO!__ +- moved sup-pixel offset fix to a separate plugin -> __TODO!__ +- added `SVG.Title` -> __TODO!__ +- added `native()` method to elements and matrix to get to the native api -> __TODO!__ +- added `untransform()` method to remove all transformations -> __TODO!__ # 1.0.0-rc.9 (17/06/2014) diff --git a/Gemfile b/Gemfile index dd15469..ddbcde0 100755 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -source :rubygems +source 'https://rubygems.org' gem 'rake' gem 'uglifier' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index d39262a..4650b9b 100755 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,5 +1,5 @@ GEM - remote: http://rubygems.org/ + remote: https://rubygems.org/ specs: execjs (1.4.0) multi_json (~> 1.0) diff --git a/README.md b/README.md index 06e2746..ffcc796 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,7 @@ 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). [![Wout on Gittip](http://files.wout.co.uk/github/gittip.png)](https://www.gittip.com/wout/) @@ -77,7 +77,7 @@ var draw = SVG('drawing').fixSubPixelOffset() ## Parent elements ### Main svg document -The main svg.js initializer function creates a root svg node in the given element and retuns an instance of `SVG.Doc`: +The main SVG.js initializer function creates a root svg node in the given element and retuns an instance of `SVG.Doc`: ```javascript var draw = SVG('drawing') @@ -101,7 +101,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() @@ -114,6 +114,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`_ @@ -458,7 +460,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: @@ -543,7 +545,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 `` 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 `` 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.") @@ -617,7 +619,7 @@ 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: +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`_ @@ -799,7 +801,7 @@ _Javascript inheritance stack: `SVG.Use` < `SVG.Container` < `SVG.Symbol`_ ## 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') @@ -840,7 +842,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 @@ -849,7 +851,7 @@ element.node __`returns`: `node`__ ### instance -Similarly, the node carries a reference to the svg.js `instance`: +Similarly, the node carries a reference to the SVG.js `instance`: ```javascript node.instance @@ -1055,7 +1057,7 @@ The `transform()` can act both as a getter or a setter. Let's start with the for Transforms are cascading sequentially. Every transform you add will build further on the effects of all the previous transforms together. So for example, when you add the `translate(10, 10)` transform three times, the total translation will equal `translate(30, 30)`. -We are going to use one node as an examle for this section: +We are going to use one node as an example for this section: ```xml @@ -1549,6 +1551,15 @@ path.rbox() __`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: @@ -2735,7 +2746,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%') @@ -2873,7 +2884,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: @@ -2961,37 +2972,37 @@ path.array() //-> returns the SVG.PathArray instance 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. @@ -2999,7 +3010,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]]) @@ -3083,10 +3094,81 @@ 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() +Get the calculated values of matrix: + +```javascript +new SVG.Matrix().extract() +``` + +returns: +```javascript +{ + x: 0 +, y: 0 +, skewX: 0 +, skewY: 0 +, scaleX: 1 +, scaleY: 1 +, rotation: 0 +} +``` + + ## Extending functionality ### SVG.invent() -Creating your own custom elements with svg.js is 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. +Creating your own custom elements with SVG.js is 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({ @@ -3132,10 +3214,10 @@ That's it, the invention is now ready to be used! The `SVG.invent()` function always expectes 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 objects 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. @@ -3188,10 +3270,10 @@ 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: ### absorb -[svg.absorb.js](https://github.com/wout/svg.absorb.js) absorb raw SVG data into a svg.js instance. +[svg.absorb.js](https://github.com/wout/svg.absorb.js) absorb raw SVG data into a SVG.js instance. ### draggable [svg.draggable.js](https://github.com/wout/svg.draggable.js) to make elements draggable. @@ -3243,7 +3325,7 @@ Be aware that pull requests without specs will be declined. ## 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. +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. You will need ruby, RubyGems, and rake installed on your system. @@ -3257,7 +3339,7 @@ $ rake -V $ gem install uglifier ``` -Build svg.js by running `rake`: +Build SVG.js by running `rake`: ``` sh $ rake @@ -3268,7 +3350,7 @@ Minified and gzipped: 4.413k, compression factor 7.289 The resulting files are: -1. `dist/svg.js` +1. `dist/SVG.js` 2. `dist/svg.min.js` To include optional modules and remove default ones, use the `concat` task. In @@ -3301,6 +3383,6 @@ 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. Important: this library is still in beta, therefore the API might be subject to change in the course of development. diff --git a/Rakefile b/Rakefile index 27e8d76..f5ce78a 100755 --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,7 @@ SVGJS_VERSION = '1.0.0-rc.10' # all available modules in the correct loading order -MODULES = %w[ svg inventor adopter regex utilities default color array pointarray patharray number viewbox bbox rbox element parent container fx relative event defs group arrange mask clip gradient pattern doc spof shape symbol use rect ellipse line poly pointed path image text textpath nested hyperlink marker sugar set data memory selector loader helpers polyfill ] +MODULES = %w[ svg inventor adopter regex utilities default color array pointarray patharray number viewbox element boxes matrix attr transform style parent container transporter fx relative event defs group arrange mask clip gradient pattern doc spof shape symbol use rect ellipse line poly pointed path image text textpath nested hyperlink marker sugar set data memory selector loader helpers polyfill ] # how many bytes in a "kilobyte" KILO = 1024 diff --git a/dist/svg.js b/dist/svg.js index a11987b..8dd3a39 100755 --- a/dist/svg.js +++ b/dist/svg.js @@ -1,4 +1,4 @@ -/* svg.js 1.0.0-rc.10-17-g0d11ad2 - svg inventor adopter regex utilities default color array pointarray patharray number viewbox bbox rbox element parent container fx relative event defs group arrange mask clip gradient pattern doc spof shape symbol use rect ellipse line poly pointed path image text textpath nested hyperlink marker sugar set data memory selector loader helpers polyfill - svgjs.com/license */ +/* svg.js 1.0.0-rc.10-19-g7cc2d36 - svg inventor adopter regex utilities default color array pointarray patharray number viewbox element boxes matrix attr transform style parent container transporter fx relative event defs group arrange mask clip gradient pattern doc spof shape symbol use rect ellipse line poly pointed path image text textpath nested hyperlink marker sugar set data memory selector loader helpers polyfill - svgjs.com/license */ ;(function() { var SVG = this.SVG = function(element) { @@ -140,40 +140,40 @@ SVG.regex = { /* parse unit value */ - unit: /^(-?[\d\.]+)([a-z%]{0,2})$/ + unit: /^(-?[\d\.]+)([a-z%]{0,2})$/ /* parse hex value */ - , hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i + , hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i /* parse rgb value */ - , rgb: /rgb\((\d+),(\d+),(\d+)\)/ + , rgb: /rgb\((\d+),(\d+),(\d+)\)/ /* parse reference id */ - , reference: /#([a-z0-9\-_]+)/i + , reference: /#([a-z0-9\-_]+)/i /* test hex value */ - , isHex: /^#[a-f0-9]{3,6}$/i + , isHex: /^#[a-f0-9]{3,6}$/i /* test rgb value */ - , isRgb: /^rgb\(/ + , isRgb: /^rgb\(/ /* test css declaration */ - , isCss: /[^:]+:[^;]+;?/ + , isCss: /[^:]+:[^;]+;?/ /* test for blank string */ - , isBlank: /^(\s+)?$/ + , isBlank: /^(\s+)?$/ /* test for numeric string */ - , isNumber: /^-?[\d\.]+$/ + , isNumber: /^-?[\d\.]+$/ /* test for percent value */ - , isPercent: /^-?[\d\.]+%$/ + , isPercent: /^-?[\d\.]+%$/ /* test for image url */ - , isImage: /\.(jpg|jpeg|png|gif)(\?[^=]+.*)?/i + , isImage: /\.(jpg|jpeg|png|gif)(\?[^=]+.*)?/i /* test for namespaced event */ - , isEvent: /^[\w]+:[\w]+$/ + , isEvent: /^[\w]+:[\w]+$/ } @@ -190,6 +190,15 @@ 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 = { @@ -717,7 +726,7 @@ 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) @@ -898,135 +907,6 @@ }) - 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 - - return boxProperties(b) - } - - }) - - 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.parent && (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 += window.scrollX - this.y += window.scrollY - - /* 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 - - return boxProperties(b) - } - - }) - - SVG.Element = SVG.invent({ // Initialize node create: function(node) { @@ -1049,7 +929,7 @@ x: function(x) { if (x != null) { x = new SVG.Number(x) - x.value /= this.trans.scaleX + x.value /= this.ctm().extract().scaleX } return this.attr('x', x) } @@ -1057,7 +937,7 @@ , y: function(y) { if (y != null) { y = new SVG.Number(y) - y.value /= this.trans.scaleY + y.value /= this.ctm().extract().scaleY } return this.attr('y', y) } @@ -1118,139 +998,10 @@ , 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) { - // 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) - - // 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(t, v) { - // Get a transformation at a given position - if (typeof t === 'number') { - - } - - 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() @@ -1299,10 +1050,9 @@ // Remove class from the node , removeClass: function(name) { if (this.hasClass(name)) { - var array = this.classes().filter(function(c) { + this.attr('class', this.classes().filter(function(c) { return c != name - }) - this.attr('class', array.join(' ')) + }).join(' ')) } return this @@ -1327,9 +1077,439 @@ 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 + } + } + }) + + + SVG.BBox = SVG.invent({ + // Initialize + create: 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) { + // Get current extracted transformations + var t = new SVG.Matrix(element).extract() + + // Find native bbox + if (element.node.getBBox) + box = element.node.getBBox() + // Mimic bbox + else + 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 + t.x + this.y = box.y + t.y + + // Plain width and height + this.width = box.width * t.scaleX + this.height = box.height * t.scaleY + } + + // 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.RBox = SVG.invent({ + // Initialize + create: 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.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.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 += window.scrollX + this.y += window.scrollY + + // Add center, right and bottom + fullBox(this) + } + + // define Parent + , parent: SVG.Element + + // Constructor + , construct: { + // Get rect box + rbox: function() { + return new SVG.RBox(this) + } } + + }) + + // Add universal merge method + ;[SVG.BBox, 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 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 + + 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.node && source.node.getCTM ? + source.node.getCTM() : + typeof source === 'string' ? + arrayToMatrix(source.replace(/\s/g, '').split(',')) : + 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]] = typeof source[abcdef[i]] === 'number' ? + source[abcdef[i]] : base[abcdef[i]] + + } + + // Add methods + , extend: { + // Extract individual transformations + extract: function() { + // Find transform points + var px = deltaTransformPoint(this, { x: 0, y: 1 }) + , py = deltaTransformPoint(this, { x: 1, y: 0 }) + + return { + // Translation + x: this.e + , y: this.f + // Skew + , skewX: 180 / Math.PI * Math.atan2(px.y, px.x) - 90 + , 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: this.skewX + } + } + // Multiply + , multiply: function(matrix) { + return new SVG.Matrix(this.native().multiply(matrix.native())) + } + // Inverse + , inverse: function() { + return new SVG.Matrix(this.native().inverse()) + } + // Translate + , translate: function(x, y) { + return new SVG.Matrix(this.native().translate(x || 0, y || 0)) + } + // Scale + , scale: function(x, y, cx, cy) { + if (y == null) + return new SVG.Matrix(this.native().scale(x)) + else + return new SVG.Matrix(this.native().scaleNonUniform(x, y)) + } + // Rotate + , rotate: function(d, x, y) { + // Fall back to native rotate method + if (x == null) return new SVG.Matrix(this.native().rotate(d)) + + // Convert degrees to radians + d = SVG.utils.radians(d) + + return new SVG.Matrix(1, 0, 0, 1, x, y) + //.multiply(new SVG.Matrix(Math.cos(d), Math.sin(d), -Math.sin(d), Math.cos(d), 0, 0)) + //.multiply(new SVG.Matrix(1, 0, 0, 1, -x, -y)) + } + // Flip + , flip: function(a) { + return new SVG.Matrix(this.native()['flip' + a.toUpperCase()]()) + } + // Skew + , skew: function(x, y) { + return new SVG.Matrix(this.native().skewX(x || 0).skewY(y || 0)) + } + // Convert this to SVGMatrix + , native: function() { + // Create new matrix + var i, matrix = SVG.parser.draw.node.createSVGMatrix() + + // Update with current values + for (i = abcdef.length - 1; i >= 0; i--) + matrix[abcdef[i]] = this[abcdef[i]] + + return matrix + } + // Convert array to string + , toString: function() { + return 'matrix(' + [this.a, this.b, this.c, this.d, this.e, this.f].join() + ')' + } + } + + // Define parent + , parent: SVG.Element + + // Add parent method + , construct: { + // Get current matrix + ctm: function() { + return new SVG.Matrix(this) + } + + } + }) + 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) + + // 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 + } + }) + + SVG.extend(SVG.Element, { + // Add transformations + transform: function(o) { + // Full getter + if (o == null) + return this.ctm().extract() + + // Get current matrix + var matrix = new SVG.Matrix(this) + + // Act on matrix + if (o.a != null) + matrix = matrix.multiply(new SVG.Matrix(o)) + + // Act on rotate + else if (o.rotation) + matrix = matrix.rotate( + o.rotation + , o.cx == null ? this.bbox().cx : o.cx + , o.cy == null ? this.bbox().cy : o.cy + ) + + // Act on scale + else if (o.scale != null || o.scaleX != null || o.scaleY != null) + matrix = matrix.scale( + o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 + , o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 + , o.cx != null ? o.cx : this.bbox().x + , o.cy != null ? o.cy : this.bbox().y + ) + + // Act on skew + else if (o.skewX || o.skewY) + matrix = matrix.skew(o.skewX, o.skewY) + + // Act on translate + else if (o.x || o.y) + matrix = matrix.translate(o.x, o.y) + console.log(o, matrix) + return this.attr('transform', matrix) + } + // 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 @@ -1454,6 +1634,40 @@ }) + SVG.extend(SVG.Parent, SVG.Text, { + // Import svg SVG data + svg: function(svg) { + // create temporary div to receive svg content + var element = document.createElement('div') + + if (svg) { + // strip away newlines and properly close tags + svg = svg + .replace(/\n/, '') + .replace(/<(\w+)([^<]+?)\/>/g, '<$1$2>') + + // ensure SVG wrapper for correct element type casting + element.innerHTML = '' + svg + '' + + // transplant content from well to target + for (var i = element.firstChild.childNodes.length - 1; i >= 0; i--) + if (element.firstChild.childNodes[i].nodeType == 1) + this.node.appendChild(element.firstChild.childNodes[i]) + + return this + + } else { + // clone element and its contents + var clone = this.node.cloneNode(true) + + // add target to clone + element.appendChild(clone) + + return element.innerHTML + } + } + }) + SVG.FX = SVG.invent({ // Initialize FX object create: function(element) { @@ -2031,11 +2245,11 @@ , extend: { // Move over x-axis x: function(x) { - return x == null ? this.trans.x : this.transform('x', x) + return x == null ? this.ctm().x : this.transform('x', x) } // Move over y-axis , y: function(y) { - return y == null ? this.trans.y : this.transform('y', y) + return y == null ? this.ctm().y : this.transform('y', y) } // Move by center over x-axis , cx: function(x) { @@ -2644,11 +2858,11 @@ } // 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.ctm().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.ctm().scaleY)) } // Set width of element , width: function(width) { @@ -2679,17 +2893,17 @@ , extend: { // Get array array: function() { - return (this._array = new SVG.PointArray([ + 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 (typeof x1 === 'number') + if (arguments.length == 4) x1 = { x1: x1, y1: y1, x2: x2, y2: y2 } else - x1 = (this._array = new SVG.PointArray(x1)).toLine() + x1 = new SVG.PointArray(x1).toLine() return this.attr(x1) } @@ -3358,37 +3572,24 @@ SVG.extend(SVG.Element, SVG.FX, { // Rotation - rotate: function(deg, x, y) { - return this.transform({ - rotation: deg || 0 - , cx: x - , cy: y - }) + rotate: function(d, cx, cy) { + return this.transform({ rotation: d, cx: cx, cy: cy }) } // Skew , skew: function(x, y) { - return this.transform({ - skewX: x || 0 - , skewY: y || 0 - }) + return this.transform({ skewX: x, skewY: y }) } // Scale - , scale: function(x, y) { - return this.transform({ - scaleX: x - , scaleY: y == null ? x : y - }) + , scale: function(x, y, cx, cy) { + return this.transform({ scaleX: x, scaleY: y, cx: cx, cy: cy }) } // Translate , translate: function(x, y) { - return this.transform({ - x: x - , y: y - }) + return this.transform({ x: x, y: y }) } // Matrix , matrix: function(m) { - return this.transform({ matrix: m }) + return this.attr('transform', new SVG.Matrix(m)) } // Opacity , opacity: function(value) { @@ -3677,151 +3878,167 @@ exports.SVG = SVG function camelCase(s) { - return s.toLowerCase().replace(/-(.)/g, function(m, g) { - return g.toUpperCase() - }) + 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) + 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 + 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 + 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 (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 - } + 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 + } + } + + // Delta transform point + function deltaTransformPoint(matrix, point) { + return { + x: point.x * matrix.a + point.y * matrix.c + 0 + , y: point.x * matrix.b + point.y * matrix.d + 0 + } + } + + // Map matrix array to object + function arrayToMatrix(a) { + return { a: a[0], b: a[1], c: a[2], e: a[3], f: a[4], g: a[5] } } // 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 + /* 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 } // 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][3] - s += ' ' - s += a[i][4] - - if (a[i][5] != null) { - s += ' ' - s += a[i][5] - s += ' ' - s += a[i][6] - - if (a[i][7] != null) { - s += ' ' - s += a[i][7] - } - } - } - } - } - } - - return s + ' ' + 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][3] + s += ' ' + s += a[i][4] + + if (a[i][5] != null) { + s += ' ' + s += a[i][5] + s += ' ' + s += a[i][6] + + if (a[i][7] != null) { + s += ' ' + s += a[i][7] + } + } + } + } + } + } + + 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]) + // 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)) + return SVG.adopt(node).id(SVG.eid(node.nodeName)) } // 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 + function fullBox(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 - return b + return b } // 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 + 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) + var m = url.toString().match(SVG.regex.reference) - if (m) return m[1] + if (m) return m[1] } + // Create matrix array for looping + var abcdef = 'abcdef'.split('') + // Shim layer with setTimeout fallback by Paul Irish window.requestAnimFrame = (function(){ - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.msRequestAnimationFrame || - function (c) { window.setTimeout(c, 1000 / 60) } + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.msRequestAnimationFrame || + function (c) { window.setTimeout(c, 1000 / 60) } })() if (typeof CustomEvent !== 'function') { diff --git a/dist/svg.min.js b/dist/svg.min.js index 992b7c1..59979e3 100755 --- a/dist/svg.min.js +++ b/dist/svg.min.js @@ -1,2 +1,3 @@ -(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==e||null==i)&&(null==i?i=t.height/t.width*e:null==e&&(e=t.width/t.height*i)),{width:e,height:i}}function s(t,e){return"number"==typeof t.from?t.from+(t.to-t.from)*e:t instanceof f.Color||t instanceof f.Number?t.at(e):1>e?t.from:t.to}function h(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 o(t){for(var e=t.childNodes.length-1;e>=0;e--)t.childNodes[e]instanceof SVGElement&&o(t.childNodes[e]);return f.adopt(t).id(f.eid(t.nodeName))}function a(t){return 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 u(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 l(t){var e=t.toString().match(f.regex.reference);return e?e[1]:void 0}function c(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}var f=this.SVG=function(t){return f.supported?(t=new f.Doc(t),f.parser||f.prepare(t),t):void 0};if(f.ns="http://www.w3.org/2000/svg",f.xmlns="http://www.w3.org/2000/xmlns/",f.xlink="http://www.w3.org/1999/xlink",f.did=1e3,f.eid=function(t){return"Svgjs"+e(t)+f.did++},f.create=function(t){var e=document.createElementNS(this.ns,t);return e.setAttribute("id",this.eid(t)),e},f.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];f.Set&&f.Set.inherit&&f.Set.inherit()},f.prepare=function(t){var e=document.getElementsByTagName("body")[0],i=(e?new f.Doc(e):t.nested()).size(2,0),n=f.create("path");i.node.appendChild(n),f.parser={body:e||t.parent(),draw:i.style("opacity:0;position:fixed;left:100%;top:100%;overflow:hidden"),poly:i.polyline().node,path:n}},f.supported=function(){return!!document.createElementNS&&!!document.createElementNS(f.ns,"svg").createSVGRect}(),!f.supported)return!1;f.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,f.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&f.extend(e,t.extend),t.construct&&f.extend(t.parent||f.Container,t.construct),e},f.adopt=function(t){if(t.instance)return t.instance;var i;return i="svg"==t.nodeName?t.parentNode instanceof SVGElement?new f.Nested:new f.Doc:"lineairGradient"==t.nodeName?new f.Gradient("lineair"):"radialGradient"==t.nodeName?new f.Gradient("radial"):f[e(t.nodeName)]?new(f[e(t.nodeName)]):new f.Element(t),i.type=t.nodeName,i.node=t,t.instance=i,i instanceof f.Doc&&i.namespace().defs(),i},f.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]+$/},f.utils={map:function(t,e){var i,n=t.length,r=[];for(i=0;n>i;i++)r.push(e(t[i]));return r}},f.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"},trans:{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}},f.Color=function(t){var e;this.r=0,this.g=0,this.b=0,"string"==typeof t?f.regex.isRgb.test(t)?(e=f.regex.rgb.exec(t.replace(/\s/g,"")),this.r=parseInt(e[1]),this.g=parseInt(e[2]),this.b=parseInt(e[3])):f.regex.isHex.test(t)&&(e=f.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)},f.extend(f.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 f.Color(t),this},at:function(t){return this.destination?(t=0>t?0:t>1?1:t,new f.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}}),f.Color.test=function(t){return t+="",f.regex.isHex.test(t)||f.regex.isRgb.test(t)},f.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},f.Color.isColor=function(t){return f.Color.isRgb(t)||f.Color.test(t)},f.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},f.extend(f.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.lengtht;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 f.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}}),f.PointArray=function(t,e){this.constructor.call(this,t,e||[[0,0]])},f.PointArray.prototype=new f.Array,f.extend(f.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 f.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 f.parser.poly.setAttribute("points",this.toString()),f.parser.poly.getBBox()}}),f.PathArray=function(t,e){this.constructor.call(this,t,e||[["M",0,0]])},f.PathArray.prototype=new f.Array,f.extend(f.PathArray,{toString:function(){return h(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 f.PathArray)return t.valueOf();var e,i,n,r,s,o,a,u,l,c,d,p=0,m=0;for(f.parser.path.setAttribute("d","string"==typeof t?t:h(t)),d=f.parser.path.pathSegList,e=0,i=d.numberOfItems;i>e;++e)c=d.getItem(e),l=c.pathSegTypeAsLetter,"M"==l||"L"==l||"H"==l||"V"==l||"C"==l||"S"==l||"Q"==l||"T"==l||"A"==l?("x"in c&&(p=c.x),"y"in c&&(m=c.y)):("x1"in c&&(s=p+c.x1),"x2"in c&&(a=p+c.x2),"y1"in c&&(o=m+c.y1),"y2"in c&&(u=m+c.y2),"x"in c&&(p+=c.x),"y"in c&&(m+=c.y),"m"==l?d.replaceItem(f.parser.path.createSVGPathSegMovetoAbs(p,m),e):"l"==l?d.replaceItem(f.parser.path.createSVGPathSegLinetoAbs(p,m),e):"h"==l?d.replaceItem(f.parser.path.createSVGPathSegLinetoHorizontalAbs(p),e):"v"==l?d.replaceItem(f.parser.path.createSVGPathSegLinetoVerticalAbs(m),e):"c"==l?d.replaceItem(f.parser.path.createSVGPathSegCurvetoCubicAbs(p,m,s,o,a,u),e):"s"==l?d.replaceItem(f.parser.path.createSVGPathSegCurvetoCubicSmoothAbs(p,m,a,u),e):"q"==l?d.replaceItem(f.parser.path.createSVGPathSegCurvetoQuadraticAbs(p,m,s,o),e):"t"==l?d.replaceItem(f.parser.path.createSVGPathSegCurvetoQuadraticSmoothAbs(p,m),e):"a"==l?d.replaceItem(f.parser.path.createSVGPathSegArcAbs(p,m,c.r1,c.r2,c.angle,c.largeArcFlag,c.sweepFlag),e):("z"==l||"Z"==l)&&(p=n,m=r)),("M"==l||"m"==l)&&(n=p,r=m);for(t=[],d=f.parser.path.pathSegList,e=0,i=d.numberOfItems;i>e;++e)c=d.getItem(e),l=c.pathSegTypeAsLetter,p=[l],"M"==l||"L"==l||"T"==l?p.push(c.x,c.y):"H"==l?p.push(c.x):"V"==l?p.push(c.y):"C"==l?p.push(c.x1,c.y1,c.x2,c.y2,c.x,c.y):"S"==l?p.push(c.x2,c.y2,c.x,c.y):"Q"==l?p.push(c.x1,c.y1,c.x,c.y):"A"==l&&p.push(c.r1,c.r2,c.angle,0|c.largeArcFlag,0|c.sweepFlag,c.x,c.y),t.push(p);return t},bbox:function(){return f.parser.path.setAttribute("d",this.toString()),f.parser.path.getBBox()}}),f.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(f.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 f.Number&&(this.value=t.value,this.unit=t.unit)},f.extend(f.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 f.Number(t),this},minus:function(t){return this.plus(-new f.Number(t))},times:function(t){return this.value=this*new f.Number(t),this},divide:function(t){return this.value=this/new f.Number(t),this},to:function(t){return"string"==typeof t&&(this.unit=t),this},morph:function(t){return this.destination=new f.Number(t),this},at:function(t){return this.destination?new f.Number(this.destination).minus(this).times(t).plus(this):this}}),f.ViewBox=function(t){var e,i,n,r,s=1,h=1,o=t.bbox(),a=(t.attr("viewBox")||"").match(/-?[\d\.]+/g),u=t,l=t;for(n=new f.Number(t.width()),r=new f.Number(t.height());"%"==n.unit;)s*=n.value,n=new f.Number(u instanceof f.Doc?u.parent().offsetWidth:u.parent().width()),u=u.parent();for(;"%"==r.unit;)h*=r.value,r=new f.Number(l instanceof f.Doc?l.parent().offsetHeight:l.parent().height()),l=l.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)},f.extend(f.ViewBox,{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height}}),f.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}a(this)},f.extend(f.BBox,{merge:function(t){var e=new f.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,a(e)}}),f.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.parent&&(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+=window.scrollX,this.y+=window.scrollY,a(this)},f.extend(f.RBox,{merge:function(t){var e=new f.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,a(e)}}),f.Element=f.invent({create:function(t){this._stroke=f.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 f.Number(t),t.value/=this.trans.scaleX),this.attr("x",t)},y:function(t){return null!=t&&(t=new f.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=r(this.bbox(),t,e);return this.width(new f.Number(i.width)).height(new f.Number(i.height))},clone:function(){return o(this.node.cloneNode(!0))},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||f.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]=f.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?f.defaults.attrs[t]:f.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)&&(f.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof f.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new f.Number(e):f.Color.isColor(e)?e=new f.Color(e):Array.isArray(e)&&(e=new f.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){return 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(!f.regex.isCss.test(e))return this.node.style[t(e)];e=e.split(";");for(var n=0;ni.x&&e>i.y&&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 f.Element&&t.apply(r[i],[i,r]),e&&r[i]instanceof f.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()}}}),f.Container=f.invent({create:function(t){this.constructor.call(this,t)},inherit:f.Parent,extend:{viewbox:function(t){return 0==arguments.length?new f.ViewBox(this):(t=1==arguments.length?[t.x,t.y,t.width,t.height]:[].slice.call(arguments),this.attr("viewBox",t))}}}),f.FX=f.invent({create:function(t){this.target=t},extend:{animate:function(t,e,i){var n,r,h,o,a=this.target,u=this;return"object"==typeof t&&(i=t.delay,e=t.ease,t=t.duration),t="="==t?t:null==t?1e3:new f.Number(t).valueOf(),e=e||"<>",u.to=function(t){var i;if(t=0>t?0:t>1?1:t,null==n){n=[];for(o in u.attrs)n.push(o);if(a.morphArray&&(u._plot||n.indexOf("points")>-1)){var l,c=new a.morphArray(u._plot||u.attrs.points||a.array);u._size&&c.size(u._size.width.to,u._size.height.to),l=c.bbox(),u._x?c.move(u._x.to,l.y):u._cx&&c.move(u._cx.to-l.width/2,l.y),l=c.bbox(),u._y?c.move(l.x,u._y.to):u._cy&&c.move(l.x,u._cy.to-l.height/2),delete u._x,delete u._y,delete u._cx,delete u._cy,delete u._size,u._plot=a.array.morph(c)}}if(null==r){r=[];for(o in u.trans)r.push(o)}if(null==h){h=[];for(o in u.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,u._plot?a.plot(u._plot.at(t)):(u._x?a.x(u._x.at(t)):u._cx&&a.cx(u._cx.at(t)),u._y?a.y(u._y.at(t)):u._cy&&a.cy(u._cy.at(t)),u._size&&a.size(u._size.width.at(t),u._size.height.at(t))),u._viewbox&&a.viewbox(u._viewbox.x.at(t),u._viewbox.y.at(t),u._viewbox.width.at(t),u._viewbox.height.at(t)),u._leading&&a.leading(u._leading.at(t)),i=n.length-1;i>=0;i--)a.attr(n[i],s(u.attrs[n[i]],t));for(i=r.length-1;i>=0;i--)a.transform(r[i],s(u.trans[r[i]],t));for(i=h.length-1;i>=0;i--)a.style(h[i],s(u.styles[h[i]],t));u._during&&u._during.call(a,t,function(e,i){return s({from:e,to:i},t)})},"number"==typeof t&&(this.timeout=setTimeout(function(){var n=(new Date).getTime();u.situation={interval:1e3/60,start:n,play:!0,finish:n+t,duration:t},u.render=function(){if(u.situation.play===!0){var n=(new Date).getTime(),r=n>u.situation.finish?1:(n-u.situation.start)/t;u.to(r),n>u.situation.finish?(u._plot&&a.plot(new f.PointArray(u._plot.destination).settle()),u._loop===!0||"number"==typeof u._loop&&u._loop>1?("number"==typeof u._loop&&--u._loop,u.animate(t,e,i)):u._after?u._after.apply(a,[u]):u.stop()):requestAnimFrame(u.render)}else requestAnimFrame(u.render)},u.render()},new f.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]=f.Color.isColor(n)?new f.Color(n).morph(e):f.regex.unit.test(n)?new f.Number(n).morph(e):{from:n,to:e}}return this},transform:function(t,e){if(1==arguments.length){t=u(t),delete t.matrix;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 f.Number(this.target.x()).morph(t),this},y:function(t){return this._y=new f.Number(this.target.y()).morph(t),this},cx:function(t){return this._cx=new f.Number(this.target.cx()).morph(t),this},cy:function(t){return this._cy=new f.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 f.Text)this.attr("font-size",t);else{var i=this.target.bbox();this._size={width:new f.Number(i.width).morph(t),height:new f.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 f.Number(this.target._leading).morph(t)),this},viewbox:function(t,e,i,n){if(this.target instanceof f.Container){var r=this.target.viewbox();this._viewbox={x:new f.Number(r.x).morph(t),y:new f.Number(r.y).morph(e),width:new f.Number(r.width).morph(i),height:new f.Number(r.height).morph(n)}}return this},update:function(t){return this.target instanceof f.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 f.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),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:f.Element,construct:{animate:function(t,e,i){return(this.fx||(this.fx=new f.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}}}),f.extend(f.Element,f.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","mouseenter","mouseleave","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){f.Element.prototype[t]=function(e){var i=this;return this.node["on"+t]="function"==typeof e?function(){return e.apply(i,arguments)}:null,this}}),f.events={},f.listeners={},f.registerEvent=function(t){f.events[t]||(f.events[t]=new c(t))},f.on=function(t,e,i){var n=i.bind(t.instance||t);f.listeners[i]=n,t.addEventListener(e,n,!1)},f.off=function(t,e,i){t.removeEventListener(e,f.listeners[i],!1),delete f.listeners[i]},f.extend(f.Element,{on:function(t,e){return f.on(this.node,t,e),this},off:function(t,e){return f.off(this.node,t,e),this},fire:function(t,e){return f.events[t].detail=e,this.node.dispatchEvent(f.events[t]),delete f.events[t].detail,this}}),f.Defs=f.invent({create:"defs",inherit:f.Container}),f.G=f.invent({create:"g",inherit:f.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 f.G)}}}),f.extend(f.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 f.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 f.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}}),f.Mask=f.invent({create:function(){this.constructor.call(this,f.create("mask")),this.targets=[]},inherit:f.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 f.Mask)}}}),f.extend(f.Element,{maskWith:function(t){return this.masker=t instanceof f.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)}}),f.ClipPath=f.invent({create:function(){this.constructor.call(this,f.create("clipPath")),this.targets=[]},inherit:f.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 f.ClipPath)}}}),f.extend(f.Element,{clipWith:function(t){return this.clipper=t instanceof f.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)}}),f.Gradient=f.invent({create:function(t){this.constructor.call(this,f.create(t+"Gradient")),this.type=t},inherit:f.Container,extend:{from:function(t,e){return"radial"==this.type?this.attr({fx:new f.Number(t),fy:new f.Number(e)}):this.attr({x1:new f.Number(t),y1:new f.Number(e)})},to:function(t,e){return"radial"==this.type?this.attr({cx:new f.Number(t),cy:new f.Number(e)}):this.attr({x2:new f.Number(t),y2:new f.Number(e)})},radius:function(t){return"radial"==this.type?this.attr({r:new f.Number(t)}):this},at:function(t,e,i){return this.put(new f.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)}}}),f.extend(f.Defs,{gradient:function(t,e){return this.put(new f.Gradient(t)).update(e)}}),f.Stop=f.invent({create:"stop",inherit:f.Element,extend:{update:function(t){return("number"==typeof t||t instanceof f.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 f.Number(t.offset)),this}}}),f.Pattern=f.invent({create:"pattern",inherit:f.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)}}}),f.extend(f.Defs,{pattern:function(t,e,i){return this.put(new f.Pattern).update(i).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),f.Doc=f.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,f.create("svg")),t.appendChild(this.node)),this.namespace().size("100%","100%").defs())},inherit:f.Container,extend:{namespace:function(){return this.attr({xmlns:f.ns,version:"1.1"}).attr("xmlns:xlink",f.xlink,f.xmlns)},defs:function(){if(!this._defs){var t;this._defs=(t=this.node.getElementsByTagName("defs")[0])?f.adopt(t):new f.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return"#document"==this.node.parentNode.nodeName?null:this.node.parentNode}}}),f.extend(f.Doc,{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(){var t=this;return this.doSpof=!0,f.on(window,"resize",function(){t.spof()}),this.spof()}}),f.Shape=f.invent({create:function(t){this.constructor.call(this,t)},inherit:f.Element}),f.Symbol=f.invent({create:"symbol",inherit:f.Container,construct:{symbol:function(){return this.defs().put(new f.Symbol)}}}),f.Use=f.invent({create:"use",inherit:f.Shape,extend:{element:function(t){return this.target=t,this.attr("href","#"+t,f.xlink)}},construct:{use:function(t){return this.put(new f.Use).element(t)}}}),f.Rect=f.invent({create:"rect",inherit:f.Shape,construct:{rect:function(t,e){return this.put((new f.Rect).size(t,e))}}}),f.Circle=f.invent({create:"circle",inherit:f.Shape,construct:{circle:function(t){return this.put(new f.Circle).rx(new f.Number(t).divide(2)).move(0,0)}}}),f.extend(f.Circle,f.FX,{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),f.Ellipse=f.invent({create:"ellipse",inherit:f.Shape,construct:{ellipse:function(t,e){return this.put(new f.Ellipse).size(t,e).move(0,0)}}}),f.extend(f.Ellipse,f.Rect,f.FX,{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),f.extend(f.Circle,f.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 f.Number(t).divide(this.trans.scaleX))},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",new f.Number(t).divide(this.trans.scaleY))},width:function(t){return null==t?2*this.rx():this.rx(new f.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new f.Number(t).divide(2))},size:function(t,e){var i=r(this.bbox(),t,e);return this.rx(new f.Number(i.width).divide(2)).ry(new f.Number(i.height).divide(2))}}),f.Line=f.invent({create:"line",inherit:f.Shape,extend:{array:function(){return this._array=new f.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,i,n){return t="number"==typeof t?{x1:t,y1:e,x2:i,y2:n}:(this._array=new f.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 f.Line).plot(t,e,i,n)}}}),f.Polyline=f.invent({create:"polyline",inherit:f.Shape,construct:{polyline:function(t){return this.put(new f.Polyline).plot(t) -}}}),f.Polygon=f.invent({create:"polygon",inherit:f.Shape,construct:{polygon:function(t){return this.put(new f.Polygon).plot(t)}}}),f.extend(f.Polyline,f.Polygon,{array:function(){return this._array||(this._array=new f.PointArray(this.attr("points")))},plot:function(t){return this.attr("points",this._array=new f.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))}}),f.extend(f.Line,f.Polyline,f.Polygon,{morphArray:f.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)}}),f.Path=f.invent({create:"path",inherit:f.Shape,extend:{morphArray:f.PathArray,array:function(){return this._array||(this._array=new f.PathArray(this.attr("d")))},plot:function(t){return this.attr("d",this._array=new f.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 f.Path).plot(t)}}}),f.Image=f.invent({create:"image",inherit:f.Shape,extend:{load:function(t){if(!t)return this;var e=this,i=document.createElement("img");return i.onload=function(){var n=e.doc(f.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,f.xlink)},loaded:function(t){return this._loaded=t,this}},construct:{image:function(t,e,i){return this.put(new f.Image).load(t).size(e||0,i||e||0)}}}),f.Text=f.invent({create:function(){this.constructor.call(this,f.create("text")),this._leading=new f.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",f.defaults.attrs["font-family"])},inherit:f.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 f.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 f.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 f.Text).text(t)},plain:function(t){return this.put(new f.Text).plain(t)}}}),f.Tspan=f.invent({create:"tspan",inherit:f.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(f.Text);return this.newLined=!0,this.dy(t._leading*t.attr("font-size")).attr("x",t.x())}}}),f.extend(f.Text,f.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 f.Tspan;return this._build===!1&&this.clear(),e.appendChild(i.node),this instanceof f.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 f.Text&&(delete this.lines,this.lines=new f.Set,this.content=""),this},length:function(){return this.node.getComputedTextLength()}}),f.registerEvent("rebuild"),f.TextPath=f.invent({create:"textPath",inherit:f.Element,parent:f.Text,construct:{path:function(t){for(this.textPath=new f.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,f.xlink),this},plot:function(t){return this.track&&this.track.plot(t),this}}}),f.Nested=f.invent({create:function(){this.constructor.call(this,f.create("svg")),this.style("overflow","visible")},inherit:f.Container,construct:{nested:function(){return this.put(new f.Nested)}}}),f.A=f.invent({create:"a",inherit:f.Container,extend:{to:function(t){return this.attr("href",t,f.xlink)},show:function(t){return this.attr("show",t,f.xlink)},target:function(t){return this.attr("target",t)}},construct:{link:function(t){return this.put(new f.A).to(t)}}}),f.extend(f.Element,{linkTo:function(t){var e=new f.A;return"function"==typeof t?t.call(e,e):e.to(t),this.parent().put(e).put(this)}}),f.Marker=f.invent({create:"marker",inherit:f.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)}}}),f.extend(f.Defs,{marker:function(t,e,i){return this.put(new f.Marker).size(t,e).ref(t/2,e/2).viewbox(0,0,t,e).attr("orient","auto").update(i)}}),f.extend(f.Line,f.Polyline,f.Polygon,f.Path,{marker:function(t,e,i,n){var r=["marker"];return"all"!=t&&r.push(t),r=r.join("-"),t=arguments[1]instanceof f.Marker?arguments[1]:this.doc().marker(e,i,n),this.attr(r,t)}});var d={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||f.Color.isRgb(i)||i&&"function"==typeof i.fill)this.attr(t,i);else for(e=d[t].length-1;e>=0;e--)null!=i[d[t][e]]&&this.attr(d.prefix(t,d[t][e]),i[d[t][e]]);return this},f.extend(f.Element,f.FX,i)}),f.extend(f.Element,f.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)}}),f.extend(f.Rect,f.Ellipse,f.Circle,f.FX,{radius:function(t,e){return this.rx(t).ry(null==e?t:e)}}),f.extend(f.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),f.extend(f.Parent,f.Text,f.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}}),f.Set=f.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},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 f.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 f.Set(t)}}}),f.SetFX=f.invent({create:function(t){this.set=t}}),f.Set.inherit=function(){var t,e=[];for(var t in f.Shape.prototype)"function"==typeof f.Shape.prototype[t]&&"function"!=typeof f.Set.prototype[t]&&e.push(t);e.forEach(function(t){f.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 f.SetFX(this)):this}}),e=[];for(var t in f.FX.prototype)"function"==typeof f.FX.prototype[t]&&"function"!=typeof f.SetFX.prototype[t]&&e.push(t);e.forEach(function(t){f.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}})},f.extend(f.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}}),f.extend(f.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={})}}),f.get=function(t){var e=document.getElementById(l(t)||t);return e?f.adopt(e):void 0},f.select=function(t,e){return new f.Set(f.utils.map((e||document).querySelectorAll(t),function(t){return f.adopt(t)}))},f.extend(f.Parent,{select:function(t){return f.select(t,this.node)}}),"function"==typeof define&&define.amd?define(function(){return f}):"undefined"!=typeof exports&&(exports.SVG=f),window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||function(t){window.setTimeout(t,1e3/60)}}(),"function"!=typeof c&&(c.prototype=window.Event.prototype,window.CustomEvent=c)}).call(this); \ No newline at end of file +/* svg.js 1.0.0-rc.10-19-g7cc2d36 - svg inventor adopter regex utilities default color array pointarray patharray number viewbox element boxes matrix attr transform style parent container transporter fx relative event defs group arrange mask clip gradient pattern doc spof shape symbol use rect ellipse line poly pointed path image text textpath nested hyperlink marker sugar set data memory selector loader helpers polyfill - svgjs.com/license */ +(function(){function n(e){return e.toLowerCase().replace(/-(.)/g,function(e,t){return t.toUpperCase()})}function r(e){return e.charAt(0).toUpperCase()+e.slice(1)}function i(e){return e.length==4?["#",e.substring(1,2),e.substring(1,2),e.substring(2,3),e.substring(2,3),e.substring(3,4),e.substring(3,4)].join(""):e}function s(e){var t=e.toString(16);return t.length==1?"0"+t:t}function o(e,t,n){if(t==null||n==null)n==null?n=e.height/e.width*t:t==null&&(t=e.width/e.height*n);return{width:t,height:n}}function u(e,t){return{x:t.x*e.a+t.y*e.c+0,y:t.x*e.b+t.y*e.d+0}}function a(e){return{a:e[0],b:e[1],c:e[2],e:e[3],f:e[4],g:e[5]}}function f(t,n){return typeof t.from=="number"?t.from+(t.to-t.from)*n:t instanceof e.Color||t instanceof e.Number?t.at(n):n<1?t.from:t.to}function l(e){for(var t=0,n=e.length,r="";t=0;n--)t.childNodes[n]instanceof SVGElement&&c(t.childNodes[n]);return e.adopt(t).id(e.eid(t.nodeName))}function h(e){return e.x2=e.x+e.width,e.y2=e.y+e.height,e.cx=e.x+e.width/2,e.cy=e.y+e.height/2,e}function p(e){if(e.matrix){var t=e.matrix.replace(/\s/g,"").split(",");t.length==6&&(e.a=parseFloat(t[0]),e.b=parseFloat(t[1]),e.c=parseFloat(t[2]),e.d=parseFloat(t[3]),e.e=parseFloat(t[4]),e.f=parseFloat(t[5]))}return e}function d(t){var n=t.toString().match(e.regex.reference);if(n)return n[1]}var e=this.SVG=function(t){if(e.supported)return t=new e.Doc(t),e.parser||e.prepare(t),t};e.ns="http://www.w3.org/2000/svg",e.xmlns="http://www.w3.org/2000/xmlns/",e.xlink="http://www.w3.org/1999/xlink",e.did=1e3,e.eid=function(t){return"Svgjs"+r(t)+e.did++},e.create=function(e){var t=document.createElementNS(this.ns,e);return t.setAttribute("id",this.eid(e)),t},e.extend=function(){var t,n,r,i;t=[].slice.call(arguments),n=t.pop();for(i=t.length-1;i>=0;i--)if(t[i])for(r in n)t[i].prototype[r]=n[r];e.Set&&e.Set.inherit&&e.Set.inherit()},e.prepare=function(t){var n=document.getElementsByTagName("body")[0],r=(n?new e.Doc(n):t.nested()).size(2,0),i=e.create("path");r.node.appendChild(i),e.parser={body:n||t.parent(),draw:r.style("opacity:0;position:fixed;left:100%;top:100%;overflow:hidden"),poly:r.polyline().node,path:i}},e.supported=function(){return!!document.createElementNS&&!!document.createElementNS(e.ns,"svg").createSVGRect}();if(!e.supported)return!1;e.invent=function(t){var n=typeof t.create=="function"?t.create:function(){this.constructor.call(this,e.create(t.create))};return t.inherit&&(n.prototype=new t.inherit),t.extend&&e.extend(n,t.extend),t.construct&&e.extend(t.parent||e.Container,t.construct),n},e.adopt=function(t){if(t.instance)return t.instance;var n;return t.nodeName=="svg"?n=t.parentNode instanceof SVGElement?new e.Nested:new e.Doc:t.nodeName=="lineairGradient"?n=new e.Gradient("lineair"):t.nodeName=="radialGradient"?n=new e.Gradient("radial"):e[r(t.nodeName)]?n=new(e[r(t.nodeName)]):n=new e.Element(t),n.type=t.nodeName,n.node=t,t.instance=n,n instanceof e.Doc&&n.namespace().defs(),n},e.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]+$/},e.utils={map:function(e,t){var n,r=e.length,i=[];for(n=0;n1?1:t,new e.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}}),e.Color.test=function(t){return t+="",e.regex.isHex.test(t)||e.regex.isRgb.test(t)},e.Color.isRgb=function(e){return e&&typeof e.r=="number"&&typeof e.g=="number"&&typeof e.b=="number"},e.Color.isColor=function(t){return e.Color.isRgb(t)||e.Color.test(t)},e.Array=function(e,t){e=(e||[]).valueOf(),e.length==0&&t&&(e=t.valueOf()),this.value=this.parse(e)},e.extend(e.Array,{morph:function(e){this.destination=this.parse(e);if(this.value.length!=this.destination.length){var t=this.value[this.value.length-1],n=this.destination[this.destination.length-1];while(this.value.length>this.destination.length)this.destination.push(n);while(this.value.length=0;r--)this.value[r]=[this.value[r][0]+e,this.value[r][1]+t];return this},size:function(e,t){var n,r=this.bbox();for(n=this.value.length-1;n>=0;n--)this.value[n][0]=(this.value[n][0]-r.x)*e/r.width+r.x,this.value[n][1]=(this.value[n][1]-r.y)*t/r.height+r.y;return this},bbox:function(){return e.parser.poly.setAttribute("points",this.toString()),e.parser.poly.getBBox()}}),e.PathArray=function(e,t){this.constructor.call(this,e,t||[["M",0,0]])},e.PathArray.prototype=new e.Array,e.extend(e.PathArray,{toString:function(){return l(this.value)},move:function(e,t){var n=this.bbox();e-=n.x,t-=n.y;if(!isNaN(e)&&!isNaN(t))for(var r,i=this.value.length-1;i>=0;i--)r=this.value[i][0],r=="M"||r=="L"||r=="T"?(this.value[i][1]+=e,this.value[i][2]+=t):r=="H"?this.value[i][1]+=e:r=="V"?this.value[i][1]+=t:r=="C"||r=="S"||r=="Q"?(this.value[i][1]+=e,this.value[i][2]+=t,this.value[i][3]+=e,this.value[i][4]+=t,r=="C"&&(this.value[i][5]+=e,this.value[i][6]+=t)):r=="A"&&(this.value[i][6]+=e,this.value[i][7]+=t);return this},size:function(e,t){var n,r,i=this.bbox();for(n=this.value.length-1;n>=0;n--)r=this.value[n][0],r=="M"||r=="L"||r=="T"?(this.value[n][1]=(this.value[n][1]-i.x)*e/i.width+i.x,this.value[n][2]=(this.value[n][2]-i.y)*t/i.height+i.y):r=="H"?this.value[n][1]=(this.value[n][1]-i.x)*e/i.width+i.x:r=="V"?this.value[n][1]=(this.value[n][1]-i.y)*t/i.height+i.y:r=="C"||r=="S"||r=="Q"?(this.value[n][1]=(this.value[n][1]-i.x)*e/i.width+i.x,this.value[n][2]=(this.value[n][2]-i.y)*t/i.height+i.y,this.value[n][3]=(this.value[n][3]-i.x)*e/i.width+i.x,this.value[n][4]=(this.value[n][4]-i.y)*t/i.height+i.y,r=="C"&&(this.value[n][5]=(this.value[n][5]-i.x)*e/i.width+i.x,this.value[n][6]=(this.value[n][6]-i.y)*t/i.height+i.y)):r=="A"&&(this.value[n][1]=this.value[n][1]*e/i.width,this.value[n][2]=this.value[n][2]*t/i.height,this.value[n][6]=(this.value[n][6]-i.x)*e/i.width+i.x,this.value[n][7]=(this.value[n][7]-i.y)*t/i.height+i.y);return this},parse:function(t){if(t instanceof e.PathArray)return t.valueOf();var n,r,i,s,o,u,a,f,c,h,p,d=0,v=0;e.parser.path.setAttribute("d",typeof t=="string"?t:l(t)),p=e.parser.path.pathSegList;for(n=0,r=p.numberOfItems;ni/s?this.height/s:this.width/i,this.x=n,this.y=r,this.width=i,this.height=s)},e.extend(e.ViewBox,{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height}}),e.Element=e.invent({create:function(t){this._stroke=e.defaults.attrs.stroke;if(this.node=t)this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke},extend:{x:function(t){return t!=null&&(t=new e.Number(t),t.value/=this.ctm().extract().scaleX),this.attr("x",t)},y:function(t){return t!=null&&(t=new e.Number(t),t.value/=this.ctm().extract().scaleY),this.attr("y",t)},cx:function(e){return e==null?this.x()+this.width()/2:this.x(e-this.width()/2)},cy:function(e){return e==null?this.y()+this.height()/2:this.y(e-this.height()/2)},move:function(e,t){return this.x(e).y(t)},center:function(e,t){return this.cx(e).cy(t)},width:function(e){return this.attr("width",e)},height:function(e){return this.attr("height",e)},size:function(t,n){var r=o(this.bbox(),t,n);return this.width(new e.Number(r.width)).height(new e.Number(r.height))},clone:function(){return c(this.node.cloneNode(!0))},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(e){return this.after(e).remove(),e},addTo:function(e){return e.put(this)},putIn:function(e){return e.add(this)},id:function(e){return this.attr("id",e)},inside:function(e,t){var n=this.bbox();return e>n.x&&t>n.y&&e=0;t--)this[v[t]]=typeof e[v[t]]=="number"?e[v[t]]:n[v[t]]},extend:{extract:function(){var e=u(this,{x:0,y:1}),t=u(this,{x:1,y:0});return{x:this.e,y:this.f,skewX:180/Math.PI*Math.atan2(e.y,e.x)-90,skewY:180/Math.PI*Math.atan2(t.y,t.x),scaleX:Math.sqrt(this.a*this.a+this.b*this.b),scaleY:Math.sqrt(this.c*this.c+this.d*this.d),rotation:this.skewX}},multiply:function(t){return new e.Matrix(this.native().multiply(t.native()))},inverse:function(){return new e.Matrix(this.native().inverse())},translate:function(t,n){return new e.Matrix(this.native().translate(t||0,n||0))},scale:function(t,n,r,i){return n==null?new e.Matrix(this.native().scale(t)):new e.Matrix(this.native().scaleNonUniform(t,n))},rotate:function(t,n,r){return n==null?new e.Matrix(this.native().rotate(t)):(t=e.utils.radians(t),new e.Matrix(1,0,0,1,n,r))},flip:function(t){return new e.Matrix(this.native()["flip"+t.toUpperCase()]())},skew:function(t,n){return new e.Matrix(this.native().skewX(t||0).skewY(n||0))},"native":function(){var t,n=e.parser.draw.node.createSVGMatrix();for(t=v.length-1;t>=0;t--)n[v[t]]=this[v[t]];return n},toString:function(){return"matrix("+[this.a,this.b,this.c,this.d,this.e,this.f].join()+")"}},parent:e.Element,construct:{ctm:function(){return new e.Matrix(this)}}}),e.extend(e.Element,{attr:function(t,n,r){if(t==null){t={},n=this.node.attributes;for(r=n.length-1;r>=0;r--)t[n[r].nodeName]=e.regex.isNumber.test(n[r].nodeValue)?parseFloat(n[r].nodeValue):n[r].nodeValue;return t}if(typeof t=="object")for(n in t)this.attr(n,t[n]);else if(n===null)this.node.removeAttribute(t);else{if(n==null)return n=this.node.getAttribute(t),n==null?e.defaults.attrs[t]:e.regex.isNumber.test(n)?parseFloat(n):n;t=="stroke-width"?this.attr("stroke",parseFloat(n)>0?this._stroke:null):t=="stroke"&&(this._stroke=n);if(t=="fill"||t=="stroke")e.regex.isImage.test(n)&&(n=this.doc().defs().image(n,0,0)),n instanceof e.Image&&(n=this.doc().defs().pattern(0,0,function(){this.add(n)}));typeof n=="number"?n=new e.Number(n):e.Color.isColor(n)?n=new e.Color(n):Array.isArray(n)&&(n=new e.Array(n)),t=="leading"?this.leading&&this.leading(n):typeof r=="string"?this.node.setAttributeNS(r,t,n.toString()):this.node.setAttribute(t,n.toString()),this.rebuild&&(t=="font-size"||t=="x")&&this.rebuild(t,n)}return this}}),e.extend(e.Element,{transform:function(t){if(t==null)return this.ctm().extract();var n=new e.Matrix(this);if(t.a!=null)n=n.multiply(new e.Matrix(t));else if(t.rotation)n=n.rotate(t.rotation,t.cx==null?this.bbox().cx:t.cx,t.cy==null?this.bbox().cy:t.cy);else if(t.scale!=null||t.scaleX!=null||t.scaleY!=null)n=n.scale(t.scale!=null?t.scale:t.scaleX!=null?t.scaleX:1,t.scale!=null?t.scale:t.scaleY!=null?t.scaleY:1,t.cx!=null?t.cx:this.bbox().x,t.cy!=null?t.cy:this.bbox().y);else if(t.skewX||t.skewY)n=n.skew(t.skewX,t.skewY);else if(t.x||t.y)n=n.translate(t.x,t.y);return console.log(t,n),this.attr("transform",n)},untransform:function(){return this.attr("transform",null)}}),e.extend(e.Element,{style:function(t,r){if(arguments.length==0)return this.node.style.cssText||"";if(arguments.length<2)if(typeof t=="object")for(r in t)this.style(r,t[r]);else{if(!e.regex.isCss.test(t))return this.node.style[n(t)];t=t.split(";");for(var i=0;i=0},index:function(e){return this.children().indexOf(e)},get:function(e){return this.children()[e]},first:function(){return this.children()[0]},last:function(){return this.children()[this.children().length-1]},each:function(t,n){var r,i,s=this.children();for(r=0,i=s.length;r/g,"<$1$2>"),t.innerHTML=""+e+"";for(var n=t.firstChild.childNodes.length-1;n>=0;n--)t.firstChild.childNodes[n].nodeType==1&&this.node.appendChild(t.firstChild.childNodes[n]);return this}var r=this.node.cloneNode(!0);return t.appendChild(r),t.innerHTML}}),e.FX=e.invent({create:function(e){this.target=e},extend:{animate:function(t,n,r){var i,s,o,u,a=this.target,l=this;return typeof t=="object"&&(r=t.delay,n=t.ease,t=t.duration),t=t=="="?t:t==null?1e3:(new e.Number(t)).valueOf(),n=n||"<>",l.to=function(e){var t;e=e<0?0:e>1?1:e;if(i==null){i=[];for(u in l.attrs)i.push(u);if(a.morphArray&&(l._plot||i.indexOf("points")>-1)){var r,c=new a.morphArray(l._plot||l.attrs.points||a.array);l._size&&c.size(l._size.width.to,l._size.height.to),r=c.bbox(),l._x?c.move(l._x.to,r.y):l._cx&&c.move(l._cx.to-r.width/2,r.y),r=c.bbox(),l._y?c.move(r.x,l._y.to):l._cy&&c.move(r.x,l._cy.to-r.height/2),delete l._x,delete l._y,delete l._cx,delete l._cy,delete l._size,l._plot=a.array.morph(c)}}if(s==null){s=[];for(u in l.trans)s.push(u)}if(o==null){o=[];for(u in l.styles)o.push(u)}e=n=="<>"?-Math.cos(e*Math.PI)/2+.5:n==">"?Math.sin(e*Math.PI/2):n=="<"?-Math.cos(e*Math.PI/2)+1:n=="-"?e:typeof n=="function"?n(e):e,l._plot?a.plot(l._plot.at(e)):(l._x?a.x(l._x.at(e)):l._cx&&a.cx(l._cx.at(e)),l._y?a.y(l._y.at(e)):l._cy&&a.cy(l._cy.at(e)),l._size&&a.size(l._size.width.at(e),l._size.height.at(e))),l._viewbox&&a.viewbox(l._viewbox.x.at(e),l._viewbox.y.at(e),l._viewbox.width.at(e),l._viewbox.height.at(e)),l._leading&&a.leading(l._leading.at(e));for(t=i.length-1;t>=0;t--)a.attr(i[t],f(l.attrs[i[t]],e));for(t=s.length-1;t>=0;t--)a.transform(s[t],f(l.trans[s[t]],e));for(t=o.length-1;t>=0;t--)a.style(o[t],f(l.styles[o[t]],e));l._during&&l._during.call(a,e,function(t,n){return f({from:t,to:n},e)})},typeof t=="number"&&(this.timeout=setTimeout(function(){var i=(new Date).getTime();l.situation={interval:1e3/60,start:i,play:!0,finish:i+t,duration:t},l.render=function(){if(l.situation.play===!0){var i=(new Date).getTime(),s=i>l.situation.finish?1:(i-l.situation.start)/t;l.to(s),i>l.situation.finish?(l._plot&&a.plot((new e.PointArray(l._plot.destination)).settle()),l._loop===!0||typeof l._loop=="number"&&l._loop>1?(typeof l._loop=="number"&&--l._loop,l.animate(t,n,r)):l._after?l._after.apply(a,[l]):l.stop()):requestAnimFrame(l.render)}else requestAnimFrame(l.render)},l.render()},(new e.Number(r)).valueOf())),this},bbox:function(){return this.target.bbox()},attr:function(t,n){if(typeof t=="object")for(var r in t)this.attr(r,t[r]);else{var i=this.target.attr(t);this.attrs[t]=e.Color.isColor(i)?(new e.Color(i)).morph(n):e.regex.unit.test(i)?(new e.Number(i)).morph(n):{from:i,to:n}}return this},transform:function(e,t){if(arguments.length==1){e=p(e),delete e.matrix;for(t in e)this.trans[t]={from:this.target.trans[t],to:e[t]}}else{var n={};n[e]=t,this.transform(n)}return this},style:function(e,t){if(typeof e=="object")for(var n in e)this.style(n,e[n]);else this.styles[e]={from:this.target.style(e),to:t};return this},x:function(t){return this._x=(new e.Number(this.target.x())).morph(t),this},y:function(t){return this._y=(new e.Number(this.target.y())).morph(t),this},cx:function(t){return this._cx=(new e.Number(this.target.cx())).morph(t),this},cy:function(t){return this._cy=(new e.Number(this.target.cy())).morph(t),this},move:function(e,t){return this.x(e).y(t)},center:function(e,t){return this.cx(e).cy(t)},size:function(t,n){if(this.target instanceof e.Text)this.attr("font-size",t);else{var r=this.target.bbox();this._size={width:(new e.Number(r.width)).morph(t),height:(new e.Number(r.height)).morph(n)}}return this},plot:function(e){return this._plot=e,this},leading:function(t){return this.target._leading&&(this._leading=(new e.Number(this.target._leading)).morph(t)),this},viewbox:function(t,n,r,i){if(this.target instanceof e.Container){var s=this.target.viewbox();this._viewbox={x:(new e.Number(s.x)).morph(t),y:(new e.Number(s.y)).morph(n),width:(new e.Number(s.width)).morph(r),height:(new e.Number(s.height)).morph(i)}}return this},update:function(t){return this.target instanceof e.Stop&&(t.opacity!=null&&this.attr("stop-opacity",t.opacity),t.color!=null&&this.attr("stop-color",t.color),t.offset!=null&&this.attr("offset",new e.Number(t.offset))),this},during:function(e){return this._during=e,this},after:function(e){return this._after=e,this},loop:function(e){return this._loop=e||!0,this},stop:function(e){return e===!0?(this.animate(0),this._after&&this._after.apply(this.target,[this])):(clearTimeout(this.timeout),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 e=(new Date).getTime()-this.situation.pause;this.situation.finish+=e,this.situation.start+=e,this.situation.play=!0}return this}},parent:e.Element,construct:{animate:function(t,n,r){return(this.fx||(this.fx=new e.FX(this))).stop().animate(t,n,r)},stop:function(e){return this.fx&&this.fx.stop(e),this},pause:function(){return this.fx&&this.fx.pause(),this},play:function(){return this.fx&&this.fx.play(),this}}}),e.extend(e.Element,e.FX,{dx:function(e){return this.x((this.target||this).x()+e)},dy:function(e){return this.y((this.target||this).y()+e)},dmove:function(e,t){return this.dx(e).dy(t)}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","mouseenter","mouseleave","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){e.Element.prototype[t]=function(e){var n=this;return this.node["on"+t]=typeof e=="function"?function(){return e.apply(n,arguments)}:null,this}}),e.events={},e.listeners={},e.registerEvent=function(t){e.events[t]||(e.events[t]=new m(t))},e.on=function(t,n,r){var i=r.bind(t.instance||t);e.listeners[r]=i,t.addEventListener(n,i,!1)},e.off=function(t,n,r){t.removeEventListener(n,e.listeners[r],!1),delete e.listeners[r]},e.extend(e.Element,{on:function(t,n){return e.on(this.node,t,n),this},off:function(t,n){return e.off(this.node,t,n),this},fire:function(t,n){return e.events[t].detail=n,this.node.dispatchEvent(e.events[t]),delete e.events[t].detail,this}}),e.Defs=e.invent({create:"defs",inherit:e.Container}),e.G=e.invent({create:"g",inherit:e.Container,extend:{x:function(e){return e==null?this.ctm().x:this.transform("x",e)},y:function(e){return e==null?this.ctm().y:this.transform("y",e)},cx:function(e){return e==null?this.bbox().cx:this.x(e-this.bbox().width/2)},cy:function(e){return e==null?this.bbox().cy:this.y(e-this.bbox().height/2)}},construct:{group:function(){return this.put(new e.G)}}}),e.extend(e.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,n=this.parent();return n.removeElement(this).add(this,t),n instanceof e.Doc&&n.node.appendChild(n.defs().node),this},backward:function(){var e=this.position();return e>0&&this.parent().removeElement(this).add(this,e-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof e.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(e){e.remove();var t=this.position();return this.parent().add(e,t),this},after:function(e){e.remove();var t=this.position();return this.parent().add(e,t+1),this}}),e.Mask=e.invent({create:function(){this.constructor.call(this,e.create("mask")),this.targets=[]},inherit:e.Container,extend:{remove:function(){for(var e=this.targets.length-1;e>=0;e--)this.targets[e]&&this.targets[e].unmask();return delete this.targets,this.parent().removeElement(this),this}},construct:{mask:function(){return this.defs().put(new e.Mask)}}}),e.extend(e.Element,{maskWith:function(t){return this.masker=t instanceof e.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)}}),e.ClipPath=e.invent({create:function(){this.constructor.call(this,e.create("clipPath")),this.targets=[]},inherit:e.Container,extend:{remove:function(){for(var e=this.targets.length-1;e>=0;e--)this.targets[e]&&this.targets[e].unclip();return delete this.targets,this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new e.ClipPath)}}}),e.extend(e.Element,{clipWith:function(t){return this.clipper=t instanceof e.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)}}),e.Gradient=e.invent({create:function(t){this.constructor.call(this,e.create(t+"Gradient")),this.type=t},inherit:e.Container,extend:{from:function(t,n){return this.type=="radial"?this.attr({fx:new e.Number(t),fy:new e.Number(n)}):this.attr({x1:new e.Number(t),y1:new e.Number(n)})},to:function(t,n){return this.type=="radial"?this.attr({cx:new e.Number(t),cy:new e.Number(n)}):this.attr({x2:new e.Number(t),y2:new e.Number(n)})},radius:function(t){return this.type=="radial"?this.attr({r:new e.Number(t)}):this},at:function(t,n,r){return this.put(new e.Stop).update(t,n,r)},update:function(e){return this.clear(),typeof e=="function"&&e.call(this,this),this},fill:function(){return"url(#"+this.id()+")"},toString:function(){return this.fill()}},construct:{gradient:function(e,t){return this.defs().gradient(e,t)}}}),e.extend(e.Defs,{gradient:function(t,n){return this.put(new e.Gradient(t)).update(n)}}),e.Stop=e.invent({create:"stop",inherit:e.Element,extend:{update:function(t){if(typeof t=="number"||t instanceof e.Number)t={offset:arguments[0],color:arguments[1],opacity:arguments[2]};return t.opacity!=null&&this.attr("stop-opacity",t.opacity),t.color!=null&&this.attr("stop-color",t.color),t.offset!=null&&this.attr("offset",new e.Number(t.offset)),this}}}),e.Pattern=e.invent({create:"pattern",inherit:e.Container,extend:{fill:function(){return"url(#"+this.id()+")"},update:function(e){return this.clear(),typeof e=="function"&&e.call(this,this),this},toString:function(){return this.fill()}},construct:{pattern:function(e,t,n){return this.defs().pattern(e,t,n)}}}),e.extend(e.Defs,{pattern:function(t,n,r){return this.put(new e.Pattern).update(r).attr({x:0,y:0,width:t,height:n,patternUnits:"userSpaceOnUse"})}}),e.Doc=e.invent({create:function(t){t&&(t=typeof t=="string"?document.getElementById +(t):t,t.nodeName=="svg"?this.constructor.call(this,t):(this.constructor.call(this,e.create("svg")),t.appendChild(this.node)),this.namespace().size("100%","100%").defs())},inherit:e.Container,extend:{namespace:function(){return this.attr({xmlns:e.ns,version:"1.1"}).attr("xmlns:xlink",e.xlink,e.xmlns)},defs:function(){if(!this._defs){var t;(t=this.node.getElementsByTagName("defs")[0])?this._defs=e.adopt(t):this._defs=new e.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return this.node.parentNode.nodeName=="#document"?null:this.node.parentNode}}}),e.extend(e.Doc,{spof:function(){if(this.doSpof){var e=this.node.getScreenCTM();e&&this.style("left",-e.e%1+"px").style("top",-e.f%1+"px")}return this},fixSubPixelOffset:function(){var t=this;return this.doSpof=!0,e.on(window,"resize",function(){t.spof()}),this.spof()}}),e.Shape=e.invent({create:function(e){this.constructor.call(this,e)},inherit:e.Element}),e.Symbol=e.invent({create:"symbol",inherit:e.Container,construct:{symbol:function(){return this.defs().put(new e.Symbol)}}}),e.Use=e.invent({create:"use",inherit:e.Shape,extend:{element:function(t){return this.target=t,this.attr("href","#"+t,e.xlink)}},construct:{use:function(t){return this.put(new e.Use).element(t)}}}),e.Rect=e.invent({create:"rect",inherit:e.Shape,construct:{rect:function(t,n){return this.put((new e.Rect).size(t,n))}}}),e.Circle=e.invent({create:"circle",inherit:e.Shape,construct:{circle:function(t){return this.put(new e.Circle).rx((new e.Number(t)).divide(2)).move(0,0)}}}),e.extend(e.Circle,e.FX,{rx:function(e){return this.attr("r",e)},ry:function(e){return this.rx(e)}}),e.Ellipse=e.invent({create:"ellipse",inherit:e.Shape,construct:{ellipse:function(t,n){return this.put(new e.Ellipse).size(t,n).move(0,0)}}}),e.extend(e.Ellipse,e.Rect,e.FX,{rx:function(e){return this.attr("rx",e)},ry:function(e){return this.attr("ry",e)}}),e.extend(e.Circle,e.Ellipse,{x:function(e){return e==null?this.cx()-this.rx():this.cx(e+this.rx())},y:function(e){return e==null?this.cy()-this.ry():this.cy(e+this.ry())},cx:function(t){return t==null?this.attr("cx"):this.attr("cx",(new e.Number(t)).divide(this.ctm().scaleX))},cy:function(t){return t==null?this.attr("cy"):this.attr("cy",(new e.Number(t)).divide(this.ctm().scaleY))},width:function(t){return t==null?this.rx()*2:this.rx((new e.Number(t)).divide(2))},height:function(t){return t==null?this.ry()*2:this.ry((new e.Number(t)).divide(2))},size:function(t,n){var r=o(this.bbox(),t,n);return this.rx((new e.Number(r.width)).divide(2)).ry((new e.Number(r.height)).divide(2))}}),e.Line=e.invent({create:"line",inherit:e.Shape,extend:{array:function(){return new e.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,n,r,i){return arguments.length==4?t={x1:t,y1:n,x2:r,y2:i}:t=(new e.PointArray(t)).toLine(),this.attr(t)},move:function(e,t){return this.attr(this.array().move(e,t).toLine())},size:function(e,t){var n=o(this.bbox(),e,t);return this.attr(this.array().size(n.width,n.height).toLine())}},construct:{line:function(t,n,r,i){return this.put(new e.Line).plot(t,n,r,i)}}}),e.Polyline=e.invent({create:"polyline",inherit:e.Shape,construct:{polyline:function(t){return this.put(new e.Polyline).plot(t)}}}),e.Polygon=e.invent({create:"polygon",inherit:e.Shape,construct:{polygon:function(t){return this.put(new e.Polygon).plot(t)}}}),e.extend(e.Polyline,e.Polygon,{array:function(){return this._array||(this._array=new e.PointArray(this.attr("points")))},plot:function(t){return this.attr("points",this._array=new e.PointArray(t))},move:function(e,t){return this.attr("points",this.array().move(e,t))},size:function(e,t){var n=o(this.bbox(),e,t);return this.attr("points",this.array().size(n.width,n.height))}}),e.extend(e.Line,e.Polyline,e.Polygon,{morphArray:e.PointArray,x:function(e){return e==null?this.bbox().x:this.move(e,this.bbox().y)},y:function(e){return e==null?this.bbox().y:this.move(this.bbox().x,e)},width:function(e){var t=this.bbox();return e==null?t.width:this.size(e,t.height)},height:function(e){var t=this.bbox();return e==null?t.height:this.size(t.width,e)}}),e.Path=e.invent({create:"path",inherit:e.Shape,extend:{morphArray:e.PathArray,array:function(){return this._array||(this._array=new e.PathArray(this.attr("d")))},plot:function(t){return this.attr("d",this._array=new e.PathArray(t))},move:function(e,t){return this.attr("d",this.array().move(e,t))},x:function(e){return e==null?this.bbox().x:this.move(e,this.bbox().y)},y:function(e){return e==null?this.bbox().y:this.move(this.bbox().x,e)},size:function(e,t){var n=o(this.bbox(),e,t);return this.attr("d",this.array().size(n.width,n.height))},width:function(e){return e==null?this.bbox().width:this.size(e,this.bbox().height)},height:function(e){return e==null?this.bbox().height:this.size(this.bbox().width,e)}},construct:{path:function(t){return this.put(new e.Path).plot(t)}}}),e.Image=e.invent({create:"image",inherit:e.Shape,extend:{load:function(t){if(!t)return this;var n=this,r=document.createElement("img");return r.onload=function(){var i=n.doc(e.Pattern);n.width()==0&&n.height()==0&&n.size(r.width,r.height),i&&i.width()==0&&i.height()==0&&i.size(n.width(),n.height()),typeof n._loaded=="function"&&n._loaded.call(n,{width:r.width,height:r.height,ratio:r.width/r.height,url:t})},this.attr("href",r.src=this.src=t,e.xlink)},loaded:function(e){return this._loaded=e,this}},construct:{image:function(t,n,r){return this.put(new e.Image).load(t).size(n||0,r||n||0)}}}),e.Text=e.invent({create:function(){this.constructor.call(this,e.create("text")),this._leading=new e.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",e.defaults.attrs["font-family"])},inherit:e.Shape,extend:{x:function(e){return e==null?this.attr("x"):(this.textPath||this.lines.each(function(){this.newLined&&this.x(e)}),this.attr("x",e))},y:function(e){var t=this.attr("y"),n=typeof t=="number"?t-this.bbox().y:0;return e==null?typeof t=="number"?t-n:t:this.attr("y",typeof e=="number"?e+n:e)},cx:function(e){return e==null?this.bbox().cx:this.x(e-this.bbox().width/2)},cy:function(e){return e==null?this.bbox().cy:this.y(e-this.bbox().height/2)},text:function(e){if(typeof e=="undefined")return this.content;this.clear().build(!0);if(typeof e=="function")e.call(this,this);else{e=(this.content=e).split("\n");for(var t=0,n=e.length;t=0;r--)i[t[n][r]]!=null&&this.attr(t.prefix(n,t[n][r]),i[t[n][r]]);return this},e.extend(e.Element,e.FX,i)}),e.extend(e.Element,e.FX,{rotate:function(e,t,n){return this.transform({rotation:e,cx:t,cy:n})},skew:function(e,t){return this.transform({skewX:e,skewY:t})},scale:function(e,t,n,r){return this.transform({scaleX:e,scaleY:t,cx:n,cy:r})},translate:function(e,t){return this.transform({x:e,y:t})},matrix:function(t){return this.attr("transform",new e.Matrix(t))},opacity:function(e){return this.attr("opacity",e)}}),e.extend(e.Rect,e.Ellipse,e.Circle,e.FX,{radius:function(e,t){return this.rx(e).ry(t==null?e:t)}}),e.extend(e.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(e){return this.node.getPointAtLength(e)}}),e.extend(e.Parent,e.Text,e.FX,{font:function(e){for(var t in e)t=="leading"?this.leading(e[t]):t=="anchor"?this.attr("text-anchor",e[t]):t=="size"||t=="family"||t=="weight"||t=="stretch"||t=="variant"||t=="style"?this.attr("font-"+t,e[t]):this.attr(t,e[t]);return this}}),e.Set=e.invent({create:function(e){Array.isArray(e)?this.members=e:this.clear()},extend:{add:function(){var e,t,n=[].slice.call(arguments);for(e=0,t=n.length;e-1&&this.members.splice(t,1),this},each:function(e){for(var t=0,n=this.members.length;t=0},index:function(e){return this.members.indexOf(e)},get:function(e){return this.members[e]},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 e.BBox;if(this.members.length==0)return t;var n=this.members[0].rbox();return t.x=n.x,t.y=n.y,t.width=n.width,t.height=n.height,this.each(function(){t=t.merge(this.rbox())}),t}},construct:{set:function(t){return new e.Set(t)}}}),e.SetFX=e.invent({create:function(e){this.set=e}}),e.Set.inherit=function(){var t,n=[];for(var t in e.Shape.prototype)typeof e.Shape.prototype[t]=="function"&&typeof e.Set.prototype[t]!="function"&&n.push(t);n.forEach(function(t){e.Set.prototype[t]=function(){for(var n=0,r=this.members.length;n=0;e--)delete this.memory()[arguments[e]];return this},memory:function(){return this._memory||(this._memory={})}}),e.get=function(t){var n=document.getElementById(d(t)||t);if(n)return e.adopt(n)},e.select=function(t,n){return new e.Set(e.utils.map((n||document).querySelectorAll(t),function(t){return e.adopt(t)}))},e.extend(e.Parent,{select:function(t){return e.select(t,this.node)}}),typeof define=="function"&&define.amd?define(function(){return e}):typeof exports!="undefined"&&(exports.SVG=e);var v="abcdef".split("");window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame||function(e){window.setTimeout(e,1e3/60)}}();if(typeof m!="function"){function m(e,t){t=t||{bubbles:!1,cancelable:!1,detail:undefined};var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,t.bubbles,t.cancelable,t.detail),n}m.prototype=window.Event.prototype,window.CustomEvent=m}}).call(this); \ No newline at end of file diff --git a/spec/index.html b/spec/index.html index 94f13db..30899fd 100755 --- a/spec/index.html +++ b/spec/index.html @@ -56,6 +56,7 @@ + diff --git a/spec/spec/container.js b/spec/spec/container.js index 63b1a01..523167d 100755 --- a/spec/spec/container.js +++ b/spec/spec/container.js @@ -273,6 +273,12 @@ describe('Container', function() { }) describe('viewbox()', function() { + + beforeEach(function() { + draw.attr('viewBox', null) + console.log(draw.node) + }) + 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') diff --git a/spec/spec/element.js b/spec/spec/element.js index 598ab1e..0611210 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() }) @@ -15,7 +19,11 @@ describe('Element', function() { beforeEach(function() { rect = draw.rect(100,100) }) - + + afterEach(function() { + rect.remove() + }) + it('sets one attribute when two arguments are given', function() { rect.attr('fill', '#ff0066') expect(rect.node.getAttribute('fill')).toBe('#ff0066') @@ -68,19 +76,20 @@ describe('Element', function() { 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) + console.log(rect.native()) 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%') }) @@ -138,27 +147,23 @@ describe('Element', function() { describe('transform()', function() { it('gets the current transformations', function() { var rect = draw.rect(100,100) - expect(rect.transform()).toEqual(SVG.defaults.trans()) + expect(rect.transform()).toEqual(new SVG.Matrix(rect).extract()) }) it('sets 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)') - }) - it('sets 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)') + var rect = draw.rect(100,100).transform({ x: 10, y: 11 }) + expect(rect.node.getAttribute('transform')).toBe('matrix(1,0,0,1,10,11)') }) - it('sets the scaleY of and element', function() { - var rect = draw.rect(100,100).transform({ scaleY: 10 }) - expect(rect.node.getAttribute('transform')).toBe('scale(1 10)') + it('sets the scaleX and scaleY of and element', function() { + var rect = draw.rect(100,100).transform({ scaleX: 0.5, scaleY: 2 }) + expect(rect.node.getAttribute('transform')).toBe('matrix(0.5,0,0,2,0,0)') }) it('sets 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)') + var rect = draw.rect(100,100).transform({ skewX: 10 }) + expect(rect.node.getAttribute('transform')).toBe('matrix(1,0,0.17632698070846498,1,0,0)') }) it('sets the skewY of and element', function() { - var rect = draw.rect(100,100).transform({ skewY: 10 }) - expect(rect.node.getAttribute('transform')).toBe('skewY(10)') + var rect = draw.rect(100,100).transform({ skewY: -10 }) + expect(rect.node.getAttribute('transform')).toBe('matrix(1,-0.17632698070846498,0,1,0,0)') }) it('rotates the element around its centre if no rotation point is given', function() { var rect = draw.rect(100,100).transform({ rotation: 45 }) diff --git a/spec/spec/matrix.js b/spec/spec/matrix.js new file mode 100644 index 0000000..9689d81 --- /dev/null +++ b/spec/spec/matrix.js @@ -0,0 +1,61 @@ +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) + }) + it('parses translation values', function() { + expect(matrix.x).toBe(0) + expect(matrix.y).toBe(0) + }) + it('parses skew values', function() { + expect(matrix.skewX).toBe(0) + expect(matrix.skewY).toBe(0) + }) + it('parses scale values', function() { + expect(matrix.scaleX).toBe(1) + expect(matrix.scaleY).toBe(1) + }) + it('parses rotaton value', function() { + expect(matrix.rotation).toBe(0) + }) + }) + + describe('with an element given', function() { + + beforeEach(function() { + matrix = new SVG.Matrix(draw.rect(100, 100).skew(10, 20).translate(50, 50).scale(3, 2)) + }) + + it('parses the current transform matrix form an element', 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) + }) + }) + }) + +}) + + + + + + + diff --git a/spec/spec/selector.js b/spec/spec/selector.js index 8c16aa1..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() { diff --git a/src/attr.js b/src/attr.js new file mode 100644 index 0000000..d18cc90 --- /dev/null +++ b/src/attr.js @@ -0,0 +1,79 @@ +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) + + // 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/bbox.js b/src/bbox.js deleted file mode 100755 index 1632a02..0000000 --- a/src/bbox.js +++ /dev/null @@ -1,55 +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 - - return boxProperties(b) - } - -}) \ No newline at end of file diff --git a/src/boxes.js b/src/boxes.js new file mode 100755 index 0000000..e3b7618 --- /dev/null +++ b/src/boxes.js @@ -0,0 +1,144 @@ +SVG.BBox = SVG.invent({ + // Initialize + create: 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) { + // Get current extracted transformations + var t = new SVG.Matrix(element).extract() + + // Find native bbox + if (element.node.getBBox) + box = element.node.getBBox() + // Mimic bbox + else + 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 + t.x + this.y = box.y + t.y + + // Plain width and height + this.width = box.width * t.scaleX + this.height = box.height * t.scaleY + } + + // 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.RBox = SVG.invent({ + // Initialize + create: 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.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.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 += window.scrollX + this.y += window.scrollY + + // Add center, right and bottom + fullBox(this) + } + + // define Parent +, parent: SVG.Element + + // Constructor +, construct: { + // Get rect box + rbox: function() { + return new SVG.RBox(this) + } + } + +}) + +// Add universal merge method +;[SVG.BBox, 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 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 + + return fullBox(b) + } + + }) + +}) diff --git a/src/element.js b/src/element.js index 3d7de12..6310b6b 100755 --- a/src/element.js +++ b/src/element.js @@ -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.ctm().extract().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.ctm().extract().scaleY } return this.attr('y', y) } @@ -90,139 +90,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) { - // 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) - - // 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(t, v) { - // Get a transformation at a given position - if (typeof t === 'number') { - - } - - 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() @@ -271,10 +142,9 @@ SVG.Element = SVG.invent({ // Remove class from the node , removeClass: function(name) { if (this.hasClass(name)) { - var array = this.classes().filter(function(c) { + this.attr('class', this.classes().filter(function(c) { return c != name - }) - this.attr('class', array.join(' ')) + }).join(' ')) } return this @@ -299,5 +169,13 @@ SVG.Element = SVG.invent({ 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 + } } }) diff --git a/src/ellipse.js b/src/ellipse.js index aab8087..f4ab6c9 100755 --- a/src/ellipse.js +++ b/src/ellipse.js @@ -64,11 +64,11 @@ SVG.extend(SVG.Circle, SVG.Ellipse, { } // 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.ctm().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.ctm().scaleY)) } // Set width of element , width: function(width) { diff --git a/src/group.js b/src/group.js index cfb99ac..a7ec2e0 100755 --- a/src/group.js +++ b/src/group.js @@ -9,11 +9,11 @@ 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.ctm().x : this.transform('x', x) } // Move over y-axis , y: function(y) { - return y == null ? this.trans.y : this.transform('y', y) + return y == null ? this.ctm().y : this.transform('y', y) } // Move by center over x-axis , cx: function(x) { diff --git a/src/helpers.js b/src/helpers.js index 9411e5e..11f5017 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,148 +1,164 @@ // Convert dash-separated-string to camelCase function camelCase(s) { - return s.toLowerCase().replace(/-(.)/g, function(m, g) { - return g.toUpperCase() - }) + 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) + 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 + 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 + 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 (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 - } + 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 + } +} + +// Delta transform point +function deltaTransformPoint(matrix, point) { + return { + x: point.x * matrix.a + point.y * matrix.c + 0 + , y: point.x * matrix.b + point.y * matrix.d + 0 + } +} + +// Map matrix array to object +function arrayToMatrix(a) { + return { a: a[0], b: a[1], c: a[2], e: a[3], f: a[4], g: a[5] } } // 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 + /* 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 } // 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][3] - s += ' ' - s += a[i][4] - - if (a[i][5] != null) { - s += ' ' - s += a[i][5] - s += ' ' - s += a[i][6] - - if (a[i][7] != null) { - s += ' ' - s += a[i][7] - } - } - } - } - } - } - - return s + ' ' + 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][3] + s += ' ' + s += a[i][4] + + if (a[i][5] != null) { + s += ' ' + s += a[i][5] + s += ' ' + s += a[i][6] + + if (a[i][7] != null) { + s += ' ' + s += a[i][7] + } + } + } + } + } + } + + 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]) + // 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)) + return SVG.adopt(node).id(SVG.eid(node.nodeName)) } // 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 +function fullBox(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 - return b + return b } // 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 + 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) + var m = url.toString().match(SVG.regex.reference) - if (m) return m[1] + if (m) return m[1] } +// Create matrix array for looping +var abcdef = 'abcdef'.split('') + // Shim layer with setTimeout fallback by Paul Irish window.requestAnimFrame = (function(){ - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.msRequestAnimationFrame || - function (c) { window.setTimeout(c, 1000 / 60) } + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.msRequestAnimationFrame || + function (c) { window.setTimeout(c, 1000 / 60) } })() \ No newline at end of file diff --git a/src/line.js b/src/line.js index d76f891..0fcf63f 100755 --- a/src/line.js +++ b/src/line.js @@ -9,17 +9,17 @@ SVG.Line = SVG.invent({ , extend: { // Get array array: function() { - return (this._array = new SVG.PointArray([ + 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 (typeof x1 === 'number') + if (arguments.length == 4) x1 = { x1: x1, y1: y1, x2: x2, y2: y2 } else - x1 = (this._array = new SVG.PointArray(x1)).toLine() + x1 = new SVG.PointArray(x1).toLine() return this.attr(x1) } diff --git a/src/matrix.js b/src/matrix.js index 2202011..fb61bf0 100644 --- a/src/matrix.js +++ b/src/matrix.js @@ -1,3 +1,113 @@ -SVG.Matrix = function() { +SVG.Matrix = SVG.invent({ + // Initialize + create: function(source) { + var i, base = arrayToMatrix([1, 0, 0, 1, 0, 0]) -} \ No newline at end of file + // Ensure source as object + source = source.node && source.node.getCTM ? + source.node.getCTM() : + typeof source === 'string' ? + arrayToMatrix(source.replace(/\s/g, '').split(',')) : + 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]] = typeof source[abcdef[i]] === 'number' ? + source[abcdef[i]] : base[abcdef[i]] + + } + + // Add methods +, extend: { + // Extract individual transformations + extract: function() { + // Find transform points + var px = deltaTransformPoint(this, { x: 0, y: 1 }) + , py = deltaTransformPoint(this, { x: 1, y: 0 }) + + return { + // Translation + x: this.e + , y: this.f + // Skew + , skewX: 180 / Math.PI * Math.atan2(px.y, px.x) - 90 + , 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: this.skewX + } + } + // Multiply + , multiply: function(matrix) { + return new SVG.Matrix(this.native().multiply(matrix.native())) + } + // Inverse + , inverse: function() { + return new SVG.Matrix(this.native().inverse()) + } + // Translate + , translate: function(x, y) { + return new SVG.Matrix(this.native().translate(x || 0, y || 0)) + } + // Scale + , scale: function(x, y, cx, cy) { + if (y == null) + return new SVG.Matrix(this.native().scale(x)) + else + return new SVG.Matrix(this.native().scaleNonUniform(x, y)) + } + // Rotate + , rotate: function(d, x, y) { + // Fall back to native rotate method + if (x == null) return new SVG.Matrix(this.native().rotate(d)) + + // Convert degrees to radians + d = SVG.utils.radians(d) + + return new SVG.Matrix(1, 0, 0, 1, x, y) + //.multiply(new SVG.Matrix(Math.cos(d), Math.sin(d), -Math.sin(d), Math.cos(d), 0, 0)) + //.multiply(new SVG.Matrix(1, 0, 0, 1, -x, -y)) + } + // Flip + , flip: function(a) { + return new SVG.Matrix(this.native()['flip' + a.toUpperCase()]()) + } + // Skew + , skew: function(x, y) { + return new SVG.Matrix(this.native().skewX(x || 0).skewY(y || 0)) + } + // Convert this to SVGMatrix + , native: function() { + // Create new matrix + var i, matrix = SVG.parser.draw.node.createSVGMatrix() + + // Update with current values + for (i = abcdef.length - 1; i >= 0; i--) + matrix[abcdef[i]] = this[abcdef[i]] + + return matrix + } + // Convert array to string + , toString: function() { + return 'matrix(' + [this.a, this.b, this.c, this.d, this.e, this.f].join() + ')' + } + } + + // 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/patharray.js b/src/patharray.js index 746cdc4..e127576 100755 --- a/src/patharray.js +++ b/src/patharray.js @@ -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/rbox.js b/src/rbox.js deleted file mode 100755 index 6ede8ae..0000000 --- a/src/rbox.js +++ /dev/null @@ -1,73 +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.parent && (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 += window.scrollX - this.y += window.scrollY - - /* 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 - - return boxProperties(b) - } - -}) diff --git a/src/regex.js b/src/regex.js index 13f62d8..8e974a9 100755 --- a/src/regex.js +++ b/src/regex.js @@ -1,39 +1,39 @@ // Storage for regular expressions SVG.regex = { /* parse unit value */ - unit: /^(-?[\d\.]+)([a-z%]{0,2})$/ + unit: /^(-?[\d\.]+)([a-z%]{0,2})$/ /* parse hex value */ -, hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i +, hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i /* parse rgb value */ -, rgb: /rgb\((\d+),(\d+),(\d+)\)/ +, rgb: /rgb\((\d+),(\d+),(\d+)\)/ /* parse reference id */ -, reference: /#([a-z0-9\-_]+)/i +, reference: /#([a-z0-9\-_]+)/i /* test hex value */ -, isHex: /^#[a-f0-9]{3,6}$/i +, isHex: /^#[a-f0-9]{3,6}$/i /* test rgb value */ -, isRgb: /^rgb\(/ +, isRgb: /^rgb\(/ /* test css declaration */ -, isCss: /[^:]+:[^;]+;?/ +, isCss: /[^:]+:[^;]+;?/ /* test for blank string */ -, isBlank: /^(\s+)?$/ +, isBlank: /^(\s+)?$/ /* test for numeric string */ -, isNumber: /^-?[\d\.]+$/ +, isNumber: /^-?[\d\.]+$/ /* test for percent value */ -, isPercent: /^-?[\d\.]+%$/ +, isPercent: /^-?[\d\.]+%$/ /* test for image url */ -, isImage: /\.(jpg|jpeg|png|gif)(\?[^=]+.*)?/i +, isImage: /\.(jpg|jpeg|png|gif)(\?[^=]+.*)?/i /* test for namespaced event */ -, isEvent: /^[\w]+:[\w]+$/ +, isEvent: /^[\w]+:[\w]+$/ } \ No newline at end of file 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 493c6c7..e759812 100755 --- a/src/sugar.js +++ b/src/sugar.js @@ -30,37 +30,24 @@ var sugar = { SVG.extend(SVG.Element, SVG.FX, { // Rotation - rotate: function(deg, x, y) { - return this.transform({ - rotation: deg || 0 - , cx: x - , cy: y - }) + rotate: function(d, cx, cy) { + return this.transform({ rotation: d, cx: cx, cy: cy }) } // Skew , skew: function(x, y) { - return this.transform({ - skewX: x || 0 - , skewY: y || 0 - }) + return this.transform({ skewX: x, skewY: y }) } // Scale -, scale: function(x, y) { - return this.transform({ - scaleX: x - , scaleY: y == null ? x : y - }) +, scale: function(x, y, cx, cy) { + return this.transform({ scaleX: x, scaleY: y, cx: cx, cy: cy }) } // Translate , translate: function(x, y) { - return this.transform({ - x: x - , y: y - }) + return this.transform({ x: x, y: y }) } // Matrix , matrix: function(m) { - return this.transform({ matrix: m }) + return this.attr('transform', new SVG.Matrix(m)) } // Opacity , opacity: function(value) { diff --git a/src/transform.js b/src/transform.js new file mode 100644 index 0000000..c78865d --- /dev/null +++ b/src/transform.js @@ -0,0 +1,47 @@ +SVG.extend(SVG.Element, { + // Add transformations + transform: function(o) { + // Full getter + if (o == null) + return this.ctm().extract() + + // Get current matrix + var matrix = new SVG.Matrix(this) + + // Act on matrix + if (o.a != null) + matrix = matrix.multiply(new SVG.Matrix(o)) + + // Act on rotate + else if (o.rotation) + matrix = matrix.rotate( + o.rotation + , o.cx == null ? this.bbox().cx : o.cx + , o.cy == null ? this.bbox().cy : o.cy + ) + + // Act on scale + else if (o.scale != null || o.scaleX != null || o.scaleY != null) + matrix = matrix.scale( + o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 + , o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 + , o.cx != null ? o.cx : this.bbox().x + , o.cy != null ? o.cy : this.bbox().y + ) + + // Act on skew + else if (o.skewX || o.skewY) + matrix = matrix.skew(o.skewX, o.skewY) + + // Act on translate + else if (o.x || o.y) + matrix = matrix.translate(o.x, o.y) +console.log(o, matrix) + return this.attr('transform', matrix) + } + // Reset all transformations +, untransform: function() { + return this.attr('transform', null) + } + +}) \ No newline at end of file diff --git a/src/transporter.js b/src/transporter.js new file mode 100644 index 0000000..d2cf609 --- /dev/null +++ b/src/transporter.js @@ -0,0 +1,33 @@ +SVG.extend(SVG.Parent, SVG.Text, { + // Import svg SVG data + svg: function(svg) { + // create temporary div to receive svg content + var element = document.createElement('div') + + if (svg) { + // strip away newlines and properly close tags + svg = svg + .replace(/\n/, '') + .replace(/<(\w+)([^<]+?)\/>/g, '<$1$2>') + + // ensure SVG wrapper for correct element type casting + element.innerHTML = '' + svg + '' + + // transplant content from well to target + for (var i = element.firstChild.childNodes.length - 1; i >= 0; i--) + if (element.firstChild.childNodes[i].nodeType == 1) + this.node.appendChild(element.firstChild.childNodes[i]) + + return this + + } else { + // clone element and its contents + var clone = this.node.cloneNode(true) + + // add target to clone + element.appendChild(clone) + + return element.innerHTML + } + } +}) \ No newline at end of file diff --git a/src/utilities.js b/src/utilities.js index 4770f9b..0c1a0a3 100644 --- a/src/utilities.js +++ b/src/utilities.js @@ -11,4 +11,13 @@ SVG.utils = { 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 -- 2.39.5