aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorUlrich-Matthias Schäfer <ulima.ums@googlemail.com>2018-11-07 15:08:41 +0100
committerUlrich-Matthias Schäfer <ulima.ums@googlemail.com>2018-11-07 15:08:41 +0100
commit47fda3cf67cdc8ab20d3b1ba9d65a810adddf5ee (patch)
treec3dbd5df7f41c41ae5b41717ee8312ba84aeed07 /src
parent23595eba0ee68a86040d4667bdae4d9e6fe426ba (diff)
parent0cae4172fa0c7ee9b876b1cf2b38ea8be66d3584 (diff)
downloadsvg.js-47fda3cf67cdc8ab20d3b1ba9d65a810adddf5ee.tar.gz
svg.js-47fda3cf67cdc8ab20d3b1ba9d65a810adddf5ee.zip
Merge branch '875-es6' into 3.0.0
Diffstat (limited to 'src')
-rw-r--r--src/HtmlNode.js29
-rw-r--r--src/animation/Animator.js85
-rw-r--r--src/animation/Controller.js173
-rw-r--r--src/animation/Queue.js59
-rw-r--r--src/animation/Runner.js928
-rw-r--r--src/animation/Timeline.js271
-rw-r--r--src/animator.js83
-rw-r--r--src/arrange.js97
-rw-r--r--src/array.js92
-rw-r--r--src/attr.js72
-rw-r--r--src/bare.js43
-rw-r--r--src/boxes.js141
-rw-r--r--src/clip.js53
-rw-r--r--src/color.js148
-rw-r--r--src/container.js9
-rw-r--r--src/controller.js193
-rw-r--r--src/css.js47
-rw-r--r--src/data.js25
-rw-r--r--src/default.js51
-rw-r--r--src/defs.js7
-rw-r--r--src/doc.js70
-rw-r--r--src/element.js331
-rw-r--r--src/elements/A.js43
-rw-r--r--src/elements/Bare.js30
-rw-r--r--src/elements/Circle.js40
-rw-r--r--src/elements/ClipPath.js57
-rw-r--r--src/elements/Container.js27
-rw-r--r--src/elements/Defs.js13
-rw-r--r--src/elements/Doc.js72
-rw-r--r--src/elements/Dom.js242
-rw-r--r--src/elements/Element.js142
-rw-r--r--src/elements/Ellipse.js21
-rw-r--r--src/elements/G.js20
-rw-r--r--src/elements/Gradient.js77
-rw-r--r--src/elements/HtmlNode.js10
-rw-r--r--src/elements/Image.js68
-rw-r--r--src/elements/Line.js63
-rw-r--r--src/elements/Marker.js81
-rw-r--r--src/elements/Mask.js57
-rw-r--r--src/elements/Path.js81
-rw-r--r--src/elements/Pattern.js71
-rw-r--r--src/elements/Polygon.js27
-rw-r--r--src/elements/Polyline.js27
-rw-r--r--src/elements/Rect.js32
-rw-r--r--src/elements/Shape.js3
-rw-r--r--src/elements/Stop.js29
-rw-r--r--src/elements/Symbol.js20
-rw-r--r--src/elements/Text.js177
-rw-r--r--src/elements/TextPath.js83
-rw-r--r--src/elements/Tspan.js64
-rw-r--r--src/elements/Use.js27
-rw-r--r--src/elemnts-svg.js88
-rw-r--r--src/ellipse.js91
-rw-r--r--src/eventtarget.js23
-rw-r--r--src/flatten.js38
-rw-r--r--src/fx.js1368
-rw-r--r--src/gradient.js104
-rw-r--r--src/group.js19
-rw-r--r--src/helpers.js311
-rw-r--r--src/hyperlink.js41
-rw-r--r--src/image.js57
-rw-r--r--src/line.js57
-rw-r--r--src/main.js163
-rw-r--r--src/marker.js78
-rw-r--r--src/mask.js51
-rw-r--r--src/matrix.js472
-rw-r--r--src/memory.js37
-rw-r--r--src/modules/core/attr.js80
-rw-r--r--src/modules/core/circled.js64
-rw-r--r--src/modules/core/defaults.js48
-rw-r--r--src/modules/core/event.js (renamed from src/event.js)86
-rw-r--r--src/modules/core/gradiented.js14
-rw-r--r--src/modules/core/namespaces.js5
-rw-r--r--src/modules/core/parser.js26
-rw-r--r--src/modules/core/pointed.js25
-rw-r--r--src/modules/core/poly.js31
-rw-r--r--src/modules/core/regex.js58
-rw-r--r--src/modules/core/selector.js16
-rw-r--r--src/modules/core/textable.js18
-rw-r--r--src/modules/optional/arrange.js98
-rw-r--r--src/modules/optional/class.js44
-rw-r--r--src/modules/optional/css.js71
-rw-r--r--src/modules/optional/data.js26
-rw-r--r--src/modules/optional/memory.js39
-rw-r--r--src/modules/optional/sugar.js (renamed from src/sugar.js)60
-rw-r--r--src/modules/optional/transform.js72
-rw-r--r--src/morph.js231
-rw-r--r--src/number.js99
-rw-r--r--src/parent.js92
-rw-r--r--src/parser.js23
-rw-r--r--src/path.js63
-rw-r--r--src/pattern.js59
-rw-r--r--src/point.js74
-rw-r--r--src/pointarray.js128
-rw-r--r--src/pointed.js25
-rw-r--r--src/poly.js67
-rw-r--r--src/queue.js61
-rw-r--r--src/rect.js16
-rw-r--r--src/regex.js61
-rw-r--r--src/runner.js928
-rw-r--r--src/selector.js31
-rw-r--r--src/shape.js10
-rw-r--r--src/svg.js106
-rw-r--r--src/symbol.js15
-rw-r--r--src/text.js234
-rw-r--r--src/textpath.js77
-rw-r--r--src/timeline.js282
-rw-r--r--src/transform.js70
-rw-r--r--src/types/ArrayPolyfill.js30
-rw-r--r--src/types/Base.js10
-rw-r--r--src/types/Box.js147
-rw-r--r--src/types/Color.js146
-rw-r--r--src/types/EventTarget.js90
-rw-r--r--src/types/Matrix.js522
-rw-r--r--src/types/Morphable.js244
-rw-r--r--src/types/PathArray.js (renamed from src/patharray.js)206
-rw-r--r--src/types/Point.js54
-rw-r--r--src/types/PointArray.js120
-rw-r--r--src/types/SVGArray.js47
-rw-r--r--src/types/SVGNumber.js87
-rw-r--r--src/types/set.js18
-rw-r--r--src/umd.js28
-rw-r--r--src/use.js25
-rw-r--r--src/utilities.js43
-rw-r--r--src/utils/adopter.js115
-rw-r--r--src/utils/methods.js32
-rw-r--r--src/utils/utils.js96
127 files changed, 6142 insertions, 7260 deletions
diff --git a/src/HtmlNode.js b/src/HtmlNode.js
deleted file mode 100644
index e04b731..0000000
--- a/src/HtmlNode.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/* global createElement */
-
-SVG.HtmlNode = SVG.invent({
- inherit: SVG.EventTarget,
- create: function (element) {
- this.node = element
- },
-
- extend: {
- add: function (element, i) {
- element = createElement(element)
-
- if (element.node !== this.node.children[i]) {
- this.node.insertBefore(element.node, this.node.children[i] || null)
- }
-
- return this
- },
-
- put: function (element, i) {
- this.add(element, i)
- return element
- },
-
- getEventTarget: function () {
- return this.node
- }
- }
-})
diff --git a/src/animation/Animator.js b/src/animation/Animator.js
new file mode 100644
index 0000000..fdb2326
--- /dev/null
+++ b/src/animation/Animator.js
@@ -0,0 +1,85 @@
+import Queue from './Queue.js'
+
+const Animator = {
+ nextDraw: null,
+ frames: new Queue(),
+ timeouts: new Queue(),
+ timer: window.performance || window.Date,
+ transforms: [],
+
+ frame (fn) {
+ // Store the node
+ var node = Animator.frames.push({ run: fn })
+
+ // Request an animation frame if we don't have one
+ if (Animator.nextDraw === null) {
+ Animator.nextDraw = window.requestAnimationFrame(Animator._draw)
+ }
+
+ // Return the node so we can remove it easily
+ return node
+ },
+
+ transform_frame (fn, id) {
+ Animator.transforms[id] = fn
+ },
+
+ timeout (fn, delay) {
+ delay = delay || 0
+
+ // Work out when the event should fire
+ var time = Animator.timer.now() + delay
+
+ // Add the timeout to the end of the queue
+ var node = Animator.timeouts.push({ run: fn, time: time })
+
+ // Request another animation frame if we need one
+ if (Animator.nextDraw === null) {
+ Animator.nextDraw = window.requestAnimationFrame(Animator._draw)
+ }
+
+ return node
+ },
+
+ cancelFrame (node) {
+ Animator.frames.remove(node)
+ },
+
+ clearTimeout (node) {
+ Animator.timeouts.remove(node)
+ },
+
+ _draw (now) {
+ // Run all the timeouts we can run, if they are not ready yet, add them
+ // to the end of the queue immediately! (bad timeouts!!! [sarcasm])
+ var nextTimeout = null
+ var lastTimeout = Animator.timeouts.last()
+ while ((nextTimeout = Animator.timeouts.shift())) {
+ // Run the timeout if its time, or push it to the end
+ if (now >= nextTimeout.time) {
+ nextTimeout.run()
+ } else {
+ Animator.timeouts.push(nextTimeout)
+ }
+
+ // If we hit the last item, we should stop shifting out more items
+ if (nextTimeout === lastTimeout) break
+ }
+
+ // Run all of the animation frames
+ var nextFrame = null
+ var lastFrame = Animator.frames.last()
+ while ((nextFrame !== lastFrame) && (nextFrame = Animator.frames.shift())) {
+ nextFrame.run()
+ }
+
+ Animator.transforms.forEach(function (el) { el() })
+
+ // If we have remaining timeouts or frames, draw until we don't anymore
+ Animator.nextDraw = Animator.timeouts.first() || Animator.frames.first()
+ ? window.requestAnimationFrame(Animator._draw)
+ : null
+ }
+}
+
+export default Animator
diff --git a/src/animation/Controller.js b/src/animation/Controller.js
new file mode 100644
index 0000000..1716545
--- /dev/null
+++ b/src/animation/Controller.js
@@ -0,0 +1,173 @@
+import { timeline } from '../modules/core/defaults.js'
+import { extend } from '../utils/adopter.js'
+
+/***
+Base Class
+==========
+The base stepper class that will be
+***/
+
+function makeSetterGetter (k, f) {
+ return function (v) {
+ if (v == null) return this[v]
+ this[k] = v
+ if (f) f.call(this)
+ return this
+ }
+}
+
+export let easing = {
+ '-': function (pos) { return pos },
+ '<>': function (pos) { return -Math.cos(pos * Math.PI) / 2 + 0.5 },
+ '>': function (pos) { return Math.sin(pos * Math.PI / 2) },
+ '<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 },
+ bezier: function (t0, x0, t1, x1) {
+ return function (t) {
+ // TODO: FINISH
+ }
+ }
+}
+
+export class Stepper {
+ done () { return false }
+}
+
+/***
+Easing Functions
+================
+***/
+
+export class Ease extends Stepper {
+ constructor (fn) {
+ super()
+ this.ease = easing[fn || timeline.ease] || fn
+ }
+
+ step (from, to, pos) {
+ if (typeof from !== 'number') {
+ return pos < 1 ? from : to
+ }
+ return from + (to - from) * this.ease(pos)
+ }
+}
+
+/***
+Controller Types
+================
+***/
+
+export class Controller extends Stepper {
+ constructor (fn) {
+ super()
+ this.stepper = fn
+ }
+
+ step (current, target, dt, c) {
+ return this.stepper(current, target, dt, c)
+ }
+
+ done (c) {
+ return c.done
+ }
+}
+
+function recalculate () {
+ // Apply the default parameters
+ var duration = (this._duration || 500) / 1000
+ var overshoot = this._overshoot || 0
+
+ // Calculate the PID natural response
+ var eps = 1e-10
+ var pi = Math.PI
+ var os = Math.log(overshoot / 100 + eps)
+ var zeta = -os / Math.sqrt(pi * pi + os * os)
+ var wn = 3.9 / (zeta * duration)
+
+ // Calculate the Spring values
+ this.d = 2 * zeta * wn
+ this.k = wn * wn
+}
+
+export class Spring extends Controller {
+ constructor (duration, overshoot) {
+ super()
+ this.duration(duration || 500)
+ .overshoot(overshoot || 0)
+ }
+
+ step (current, target, dt, c) {
+ if (typeof current === 'string') return current
+ c.done = dt === Infinity
+ if (dt === Infinity) return target
+ if (dt === 0) return current
+
+ if (dt > 100) dt = 16
+
+ dt /= 1000
+
+ // Get the previous velocity
+ var velocity = c.velocity || 0
+
+ // Apply the control to get the new position and store it
+ var acceleration = -this.d * velocity - this.k * (current - target)
+ var newPosition = current +
+ velocity * dt +
+ acceleration * dt * dt / 2
+
+ // Store the velocity
+ c.velocity = velocity + acceleration * dt
+
+ // Figure out if we have converged, and if so, pass the value
+ c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002
+ return c.done ? target : newPosition
+ }
+}
+
+extend(Spring, {
+ duration: makeSetterGetter('_duration', recalculate),
+ overshoot: makeSetterGetter('_overshoot', recalculate)
+})
+
+export class PID extends Controller {
+ constructor (p, i, d, windup) {
+ super()
+
+ p = p == null ? 0.1 : p
+ i = i == null ? 0.01 : i
+ d = d == null ? 0 : d
+ windup = windup == null ? 1000 : windup
+ this.p(p).i(i).d(d).windup(windup)
+ }
+
+ step (current, target, dt, c) {
+ if (typeof current === 'string') return current
+ c.done = dt === Infinity
+
+ if (dt === Infinity) return target
+ if (dt === 0) return current
+
+ var p = target - current
+ var i = (c.integral || 0) + p * dt
+ var d = (p - (c.error || 0)) / dt
+ var windup = this.windup
+
+ // antiwindup
+ if (windup !== false) {
+ i = Math.max(-windup, Math.min(i, windup))
+ }
+
+ c.error = p
+ c.integral = i
+
+ c.done = Math.abs(p) < 0.001
+
+ return c.done ? target : current + (this.P * p + this.I * i + this.D * d)
+ }
+}
+
+extend(PID, {
+ windup: makeSetterGetter('windup'),
+ p: makeSetterGetter('P'),
+ i: makeSetterGetter('I'),
+ d: makeSetterGetter('D')
+})
diff --git a/src/animation/Queue.js b/src/animation/Queue.js
new file mode 100644
index 0000000..14b92b4
--- /dev/null
+++ b/src/animation/Queue.js
@@ -0,0 +1,59 @@
+export default class Queue {
+ constructor () {
+ this._first = null
+ this._last = null
+ }
+
+ push (value) {
+ // An item stores an id and the provided value
+ var item = value.next ? value : { value: value, next: null, prev: null }
+
+ // Deal with the queue being empty or populated
+ if (this._last) {
+ item.prev = this._last
+ this._last.next = item
+ this._last = item
+ } else {
+ this._last = item
+ this._first = item
+ }
+
+ // Update the length and return the current item
+ return item
+ }
+
+ shift () {
+ // Check if we have a value
+ var remove = this._first
+ if (!remove) return null
+
+ // If we do, remove it and relink things
+ this._first = remove.next
+ if (this._first) this._first.prev = null
+ this._last = this._first ? this._last : null
+ return remove.value
+ }
+
+ // Shows us the first item in the list
+ first () {
+ return this._first && this._first.value
+ }
+
+ // Shows us the last item in the list
+ last () {
+ return this._last && this._last.value
+ }
+
+ // Removes the item that was returned from the push
+ remove (item) {
+ // Relink the previous item
+ if (item.prev) item.prev.next = item.next
+ if (item.next) item.next.prev = item.prev
+ if (item === this._last) this._last = item.prev
+ if (item === this._first) this._first = item.next
+
+ // Invalidate item
+ item.prev = null
+ item.next = null
+ }
+}
diff --git a/src/animation/Runner.js b/src/animation/Runner.js
new file mode 100644
index 0000000..f752185
--- /dev/null
+++ b/src/animation/Runner.js
@@ -0,0 +1,928 @@
+import { Controller, Ease, Stepper } from './Controller.js'
+import { extend } from '../utils/adopter.js'
+import { getOrigin } from '../utils/utils.js'
+import { noop, timeline } from '../modules/core/defaults.js'
+import { registerMethods } from '../utils/methods.js'
+import Animator from './Animator.js'
+import Box from '../types/Box.js'
+import EventTarget from '../types/EventTarget.js'
+import Matrix from '../types/Matrix.js'
+import Morphable, { TransformBag } from '../types/Morphable.js'
+import Point from '../types/Point.js'
+import SVGNumber from '../types/SVGNumber.js'
+import Timeline from './Timeline.js'
+
+export default class Runner extends EventTarget {
+ constructor (options) {
+ super()
+
+ // Store a unique id on the runner, so that we can identify it later
+ this.id = Runner.id++
+
+ // Ensure a default value
+ options = options == null
+ ? timeline.duration
+ : options
+
+ // Ensure that we get a controller
+ options = typeof options === 'function'
+ ? new Controller(options)
+ : options
+
+ // Declare all of the variables
+ this._element = null
+ this._timeline = null
+ this.done = false
+ this._queue = []
+
+ // Work out the stepper and the duration
+ this._duration = typeof options === 'number' && options
+ this._isDeclarative = options instanceof Controller
+ this._stepper = this._isDeclarative ? options : new Ease()
+
+ // We copy the current values from the timeline because they can change
+ this._history = {}
+
+ // Store the state of the runner
+ this.enabled = true
+ this._time = 0
+ this._last = 0
+
+ // Save transforms applied to this runner
+ this.transforms = new Matrix()
+ this.transformId = 1
+
+ // Looping variables
+ this._haveReversed = false
+ this._reverse = false
+ this._loopsDone = 0
+ this._swing = false
+ this._wait = 0
+ this._times = 1
+ }
+
+ /*
+ Runner Definitions
+ ==================
+ These methods help us define the runtime behaviour of the Runner or they
+ help us make new runners from the current runner
+ */
+
+ element (element) {
+ if (element == null) return this._element
+ this._element = element
+ element._prepareRunner()
+ return this
+ }
+
+ timeline (timeline) {
+ // check explicitly for undefined so we can set the timeline to null
+ if (typeof timeline === 'undefined') return this._timeline
+ this._timeline = timeline
+ return this
+ }
+
+ animate (duration, delay, when) {
+ var o = Runner.sanitise(duration, delay, when)
+ var runner = new Runner(o.duration)
+ if (this._timeline) runner.timeline(this._timeline)
+ if (this._element) runner.element(this._element)
+ return runner.loop(o).schedule(delay, when)
+ }
+
+ schedule (timeline, delay, when) {
+ // The user doesn't need to pass a timeline if we already have one
+ if (!(timeline instanceof Timeline)) {
+ when = delay
+ delay = timeline
+ timeline = this.timeline()
+ }
+
+ // If there is no timeline, yell at the user...
+ if (!timeline) {
+ throw Error('Runner cannot be scheduled without timeline')
+ }
+
+ // Schedule the runner on the timeline provided
+ timeline.schedule(this, delay, when)
+ return this
+ }
+
+ unschedule () {
+ var timeline = this.timeline()
+ timeline && timeline.unschedule(this)
+ return this
+ }
+
+ loop (times, swing, wait) {
+ // Deal with the user passing in an object
+ if (typeof times === 'object') {
+ swing = times.swing
+ wait = times.wait
+ times = times.times
+ }
+
+ // Sanitise the values and store them
+ this._times = times || Infinity
+ this._swing = swing || false
+ this._wait = wait || 0
+ return this
+ }
+
+ delay (delay) {
+ return this.animate(0, delay)
+ }
+
+ /*
+ Basic Functionality
+ ===================
+ These methods allow us to attach basic functions to the runner directly
+ */
+
+ queue (initFn, runFn, isTransform) {
+ this._queue.push({
+ initialiser: initFn || noop,
+ runner: runFn || noop,
+ isTransform: isTransform,
+ initialised: false,
+ finished: false
+ })
+ var timeline = this.timeline()
+ timeline && this.timeline()._continue()
+ return this
+ }
+
+ during (fn) {
+ return this.queue(null, fn)
+ }
+
+ after (fn) {
+ return this.on('finish', fn)
+ }
+
+ /*
+ Runner animation methods
+ ========================
+ Control how the animation plays
+ */
+
+ time (time) {
+ if (time == null) {
+ return this._time
+ }
+ let dt = time - this._time
+ this.step(dt)
+ return this
+ }
+
+ duration () {
+ return this._times * (this._wait + this._duration) - this._wait
+ }
+
+ loops (p) {
+ var loopDuration = this._duration + this._wait
+ if (p == null) {
+ var loopsDone = Math.floor(this._time / loopDuration)
+ var relativeTime = (this._time - loopsDone * loopDuration)
+ var position = relativeTime / this._duration
+ return Math.min(loopsDone + position, this._times)
+ }
+ var whole = Math.floor(p)
+ var partial = p % 1
+ var time = loopDuration * whole + this._duration * partial
+ return this.time(time)
+ }
+
+ position (p) {
+ // Get all of the variables we need
+ var x = this._time
+ var d = this._duration
+ var w = this._wait
+ var t = this._times
+ var s = this._swing
+ var r = this._reverse
+ var position
+
+ if (p == null) {
+ /*
+ This function converts a time to a position in the range [0, 1]
+ The full explanation can be found in this desmos demonstration
+ https://www.desmos.com/calculator/u4fbavgche
+ The logic is slightly simplified here because we can use booleans
+ */
+
+ // Figure out the value without thinking about the start or end time
+ const f = function (x) {
+ var swinging = s * Math.floor(x % (2 * (w + d)) / (w + d))
+ var backwards = (swinging && !r) || (!swinging && r)
+ var uncliped = Math.pow(-1, backwards) * (x % (w + d)) / d + backwards
+ var clipped = Math.max(Math.min(uncliped, 1), 0)
+ return clipped
+ }
+
+ // Figure out the value by incorporating the start time
+ var endTime = t * (w + d) - w
+ position = x <= 0 ? Math.round(f(1e-5))
+ : x < endTime ? f(x)
+ : Math.round(f(endTime - 1e-5))
+ return position
+ }
+
+ // Work out the loops done and add the position to the loops done
+ var loopsDone = Math.floor(this.loops())
+ var swingForward = s && (loopsDone % 2 === 0)
+ var forwards = (swingForward && !r) || (r && swingForward)
+ position = loopsDone + (forwards ? p : 1 - p)
+ return this.loops(position)
+ }
+
+ progress (p) {
+ if (p == null) {
+ return Math.min(1, this._time / this.duration())
+ }
+ return this.time(p * this.duration())
+ }
+
+ step (dt) {
+ // If we are inactive, this stepper just gets skipped
+ if (!this.enabled) return this
+
+ // Update the time and get the new position
+ dt = dt == null ? 16 : dt
+ this._time += dt
+ var position = this.position()
+
+ // Figure out if we need to run the stepper in this frame
+ var running = this._lastPosition !== position && this._time >= 0
+ this._lastPosition = position
+
+ // Figure out if we just started
+ var duration = this.duration()
+ var justStarted = this._lastTime < 0 && this._time > 0
+ var justFinished = this._lastTime < this._time && this.time > duration
+ this._lastTime = this._time
+ if (justStarted) {
+ this.fire('start', this)
+ }
+
+ // Work out if the runner is finished set the done flag here so animations
+ // know, that they are running in the last step (this is good for
+ // transformations which can be merged)
+ var declarative = this._isDeclarative
+ this.done = !declarative && !justFinished && this._time >= duration
+
+ // Call initialise and the run function
+ if (running || declarative) {
+ this._initialise(running)
+
+ // clear the transforms on this runner so they dont get added again and again
+ this.transforms = new Matrix()
+ var converged = this._run(declarative ? dt : position)
+ this.fire('step', this)
+ }
+ // correct the done flag here
+ // declaritive animations itself know when they converged
+ this.done = this.done || (converged && declarative)
+ if (this.done) {
+ this.fire('finish', this)
+ }
+ return this
+ }
+
+ finish () {
+ return this.step(Infinity)
+ }
+
+ reverse (reverse) {
+ this._reverse = reverse == null ? !this._reverse : reverse
+ return this
+ }
+
+ ease (fn) {
+ this._stepper = new Ease(fn)
+ return this
+ }
+
+ active (enabled) {
+ if (enabled == null) return this.enabled
+ this.enabled = enabled
+ return this
+ }
+
+ /*
+ Private Methods
+ ===============
+ Methods that shouldn't be used externally
+ */
+
+ // Save a morpher to the morpher list so that we can retarget it later
+ _rememberMorpher (method, morpher) {
+ this._history[method] = {
+ morpher: morpher,
+ caller: this._queue[this._queue.length - 1]
+ }
+ }
+
+ // Try to set the target for a morpher if the morpher exists, otherwise
+ // do nothing and return false
+ _tryRetarget (method, target) {
+ if (this._history[method]) {
+ // if the last method wasnt even initialised, throw it away
+ if (!this._history[method].caller.initialised) {
+ let index = this._queue.indexOf(this._history[method].caller)
+ this._queue.splice(index, 1)
+ return false
+ }
+
+ // for the case of transformations, we use the special retarget function
+ // which has access to the outer scope
+ if (this._history[method].caller.isTransform) {
+ this._history[method].caller.isTransform(target)
+ // for everything else a simple morpher change is sufficient
+ } else {
+ this._history[method].morpher.to(target)
+ }
+
+ this._history[method].caller.finished = false
+ var timeline = this.timeline()
+ timeline && timeline._continue()
+ return true
+ }
+ return false
+ }
+
+ // Run each initialise function in the runner if required
+ _initialise (running) {
+ // If we aren't running, we shouldn't initialise when not declarative
+ if (!running && !this._isDeclarative) return
+
+ // Loop through all of the initialisers
+ for (var i = 0, len = this._queue.length; i < len; ++i) {
+ // Get the current initialiser
+ var current = this._queue[i]
+
+ // Determine whether we need to initialise
+ var needsIt = this._isDeclarative || (!current.initialised && running)
+ running = !current.finished
+
+ // Call the initialiser if we need to
+ if (needsIt && running) {
+ current.initialiser.call(this)
+ current.initialised = true
+ }
+ }
+ }
+
+ // Run each run function for the position or dt given
+ _run (positionOrDt) {
+ // Run all of the _queue directly
+ var allfinished = true
+ for (var i = 0, len = this._queue.length; i < len; ++i) {
+ // Get the current function to run
+ var current = this._queue[i]
+
+ // Run the function if its not finished, we keep track of the finished
+ // flag for the sake of declarative _queue
+ var converged = current.runner.call(this, positionOrDt)
+ current.finished = current.finished || (converged === true)
+ allfinished = allfinished && current.finished
+ }
+
+ // We report when all of the constructors are finished
+ return allfinished
+ }
+
+ addTransform (transform, index) {
+ this.transforms.lmultiplyO(transform)
+ return this
+ }
+
+ clearTransform () {
+ this.transforms = new Matrix()
+ return this
+ }
+
+ static sanitise (duration, delay, when) {
+ // Initialise the default parameters
+ var times = 1
+ var swing = false
+ var wait = 0
+ duration = duration || timeline.duration
+ delay = delay || timeline.delay
+ when = when || 'last'
+
+ // If we have an object, unpack the values
+ if (typeof duration === 'object' && !(duration instanceof Stepper)) {
+ delay = duration.delay || delay
+ when = duration.when || when
+ swing = duration.swing || swing
+ times = duration.times || times
+ wait = duration.wait || wait
+ duration = duration.duration || timeline.duration
+ }
+
+ return {
+ duration: duration,
+ delay: delay,
+ swing: swing,
+ times: times,
+ wait: wait,
+ when: when
+ }
+ }
+}
+
+Runner.id = 0
+
+class FakeRunner {
+ constructor (transforms = new Matrix(), id = -1, done = true) {
+ this.transforms = transforms
+ this.id = id
+ this.done = done
+ }
+}
+
+extend([Runner, FakeRunner], {
+ mergeWith (runner) {
+ return new FakeRunner(
+ runner.transforms.lmultiply(this.transforms),
+ runner.id
+ )
+ }
+})
+
+// FakeRunner.emptyRunner = new FakeRunner()
+
+const lmultiply = (last, curr) => last.lmultiplyO(curr)
+const getRunnerTransform = (runner) => runner.transforms
+
+function mergeTransforms () {
+ // Find the matrix to apply to the element and apply it
+ let runners = this._transformationRunners.runners
+ let netTransform = runners
+ .map(getRunnerTransform)
+ .reduce(lmultiply, new Matrix())
+
+ this.transform(netTransform)
+
+ this._transformationRunners.merge()
+
+ if (this._transformationRunners.length() === 1) {
+ this._frameId = null
+ }
+}
+
+class RunnerArray {
+ constructor () {
+ this.runners = []
+ this.ids = []
+ }
+
+ add (runner) {
+ if (this.runners.includes(runner)) return
+
+ let id = runner.id + 1
+
+ let leftSibling = this.ids.reduce((last, curr) => {
+ if (curr > last && curr < id) return curr
+ return last
+ }, 0)
+
+ let index = this.ids.indexOf(leftSibling) + 1
+
+ this.ids.splice(index, 0, id)
+ this.runners.splice(index, 0, runner)
+
+ return this
+ }
+
+ getByID (id) {
+ return this.runners[this.ids.indexOf(id + 1)]
+ }
+
+ remove (id) {
+ let index = this.ids.indexOf(id + 1)
+ this.ids.splice(index, 1)
+ this.runners.splice(index, 1)
+ return this
+ }
+
+ merge () {
+ let lastRunner = null
+ this.runners.forEach((runner, i) => {
+ if (lastRunner && runner.done && lastRunner.done) {
+ this.remove(runner.id)
+ this.edit(lastRunner.id, runner.mergeWith(lastRunner))
+ }
+
+ lastRunner = runner
+ })
+
+ return this
+ }
+
+ edit (id, newRunner) {
+ let index = this.ids.indexOf(id + 1)
+ this.ids.splice(index, 1, id)
+ this.runners.splice(index, 1, newRunner)
+ return this
+ }
+
+ length () {
+ return this.ids.length
+ }
+
+ clearBefore (id) {
+ let deleteCnt = this.ids.indexOf(id + 1) || 1
+ this.ids.splice(0, deleteCnt, 0)
+ this.runners.splice(0, deleteCnt, new FakeRunner())
+ return this
+ }
+}
+
+let frameId = 0
+registerMethods({
+ Element: {
+ animate (duration, delay, when) {
+ var o = Runner.sanitise(duration, delay, when)
+ var timeline = this.timeline()
+ return new Runner(o.duration)
+ .loop(o)
+ .element(this)
+ .timeline(timeline)
+ .schedule(delay, when)
+ },
+
+ delay (by, when) {
+ return this.animate(0, by, when)
+ },
+
+ // this function searches for all runners on the element and deletes the ones
+ // which run before the current one. This is because absolute transformations
+ // overwfrite anything anyway so there is no need to waste time computing
+ // other runners
+ _clearTransformRunnersBefore (currentRunner) {
+ this._transformationRunners.clearBefore(currentRunner.id)
+ },
+
+ _currentTransform (current) {
+ return this._transformationRunners.runners
+ // we need the equal sign here to make sure, that also transformations
+ // on the same runner which execute before the current transformation are
+ // taken into account
+ .filter((runner) => runner.id <= current.id)
+ .map(getRunnerTransform)
+ .reduce(lmultiply, new Matrix())
+ },
+
+ addRunner (runner) {
+ this._transformationRunners.add(runner)
+
+ Animator.transform_frame(
+ mergeTransforms.bind(this), this._frameId
+ )
+ },
+
+ _prepareRunner () {
+ if (this._frameId == null) {
+ this._transformationRunners = new RunnerArray()
+ .add(new FakeRunner(new Matrix(this)))
+
+ this._frameId = frameId++
+ }
+ }
+ }
+})
+
+extend(Runner, {
+ attr (a, v) {
+ return this.styleAttr('attr', a, v)
+ },
+
+ // Add animatable styles
+ css (s, v) {
+ return this.styleAttr('css', s, v)
+ },
+
+ styleAttr (type, name, val) {
+ // apply attributes individually
+ if (typeof name === 'object') {
+ for (var key in val) {
+ this.styleAttr(type, key, val[key])
+ }
+ }
+
+ var morpher = new Morphable(this._stepper).to(val)
+
+ this.queue(function () {
+ morpher = morpher.from(this.element()[type](name))
+ }, function (pos) {
+ this.element()[type](name, morpher.at(pos))
+ return morpher.done()
+ })
+
+ return this
+ },
+
+ zoom (level, point) {
+ var morpher = new Morphable(this._stepper).to(new SVGNumber(level))
+
+ this.queue(function () {
+ morpher = morpher.from(this.zoom())
+ }, function (pos) {
+ this.element().zoom(morpher.at(pos), point)
+ return morpher.done()
+ })
+
+ return this
+ },
+
+ /**
+ ** absolute transformations
+ **/
+
+ //
+ // M v -----|-----(D M v = F v)------|-----> T v
+ //
+ // 1. define the final state (T) and decompose it (once)
+ // t = [tx, ty, the, lam, sy, sx]
+ // 2. on every frame: pull the current state of all previous transforms
+ // (M - m can change)
+ // and then write this as m = [tx0, ty0, the0, lam0, sy0, sx0]
+ // 3. Find the interpolated matrix F(pos) = m + pos * (t - m)
+ // - Note F(0) = M
+ // - Note F(1) = T
+ // 4. Now you get the delta matrix as a result: D = F * inv(M)
+
+ transform (transforms, relative, affine) {
+ // If we have a declarative function, we should retarget it if possible
+ relative = transforms.relative || relative
+ if (this._isDeclarative && !relative && this._tryRetarget('transform', transforms)) {
+ return this
+ }
+
+ // Parse the parameters
+ var isMatrix = Matrix.isMatrixLike(transforms)
+ affine = transforms.affine != null
+ ? transforms.affine
+ : (affine != null ? affine : !isMatrix)
+
+ // Create a morepher and set its type
+ const morpher = new Morphable()
+ .type(affine ? TransformBag : Matrix)
+ .stepper(this._stepper)
+
+ let origin
+ let element
+ let current
+ let currentAngle
+ let startTransform
+
+ function setup () {
+ // make sure element and origin is defined
+ element = element || this.element()
+ origin = origin || getOrigin(transforms, element)
+
+ startTransform = new Matrix(relative ? undefined : element)
+
+ // add the runner to the element so it can merge transformations
+ element.addRunner(this)
+
+ // Deactivate all transforms that have run so far if we are absolute
+ if (!relative) {
+ element._clearTransformRunnersBefore(this)
+ }
+ }
+
+ function run (pos) {
+ // clear all other transforms before this in case something is saved
+ // on this runner. We are absolute. We dont need these!
+ if (!relative) this.clearTransform()
+
+ let { x, y } = new Point(origin).transform(element._currentTransform(this))
+
+ let target = new Matrix({ ...transforms, origin: [x, y] })
+ let start = this._isDeclarative && current
+ ? current
+ : startTransform
+
+ if (affine) {
+ target = target.decompose(x, y)
+ start = start.decompose(x, y)
+
+ // Get the current and target angle as it was set
+ const rTarget = target.rotate
+ const rCurrent = start.rotate
+
+ // Figure out the shortest path to rotate directly
+ const possibilities = [rTarget - 360, rTarget, rTarget + 360]
+ const distances = possibilities.map(a => Math.abs(a - rCurrent))
+ const shortest = Math.min(...distances)
+ const index = distances.indexOf(shortest)
+ target.rotate = possibilities[index]
+ }
+
+ if (relative) {
+ // we have to be careful here not to overwrite the rotation
+ // with the rotate method of Matrix
+ if (!isMatrix) {
+ target.rotate = transforms.rotate || 0
+ }
+ if (this._isDeclarative && currentAngle) {
+ start.rotate = currentAngle
+ }
+ }
+
+ morpher.from(start)
+ morpher.to(target)
+
+ let affineParameters = morpher.at(pos)
+ currentAngle = affineParameters.rotate
+ current = new Matrix(affineParameters)
+
+ this.addTransform(current)
+ return morpher.done()
+ }
+
+ function retarget (newTransforms) {
+ // only get a new origin if it changed since the last call
+ if (
+ (newTransforms.origin || 'center').toString() !==
+ (transforms.origin || 'center').toString()
+ ) {
+ origin = getOrigin(transforms, element)
+ }
+
+ // overwrite the old transformations with the new ones
+ transforms = { ...newTransforms, origin }
+ }
+
+ this.queue(setup, run, retarget)
+ this._isDeclarative && this._rememberMorpher('transform', morpher)
+ return this
+ },
+
+ // Animatable x-axis
+ x (x, relative) {
+ return this._queueNumber('x', x)
+ },
+
+ // Animatable y-axis
+ y (y) {
+ return this._queueNumber('y', y)
+ },
+
+ dx (x) {
+ return this._queueNumberDelta('dx', x)
+ },
+
+ dy (y) {
+ return this._queueNumberDelta('dy', y)
+ },
+
+ _queueNumberDelta (method, to) {
+ to = new SVGNumber(to)
+
+ // Try to change the target if we have this method already registerd
+ if (this._tryRetargetDelta(method, to)) return this
+
+ // Make a morpher and queue the animation
+ var morpher = new Morphable(this._stepper).to(to)
+ this.queue(function () {
+ var from = this.element()[method]()
+ morpher.from(from)
+ morpher.to(from + to)
+ }, function (pos) {
+ this.element()[method](morpher.at(pos))
+ return morpher.done()
+ })
+
+ // Register the morpher so that if it is changed again, we can retarget it
+ this._rememberMorpher(method, morpher)
+ return this
+ },
+
+ _queueObject (method, to) {
+ // Try to change the target if we have this method already registerd
+ if (this._tryRetarget(method, to)) return this
+
+ // Make a morpher and queue the animation
+ var morpher = new Morphable(this._stepper).to(to)
+ this.queue(function () {
+ morpher.from(this.element()[method]())
+ }, function (pos) {
+ this.element()[method](morpher.at(pos))
+ return morpher.done()
+ })
+
+ // Register the morpher so that if it is changed again, we can retarget it
+ this._rememberMorpher(method, morpher)
+ return this
+ },
+
+ _queueNumber (method, value) {
+ return this._queueObject(method, new SVGNumber(value))
+ },
+
+ // Animatable center x-axis
+ cx (x) {
+ return this._queueNumber('cx', x)
+ },
+
+ // Animatable center y-axis
+ cy (y) {
+ return this._queueNumber('cy', y)
+ },
+
+ // Add animatable move
+ move (x, y) {
+ return this.x(x).y(y)
+ },
+
+ // Add animatable center
+ center (x, y) {
+ return this.cx(x).cy(y)
+ },
+
+ // Add animatable size
+ size (width, height) {
+ // animate bbox based size for all other elements
+ var box
+
+ if (!width || !height) {
+ box = this._element.bbox()
+ }
+
+ if (!width) {
+ width = box.width / box.height * height
+ }
+
+ if (!height) {
+ height = box.height / box.width * width
+ }
+
+ return this
+ .width(width)
+ .height(height)
+ },
+
+ // Add animatable width
+ width (width) {
+ return this._queueNumber('width', width)
+ },
+
+ // Add animatable height
+ height (height) {
+ return this._queueNumber('height', height)
+ },
+
+ // Add animatable plot
+ plot (a, b, c, d) {
+ // Lines can be plotted with 4 arguments
+ if (arguments.length === 4) {
+ return this.plot([a, b, c, d])
+ }
+
+ // FIXME: this needs to be rewritten such that the element is only accesed
+ // in the init function
+ return this._queueObject('plot', new this._element.MorphArray(a))
+
+ /*
+ var morpher = this._element.morphArray().to(a)
+
+ this.queue(function () {
+ morpher.from(this._element.array())
+ }, function (pos) {
+ this._element.plot(morpher.at(pos))
+ })
+
+ return this
+ */
+ },
+
+ // Add leading method
+ leading (value) {
+ return this._queueNumber('leading', value)
+ },
+
+ // Add animatable viewbox
+ viewbox (x, y, width, height) {
+ return this._queueObject('viewbox', new Box(x, y, width, height))
+ },
+
+ update (o) {
+ if (typeof o !== 'object') {
+ return this.update({
+ offset: arguments[0],
+ color: arguments[1],
+ opacity: arguments[2]
+ })
+ }
+
+ if (o.opacity != null) this.attr('stop-opacity', o.opacity)
+ if (o.color != null) this.attr('stop-color', o.color)
+ if (o.offset != null) this.attr('offset', o.offset)
+
+ return this
+ }
+})
diff --git a/src/animation/Timeline.js b/src/animation/Timeline.js
new file mode 100644
index 0000000..619e50a
--- /dev/null
+++ b/src/animation/Timeline.js
@@ -0,0 +1,271 @@
+import { registerMethods } from '../utils/methods.js'
+import Animator from './Animator.js'
+
+var time = window.performance || Date
+
+var makeSchedule = function (runnerInfo) {
+ var start = runnerInfo.start
+ var duration = runnerInfo.runner.duration()
+ var end = start + duration
+ return { start: start, duration: duration, end: end, runner: runnerInfo.runner }
+}
+
+export default class Timeline {
+ // Construct a new timeline on the given element
+ constructor () {
+ this._timeSource = function () {
+ return time.now()
+ }
+
+ this._dispatcher = document.createElement('div')
+
+ // Store the timing variables
+ this._startTime = 0
+ this._speed = 1.0
+
+ // Play control variables control how the animation proceeds
+ this._reverse = false
+ this._persist = 0
+
+ // Keep track of the running animations and their starting parameters
+ this._nextFrame = null
+ this._paused = false
+ this._runners = []
+ this._order = []
+ this._time = 0
+ this._lastSourceTime = 0
+ this._lastStepTime = 0
+ }
+
+ getEventTarget () {
+ return this._dispatcher
+ }
+
+ /**
+ *
+ */
+
+ // schedules a runner on the timeline
+ schedule (runner, delay, when) {
+ if (runner == null) {
+ return this._runners.map(makeSchedule).sort(function (a, b) {
+ return (a.start - b.start) || (a.duration - b.duration)
+ })
+ }
+
+ if (!this.active()) {
+ this._step()
+ if (when == null) {
+ when = 'now'
+ }
+ }
+
+ // The start time for the next animation can either be given explicitly,
+ // derived from the current timeline time or it can be relative to the
+ // last start time to chain animations direclty
+ var absoluteStartTime = 0
+ delay = delay || 0
+
+ // Work out when to start the animation
+ if (when == null || when === 'last' || when === 'after') {
+ // Take the last time and increment
+ absoluteStartTime = this._startTime
+ } else if (when === 'absolute' || when === 'start') {
+ absoluteStartTime = delay
+ delay = 0
+ } else if (when === 'now') {
+ absoluteStartTime = this._time
+ } else if (when === 'relative') {
+ let runnerInfo = this._runners[runner.id]
+ if (runnerInfo) {
+ absoluteStartTime = runnerInfo.start + delay
+ delay = 0
+ }
+ } else {
+ throw new Error('Invalid value for the "when" parameter')
+ }
+
+ // Manage runner
+ runner.unschedule()
+ runner.timeline(this)
+ runner.time(-delay)
+
+ // Save startTime for next runner
+ this._startTime = absoluteStartTime + runner.duration() + delay
+
+ // Save runnerInfo
+ this._runners[runner.id] = {
+ persist: this.persist(),
+ runner: runner,
+ start: absoluteStartTime
+ }
+
+ // Save order and continue
+ this._order.push(runner.id)
+ this._continue()
+ return this
+ }
+
+ // Remove the runner from this timeline
+ unschedule (runner) {
+ var index = this._order.indexOf(runner.id)
+ if (index < 0) return this
+
+ delete this._runners[runner.id]
+ this._order.splice(index, 1)
+ runner.timeline(null)
+ return this
+ }
+
+ play () {
+ // Now make sure we are not paused and continue the animation
+ this._paused = false
+ return this._continue()
+ }
+
+ pause () {
+ // Cancel the next animation frame and pause
+ this._nextFrame = null
+ this._paused = true
+ return this
+ }
+
+ stop () {
+ // Cancel the next animation frame and go to start
+ this.seek(-this._time)
+ return this.pause()
+ }
+
+ finish () {
+ this.seek(Infinity)
+ return this.pause()
+ }
+
+ speed (speed) {
+ if (speed == null) return this._speed
+ this._speed = speed
+ return this
+ }
+
+ reverse (yes) {
+ var currentSpeed = this.speed()
+ if (yes == null) return this.speed(-currentSpeed)
+
+ var positive = Math.abs(currentSpeed)
+ return this.speed(yes ? positive : -positive)
+ }
+
+ seek (dt) {
+ this._time += dt
+ return this._continue()
+ }
+
+ time (time) {
+ if (time == null) return this._time
+ this._time = time
+ return this
+ }
+
+ persist (dtOrForever) {
+ if (dtOrForever == null) return this._persist
+ this._persist = dtOrForever
+ return this
+ }
+
+ source (fn) {
+ if (fn == null) return this._timeSource
+ this._timeSource = fn
+ return this
+ }
+
+ _step () {
+ // If the timeline is paused, just do nothing
+ if (this._paused) return
+
+ // Get the time delta from the last time and update the time
+ // TODO: Deal with window.blur window.focus to pause animations
+ var time = this._timeSource()
+ var dtSource = time - this._lastSourceTime
+ var dtTime = this._speed * dtSource + (this._time - this._lastStepTime)
+ this._lastSourceTime = time
+
+ // Update the time
+ this._time += dtTime
+ this._lastStepTime = this._time
+ // this.fire('time', this._time)
+
+ // Run all of the runners directly
+ var runnersLeft = false
+ for (var i = 0, len = this._order.length; i < len; i++) {
+ // Get and run the current runner and ignore it if its inactive
+ var runnerInfo = this._runners[this._order[i]]
+ var runner = runnerInfo.runner
+ let dt = dtTime
+
+ // Make sure that we give the actual difference
+ // between runner start time and now
+ let dtToStart = this._time - runnerInfo.start
+
+ // Dont run runner if not started yet
+ if (dtToStart < 0) {
+ runnersLeft = true
+ continue
+ } else if (dtToStart < dt) {
+ // Adjust dt to make sure that animation is on point
+ dt = dtToStart
+ }
+
+ if (!runner.active()) continue
+
+ // If this runner is still going, signal that we need another animation
+ // frame, otherwise, remove the completed runner
+ var finished = runner.step(dt).done
+ if (!finished) {
+ runnersLeft = true
+ // continue
+ } else if (runnerInfo.persist !== true) {
+ // runner is finished. And runner might get removed
+
+ // TODO: Figure out end time of runner
+ var endTime = runner.duration() - runner.time() + this._time
+
+ if (endTime + this._persist < this._time) {
+ // Delete runner and correct index
+ delete this._runners[this._order[i]]
+ this._order.splice(i--, 1) && --len
+ runner.timeline(null)
+ }
+ }
+ }
+
+ // Get the next animation frame to keep the simulation going
+ if (runnersLeft) {
+ this._nextFrame = Animator.frame(this._step.bind(this))
+ } else {
+ this._nextFrame = null
+ }
+ return this
+ }
+
+ // Checks if we are running and continues the animation
+ _continue () {
+ if (this._paused) return this
+ if (!this._nextFrame) {
+ this._nextFrame = Animator.frame(this._step.bind(this))
+ }
+ return this
+ }
+
+ active () {
+ return !!this._nextFrame
+ }
+}
+
+registerMethods({
+ Element: {
+ timeline: function () {
+ this._timeline = (this._timeline || new Timeline())
+ return this._timeline
+ }
+ }
+})
diff --git a/src/animator.js b/src/animator.js
deleted file mode 100644
index eb8ca72..0000000
--- a/src/animator.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/* global requestAnimationFrame */
-
-SVG.Animator = {
- nextDraw: null,
- frames: new SVG.Queue(),
- timeouts: new SVG.Queue(),
- timer: window.performance || window.Date,
- transforms: [],
-
- frame: function (fn) {
- // Store the node
- var node = SVG.Animator.frames.push({ run: fn })
-
- // Request an animation frame if we don't have one
- if (SVG.Animator.nextDraw === null) {
- SVG.Animator.nextDraw = requestAnimationFrame(SVG.Animator._draw)
- }
-
- // Return the node so we can remove it easily
- return node
- },
-
- transform_frame: function (fn, id) {
- SVG.Animator.transforms[id] = fn
- },
-
- timeout: function (fn, delay) {
- delay = delay || 0
-
- // Work out when the event should fire
- var time = SVG.Animator.timer.now() + delay
-
- // Add the timeout to the end of the queue
- var node = SVG.Animator.timeouts.push({ run: fn, time: time })
-
- // Request another animation frame if we need one
- if (SVG.Animator.nextDraw === null) {
- SVG.Animator.nextDraw = requestAnimationFrame(SVG.Animator._draw)
- }
-
- return node
- },
-
- cancelFrame: function (node) {
- SVG.Animator.frames.remove(node)
- },
-
- clearTimeout: function (node) {
- SVG.Animator.timeouts.remove(node)
- },
-
- _draw: function (now) {
- // Run all the timeouts we can run, if they are not ready yet, add them
- // to the end of the queue immediately! (bad timeouts!!! [sarcasm])
- var nextTimeout = null
- var lastTimeout = SVG.Animator.timeouts.last()
- while ((nextTimeout = SVG.Animator.timeouts.shift())) {
- // Run the timeout if its time, or push it to the end
- if (now >= nextTimeout.time) {
- nextTimeout.run()
- } else {
- SVG.Animator.timeouts.push(nextTimeout)
- }
-
- // If we hit the last item, we should stop shifting out more items
- if (nextTimeout === lastTimeout) break
- }
-
- // Run all of the animation frames
- var nextFrame = null
- var lastFrame = SVG.Animator.frames.last()
- while ((nextFrame !== lastFrame) && (nextFrame = SVG.Animator.frames.shift())) {
- nextFrame.run()
- }
-
- SVG.Animator.transforms.forEach(function (el) { el() })
-
- // If we have remaining timeouts or frames, draw until we don't anymore
- SVG.Animator.nextDraw = SVG.Animator.timeouts.first() || SVG.Animator.frames.first()
- ? requestAnimationFrame(SVG.Animator._draw)
- : null
- }
-}
diff --git a/src/arrange.js b/src/arrange.js
deleted file mode 100644
index a908143..0000000
--- a/src/arrange.js
+++ /dev/null
@@ -1,97 +0,0 @@
-// ### This module adds backward / forward functionality to elements.
-
-//
-SVG.extend(SVG.Element, {
- // Get all siblings, including myself
- siblings: function () {
- return this.parent().children()
- },
-
- // Get the curent position siblings
- position: function () {
- return this.parent().index(this)
- },
-
- // Get the next element (will return null if there is none)
- next: function () {
- return this.siblings()[this.position() + 1]
- },
-
- // Get the next element (will return null if there is none)
- prev: function () {
- return this.siblings()[this.position() - 1]
- },
-
- // Send given element one step forward
- forward: function () {
- var i = this.position() + 1
- var p = this.parent()
-
- // move node one step forward
- p.removeElement(this).add(this, i)
-
- // make sure defs node is always at the top
- if (p instanceof SVG.Doc) {
- p.node.appendChild(p.defs().node)
- }
-
- return this
- },
-
- // Send given element one step backward
- backward: function () {
- var i = this.position()
-
- if (i > 0) {
- this.parent().removeElement(this).add(this, i - 1)
- }
-
- return this
- },
-
- // Send given element all the way to the front
- front: function () {
- var p = this.parent()
-
- // Move node forward
- p.node.appendChild(this.node)
-
- // Make sure defs node is always at the top
- if (p instanceof SVG.Doc) {
- p.node.appendChild(p.defs().node)
- }
-
- return this
- },
-
- // Send given element all the way to the back
- back: function () {
- if (this.position() > 0) {
- this.parent().removeElement(this).add(this, 0)
- }
-
- return this
- },
-
- // Inserts a given element before the targeted element
- before: function (element) {
- element.remove()
-
- var i = this.position()
-
- this.parent().add(element, i)
-
- return this
- },
-
- // Insters a given element after the targeted element
- after: function (element) {
- element.remove()
-
- var i = this.position()
-
- this.parent().add(element, i + 1)
-
- return this
- }
-})
diff --git a/src/array.js b/src/array.js
deleted file mode 100644
index aa43d5c..0000000
--- a/src/array.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/* global arrayClone */
-
-// Module for array conversion
-SVG.Array = function (array, fallback) {
- array = (array || []).valueOf()
-
- // if array is empty and fallback is provided, use fallback
- if (array.length === 0 && fallback) {
- array = fallback.valueOf()
- }
-
- // parse array
- this.value = this.parse(array)
-}
-
-SVG.extend(SVG.Array, {
- // Make array morphable
- morph: function (array) {
- this.destination = this.parse(array)
-
- // normalize length of arrays
- if (this.value.length !== this.destination.length) {
- var lastValue = this.value[this.value.length - 1]
- var lastDestination = this.destination[this.destination.length - 1]
-
- while (this.value.length > this.destination.length) {
- this.destination.push(lastDestination)
- }
- while (this.value.length < this.destination.length) {
- this.value.push(lastValue)
- }
- }
-
- return this
- },
- // Clean up any duplicate points
- settle: function () {
- // find all unique values
- for (var i = 0, il = this.value.length, seen = []; i < il; i++) {
- if (seen.indexOf(this.value[i]) === -1) {
- seen.push(this.value[i])
- }
- }
-
- // set new value
- this.value = seen
- return seen
- },
- // Get morphed array at given position
- at: function (pos) {
- // make sure a destination is defined
- if (!this.destination) return this
-
- // generate morphed array
- for (var i = 0, il = this.value.length, array = []; i < il; i++) {
- array.push(this.value[i] + (this.destination[i] - this.value[i]) * pos)
- }
-
- return new SVG.Array(array)
- },
- toArray: function () {
- return this.value
- },
- // Convert array to string
- toString: function () {
- return this.value.join(' ')
- },
- // Real value
- valueOf: function () {
- return this.value
- },
- // Parse whitespace separated string
- parse: function (array) {
- array = array.valueOf()
-
- // if already is an array, no need to parse it
- if (Array.isArray(array)) return array
-
- return array.trim().split(SVG.regex.delimiter).map(parseFloat)
- },
- // Reverse array
- reverse: function () {
- this.value.reverse()
-
- return this
- },
- clone: function () {
- var clone = new this.constructor()
- clone.value = arrayClone(this.value)
- return clone
- }
-})
diff --git a/src/attr.js b/src/attr.js
deleted file mode 100644
index 19c7525..0000000
--- a/src/attr.js
+++ /dev/null
@@ -1,72 +0,0 @@
-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 {
- // convert image fill and stroke to patterns
- if (a === 'fill' || a === 'stroke') {
- if (SVG.regex.isImage.test(v)) {
- v = this.doc().defs().image(v)
- }
-
- 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)
- } else if (SVG.Color.isColor(v)) {
- // ensure full hex color
- v = new SVG.Color(v)
- } else if (Array.isArray(v)) {
- // parse array values
- 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
- }
-})
diff --git a/src/bare.js b/src/bare.js
deleted file mode 100644
index 393ce6e..0000000
--- a/src/bare.js
+++ /dev/null
@@ -1,43 +0,0 @@
-
-SVG.Bare = SVG.invent({
- // Initialize
- create: function (element, inherit) {
- // construct element
- SVG.Element.call(this, SVG.create(element))
-
- // inherit custom methods
- if (inherit) {
- for (var method in inherit.prototype) {
- if (typeof inherit.prototype[method] === 'function') {
- this[method] = inherit.prototype[method]
- }
- }
- }
- },
-
- // Inherit from
- inherit: SVG.Element,
-
- // Add methods
- extend: {
- // Insert some plain text
- words: function (text) {
- // remove contents
- while (this.node.hasChildNodes()) {
- this.node.removeChild(this.node.lastChild)
- }
-
- // create text node
- this.node.appendChild(document.createTextNode(text))
-
- return this
- }
- }
-})
-
-SVG.extend(SVG.Parent, {
- // Create an element that is not described by SVG.js
- element: function (element, inherit) {
- return this.put(new SVG.Bare(element, inherit))
- }
-})
diff --git a/src/boxes.js b/src/boxes.js
deleted file mode 100644
index a9247ef..0000000
--- a/src/boxes.js
+++ /dev/null
@@ -1,141 +0,0 @@
-/* globals fullBox, domContains, isNulledBox, Exception */
-
-SVG.Box = SVG.invent({
- create: function (source) {
- var base = [0, 0, 0, 0]
- source = typeof source === 'string' ? source.split(SVG.regex.delimiter).map(parseFloat)
- : Array.isArray(source) ? source
- : typeof source === 'object' ? [source.left != null ? source.left
- : source.x, source.top != null ? source.top : source.y, source.width, source.height]
- : arguments.length === 4 ? [].slice.call(arguments)
- : base
-
- this.x = source[0]
- this.y = source[1]
- this.width = source[2]
- this.height = source[3]
-
- // add center, right, bottom...
- fullBox(this)
- },
- extend: {
- // Merge rect box with another, return a new instance
- merge: function (box) {
- var x = Math.min(this.x, box.x)
- var y = Math.min(this.y, box.y)
-
- return new SVG.Box(
- x, y,
- Math.max(this.x + this.width, box.x + box.width) - x,
- Math.max(this.y + this.height, box.y + box.height) - y
- )
- },
-
- transform: function (m) {
- var xMin = Infinity
- var xMax = -Infinity
- var yMin = Infinity
- var yMax = -Infinity
-
- var pts = [
- new SVG.Point(this.x, this.y),
- new SVG.Point(this.x2, this.y),
- new SVG.Point(this.x, this.y2),
- new SVG.Point(this.x2, this.y2)
- ]
-
- pts.forEach(function (p) {
- p = p.transform(m)
- xMin = Math.min(xMin, p.x)
- xMax = Math.max(xMax, p.x)
- yMin = Math.min(yMin, p.y)
- yMax = Math.max(yMax, p.y)
- })
-
- return new SVG.Box(
- xMin, yMin,
- xMax - xMin,
- yMax - yMin
- )
- },
-
- addOffset: function () {
- // offset by window scroll position, because getBoundingClientRect changes when window is scrolled
- this.x += window.pageXOffset
- this.y += window.pageYOffset
- return this
- },
- toString: function () {
- return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height
- },
- toArray: function () {
- return [this.x, this.y, this.width, this.height]
- },
- morph: function (x, y, width, height) {
- this.destination = new SVG.Box(x, y, width, height)
- return this
- },
-
- at: function (pos) {
- if (!this.destination) return this
-
- return new SVG.Box(
- this.x + (this.destination.x - this.x) * pos
- , this.y + (this.destination.y - this.y) * pos
- , this.width + (this.destination.width - this.width) * pos
- , this.height + (this.destination.height - this.height) * pos
- )
- }
- },
-
- // Define Parent
- parent: SVG.Element,
-
- // Constructor
- construct: {
- // Get bounding box
- bbox: function () {
- var box
-
- try {
- // find native bbox
- box = this.node.getBBox()
-
- if (isNulledBox(box) && !domContains(this.node)) {
- throw new Exception('Element not in the dom')
- }
- } catch (e) {
- try {
- var clone = this.clone(SVG.parser().svg).show()
- box = clone.node.getBBox()
- clone.remove()
- } catch (e) {
- console.warn('Getting a bounding box of this element is not possible')
- }
- }
-
- return new SVG.Box(box)
- },
-
- rbox: function (el) {
- // IE11 throws an error when element not in dom
- try {
- var box = new SVG.Box(this.node.getBoundingClientRect())
- if (el) return box.transform(el.screenCTM().inverse())
- return box.addOffset()
- } catch (e) {
- return new SVG.Box()
- }
- }
- }
-})
-
-SVG.extend([SVG.Doc, SVG.Symbol, SVG.Image, SVG.Pattern, SVG.Marker, SVG.ForeignObject, SVG.View], {
- viewbox: function (x, y, width, height) {
- // act as getter
- if (x == null) return new SVG.Box(this.attr('viewBox'))
-
- // act as setter
- return this.attr('viewBox', new SVG.Box(x, y, width, height))
- }
-})
diff --git a/src/clip.js b/src/clip.js
deleted file mode 100644
index 63fff74..0000000
--- a/src/clip.js
+++ /dev/null
@@ -1,53 +0,0 @@
-SVG.ClipPath = SVG.invent({
- // Initialize node
- create: 'clipPath',
-
- // Inherit from
- inherit: SVG.Container,
-
- // Add class methods
- extend: {
- // Unclip all clipped elements and remove itself
- remove: function () {
- // unclip all targets
- this.targets().forEach(function (el) {
- el.unclip()
- })
-
- // remove clipPath from parent
- return SVG.Element.prototype.remove.call(this)
- },
-
- targets: function () {
- return SVG.select('svg [clip-path*="' + this.id() + '"]')
- }
- },
-
- // Add parent method
- construct: {
- // Create clipping element
- clip: function () {
- return this.defs().put(new SVG.ClipPath())
- }
- }
-})
-
-//
-SVG.extend(SVG.Element, {
- // Distribute clipPath to svg element
- clipWith: function (element) {
- // use given clip or create a new one
- var clipper = element instanceof SVG.ClipPath ? element : this.parent().clip().add(element)
-
- // apply mask
- return this.attr('clip-path', 'url("#' + clipper.id() + '")')
- },
- // Unclip element
- unclip: function () {
- return this.attr('clip-path', null)
- },
- clipper: function () {
- return this.reference('clip-path')
- }
-
-})
diff --git a/src/color.js b/src/color.js
deleted file mode 100644
index 43bafcb..0000000
--- a/src/color.js
+++ /dev/null
@@ -1,148 +0,0 @@
-/* globals fullHex, compToHex */
-
-/*
-
-Color {
- constructor (a, b, c, space) {
- space: 'hsl'
- a: 30
- b: 20
- c: 10
- },
-
- toRgb () { return new Color in rgb space }
- toHsl () { return new Color in hsl space }
- toLab () { return new Color in lab space }
-
- toArray () { [space, a, b, c] }
- fromArray () { convert it back }
-}
-
-// Conversions aren't always exact because of monitor profiles etc...
-new Color(h, s, l, 'hsl') !== new Color(r, g, b).hsl()
-new Color(100, 100, 100, [space])
-new Color('hsl(30, 20, 10)')
-
-// Sugar
-SVG.rgb(30, 20, 50).lab()
-SVG.hsl()
-SVG.lab('rgb(100, 100, 100)')
-*/
-
-// Module for color convertions
-SVG.Color = function (color, g, b) {
- var match
-
- // initialize defaults
- this.r = 0
- this.g = 0
- this.b = 0
-
- if (!color) return
-
- // parse color
- if (typeof color === 'string') {
- if (SVG.regex.isRgb.test(color)) {
- // get rgb values
- match = SVG.regex.rgb.exec(color.replace(SVG.regex.whitespace, ''))
-
- // parse numeric values
- this.r = parseInt(match[1])
- this.g = parseInt(match[2])
- this.b = parseInt(match[3])
- } else if (SVG.regex.isHex.test(color)) {
- // get hex values
- match = SVG.regex.hex.exec(fullHex(color))
-
- // parse numeric values
- this.r = parseInt(match[1], 16)
- this.g = parseInt(match[2], 16)
- this.b = parseInt(match[3], 16)
- }
- } else if (Array.isArray(color)) {
- this.r = color[0]
- this.g = color[1]
- this.b = color[2]
- } else if (typeof color === 'object') {
- this.r = color.r
- this.g = color.g
- this.b = color.b
- } else if (arguments.length === 3) {
- this.r = color
- this.g = g
- this.b = b
- }
-}
-
-SVG.extend(SVG.Color, {
- // Default to hex conversion
- toString: function () {
- return this.toHex()
- },
- toArray: function () {
- return [this.r, this.g, this.b]
- },
- fromArray: function (a) {
- return new SVG.Color(a)
- },
- // Build hex value
- toHex: function () {
- return '#' +
- compToHex(Math.round(this.r)) +
- compToHex(Math.round(this.g)) +
- compToHex(Math.round(this.b))
- },
- // Build rgb value
- toRgb: function () {
- return 'rgb(' + [this.r, this.g, this.b].join() + ')'
- },
- // Calculate true brightness
- brightness: function () {
- return (this.r / 255 * 0.30) +
- (this.g / 255 * 0.59) +
- (this.b / 255 * 0.11)
- },
- // Make color morphable
- morph: function (color) {
- this.destination = new SVG.Color(color)
-
- return this
- },
- // Get morphed color at given position
- at: function (pos) {
- // make sure a destination is defined
- if (!this.destination) return this
-
- // normalise pos
- pos = pos < 0 ? 0 : pos > 1 ? 1 : pos
-
- // generate morphed color
- return new SVG.Color({
- r: ~~(this.r + (this.destination.r - this.r) * pos),
- g: ~~(this.g + (this.destination.g - this.g) * pos),
- b: ~~(this.b + (this.destination.b - this.b) * pos)
- })
- }
-
-})
-
-// Testers
-
-// Test if given value is a color string
-SVG.Color.test = function (color) {
- color += ''
- return SVG.regex.isHex.test(color) ||
- SVG.regex.isRgb.test(color)
-}
-
-// Test if given value is a rgb object
-SVG.Color.isRgb = function (color) {
- return color && typeof color.r === 'number' &&
- typeof color.g === 'number' &&
- typeof color.b === 'number'
-}
-
-// Test if given value is a color
-SVG.Color.isColor = function (color) {
- return SVG.Color.isRgb(color) || SVG.Color.test(color)
-}
diff --git a/src/container.js b/src/container.js
deleted file mode 100644
index 8b324bd..0000000
--- a/src/container.js
+++ /dev/null
@@ -1,9 +0,0 @@
-SVG.Container = SVG.invent({
- // Initialize node
- create: function (node) {
- SVG.Element.call(this, node)
- },
-
- // Inherit from
- inherit: SVG.Parent
-})
diff --git a/src/controller.js b/src/controller.js
deleted file mode 100644
index 842c772..0000000
--- a/src/controller.js
+++ /dev/null
@@ -1,193 +0,0 @@
-
-// c = {
-// finished: Whether or not we are finished
-// }
-
-/***
-Base Class
-==========
-The base stepper class that will be
-***/
-
-function makeSetterGetter (k, f) {
- return function (v) {
- if (v == null) return this[v]
- this[k] = v
- if (f) f.call(this)
- return this
- }
-}
-
-SVG.Stepper = SVG.invent({
- create: function () {}
-})
-
-/***
-Easing Functions
-================
-***/
-
-SVG.Ease = SVG.invent({
- inherit: SVG.Stepper,
-
- create: function (fn) {
- SVG.Stepper.call(this, fn)
-
- this.ease = SVG.easing[fn || SVG.defaults.timeline.ease] || fn
- },
-
- extend: {
-
- step: function (from, to, pos) {
- if (typeof from !== 'number') {
- return pos < 1 ? from : to
- }
- return from + (to - from) * this.ease(pos)
- },
-
- done: function (dt, c) {
- return false
- }
- }
-})
-
-SVG.easing = {
- '-': function (pos) { return pos },
- '<>': function (pos) { return -Math.cos(pos * Math.PI) / 2 + 0.5 },
- '>': function (pos) { return Math.sin(pos * Math.PI / 2) },
- '<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 },
- bezier: function (t0, x0, t1, x1) {
- return function (t) {
- // TODO: FINISH
- }
- }
-}
-
-/***
-Controller Types
-================
-***/
-
-SVG.Controller = SVG.invent({
- inherit: SVG.Stepper,
-
- create: function (fn) {
- SVG.Stepper.call(this, fn)
- this.stepper = fn
- },
-
- extend: {
-
- step: function (current, target, dt, c) {
- return this.stepper(current, target, dt, c)
- },
-
- done: function (c) {
- return c.done
- }
- }
-})
-
-function recalculate () {
- // Apply the default parameters
- var duration = (this._duration || 500) / 1000
- var overshoot = this._overshoot || 0
-
- // Calculate the PID natural response
- var eps = 1e-10
- var pi = Math.PI
- var os = Math.log(overshoot / 100 + eps)
- var zeta = -os / Math.sqrt(pi * pi + os * os)
- var wn = 3.9 / (zeta * duration)
-
- // Calculate the Spring values
- this.d = 2 * zeta * wn
- this.k = wn * wn
-}
-
-SVG.Spring = SVG.invent({
- inherit: SVG.Controller,
-
- create: function (duration, overshoot) {
- this.duration(duration || 500)
- .overshoot(overshoot || 0)
- },
-
- extend: {
- step: function (current, target, dt, c) {
- if (typeof current === 'string') return current
- c.done = dt === Infinity
- if (dt === Infinity) return target
- if (dt === 0) return current
-
- if (dt > 100) dt = 16
-
- dt /= 1000
-
- // Get the previous velocity
- var velocity = c.velocity || 0
-
- // Apply the control to get the new position and store it
- var acceleration = -this.d * velocity - this.k * (current - target)
- var newPosition = current +
- velocity * dt +
- acceleration * dt * dt / 2
-
- // Store the velocity
- c.velocity = velocity + acceleration * dt
-
- // Figure out if we have converged, and if so, pass the value
- c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002
- return c.done ? target : newPosition
- },
-
- duration: makeSetterGetter('_duration', recalculate),
- overshoot: makeSetterGetter('_overshoot', recalculate)
- }
-})
-
-SVG.PID = SVG.invent({
- inherit: SVG.Controller,
-
- create: function (p, i, d, windup) {
- SVG.Controller.call(this)
-
- p = p == null ? 0.1 : p
- i = i == null ? 0.01 : i
- d = d == null ? 0 : d
- windup = windup == null ? 1000 : windup
- this.p(p).i(i).d(d).windup(windup)
- },
-
- extend: {
- step: function (current, target, dt, c) {
- if (typeof current === 'string') return current
- c.done = dt === Infinity
-
- if (dt === Infinity) return target
- if (dt === 0) return current
-
- var p = target - current
- var i = (c.integral || 0) + p * dt
- var d = (p - (c.error || 0)) / dt
- var windup = this.windup
-
- // antiwindup
- if (windup !== false) {
- i = Math.max(-windup, Math.min(i, windup))
- }
-
- c.error = p
- c.integral = i
-
- c.done = Math.abs(p) < 0.001
-
- return c.done ? target : current + (this.P * p + this.I * i + this.D * d)
- },
-
- windup: makeSetterGetter('windup'),
- p: makeSetterGetter('P'),
- i: makeSetterGetter('I'),
- d: makeSetterGetter('D')
- }
-})
diff --git a/src/css.js b/src/css.js
deleted file mode 100644
index c549bd5..0000000
--- a/src/css.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/* global camelCase */
-
-SVG.extend(SVG.Element, {
- // Dynamic style generator
- css: function (s, v) {
- var ret = {}
- var t, i
- if (arguments.length === 0) {
- // get full style as object
- this.node.style.cssText.split(/\s*;\s*/).filter(function (el) { return !!el.length }).forEach(function (el) {
- t = el.split(/\s*:\s*/)
- ret[t[0]] = t[1]
- })
- return ret
- }
-
- if (arguments.length < 2) {
- // get style properties in the array
- if (Array.isArray(s)) {
- for (i = s.length; i--;) {
- ret[camelCase(s[i])] = this.node.style[camelCase(s[i])]
- }
- return ret
- }
-
- // get style for property
- if (typeof s === 'string') {
- return this.node.style[camelCase(s)]
- }
-
- // set styles in object
- if (typeof s === 'object') {
- for (i in s) {
- // set empty string if null/undefined/'' was given
- this.node.style[camelCase(i)] = (s[i] == null || SVG.regex.isBlank.test(s[i])) ? '' : s[i]
- }
- }
- }
-
- // set style for property
- if (arguments.length === 2) {
- this.node.style[camelCase(s)] = (v == null || SVG.regex.isBlank.test(v)) ? '' : v
- }
-
- return this
- }
-})
diff --git a/src/data.js b/src/data.js
deleted file mode 100644
index f7fcd55..0000000
--- a/src/data.js
+++ /dev/null
@@ -1,25 +0,0 @@
-
-SVG.extend(SVG.Element, {
- // Store data values on svg nodes
- data: function (a, v, r) {
- if (typeof a === 'object') {
- for (v in a) {
- this.data(v, a[v])
- }
- } else if (arguments.length < 2) {
- try {
- return JSON.parse(this.attr('data-' + a))
- } catch (e) {
- return this.attr('data-' + a)
- }
- } else {
- this.attr('data-' + a,
- v === null ? null
- : r === true || typeof v === 'string' || typeof v === 'number' ? v
- : JSON.stringify(v)
- )
- }
-
- return this
- }
-})
diff --git a/src/default.js b/src/default.js
deleted file mode 100644
index e82d1db..0000000
--- a/src/default.js
+++ /dev/null
@@ -1,51 +0,0 @@
-
-SVG.void = function () {}
-
-SVG.defaults = {
-
- // Default animation values
- timeline: {
- duration: 400,
- ease: '>',
- delay: 0
- },
-
- // Default attribute values
- attrs: {
-
- // fill and stroke
- 'fill-opacity': 1,
- 'stroke-opacity': 1,
- 'stroke-width': 0,
- 'stroke-linejoin': 'miter',
- 'stroke-linecap': 'butt',
- fill: '#000000',
- stroke: '#000000',
- opacity: 1,
-
- // position
- x: 0,
- y: 0,
- cx: 0,
- cy: 0,
-
- // size
- width: 0,
- height: 0,
-
- // radius
- r: 0,
- rx: 0,
- ry: 0,
-
- // gradient
- offset: 0,
- 'stop-opacity': 1,
- 'stop-color': '#000000',
-
- // text
- 'font-size': 16,
- 'font-family': 'Helvetica, Arial, sans-serif',
- 'text-anchor': 'start'
- }
-}
diff --git a/src/defs.js b/src/defs.js
deleted file mode 100644
index 3d6ebb9..0000000
--- a/src/defs.js
+++ /dev/null
@@ -1,7 +0,0 @@
-SVG.Defs = SVG.invent({
- // Initialize node
- create: 'defs',
-
- // Inherit from
- inherit: SVG.Container
-})
diff --git a/src/doc.js b/src/doc.js
deleted file mode 100644
index 423204f..0000000
--- a/src/doc.js
+++ /dev/null
@@ -1,70 +0,0 @@
-SVG.Doc = SVG.invent({
- // Initialize node
- create: function (node) {
- SVG.Element.call(this, node || SVG.create('svg'))
-
- // set svg element attributes and ensure defs node
- this.namespace()
- },
-
- // Inherit from
- inherit: SVG.Container,
-
- // Add class methods
- extend: {
- isRoot: function () {
- return !this.node.parentNode || !(this.node.parentNode instanceof window.SVGElement) || this.node.parentNode.nodeName === '#document'
- },
- // Check if this is a root svg. If not, call docs from this element
- doc: function () {
- if (this.isRoot()) return this
- return SVG.Element.prototype.doc.call(this)
- },
- // Add namespaces
- namespace: function () {
- if (!this.isRoot()) return this.doc().namespace()
- return this
- .attr({ xmlns: SVG.ns, version: '1.1' })
- .attr('xmlns:xlink', SVG.xlink, SVG.xmlns)
- .attr('xmlns:svgjs', SVG.svgjs, SVG.xmlns)
- },
- // Creates and returns defs element
- defs: function () {
- if (!this.isRoot()) return this.doc().defs()
- return SVG.adopt(this.node.getElementsByTagName('defs')[0]) || this.put(new SVG.Defs())
- },
- // custom parent method
- parent: function (type) {
- if (this.isRoot()) {
- return this.node.parentNode.nodeName === '#document' ? null : this.node.parentNode
- }
-
- return SVG.Element.prototype.parent.call(this, type)
- },
- // Removes the doc from the DOM
- remove: function () {
- if (!this.isRoot()) {
- return SVG.Element.prototype.remove.call(this)
- }
-
- if (this.parent()) {
- this.parent().removeChild(this.node)
- }
-
- return this
- },
- clear: function () {
- // remove children
- while (this.node.hasChildNodes()) {
- this.node.removeChild(this.node.lastChild)
- }
- return this
- }
- },
- construct: {
- // Create nested svg document
- nested: function () {
- return this.put(new SVG.Doc())
- }
- }
-})
diff --git a/src/element.js b/src/element.js
deleted file mode 100644
index 406a35e..0000000
--- a/src/element.js
+++ /dev/null
@@ -1,331 +0,0 @@
-/* global proportionalSize, assignNewId, createElement, matches, is */
-
-SVG.Element = SVG.invent({
- inherit: SVG.EventTarget,
-
- // Initialize node
- create: function (node) {
- // event listener
- this.events = {}
-
- // initialize data object
- this.dom = {}
-
- // create circular reference
- this.node = node
- if (this.node) {
- this.type = node.nodeName
- this.node.instance = this
- this.events = node.events || {}
-
- if (node.hasAttribute('svgjs:data')) {
- // pull svgjs data from the dom (getAttributeNS doesn't work in html5)
- this.setData(JSON.parse(node.getAttribute('svgjs:data')) || {})
- }
- }
- },
-
- // Add class methods
- extend: {
- // Move over x-axis
- x: function (x) {
- return this.attr('x', x)
- },
-
- // Move over y-axis
- y: function (y) {
- return this.attr('y', y)
- },
-
- // Move by center over x-axis
- cx: function (x) {
- return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2)
- },
-
- // Move by center over y-axis
- cy: function (y) {
- return y == null
- ? this.y() + this.height() / 2
- : this.y(y - this.height() / 2)
- },
-
- // Move element to given x and y values
- move: function (x, y) {
- return this.x(x).y(y)
- },
-
- // Move element by its center
- center: function (x, y) {
- return this.cx(x).cy(y)
- },
-
- // Set width of element
- width: function (width) {
- return this.attr('width', width)
- },
-
- // Set height of element
- height: function (height) {
- return this.attr('height', height)
- },
-
- // Set element size to given width and height
- size: function (width, height) {
- var p = proportionalSize(this, width, height)
-
- return this
- .width(new SVG.Number(p.width))
- .height(new SVG.Number(p.height))
- },
-
- // Clone element
- clone: function (parent) {
- // write dom data to the dom so the clone can pickup the data
- this.writeDataToDom()
-
- // clone element and assign new id
- var clone = assignNewId(this.node.cloneNode(true))
-
- // insert the clone in the given parent or after myself
- if (parent) parent.add(clone)
- else this.after(clone)
-
- return clone
- },
-
- // Remove element
- remove: function () {
- if (this.parent()) { this.parent().removeElement(this) }
-
- return this
- },
-
- // Replace element
- replace: function (element) {
- this.after(element).remove()
-
- return element
- },
-
- // Add element to given container and return self
- addTo: function (parent) {
- return createElement(parent).put(this)
- },
-
- // Add element to given container and return container
- putIn: function (parent) {
- return createElement(parent).add(this)
- },
-
- // Get / set id
- id: function (id) {
- // generate new id if no id set
- if (typeof id === 'undefined' && !this.node.id) {
- this.node.id = SVG.eid(this.type)
- }
-
- // dont't set directly width this.node.id to make `null` work correctly
- return this.attr('id', id)
- },
-
- // Checks whether the given point inside the bounding box of the element
- inside: function (x, y) {
- var box = this.bbox()
-
- return x > box.x &&
- y > box.y &&
- x < box.x + box.width &&
- y < box.y + box.height
- },
-
- // Show element
- show: function () {
- return this.css('display', '')
- },
-
- // Hide element
- hide: function () {
- return this.css('display', 'none')
- },
-
- // Is element visible?
- visible: function () {
- return this.css('display') !== 'none'
- },
-
- // Return id on string conversion
- toString: function () {
- return this.id()
- },
-
- // Return array of classes on the node
- classes: function () {
- var attr = this.attr('class')
- return attr == null ? [] : attr.trim().split(SVG.regex.delimiter)
- },
-
- // Return true if class exists on the node, false otherwise
- hasClass: function (name) {
- return this.classes().indexOf(name) !== -1
- },
-
- // Add class to the node
- addClass: function (name) {
- if (!this.hasClass(name)) {
- var array = this.classes()
- array.push(name)
- this.attr('class', array.join(' '))
- }
-
- return this
- },
-
- // Remove class from the node
- removeClass: function (name) {
- if (this.hasClass(name)) {
- this.attr('class', this.classes().filter(function (c) {
- return c !== name
- }).join(' '))
- }
-
- return this
- },
-
- // Toggle the presence of a class on the node
- toggleClass: function (name) {
- return this.hasClass(name) ? this.removeClass(name) : this.addClass(name)
- },
-
- // Get referenced element form attribute value
- reference: function (attr) {
- return SVG.get(this.attr(attr))
- },
-
- // Returns the parent element instance
- parent: function (type) {
- var parent = this
-
- // check for parent
- if (!parent.node.parentNode) return null
-
- // get parent element
- parent = SVG.adopt(parent.node.parentNode)
-
- if (!type) return parent
-
- // loop trough ancestors if type is given
- while (parent && parent.node instanceof window.SVGElement) {
- if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent
- parent = SVG.adopt(parent.node.parentNode)
- }
- },
-
- // Get parent document
- doc: function () {
- var p = this.parent(SVG.Doc)
- return p && p.doc()
- },
-
- // Get defs
- defs: function () {
- return this.doc().defs()
- },
-
- // return array of all ancestors of given type up to the root svg
- parents: function (type) {
- var parents = []
- var parent = this
-
- do {
- parent = parent.parent(type)
- if (!parent || !parent.node) break
-
- parents.push(parent)
- } while (parent.parent)
-
- return parents
- },
-
- // matches the element vs a css selector
- matches: function (selector) {
- return matches(this.node, selector)
- },
-
- // Returns the svg node to call native svg methods on it
- native: function () {
- return this.node
- },
-
- // Import raw svg
- svg: function (svg) {
- var well, len
-
- // act as a setter if svg is given
- if (typeof svg === 'string' && this instanceof SVG.Parent) {
- // create temporary holder
- well = document.createElementNS(SVG.ns, 'svg')
- // dump raw svg
- well.innerHTML = svg
-
- // transplant nodes
- for (len = well.children.length; len--;) {
- this.node.appendChild(well.firstElementChild)
- }
- // otherwise act as a getter
- } else {
- // expose node modifiers
- if (typeof svg === 'function') {
- this.each(function () {
- well = svg(this)
-
- // If modifier returns false, discard node
- if (well === false) {
- this.remove()
-
- // If modifier returns new node, use it
- } else if (well && well !== this) {
- this.replace(well)
- }
- }, true)
- }
-
- // write svgjs data to the dom
- this.writeDataToDom()
-
- return this.node.outerHTML
- }
-
- return this
- },
-
- // write svgjs data to the dom
- writeDataToDom: function () {
- // dump variables recursively
- if (this.is(SVG.Parent)) {
- this.each(function () {
- this.writeDataToDom()
- })
- }
-
- // remove previously set data
- this.node.removeAttribute('svgjs:data')
-
- if (Object.keys(this.dom).length) {
- this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)) // see #428
- }
- return this
- },
-
- // set given data to the elements data property
- setData: function (o) {
- this.dom = o
- return this
- },
- is: function (obj) {
- return is(this, obj)
- },
- getEventTarget: function () {
- return this.node
- }
- }
-})
diff --git a/src/elements/A.js b/src/elements/A.js
new file mode 100644
index 0000000..68da597
--- /dev/null
+++ b/src/elements/A.js
@@ -0,0 +1,43 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import { xlink } from '../modules/core/namespaces.js'
+import Container from './Container.js'
+
+export default class A extends Container {
+ constructor (node) {
+ super(nodeOrNew('a', node), A)
+ }
+
+ // Link url
+ to (url) {
+ return this.attr('href', url, xlink)
+ }
+
+ // Link target attribute
+ target (target) {
+ return this.attr('target', target)
+ }
+}
+
+registerMethods({
+ Container: {
+ // Create a hyperlink element
+ link: function (url) {
+ return this.put(new A()).to(url)
+ }
+ },
+ Element: {
+ // Create a hyperlink element
+ linkTo: function (url) {
+ var link = new A()
+
+ if (typeof url === 'function') { url.call(link, link) } else {
+ link.to(url)
+ }
+
+ return this.parent().put(link).put(this)
+ }
+ }
+})
+
+register(A)
diff --git a/src/elements/Bare.js b/src/elements/Bare.js
new file mode 100644
index 0000000..43fc075
--- /dev/null
+++ b/src/elements/Bare.js
@@ -0,0 +1,30 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import Container from './Container.js'
+
+export default class Bare extends Container {
+ constructor (node) {
+ super(nodeOrNew(node, typeof node === 'string' ? null : node), Bare)
+ }
+
+ words (text) {
+ // remove contents
+ while (this.node.hasChildNodes()) {
+ this.node.removeChild(this.node.lastChild)
+ }
+
+ // create text node
+ this.node.appendChild(document.createTextNode(text))
+
+ return this
+ }
+}
+
+register(Bare)
+
+registerMethods('Container', {
+ // Create an element that is not described by SVG.js
+ element (node, inherit) {
+ return this.put(new Bare(node, inherit))
+ }
+})
diff --git a/src/elements/Circle.js b/src/elements/Circle.js
new file mode 100644
index 0000000..c296885
--- /dev/null
+++ b/src/elements/Circle.js
@@ -0,0 +1,40 @@
+import { cx, cy, height, size, width, x, y } from '../modules/core/circled.js'
+import { extend, nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import SVGNumber from '../types/SVGNumber.js'
+import Shape from './Shape.js'
+
+export default class Circle extends Shape {
+ constructor (node) {
+ super(nodeOrNew('circle', node), Circle)
+ }
+
+ radius (r) {
+ return this.attr('r', r)
+ }
+
+ // Radius x value
+ rx (rx) {
+ return this.attr('r', rx)
+ }
+
+ // Alias radius x value
+ ry (ry) {
+ return this.rx(ry)
+ }
+}
+
+extend(Circle, { x, y, cx, cy, width, height, size })
+
+registerMethods({
+ Element: {
+ // Create circle element
+ circle (size) {
+ return this.put(new Circle())
+ .radius(new SVGNumber(size).divide(2))
+ .move(0, 0)
+ }
+ }
+})
+
+register(Circle)
diff --git a/src/elements/ClipPath.js b/src/elements/ClipPath.js
new file mode 100644
index 0000000..2828d6e
--- /dev/null
+++ b/src/elements/ClipPath.js
@@ -0,0 +1,57 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import Container from './Container.js'
+import baseFind from '../modules/core/selector.js'
+
+export default class ClipPath extends Container {
+ constructor (node) {
+ super(nodeOrNew('clipPath', node), ClipPath)
+ }
+
+ // Unclip all clipped elements and remove itself
+ remove () {
+ // unclip all targets
+ this.targets().forEach(function (el) {
+ el.unclip()
+ })
+
+ // remove clipPath from parent
+ return super.remove()
+ }
+
+ targets () {
+ return baseFind('svg [clip-path*="' + this.id() + '"]')
+ }
+}
+
+registerMethods({
+ Container: {
+ // Create clipping element
+ clip: function () {
+ return this.defs().put(new ClipPath())
+ }
+ },
+ Element: {
+ // Distribute clipPath to svg element
+ clipWith (element) {
+ // use given clip or create a new one
+ let clipper = element instanceof ClipPath
+ ? element
+ : this.parent().clip().add(element)
+
+ // apply mask
+ return this.attr('clip-path', 'url("#' + clipper.id() + '")')
+ },
+
+ // Unclip element
+ unclip () {
+ return this.attr('clip-path', null)
+ },
+
+ clipper () {
+ return this.reference('clip-path')
+ }
+ }
+})
+
+register(ClipPath)
diff --git a/src/elements/Container.js b/src/elements/Container.js
new file mode 100644
index 0000000..cdf8495
--- /dev/null
+++ b/src/elements/Container.js
@@ -0,0 +1,27 @@
+import Element from './Element.js'
+
+export default class Container extends Element {
+ flatten (parent) {
+ this.each(function () {
+ if (this instanceof Container) return this.flatten(parent).ungroup(parent)
+ return this.toParent(parent)
+ })
+
+ // we need this so that Doc does not get removed
+ this.node.firstElementChild || this.remove()
+
+ return this
+ }
+
+ ungroup (parent) {
+ parent = parent || this.parent()
+
+ this.each(function () {
+ return this.toParent(parent)
+ })
+
+ this.remove()
+
+ return this
+ }
+}
diff --git a/src/elements/Defs.js b/src/elements/Defs.js
new file mode 100644
index 0000000..58932cb
--- /dev/null
+++ b/src/elements/Defs.js
@@ -0,0 +1,13 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import Container from './Container.js'
+
+export default class Defs extends Container {
+ constructor (node) {
+ super(nodeOrNew('defs', node), Defs)
+ }
+
+ flatten () { return this }
+ ungroup () { return this }
+}
+
+register(Defs)
diff --git a/src/elements/Doc.js b/src/elements/Doc.js
new file mode 100644
index 0000000..8d450ce
--- /dev/null
+++ b/src/elements/Doc.js
@@ -0,0 +1,72 @@
+import { adopt, nodeOrNew, register } from '../utils/adopter.js'
+import { ns, svgjs, xlink, xmlns } from '../modules/core/namespaces.js'
+import { registerMethods } from '../utils/methods.js'
+import Container from './Container.js'
+import Defs from './Defs.js'
+
+export default class Doc extends Container {
+ constructor (node) {
+ super(nodeOrNew('svg', node), Doc)
+ this.namespace()
+ }
+
+ isRoot () {
+ return !this.node.parentNode ||
+ !(this.node.parentNode instanceof window.SVGElement) ||
+ this.node.parentNode.nodeName === '#document'
+ }
+
+ // Check if this is a root svg
+ // If not, call docs from this element
+ doc () {
+ if (this.isRoot()) return this
+ return super.doc()
+ }
+
+ // Add namespaces
+ namespace () {
+ if (!this.isRoot()) return this.doc().namespace()
+ return this
+ .attr({ xmlns: ns, version: '1.1' })
+ .attr('xmlns:xlink', xlink, xmlns)
+ .attr('xmlns:svgjs', svgjs, xmlns)
+ }
+
+ // Creates and returns defs element
+ defs () {
+ if (!this.isRoot()) return this.doc().defs()
+
+ return adopt(this.node.getElementsByTagName('defs')[0]) ||
+ this.put(new Defs())
+ }
+
+ // custom parent method
+ parent (type) {
+ if (this.isRoot()) {
+ return this.node.parentNode.nodeName === '#document'
+ ? null
+ : adopt(this.node.parentNode)
+ }
+
+ return super.parent(type)
+ }
+
+ clear () {
+ // remove children
+ while (this.node.hasChildNodes()) {
+ this.node.removeChild(this.node.lastChild)
+ }
+ return this
+ }
+}
+
+registerMethods({
+ Container: {
+ // Create nested svg document
+ nested () {
+ return this.put(new Doc())
+ }
+ }
+})
+
+register(Doc, 'Doc', true)
diff --git a/src/elements/Dom.js b/src/elements/Dom.js
new file mode 100644
index 0000000..eab3f0d
--- /dev/null
+++ b/src/elements/Dom.js
@@ -0,0 +1,242 @@
+import {
+ adopt,
+ assignNewId,
+ eid,
+ extend,
+ makeInstance
+} from '../utils/adopter.js'
+import { map } from '../utils/utils.js'
+import { ns } from '../modules/core/namespaces.js'
+import EventTarget from '../types/EventTarget.js'
+import attr from '../modules/core/attr.js'
+
+export default class Dom extends EventTarget {
+ constructor (node) {
+ super(node)
+ this.node = node
+ this.type = node.nodeName
+ }
+
+ // Add given element at a position
+ add (element, i) {
+ element = makeInstance(element)
+
+ if (i == null) {
+ this.node.appendChild(element.node)
+ } else if (element.node !== this.node.childNodes[i]) {
+ this.node.insertBefore(element.node, this.node.childNodes[i])
+ }
+
+ return this
+ }
+
+ // Add element to given container and return self
+ addTo (parent) {
+ return makeInstance(parent).put(this)
+ }
+
+ // Returns all child elements
+ children () {
+ return map(this.node.children, function (node) {
+ return adopt(node)
+ })
+ }
+
+ // Remove all elements in this container
+ clear () {
+ // remove children
+ while (this.node.hasChildNodes()) {
+ this.node.removeChild(this.node.lastChild)
+ }
+
+ // remove defs reference
+ delete this._defs
+
+ return this
+ }
+
+ // Clone element
+ clone (parent) {
+ // write dom data to the dom so the clone can pickup the data
+ this.writeDataToDom()
+
+ // clone element and assign new id
+ let clone = assignNewId(this.node.cloneNode(true))
+
+ // insert the clone in the given parent or after myself
+ if (parent) parent.add(clone)
+ // FIXME: after might not be available here
+ else this.after(clone)
+
+ return clone
+ }
+
+ // Iterates over all children and invokes a given block
+ each (block, deep) {
+ var children = this.children()
+ var i, il
+
+ for (i = 0, il = children.length; i < il; i++) {
+ block.apply(children[i], [i, children])
+
+ if (deep) {
+ children[i].each(block, deep)
+ }
+ }
+
+ return this
+ }
+
+ // Get first child
+ first () {
+ return adopt(this.node.firstChild)
+ }
+
+ // Get a element at the given index
+ get (i) {
+ return adopt(this.node.childNodes[i])
+ }
+
+ getEventHolder () {
+ return this.node
+ }
+
+ getEventTarget () {
+ return this.node
+ }
+
+ // Checks if the given element is a child
+ has (element) {
+ return this.index(element) >= 0
+ }
+
+ // Get / set id
+ id (id) {
+ // generate new id if no id set
+ if (typeof id === 'undefined' && !this.node.id) {
+ this.node.id = eid(this.type)
+ }
+
+ // dont't set directly width this.node.id to make `null` work correctly
+ return this.attr('id', id)
+ }
+
+ // Gets index of given element
+ index (element) {
+ return [].slice.call(this.node.childNodes).indexOf(element.node)
+ }
+
+ // Get the last child
+ last () {
+ return adopt(this.node.lastChild)
+ }
+
+ // matches the element vs a css selector
+ matches (selector) {
+ const el = this.node
+ return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector)
+ }
+
+ // Returns the svg node to call native svg methods on it
+ native () {
+ return this.node
+ }
+
+ // Returns the parent element instance
+ parent (type) {
+ var parent = this
+
+ // check for parent
+ if (!parent.node.parentNode) return null
+
+ // get parent element
+ parent = adopt(parent.node.parentNode)
+
+ if (!type) return parent
+
+ // loop trough ancestors if type is given
+ while (parent && parent.node instanceof window.SVGElement) {
+ if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent
+ parent = adopt(parent.node.parentNode)
+ }
+ }
+
+ // Basically does the same as `add()` but returns the added element instead
+ put (element, i) {
+ this.add(element, i)
+ return element
+ }
+
+ // Add element to given container and return container
+ putIn (parent) {
+ return makeInstance(parent).add(this)
+ }
+
+ // Remove element
+ remove () {
+ if (this.parent()) {
+ this.parent().removeElement(this)
+ }
+
+ return this
+ }
+
+ // Remove a given child
+ removeElement (element) {
+ this.node.removeChild(element.node)
+
+ return this
+ }
+
+ // Replace element
+ replace (element) {
+ // FIXME: after() might not be available here
+ this.after(element).remove()
+
+ return element
+ }
+
+ // Return id on string conversion
+ toString () {
+ return this.id()
+ }
+
+ // Import raw svg
+ svg (svg) {
+ var well, len
+
+ // act as a setter if svg is given
+ if (svg) {
+ // create temporary holder
+ well = document.createElementNS(ns, 'svg')
+ // dump raw svg
+ well.innerHTML = svg
+
+ // transplant nodes
+ for (len = well.children.length; len--;) {
+ this.node.appendChild(well.firstElementChild)
+ }
+
+ // otherwise act as a getter
+ } else {
+ // write svgjs data to the dom
+ this.writeDataToDom()
+
+ return this.node.outerHTML
+ }
+
+ return this
+ }
+
+ // write svgjs data to the dom
+ writeDataToDom () {
+ // dump variables recursively
+ this.each(function () {
+ this.writeDataToDom()
+ })
+
+ return this
+ }
+}
+
+extend(Dom, { attr })
diff --git a/src/elements/Element.js b/src/elements/Element.js
new file mode 100644
index 0000000..a38b2ac
--- /dev/null
+++ b/src/elements/Element.js
@@ -0,0 +1,142 @@
+import { getClass, makeInstance, root } from '../utils/adopter.js'
+import { proportionalSize } from '../utils/utils.js'
+import { reference } from '../modules/core/regex.js'
+import Dom from './Dom.js'
+import SVGNumber from '../types/SVGNumber.js'
+
+const Doc = getClass(root)
+
+export default class Element extends Dom {
+ constructor (node) {
+ super(node)
+
+ // initialize data object
+ this.dom = {}
+
+ // create circular reference
+ this.node.instance = this
+
+ if (node.hasAttribute('svgjs:data')) {
+ // pull svgjs data from the dom (getAttributeNS doesn't work in html5)
+ this.setData(JSON.parse(node.getAttribute('svgjs:data')) || {})
+ }
+ }
+
+ // Move element by its center
+ center (x, y) {
+ return this.cx(x).cy(y)
+ }
+
+ // Move by center over x-axis
+ cx (x) {
+ return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2)
+ }
+
+ // Move by center over y-axis
+ cy (y) {
+ return y == null
+ ? this.y() + this.height() / 2
+ : this.y(y - this.height() / 2)
+ }
+
+ // Get defs
+ defs () {
+ return this.doc().defs()
+ }
+
+ // Get parent document
+ doc () {
+ let p = this.parent(Doc)
+ return p && p.doc()
+ }
+
+ getEventHolder () {
+ return this
+ }
+
+ // Set height of element
+ height (height) {
+ return this.attr('height', height)
+ }
+
+ // Checks whether the given point inside the bounding box of the element
+ inside (x, y) {
+ let box = this.bbox()
+
+ return x > box.x &&
+ y > box.y &&
+ x < box.x + box.width &&
+ y < box.y + box.height
+ }
+
+ // Move element to given x and y values
+ move (x, y) {
+ return this.x(x).y(y)
+ }
+
+ // return array of all ancestors of given type up to the root svg
+ parents (type) {
+ let parents = []
+ let parent = this
+
+ do {
+ parent = parent.parent(type)
+ if (!parent || parent instanceof getClass('HtmlNode')) break
+
+ parents.push(parent)
+ } while (parent.parent)
+
+ return parents
+ }
+
+ // Get referenced element form attribute value
+ reference (attr) {
+ attr = this.attr(attr)
+ if (!attr) return null
+
+ const m = attr.match(reference)
+ return m ? makeInstance(m[1]) : null
+ }
+
+ // set given data to the elements data property
+ setData (o) {
+ this.dom = o
+ return this
+ }
+
+ // Set element size to given width and height
+ size (width, height) {
+ let p = proportionalSize(this, width, height)
+
+ return this
+ .width(new SVGNumber(p.width))
+ .height(new SVGNumber(p.height))
+ }
+
+ // Set width of element
+ width (width) {
+ return this.attr('width', width)
+ }
+
+ // write svgjs data to the dom
+ writeDataToDom () {
+ // remove previously set data
+ this.node.removeAttribute('svgjs:data')
+
+ if (Object.keys(this.dom).length) {
+ this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)) // see #428
+ }
+
+ return super.writeDataToDom()
+ }
+
+ // Move over x-axis
+ x (x) {
+ return this.attr('x', x)
+ }
+
+ // Move over y-axis
+ y (y) {
+ return this.attr('y', y)
+ }
+}
diff --git a/src/elements/Ellipse.js b/src/elements/Ellipse.js
new file mode 100644
index 0000000..40b9369
--- /dev/null
+++ b/src/elements/Ellipse.js
@@ -0,0 +1,21 @@
+import { extend, nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import Shape from './Shape.js'
+import * as circled from '../modules/core/circled.js'
+
+export default class Ellipse extends Shape {
+ constructor (node) {
+ super(nodeOrNew('ellipse', node), Ellipse)
+ }
+}
+
+extend(Ellipse, circled)
+
+registerMethods('Container', {
+ // Create an ellipse
+ ellipse: function (width, height) {
+ return this.put(new Ellipse()).size(width, height).move(0, 0)
+ }
+})
+
+register(Ellipse)
diff --git a/src/elements/G.js b/src/elements/G.js
new file mode 100644
index 0000000..00803c0
--- /dev/null
+++ b/src/elements/G.js
@@ -0,0 +1,20 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import Container from './Container.js'
+
+export default class G extends Container {
+ constructor (node) {
+ super(nodeOrNew('g', node), G)
+ }
+}
+
+registerMethods({
+ Element: {
+ // Create a group element
+ group: function () {
+ return this.put(new G())
+ }
+ }
+})
+
+register(G)
diff --git a/src/elements/Gradient.js b/src/elements/Gradient.js
new file mode 100644
index 0000000..cf8aeaa
--- /dev/null
+++ b/src/elements/Gradient.js
@@ -0,0 +1,77 @@
+import { extend, nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import Box from '../types/Box.js'
+import Container from './Container.js'
+import Stop from './Stop.js'
+import baseFind from '../modules/core/selector.js'
+import * as gradiented from '../modules/core/gradiented.js'
+
+export default class Gradient extends Container {
+ constructor (type) {
+ super(
+ nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type),
+ Gradient
+ )
+ }
+
+ // Add a color stop
+ stop (offset, color, opacity) {
+ return this.put(new Stop()).update(offset, color, opacity)
+ }
+
+ // Update gradient
+ update (block) {
+ // remove all stops
+ this.clear()
+
+ // invoke passed block
+ if (typeof block === 'function') {
+ block.call(this, this)
+ }
+
+ return this
+ }
+
+ // Return the fill id
+ url () {
+ return 'url(#' + this.id() + ')'
+ }
+
+ // Alias string convertion to fill
+ toString () {
+ return this.url()
+ }
+
+ // custom attr to handle transform
+ attr (a, b, c) {
+ if (a === 'transform') a = 'gradientTransform'
+ return super.attr(a, b, c)
+ }
+
+ targets () {
+ return baseFind('svg [fill*="' + this.id() + '"]')
+ }
+
+ bbox () {
+ return new Box()
+ }
+}
+
+extend(Gradient, gradiented)
+
+registerMethods({
+ Container: {
+ // Create gradient element in defs
+ gradient (type, block) {
+ return this.defs().gradient(type, block)
+ }
+ },
+ // define gradient
+ Defs: {
+ gradient (type, block) {
+ return this.put(new Gradient(type)).update(block)
+ }
+ }
+})
+
+register(Gradient)
diff --git a/src/elements/HtmlNode.js b/src/elements/HtmlNode.js
new file mode 100644
index 0000000..59152d3
--- /dev/null
+++ b/src/elements/HtmlNode.js
@@ -0,0 +1,10 @@
+import { register } from '../utils/adopter.js'
+import Dom from './Dom.js'
+
+export default class HtmlNode extends Dom {
+ constructor (node) {
+ super(node, HtmlNode)
+ }
+}
+
+register(HtmlNode)
diff --git a/src/elements/Image.js b/src/elements/Image.js
new file mode 100644
index 0000000..5e672f4
--- /dev/null
+++ b/src/elements/Image.js
@@ -0,0 +1,68 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import { off, on } from '../modules/core/event.js'
+import { registerMethods } from '../utils/methods.js'
+import { xlink } from '../modules/core/namespaces.js'
+import Pattern from './Pattern.js'
+import Shape from './Shape.js'
+
+export default class Image extends Shape {
+ constructor (node) {
+ super(nodeOrNew('image', node), Image)
+ }
+
+ // (re)load image
+ load (url, callback) {
+ if (!url) return this
+
+ var img = new window.Image()
+
+ on(img, 'load', function (e) {
+ var p = this.parent(Pattern)
+
+ // ensure image size
+ if (this.width() === 0 && this.height() === 0) {
+ this.size(img.width, img.height)
+ }
+
+ if (p instanceof Pattern) {
+ // ensure pattern size if not set
+ if (p.width() === 0 && p.height() === 0) {
+ p.size(this.width(), this.height())
+ }
+ }
+
+ if (typeof callback === 'function') {
+ callback.call(this, {
+ width: img.width,
+ height: img.height,
+ ratio: img.width / img.height,
+ url: url
+ })
+ }
+ }, this)
+
+ on(img, 'load error', function () {
+ // dont forget to unbind memory leaking events
+ off(img)
+ })
+
+ return this.attr('href', (img.src = url), xlink)
+ }
+
+ attrHook (obj) {
+ return obj.doc().defs().pattern(0, 0, (pattern) => {
+ pattern.add(this)
+ })
+ }
+}
+
+registerMethods({
+ Container: {
+ // create image element, load image and set its size
+ image (source, callback) {
+ return this.put(new Image()).size(0, 0).load(source, callback)
+ }
+ }
+})
+
+register(Image)
diff --git a/src/elements/Line.js b/src/elements/Line.js
new file mode 100644
index 0000000..b9bc4e8
--- /dev/null
+++ b/src/elements/Line.js
@@ -0,0 +1,63 @@
+import { extend, nodeOrNew, register } from '../utils/adopter.js'
+import { proportionalSize } from '../utils/utils.js'
+import { registerMethods } from '../utils/methods.js'
+import PointArray from '../types/PointArray.js'
+import Shape from './Shape.js'
+import * as pointed from '../modules/core/pointed.js'
+
+export default class Line extends Shape {
+ // Initialize node
+ constructor (node) {
+ super(nodeOrNew('line', node), Line)
+ }
+
+ // Get array
+ array () {
+ return new PointArray([
+ [ this.attr('x1'), this.attr('y1') ],
+ [ this.attr('x2'), this.attr('y2') ]
+ ])
+ }
+
+ // Overwrite native plot() method
+ plot (x1, y1, x2, y2) {
+ if (x1 == null) {
+ return this.array()
+ } else if (typeof y1 !== 'undefined') {
+ x1 = { x1: x1, y1: y1, x2: x2, y2: y2 }
+ } else {
+ x1 = new PointArray(x1).toLine()
+ }
+
+ return this.attr(x1)
+ }
+
+ // Move by left top corner
+ move (x, y) {
+ return this.attr(this.array().move(x, y).toLine())
+ }
+
+ // Set element size to given width and height
+ size (width, height) {
+ var p = proportionalSize(this, width, height)
+ return this.attr(this.array().size(p.width, p.height).toLine())
+ }
+}
+
+extend(Line, pointed)
+
+registerMethods({
+ Container: {
+ // Create a line element
+ line (...args) {
+ // make sure plot is called as a setter
+ // x1 is not necessarily a number, it can also be an array, a string and a PointArray
+ return Line.prototype.plot.apply(
+ this.put(new Line())
+ , args[0] != null ? args : [0, 0, 0, 0]
+ )
+ }
+ }
+})
+
+register(Line)
diff --git a/src/elements/Marker.js b/src/elements/Marker.js
new file mode 100644
index 0000000..2b0541b
--- /dev/null
+++ b/src/elements/Marker.js
@@ -0,0 +1,81 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import Container from './Container.js'
+
+export default class Marker extends Container {
+ // Initialize node
+ constructor (node) {
+ super(nodeOrNew('marker', node), Marker)
+ }
+
+ // Set width of element
+ width (width) {
+ return this.attr('markerWidth', width)
+ }
+
+ // Set height of element
+ height (height) {
+ return this.attr('markerHeight', height)
+ }
+
+ // Set marker refX and refY
+ ref (x, y) {
+ return this.attr('refX', x).attr('refY', y)
+ }
+
+ // Update marker
+ update (block) {
+ // remove all content
+ this.clear()
+
+ // invoke passed block
+ if (typeof block === 'function') { block.call(this, this) }
+
+ return this
+ }
+
+ // Return the fill id
+ toString () {
+ return 'url(#' + this.id() + ')'
+ }
+}
+
+registerMethods({
+ Container: {
+ marker (width, height, block) {
+ // Create marker element in defs
+ return this.defs().marker(width, height, block)
+ }
+ },
+ Defs: {
+ // Create marker
+ marker (width, height, block) {
+ // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto
+ return this.put(new Marker())
+ .size(width, height)
+ .ref(width / 2, height / 2)
+ .viewbox(0, 0, width, height)
+ .attr('orient', 'auto')
+ .update(block)
+ }
+ },
+ marker: {
+ // Create and attach markers
+ marker (marker, width, height, block) {
+ var attr = ['marker']
+
+ // Build attribute name
+ if (marker !== 'all') attr.push(marker)
+ attr = attr.join('-')
+
+ // Set marker attribute
+ marker = arguments[1] instanceof Marker
+ ? arguments[1]
+ : this.defs().marker(width, height, block)
+
+ return this.attr(attr, marker)
+ }
+ }
+})
+
+register(Marker)
diff --git a/src/elements/Mask.js b/src/elements/Mask.js
new file mode 100644
index 0000000..1ed5a8b
--- /dev/null
+++ b/src/elements/Mask.js
@@ -0,0 +1,57 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import Container from './Container.js'
+import baseFind from '../modules/core/selector.js'
+
+export default class Mask extends Container {
+ // Initialize node
+ constructor (node) {
+ super(nodeOrNew('mask', node), Mask)
+ }
+
+ // Unmask all masked elements and remove itself
+ remove () {
+ // unmask all targets
+ this.targets().forEach(function (el) {
+ el.unmask()
+ })
+
+ // remove mask from parent
+ return super.remove()
+ }
+
+ targets () {
+ return baseFind('svg [mask*="' + this.id() + '"]')
+ }
+}
+
+registerMethods({
+ Container: {
+ mask () {
+ return this.defs().put(new Mask())
+ }
+ },
+ Element: {
+ // Distribute mask to svg element
+ maskWith (element) {
+ // use given mask or create a new one
+ var masker = element instanceof Mask
+ ? element
+ : this.parent().mask().add(element)
+
+ // apply mask
+ return this.attr('mask', 'url("#' + masker.id() + '")')
+ },
+
+ // Unmask element
+ unmask () {
+ return this.attr('mask', null)
+ },
+
+ masker () {
+ return this.reference('mask')
+ }
+ }
+})
+
+register(Mask)
diff --git a/src/elements/Path.js b/src/elements/Path.js
new file mode 100644
index 0000000..71be8a1
--- /dev/null
+++ b/src/elements/Path.js
@@ -0,0 +1,81 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import { proportionalSize } from '../utils/utils.js'
+import { registerMethods } from '../utils/methods.js'
+import PathArray from '../types/PathArray.js'
+import Shape from './Shape.js'
+import baseFind from '../modules/core/selector.js'
+
+export default class Path extends Shape {
+ // Initialize node
+ constructor (node) {
+ super(nodeOrNew('path', node), Path)
+ }
+
+ // Get array
+ array () {
+ return this._array || (this._array = new PathArray(this.attr('d')))
+ }
+
+ // Plot new path
+ plot (d) {
+ return (d == null) ? this.array()
+ : this.clear().attr('d', typeof d === 'string' ? d : (this._array = new PathArray(d)))
+ }
+
+ // Clear array cache
+ clear () {
+ delete this._array
+ return this
+ }
+
+ // Move by left top corner
+ move (x, y) {
+ return this.attr('d', this.array().move(x, y))
+ }
+
+ // Move by left top corner over x-axis
+ x (x) {
+ return x == null ? this.bbox().x : this.move(x, this.bbox().y)
+ }
+
+ // Move by left top corner over y-axis
+ y (y) {
+ return y == null ? this.bbox().y : this.move(this.bbox().x, y)
+ }
+
+ // Set element size to given width and height
+ size (width, height) {
+ var p = proportionalSize(this, width, height)
+ return this.attr('d', this.array().size(p.width, p.height))
+ }
+
+ // Set width of element
+ width (width) {
+ return width == null ? this.bbox().width : this.size(width, this.bbox().height)
+ }
+
+ // Set height of element
+ height (height) {
+ return height == null ? this.bbox().height : this.size(this.bbox().width, height)
+ }
+
+ targets () {
+ return baseFind('svg textpath [href*="' + this.id() + '"]')
+ }
+}
+
+// Define morphable array
+Path.prototype.MorphArray = PathArray
+
+// Add parent method
+registerMethods({
+ Container: {
+ // Create a wrapped path element
+ path (d) {
+ // make sure plot is called as a setter
+ return this.put(new Path()).plot(d || new PathArray())
+ }
+ }
+})
+
+register(Path)
diff --git a/src/elements/Pattern.js b/src/elements/Pattern.js
new file mode 100644
index 0000000..9111837
--- /dev/null
+++ b/src/elements/Pattern.js
@@ -0,0 +1,71 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import Box from '../types/Box.js'
+import Container from './Container.js'
+import baseFind from '../modules/core/selector.js'
+
+export default class Pattern extends Container {
+ // Initialize node
+ constructor (node) {
+ super(nodeOrNew('pattern', node), Pattern)
+ }
+
+ // Return the fill id
+ url () {
+ return 'url(#' + this.id() + ')'
+ }
+
+ // Update pattern by rebuilding
+ update (block) {
+ // remove content
+ this.clear()
+
+ // invoke passed block
+ if (typeof block === 'function') {
+ block.call(this, this)
+ }
+
+ return this
+ }
+
+ // Alias string convertion to fill
+ toString () {
+ return this.url()
+ }
+
+ // custom attr to handle transform
+ attr (a, b, c) {
+ if (a === 'transform') a = 'patternTransform'
+ return super.attr(a, b, c)
+ }
+
+ targets () {
+ return baseFind('svg [fill*="' + this.id() + '"]')
+ }
+
+ bbox () {
+ return new Box()
+ }
+}
+
+registerMethods({
+ Container: {
+ // Create pattern element in defs
+ pattern (width, height, block) {
+ return this.defs().pattern(width, height, block)
+ }
+ },
+ Defs: {
+ pattern (width, height, block) {
+ return this.put(new Pattern()).update(block).attr({
+ x: 0,
+ y: 0,
+ width: width,
+ height: height,
+ patternUnits: 'userSpaceOnUse'
+ })
+ }
+ }
+})
+
+register(Pattern)
diff --git a/src/elements/Polygon.js b/src/elements/Polygon.js
new file mode 100644
index 0000000..6097977
--- /dev/null
+++ b/src/elements/Polygon.js
@@ -0,0 +1,27 @@
+import { extend, nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import PointArray from '../types/PointArray.js'
+import Shape from './Shape.js'
+import * as pointed from '../modules/core/pointed.js'
+import * as poly from '../modules/core/poly.js'
+
+export default class Polygon extends Shape {
+ // Initialize node
+ constructor (node) {
+ super(nodeOrNew('polygon', node), Polygon)
+ }
+}
+
+registerMethods({
+ Container: {
+ // Create a wrapped polygon element
+ polygon (p) {
+ // make sure plot is called as a setter
+ return this.put(new Polygon()).plot(p || new PointArray())
+ }
+ }
+})
+
+extend(Polygon, pointed)
+extend(Polygon, poly)
+register(Polygon)
diff --git a/src/elements/Polyline.js b/src/elements/Polyline.js
new file mode 100644
index 0000000..b2cb15b
--- /dev/null
+++ b/src/elements/Polyline.js
@@ -0,0 +1,27 @@
+import { extend, nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import PointArray from '../types/PointArray.js'
+import Shape from './Shape.js'
+import * as pointed from '../modules/core/pointed.js'
+import * as poly from '../modules/core/poly.js'
+
+export default class Polyline extends Shape {
+ // Initialize node
+ constructor (node) {
+ super(nodeOrNew('polyline', node), Polyline)
+ }
+}
+
+registerMethods({
+ Container: {
+ // Create a wrapped polygon element
+ polyline (p) {
+ // make sure plot is called as a setter
+ return this.put(new Polyline()).plot(p || new PointArray())
+ }
+ }
+})
+
+extend(Polyline, pointed)
+extend(Polyline, poly)
+register(Polyline)
diff --git a/src/elements/Rect.js b/src/elements/Rect.js
new file mode 100644
index 0000000..9d6163c
--- /dev/null
+++ b/src/elements/Rect.js
@@ -0,0 +1,32 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import Shape from './Shape.js'
+
+export default class Rect extends Shape {
+ // Initialize node
+ constructor (node) {
+ super(nodeOrNew('rect', node), Rect)
+ }
+
+ // FIXME: unify with circle
+ // Radius x value
+ rx (rx) {
+ return this.attr('rx', rx)
+ }
+
+ // Radius y value
+ ry (ry) {
+ return this.attr('ry', ry)
+ }
+}
+
+registerMethods({
+ Container: {
+ // Create a rect element
+ rect (width, height) {
+ return this.put(new Rect()).size(width, height)
+ }
+ }
+})
+
+register(Rect)
diff --git a/src/elements/Shape.js b/src/elements/Shape.js
new file mode 100644
index 0000000..bf68a8d
--- /dev/null
+++ b/src/elements/Shape.js
@@ -0,0 +1,3 @@
+import Element from './Element.js'
+
+export default class Shape extends Element {}
diff --git a/src/elements/Stop.js b/src/elements/Stop.js
new file mode 100644
index 0000000..bf919e8
--- /dev/null
+++ b/src/elements/Stop.js
@@ -0,0 +1,29 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import Element from './Element.js'
+import SVGNumber from '../types/SVGNumber.js'
+
+export default class Stop extends Element {
+ constructor (node) {
+ super(nodeOrNew('stop', node), Stop)
+ }
+
+ // add color stops
+ update (o) {
+ if (typeof o === 'number' || o instanceof SVGNumber) {
+ o = {
+ offset: arguments[0],
+ color: arguments[1],
+ opacity: arguments[2]
+ }
+ }
+
+ // set attributes
+ if (o.opacity != null) this.attr('stop-opacity', o.opacity)
+ if (o.color != null) this.attr('stop-color', o.color)
+ if (o.offset != null) this.attr('offset', new SVGNumber(o.offset))
+
+ return this
+ }
+}
+
+register(Stop)
diff --git a/src/elements/Symbol.js b/src/elements/Symbol.js
new file mode 100644
index 0000000..183f449
--- /dev/null
+++ b/src/elements/Symbol.js
@@ -0,0 +1,20 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import Container from './Container.js'
+
+export default class Symbol extends Container {
+ // Initialize node
+ constructor (node) {
+ super(nodeOrNew('symbol', node), Symbol)
+ }
+}
+
+registerMethods({
+ Container: {
+ symbol () {
+ return this.put(new Symbol())
+ }
+ }
+})
+
+register(Symbol)
diff --git a/src/elements/Text.js b/src/elements/Text.js
new file mode 100644
index 0000000..58d50a3
--- /dev/null
+++ b/src/elements/Text.js
@@ -0,0 +1,177 @@
+import { adopt, extend, nodeOrNew, register } from '../utils/adopter.js'
+import { attrs } from '../modules/core/defaults.js'
+import { registerMethods } from '../utils/methods.js'
+import SVGNumber from '../types/SVGNumber.js'
+import Shape from './Shape.js'
+import * as textable from '../modules/core/textable.js'
+
+export default class Text extends Shape {
+ // Initialize node
+ constructor (node) {
+ super(nodeOrNew('text', node), Text)
+
+ this.dom.leading = new SVGNumber(1.3) // store leading value for rebuilding
+ this._rebuild = true // enable automatic updating of dy values
+ this._build = false // disable build mode for adding multiple lines
+
+ // set default font
+ this.attr('font-family', attrs['font-family'])
+ }
+
+ // Move over x-axis
+ x (x) {
+ // act as getter
+ if (x == null) {
+ return this.attr('x')
+ }
+
+ return this.attr('x', x)
+ }
+
+ // Move over y-axis
+ y (y) {
+ var oy = this.attr('y')
+ var o = typeof oy === 'number' ? oy - this.bbox().y : 0
+
+ // act as getter
+ if (y == null) {
+ return typeof oy === 'number' ? oy - o : oy
+ }
+
+ return this.attr('y', typeof y === 'number' ? y + o : y)
+ }
+
+ // Move center over x-axis
+ cx (x) {
+ return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2)
+ }
+
+ // Move center over y-axis
+ cy (y) {
+ return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2)
+ }
+
+ // Set the text content
+ text (text) {
+ // act as getter
+ if (text === undefined) {
+ // FIXME use children() or each()
+ var children = this.node.childNodes
+ var firstLine = 0
+ text = ''
+
+ for (var i = 0, len = children.length; i < len; ++i) {
+ // skip textPaths - they are no lines
+ if (children[i].nodeName === 'textPath') {
+ if (i === 0) firstLine = 1
+ continue
+ }
+
+ // add newline if its not the first child and newLined is set to true
+ if (i !== firstLine && children[i].nodeType !== 3 && adopt(children[i]).dom.newLined === true) {
+ text += '\n'
+ }
+
+ // add content of this node
+ text += children[i].textContent
+ }
+
+ return text
+ }
+
+ // remove existing content
+ this.clear().build(true)
+
+ if (typeof text === 'function') {
+ // call block
+ text.call(this, this)
+ } else {
+ // store text and make sure text is not blank
+ text = text.split('\n')
+
+ // build new lines
+ for (var j = 0, jl = text.length; j < jl; j++) {
+ this.tspan(text[j]).newLine()
+ }
+ }
+
+ // disable build mode and rebuild lines
+ return this.build(false).rebuild()
+ }
+
+ // Set / get leading
+ leading (value) {
+ // act as getter
+ if (value == null) {
+ return this.dom.leading
+ }
+
+ // act as setter
+ this.dom.leading = new SVGNumber(value)
+
+ return this.rebuild()
+ }
+
+ // Rebuild appearance type
+ rebuild (rebuild) {
+ // store new rebuild flag if given
+ if (typeof rebuild === 'boolean') {
+ this._rebuild = rebuild
+ }
+
+ // define position of all lines
+ if (this._rebuild) {
+ var self = this
+ var blankLineOffset = 0
+ var dy = this.dom.leading * new SVGNumber(this.attr('font-size'))
+
+ this.each(function () {
+ if (this.dom.newLined) {
+ this.attr('x', self.attr('x'))
+
+ if (this.text() === '\n') {
+ blankLineOffset += dy
+ } else {
+ this.attr('dy', dy + blankLineOffset)
+ blankLineOffset = 0
+ }
+ }
+ })
+
+ this.fire('rebuild')
+ }
+
+ return this
+ }
+
+ // Enable / disable build mode
+ build (build) {
+ this._build = !!build
+ return this
+ }
+
+ // overwrite method from parent to set data properly
+ setData (o) {
+ this.dom = o
+ this.dom.leading = new SVGNumber(o.leading || 1.3)
+ return this
+ }
+}
+
+extend(Text, textable)
+
+registerMethods({
+ Container: {
+ // Create text element
+ text (text) {
+ return this.put(new Text()).text(text)
+ },
+
+ // Create plain text element
+ plain (text) {
+ return this.put(new Text()).plain(text)
+ }
+ }
+})
+
+register(Text)
diff --git a/src/elements/TextPath.js b/src/elements/TextPath.js
new file mode 100644
index 0000000..04146bc
--- /dev/null
+++ b/src/elements/TextPath.js
@@ -0,0 +1,83 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import { xlink } from '../modules/core/namespaces.js'
+import Path from './Path.js'
+import PathArray from '../types/PathArray.js'
+import Text from './Text.js'
+
+export default class TextPath extends Text {
+ // Initialize node
+ constructor (node) {
+ super(nodeOrNew('textPath', node), TextPath)
+ }
+
+ // return the array of the path track element
+ array () {
+ var track = this.track()
+
+ return track ? track.array() : null
+ }
+
+ // Plot path if any
+ plot (d) {
+ var track = this.track()
+ var pathArray = null
+
+ if (track) {
+ pathArray = track.plot(d)
+ }
+
+ return (d == null) ? pathArray : this
+ }
+
+ // Get the path element
+ track () {
+ return this.reference('href')
+ }
+}
+
+registerMethods({
+ Container: {
+ textPath (text, path) {
+ return this.defs().path(path).text(text).addTo(this)
+ }
+ },
+ Text: {
+ // Create path for text to run on
+ path: function (track) {
+ var path = new TextPath()
+
+ // if d is a path, reuse it
+ if (!(track instanceof Path)) {
+ // create path element
+ track = this.doc().defs().path(track)
+ }
+
+ // link textPath to path and add content
+ path.attr('href', '#' + track, xlink)
+
+ // add textPath element as child node and return textPath
+ return this.put(path)
+ },
+
+ // FIXME: make this plural?
+ // Get the textPath children
+ textPath: function () {
+ return this.find('textPath')
+ }
+ },
+ Path: {
+ // creates a textPath from this path
+ text: function (text) {
+ if (text instanceof Text) {
+ var txt = text.text()
+ return text.clear().path(this).text(txt)
+ }
+ return this.parent().put(new Text()).path(this).text(text)
+ }
+ // FIXME: Maybe add `targets` to get all textPaths associated with this path
+ }
+})
+
+TextPath.prototype.MorphArray = PathArray
+register(TextPath)
diff --git a/src/elements/Tspan.js b/src/elements/Tspan.js
new file mode 100644
index 0000000..69815d4
--- /dev/null
+++ b/src/elements/Tspan.js
@@ -0,0 +1,64 @@
+import { extend, nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import Text from './Text.js'
+import * as textable from '../modules/core/textable.js'
+
+export default class Tspan extends Text {
+ // Initialize node
+ constructor (node) {
+ super(nodeOrNew('tspan', node), Tspan)
+ }
+
+ // Set text content
+ text (text) {
+ if (text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '')
+
+ typeof text === 'function' ? text.call(this, this) : this.plain(text)
+
+ return this
+ }
+
+ // Shortcut dx
+ dx (dx) {
+ return this.attr('dx', dx)
+ }
+
+ // Shortcut dy
+ dy (dy) {
+ return this.attr('dy', dy)
+ }
+
+ // Create new line
+ newLine () {
+ // fetch text parent
+ var t = this.parent(Text)
+
+ // mark new line
+ this.dom.newLined = true
+
+ // apply new position
+ return this.dy(t.dom.leading * t.attr('font-size')).attr('x', t.x())
+ }
+}
+
+extend(Tspan, textable)
+
+registerMethods({
+ Tspan: {
+ tspan (text) {
+ var tspan = new Tspan()
+
+ // clear if build mode is disabled
+ if (!this._build) {
+ this.clear()
+ }
+
+ // add new tspan
+ this.node.appendChild(tspan.node)
+
+ return tspan.text(text)
+ }
+ }
+})
+
+register(Tspan)
diff --git a/src/elements/Use.js b/src/elements/Use.js
new file mode 100644
index 0000000..43a4e9b
--- /dev/null
+++ b/src/elements/Use.js
@@ -0,0 +1,27 @@
+import { nodeOrNew, register } from '../utils/adopter.js'
+import { registerMethods } from '../utils/methods.js'
+import { xlink } from '../modules/core/namespaces.js'
+import Shape from './Shape.js'
+
+export default class Use extends Shape {
+ constructor (node) {
+ super(nodeOrNew('use', node), Use)
+ }
+
+ // Use element as a reference
+ element (element, file) {
+ // Set lined element
+ return this.attr('href', (file || '') + '#' + element, xlink)
+ }
+}
+
+registerMethods({
+ Container: {
+ // Create a use element
+ use: function (element, file) {
+ return this.put(new Use()).element(element, file)
+ }
+ }
+})
+
+register(Use)
diff --git a/src/elemnts-svg.js b/src/elemnts-svg.js
index 082ccd5..b5b2542 100644
--- a/src/elemnts-svg.js
+++ b/src/elemnts-svg.js
@@ -1,34 +1,68 @@
- // Import raw svg
- svg: function (svg) {
- var well, len
-
- // act as getter if no svg string is given
- if(svg == null || svg === true) {
- // write svgjs data to the dom
- this.writeDataToDom()
-
- // return outer or inner content
- return svg
- ? this.node.innerHTML
- : this.node.outerHTML
- }
+import { ns } from './namespaces.js'
+
+/* eslint no-unused-vars: "off" */
+var a = {
+ // Import raw svg
+ svg (svg, fn = false) {
+ var well, len, fragment
+
+ // act as getter if no svg string is given
+ if (svg == null || svg === true || typeof svg === 'function') {
+ // write svgjs data to the dom
+ this.writeDataToDom()
+ let current = this
- // act as setter if we got a string
+ // An export modifier was passed
+ if (typeof svg === 'function') {
+ // Juggle arguments
+ [fn, svg] = [svg, fn]
- // make sure we are on a parent when trying to import
- if(!(this instanceof SVG.Parent))
- throw Error('Cannot import svg into non-parent element')
+ // If the user wants outerHTML we need to process this node, too
+ if (!svg) {
+ current = fn(current)
- // create temporary holder
- well = document.createElementNS(SVG.ns, 'svg')
+ // The user does not want this node? Well, then he gets nothing
+ if (current === false) return ''
+ }
- // dump raw svg
- well.innerHTML = svg
+ // Deep loop through all children and apply modifier
+ current.each(function () {
+ let result = fn(this)
- // transplant nodes
- for (len = well.children.length; len--;) {
- this.node.appendChild(well.firstElementChild)
+ // If modifier returns false, discard node
+ if (result === false) {
+ this.remove()
+
+ // If modifier returns new node, use it
+ } else if (result !== this) {
+ this.replace(result)
+ }
+ }, true)
}
- return this
- }, \ No newline at end of file
+ // Return outer or inner content
+ return svg
+ ? current.node.innerHTML
+ : current.node.outerHTML
+ }
+
+ // Act as setter if we got a string
+
+ // Create temporary holder
+ well = document.createElementNS(ns, 'svg')
+ fragment = document.createDocumentFragment()
+
+ // Dump raw svg
+ well.innerHTML = svg
+
+ // Transplant nodes into the fragment
+ for (len = well.children.length; len--;) {
+ fragment.appendChild(well.firstElementChild)
+ }
+
+ // Add the whole fragment at once
+ this.node.appendChild(fragment)
+
+ return this
+ }
+}
diff --git a/src/ellipse.js b/src/ellipse.js
deleted file mode 100644
index 8a8f027..0000000
--- a/src/ellipse.js
+++ /dev/null
@@ -1,91 +0,0 @@
-/* global proportionalSize */
-
-SVG.Circle = SVG.invent({
- // Initialize node
- create: 'circle',
-
- // Inherit from
- inherit: SVG.Shape,
-
- // Add parent method
- construct: {
- // Create circle element, based on ellipse
- circle: function (size) {
- return this.put(new SVG.Circle()).rx(new SVG.Number(size).divide(2)).move(0, 0)
- }
- }
-})
-
-SVG.extend([SVG.Circle, SVG.Timeline], {
- // Radius x value
- rx: function (rx) {
- return this.attr('r', rx)
- },
- // Alias radius x value
- ry: function (ry) {
- return this.rx(ry)
- }
-})
-
-SVG.Ellipse = SVG.invent({
- // Initialize node
- create: 'ellipse',
-
- // Inherit from
- inherit: SVG.Shape,
-
- // Add parent method
- construct: {
- // Create an ellipse
- ellipse: function (width, height) {
- return this.put(new SVG.Ellipse()).size(width, height).move(0, 0)
- }
- }
-})
-
-SVG.extend([SVG.Ellipse, SVG.Rect, SVG.Timeline], {
- // Radius x value
- rx: function (rx) {
- return this.attr('rx', rx)
- },
- // Radius y value
- ry: function (ry) {
- return this.attr('ry', ry)
- }
-})
-
-// Add common method
-SVG.extend([SVG.Circle, SVG.Ellipse], {
- // Move over x-axis
- x: function (x) {
- return x == null ? this.cx() - this.rx() : this.cx(x + this.rx())
- },
- // Move over y-axis
- y: function (y) {
- return y == null ? this.cy() - this.ry() : this.cy(y + this.ry())
- },
- // Move by center over x-axis
- cx: function (x) {
- return x == null ? this.attr('cx') : this.attr('cx', x)
- },
- // Move by center over y-axis
- cy: function (y) {
- return y == null ? this.attr('cy') : this.attr('cy', y)
- },
- // Set width of element
- width: function (width) {
- return width == null ? this.rx() * 2 : this.rx(new SVG.Number(width).divide(2))
- },
- // Set height of element
- height: function (height) {
- return height == null ? this.ry() * 2 : this.ry(new SVG.Number(height).divide(2))
- },
- // Custom size function
- size: function (width, height) {
- var p = proportionalSize(this, width, height)
-
- return this
- .rx(new SVG.Number(p.width).divide(2))
- .ry(new SVG.Number(p.height).divide(2))
- }
-})
diff --git a/src/eventtarget.js b/src/eventtarget.js
deleted file mode 100644
index fbe4781..0000000
--- a/src/eventtarget.js
+++ /dev/null
@@ -1,23 +0,0 @@
-SVG.EventTarget = SVG.invent({
- create: function () {},
- extend: {
- // Bind given event to listener
- on: function (event, listener, binding, options) {
- SVG.on(this, event, listener, binding, options)
- return this
- },
- // Unbind event from listener
- off: function (event, listener) {
- SVG.off(this, event, listener)
- return this
- },
- dispatch: function (event, data) {
- return SVG.dispatch(this, event, data)
- },
- // Fire given event
- fire: function (event, data) {
- this.dispatch(event, data)
- return this
- }
- }
-})
diff --git a/src/flatten.js b/src/flatten.js
deleted file mode 100644
index 19eebd7..0000000
--- a/src/flatten.js
+++ /dev/null
@@ -1,38 +0,0 @@
-SVG.extend(SVG.Parent, {
- flatten: function (parent) {
- // flattens is only possible for nested svgs and groups
- if (!(this instanceof SVG.G || this instanceof SVG.Doc)) {
- return this
- }
-
- parent = parent || (this instanceof SVG.Doc && this.isRoot() ? this : this.parent(SVG.Parent))
-
- this.each(function () {
- if (this instanceof SVG.Defs) return this
- if (this instanceof SVG.Parent) return this.flatten(parent)
- return this.toParent(parent)
- })
-
- // we need this so that SVG.Doc does not get removed
- this.node.firstElementChild || this.remove()
-
- return this
- },
- ungroup: function (parent) {
- // ungroup is only possible for nested svgs and groups
- if (!(this instanceof SVG.G || (this instanceof SVG.Doc && !this.isRoot()))) {
- return this
- }
-
- parent = parent || this.parent(SVG.Parent)
-
- this.each(function () {
- return this.toParent(parent)
- })
-
- // we need this so that SVG.Doc does not get removed
- this.remove()
-
- return this
- }
-})
diff --git a/src/fx.js b/src/fx.js
deleted file mode 100644
index dd515df..0000000
--- a/src/fx.js
+++ /dev/null
@@ -1,1368 +0,0 @@
-SVG.easing = {
- '-': function (pos) { return pos },
- '<>': function (pos) { return -Math.cos(pos * Math.PI) / 2 + 0.5 },
- '>': function (pos) { return Math.sin(pos * Math.PI / 2) },
- '<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 }
-}
-
-SVG.morph = function (pos) {
- return function (from, to) {
- return new SVG.MorphObj(from, to).at(pos)
- }
-}
-
-let time = window.performance || window.Date
-
-SVG.Timeline = SVG.invent ({
-
- create: function () {
-
- // Store all of the closures to animate
- this._closures = []
-
- this._startTime = time.now()
- this._duration = 0
-
- this._running = true
-
- },
-
- extend: {
-
- animate (duration, ease, delay, epoch) {
-
- }
-
- loop (times, reverse) {
-
- }
-
- duration (time) {
- this._duration = time
- }
-
- delay (by, epoch) {
- if (epoch) {
- this._startTime = time.now()
- }
- this._duration = 0
- this._startTime += by
- }
-
- ease (fn) {
-
- }
-
- play ()
- pause ()
- stop ()
- finish (all=true)
- speed (newSpeed)
- seek (dt)
- persist (dt || forever) // 0 by default
- reverse ()
-
-
-
-
-
-
- // fn is a function that takes a position in range [0, 1]
- schedule (fn) { // fn can not take parameters
-
-
-
-
-
-
-let declarative = rect.animate(300, '>', 200)
- .loop().color('blue')
- .animate(SVG.Spring(300))
-
-onmousemove() {
- declarative.x(mouseX).y(mouseY)
-}
-
- SVG.MorphObj = SVG.invent({
-
- create: function (from, to) {
- // prepare color for morphing
- if (SVG.Color.isColor(to)) return new SVG.Color(from).morph(to)
- // prepare value list for morphing
- if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to)
- // prepare number for morphing
- if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to)
-
- // prepare for plain morphing
- this.value = from
- this.destination = to
- },
-
- extend: {
- at: function (pos, real) {
- return real < 1 ? this.value : this.destination
- },
-
- valueOf: function () {
- return this.value
- }
- }
-
- })
-
-
-add('fill-color', val)
-
-add('x', val, 'animations')
-
-add('x', val, 'styles')
-
-add('line-cap', val, 'attrs')
-
-.style(name, val) {
-
-
- styleAttr ('style', name, val)
-}
-
-.animate(spring)
-
-onmousemove(() => {
- el.animate(SVG.Spring(500))
- .move(event.pointX, event.pointY)
- .finish()
-})
-
-
-
-Morphable ()
-
-Controlable ()
-
-new Controller(target, controller)
-
-
-
-
-Number
-Array
-PathArray
-ViewBox
-PointArray
-Color
-
-
-
-
-
-
-
-
-
-
-SVG.Timeline = {
- styleAttr (type, name, val) {
- let morpher = new Morph(val).controller(this.controller)
- queue (
- ()=> {
- morpher = morpher.morph(element[type]('name'))
- },
- morpher.at
- )
- }
-}
-
-.styleAttr (type, name, val) {
-
- let morpher = declarative ? new Controller(target) : new Morph().to(val)
- queue (
- ()=> {
- morpher = morpher.from(element[type](name))
- },
- () => {
- this.element[type](name, morpher.at(pos))
- }
- )
-}
-
-viewbox(box) {
- new Box
- let morpher = new Morph().to(box) // box: {width, heught, x, y}
-}
-
-
-new Morph(from, to)
-
-
-new Morpg(from, to, controller = (from, to, pos) => {from + pos * (to - from)})
-
-
-// Something line
-path = "a, b, c"
-
-SVG.color {
- toArray: [r, g, b]
- fromArray: new Color({r, g, b})
-}
-
-
-
-
-
-
-morph: function (pathArray) {
- pathArray = new SVG.PathArray(pathArray)
-
- if (this.equalCommands(pathArray)) {
- this.destination = pathArray
- } else {
- this.destination = null
- }
-
- return this
-},
-
-[['M', 3, 5], ['L', 5, 6]]
-
-['M', 3, 4, 'L', ...]
-
-
-
-
-function detectSomething (item) {
- if(from instanceof SVG.Morphable) return from.controller(controller)
- // prepare color for morphing
- if (SVG.Color.isColor(to)) return new SVG.Color(from, controller)
- // prepare value list for morphing
- if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to)
- // prepare number for morphing
- if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to)
-
- return item
-}
-
-foo->bar
-
-
-all of these things implement
-
-interface Morphable {
- from: (thing)=> {}
- to: (thing)=> {}
- at: (pos)=> {}
- controller: (fn (nowOrFrom, target, pos))=> {}
-}
-
-
-new SVG.MorphObj(el.attr(name))
-
-animate().attr('line-joint', 5)
-
-SVG.MorphObj = SVG.invent({
-
- create: function (from, to) {
- // prepare color for morphing
- if (SVG.Color.isColor(to)) return new SVG.Color(from).morph(to)
- // prepare value list for morphing
- if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to)
- // prepare number for morphing
- if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to)
-
- // prepare for plain morphing
- this.value = from
- this.destination = to
- },
-
- extend: {
- at: function (pos, real) {
- return real < 1 ? this.value : this.destination
- },
-
- valueOf: function () {
- return this.value
- }
- }
-
-})
-
-
-// Only works with a single number
-new MorphObj {
-
- constr: (control= (from, to, c)=> {from + pos * (to - from)}) {
- }
-
- _detect: // Gets the user input and returns the right kind of object
-
- from: (from) => {
-
- if (SVG.Color.isColor(to)) return new SVG.Color(from).morph(to)
- // prepare value list for morphing
- if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to)
- // prepare number for morphing
- if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to)
-
- // prepare for plain morphing
- this.value = from
- this.destination = to
- }
-
- to: (val) => {
-
- }
- at (pos) {
-
- let type = from.type
- let from = from.toArray()
- let to = to.toArray()
- result = []
- for (i)
- result[i] = this.controller(from[i], to[i], pos) : to[i]
-
- type.fromArray(result)
- }
-}
-
-if(declartive) {
- mropher.init()
- morpher.at(pos/fn)
-}
-
-
-
-controller(currentPos, target)
-
-
-morph interface
-detect type function
-
-
-if (mouse in box)
- move box
- animate(spring)
-
-zoom(level, point) {
- let morpher = SVG.Number(level).controller(this.controller)
- this.queue(
- () => {morpher = morpher.from(element.zoom())},
- (pos) => {element.zoom(morpher.at(pos), point)}
- )
-}
-
-x (x) {
-
-}
-
-this.queue(fn, morpher)
-
-new Morph(x(), xGiven)
-
- x: function (x, relative) {
- if (this.target() instanceof SVG.G) {
- this.transform({x: x}, relative)
- return this
- }
-
- var num = new SVG.Number(x)
- num.relative = relative
- return this.add('x', num)
- },
-
-
- viewbox: function(box) {
- var m = SVG.Box(box)
- }
-
-
- new Runner (function(time) {
-
-
- })
-
-
- var closure = function (time) {
-
- // If it is time to do something, act now.
- var running = start < time && time < end
- if (running && this._running) {
- closure.position = (time - closure.start) / closure.duration
- fn (time)
- }
-
- // If we are not paused or stopped, request another frame
- if (this._running) SVG.Animator.frame(closure, this._startTime)
-
- // Tell the caller whether this animation is finished
- closure.finished = !running
-
- }.bind(this)
-
- closure.stop() // toggles a stop flag
- closure.pause()
- closure.run(t) // If it was paused, it
-
-
- closure.start = this._startTime
- closure.end = this._startTime + this._duration
- closure.positon =
- var forwards = true // Decide if running forward based on looping
-
-
- // TODO: Store a list of closures
-
- SVG.Animator.timeout(closure, this._startTime)
- _continue()
- }
-
- _step (dt) {
-
- }
-
- // Checks if we are running and continues the animation
- _continue () {
- , continue: function () {
- if (this.paused) return
- if (!this.nextFrame)
- this.step()
- return this
- }
-
- }
- },
-
-
- construct: {
- animate: function(o, ease, delay, epoch) {
- return (this.timeline = this.timeline || new SVG.Timeline(o, ease, delay, epoch))
- }
- }
-})
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-// SVG.Situation = SVG.invent({
-//
-// create: function (o) {
-// this.init = false
-// this.reversed = false
-// this.reversing = false
-//
-// this.duration = new SVG.Number(o.duration).valueOf()
-// this.delay = new SVG.Number(o.delay).valueOf()
-//
-// this.start = +new Date() + this.delay
-// this.finish = this.start + this.duration
-// this.ease = o.ease
-//
-// // this.loop is incremented from 0 to this.loops
-// // it is also incremented when in an infinite loop (when this.loops is true)
-// this.loop = 0
-// this.loops = false
-//
-// this.animations = {
-// // functionToCall: [list of morphable objects]
-// // e.g. move: [SVG.Number, SVG.Number]
-// }
-//
-// this.attrs = {
-// // holds all attributes which are not represented from a function svg.js provides
-// // e.g. someAttr: SVG.Number
-// }
-//
-// this.styles = {
-// // holds all styles which should be animated
-// // e.g. fill-color: SVG.Color
-// }
-//
-// this.transforms = [
-// // holds all transformations as transformation objects
-// // e.g. [SVG.Rotate, SVG.Translate, SVG.Matrix]
-// ]
-//
-// this.once = {
-// // functions to fire at a specific position
-// // e.g. "0.5": function foo(){}
-// }
-// }
-//
-// })
-//
-// SVG.Timeline = SVG.invent({
-//
-// create: function (element) {
-// this._target = element
-// this.situations = []
-// this.active = false
-// this.situation = null
-// this.paused = false
-// this.lastPos = 0
-// this.pos = 0
-// // The absolute position of an animation is its position in the context of its complete duration (including delay and loops)
-// // When performing a delay, absPos is below 0 and when performing a loop, its value is above 1
-// this.absPos = 0
-// this._speed = 1
-// },
-//
-// extend: {
-//
-// /**
-// * sets or returns the target of this animation
-// * @param o object || number In case of Object it holds all parameters. In case of number its the duration of the animation
-// * @param ease function || string Function which should be used for easing or easing keyword
-// * @param delay Number indicating the delay before the animation starts
-// * @return target || this
-// */
-// animate: function (o, ease, delay) {
-// if (typeof o === 'object') {
-// ease = o.ease
-// delay = o.delay
-// o = o.duration
-// }
-//
-// var situation = new SVG.Situation({
-// duration: o || 1000,
-// delay: delay || 0,
-// ease: SVG.easing[ease || '-'] || ease
-// })
-//
-// this.queue(situation)
-//
-// return this
-// },
-//
-// /**
-// * sets a delay before the next element of the queue is called
-// * @param delay Duration of delay in milliseconds
-// * @return this.target()
-// */
-// delay: function (delay) {
-// // The delay is performed by an empty situation with its duration
-// // attribute set to the duration of the delay
-// var situation = new SVG.Situation({
-// duration: delay,
-// delay: 0,
-// ease: SVG.easing['-']
-// })
-//
-// return this.queue(situation)
-// },
-//
-// /**
-// * sets or returns the target of this animation
-// * @param null || target SVG.Element which should be set as new target
-// * @return target || this
-// */
-// target: function (target) {
-// if (target && target instanceof SVG.Element) {
-// this._target = target
-// return this
-// }
-//
-// return this._target
-// },
-//
-// // returns the absolute position at a given time
-// timeToAbsPos: function (timestamp) {
-// return (timestamp - this.situation.start) / (this.situation.duration / this._speed)
-// },
-//
-// // returns the timestamp from a given absolute positon
-// absPosToTime: function (absPos) {
-// return this.situation.duration / this._speed * absPos + this.situation.start
-// },
-//
-// // starts the animationloop
-// startAnimFrame: function () {
-// this.stopAnimFrame()
-// this.animationFrame = window.requestAnimationFrame(function () { this.step() }.bind(this))
-// },
-//
-// // cancels the animationframe
-// stopAnimFrame: function () {
-// window.cancelAnimationFrame(this.animationFrame)
-// },
-//
-// // kicks off the animation - only does something when the queue is currently not active and at least one situation is set
-// start: function () {
-// // dont start if already started
-// if (!this.active && this.situation) {
-// this.active = true
-// this.startCurrent()
-// }
-//
-// return this
-// },
-//
-// // start the current situation
-// startCurrent: function () {
-// this.situation.start = +new Date() + this.situation.delay / this._speed
-// this.situation.finish = this.situation.start + this.situation.duration / this._speed
-// return this.initAnimations().step()
-// },
-//
-// /**
-// * adds a function / Situation to the animation queue
-// * @param fn function / situation to add
-// * @return this
-// */
-// queue: function (fn) {
-// if (typeof fn === 'function' || fn instanceof SVG.Situation) {
-// this.situations.push(fn)
-// }
-//
-// if (!this.situation) this.situation = this.situations.shift()
-//
-// return this
-// },
-//
-// /**
-// * pulls next element from the queue and execute it
-// * @return this
-// */
-// dequeue: function () {
-// // stop current animation
-// this.stop()
-//
-// // get next animation from queue
-// this.situation = this.situations.shift()
-//
-// if (this.situation) {
-// if (this.situation instanceof SVG.Situation) {
-// this.start()
-// } else {
-// // If it is not a SVG.Situation, then it is a function, we execute it
-// this.situation(this)
-// }
-// }
-//
-// return this
-// },
-//
-// // updates all animations to the current state of the element
-// // this is important when one property could be changed from another property
-// initAnimations: function () {
-// var i, j, source
-// var s = this.situation
-//
-// if (s.init) return this
-//
-// for (i in s.animations) {
-// source = this.target()[i]()
-//
-// if (!Array.isArray(source)) {
-// source = [source]
-// }
-//
-// if (!Array.isArray(s.animations[i])) {
-// s.animations[i] = [s.animations[i]]
-// }
-//
-// // if(s.animations[i].length > source.length) {
-// // source.concat = source.concat(s.animations[i].slice(source.length, s.animations[i].length))
-// // }
-//
-// for (j = source.length; j--;) {
-// // The condition is because some methods return a normal number instead
-// // of a SVG.Number
-// if (s.animations[i][j] instanceof SVG.Number) {
-// source[j] = new SVG.Number(source[j])
-// }
-//
-// s.animations[i][j] = source[j].morph(s.animations[i][j])
-// }
-// }
-//
-// for (i in s.attrs) {
-// s.attrs[i] = new SVG.MorphObj(this.target().attr(i), s.attrs[i])
-// }
-//
-// for (i in s.styles) {
-// s.styles[i] = new SVG.MorphObj(this.target().css(i), s.styles[i])
-// }
-//
-// s.initialTransformation = this.target().matrixify()
-//
-// s.init = true
-// return this
-// },
-//
-// clearQueue: function () {
-// this.situations = []
-// return this
-// },
-//
-// clearCurrent: function () {
-// this.situation = null
-// return this
-// },
-//
-// /** stops the animation immediately
-// * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately.
-// * @param clearQueue A Boolean indicating whether to remove queued animation as well.
-// * @return this
-// */
-// stop: function (jumpToEnd, clearQueue) {
-// var active = this.active
-// this.active = false
-//
-// if (clearQueue) {
-// this.clearQueue()
-// }
-//
-// if (jumpToEnd && this.situation) {
-// // initialize the situation if it was not
-// !active && this.startCurrent()
-// this.atEnd()
-// }
-//
-// this.stopAnimFrame()
-//
-// return this.clearCurrent()
-// },
-//
-// /** resets the element to the state where the current element has started
-// * @return this
-// */
-// reset: function () {
-// if (this.situation) {
-// var temp = this.situation
-// this.stop()
-// this.situation = temp
-// this.atStart()
-// }
-// return this
-// },
-//
-// // Stop the currently-running animation, remove all queued animations, and complete all animations for the element.
-// finish: function () {
-// this.stop(true, false)
-//
-// while (this.dequeue().situation && this.stop(true, false));
-//
-// this.clearQueue().clearCurrent()
-//
-// return this
-// },
-//
-// // set the internal animation pointer at the start position, before any loops, and updates the visualisation
-// atStart: function () {
-// return this.at(0, true)
-// },
-//
-// // set the internal animation pointer at the end position, after all the loops, and updates the visualisation
-// atEnd: function () {
-// if (this.situation.loops === true) {
-// // If in a infinite loop, we end the current iteration
-// this.situation.loops = this.situation.loop + 1
-// }
-//
-// if (typeof this.situation.loops === 'number') {
-// // If performing a finite number of loops, we go after all the loops
-// return this.at(this.situation.loops, true)
-// } else {
-// // If no loops, we just go at the end
-// return this.at(1, true)
-// }
-// },
-//
-// // set the internal animation pointer to the specified position and updates the visualisation
-// // if isAbsPos is true, pos is treated as an absolute position
-// at: function (pos, isAbsPos) {
-// var durDivSpd = this.situation.duration / this._speed
-//
-// this.absPos = pos
-// // If pos is not an absolute position, we convert it into one
-// if (!isAbsPos) {
-// if (this.situation.reversed) this.absPos = 1 - this.absPos
-// this.absPos += this.situation.loop
-// }
-//
-// this.situation.start = +new Date() - this.absPos * durDivSpd
-// this.situation.finish = this.situation.start + durDivSpd
-//
-// return this.step(true)
-// },
-//
-// /**
-// * sets or returns the speed of the animations
-// * @param speed null || Number The new speed of the animations
-// * @return Number || this
-// */
-// speed: function (speed) {
-// if (speed === 0) return this.pause()
-//
-// if (speed) {
-// this._speed = speed
-// // We use an absolute position here so that speed can affect the delay before the animation
-// return this.at(this.absPos, true)
-// } else return this._speed
-// },
-//
-// // Make loopable
-// loop: function (times, reverse) {
-// var c = this.last()
-//
-// // store total loops
-// c.loops = (times != null) ? times : true
-// c.loop = 0
-//
-// if (reverse) c.reversing = true
-// return this
-// },
-//
-// // pauses the animation
-// pause: function () {
-// this.paused = true
-// this.stopAnimFrame()
-//
-// return this
-// },
-//
-// // unpause the animation
-// play: function () {
-// if (!this.paused) return this
-// this.paused = false
-// // We use an absolute position here so that the delay before the animation can be paused
-// return this.at(this.absPos, true)
-// },
-//
-// /**
-// * toggle or set the direction of the animation
-// * true sets direction to backwards while false sets it to forwards
-// * @param reversed Boolean indicating whether to reverse the animation or not (default: toggle the reverse status)
-// * @return this
-// */
-// reverse: function (reversed) {
-// var c = this.last()
-//
-// if (typeof reversed === 'undefined') c.reversed = !c.reversed
-// else c.reversed = reversed
-//
-// return this
-// },
-//
-// /**
-// * returns a float from 0-1 indicating the progress of the current animation
-// * @param eased Boolean indicating whether the returned position should be eased or not
-// * @return number
-// */
-// progress: function (easeIt) {
-// return easeIt ? this.situation.ease(this.pos) : this.pos
-// },
-//
-// /**
-// * adds a callback function which is called when the current animation is finished
-// * @param fn Function which should be executed as callback
-// * @return number
-// */
-// after: function (fn) {
-// var c = this.last()
-// function wrapper (e) {
-// if (e.detail.situation === c) {
-// fn.call(this, c)
-// this.off('finished.fx', wrapper) // prevent memory leak
-// }
-// }
-//
-// this.target().on('finished.fx', wrapper)
-//
-// return this._callStart()
-// },
-//
-// // adds a callback which is called whenever one animation step is performed
-// during: function (fn) {
-// var c = this.last()
-// function wrapper (e) {
-// if (e.detail.situation === c) {
-// fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, c)
-// }
-// }
-//
-// // see above
-// this.target().off('during.fx', wrapper).on('during.fx', wrapper)
-//
-// this.after(function () {
-// this.off('during.fx', wrapper)
-// })
-//
-// return this._callStart()
-// },
-//
-// // calls after ALL animations in the queue are finished
-// afterAll: function (fn) {
-// var wrapper = function wrapper (e) {
-// fn.call(this)
-// this.off('allfinished.fx', wrapper)
-// }
-//
-// // see above
-// this.target().off('allfinished.fx', wrapper).on('allfinished.fx', wrapper)
-//
-// return this._callStart()
-// },
-//
-// // calls on every animation step for all animations
-// duringAll: function (fn) {
-// var wrapper = function (e) {
-// fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, e.detail.situation)
-// }
-//
-// this.target().off('during.fx', wrapper).on('during.fx', wrapper)
-//
-// this.afterAll(function () {
-// this.off('during.fx', wrapper)
-// })
-//
-// return this._callStart()
-// },
-//
-// last: function () {
-// return this.situations.length ? this.situations[this.situations.length - 1] : this.situation
-// },
-//
-// // adds one property to the animations
-// add: function (method, args, type) {
-// this.last()[type || 'animations'][method] = args
-// return this._callStart()
-// },
-//
-// /** perform one step of the animation
-// * @param ignoreTime Boolean indicating whether to ignore time and use position directly or recalculate position based on time
-// * @return this
-// */
-// step: function (ignoreTime) {
-// // convert current time to an absolute position
-// if (!ignoreTime) this.absPos = this.timeToAbsPos(+new Date())
-//
-// // This part convert an absolute position to a position
-// if (this.situation.loops !== false) {
-// var absPos, absPosInt, lastLoop
-//
-// // If the absolute position is below 0, we just treat it as if it was 0
-// absPos = Math.max(this.absPos, 0)
-// absPosInt = Math.floor(absPos)
-//
-// if (this.situation.loops === true || absPosInt < this.situation.loops) {
-// this.pos = absPos - absPosInt
-// lastLoop = this.situation.loop
-// this.situation.loop = absPosInt
-// } else {
-// this.absPos = this.situation.loops
-// this.pos = 1
-// // The -1 here is because we don't want to toggle reversed when all the loops have been completed
-// lastLoop = this.situation.loop - 1
-// this.situation.loop = this.situation.loops
-// }
-//
-// if (this.situation.reversing) {
-// // Toggle reversed if an odd number of loops as occured since the last call of step
-// this.situation.reversed = this.situation.reversed !== Boolean((this.situation.loop - lastLoop) % 2)
-// }
-// } else {
-// // If there are no loop, the absolute position must not be above 1
-// this.absPos = Math.min(this.absPos, 1)
-// this.pos = this.absPos
-// }
-//
-// // while the absolute position can be below 0, the position must not be below 0
-// if (this.pos < 0) this.pos = 0
-//
-// if (this.situation.reversed) this.pos = 1 - this.pos
-//
-// // apply easing
-// var eased = this.situation.ease(this.pos)
-//
-// // call once-callbacks
-// for (var i in this.situation.once) {
-// if (i > this.lastPos && i <= eased) {
-// this.situation.once[i].call(this.target(), this.pos, eased)
-// delete this.situation.once[i]
-// }
-// }
-//
-// // fire during callback with position, eased position and current situation as parameter
-// if (this.active) this.target().fire('during', {pos: this.pos, eased: eased, fx: this, situation: this.situation})
-//
-// // the user may call stop or finish in the during callback
-// // so make sure that we still have a valid situation
-// if (!this.situation) {
-// return this
-// }
-//
-// // apply the actual animation to every property
-// this.eachAt()
-//
-// // do final code when situation is finished
-// if ((this.pos === 1 && !this.situation.reversed) || (this.situation.reversed && this.pos === 0)) {
-// // stop animation callback
-// this.stopAnimFrame()
-//
-// // fire finished callback with current situation as parameter
-// this.target().fire('finished', {fx: this, situation: this.situation})
-//
-// if (!this.situations.length) {
-// this.target().fire('allfinished')
-//
-// // Recheck the length since the user may call animate in the afterAll callback
-// if (!this.situations.length) {
-// this.target().off('.fx') // there shouldnt be any binding left, but to make sure...
-// this.active = false
-// }
-// }
-//
-// // start next animation
-// if (this.active) this.dequeue()
-// else this.clearCurrent()
-// } else if (!this.paused && this.active) {
-// // we continue animating when we are not at the end
-// this.startAnimFrame()
-// }
-//
-// // save last eased position for once callback triggering
-// this.lastPos = eased
-// return this
-// },
-//
-// // calculates the step for every property and calls block with it
-// eachAt: function () {
-// var i, at
-// var self = this
-// var target = this.target()
-// var s = this.situation
-//
-// // apply animations which can be called trough a method
-// for (i in s.animations) {
-// at = [].concat(s.animations[i]).map(function (el) {
-// return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el
-// })
-//
-// target[i].apply(target, at)
-// }
-//
-// // apply animation which has to be applied with attr()
-// for (i in s.attrs) {
-// at = [i].concat(s.attrs[i]).map(function (el) {
-// return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el
-// })
-//
-// target.attr.apply(target, at)
-// }
-//
-// // apply animation which has to be applied with css()
-// for (i in s.styles) {
-// at = [i].concat(s.styles[i]).map(function (el) {
-// return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el
-// })
-//
-// target.css.apply(target, at)
-// }
-//
-// // animate initialTransformation which has to be chained
-// if (s.transforms.length) {
-//
-// // TODO: ANIMATE THE TRANSFORMS
-//
-// // // get initial initialTransformation
-// // at = s.initialTransformation
-// // for(i = 0, len = s.transforms.length; i < len; i++){
-// //
-// // // get next transformation in chain
-// // var a = s.transforms[i]
-// //
-// // // multiply matrix directly
-// // if(a instanceof SVG.Matrix){
-// //
-// // if(a.relative){
-// // at = at.multiply(new SVG.Matrix().morph(a).at(s.ease(this.pos)))
-// // }else{
-// // at = at.morph(a).at(s.ease(this.pos))
-// // }
-// // continue
-// // }
-// //
-// // // when transformation is absolute we have to reset the needed transformation first
-// // if(!a.relative)
-// // a.undo(at.decompose())
-// //
-// // // and reapply it after
-// // at = at.multiply(a.at(s.ease(this.pos)))
-// //
-// // }
-// //
-// // // set new matrix on element
-// // target.matrix(at)
-// }
-//
-// return this
-// },
-//
-// // adds an once-callback which is called at a specific position and never again
-// once: function (pos, fn, isEased) {
-// var c = this.last()
-// if (!isEased) pos = c.ease(pos)
-//
-// c.once[pos] = fn
-//
-// return this
-// },
-//
-// _callStart: function () {
-// setTimeout(function () { this.start() }.bind(this), 0)
-// return this
-// }
-//
-// },
-//
-// parent: SVG.Element,
-//
-// // Add method to parent elements
-// construct: {
-// // Get fx module or create a new one, then animate with given duration and ease
-// animate: function (o, ease, delay) {
-// return (this.fx || (this.fx = new SVG.Timeline(this))).animate(o, ease, delay)
-// },
-// delay: function (delay) {
-// return (this.fx || (this.fx = new SVG.Timeline(this))).delay(delay)
-// },
-// stop: function (jumpToEnd, clearQueue) {
-// if (this.fx) {
-// this.fx.stop(jumpToEnd, clearQueue)
-// }
-//
-// return this
-// },
-// finish: function () {
-// if (this.fx) {
-// this.fx.finish()
-// }
-//
-// return this
-// },
-// // Pause current animation
-// pause: function () {
-// if (this.fx) {
-// this.fx.pause()
-// }
-//
-// return this
-// },
-// // Play paused current animation
-// play: function () {
-// if (this.fx) { this.fx.play() }
-//
-// return this
-// },
-// // Set/Get the speed of the animations
-// speed: function (speed) {
-// if (this.fx) {
-// if (speed == null) { return this.fx.speed() } else { this.fx.speed(speed) }
-// }
-//
-// return this
-// }
-// }
-//
-// })
-//
-// // MorphObj is used whenever no morphable object is given
-// SVG.MorphObj = SVG.invent({
-//
-// create: function (from, to) {
-// // prepare color for morphing
-// if (SVG.Color.isColor(to)) return new SVG.Color(from).morph(to)
-// // prepare value list for morphing
-// if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to)
-// // prepare number for morphing
-// if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to)
-//
-// // prepare for plain morphing
-// this.value = from
-// this.destination = to
-// },
-//
-// extend: {
-// at: function (pos, real) {
-// return real < 1 ? this.value : this.destination
-// },
-//
-// valueOf: function () {
-// return this.value
-// }
-// }
-//
-// })
-//
-// SVG.extend(SVG.Timeline, {
-// // Add animatable attributes
-// attr: function (a, v, relative) {
-// // apply attributes individually
-// if (typeof a === 'object') {
-// for (var key in a) {
-// this.attr(key, a[key])
-// }
-// } else {
-// this.add(a, v, 'attrs')
-// }
-//
-// return this
-// },
-// // Add animatable styles
-// css: function (s, v) {
-// if (typeof s === 'object') {
-// for (var key in s) {
-// this.css(key, s[key])
-// }
-// } else {
-// this.add(s, v, 'styles')
-// }
-//
-// return this
-// },
-// // Animatable x-axis
-// x: function (x, relative) {
-// if (this.target() instanceof SVG.G) {
-// this.transform({x: x}, relative)
-// return this
-// }
-//
-// var num = new SVG.Number(x)
-// num.relative = relative
-// return this.add('x', num)
-// },
-// // Animatable y-axis
-// y: function (y, relative) {
-// if (this.target() instanceof SVG.G) {
-// this.transform({y: y}, relative)
-// return this
-// }
-//
-// var num = new SVG.Number(y)
-// num.relative = relative
-// return this.add('y', num)
-// },
-// // Animatable center x-axis
-// cx: function (x) {
-// return this.add('cx', new SVG.Number(x))
-// },
-// // Animatable center y-axis
-// cy: function (y) {
-// return this.add('cy', new SVG.Number(y))
-// },
-// // Add animatable move
-// move: function (x, y) {
-// return this.x(x).y(y)
-// },
-// // Add animatable center
-// center: function (x, y) {
-// return this.cx(x).cy(y)
-// },
-// // Add animatable size
-// size: function (width, height) {
-// if (this.target() instanceof SVG.Text) {
-// // animate font size for Text elements
-// this.attr('font-size', width)
-// } else {
-// // animate bbox based size for all other elements
-// var box
-//
-// if (!width || !height) {
-// box = this.target().bbox()
-// }
-//
-// if (!width) {
-// width = box.width / box.height * height
-// }
-//
-// if (!height) {
-// height = box.height / box.width * width
-// }
-//
-// this.add('width', new SVG.Number(width))
-// .add('height', new SVG.Number(height))
-// }
-//
-// return this
-// },
-// // Add animatable width
-// width: function (width) {
-// return this.add('width', new SVG.Number(width))
-// },
-// // Add animatable height
-// height: function (height) {
-// return this.add('height', new SVG.Number(height))
-// },
-// // Add animatable plot
-// plot: function (a, b, c, d) {
-// // Lines can be plotted with 4 arguments
-// if (arguments.length === 4) {
-// return this.plot([a, b, c, d])
-// }
-//
-// return this.add('plot', new (this.target().MorphArray)(a))
-// },
-// // Add leading method
-// leading: function (value) {
-// return this.target().leading
-// ? this.add('leading', new SVG.Number(value))
-// : this
-// },
-// // Add animatable viewbox
-// viewbox: function (x, y, width, height) {
-// if (this.target() instanceof SVG.Container) {
-// this.add('viewbox', new SVG.Box(x, y, width, height))
-// }
-//
-// return this
-// },
-// update: function (o) {
-// if (this.target() instanceof SVG.Stop) {
-// if (typeof o === 'number' || o instanceof SVG.Number) {
-// return this.update({
-// offset: arguments[0],
-// color: arguments[1],
-// opacity: arguments[2]
-// })
-// }
-//
-// if (o.opacity != null) this.attr('stop-opacity', o.opacity)
-// if (o.color != null) this.attr('stop-color', o.color)
-// if (o.offset != null) this.attr('offset', o.offset)
-// }
-//
-// return this
-// }
-// })
diff --git a/src/gradient.js b/src/gradient.js
deleted file mode 100644
index 45a4e08..0000000
--- a/src/gradient.js
+++ /dev/null
@@ -1,104 +0,0 @@
-SVG.Gradient = SVG.invent({
- // Initialize node
- create: function (type) {
- SVG.Element.call(this, typeof type === 'object' ? type : SVG.create(type + 'Gradient'))
- },
-
- // Inherit from
- inherit: SVG.Container,
-
- // Add class methods
- extend: {
- // Add a color stop
- stop: function (offset, color, opacity) {
- return this.put(new SVG.Stop()).update(offset, color, opacity)
- },
- // Update gradient
- update: function (block) {
- // remove all stops
- this.clear()
-
- // invoke passed block
- if (typeof block === 'function') {
- block.call(this, this)
- }
-
- return this
- },
- // Return the fill id
- url: function () {
- return 'url(#' + this.id() + ')'
- },
- // Alias string convertion to fill
- toString: function () {
- return this.url()
- },
- // custom attr to handle transform
- attr: function (a, b, c) {
- if (a === 'transform') a = 'gradientTransform'
- return SVG.Container.prototype.attr.call(this, a, b, c)
- }
- },
-
- // Add parent method
- construct: {
- // Create gradient element in defs
- gradient: function (type, block) {
- return this.defs().gradient(type, block)
- }
- }
-})
-
-// Add animatable methods to both gradient and fx module
-SVG.extend([SVG.Gradient, SVG.Timeline], {
- // From position
- from: function (x, y) {
- return (this._target || this).type === 'radialGradient'
- ? this.attr({ fx: new SVG.Number(x), fy: new SVG.Number(y) })
- : this.attr({ x1: new SVG.Number(x), y1: new SVG.Number(y) })
- },
- // To position
- to: function (x, y) {
- return (this._target || this).type === 'radialGradient'
- ? this.attr({ cx: new SVG.Number(x), cy: new SVG.Number(y) })
- : this.attr({ x2: new SVG.Number(x), y2: new SVG.Number(y) })
- }
-})
-
-// Base gradient generation
-SVG.extend(SVG.Defs, {
- // define gradient
- gradient: function (type, block) {
- return this.put(new SVG.Gradient(type)).update(block)
- }
-
-})
-
-SVG.Stop = SVG.invent({
- // Initialize node
- create: 'stop',
-
- // Inherit from
- inherit: SVG.Element,
-
- // Add class methods
- extend: {
- // add color stops
- update: function (o) {
- if (typeof o === 'number' || o instanceof SVG.Number) {
- o = {
- offset: arguments[0],
- color: arguments[1],
- opacity: arguments[2]
- }
- }
-
- // set attributes
- if (o.opacity != null) this.attr('stop-opacity', o.opacity)
- if (o.color != null) this.attr('stop-color', o.color)
- if (o.offset != null) this.attr('offset', new SVG.Number(o.offset))
-
- return this
- }
- }
-})
diff --git a/src/group.js b/src/group.js
deleted file mode 100644
index 0088a1c..0000000
--- a/src/group.js
+++ /dev/null
@@ -1,19 +0,0 @@
-SVG.G = SVG.invent({
- // Initialize node
- create: 'g',
-
- // Inherit from
- inherit: SVG.Container,
-
- // Add class methods
- extend: {
- },
-
- // Add parent method
- construct: {
- // Create a group element
- group: function () {
- return this.put(new SVG.G())
- }
- }
-})
diff --git a/src/helpers.js b/src/helpers.js
deleted file mode 100644
index c2073cf..0000000
--- a/src/helpers.js
+++ /dev/null
@@ -1,311 +0,0 @@
-/* eslint no-unused-vars: 0 */
-
-function createElement (element, makeNested) {
- if (element instanceof SVG.Element) return element
-
- if (typeof element === 'object') {
- return SVG.adopt(element)
- }
-
- if (element == null) {
- return new SVG.Doc()
- }
-
- if (typeof element === 'string' && element.charAt(0) !== '<') {
- return SVG.adopt(document.querySelector(element))
- }
-
- var node = SVG.create('svg')
- node.innerHTML = element
-
- element = SVG.adopt(node.firstElementChild)
-
- return element
-}
-
-function isNulledBox (box) {
- return !box.w && !box.h && !box.x && !box.y
-}
-
-function domContains (node) {
- return (document.documentElement.contains || function (node) {
- // This is IE - it does not support contains() for top-level SVGs
- while (node.parentNode) {
- node = node.parentNode
- }
- return node === document
- }).call(document.documentElement, node)
-}
-
-function pathRegReplace (a, b, c, d) {
- return c + d.replace(SVG.regex.dots, ' .')
-}
-
-// creates deep clone of array
-function arrayClone (arr) {
- var clone = arr.slice(0)
- for (var i = clone.length; i--;) {
- if (Array.isArray(clone[i])) {
- clone[i] = arrayClone(clone[i])
- }
- }
- return clone
-}
-
-// tests if a given element is instance of an object
-function is (el, obj) {
- return el instanceof obj
-}
-
-// tests if a given selector matches an element
-function matches (el, selector) {
- return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector)
-}
-
-// Convert dash-separated-string to camelCase
-function camelCase (s) {
- return s.toLowerCase().replace(/-(.)/g, function (m, g) {
- return g.toUpperCase()
- })
-}
-
-// Capitalize first letter of a string
-function capitalize (s) {
- return s.charAt(0).toUpperCase() + s.slice(1)
-}
-
-// Ensure to six-based hex
-function fullHex (hex) {
- return hex.length === 4
- ? [ '#',
- hex.substring(1, 2), hex.substring(1, 2),
- hex.substring(2, 3), hex.substring(2, 3),
- hex.substring(3, 4), hex.substring(3, 4)
- ].join('')
- : hex
-}
-
-// Component to hex value
-function compToHex (comp) {
- var hex = comp.toString(16)
- return hex.length === 1 ? '0' + hex : hex
-}
-
-// Calculate proportional width and height values when necessary
-function proportionalSize (element, width, height) {
- if (width == null || height == null) {
- var box = element.bbox()
-
- if (width == null) {
- width = box.width / box.height * height
- } else if (height == null) {
- height = box.height / box.width * width
- }
- }
-
- return {
- width: width,
- height: height
- }
-}
-
-// Map matrix array to object
-function arrayToMatrix (a) {
- return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] }
-}
-
-// Add centre point to transform object
-function ensureCentre (o, target) {
- o.cx = o.cx == null ? target.bbox().cx : o.cx
- o.cy = o.cy == null ? target.bbox().cy : o.cy
-}
-
-// 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 + ' '
-}
-
-// Deep new id assignment
-function assignNewId (node) {
- // do the same for SVG child nodes as well
- for (var i = node.children.length - 1; i >= 0; i--) {
- assignNewId(node.children[i])
- }
-
- if (node.id) {
- return SVG.adopt(node).id(SVG.eid(node.nodeName))
- }
-
- return SVG.adopt(node)
-}
-
-// Add more bounding box properties
-function fullBox (b) {
- if (b.x == null) {
- b.x = 0
- b.y = 0
- b.width = 0
- b.height = 0
- }
-
- b.w = b.width
- b.h = b.height
- b.x2 = b.x + b.width
- b.y2 = b.y + b.height
- b.cx = b.x + b.width / 2
- b.cy = b.y + b.height / 2
-
- return b
-}
-
-// Get id from reference string
-function idFromReference (url) {
- var m = (url || '').toString().match(SVG.regex.reference)
-
- if (m) return m[1]
-}
-
-// Create matrix array for looping
-var abcdef = 'abcdef'.split('')
-
-function closeEnough (a, b, threshold) {
- return Math.abs(b - a) < (threshold || 1e-6)
-}
-
-function isMatrixLike (o) {
- return (
- o.a != null ||
- o.b != null ||
- o.c != null ||
- o.d != null ||
- o.e != null ||
- o.f != null
- )
-}
-
-// TODO: Refactor this to a static function of matrix.js
-function formatTransforms (o) {
- // Get all of the parameters required to form the matrix
- var flipBoth = o.flip === 'both' || o.flip === true
- var flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1
- var flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1
- var skewX = o.skew && o.skew.length ? o.skew[0]
- : isFinite(o.skew) ? o.skew
- : isFinite(o.skewX) ? o.skewX
- : 0
- var skewY = o.skew && o.skew.length ? o.skew[1]
- : isFinite(o.skew) ? o.skew
- : isFinite(o.skewY) ? o.skewY
- : 0
- var scaleX = o.scale && o.scale.length ? o.scale[0] * flipX
- : isFinite(o.scale) ? o.scale * flipX
- : isFinite(o.scaleX) ? o.scaleX * flipX
- : flipX
- var scaleY = o.scale && o.scale.length ? o.scale[1] * flipY
- : isFinite(o.scale) ? o.scale * flipY
- : isFinite(o.scaleY) ? o.scaleY * flipY
- : flipY
- var shear = o.shear || 0
- var theta = o.rotate || o.theta || 0
- var origin = new SVG.Point(o.origin || o.around || o.ox || o.originX, o.oy || o.originY)
- var ox = origin.x
- var oy = origin.y
- var position = new SVG.Point(o.position || o.px || o.positionX, o.py || o.positionY)
- var px = position.x
- var py = position.y
- var translate = new SVG.Point(o.translate || o.tx || o.translateX, o.ty || o.translateY)
- var tx = translate.x
- var ty = translate.y
- var relative = new SVG.Point(o.relative || o.rx || o.relativeX, o.ry || o.relativeY)
- var rx = relative.x
- var ry = relative.y
-
- // Populate all of the values
- return {
- scaleX, scaleY, skewX, skewY, shear, theta, rx, ry, tx, ty, ox, oy, px, py
- }
-}
-
-// left matrix, right matrix, target matrix which is overwritten
-function matrixMultiply (l, r, o) {
- // Work out the product directly
- var a = l.a * r.a + l.c * r.b
- var b = l.b * r.a + l.d * r.b
- var c = l.a * r.c + l.c * r.d
- var d = l.b * r.c + l.d * r.d
- var e = l.e + l.a * r.e + l.c * r.f
- var f = l.f + l.b * r.e + l.d * r.f
-
- // make sure to use local variables because l/r and o could be the same
- o.a = a
- o.b = b
- o.c = c
- o.d = d
- o.e = e
- o.f = f
-
- return o
-}
-
-function getOrigin (o, element) {
- // Allow origin or around as the names
- let origin = o.origin // o.around == null ? o.origin : o.around
- let ox, oy
-
- // Allow the user to pass a string to rotate around a given point
- if (typeof origin === 'string' || origin == null) {
- // Get the bounding box of the element with no transformations applied
- const string = (origin || 'center').toLowerCase().trim()
- const { height, width, x, y } = element.bbox()
-
- // Calculate the transformed x and y coordinates
- let bx = string.includes('left') ? x
- : string.includes('right') ? x + width
- : x + width / 2
- let by = string.includes('top') ? y
- : string.includes('bottom') ? y + height
- : y + height / 2
-
- // Set the bounds eg : "bottom-left", "Top right", "middle" etc...
- ox = o.ox != null ? o.ox : bx
- oy = o.oy != null ? o.oy : by
- } else {
- ox = origin[0]
- oy = origin[1]
- }
-
- // Return the origin as it is if it wasn't a string
- return [ ox, oy ]
-}
diff --git a/src/hyperlink.js b/src/hyperlink.js
deleted file mode 100644
index cb0a341..0000000
--- a/src/hyperlink.js
+++ /dev/null
@@ -1,41 +0,0 @@
-SVG.A = SVG.invent({
- // Initialize node
- create: 'a',
-
- // Inherit from
- inherit: SVG.Container,
-
- // Add class methods
- extend: {
- // Link url
- to: function (url) {
- return this.attr('href', url, SVG.xlink)
- },
- // Link target attribute
- target: function (target) {
- return this.attr('target', target)
- }
- },
-
- // Add parent method
- construct: {
- // Create a hyperlink element
- link: function (url) {
- return this.put(new SVG.A()).to(url)
- }
- }
-})
-
-SVG.extend(SVG.Element, {
- // Create a hyperlink element
- linkTo: function (url) {
- var link = new SVG.A()
-
- if (typeof url === 'function') { url.call(link, link) } else {
- link.to(url)
- }
-
- return this.parent().put(link).put(this)
- }
-
-})
diff --git a/src/image.js b/src/image.js
deleted file mode 100644
index f9395eb..0000000
--- a/src/image.js
+++ /dev/null
@@ -1,57 +0,0 @@
-SVG.Image = SVG.invent({
- // Initialize node
- create: 'image',
-
- // Inherit from
- inherit: SVG.Shape,
-
- // Add class methods
- extend: {
- // (re)load image
- load: function (url, callback) {
- if (!url) return this
-
- var img = new window.Image()
-
- SVG.on(img, 'load', function (e) {
- var p = this.parent(SVG.Pattern)
-
- // ensure image size
- if (this.width() === 0 && this.height() === 0) {
- this.size(img.width, img.height)
- }
-
- if (p instanceof SVG.Pattern) {
- // ensure pattern size if not set
- if (p.width() === 0 && p.height() === 0) {
- p.size(this.width(), this.height())
- }
- }
-
- if (typeof callback === 'function') {
- callback.call(this, {
- width: img.width,
- height: img.height,
- ratio: img.width / img.height,
- url: url
- })
- }
- }, this)
-
- SVG.on(img, 'load error', function () {
- // dont forget to unbind memory leaking events
- SVG.off(img)
- })
-
- return this.attr('href', (img.src = url), SVG.xlink)
- }
- },
-
- // Add parent method
- construct: {
- // create image element, load image and set its size
- image: function (source, callback) {
- return this.put(new SVG.Image()).size(0, 0).load(source, callback)
- }
- }
-})
diff --git a/src/line.js b/src/line.js
deleted file mode 100644
index da0c0ca..0000000
--- a/src/line.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/* global proportionalSize */
-
-SVG.Line = SVG.invent({
- // Initialize node
- create: 'line',
-
- // Inherit from
- inherit: SVG.Shape,
-
- // Add class methods
- extend: {
- // Get array
- array: function () {
- return new SVG.PointArray([
- [ this.attr('x1'), this.attr('y1') ],
- [ this.attr('x2'), this.attr('y2') ]
- ])
- },
-
- // Overwrite native plot() method
- plot: function (x1, y1, x2, y2) {
- if (x1 == null) {
- return this.array()
- } else if (typeof y1 !== 'undefined') {
- x1 = { x1: x1, y1: y1, x2: x2, y2: y2 }
- } else {
- x1 = new SVG.PointArray(x1).toLine()
- }
-
- return this.attr(x1)
- },
-
- // Move by left top corner
- move: function (x, y) {
- return this.attr(this.array().move(x, y).toLine())
- },
-
- // Set element size to given width and height
- size: function (width, height) {
- var p = proportionalSize(this, width, height)
- return this.attr(this.array().size(p.width, p.height).toLine())
- }
- },
-
- // Add parent method
- construct: {
- // Create a line element
- line: function (x1, y1, x2, y2) {
- // make sure plot is called as a setter
- // x1 is not necessarily a number, it can also be an array, a string and a SVG.PointArray
- return SVG.Line.prototype.plot.apply(
- this.put(new SVG.Line())
- , x1 != null ? [x1, y1, x2, y2] : [0, 0, 0, 0]
- )
- }
- }
-})
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..278e8fd
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,163 @@
+/* Optional Modules */
+import './modules/optional/arrange.js'
+import './modules/optional/class.js'
+import './modules/optional/css.js'
+import './modules/optional/data.js'
+import './modules/optional/memory.js'
+import './modules/optional/sugar.js'
+import './modules/optional/transform.js'
+
+import Morphable, {
+ NonMorphable,
+ ObjectBag,
+ TransformBag,
+ makeMorphable,
+ registerMorphableType
+} from './types/Morphable.js'
+import { extend } from './utils/adopter.js'
+import { getMethodsFor } from './utils/methods.js'
+import Box from './types/Box.js'
+import Circle from './elements/Circle.js'
+import Color from './types/Color.js'
+import Container from './elements/Container.js'
+import Defs from './elements/Defs.js'
+import Doc from './elements/Doc.js'
+import Dom from './elements/Dom.js'
+import Element from './elements/Element.js'
+import Ellipse from './elements/Ellipse.js'
+import EventTarget from './types/EventTarget.js'
+import Gradient from './elements/Gradient.js'
+import Image from './elements/Image.js'
+import Line from './elements/Line.js'
+import Marker from './elements/Marker.js'
+import Matrix from './types/Matrix.js'
+import Path from './elements/Path.js'
+import PathArray from './types/PathArray.js'
+import Pattern from './elements/Pattern.js'
+import PointArray from './types/PointArray.js'
+import Polygon from './elements/Polygon.js'
+import Polyline from './elements/Polyline.js'
+import Rect from './elements/Rect.js'
+import SVGArray from './types/SVGArray.js'
+import SVGNumber from './types/SVGNumber.js'
+import Shape from './elements/Shape.js'
+import Text from './elements/Text.js'
+import Tspan from './elements/Tspan.js'
+import * as defaults from './modules/core/defaults.js'
+
+export {
+ Morphable,
+ registerMorphableType,
+ makeMorphable,
+ TransformBag,
+ ObjectBag,
+ NonMorphable
+}
+
+export { defaults }
+export * from './utils/utils.js'
+export * from './modules/core/namespaces.js'
+export { default as parser } from './modules/core/parser.js'
+export { default as find } from './modules/core/selector.js'
+export * from './modules/core/event.js'
+export * from './utils/adopter.js'
+
+/* Animation Modules */
+export { default as Animator } from './animation/Animator.js'
+export { Controller, Ease, PID, Spring, easing } from './animation/Controller.js'
+export { default as Queue } from './animation/Queue.js'
+export { default as Runner } from './animation/Runner.js'
+export { default as Timeline } from './animation/Timeline.js'
+
+/* Types */
+export { default as SVGArray } from './types/SVGArray.js'
+export { default as Box } from './types/Box.js'
+export { default as Color } from './types/Color.js'
+export { default as EventTarget } from './types/EventTarget.js'
+export { default as Matrix } from './types/Matrix.js'
+export { default as SVGNumber } from './types/SVGNumber.js'
+export { default as PathArray } from './types/PathArray.js'
+export { default as Point } from './types/Point.js'
+export { default as PointArray } from './types/PointArray.js'
+
+/* Elements */
+export { default as Bare } from './elements/Bare.js'
+export { default as Circle } from './elements/Circle.js'
+export { default as ClipPath } from './elements/ClipPath.js'
+export { default as Container } from './elements/Container.js'
+export { default as Defs } from './elements/Defs.js'
+export { default as Doc } from './elements/Doc.js'
+export { default as Dom } from './elements/Dom.js'
+export { default as Element } from './elements/Element.js'
+export { default as Ellipse } from './elements/Ellipse.js'
+export { default as Gradient } from './elements/Gradient.js'
+export { default as G } from './elements/G.js'
+export { default as HtmlNode } from './elements/HtmlNode.js'
+export { default as A } from './elements/A.js'
+export { default as Image } from './elements/Image.js'
+export { default as Line } from './elements/Line.js'
+export { default as Marker } from './elements/Marker.js'
+export { default as Mask } from './elements/Mask.js'
+export { default as Path } from './elements/Path.js'
+export { default as Pattern } from './elements/Pattern.js'
+export { default as Polygon } from './elements/Polygon.js'
+export { default as Polyline } from './elements/Polyline.js'
+export { default as Rect } from './elements/Rect.js'
+export { default as Shape } from './elements/Shape.js'
+export { default as Stop } from './elements/Stop.js'
+export { default as Symbol } from './elements/Symbol.js'
+export { default as Text } from './elements/Text.js'
+export { default as TextPath } from './elements/TextPath.js'
+export { default as Tspan } from './elements/Tspan.js'
+export { default as Use } from './elements/Use.js'
+
+extend([
+ Doc,
+ Symbol,
+ Image,
+ Pattern,
+ Marker
+], getMethodsFor('viewbox'))
+
+extend([
+ Line,
+ Polyline,
+ Polygon,
+ Path
+], getMethodsFor('marker'))
+
+extend(Text, getMethodsFor('Text'))
+extend(Path, getMethodsFor('Path'))
+
+extend(Defs, getMethodsFor('Defs'))
+
+extend([
+ Text,
+ Tspan
+], getMethodsFor('Tspan'))
+
+extend([
+ Rect,
+ Ellipse,
+ Circle,
+ Gradient
+], getMethodsFor('radius'))
+
+extend(EventTarget, getMethodsFor('EventTarget'))
+extend(Dom, getMethodsFor('Dom'))
+extend(Element, getMethodsFor('Element'))
+extend(Shape, getMethodsFor('Shape'))
+// extend(Element, getConstructor('Memory'))
+extend(Container, getMethodsFor('Container'))
+
+registerMorphableType([
+ SVGNumber,
+ Color,
+ Box,
+ Matrix,
+ SVGArray,
+ PointArray,
+ PathArray
+])
+
+makeMorphable()
diff --git a/src/marker.js b/src/marker.js
deleted file mode 100644
index 32f8e4e..0000000
--- a/src/marker.js
+++ /dev/null
@@ -1,78 +0,0 @@
-SVG.Marker = SVG.invent({
- // Initialize node
- create: 'marker',
-
- // Inherit from
- inherit: SVG.Container,
-
- // Add class methods
- extend: {
- // Set width of element
- width: function (width) {
- return this.attr('markerWidth', width)
- },
- // Set height of element
- height: function (height) {
- return this.attr('markerHeight', height)
- },
- // Set marker refX and refY
- ref: function (x, y) {
- return this.attr('refX', x).attr('refY', y)
- },
- // Update marker
- update: function (block) {
- // remove all content
- this.clear()
-
- // invoke passed block
- if (typeof block === 'function') { block.call(this, this) }
-
- return this
- },
- // Return the fill id
- toString: function () {
- return 'url(#' + this.id() + ')'
- }
- },
-
- // Add parent method
- construct: {
- marker: function (width, height, block) {
- // Create marker element in defs
- return this.defs().marker(width, height, block)
- }
- }
-
-})
-
-SVG.extend(SVG.Defs, {
- // Create marker
- marker: function (width, height, block) {
- // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto
- return this.put(new SVG.Marker())
- .size(width, height)
- .ref(width / 2, height / 2)
- .viewbox(0, 0, width, height)
- .attr('orient', 'auto')
- .update(block)
- }
-
-})
-
-SVG.extend([SVG.Line, SVG.Polyline, SVG.Polygon, SVG.Path], {
- // Create and attach markers
- marker: function (marker, width, height, block) {
- var attr = ['marker']
-
- // Build attribute name
- if (marker !== 'all') attr.push(marker)
- attr = attr.join('-')
-
- // Set marker attribute
- marker = arguments[1] instanceof SVG.Marker
- ? arguments[1]
- : this.doc().marker(width, height, block)
-
- return this.attr(attr, marker)
- }
-})
diff --git a/src/mask.js b/src/mask.js
deleted file mode 100644
index e40d80f..0000000
--- a/src/mask.js
+++ /dev/null
@@ -1,51 +0,0 @@
-SVG.Mask = SVG.invent({
- // Initialize node
- create: 'mask',
-
- // Inherit from
- inherit: SVG.Container,
-
- // Add class methods
- extend: {
- // Unmask all masked elements and remove itself
- remove: function () {
- // unmask all targets
- this.targets().forEach(function (el) {
- el.unmask()
- })
-
- // remove mask from parent
- return SVG.Element.prototype.remove.call(this)
- },
-
- targets: function () {
- return SVG.select('svg [mask*="' + this.id() + '"]')
- }
- },
-
- // Add parent method
- construct: {
- // Create masking element
- mask: function () {
- return this.defs().put(new SVG.Mask())
- }
- }
-})
-
-SVG.extend(SVG.Element, {
- // Distribute mask to svg element
- maskWith: function (element) {
- // use given mask or create a new one
- var masker = element instanceof SVG.Mask ? element : this.parent().mask().add(element)
-
- // apply mask
- return this.attr('mask', 'url("#' + masker.id() + '")')
- },
- // Unmask element
- unmask: function () {
- return this.attr('mask', null)
- },
- masker: function () {
- return this.reference('mask')
- }
-})
diff --git a/src/matrix.js b/src/matrix.js
deleted file mode 100644
index 666b898..0000000
--- a/src/matrix.js
+++ /dev/null
@@ -1,472 +0,0 @@
-/* global abcdef arrayToMatrix closeEnough formatTransforms isMatrixLike matrixMultiply */
-
-SVG.Matrix = SVG.invent({
- // Initialize
- create: function (source) {
- var base = arrayToMatrix([1, 0, 0, 1, 0, 0])
-
- // ensure source as object
- source = source instanceof SVG.Element ? source.matrixify()
- : typeof source === 'string' ? arrayToMatrix(source.split(SVG.regex.delimiter).map(parseFloat))
- : Array.isArray(source) ? arrayToMatrix(source)
- : (typeof source === 'object' && isMatrixLike(source)) ? source
- : (typeof source === 'object') ? new SVG.Matrix().transform(source)
- : arguments.length === 6 ? arrayToMatrix([].slice.call(arguments))
- : base
-
- // Merge the source matrix with the base matrix
- this.a = source.a != null ? source.a : base.a
- this.b = source.b != null ? source.b : base.b
- this.c = source.c != null ? source.c : base.c
- this.d = source.d != null ? source.d : base.d
- this.e = source.e != null ? source.e : base.e
- this.f = source.f != null ? source.f : base.f
- },
-
- // Add methods
- extend: {
-
- // Clones this matrix
- clone: function () {
- return new SVG.Matrix(this)
- },
-
- // Transform a matrix into another matrix by manipulating the space
- transform: function (o) {
- // Check if o is a matrix and then left multiply it directly
- if (isMatrixLike(o)) {
- var matrix = new SVG.Matrix(o)
- return matrix.multiplyO(this)
- }
-
- // Get the proposed transformations and the current transformations
- var t = formatTransforms(o)
- var current = this
- let { x: ox, y: oy } = new SVG.Point(t.ox, t.oy).transform(current)
-
- // Construct the resulting matrix
- var transformer = new SVG.Matrix()
- .translateO(t.rx, t.ry)
- .lmultiplyO(current)
- .translateO(-ox, -oy)
- .scaleO(t.scaleX, t.scaleY)
- .skewO(t.skewX, t.skewY)
- .shearO(t.shear)
- .rotateO(t.theta)
- .translateO(ox, oy)
-
- // If we want the origin at a particular place, we force it there
- if (isFinite(t.px) || isFinite(t.py)) {
- const origin = new SVG.Point(ox, oy).transform(transformer)
- // TODO: Replace t.px with isFinite(t.px)
- const dx = t.px ? t.px - origin.x : 0
- const dy = t.py ? t.py - origin.y : 0
- transformer.translateO(dx, dy)
- }
-
- // Translate now after positioning
- transformer.translateO(t.tx, t.ty)
- return transformer
- },
-
- // Applies a matrix defined by its affine parameters
- compose: function (o) {
- if (o.origin) {
- o.originX = o.origin[0]
- o.originY = o.origin[1]
- }
- // Get the parameters
- var ox = o.originX || 0
- var oy = o.originY || 0
- var sx = o.scaleX || 1
- var sy = o.scaleY || 1
- var lam = o.shear || 0
- var theta = o.rotate || 0
- var tx = o.translateX || 0
- var ty = o.translateY || 0
-
- // Apply the standard matrix
- var result = new SVG.Matrix()
- .translateO(-ox, -oy)
- .scaleO(sx, sy)
- .shearO(lam)
- .rotateO(theta)
- .translateO(tx, ty)
- .lmultiplyO(this)
- .translateO(ox, oy)
- return result
- },
-
- // Decomposes this matrix into its affine parameters
- decompose: function (cx = 0, cy = 0) {
- // Get the parameters from the matrix
- var a = this.a
- var b = this.b
- var c = this.c
- var d = this.d
- var e = this.e
- var f = this.f
-
- // Figure out if the winding direction is clockwise or counterclockwise
- var determinant = a * d - b * c
- var ccw = determinant > 0 ? 1 : -1
-
- // Since we only shear in x, we can use the x basis to get the x scale
- // and the rotation of the resulting matrix
- var sx = ccw * Math.sqrt(a * a + b * b)
- var thetaRad = Math.atan2(ccw * b, ccw * a)
- var theta = 180 / Math.PI * thetaRad
- var ct = Math.cos(thetaRad)
- var st = Math.sin(thetaRad)
-
- // We can then solve the y basis vector simultaneously to get the other
- // two affine parameters directly from these parameters
- var lam = (a * c + b * d) / determinant
- var sy = ((c * sx) / (lam * a - b)) || ((d * sx) / (lam * b + a))
-
- // Use the translations
- let tx = e - cx + cx * ct * sx + cy * (lam * ct * sx - st * sy)
- let ty = f - cy + cx * st * sx + cy * (lam * st * sx + ct * sy)
-
- // Construct the decomposition and return it
- return {
- // Return the affine parameters
- scaleX: sx,
- scaleY: sy,
- shear: lam,
- rotate: theta,
- translateX: tx,
- translateY: ty,
- originX: cx,
- originY: cy,
-
- // Return the matrix parameters
- a: this.a,
- b: this.b,
- c: this.c,
- d: this.d,
- e: this.e,
- f: this.f
- }
- },
-
- // Morph one matrix into another
- morph: function (matrix) {
- // Store new destination
- this.destination = new SVG.Matrix(matrix)
- return this
- },
-
- // Get morphed matrix at a given position
- at: function (pos) {
- // Make sure a destination is defined
- if (!this.destination) return this
-
- // Calculate morphed matrix at a given position
- var matrix = new SVG.Matrix({
- a: this.a + (this.destination.a - this.a) * pos,
- b: this.b + (this.destination.b - this.b) * pos,
- c: this.c + (this.destination.c - this.c) * pos,
- d: this.d + (this.destination.d - this.d) * pos,
- e: this.e + (this.destination.e - this.e) * pos,
- f: this.f + (this.destination.f - this.f) * pos
- })
-
- return matrix
- },
-
- // Left multiplies by the given matrix
- multiply: function (matrix) {
- return this.clone().multiplyO(matrix)
- },
-
- multiplyO: function (matrix) {
- // Get the matrices
- var l = this
- var r = matrix instanceof SVG.Matrix
- ? matrix
- : new SVG.Matrix(matrix)
-
- return matrixMultiply(l, r, this)
- },
-
- lmultiply: function (matrix) {
- return this.clone().lmultiplyO(matrix)
- },
-
- lmultiplyO: function (matrix) {
- var r = this
- var l = matrix instanceof SVG.Matrix
- ? matrix
- : new SVG.Matrix(matrix)
-
- return matrixMultiply(l, r, this)
- },
-
- // Inverses matrix
- inverseO: function () {
- // Get the current parameters out of the matrix
- var a = this.a
- var b = this.b
- var c = this.c
- var d = this.d
- var e = this.e
- var f = this.f
-
- // Invert the 2x2 matrix in the top left
- var det = a * d - b * c
- if (!det) throw new Error('Cannot invert ' + this)
-
- // Calculate the top 2x2 matrix
- var na = d / det
- var nb = -b / det
- var nc = -c / det
- var nd = a / det
-
- // Apply the inverted matrix to the top right
- var ne = -(na * e + nc * f)
- var nf = -(nb * e + nd * f)
-
- // Construct the inverted matrix
- this.a = na
- this.b = nb
- this.c = nc
- this.d = nd
- this.e = ne
- this.f = nf
-
- return this
- },
-
- inverse: function () {
- return this.clone().inverseO()
- },
-
- // Translate matrix
- translate: function (x, y) {
- return this.clone().translateO(x, y)
- },
-
- translateO: function (x, y) {
- this.e += x || 0
- this.f += y || 0
- return this
- },
-
- // Scale matrix
- scale: function (x, y, cx, cy) {
- return this.clone().scaleO(...arguments)
- },
-
- scaleO: function (x, y = x, cx = 0, cy = 0) {
- // Support uniform scaling
- if (arguments.length === 3) {
- cy = cx
- cx = y
- y = x
- }
-
- let {a, b, c, d, e, f} = this
-
- this.a = a * x
- this.b = b * y
- this.c = c * x
- this.d = d * y
- this.e = e * x - cx * x + cx
- this.f = f * y - cy * y + cy
-
- return this
- },
-
- // Rotate matrix
- rotate: function (r, cx, cy) {
- return this.clone().rotateO(r, cx, cy)
- },
-
- rotateO: function (r, cx = 0, cy = 0) {
- // Convert degrees to radians
- r = SVG.utils.radians(r)
-
- let cos = Math.cos(r)
- let sin = Math.sin(r)
-
- let {a, b, c, d, e, f} = this
-
- this.a = a * cos - b * sin
- this.b = b * cos + a * sin
- this.c = c * cos - d * sin
- this.d = d * cos + c * sin
- this.e = e * cos - f * sin + cy * sin - cx * cos + cx
- this.f = f * cos + e * sin - cx * sin - cy * cos + cy
-
- return this
- },
-
- // Flip matrix on x or y, at a given offset
- flip: function (axis, around) {
- return this.clone().flipO(axis, around)
- },
-
- flipO: function (axis, around) {
- return axis === 'x' ? this.scaleO(-1, 1, around, 0)
- : axis === 'y' ? this.scaleO(1, -1, 0, around)
- : this.scaleO(-1, -1, axis, around || axis) // Define an x, y flip point
- },
-
- // Shear matrix
- shear: function (a, cx, cy) {
- return this.clone().shearO(a, cx, cy)
- },
-
- shearO: function (lx, cx = 0, cy = 0) {
- let {a, b, c, d, e, f} = this
-
- this.a = a + b * lx
- this.c = c + d * lx
- this.e = e + f * lx - cy * lx
-
- return this
- },
-
- // Skew Matrix
- skew: function (x, y, cx, cy) {
- return this.clone().skewO(...arguments)
- },
-
- skewO: function (x, y = x, cx = 0, cy = 0) {
- // support uniformal skew
- if (arguments.length === 3) {
- cy = cx
- cx = y
- y = x
- }
-
- // Convert degrees to radians
- x = SVG.utils.radians(x)
- y = SVG.utils.radians(y)
-
- let lx = Math.tan(x)
- let ly = Math.tan(y)
-
- let {a, b, c, d, e, f} = this
-
- this.a = a + b * lx
- this.b = b + a * ly
- this.c = c + d * lx
- this.d = d + c * ly
- this.e = e + f * lx - cy * lx
- this.f = f + e * ly - cx * ly
-
- return this
- },
-
- // SkewX
- skewX: function (x, cx, cy) {
- return this.skew(x, 0, cx, cy)
- },
-
- skewXO: function (x, cx, cy) {
- return this.skewO(x, 0, cx, cy)
- },
-
- // SkewY
- skewY: function (y, cx, cy) {
- return this.skew(0, y, cx, cy)
- },
-
- skewYO: function (y, cx, cy) {
- return this.skewO(0, y, cx, cy)
- },
-
- // Transform around a center point
- aroundO: function (cx, cy, matrix) {
- var dx = cx || 0
- var dy = cy || 0
- return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy)
- },
-
- around: function (cx, cy, matrix) {
- return this.clone().aroundO(cx, cy, matrix)
- },
-
- // Convert to native SVGMatrix
- native: function () {
- // create new matrix
- var matrix = SVG.parser.nodes.svg.node.createSVGMatrix()
-
- // update with current values
- for (var i = abcdef.length - 1; i >= 0; i--) {
- matrix[abcdef[i]] = this[abcdef[i]]
- }
-
- return matrix
- },
-
- // Check if two matrices are equal
- equals: function (other) {
- var comp = new SVG.Matrix(other)
- return closeEnough(this.a, comp.a) && closeEnough(this.b, comp.b) &&
- closeEnough(this.c, comp.c) && closeEnough(this.d, comp.d) &&
- closeEnough(this.e, comp.e) && closeEnough(this.f, comp.f)
- },
-
- // Convert matrix to string
- toString: function () {
- return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')'
- },
-
- toArray: function () {
- return [this.a, this.b, this.c, this.d, this.e, this.f]
- },
-
- valueOf: function () {
- return {
- a: this.a,
- b: this.b,
- c: this.c,
- d: this.d,
- e: this.e,
- f: this.f
- }
- }
- },
-
- // Define parent
- parent: SVG.Element,
-
- // Add parent method
- construct: {
- // Get current matrix
- ctm: function () {
- return new SVG.Matrix(this.node.getCTM())
- },
- // Get current screen matrix
- screenCTM: function () {
- /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537
- This is needed because FF does not return the transformation matrix
- for the inner coordinate system when getScreenCTM() is called on nested svgs.
- However all other Browsers do that */
- if (this instanceof SVG.Doc && !this.isRoot()) {
- var rect = this.rect(1, 1)
- var m = rect.node.getScreenCTM()
- rect.remove()
- return new SVG.Matrix(m)
- }
- return new SVG.Matrix(this.node.getScreenCTM())
- }
- }
-})
-
-// let extensions = {}
-// ['rotate'].forEach((method) => {
-// let methodO = method + 'O'
-// extensions[method] = function (...args) {
-// return new SVG.Matrix(this)[methodO](...args)
-// }
-// })
-//
-// SVG.extend(SVG.Matrix, extensions)
-
-// function matrixMultiplyParams (matrix, a, b, c, d, e, f) {
-// return matrixMultiply({a, b, c, d, e, f}, matrix, matrix)
-// }
diff --git a/src/memory.js b/src/memory.js
deleted file mode 100644
index 57dfa02..0000000
--- a/src/memory.js
+++ /dev/null
@@ -1,37 +0,0 @@
-
-SVG.extend(SVG.Element, {
- // Remember arbitrary data
- remember: function (k, v) {
- // remember every item in an object individually
- if (typeof arguments[0] === 'object') {
- for (var key in k) {
- this.remember(key, k[key])
- }
- } else if (arguments.length === 1) {
- // retrieve memory
- return this.memory()[k]
- } else {
- // store memory
- this.memory()[k] = v
- }
-
- return this
- },
-
- // Erase a given memory
- forget: function () {
- if (arguments.length === 0) {
- this._memory = {}
- } else {
- for (var i = arguments.length - 1; i >= 0; i--) {
- delete this.memory()[arguments[i]]
- }
- }
- return this
- },
-
- // Initialize or return local memory object
- memory: function () {
- return this._memory || (this._memory = {})
- }
-})
diff --git a/src/modules/core/attr.js b/src/modules/core/attr.js
new file mode 100644
index 0000000..ed34dc9
--- /dev/null
+++ b/src/modules/core/attr.js
@@ -0,0 +1,80 @@
+import { isImage, isNumber } from './regex.js'
+import { attrs as defaults } from './defaults.js'
+import Color from '../../types/Color.js'
+import SVGArray from '../../types/SVGArray.js'
+import SVGNumber from '../../types/SVGNumber.js'
+
+// Set svg element attribute
+export default function attr (attr, val, ns) {
+ // act as full getter
+ if (attr == null) {
+ // get an object of attributes
+ attr = {}
+ val = this.node.attributes
+
+ for (let node of val) {
+ attr[node.nodeName] = isNumber.test(node.nodeValue)
+ ? parseFloat(node.nodeValue)
+ : node.nodeValue
+ }
+
+ return attr
+ } else if (Array.isArray(attr)) {
+ // FIXME: implement
+ } else if (typeof attr === 'object') {
+ // apply every attribute individually if an object is passed
+ for (val in attr) this.attr(val, attr[val])
+ } else if (val === null) {
+ // remove value
+ this.node.removeAttribute(attr)
+ } else if (val == null) {
+ // act as a getter if the first and only argument is not an object
+ val = this.node.getAttribute(attr)
+ return val == null ? defaults[attr] // FIXME: do we need to return defaults?
+ : isNumber.test(val) ? parseFloat(val)
+ : val
+ } else {
+ // convert image fill and stroke to patterns
+ if (attr === 'fill' || attr === 'stroke') {
+ if (isImage.test(val)) {
+ val = this.doc().defs().image(val)
+ }
+ }
+
+ // FIXME: This is fine, but what about the lines above?
+ // How does attr know about image()?
+ while (typeof val.attrHook === 'function') {
+ val = val.attrHook(this, attr)
+ }
+
+ // ensure correct numeric values (also accepts NaN and Infinity)
+ if (typeof val === 'number') {
+ val = new SVGNumber(val)
+ } else if (Color.isColor(val)) {
+ // ensure full hex color
+ val = new Color(val)
+ } else if (val.constructor === Array) {
+ // Check for plain arrays and parse array values
+ val = new SVGArray(val)
+ }
+
+ // if the passed attribute is leading...
+ if (attr === 'leading') {
+ // ... call the leading method instead
+ if (this.leading) {
+ this.leading(val)
+ }
+ } else {
+ // set given attribute on node
+ typeof ns === 'string' ? this.node.setAttributeNS(ns, attr, val.toString())
+ : this.node.setAttribute(attr, val.toString())
+ }
+
+ // rebuild if required
+ if (this.rebuild && (attr === 'font-size' || attr === 'x')) {
+ this.rebuild()
+ }
+ }
+
+ return this
+}
diff --git a/src/modules/core/circled.js b/src/modules/core/circled.js
new file mode 100644
index 0000000..9a3b1ad
--- /dev/null
+++ b/src/modules/core/circled.js
@@ -0,0 +1,64 @@
+// FIXME: import this to runner
+import { proportionalSize } from '../../utils/utils.js'
+import SVGNumber from '../../types/SVGNumber.js'
+
+// Radius x value
+export function rx (rx) {
+ return this.attr('rx', rx)
+}
+
+// Radius y value
+export function ry (ry) {
+ return this.attr('ry', ry)
+}
+
+// Move over x-axis
+export function x (x) {
+ return x == null
+ ? this.cx() - this.rx()
+ : this.cx(x + this.rx())
+}
+
+// Move over y-axis
+export function y (y) {
+ return y == null
+ ? this.cy() - this.ry()
+ : this.cy(y + this.ry())
+}
+
+// Move by center over x-axis
+export function cx (x) {
+ return x == null
+ ? this.attr('cx')
+ : this.attr('cx', x)
+}
+
+// Move by center over y-axis
+export function cy (y) {
+ return y == null
+ ? this.attr('cy')
+ : this.attr('cy', y)
+}
+
+// Set width of element
+export function width (width) {
+ return width == null
+ ? this.rx() * 2
+ : this.rx(new SVGNumber(width).divide(2))
+}
+
+// Set height of element
+export function height (height) {
+ return height == null
+ ? this.ry() * 2
+ : this.ry(new SVGNumber(height).divide(2))
+}
+
+// Custom size function
+export function size (width, height) {
+ var p = proportionalSize(this, width, height)
+
+ return this
+ .rx(new SVGNumber(p.width).divide(2))
+ .ry(new SVGNumber(p.height).divide(2))
+}
diff --git a/src/modules/core/defaults.js b/src/modules/core/defaults.js
new file mode 100644
index 0000000..0d496bc
--- /dev/null
+++ b/src/modules/core/defaults.js
@@ -0,0 +1,48 @@
+
+export function noop () {}
+
+// Default animation values
+export let timeline = {
+ duration: 400,
+ ease: '>',
+ delay: 0
+}
+
+// Default attribute values
+export let attrs = {
+
+ // fill and stroke
+ 'fill-opacity': 1,
+ 'stroke-opacity': 1,
+ 'stroke-width': 0,
+ 'stroke-linejoin': 'miter',
+ 'stroke-linecap': 'butt',
+ fill: '#000000',
+ stroke: '#000000',
+ opacity: 1,
+
+ // position
+ x: 0,
+ y: 0,
+ cx: 0,
+ cy: 0,
+
+ // size
+ width: 0,
+ height: 0,
+
+ // radius
+ r: 0,
+ rx: 0,
+ ry: 0,
+
+ // gradient
+ offset: 0,
+ 'stop-opacity': 1,
+ 'stop-color': '#000000',
+
+ // text
+ 'font-size': 16,
+ 'font-family': 'Helvetica, Arial, sans-serif',
+ 'text-anchor': 'start'
+}
diff --git a/src/event.js b/src/modules/core/event.js
index 4f16609..2fcaf58 100644
--- a/src/event.js
+++ b/src/modules/core/event.js
@@ -1,48 +1,35 @@
-// Add events to elements
-;[ 'click',
- 'dblclick',
- 'mousedown',
- 'mouseup',
- 'mouseover',
- 'mouseout',
- 'mousemove',
- 'mouseenter',
- 'mouseleave',
- 'touchstart',
- 'touchmove',
- 'touchleave',
- 'touchend',
- 'touchcancel' ].forEach(function (event) {
- // add event to SVG.Element
- SVG.Element.prototype[event] = function (f) {
- if (f === null) {
- SVG.off(this, event)
- } else {
- SVG.on(this, event, f)
- }
- return this
- }
- })
+import { delimiter } from './regex.js'
+import { makeInstance } from '../../utils/adopter.js'
+
+let listenerId = 0
+
+function getEvents (node) {
+ const n = makeInstance(node).getEventHolder()
+ if (!n.events) n.events = {}
+ return n.events
+}
-SVG.listenerId = 0
+function getEventTarget (node) {
+ return makeInstance(node).getEventTarget()
+}
+
+function clearEvents (node) {
+ const n = makeInstance(node).getEventHolder()
+ if (n.events) n.events = {}
+}
// Add event binder in the SVG namespace
-SVG.on = function (node, events, listener, binding, options) {
+export function on (node, events, listener, binding, options) {
var l = listener.bind(binding || node)
- var n = node instanceof SVG.EventTarget ? node.getEventTarget() : node
+ var bag = getEvents(node)
+ var n = getEventTarget(node)
// events can be an array of events or a string of events
- events = Array.isArray(events) ? events : events.split(SVG.regex.delimiter)
-
- // ensure instance object for nodes which are not adopted
- n.instance = n.instance || {events: {}}
-
- // pull event handlers from the element
- var bag = n.instance.events
+ events = Array.isArray(events) ? events : events.split(delimiter)
// add id to listener
if (!listener._svgjsListenerId) {
- listener._svgjsListenerId = ++SVG.listenerId
+ listener._svgjsListenerId = ++listenerId
}
events.forEach(function (event) {
@@ -62,9 +49,9 @@ SVG.on = function (node, events, listener, binding, options) {
}
// Add event unbinder in the SVG namespace
-SVG.off = function (node, events, listener, options) {
- var n = node instanceof SVG.EventTarget ? node.getEventTarget() : node
- if (!n.instance) return
+export function off (node, events, listener, options) {
+ var bag = getEvents(node)
+ var n = getEventTarget(node)
// listener can be a function or a number
if (typeof listener === 'function') {
@@ -72,11 +59,8 @@ SVG.off = function (node, events, listener, options) {
if (!listener) return
}
- // pull event handlers from the element
- var bag = n.instance.events
-
// events can be an array of events or a string or undefined
- events = Array.isArray(events) ? events : (events || '').split(SVG.regex.delimiter)
+ events = Array.isArray(events) ? events : (events || '').split(delimiter)
events.forEach(function (event) {
var ev = event && event.split('.')[0]
@@ -94,7 +78,7 @@ SVG.off = function (node, events, listener, options) {
} else if (ev && ns) {
// remove all listeners for a namespaced event
if (bag[ev] && bag[ev][ns]) {
- for (l in bag[ev][ns]) { SVG.off(n, [ev, ns].join('.'), l) }
+ for (l in bag[ev][ns]) { off(n, [ev, ns].join('.'), l) }
delete bag[ev][ns]
}
@@ -102,33 +86,33 @@ SVG.off = function (node, events, listener, options) {
// remove all listeners for a specific namespace
for (event in bag) {
for (namespace in bag[event]) {
- if (ns === namespace) { SVG.off(n, [event, ns].join('.')) }
+ if (ns === namespace) { off(n, [event, ns].join('.')) }
}
}
} else if (ev) {
// remove all listeners for the event
if (bag[ev]) {
- for (namespace in bag[ev]) { SVG.off(n, [ev, namespace].join('.')) }
+ for (namespace in bag[ev]) { off(n, [ev, namespace].join('.')) }
delete bag[ev]
}
} else {
// remove all listeners on a given node
- for (event in bag) { SVG.off(n, event) }
+ for (event in bag) { off(n, event) }
- n.instance.events = {}
+ clearEvents(node)
}
})
}
-SVG.dispatch = function (node, event, data) {
- var n = node instanceof SVG.EventTarget ? node.getEventTarget() : node
+export function dispatch (node, event, data) {
+ var n = getEventTarget(node)
// Dispatch event
if (event instanceof window.Event) {
n.dispatchEvent(event)
} else {
- event = new window.CustomEvent(event, {detail: data, cancelable: true})
+ event = new window.CustomEvent(event, { detail: data, cancelable: true })
n.dispatchEvent(event)
}
return event
diff --git a/src/modules/core/gradiented.js b/src/modules/core/gradiented.js
new file mode 100644
index 0000000..d34a9fe
--- /dev/null
+++ b/src/modules/core/gradiented.js
@@ -0,0 +1,14 @@
+// FIXME: add to runner
+import SVGNumber from '../../types/SVGNumber.js'
+
+export function from (x, y) {
+ return (this._element || this).type === 'radialGradient'
+ ? this.attr({ fx: new SVGNumber(x), fy: new SVGNumber(y) })
+ : this.attr({ x1: new SVGNumber(x), y1: new SVGNumber(y) })
+}
+
+export function to (x, y) {
+ return (this._element || this).type === 'radialGradient'
+ ? this.attr({ cx: new SVGNumber(x), cy: new SVGNumber(y) })
+ : this.attr({ x2: new SVGNumber(x), y2: new SVGNumber(y) })
+}
diff --git a/src/modules/core/namespaces.js b/src/modules/core/namespaces.js
new file mode 100644
index 0000000..3791298
--- /dev/null
+++ b/src/modules/core/namespaces.js
@@ -0,0 +1,5 @@
+// Default namespaces
+export let ns = 'http://www.w3.org/2000/svg'
+export let xmlns = 'http://www.w3.org/2000/xmlns/'
+export let xlink = 'http://www.w3.org/1999/xlink'
+export let svgjs = 'http://svgjs.com/svgjs'
diff --git a/src/modules/core/parser.js b/src/modules/core/parser.js
new file mode 100644
index 0000000..7a656ef
--- /dev/null
+++ b/src/modules/core/parser.js
@@ -0,0 +1,26 @@
+import Doc from '../../elements/Doc.js'
+
+export default function parser () {
+ // Reuse cached element if possible
+ if (!parser.nodes) {
+ let svg = new Doc().size(2, 0)
+ svg.node.cssText = [
+ 'opacity: 0',
+ 'position: absolute',
+ 'left: -100%',
+ 'top: -100%',
+ 'overflow: hidden'
+ ].join(';')
+
+ let path = svg.path().node
+
+ parser.nodes = { svg, path }
+ }
+
+ if (!parser.nodes.svg.node.parentNode) {
+ let b = document.body || document.documentElement
+ parser.nodes.svg.addTo(b)
+ }
+
+ return parser.nodes
+}
diff --git a/src/modules/core/pointed.js b/src/modules/core/pointed.js
new file mode 100644
index 0000000..95e6819
--- /dev/null
+++ b/src/modules/core/pointed.js
@@ -0,0 +1,25 @@
+import PointArray from '../../types/PointArray.js'
+
+export let MorphArray = PointArray
+
+// Move by left top corner over x-axis
+export function x (x) {
+ return x == null ? this.bbox().x : this.move(x, this.bbox().y)
+}
+
+// Move by left top corner over y-axis
+export function y (y) {
+ return y == null ? this.bbox().y : this.move(this.bbox().x, y)
+}
+
+// Set width of element
+export function width (width) {
+ let b = this.bbox()
+ return width == null ? b.width : this.size(width, b.height)
+}
+
+// Set height of element
+export function height (height) {
+ let b = this.bbox()
+ return height == null ? b.height : this.size(b.width, height)
+}
diff --git a/src/modules/core/poly.js b/src/modules/core/poly.js
new file mode 100644
index 0000000..ad12020
--- /dev/null
+++ b/src/modules/core/poly.js
@@ -0,0 +1,31 @@
+import { proportionalSize } from '../../utils/utils.js'
+import PointArray from '../../types/PointArray.js'
+
+// Get array
+export function array () {
+ return this._array || (this._array = new PointArray(this.attr('points')))
+}
+
+// Plot new path
+export function plot (p) {
+ return (p == null) ? this.array()
+ : this.clear().attr('points', typeof p === 'string' ? p
+ : (this._array = new PointArray(p)))
+}
+
+// Clear array cache
+export function clear () {
+ delete this._array
+ return this
+}
+
+// Move by left top corner
+export function move (x, y) {
+ return this.attr('points', this.array().move(x, y))
+}
+
+// Set element size to given width and height
+export function size (width, height) {
+ let p = proportionalSize(this, width, height)
+ return this.attr('points', this.array().size(p.width, p.height))
+}
diff --git a/src/modules/core/regex.js b/src/modules/core/regex.js
new file mode 100644
index 0000000..1056554
--- /dev/null
+++ b/src/modules/core/regex.js
@@ -0,0 +1,58 @@
+// Parse unit value
+export let numberAndUnit = /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i
+
+// Parse hex value
+export let hex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i
+
+// Parse rgb value
+export let rgb = /rgb\((\d+),(\d+),(\d+)\)/
+
+// Parse reference id
+export let reference = /(#[a-z0-9\-_]+)/i
+
+// splits a transformation chain
+export let transforms = /\)\s*,?\s*/
+
+// Whitespace
+export let whitespace = /\s/g
+
+// Test hex value
+export let isHex = /^#[a-f0-9]{3,6}$/i
+
+// Test rgb value
+export let isRgb = /^rgb\(/
+
+// Test css declaration
+export let isCss = /[^:]+:[^;]+;?/
+
+// Test for blank string
+export let isBlank = /^(\s+)?$/
+
+// Test for numeric string
+export let isNumber = /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i
+
+// Test for percent value
+export let isPercent = /^-?[\d.]+%$/
+
+// Test for image url
+export let isImage = /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i
+
+// split at whitespace and comma
+export let delimiter = /[\s,]+/
+
+// The following regex are used to parse the d attribute of a path
+
+// Matches all hyphens which are not after an exponent
+export let hyphen = /([^e])-/gi
+
+// Replaces and tests for all path letters
+export let pathLetters = /[MLHVCSQTAZ]/gi
+
+// yes we need this one, too
+export let isPathLetter = /[MLHVCSQTAZ]/i
+
+// matches 0.154.23.45
+export let numbersWithDots = /((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi
+
+// matches .
+export let dots = /\./g
diff --git a/src/modules/core/selector.js b/src/modules/core/selector.js
new file mode 100644
index 0000000..1e0b55e
--- /dev/null
+++ b/src/modules/core/selector.js
@@ -0,0 +1,16 @@
+import { adopt } from '../../utils/adopter.js'
+import { map } from '../../utils/utils.js'
+import { registerMethods } from '../../utils/methods.js'
+
+export default function baseFind (query, parent) {
+ return map((parent || document).querySelectorAll(query), function (node) {
+ return adopt(node)
+ })
+}
+
+// Scoped find method
+export function find (query) {
+ return baseFind(query, this.node)
+}
+
+registerMethods('Dom', { find })
diff --git a/src/modules/core/textable.js b/src/modules/core/textable.js
new file mode 100644
index 0000000..c9a90db
--- /dev/null
+++ b/src/modules/core/textable.js
@@ -0,0 +1,18 @@
+// Create plain text node
+export function plain (text) {
+ // clear if build mode is disabled
+ if (this._build === false) {
+ this.clear()
+ }
+
+ // create text node
+ this.node.appendChild(document.createTextNode(text))
+
+ return this
+}
+
+// FIXME: Does this also work for textpath?
+// Get length of text element
+export function length () {
+ return this.node.getComputedTextLength()
+}
diff --git a/src/modules/optional/arrange.js b/src/modules/optional/arrange.js
new file mode 100644
index 0000000..ca0e074
--- /dev/null
+++ b/src/modules/optional/arrange.js
@@ -0,0 +1,98 @@
+import { registerMethods } from '../../utils/methods.js'
+
+// Get all siblings, including myself
+export function siblings () {
+ return this.parent().children()
+}
+
+// Get the curent position siblings
+export function position () {
+ return this.parent().index(this)
+}
+
+// Get the next element (will return null if there is none)
+export function next () {
+ return this.siblings()[this.position() + 1]
+}
+
+// Get the next element (will return null if there is none)
+export function prev () {
+ return this.siblings()[this.position() - 1]
+}
+
+// Send given element one step forward
+export function forward () {
+ var i = this.position() + 1
+ var p = this.parent()
+
+ // move node one step forward
+ p.removeElement(this).add(this, i)
+
+ // make sure defs node is always at the top
+ if (typeof p.isRoot === 'function' && p.isRoot()) {
+ p.node.appendChild(p.defs().node)
+ }
+
+ return this
+}
+
+// Send given element one step backward
+export function backward () {
+ var i = this.position()
+
+ if (i > 0) {
+ this.parent().removeElement(this).add(this, i - 1)
+ }
+
+ return this
+}
+
+// Send given element all the way to the front
+export function front () {
+ var p = this.parent()
+
+ // Move node forward
+ p.node.appendChild(this.node)
+
+ // Make sure defs node is always at the top
+ if (typeof p.isRoot === 'function' && p.isRoot()) {
+ p.node.appendChild(p.defs().node)
+ }
+
+ return this
+}
+
+// Send given element all the way to the back
+export function back () {
+ if (this.position() > 0) {
+ this.parent().removeElement(this).add(this, 0)
+ }
+
+ return this
+}
+
+// Inserts a given element before the targeted element
+export function before (element) {
+ element.remove()
+
+ var i = this.position()
+
+ this.parent().add(element, i)
+
+ return this
+}
+
+// Inserts a given element after the targeted element
+export function after (element) {
+ element.remove()
+
+ var i = this.position()
+
+ this.parent().add(element, i + 1)
+
+ return this
+}
+
+registerMethods('Dom', {
+ siblings, position, next, prev, forward, backward, front, back, before, after
+})
diff --git a/src/modules/optional/class.js b/src/modules/optional/class.js
new file mode 100644
index 0000000..1d28fd5
--- /dev/null
+++ b/src/modules/optional/class.js
@@ -0,0 +1,44 @@
+import { delimiter } from '../core/regex.js'
+import { registerMethods } from '../../utils/methods.js'
+
+// Return array of classes on the node
+function classes () {
+ var attr = this.attr('class')
+ return attr == null ? [] : attr.trim().split(delimiter)
+}
+
+// Return true if class exists on the node, false otherwise
+function hasClass (name) {
+ return this.classes().indexOf(name) !== -1
+}
+
+// Add class to the node
+function addClass (name) {
+ if (!this.hasClass(name)) {
+ var array = this.classes()
+ array.push(name)
+ this.attr('class', array.join(' '))
+ }
+
+ return this
+}
+
+// Remove class from the node
+function removeClass (name) {
+ if (this.hasClass(name)) {
+ this.attr('class', this.classes().filter(function (c) {
+ return c !== name
+ }).join(' '))
+ }
+
+ return this
+}
+
+// Toggle the presence of a class on the node
+function toggleClass (name) {
+ return this.hasClass(name) ? this.removeClass(name) : this.addClass(name)
+}
+
+registerMethods('Dom', {
+ classes, hasClass, addClass, removeClass, toggleClass
+})
diff --git a/src/modules/optional/css.js b/src/modules/optional/css.js
new file mode 100644
index 0000000..924b13d
--- /dev/null
+++ b/src/modules/optional/css.js
@@ -0,0 +1,71 @@
+// FIXME: We dont need exports
+import { camelCase } from '../../utils/utils.js'
+import { isBlank } from '../core/regex.js'
+import { registerMethods } from '../../utils/methods.js'
+
+// Dynamic style generator
+export function css (style, val) {
+ let ret = {}
+ if (arguments.length === 0) {
+ // get full style as object
+ this.node.style.cssText.split(/\s*;\s*/)
+ .filter(function (el) { return !!el.length })
+ .forEach(function (el) {
+ let t = el.split(/\s*:\s*/)
+ ret[t[0]] = t[1]
+ })
+ return ret
+ }
+
+ if (arguments.length < 2) {
+ // get style properties in the array
+ if (Array.isArray(style)) {
+ for (let name of style) {
+ let cased = camelCase(name)
+ ret[cased] = this.node.style[cased]
+ }
+ return ret
+ }
+
+ // get style for property
+ if (typeof style === 'string') {
+ return this.node.style[camelCase(style)]
+ }
+
+ // set styles in object
+ if (typeof style === 'object') {
+ for (let name in style) {
+ // set empty string if null/undefined/'' was given
+ this.node.style[camelCase(name)] =
+ (style[name] == null || isBlank.test(style[name])) ? '' : style[name]
+ }
+ }
+ }
+
+ // set style for property
+ if (arguments.length === 2) {
+ this.node.style[camelCase(style)] =
+ (val == null || isBlank.test(val)) ? '' : val
+ }
+
+ return this
+}
+
+// Show element
+export function show () {
+ return this.css('display', '')
+}
+
+// Hide element
+export function hide () {
+ return this.css('display', 'none')
+}
+
+// Is element visible?
+export function visible () {
+ return this.css('display') !== 'none'
+}
+
+registerMethods('Dom', {
+ css, show, hide, visible
+})
diff --git a/src/modules/optional/data.js b/src/modules/optional/data.js
new file mode 100644
index 0000000..341d129
--- /dev/null
+++ b/src/modules/optional/data.js
@@ -0,0 +1,26 @@
+import { registerMethods } from '../../utils/methods.js'
+
+// Store data values on svg nodes
+export function data (a, v, r) {
+ if (typeof a === 'object') {
+ for (v in a) {
+ this.data(v, a[v])
+ }
+ } else if (arguments.length < 2) {
+ try {
+ return JSON.parse(this.attr('data-' + a))
+ } catch (e) {
+ return this.attr('data-' + a)
+ }
+ } else {
+ this.attr('data-' + a,
+ v === null ? null
+ : r === true || typeof v === 'string' || typeof v === 'number' ? v
+ : JSON.stringify(v)
+ )
+ }
+
+ return this
+}
+
+registerMethods('Dom', { data })
diff --git a/src/modules/optional/memory.js b/src/modules/optional/memory.js
new file mode 100644
index 0000000..d1bf7cf
--- /dev/null
+++ b/src/modules/optional/memory.js
@@ -0,0 +1,39 @@
+import { registerMethods } from '../../utils/methods.js'
+// FIXME: We need a constructor to set this up
+
+// Remember arbitrary data
+export function remember (k, v) {
+ // remember every item in an object individually
+ if (typeof arguments[0] === 'object') {
+ for (var key in k) {
+ this.remember(key, k[key])
+ }
+ } else if (arguments.length === 1) {
+ // retrieve memory
+ return this.memory()[k]
+ } else {
+ // store memory
+ this.memory()[k] = v
+ }
+
+ return this
+}
+
+// Erase a given memory
+export function forget () {
+ if (arguments.length === 0) {
+ this._memory = {}
+ } else {
+ for (var i = arguments.length - 1; i >= 0; i--) {
+ delete this.memory()[arguments[i]]
+ }
+ }
+ return this
+}
+
+// return local memory object
+export function memory () {
+ return (this._memory = this._memory || {})
+}
+
+registerMethods('Dom', { remember, forget, memory })
diff --git a/src/sugar.js b/src/modules/optional/sugar.js
index ad991af..904e353 100644
--- a/src/sugar.js
+++ b/src/modules/optional/sugar.js
@@ -1,3 +1,11 @@
+import { registerMethods } from '../../utils/methods.js'
+import Color from '../../types/Color.js'
+import Element from '../../elements/Element.js'
+import Matrix from '../../types/Matrix.js'
+import Point from '../../types/Point.js'
+import Runner from '../../animation/Runner.js'
+import SVGNumber from '../../types/SVGNumber.js'
+
// Define list of available attributes for stroke and fill
var sugar = {
stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'],
@@ -16,7 +24,7 @@ var sugar = {
if (typeof o === 'undefined') {
return this
}
- if (typeof o === 'string' || SVG.Color.isRgb(o) || (o && typeof o.fill === 'function')) {
+ if (typeof o === 'string' || Color.isRgb(o) || (o instanceof Element)) {
this.attr(m, o)
} else {
// set all attributes from sugar.fill and sugar.stroke list
@@ -30,35 +38,35 @@ var sugar = {
return this
}
- SVG.extend([SVG.Element, SVG.Timeline], extension)
+ registerMethods(['Shape', 'Runner'], extension)
})
-SVG.extend([SVG.Element, SVG.Timeline], {
+registerMethods(['Element', 'Runner'], {
// Let the user set the matrix directly
matrix: function (mat, b, c, d, e, f) {
// Act as a getter
if (mat == null) {
- return new SVG.Matrix(this)
+ return new Matrix(this)
}
// Act as a setter, the user can pass a matrix or a set of numbers
- return this.attr('transform', new SVG.Matrix(mat, b, c, d, e, f))
+ return this.attr('transform', new Matrix(mat, b, c, d, e, f))
},
// Map rotation to transform
rotate: function (angle, cx, cy) {
- return this.transform({rotate: angle, ox: cx, oy: cy}, true)
+ return this.transform({ rotate: angle, ox: cx, oy: cy }, true)
},
// Map skew to transform
skew: function (x, y, cx, cy) {
return arguments.length === 1 || arguments.length === 3
- ? this.transform({skew: x, ox: y, oy: cx}, true)
- : this.transform({skew: [x, y], ox: cx, oy: cy}, true)
+ ? this.transform({ skew: x, ox: y, oy: cx }, true)
+ : this.transform({ skew: [x, y], ox: cx, oy: cy }, true)
},
shear: function (lam, cx, cy) {
- return this.transform({shear: lam, ox: cx, oy: cy}, true)
+ return this.transform({ shear: lam, ox: cx, oy: cy }, true)
},
// Map scale to transform
@@ -82,13 +90,13 @@ SVG.extend([SVG.Element, SVG.Timeline], {
flip: function (direction, around) {
var directionString = typeof direction === 'string' ? direction
: isFinite(direction) ? 'both'
- : 'both'
+ : 'both'
var origin = (direction === 'both' && isFinite(around)) ? [around, around]
: (direction === 'x') ? [around, 0]
- : (direction === 'y') ? [0, around]
- : isFinite(direction) ? [direction, direction]
- : [0, 0]
- this.transform({flip: directionString, origin: origin}, true)
+ : (direction === 'y') ? [0, around]
+ : isFinite(direction) ? [direction, direction]
+ : [0, 0]
+ this.transform({ flip: directionString, origin: origin }, true)
},
// Opacity
@@ -98,12 +106,12 @@ SVG.extend([SVG.Element, SVG.Timeline], {
// Relative move over x axis
dx: function (x) {
- return this.x(new SVG.Number(x).plus(this instanceof SVG.Timeline ? 0 : this.x()), true)
+ return this.x(new SVGNumber(x).plus(this instanceof Runner ? 0 : this.x()), true)
},
// Relative move over y axis
dy: function (y) {
- return this.y(new SVG.Number(y).plus(this instanceof SVG.Timeline ? 0 : this.y()), true)
+ return this.y(new SVGNumber(y).plus(this instanceof Runner ? 0 : this.y()), true)
},
// Relative move over x and y axes
@@ -112,28 +120,28 @@ SVG.extend([SVG.Element, SVG.Timeline], {
}
})
-SVG.extend([SVG.Rect, SVG.Ellipse, SVG.Circle, SVG.Gradient, SVG.Timeline], {
+registerMethods('radius', {
// Add x and y radius
radius: function (x, y) {
- var type = (this._target || this).type
+ var type = (this._element || this).type
return type === 'radialGradient' || type === 'radialGradient'
- ? this.attr('r', new SVG.Number(x))
+ ? this.attr('r', new SVGNumber(x))
: this.rx(x).ry(y == null ? x : y)
}
})
-SVG.extend(SVG.Path, {
+registerMethods('Path', {
// Get path length
length: function () {
return this.node.getTotalLength()
},
// Get point at length
pointAt: function (length) {
- return new SVG.Point(this.node.getPointAtLength(length))
+ return new Point(this.node.getPointAtLength(length))
}
})
-SVG.extend([SVG.Parent, SVG.Text, SVG.Tspan, SVG.Timeline], {
+registerMethods(['Element', 'Runner'], {
// Set font
font: function (a, v) {
if (typeof a === 'object') {
@@ -141,11 +149,11 @@ SVG.extend([SVG.Parent, SVG.Text, SVG.Tspan, SVG.Timeline], {
}
return a === 'leading'
- ? this.leading(v)
+ ? this.leading(v)
: a === 'anchor'
? this.attr('text-anchor', v)
- : a === 'size' || a === 'family' || a === 'weight' || a === 'stretch' || a === 'variant' || a === 'style'
- ? this.attr('font-' + a, v)
- : this.attr(a, v)
+ : a === 'size' || a === 'family' || a === 'weight' || a === 'stretch' || a === 'variant' || a === 'style'
+ ? this.attr('font-' + a, v)
+ : this.attr(a, v)
}
})
diff --git a/src/modules/optional/transform.js b/src/modules/optional/transform.js
new file mode 100644
index 0000000..7535fdc
--- /dev/null
+++ b/src/modules/optional/transform.js
@@ -0,0 +1,72 @@
+import { getOrigin } from '../../utils/utils.js'
+import { delimiter, transforms } from '../core/regex.js'
+import { registerMethods } from '../../utils/methods.js'
+import Matrix from '../../types/Matrix.js'
+
+// Reset all transformations
+export function untransform () {
+ return this.attr('transform', null)
+}
+
+// merge the whole transformation chain into one matrix and returns it
+export function matrixify () {
+ var matrix = (this.attr('transform') || '')
+ // split transformations
+ .split(transforms).slice(0, -1).map(function (str) {
+ // generate key => value pairs
+ var kv = str.trim().split('(')
+ return [kv[0],
+ kv[1].split(delimiter)
+ .map(function (str) { return parseFloat(str) })
+ ]
+ })
+ .reverse()
+ // merge every transformation into one matrix
+ .reduce(function (matrix, transform) {
+ if (transform[0] === 'matrix') {
+ return matrix.lmultiply(Matrix.fromArray(transform[1]))
+ }
+ return matrix[transform[0]].apply(matrix, transform[1])
+ }, new Matrix())
+
+ return matrix
+}
+
+// add an element to another parent without changing the visual representation on the screen
+export function toParent (parent) {
+ if (this === parent) return this
+ var ctm = this.screenCTM()
+ var pCtm = parent.screenCTM().inverse()
+
+ this.addTo(parent).untransform().transform(pCtm.multiply(ctm))
+
+ return this
+}
+
+// same as above with parent equals root-svg
+export function toDoc () {
+ return this.toParent(this.doc())
+}
+
+// Add transformations
+export function transform (o, relative) {
+ // Act as a getter if no object was passed
+ if (o == null || typeof o === 'string') {
+ var decomposed = new Matrix(this).decompose()
+ return decomposed[o] || decomposed
+ }
+
+ if (!Matrix.isMatrixLike(o)) {
+ // Set the origin according to the defined transform
+ o = { ...o, origin: getOrigin(o, this) }
+ }
+
+ // The user can pass a boolean, an Element or an Matrix or nothing
+ var cleanRelative = relative === true ? this : (relative || false)
+ var result = new Matrix(cleanRelative).transform(o)
+ return this.attr('transform', result)
+}
+
+registerMethods('Element', {
+ untransform, matrixify, toParent, toDoc, transform
+})
diff --git a/src/morph.js b/src/morph.js
deleted file mode 100644
index acb9e21..0000000
--- a/src/morph.js
+++ /dev/null
@@ -1,231 +0,0 @@
-
-SVG.Morphable = SVG.invent({
- create: function (stepper) {
- // FIXME: the default stepper does not know about easing
- this._stepper = stepper || new SVG.Ease('-')
-
- this._from = null
- this._to = null
- this._type = null
- this._context = null
- this._morphObj = null
- },
-
- extend: {
-
- from: function (val) {
- if (val == null) {
- return this._from
- }
-
- this._from = this._set(val)
- return this
- },
-
- to: function (val) {
- if (val == null) {
- return this._to
- }
-
- this._to = this._set(val)
- return this
- },
-
- type: function (type) {
- // getter
- if (type == null) {
- return this._type
- }
-
- // setter
- this._type = type
- return this
- },
-
- _set: function (value) {
- if (!this._type) {
- var type = typeof value
-
- if (type === 'number') {
- this.type(SVG.Number)
- } else if (type === 'string') {
- if (SVG.Color.isColor(value)) {
- this.type(SVG.Color)
- } else if (SVG.regex.delimiter.test(value)) {
- this.type(SVG.regex.pathLetters.test(value)
- ? SVG.PathArray
- : SVG.Array
- )
- } else if (SVG.regex.numberAndUnit.test(value)) {
- this.type(SVG.Number)
- } else {
- this.type(SVG.Morphable.NonMorphable)
- }
- } else if (SVG.MorphableTypes.indexOf(value.constructor) > -1) {
- this.type(value.constructor)
- } else if (Array.isArray(value)) {
- this.type(SVG.Array)
- } else if (type === 'object') {
- this.type(SVG.Morphable.ObjectBag)
- } else {
- this.type(SVG.Morphable.NonMorphable)
- }
- }
-
- var result = (new this._type(value)).toArray()
- this._morphObj = this._morphObj || new this._type()
- this._context = this._context ||
- Array.apply(null, Array(result.length)).map(Object)
- return result
- },
-
- stepper: function (stepper) {
- if (stepper == null) return this._stepper
- this._stepper = stepper
- return this
- },
-
- done: function () {
- var complete = this._context
- .map(this._stepper.done)
- .reduce(function (last, curr) {
- return last && curr
- }, true)
- return complete
- },
-
- at: function (pos) {
- var _this = this
-
- return this._morphObj.fromArray(
- this._from.map(function (i, index) {
- return _this._stepper.step(i, _this._to[index], pos, _this._context[index], _this._context)
- })
- )
- }
- }
-})
-
-SVG.Morphable.NonMorphable = SVG.invent({
- create: function (val) {
- val = Array.isArray(val) ? val[0] : val
- this.value = val
- },
-
- extend: {
- valueOf: function () {
- return this.value
- },
-
- toArray: function () {
- return [this.value]
- }
- }
-})
-
-SVG.Morphable.TransformBag = SVG.invent({
- create: function (obj) {
- if (Array.isArray(obj)) {
- obj = {
- scaleX: obj[0],
- scaleY: obj[1],
- shear: obj[2],
- rotate: obj[3],
- translateX: obj[4],
- translateY: obj[5],
- originX: obj[6],
- originY: obj[7]
- }
- }
-
- Object.assign(this, SVG.Morphable.TransformBag.defaults, obj)
- },
-
- extend: {
- toArray: function () {
- var v = this
-
- return [
- v.scaleX,
- v.scaleY,
- v.shear,
- v.rotate,
- v.translateX,
- v.translateY,
- v.originX,
- v.originY
- ]
- }
- }
-})
-
-SVG.Morphable.TransformBag.defaults = {
- scaleX: 1,
- scaleY: 1,
- shear: 0,
- rotate: 0,
- translateX: 0,
- translateY: 0,
- originX: 0,
- originY: 0
-}
-
-SVG.Morphable.ObjectBag = SVG.invent({
- create: function (objOrArr) {
- this.values = []
-
- if (Array.isArray(objOrArr)) {
- this.values = objOrArr
- return
- }
-
- var entries = Object.entries(objOrArr || {}).sort((a, b) => {
- return a[0] - b[0]
- })
-
- this.values = entries.reduce((last, curr) => last.concat(curr), [])
- },
-
- extend: {
- valueOf: function () {
- var obj = {}
- var arr = this.values
-
- for (var i = 0, len = arr.length; i < len; i += 2) {
- obj[arr[i]] = arr[i + 1]
- }
-
- return obj
- },
-
- toArray: function () {
- return this.values
- }
- }
-})
-
-SVG.MorphableTypes = [
- SVG.Number,
- SVG.Color,
- SVG.Box,
- SVG.Matrix,
- SVG.Array,
- SVG.PointArray,
- SVG.PathArray,
- SVG.Morphable.NonMorphable,
- SVG.Morphable.TransformBag,
- SVG.Morphable.ObjectBag
-]
-
-SVG.extend(SVG.MorphableTypes, {
- to: function (val, args) {
- return new SVG.Morphable()
- .type(this.constructor)
- .from(this.valueOf())
- .to(val, args)
- },
- fromArray: function (arr) {
- this.constructor(arr)
- return this
- }
-})
diff --git a/src/number.js b/src/number.js
deleted file mode 100644
index 2135b61..0000000
--- a/src/number.js
+++ /dev/null
@@ -1,99 +0,0 @@
-
-// Module for unit convertions
-SVG.Number = SVG.invent({
- // Initialize
- create: function (value, unit) {
- unit = Array.isArray(value) ? value[1] : unit
- value = Array.isArray(value) ? value[0] : value
-
- // initialize defaults
- this.value = 0
- this.unit = unit || ''
-
- // parse value
- if (typeof value === 'number') {
- // ensure a valid numeric value
- this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value
- } else if (typeof value === 'string') {
- unit = value.match(SVG.regex.numberAndUnit)
-
- if (unit) {
- // make value numeric
- this.value = parseFloat(unit[1])
-
- // normalize
- if (unit[5] === '%') { this.value /= 100 } else if (unit[5] === 's') {
- this.value *= 1000
- }
-
- // store unit
- this.unit = unit[5]
- }
- } else {
- if (value instanceof SVG.Number) {
- this.value = value.valueOf()
- this.unit = value.unit
- }
- }
- },
- // Add methods
- extend: {
- // Stringalize
- toString: function () {
- return (this.unit === '%' ? ~~(this.value * 1e8) / 1e6
- : this.unit === 's' ? this.value / 1e3
- : this.value
- ) + this.unit
- },
- toJSON: function () {
- return this.toString()
- }, // Convert to primitive
- toArray: function () {
- return [this.value, this.unit]
- },
- valueOf: function () {
- return this.value
- },
- // Add number
- plus: function (number) {
- number = new SVG.Number(number)
- return new SVG.Number(this + number, this.unit || number.unit)
- },
- // Subtract number
- minus: function (number) {
- number = new SVG.Number(number)
- return new SVG.Number(this - number, this.unit || number.unit)
- },
- // Multiply number
- times: function (number) {
- number = new SVG.Number(number)
- return new SVG.Number(this * number, this.unit || number.unit)
- },
- // Divide number
- divide: function (number) {
- number = new SVG.Number(number)
- return new SVG.Number(this / number, this.unit || number.unit)
- },
- // Make number morphable
- morph: function (number) {
- this.destination = new SVG.Number(number)
-
- if (number.relative) {
- this.destination.value += this.value
- }
-
- return this
- },
- // Get morphed number at given position
- at: function (pos) {
- // Make sure a destination is defined
- if (!this.destination) return this
-
- // Generate new morphed number
- return new SVG.Number(this.destination)
- .minus(this)
- .times(pos)
- .plus(this)
- }
- }
-})
diff --git a/src/parent.js b/src/parent.js
deleted file mode 100644
index 6bdad58..0000000
--- a/src/parent.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/* global createElement */
-
-SVG.Parent = SVG.invent({
- // Initialize node
- create: function (node) {
- SVG.Element.call(this, node)
- },
-
- // Inherit from
- inherit: SVG.Element,
-
- // Add class methods
- extend: {
- // Returns all child elements
- children: function () {
- return SVG.utils.map(this.node.children, function (node) {
- return SVG.adopt(node)
- })
- },
- // Add given element at a position
- add: function (element, i) {
- element = createElement(element)
-
- if (element.node !== this.node.children[i]) {
- this.node.insertBefore(element.node, this.node.children[i] || null)
- }
-
- return this
- },
- // Basically does the same as `add()` but returns the added element instead
- put: function (element, i) {
- this.add(element, i)
- return element.instance || element
- },
- // Checks if the given element is a child
- has: function (element) {
- return this.index(element) >= 0
- },
- // Gets index of given element
- index: function (element) {
- return [].slice.call(this.node.children).indexOf(element.node)
- },
- // Get a element at the given index
- get: function (i) {
- return SVG.adopt(this.node.children[i])
- },
- // Get first child
- first: function () {
- return this.get(0)
- },
- // Get the last child
- last: function () {
- return this.get(this.node.children.length - 1)
- },
- // Iterates over all children and invokes a given block
- each: function (block, deep) {
- var children = this.children()
- var i, il
-
- for (i = 0, il = children.length; i < il; i++) {
- if (children[i] instanceof SVG.Element) {
- block.apply(children[i], [i, children])
- }
-
- if (deep && (children[i] instanceof SVG.Parent)) {
- children[i].each(block, deep)
- }
- }
-
- return this
- },
- // Remove a given child
- removeElement: function (element) {
- this.node.removeChild(element.node)
-
- return this
- },
- // Remove all elements in this container
- clear: function () {
- // remove children
- while (this.node.hasChildNodes()) {
- this.node.removeChild(this.node.lastChild)
- }
-
- // remove defs reference
- delete this._defs
-
- return this
- }
- }
-
-})
diff --git a/src/parser.js b/src/parser.js
deleted file mode 100644
index 84c8d77..0000000
--- a/src/parser.js
+++ /dev/null
@@ -1,23 +0,0 @@
-
-SVG.parser = function () {
- var b
-
- if (!SVG.parser.nodes.svg.node.parentNode) {
- b = document.body || document.documentElement
- SVG.parser.nodes.svg.addTo(b)
- }
-
- return SVG.parser.nodes
-}
-
-SVG.parser.nodes = {
- svg: SVG().size(2, 0).css({
- opacity: 0,
- position: 'absolute',
- left: '-100%',
- top: '-100%',
- overflow: 'hidden'
- })
-}
-
-SVG.parser.nodes.path = SVG.parser.nodes.svg.path().node
diff --git a/src/path.js b/src/path.js
deleted file mode 100644
index db3929b..0000000
--- a/src/path.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/* global proportionalSize */
-
-SVG.Path = SVG.invent({
- // Initialize node
- create: 'path',
-
- // Inherit from
- inherit: SVG.Shape,
-
- // Add class methods
- extend: {
- // Define morphable array
- MorphArray: SVG.PathArray,
- // Get array
- array: function () {
- return this._array || (this._array = new SVG.PathArray(this.attr('d')))
- },
- // Plot new path
- plot: function (d) {
- return (d == null) ? this.array()
- : this.clear().attr('d', typeof d === 'string' ? d : (this._array = new SVG.PathArray(d)))
- },
- // Clear array cache
- clear: function () {
- delete this._array
- return this
- },
- // Move by left top corner
- move: function (x, y) {
- return this.attr('d', this.array().move(x, y))
- },
- // Move by left top corner over x-axis
- x: function (x) {
- return x == null ? this.bbox().x : this.move(x, this.bbox().y)
- },
- // Move by left top corner over y-axis
- y: function (y) {
- return y == null ? this.bbox().y : this.move(this.bbox().x, y)
- },
- // Set element size to given width and height
- size: function (width, height) {
- var p = proportionalSize(this, width, height)
- return this.attr('d', this.array().size(p.width, p.height))
- },
- // Set width of element
- width: function (width) {
- return width == null ? this.bbox().width : this.size(width, this.bbox().height)
- },
- // Set height of element
- height: function (height) {
- return height == null ? this.bbox().height : this.size(this.bbox().width, height)
- }
- },
-
- // Add parent method
- construct: {
- // Create a wrapped path element
- path: function (d) {
- // make sure plot is called as a setter
- return this.put(new SVG.Path()).plot(d || new SVG.PathArray())
- }
- }
-})
diff --git a/src/pattern.js b/src/pattern.js
deleted file mode 100644
index d4c4116..0000000
--- a/src/pattern.js
+++ /dev/null
@@ -1,59 +0,0 @@
-SVG.Pattern = SVG.invent({
- // Initialize node
- create: 'pattern',
-
- // Inherit from
- inherit: SVG.Container,
-
- // Add class methods
- extend: {
- // Return the fill id
- url: function () {
- return 'url(#' + this.id() + ')'
- },
- // Update pattern by rebuilding
- update: function (block) {
- // remove content
- this.clear()
-
- // invoke passed block
- if (typeof block === 'function') {
- block.call(this, this)
- }
-
- return this
- },
- // Alias string convertion to fill
- toString: function () {
- return this.url()
- },
- // custom attr to handle transform
- attr: function (a, b, c) {
- if (a === 'transform') a = 'patternTransform'
- return SVG.Container.prototype.attr.call(this, a, b, c)
- }
-
- },
-
- // Add parent method
- construct: {
- // Create pattern element in defs
- pattern: function (width, height, block) {
- return this.defs().pattern(width, height, block)
- }
- }
-})
-
-SVG.extend(SVG.Defs, {
- // Define gradient
- pattern: function (width, height, block) {
- return this.put(new SVG.Pattern()).update(block).attr({
- x: 0,
- y: 0,
- width: width,
- height: height,
- patternUnits: 'userSpaceOnUse'
- })
- }
-
-})
diff --git a/src/point.js b/src/point.js
deleted file mode 100644
index 6c64ed6..0000000
--- a/src/point.js
+++ /dev/null
@@ -1,74 +0,0 @@
-
-SVG.Point = SVG.invent({
- // Initialize
- create: function (x, y, base) {
- var source
- base = base || {x: 0, y: 0}
-
- // ensure source as object
- source = Array.isArray(x) ? {x: x[0], y: x[1]}
- : typeof x === 'object' ? {x: x.x, y: x.y}
- : {x: x, y: y}
-
- // merge source
- this.x = source.x == null ? base.x : source.x
- this.y = source.y == null ? base.y : source.y
- },
-
- // Add methods
- extend: {
- // Clone point
- clone: function () {
- return new SVG.Point(this)
- },
-
- // Morph one point into another
- morph: function (x, y) {
- // store new destination
- this.destination = new SVG.Point(x, y)
- return this
- },
-
- // Get morphed point at a given position
- at: function (pos) {
- // make sure a destination is defined
- if (!this.destination) return this
-
- // calculate morphed matrix at a given position
- var point = new SVG.Point({
- x: this.x + (this.destination.x - this.x) * pos,
- y: this.y + (this.destination.y - this.y) * pos
- })
- return point
- },
-
- // Convert to native SVGPoint
- native: function () {
- // create new point
- var point = SVG.parser.nodes.svg.node.createSVGPoint()
-
- // update with current values
- point.x = this.x
- point.y = this.y
- return point
- },
-
- // transform point with matrix
- transform: function (m) {
- // Perform the matrix multiplication
- var x = m.a * this.x + m.c * this.y + m.e
- var y = m.b * this.x + m.d * this.y + m.f
-
- // Return the required point
- return new SVG.Point(x, y)
- }
- }
-})
-
-SVG.extend(SVG.Element, {
-
- // Get point
- point: function (x, y) {
- return new SVG.Point(x, y).transform(this.screenCTM().inverse())
- }
-})
diff --git a/src/pointarray.js b/src/pointarray.js
deleted file mode 100644
index aa5f84a..0000000
--- a/src/pointarray.js
+++ /dev/null
@@ -1,128 +0,0 @@
-
-// Poly points array
-SVG.PointArray = function (array, fallback) {
- SVG.Array.call(this, array, fallback || [[0, 0]])
-}
-
-// Inherit from SVG.Array
-SVG.PointArray.prototype = new SVG.Array()
-SVG.PointArray.prototype.constructor = SVG.PointArray
-
-SVG.extend(SVG.PointArray, {
- // Convert array to string
- toString: function () {
- // convert to a poly point string
- for (var i = 0, il = this.value.length, array = []; i < il; i++) {
- array.push(this.value[i].join(','))
- }
-
- return array.join(' ')
- },
-
- toArray: function () {
- return this.value.reduce(function (prev, curr) {
- return [].concat.call(prev, curr)
- }, [])
- },
-
- // Convert array to line object
- toLine: function () {
- return {
- x1: this.value[0][0],
- y1: this.value[0][1],
- x2: this.value[1][0],
- y2: this.value[1][1]
- }
- },
-
- // Get morphed array at given position
- at: function (pos) {
- // make sure a destination is defined
- if (!this.destination) return this
-
- // generate morphed point string
- for (var i = 0, il = this.value.length, array = []; i < il; i++) {
- array.push([
- this.value[i][0] + (this.destination[i][0] - this.value[i][0]) * pos,
- this.value[i][1] + (this.destination[i][1] - this.value[i][1]) * pos
- ])
- }
-
- return new SVG.PointArray(array)
- },
-
- // Parse point string and flat array
- parse: function (array) {
- var points = []
-
- array = array.valueOf()
-
- // if it is an array
- if (Array.isArray(array)) {
- // and it is not flat, there is no need to parse it
- if (Array.isArray(array[0])) {
- return array
- }
- } else { // Else, it is considered as a string
- // parse points
- array = array.trim().split(SVG.regex.delimiter).map(parseFloat)
- }
-
- // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints
- // Odd number of coordinates is an error. In such cases, drop the last odd coordinate.
- if (array.length % 2 !== 0) array.pop()
-
- // wrap points in two-tuples and parse points as floats
- for (var i = 0, len = array.length; i < len; i = i + 2) {
- points.push([ array[i], array[i + 1] ])
- }
-
- return points
- },
-
- // Move point string
- move: function (x, y) {
- var box = this.bbox()
-
- // get relative offset
- x -= box.x
- y -= box.y
-
- // move every point
- if (!isNaN(x) && !isNaN(y)) {
- for (var i = this.value.length - 1; i >= 0; i--) {
- this.value[i] = [this.value[i][0] + x, this.value[i][1] + y]
- }
- }
-
- return this
- },
- // Resize poly string
- size: function (width, height) {
- var i
- var box = this.bbox()
-
- // recalculate position of all points according to new size
- for (i = this.value.length - 1; i >= 0; i--) {
- if (box.width) this.value[i][0] = ((this.value[i][0] - box.x) * width) / box.width + box.x
- if (box.height) this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y
- }
-
- return this
- },
-
- // Get bounding box of points
- bbox: function () {
- var maxX = -Infinity
- var maxY = -Infinity
- var minX = Infinity
- var minY = Infinity
- this.value.forEach(function (el) {
- maxX = Math.max(el[0], maxX)
- maxY = Math.max(el[1], maxY)
- minX = Math.min(el[0], minX)
- minY = Math.min(el[1], minY)
- })
- return {x: minX, y: minY, width: maxX - minX, height: maxY - minY}
- }
-})
diff --git a/src/pointed.js b/src/pointed.js
deleted file mode 100644
index 6493964..0000000
--- a/src/pointed.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// unify all point to point elements
-SVG.extend([SVG.Line, SVG.Polyline, SVG.Polygon], {
- // Define morphable array
- MorphArray: SVG.PointArray,
- // Move by left top corner over x-axis
- x: function (x) {
- return x == null ? this.bbox().x : this.move(x, this.bbox().y)
- },
- // Move by left top corner over y-axis
- y: function (y) {
- return y == null ? this.bbox().y : this.move(this.bbox().x, y)
- },
- // Set width of element
- width: function (width) {
- var b = this.bbox()
-
- return width == null ? b.width : this.size(width, b.height)
- },
- // Set height of element
- height: function (height) {
- var b = this.bbox()
-
- return height == null ? b.height : this.size(b.width, height)
- }
-})
diff --git a/src/poly.js b/src/poly.js
deleted file mode 100644
index 9625776..0000000
--- a/src/poly.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/* global proportionalSize */
-
-SVG.Polyline = SVG.invent({
- // Initialize node
- create: 'polyline',
-
- // Inherit from
- inherit: SVG.Shape,
-
- // Add parent method
- construct: {
- // Create a wrapped polyline element
- polyline: function (p) {
- // make sure plot is called as a setter
- return this.put(new SVG.Polyline()).plot(p || new SVG.PointArray())
- }
- }
-})
-
-SVG.Polygon = SVG.invent({
- // Initialize node
- create: 'polygon',
-
- // Inherit from
- inherit: SVG.Shape,
-
- // Add parent method
- construct: {
- // Create a wrapped polygon element
- polygon: function (p) {
- // make sure plot is called as a setter
- return this.put(new SVG.Polygon()).plot(p || new SVG.PointArray())
- }
- }
-})
-
-// Add polygon-specific functions
-SVG.extend([SVG.Polyline, SVG.Polygon], {
- // Get array
- array: function () {
- return this._array || (this._array = new SVG.PointArray(this.attr('points')))
- },
-
- // Plot new path
- plot: function (p) {
- return (p == null) ? this.array()
- : this.clear().attr('points', typeof p === 'string' ? p
- : (this._array = new SVG.PointArray(p)))
- },
-
- // Clear array cache
- clear: function () {
- delete this._array
- return this
- },
-
- // Move by left top corner
- move: function (x, y) {
- return this.attr('points', this.array().move(x, y))
- },
-
- // Set element size to given width and height
- size: function (width, height) {
- var p = proportionalSize(this, width, height)
- return this.attr('points', this.array().size(p.width, p.height))
- }
-})
diff --git a/src/queue.js b/src/queue.js
deleted file mode 100644
index 621c887..0000000
--- a/src/queue.js
+++ /dev/null
@@ -1,61 +0,0 @@
-SVG.Queue = SVG.invent({
- create: function () {
- this._first = null
- this._last = null
- },
-
- extend: {
- push: function (value) {
- // An item stores an id and the provided value
- var item = value.next ? value : { value: value, next: null, prev: null }
-
- // Deal with the queue being empty or populated
- if (this._last) {
- item.prev = this._last
- this._last.next = item
- this._last = item
- } else {
- this._last = item
- this._first = item
- }
-
- // Update the length and return the current item
- return item
- },
-
- shift: function () {
- // Check if we have a value
- var remove = this._first
- if (!remove) return null
-
- // If we do, remove it and relink things
- this._first = remove.next
- if (this._first) this._first.prev = null
- this._last = this._first ? this._last : null
- return remove.value
- },
-
- // Shows us the first item in the list
- first: function () {
- return this._first && this._first.value
- },
-
- // Shows us the last item in the list
- last: function () {
- return this._last && this._last.value
- },
-
- // Removes the item that was returned from the push
- remove: function (item) {
- // Relink the previous item
- if (item.prev) item.prev.next = item.next
- if (item.next) item.next.prev = item.prev
- if (item === this._last) this._last = item.prev
- if (item === this._first) this._first = item.next
-
- // Invalidate item
- item.prev = null
- item.next = null
- }
- }
-})
diff --git a/src/rect.js b/src/rect.js
deleted file mode 100644
index 35a3678..0000000
--- a/src/rect.js
+++ /dev/null
@@ -1,16 +0,0 @@
-
-SVG.Rect = SVG.invent({
- // Initialize node
- create: 'rect',
-
- // Inherit from
- inherit: SVG.Shape,
-
- // Add parent method
- construct: {
- // Create a rect element
- rect: function (width, height) {
- return this.put(new SVG.Rect()).size(width, height)
- }
- }
-})
diff --git a/src/regex.js b/src/regex.js
deleted file mode 100644
index 5a3e3eb..0000000
--- a/src/regex.js
+++ /dev/null
@@ -1,61 +0,0 @@
-// Storage for regular expressions
-SVG.regex = {
- // Parse unit value
- numberAndUnit: /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,
-
- // Parse hex value
- hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,
-
- // Parse rgb value
- rgb: /rgb\((\d+),(\d+),(\d+)\)/,
-
- // Parse reference id
- reference: /#([a-z0-9\-_]+)/i,
-
- // splits a transformation chain
- transforms: /\)\s*,?\s*/,
-
- // Whitespace
- whitespace: /\s/g,
-
- // Test hex value
- isHex: /^#[a-f0-9]{3,6}$/i,
-
- // Test rgb value
- isRgb: /^rgb\(/,
-
- // Test css declaration
- isCss: /[^:]+:[^;]+;?/,
-
- // Test for blank string
- isBlank: /^(\s+)?$/,
-
- // Test for numeric string
- isNumber: /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,
-
- // Test for percent value
- isPercent: /^-?[\d.]+%$/,
-
- // Test for image url
- isImage: /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,
-
- // split at whitespace and comma
- delimiter: /[\s,]+/,
-
- // The following regex are used to parse the d attribute of a path
-
- // Matches all hyphens which are not after an exponent
- hyphen: /([^e])-/gi,
-
- // Replaces and tests for all path letters
- pathLetters: /[MLHVCSQTAZ]/gi,
-
- // yes we need this one, too
- isPathLetter: /[MLHVCSQTAZ]/i,
-
- // matches 0.154.23.45
- numbersWithDots: /((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi,
-
- // matches .
- dots: /\./g
-}
diff --git a/src/runner.js b/src/runner.js
deleted file mode 100644
index 97e04e2..0000000
--- a/src/runner.js
+++ /dev/null
@@ -1,928 +0,0 @@
-/* global isMatrixLike getOrigin */
-
-SVG.easing = {
- '-': function (pos) { return pos },
- '<>': function (pos) { return -Math.cos(pos * Math.PI) / 2 + 0.5 },
- '>': function (pos) { return Math.sin(pos * Math.PI / 2) },
- '<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 }
-}
-
-SVG.Runner = SVG.invent({
- parent: SVG.Element,
-
- create: function (options) {
- // Store a unique id on the runner, so that we can identify it later
- this.id = SVG.Runner.id++
-
- // Ensure a default value
- options = options == null
- ? SVG.defaults.timeline.duration
- : options
-
- // Ensure that we get a controller
- options = typeof options === 'function'
- ? new SVG.Controller(options)
- : options
-
- // Declare all of the variables
- this._element = null
- this._timeline = null
- this.done = false
- this._queue = []
-
- // Work out the stepper and the duration
- this._duration = typeof options === 'number' && options
- this._isDeclarative = options instanceof SVG.Controller
- this._stepper = this._isDeclarative ? options : new SVG.Ease()
-
- // We copy the current values from the timeline because they can change
- this._history = {}
-
- // Store the state of the runner
- this.enabled = true
- this._time = 0
- this._last = 0
-
- // Save transforms applied to this runner
- this.transforms = new SVG.Matrix()
- this.transformId = 1
-
- // Looping variables
- this._haveReversed = false
- this._reverse = false
- this._loopsDone = 0
- this._swing = false
- this._wait = 0
- this._times = 1
- },
-
- construct: {
-
- animate: function (duration, delay, when) {
- var o = SVG.Runner.sanitise(duration, delay, when)
- var timeline = this.timeline()
- return new SVG.Runner(o.duration)
- .loop(o)
- .element(this)
- .timeline(timeline)
- .schedule(delay, when)
- },
-
- delay: function (by, when) {
- return this.animate(0, by, when)
- }
- },
-
- extend: {
-
- /*
- Runner Definitions
- ==================
- These methods help us define the runtime behaviour of the Runner or they
- help us make new runners from the current runner
- */
-
- element: function (element) {
- if (element == null) return this._element
- this._element = element
- element._prepareRunner()
- return this
- },
-
- timeline: function (timeline) {
- // check explicitly for undefined so we can set the timeline to null
- if (typeof timeline === 'undefined') return this._timeline
- this._timeline = timeline
- return this
- },
-
- animate: function (duration, delay, when) {
- var o = SVG.Runner.sanitise(duration, delay, when)
- var runner = new SVG.Runner(o.duration)
- if (this._timeline) runner.timeline(this._timeline)
- if (this._element) runner.element(this._element)
- return runner.loop(o).schedule(delay, when)
- },
-
- schedule: function (timeline, delay, when) {
- // The user doesn't need to pass a timeline if we already have one
- if (!(timeline instanceof SVG.Timeline)) {
- when = delay
- delay = timeline
- timeline = this.timeline()
- }
-
- // If there is no timeline, yell at the user...
- if (!timeline) {
- throw Error('Runner cannot be scheduled without timeline')
- }
-
- // Schedule the runner on the timeline provided
- timeline.schedule(this, delay, when)
- return this
- },
-
- unschedule: function () {
- var timeline = this.timeline()
- timeline && timeline.unschedule(this)
- return this
- },
-
- loop: function (times, swing, wait) {
- // Deal with the user passing in an object
- if (typeof times === 'object') {
- swing = times.swing
- wait = times.wait
- times = times.times
- }
-
- // Sanitise the values and store them
- this._times = times || Infinity
- this._swing = swing || false
- this._wait = wait || 0
- return this
- },
-
- delay: function (delay) {
- return this.animate(0, delay)
- },
-
- /*
- Basic Functionality
- ===================
- These methods allow us to attach basic functions to the runner directly
- */
-
- queue: function (initFn, runFn, isTransform) {
- this._queue.push({
- initialiser: initFn || SVG.void,
- runner: runFn || SVG.void,
- isTransform: isTransform,
- initialised: false,
- finished: false
- })
- var timeline = this.timeline()
- timeline && this.timeline()._continue()
- return this
- },
-
- during: function (fn) {
- return this.queue(null, fn)
- },
-
- after (fn) {
- return this.on('finish', fn)
- },
-
- /*
- Runner animation methods
- ========================
- Control how the animation plays
- */
-
- time: function (time) {
- if (time == null) {
- return this._time
- }
- let dt = time - this._time
- this.step(dt)
- return this
- },
-
- duration: function () {
- return this._times * (this._wait + this._duration) - this._wait
- },
-
- loops: function (p) {
- var loopDuration = this._duration + this._wait
- if (p == null) {
- var loopsDone = Math.floor(this._time / loopDuration)
- var relativeTime = (this._time - loopsDone * loopDuration)
- var position = relativeTime / this._duration
- return Math.min(loopsDone + position, this._times)
- }
- var whole = Math.floor(p)
- var partial = p % 1
- var time = loopDuration * whole + this._duration * partial
- return this.time(time)
- },
-
- position: function (p) {
- // Get all of the variables we need
- var x = this._time
- var d = this._duration
- var w = this._wait
- var t = this._times
- var s = this._swing
- var r = this._reverse
- var position
-
- if (p == null) {
- /*
- This function converts a time to a position in the range [0, 1]
- The full explanation can be found in this desmos demonstration
- https://www.desmos.com/calculator/u4fbavgche
- The logic is slightly simplified here because we can use booleans
- */
-
- // Figure out the value without thinking about the start or end time
- const f = function (x) {
- var swinging = s * Math.floor(x % (2 * (w + d)) / (w + d))
- var backwards = (swinging && !r) || (!swinging && r)
- var uncliped = Math.pow(-1, backwards) * (x % (w + d)) / d + backwards
- var clipped = Math.max(Math.min(uncliped, 1), 0)
- return clipped
- }
-
- // Figure out the value by incorporating the start time
- var endTime = t * (w + d) - w
- position = x <= 0 ? Math.round(f(1e-5))
- : x < endTime ? f(x)
- : Math.round(f(endTime - 1e-5))
- return position
- }
-
- // Work out the loops done and add the position to the loops done
- var loopsDone = Math.floor(this.loops())
- var swingForward = s && (loopsDone % 2 === 0)
- var forwards = (swingForward && !r) || (r && swingForward)
- position = loopsDone + (forwards ? p : 1 - p)
- return this.loops(position)
- },
-
- progress: function (p) {
- if (p == null) {
- return Math.min(1, this._time / this.duration())
- }
- return this.time(p * this.duration())
- },
-
- step: function (dt) {
- // If we are inactive, this stepper just gets skipped
- if (!this.enabled) return this
-
- // Update the time and get the new position
- dt = dt == null ? 16 : dt
- this._time += dt
- var position = this.position()
-
- // Figure out if we need to run the stepper in this frame
- var running = this._lastPosition !== position && this._time >= 0
- this._lastPosition = position
-
- // Figure out if we just started
- var duration = this.duration()
- var justStarted = this._lastTime < 0 && this._time > 0
- var justFinished = this._lastTime < this._time && this.time > duration
- this._lastTime = this._time
- if (justStarted) {
- // this.fire('start', this)
- }
-
- // Work out if the runner is finished set the done flag here so animations
- // know, that they are running in the last step (this is good for
- // transformations which can be merged)
- var declarative = this._isDeclarative
- this.done = !declarative && !justFinished && this._time >= duration
-
- // Call initialise and the run function
- if (running || declarative) {
- this._initialise(running)
-
- // clear the transforms on this runner so they dont get added again and again
- this.transforms = new SVG.Matrix()
- var converged = this._run(declarative ? dt : position)
- // this.fire('step', this)
- }
- // correct the done flag here
- // declaritive animations itself know when they converged
- this.done = this.done || (converged && declarative)
- // if (this.done) {
- // this.fire('finish', this)
- // }
- return this
- },
-
- finish: function () {
- return this.step(Infinity)
- },
-
- reverse: function (reverse) {
- this._reverse = reverse == null ? !this._reverse : reverse
- return this
- },
-
- ease: function (fn) {
- this._stepper = new SVG.Ease(fn)
- return this
- },
-
- active: function (enabled) {
- if (enabled == null) return this.enabled
- this.enabled = enabled
- return this
- },
-
- /*
- Private Methods
- ===============
- Methods that shouldn't be used externally
- */
-
- // Save a morpher to the morpher list so that we can retarget it later
- _rememberMorpher: function (method, morpher) {
- this._history[method] = {
- morpher: morpher,
- caller: this._queue[this._queue.length - 1]
- }
- },
-
- // Try to set the target for a morpher if the morpher exists, otherwise
- // do nothing and return false
- _tryRetarget: function (method, target) {
- if (this._history[method]) {
- // if the last method wasnt even initialised, throw it away
- if (!this._history[method].caller.initialised) {
- let index = this._queue.indexOf(this._history[method].caller)
- this._queue.splice(index, 1)
- return false
- }
-
- // for the case of transformations, we use the special retarget function
- // which has access to the outer scope
- if (this._history[method].caller.isTransform) {
- this._history[method].caller.isTransform(target)
- // for everything else a simple morpher change is sufficient
- } else {
- this._history[method].morpher.to(target)
- }
-
- this._history[method].caller.finished = false
- var timeline = this.timeline()
- timeline && timeline._continue()
- return true
- }
- return false
- },
-
- // Run each initialise function in the runner if required
- _initialise: function (running) {
- // If we aren't running, we shouldn't initialise when not declarative
- if (!running && !this._isDeclarative) return
-
- // Loop through all of the initialisers
- for (var i = 0, len = this._queue.length; i < len; ++i) {
- // Get the current initialiser
- var current = this._queue[i]
-
- // Determine whether we need to initialise
- var needsIt = this._isDeclarative || (!current.initialised && running)
- running = !current.finished
-
- // Call the initialiser if we need to
- if (needsIt && running) {
- current.initialiser.call(this)
- current.initialised = true
- }
- }
- },
-
- // Run each run function for the position or dt given
- _run: function (positionOrDt) {
- // Run all of the _queue directly
- var allfinished = true
- for (var i = 0, len = this._queue.length; i < len; ++i) {
- // Get the current function to run
- var current = this._queue[i]
-
- // Run the function if its not finished, we keep track of the finished
- // flag for the sake of declarative _queue
- var converged = current.runner.call(this, positionOrDt)
- current.finished = current.finished || (converged === true)
- allfinished = allfinished && current.finished
- }
-
- // We report when all of the constructors are finished
- return allfinished
- },
-
- addTransform: function (transform, index) {
- this.transforms.lmultiplyO(transform)
- return this
- },
-
- clearTransform: function () {
- this.transforms = new SVG.Matrix()
- return this
- }
- }
-})
-
-SVG.Runner.id = 0
-
-SVG.Runner.sanitise = function (duration, delay, when) {
- // Initialise the default parameters
- var times = 1
- var swing = false
- var wait = 0
- duration = duration || SVG.defaults.timeline.duration
- delay = delay || SVG.defaults.timeline.delay
- when = when || 'last'
-
- // If we have an object, unpack the values
- if (typeof duration === 'object' && !(duration instanceof SVG.Stepper)) {
- delay = duration.delay || delay
- when = duration.when || when
- swing = duration.swing || swing
- times = duration.times || times
- wait = duration.wait || wait
- duration = duration.duration || SVG.defaults.timeline.duration
- }
-
- return {
- duration: duration,
- delay: delay,
- swing: swing,
- times: times,
- wait: wait,
- when: when
- }
-}
-
-SVG.FakeRunner = class {
- constructor (transforms = new SVG.Matrix(), id = -1, done = true) {
- this.transforms = transforms
- this.id = id
- this.done = done
- }
-}
-
-SVG.extend([SVG.Runner, SVG.FakeRunner], {
- mergeWith (runner) {
- return new SVG.FakeRunner(
- runner.transforms.lmultiply(this.transforms),
- runner.id
- )
- }
-})
-
-// SVG.FakeRunner.emptyRunner = new SVG.FakeRunner()
-
-const lmultiply = (last, curr) => last.lmultiplyO(curr)
-const getRunnerTransform = (runner) => runner.transforms
-
-function mergeTransforms () {
- // Find the matrix to apply to the element and apply it
- let runners = this._transformationRunners.runners
- let netTransform = runners
- .map(getRunnerTransform)
- .reduce(lmultiply, new SVG.Matrix())
-
- this.transform(netTransform)
-
- this._transformationRunners.merge()
-
- if (this._transformationRunners.length() === 1) {
- this._frameId = null
- }
-}
-
-class RunnerArray {
- constructor () {
- this.runners = []
- this.ids = []
- }
-
- add (runner) {
- if (this.runners.includes(runner)) return
-
- let id = runner.id + 1
-
- let leftSibling = this.ids.reduce((last, curr) => {
- if (curr > last && curr < id) return curr
- return last
- }, 0)
-
- let index = this.ids.indexOf(leftSibling) + 1
-
- this.ids.splice(index, 0, id)
- this.runners.splice(index, 0, runner)
-
- return this
- }
-
- getByID (id) {
- return this.runners[this.ids.indexOf(id + 1)]
- }
-
- remove (id) {
- let index = this.ids.indexOf(id + 1)
- this.ids.splice(index, 1)
- this.runners.splice(index, 1)
- return this
- }
-
- merge () {
- let lastRunner = null
- this.runners.forEach((runner, i) => {
- if (lastRunner && runner.done && lastRunner.done) {
- this.remove(runner.id)
- this.edit(lastRunner.id, runner.mergeWith(lastRunner))
- }
-
- lastRunner = runner
- })
-
- return this
- }
-
- edit (id, newRunner) {
- let index = this.ids.indexOf(id + 1)
- this.ids.splice(index, 1, id)
- this.runners.splice(index, 1, newRunner)
- return this
- }
-
- length () {
- return this.ids.length
- }
-
- clearBefore (id) {
- let deleteCnt = this.ids.indexOf(id + 1) || 1
- this.ids.splice(0, deleteCnt, 0)
- this.runners.splice(0, deleteCnt, new SVG.FakeRunner())
- return this
- }
-}
-
-SVG.extend(SVG.Element, {
- // this function searches for all runners on the element and deletes the ones
- // which run before the current one. This is because absolute transformations
- // overwfrite anything anyway so there is no need to waste time computing
- // other runners
- _clearTransformRunnersBefore: function (currentRunner) {
- this._transformationRunners.clearBefore(currentRunner.id)
- },
-
- _currentTransform (current) {
- return this._transformationRunners.runners
- // we need the equal sign here to make sure, that also transformations
- // on the same runner which execute before the current transformation are
- // taken into account
- .filter((runner) => runner.id <= current.id)
- .map(getRunnerTransform)
- .reduce(lmultiply, new SVG.Matrix())
- },
-
- addRunner: function (runner) {
- this._transformationRunners.add(runner)
-
- SVG.Animator.transform_frame(
- mergeTransforms.bind(this), this._frameId
- )
- },
-
- _prepareRunner: function () {
- if (this._frameId == null) {
- this._transformationRunners = new RunnerArray()
- .add(new SVG.FakeRunner(new SVG.Matrix(this)))
-
- this._frameId = SVG.Element.frameId++
- }
- }
-})
-
-SVG.Element.frameId = 0
-
-SVG.extend(SVG.Runner, {
- attr: function (a, v) {
- return this.styleAttr('attr', a, v)
- },
-
- // Add animatable styles
- css: function (s, v) {
- return this.styleAttr('css', s, v)
- },
-
- styleAttr (type, name, val) {
- // apply attributes individually
- if (typeof name === 'object') {
- for (var key in val) {
- this.styleAttr(type, key, val[key])
- }
- }
-
- var morpher = new SVG.Morphable(this._stepper).to(val)
-
- this.queue(function () {
- morpher = morpher.from(this.element()[type](name))
- }, function (pos) {
- this.element()[type](name, morpher.at(pos))
- return morpher.done()
- })
-
- return this
- },
-
- zoom: function (level, point) {
- var morpher = new SVG.Morphable(this._stepper).to(new SVG.Number(level))
-
- this.queue(function () {
- morpher = morpher.from(this.zoom())
- }, function (pos) {
- this.element().zoom(morpher.at(pos), point)
- return morpher.done()
- })
-
- return this
- },
-
- /**
- ** absolute transformations
- **/
-
- //
- // M v -----|-----(D M v = F v)------|-----> T v
- //
- // 1. define the final state (T) and decompose it (once)
- // t = [tx, ty, the, lam, sy, sx]
- // 2. on every frame: pull the current state of all previous transforms
- // (M - m can change)
- // and then write this as m = [tx0, ty0, the0, lam0, sy0, sx0]
- // 3. Find the interpolated matrix F(pos) = m + pos * (t - m)
- // - Note F(0) = M
- // - Note F(1) = T
- // 4. Now you get the delta matrix as a result: D = F * inv(M)
-
- transform: function (transforms, relative, affine) {
- // If we have a declarative function, we should retarget it if possible
- relative = transforms.relative || relative
- if (this._isDeclarative && !relative && this._tryRetarget('transform', transforms)) {
- return this
- }
-
- // Parse the parameters
- var isMatrix = isMatrixLike(transforms)
- affine = transforms.affine != null
- ? transforms.affine
- : (affine != null ? affine : !isMatrix)
-
- // Create a morepher and set its type
- const morpher = new SVG.Morphable()
- .type(affine ? SVG.Morphable.TransformBag : SVG.Matrix)
- .stepper(this._stepper)
-
- let origin
- let element
- let current
- let currentAngle
- let startTransform
-
- function setup () {
- // make sure element and origin is defined
- element = element || this.element()
- origin = origin || getOrigin(transforms, element)
-
- startTransform = new SVG.Matrix(relative ? undefined : element)
-
- // add the runner to the element so it can merge transformations
- element.addRunner(this)
-
- // Deactivate all transforms that have run so far if we are absolute
- if (!relative) {
- element._clearTransformRunnersBefore(this)
- }
- }
-
- function run (pos) {
- // clear all other transforms before this in case something is saved
- // on this runner. We are absolute. We dont need these!
- if (!relative) this.clearTransform()
-
- let {x, y} = new SVG.Point(origin).transform(element._currentTransform(this))
-
- let target = new SVG.Matrix({...transforms, origin: [x, y]})
- let start = this._isDeclarative && current
- ? current
- : startTransform
-
- if (affine) {
- target = target.decompose(x, y)
- start = start.decompose(x, y)
-
- // Get the current and target angle as it was set
- const rTarget = target.rotate
- const rCurrent = start.rotate
-
- // Figure out the shortest path to rotate directly
- const possibilities = [rTarget - 360, rTarget, rTarget + 360]
- const distances = possibilities.map(a => Math.abs(a - rCurrent))
- const shortest = Math.min(...distances)
- const index = distances.indexOf(shortest)
- target.rotate = possibilities[index]
- }
-
- if (relative) {
- // we have to be careful here not to overwrite the rotation
- // with the rotate method of SVG.Matrix
- if (!isMatrix) {
- target.rotate = transforms.rotate || 0
- }
- if (this._isDeclarative && currentAngle) {
- start.rotate = currentAngle
- }
- }
-
- morpher.from(start)
- morpher.to(target)
-
- let affineParameters = morpher.at(pos)
- currentAngle = affineParameters.rotate
- current = new SVG.Matrix(affineParameters)
-
- this.addTransform(current)
- return morpher.done()
- }
-
- function retarget (newTransforms) {
- // only get a new origin if it changed since the last call
- if (
- (newTransforms.origin || 'center').toString() !==
- (transforms.origin || 'center').toString()
- ) {
- origin = getOrigin(transforms, element)
- }
-
- // overwrite the old transformations with the new ones
- transforms = {...newTransforms, origin}
- }
-
- this.queue(setup, run, retarget)
- this._isDeclarative && this._rememberMorpher('transform', morpher)
- return this
- },
-
- // Animatable x-axis
- x: function (x, relative) {
- return this._queueNumber('x', x)
- },
-
- // Animatable y-axis
- y: function (y) {
- return this._queueNumber('y', y)
- },
-
- dx: function (x) {
- return this._queueNumberDelta('dx', x)
- },
-
- dy: function (y) {
- return this._queueNumberDelta('dy', y)
- },
-
- _queueNumberDelta: function (method, to) {
- to = new SVG.Number(to)
-
- // Try to change the target if we have this method already registerd
- if (this._tryRetargetDelta(method, to)) return this
-
- // Make a morpher and queue the animation
- var morpher = new SVG.Morphable(this._stepper).to(to)
- this.queue(function () {
- var from = this.element()[method]()
- morpher.from(from)
- morpher.to(from + to)
- }, function (pos) {
- this.element()[method](morpher.at(pos))
- return morpher.done()
- })
-
- // Register the morpher so that if it is changed again, we can retarget it
- this._rememberMorpher(method, morpher)
- return this
- },
-
- _queueObject: function (method, to) {
- // Try to change the target if we have this method already registerd
- if (this._tryRetarget(method, to)) return this
-
- // Make a morpher and queue the animation
- var morpher = new SVG.Morphable(this._stepper).to(to)
- this.queue(function () {
- morpher.from(this.element()[method]())
- }, function (pos) {
- this.element()[method](morpher.at(pos))
- return morpher.done()
- })
-
- // Register the morpher so that if it is changed again, we can retarget it
- this._rememberMorpher(method, morpher)
- return this
- },
-
- _queueNumber: function (method, value) {
- return this._queueObject(method, new SVG.Number(value))
- },
-
- // Animatable center x-axis
- cx: function (x) {
- return this._queueNumber('cx', x)
- },
-
- // Animatable center y-axis
- cy: function (y) {
- return this._queueNumber('cy', y)
- },
-
- // Add animatable move
- move: function (x, y) {
- return this.x(x).y(y)
- },
-
- // Add animatable center
- center: function (x, y) {
- return this.cx(x).cy(y)
- },
-
- // Add animatable size
- size: function (width, height) {
- // animate bbox based size for all other elements
- var box
-
- if (!width || !height) {
- box = this._element.bbox()
- }
-
- if (!width) {
- width = box.width / box.height * height
- }
-
- if (!height) {
- height = box.height / box.width * width
- }
-
- return this
- .width(width)
- .height(height)
- },
-
- // Add animatable width
- width: function (width) {
- return this._queueNumber('width', width)
- },
-
- // Add animatable height
- height: function (height) {
- return this._queueNumber('height', height)
- },
-
- // Add animatable plot
- plot: function (a, b, c, d) {
- // Lines can be plotted with 4 arguments
- if (arguments.length === 4) {
- return this.plot([a, b, c, d])
- }
-
- // FIXME: this needs to be rewritten such that the element is only accesed
- // in the init function
- return this._queueObject('plot', new this._element.MorphArray(a))
-
- /*
- var morpher = this._element.morphArray().to(a)
-
- this.queue(function () {
- morpher.from(this._element.array())
- }, function (pos) {
- this._element.plot(morpher.at(pos))
- })
-
- return this
- */
- },
-
- // Add leading method
- leading: function (value) {
- return this._queueNumber('leading', value)
- },
-
- // Add animatable viewbox
- viewbox: function (x, y, width, height) {
- return this._queueObject('viewbox', new SVG.Box(x, y, width, height))
- },
-
- update: function (o) {
- if (typeof o !== 'object') {
- return this.update({
- offset: arguments[0],
- color: arguments[1],
- opacity: arguments[2]
- })
- }
-
- if (o.opacity != null) this.attr('stop-opacity', o.opacity)
- if (o.color != null) this.attr('stop-color', o.color)
- if (o.offset != null) this.attr('offset', o.offset)
-
- return this
- }
-})
diff --git a/src/selector.js b/src/selector.js
deleted file mode 100644
index b4ea05f..0000000
--- a/src/selector.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/* global idFromReference */
-
-// Method for getting an element by id
-SVG.get = function (id) {
- var node = document.getElementById(idFromReference(id) || id)
- return SVG.adopt(node)
-}
-
-// Select elements by query string
-SVG.select = function (query, parent) {
- return SVG.utils.map((parent || document).querySelectorAll(query), function (node) {
- return SVG.adopt(node)
- })
-}
-
-SVG.$$ = function (query, parent) {
- return SVG.utils.map((parent || document).querySelectorAll(query), function (node) {
- return SVG.adopt(node)
- })
-}
-
-SVG.$ = function (query, parent) {
- return SVG.adopt((parent || document).querySelector(query))
-}
-
-SVG.extend(SVG.Parent, {
- // Scoped select method
- select: function (query) {
- return SVG.select(query, this.node)
- }
-})
diff --git a/src/shape.js b/src/shape.js
deleted file mode 100644
index cb15098..0000000
--- a/src/shape.js
+++ /dev/null
@@ -1,10 +0,0 @@
-
-SVG.Shape = SVG.invent({
- // Initialize node
- create: function (node) {
- SVG.Element.call(this, node)
- },
-
- // Inherit from
- inherit: SVG.Element
-})
diff --git a/src/svg.js b/src/svg.js
index bf02df0..4026598 100644
--- a/src/svg.js
+++ b/src/svg.js
@@ -1,102 +1,14 @@
-/* global createElement, capitalize */
-/* eslint-disable new-cap */
+import * as svgMembers from './main.js'
+import * as regex from './modules/core/regex.js'
+import { makeInstance } from './utils/adopter'
// The main wrapping element
-var SVG = window.SVG = function (element) {
- if (SVG.supported) {
- element = createElement(element)
- return element
- }
+export default function SVG (element) {
+ return makeInstance(element)
}
-// Svg must be supported if we reached this stage
-SVG.supported = true
+Object.assign(SVG, svgMembers)
-// Default namespaces
-SVG.ns = 'http://www.w3.org/2000/svg'
-SVG.xmlns = 'http://www.w3.org/2000/xmlns/'
-SVG.xlink = 'http://www.w3.org/1999/xlink'
-SVG.svgjs = 'http://svgjs.com/svgjs'
-
-// Element id sequence
-SVG.did = 1000
-
-// Get next named element id
-SVG.eid = function (name) {
- return 'Svgjs' + capitalize(name) + (SVG.did++)
-}
-
-// Method for element creation
-SVG.create = function (name) {
- // create element
- return document.createElementNS(this.ns, name)
-}
-
-// Method for extending objects
-SVG.extend = function (modules, methods) {
- var key, i
-
- modules = Array.isArray(modules) ? modules : [modules]
-
- for (i = modules.length - 1; i >= 0; i--) {
- if (modules[i]) {
- for (key in methods) {
- modules[i].prototype[key] = methods[key]
- }
- }
- }
-}
-
-// Invent new element
-SVG.invent = function (config) {
- // Create element initializer
- var initializer = typeof config.create === 'function' ? config.create
- : function (node) {
- config.inherit.call(this, node || SVG.create(config.create))
- }
-
- // Inherit prototype
- if (config.inherit) {
- initializer.prototype = new config.inherit()
- initializer.prototype.constructor = initializer
- }
-
- // Extend with methods
- if (config.extend) {
- SVG.extend(initializer, config.extend)
- }
-
- // Attach construct method to parent
- if (config.construct) { SVG.extend(config.parent || SVG.Container, config.construct) }
-
- return initializer
-}
-
-// Adopt existing svg elements
-SVG.adopt = function (node) {
- // check for presence of node
- if (!node) return null
-
- // make sure a node isn't already adopted
- if (node.instance instanceof SVG.Element) return node.instance
-
- if (!(node instanceof window.SVGElement)) {
- return new SVG.HtmlNode(node)
- }
-
- // initialize variables
- var element
-
- // adopt with element-specific settings
- if (node.nodeName === 'svg') {
- element = new SVG.Doc(node)
- } else if (node.nodeName === 'linearGradient' || node.nodeName === 'radialGradient') {
- element = new SVG.Gradient(node)
- } else if (SVG[capitalize(node.nodeName)]) {
- element = new SVG[capitalize(node.nodeName)](node)
- } else {
- element = new SVG.Parent(node)
- }
-
- return element
-}
+SVG.utils = SVG
+SVG.regex = regex
+SVG.get = SVG
diff --git a/src/symbol.js b/src/symbol.js
deleted file mode 100644
index ca67607..0000000
--- a/src/symbol.js
+++ /dev/null
@@ -1,15 +0,0 @@
-
-SVG.Symbol = SVG.invent({
- // Initialize node
- create: 'symbol',
-
- // Inherit from
- inherit: SVG.Container,
-
- construct: {
- // create symbol
- symbol: function () {
- return this.put(new SVG.Symbol())
- }
- }
-})
diff --git a/src/text.js b/src/text.js
deleted file mode 100644
index 8a50df9..0000000
--- a/src/text.js
+++ /dev/null
@@ -1,234 +0,0 @@
-SVG.Text = SVG.invent({
- // Initialize node
- create: function (node) {
- SVG.Element.call(this, node || SVG.create('text'))
- this.dom.leading = new SVG.Number(1.3) // store leading value for rebuilding
- this._rebuild = true // enable automatic updating of dy values
- this._build = false // disable build mode for adding multiple lines
-
- // set default font
- this.attr('font-family', SVG.defaults.attrs['font-family'])
- },
-
- // Inherit from
- inherit: SVG.Parent,
-
- // Add class methods
- extend: {
- // Move over x-axis
- x: function (x) {
- // act as getter
- if (x == null) {
- return this.attr('x')
- }
-
- return this.attr('x', x)
- },
- // Move over y-axis
- y: function (y) {
- var oy = this.attr('y')
- var o = typeof oy === 'number' ? oy - this.bbox().y : 0
-
- // act as getter
- if (y == null) {
- return typeof oy === 'number' ? oy - o : oy
- }
-
- return this.attr('y', typeof y === 'number' ? y + o : y)
- },
- // Move center over x-axis
- cx: function (x) {
- return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2)
- },
- // Move center over y-axis
- cy: function (y) {
- return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2)
- },
- // Set the text content
- text: function (text) {
- // act as getter
- if (text === undefined) {
- var children = this.node.childNodes
- var firstLine = 0
- text = ''
-
- for (var i = 0, len = children.length; i < len; ++i) {
- // skip textPaths - they are no lines
- if (children[i].nodeName === 'textPath') {
- if (i === 0) firstLine = 1
- continue
- }
-
- // add newline if its not the first child and newLined is set to true
- if (i !== firstLine && children[i].nodeType !== 3 && SVG.adopt(children[i]).dom.newLined === true) {
- text += '\n'
- }
-
- // add content of this node
- text += children[i].textContent
- }
-
- return text
- }
-
- // remove existing content
- this.clear().build(true)
-
- if (typeof text === 'function') {
- // call block
- text.call(this, this)
- } else {
- // store text and make sure text is not blank
- text = text.split('\n')
-
- // build new lines
- for (var j = 0, jl = text.length; j < jl; j++) {
- this.tspan(text[j]).newLine()
- }
- }
-
- // disable build mode and rebuild lines
- return this.build(false).rebuild()
- },
- // Set / get leading
- leading: function (value) {
- // act as getter
- if (value == null) {
- return this.dom.leading
- }
-
- // act as setter
- this.dom.leading = new SVG.Number(value)
-
- return this.rebuild()
- },
- // Rebuild appearance type
- rebuild: function (rebuild) {
- // store new rebuild flag if given
- if (typeof rebuild === 'boolean') {
- this._rebuild = rebuild
- }
-
- // define position of all lines
- if (this._rebuild) {
- var self = this
- var blankLineOffset = 0
- var dy = this.dom.leading * new SVG.Number(this.attr('font-size'))
-
- this.each(function () {
- if (this.dom.newLined) {
- this.attr('x', self.attr('x'))
-
- if (this.text() === '\n') {
- blankLineOffset += dy
- } else {
- this.attr('dy', dy + blankLineOffset)
- blankLineOffset = 0
- }
- }
- })
-
- this.fire('rebuild')
- }
-
- return this
- },
- // Enable / disable build mode
- build: function (build) {
- this._build = !!build
- return this
- },
- // overwrite method from parent to set data properly
- setData: function (o) {
- this.dom = o
- this.dom.leading = new SVG.Number(o.leading || 1.3)
- return this
- }
- },
-
- // Add parent method
- construct: {
- // Create text element
- text: function (text) {
- return this.put(new SVG.Text()).text(text)
- },
- // Create plain text element
- plain: function (text) {
- return this.put(new SVG.Text()).plain(text)
- }
- }
-
-})
-
-SVG.Tspan = SVG.invent({
- // Initialize node
- create: 'tspan',
-
- // Inherit from
- inherit: SVG.Parent,
-
- // Add class methods
- extend: {
- // Set text content
- text: function (text) {
- if (text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '')
-
- typeof text === 'function' ? text.call(this, this) : this.plain(text)
-
- return this
- },
- // Shortcut dx
- dx: function (dx) {
- return this.attr('dx', dx)
- },
- // Shortcut dy
- dy: function (dy) {
- return this.attr('dy', dy)
- },
- // Create new line
- newLine: function () {
- // fetch text parent
- var t = this.parent(SVG.Text)
-
- // mark new line
- this.dom.newLined = true
-
- // apply new position
- return this.dy(t.dom.leading * t.attr('font-size')).attr('x', t.x())
- }
- }
-})
-
-SVG.extend([SVG.Text, SVG.Tspan], {
- // Create plain text node
- plain: function (text) {
- // clear if build mode is disabled
- if (this._build === false) {
- this.clear()
- }
-
- // create text node
- this.node.appendChild(document.createTextNode(text))
-
- return this
- },
- // Create a tspan
- tspan: function (text) {
- var tspan = new SVG.Tspan()
-
- // clear if build mode is disabled
- if (!this._build) {
- this.clear()
- }
-
- // add new tspan
- this.node.appendChild(tspan.node)
-
- return tspan.text(text)
- },
- // FIXME: Does this also work for textpath?
- // Get length of text element
- length: function () {
- return this.node.getComputedTextLength()
- }
-})
diff --git a/src/textpath.js b/src/textpath.js
deleted file mode 100644
index 561f147..0000000
--- a/src/textpath.js
+++ /dev/null
@@ -1,77 +0,0 @@
-SVG.TextPath = SVG.invent({
- // Initialize node
- create: 'textPath',
-
- // Inherit from
- inherit: SVG.Text,
-
- // Define parent class
- parent: SVG.Parent,
-
- // Add parent method
- extend: {
- MorphArray: SVG.PathArray,
- // return the array of the path track element
- array: function () {
- var track = this.track()
-
- return track ? track.array() : null
- },
- // Plot path if any
- plot: function (d) {
- var track = this.track()
- var pathArray = null
-
- if (track) {
- pathArray = track.plot(d)
- }
-
- return (d == null) ? pathArray : this
- },
- // Get the path element
- track: function () {
- return this.reference('href')
- }
- },
- construct: {
- textPath: function (text, path) {
- return this.defs().path(path).text(text).addTo(this)
- }
- }
-})
-
-SVG.extend([SVG.Text], {
- // Create path for text to run on
- path: function (track) {
- var path = new SVG.TextPath()
-
- // if d is a path, reuse it
- if (!(track instanceof SVG.Path)) {
- // create path element
- track = this.doc().defs().path(track)
- }
-
- // link textPath to path and add content
- path.attr('href', '#' + track, SVG.xlink)
-
- // add textPath element as child node and return textPath
- return this.put(path)
- },
- // Todo: make this plural?
- // Get the textPath children
- textPath: function () {
- return this.select('textPath')
- }
-})
-
-SVG.extend([SVG.Path], {
- // creates a textPath from this path
- text: function (text) {
- if (text instanceof SVG.Text) {
- var txt = text.text()
- return text.clear().path(this).text(txt)
- }
- return this.parent().put(new SVG.Text()).path(this).text(text)
- }
- // TODO: Maybe add `targets` to get all textPaths associated with this path
-})
diff --git a/src/timeline.js b/src/timeline.js
deleted file mode 100644
index 0bf8ac5..0000000
--- a/src/timeline.js
+++ /dev/null
@@ -1,282 +0,0 @@
-
-// Must Change ....
-SVG.easing = {
- '-': function (pos) { return pos },
- '<>': function (pos) { return -Math.cos(pos * Math.PI) / 2 + 0.5 },
- '>': function (pos) { return Math.sin(pos * Math.PI / 2) },
- '<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 }
-}
-
-var time = window.performance || Date
-
-var makeSchedule = function (runnerInfo) {
- var start = runnerInfo.start
- var duration = runnerInfo.runner.duration()
- var end = start + duration
- return {start: start, duration: duration, end: end, runner: runnerInfo.runner}
-}
-
-SVG.Timeline = SVG.invent({
- inherit: SVG.EventTarget,
-
- // Construct a new timeline on the given element
- create: function () {
- this._timeSource = function () {
- return time.now()
- }
-
- this._dispatcher = document.createElement('div')
-
- // Store the timing variables
- this._startTime = 0
- this._speed = 1.0
-
- // Play control variables control how the animation proceeds
- this._reverse = false
- this._persist = 0
-
- // Keep track of the running animations and their starting parameters
- this._nextFrame = null
- this._paused = false
- this._runners = []
- this._order = []
- this._time = 0
- this._lastSourceTime = 0
- this._lastStepTime = 0
- },
-
- extend: {
-
- getEventTarget () {
- return this._dispatcher
- },
-
- /**
- *
- */
-
- // schedules a runner on the timeline
- schedule (runner, delay, when) {
- if (runner == null) {
- return this._runners.map(makeSchedule).sort(function (a, b) {
- return (a.start - b.start) || (a.duration - b.duration)
- })
- }
-
- if (!this.active()) {
- this._step()
- if (when == null) {
- when = 'now'
- }
- }
-
- // The start time for the next animation can either be given explicitly,
- // derived from the current timeline time or it can be relative to the
- // last start time to chain animations direclty
- var absoluteStartTime = 0
- delay = delay || 0
-
- // Work out when to start the animation
- if (when == null || when === 'last' || when === 'after') {
- // Take the last time and increment
- absoluteStartTime = this._startTime
- } else if (when === 'absolute' || when === 'start') {
- absoluteStartTime = delay
- delay = 0
- } else if (when === 'now') {
- absoluteStartTime = this._time
- } else if (when === 'relative') {
- let runnerInfo = this._runners[runner.id]
- if (runnerInfo) {
- absoluteStartTime = runnerInfo.start + delay
- delay = 0
- }
- } else {
- throw new Error('Invalid value for the "when" parameter')
- }
-
- // Manage runner
- runner.unschedule()
- runner.timeline(this)
- runner.time(-delay)
-
- // Save startTime for next runner
- this._startTime = absoluteStartTime + runner.duration() + delay
-
- // Save runnerInfo
- this._runners[runner.id] = {
- persist: this.persist(),
- runner: runner,
- start: absoluteStartTime
- }
-
- // Save order and continue
- this._order.push(runner.id)
- this._continue()
- return this
- },
-
- // Remove the runner from this timeline
- unschedule (runner) {
- var index = this._order.indexOf(runner.id)
- if (index < 0) return this
-
- delete this._runners[runner.id]
- this._order.splice(index, 1)
- runner.timeline(null)
- return this
- },
-
- play () {
- // Now make sure we are not paused and continue the animation
- this._paused = false
- return this._continue()
- },
-
- pause () {
- // Cancel the next animation frame and pause
- this._nextFrame = null
- this._paused = true
- return this
- },
-
- stop () {
- // Cancel the next animation frame and go to start
- this.seek(-this._time)
- return this.pause()
- },
-
- finish () {
- this.seek(Infinity)
- return this.pause()
- },
-
- speed (speed) {
- if (speed == null) return this._speed
- this._speed = speed
- return this
- },
-
- reverse (yes) {
- var currentSpeed = this.speed()
- if (yes == null) return this.speed(-currentSpeed)
-
- var positive = Math.abs(currentSpeed)
- return this.speed(yes ? positive : -positive)
- },
-
- seek (dt) {
- this._time += dt
- return this._continue()
- },
-
- time (time) {
- if (time == null) return this._time
- this._time = time
- return this
- },
-
- persist (dtOrForever) {
- if (dtOrForever == null) return this._persist
- this._persist = dtOrForever
- return this
- },
-
- source (fn) {
- if (fn == null) return this._timeSource
- this._timeSource = fn
- return this
- },
-
- _step () {
- // If the timeline is paused, just do nothing
- if (this._paused) return
-
- // Get the time delta from the last time and update the time
- // TODO: Deal with window.blur window.focus to pause animations
- var time = this._timeSource()
- var dtSource = time - this._lastSourceTime
- var dtTime = this._speed * dtSource + (this._time - this._lastStepTime)
- this._lastSourceTime = time
-
- // Update the time
- this._time += dtTime
- this._lastStepTime = this._time
- // this.fire('time', this._time)
-
- // Run all of the runners directly
- var runnersLeft = false
- for (var i = 0, len = this._order.length; i < len; i++) {
- // Get and run the current runner and ignore it if its inactive
- var runnerInfo = this._runners[this._order[i]]
- var runner = runnerInfo.runner
- let dt = dtTime
-
- // Make sure that we give the actual difference
- // between runner start time and now
- let dtToStart = this._time - runnerInfo.start
-
- // Dont run runner if not started yet
- if (dtToStart < 0) {
- runnersLeft = true
- continue
- } else if (dtToStart < dt) {
- // Adjust dt to make sure that animation is on point
- dt = dtToStart
- }
-
- if (!runner.active()) continue
-
- // If this runner is still going, signal that we need another animation
- // frame, otherwise, remove the completed runner
- var finished = runner.step(dt).done
- if (!finished) {
- runnersLeft = true
- // continue
- } else if (runnerInfo.persist !== true) {
- // runner is finished. And runner might get removed
-
- // TODO: Figure out end time of runner
- var endTime = runner.duration() - runner.time() + this._time
-
- if (endTime + this._persist < this._time) {
- // Delete runner and correct index
- delete this._runners[this._order[i]]
- this._order.splice(i--, 1) && --len
- runner.timeline(null)
- }
- }
- }
-
- // Get the next animation frame to keep the simulation going
- if (runnersLeft) {
- this._nextFrame = SVG.Animator.frame(this._step.bind(this))
- } else {
- this._nextFrame = null
- }
- return this
- },
-
- // Checks if we are running and continues the animation
- _continue () {
- if (this._paused) return this
- if (!this._nextFrame) {
- this._nextFrame = SVG.Animator.frame(this._step.bind(this))
- }
- return this
- },
-
- active () {
- return !!this._nextFrame
- }
- },
-
- // These methods will be added to all SVG.Element objects
- parent: SVG.Element,
- construct: {
- timeline: function () {
- this._timeline = (this._timeline || new SVG.Timeline())
- return this._timeline
- }
- }
-})
diff --git a/src/transform.js b/src/transform.js
deleted file mode 100644
index 96c0aec..0000000
--- a/src/transform.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/* global arrayToMatrix getOrigin isMatrixLike */
-
-SVG.extend(SVG.Element, {
- // Reset all transformations
- untransform: function () {
- return this.attr('transform', null)
- },
-
- // merge the whole transformation chain into one matrix and returns it
- matrixify: function () {
- var matrix = (this.attr('transform') || '')
- // split transformations
- .split(SVG.regex.transforms).slice(0, -1).map(function (str) {
- // generate key => value pairs
- var kv = str.trim().split('(')
- return [kv[0],
- kv[1].split(SVG.regex.delimiter)
- .map(function (str) { return parseFloat(str) })
- ]
- })
- .reverse()
- // merge every transformation into one matrix
- .reduce(function (matrix, transform) {
- if (transform[0] === 'matrix') {
- return matrix.lmultiply(arrayToMatrix(transform[1]))
- }
- return matrix[transform[0]].apply(matrix, transform[1])
- }, new SVG.Matrix())
-
- return matrix
- },
-
- // add an element to another parent without changing the visual representation on the screen
- toParent: function (parent) {
- if (this === parent) return this
- var ctm = this.screenCTM()
- var pCtm = parent.screenCTM().inverse()
-
- this.addTo(parent).untransform().transform(pCtm.multiply(ctm))
-
- return this
- },
-
- // same as above with parent equals root-svg
- toDoc: function () {
- return this.toParent(this.doc())
- }
-})
-
-SVG.extend(SVG.Element, {
-
- // Add transformations
- transform: function (o, relative) {
- // Act as a getter if no object was passed
- if (o == null || typeof o === 'string') {
- var decomposed = new SVG.Matrix(this).decompose()
- return decomposed[o] || decomposed
- }
-
- if (!isMatrixLike(o)) {
- // Set the origin according to the defined transform
- o = {...o, origin: getOrigin(o, this)}
- }
-
- // The user can pass a boolean, an SVG.Element or an SVG.Matrix or nothing
- var cleanRelative = relative === true ? this : (relative || false)
- var result = new SVG.Matrix(cleanRelative).transform(o)
- return this.attr('transform', result)
- }
-})
diff --git a/src/types/ArrayPolyfill.js b/src/types/ArrayPolyfill.js
new file mode 100644
index 0000000..cf95d54
--- /dev/null
+++ b/src/types/ArrayPolyfill.js
@@ -0,0 +1,30 @@
+/* eslint no-new-func: "off" */
+export const subClassArray = (function () {
+ try {
+ // try es6 subclassing
+ return Function('name', 'baseClass', '_constructor', [
+ 'baseClass = baseClass || Array',
+ 'return {',
+ '[name]: class extends baseClass {',
+ 'constructor (...args) {',
+ 'super(...args)',
+ '_constructor && _constructor.apply(this, args)',
+ '}',
+ '}',
+ '}[name]'
+ ].join('\n'))
+ } catch (e) {
+ // Use es5 approach
+ return (name, baseClass = Array, _constructor) => {
+ const Arr = function () {
+ baseClass.apply(this, arguments)
+ _constructor && _constructor.apply(this, arguments)
+ }
+
+ Arr.prototype = Object.create(baseClass.prototype)
+ Arr.prototype.constructor = Arr
+
+ return Arr
+ }
+ }
+})()
diff --git a/src/types/Base.js b/src/types/Base.js
new file mode 100644
index 0000000..d2897a1
--- /dev/null
+++ b/src/types/Base.js
@@ -0,0 +1,10 @@
+export default class Base {
+ // constructor (node/*, {extensions = []} */) {
+ // // this.tags = []
+ // //
+ // // for (let extension of extensions) {
+ // // extension.setup.call(this, node)
+ // // this.tags.push(extension.name)
+ // // }
+ // }
+}
diff --git a/src/types/Box.js b/src/types/Box.js
new file mode 100644
index 0000000..b51415f
--- /dev/null
+++ b/src/types/Box.js
@@ -0,0 +1,147 @@
+import { delimiter } from '../modules/core/regex.js'
+import { registerMethods } from '../utils/methods.js'
+import Point from './Point.js'
+import parser from '../modules/core/parser.js'
+
+function isNulledBox (box) {
+ return !box.w && !box.h && !box.x && !box.y
+}
+
+function domContains (node) {
+ return (document.documentElement.contains || function (node) {
+ // This is IE - it does not support contains() for top-level SVGs
+ while (node.parentNode) {
+ node = node.parentNode
+ }
+ return node === document
+ }).call(document.documentElement, node)
+}
+
+export default class Box {
+ constructor (...args) {
+ this.init(...args)
+ }
+
+ init (source) {
+ var base = [0, 0, 0, 0]
+ source = typeof source === 'string' ? source.split(delimiter).map(parseFloat)
+ : Array.isArray(source) ? source
+ : typeof source === 'object' ? [source.left != null ? source.left
+ : source.x, source.top != null ? source.top : source.y, source.width, source.height]
+ : arguments.length === 4 ? [].slice.call(arguments)
+ : base
+
+ this.x = source[0] || 0
+ this.y = source[1] || 0
+ this.width = this.w = source[2] || 0
+ this.height = this.h = source[3] || 0
+
+ // Add more bounding box properties
+ this.x2 = this.x + this.w
+ this.y2 = this.y + this.h
+ this.cx = this.x + this.w / 2
+ this.cy = this.y + this.h / 2
+ }
+
+ // Merge rect box with another, return a new instance
+ merge (box) {
+ let x = Math.min(this.x, box.x)
+ let y = Math.min(this.y, box.y)
+ let width = Math.max(this.x + this.width, box.x + box.width) - x
+ let height = Math.max(this.y + this.height, box.y + box.height) - y
+
+ return new Box(x, y, width, height)
+ }
+
+ transform (m) {
+ let xMin = Infinity
+ let xMax = -Infinity
+ let yMin = Infinity
+ let yMax = -Infinity
+
+ let pts = [
+ new Point(this.x, this.y),
+ new Point(this.x2, this.y),
+ new Point(this.x, this.y2),
+ new Point(this.x2, this.y2)
+ ]
+
+ pts.forEach(function (p) {
+ p = p.transform(m)
+ xMin = Math.min(xMin, p.x)
+ xMax = Math.max(xMax, p.x)
+ yMin = Math.min(yMin, p.y)
+ yMax = Math.max(yMax, p.y)
+ })
+
+ return new Box(
+ xMin, yMin,
+ xMax - xMin,
+ yMax - yMin
+ )
+ }
+
+ addOffset () {
+ // offset by window scroll position, because getBoundingClientRect changes when window is scrolled
+ this.x += window.pageXOffset
+ this.y += window.pageYOffset
+ return this
+ }
+
+ toString () {
+ return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height
+ }
+
+ toArray () {
+ return [this.x, this.y, this.width, this.height]
+ }
+
+ isNulled () {
+ return isNulledBox(this)
+ }
+}
+
+function getBox (cb) {
+ let box
+
+ try {
+ box = cb(this.node)
+
+ if (isNulledBox(box) && !domContains(this.node)) {
+ throw new Error('Element not in the dom')
+ }
+ } catch (e) {
+ try {
+ let clone = this.clone(parser().svg).show()
+ box = cb(clone.node)
+ clone.remove()
+ } catch (e) {
+ console.warn('Getting a bounding box of this element is not possible')
+ }
+ }
+ return box
+}
+
+registerMethods({
+ Element: {
+ // Get bounding box
+ bbox () {
+ return new Box(getBox.call(this, (node) => node.getBBox()))
+ },
+
+ rbox (el) {
+ let box = new Box(getBox.call(this, (node) => node.getBoundingClientRect()))
+ if (el) return box.transform(el.screenCTM().inverse())
+ return box.addOffset()
+ }
+ },
+ viewbox: {
+ viewbox (x, y, width, height) {
+ // act as getter
+ if (x == null) return new Box(this.attr('viewBox'))
+
+ // act as setter
+ return this.attr('viewBox', new Box(x, y, width, height))
+ }
+ }
+})
diff --git a/src/types/Color.js b/src/types/Color.js
new file mode 100644
index 0000000..6bbfd82
--- /dev/null
+++ b/src/types/Color.js
@@ -0,0 +1,146 @@
+/*
+
+Color {
+ constructor (a, b, c, space) {
+ space: 'hsl'
+ a: 30
+ b: 20
+ c: 10
+ },
+
+ toRgb () { return new Color in rgb space }
+ toHsl () { return new Color in hsl space }
+ toLab () { return new Color in lab space }
+
+ toArray () { [space, a, b, c] }
+ fromArray () { convert it back }
+}
+
+// Conversions aren't always exact because of monitor profiles etc...
+new Color(h, s, l, 'hsl') !== new Color(r, g, b).hsl()
+new Color(100, 100, 100, [space])
+new Color('hsl(30, 20, 10)')
+
+// Sugar
+SVG.rgb(30, 20, 50).lab()
+SVG.hsl()
+SVG.lab('rgb(100, 100, 100)')
+*/
+
+import { hex, isHex, isRgb, rgb, whitespace } from '../modules/core/regex.js'
+
+// Ensure to six-based hex
+function fullHex (hex) {
+ return hex.length === 4
+ ? [ '#',
+ hex.substring(1, 2), hex.substring(1, 2),
+ hex.substring(2, 3), hex.substring(2, 3),
+ hex.substring(3, 4), hex.substring(3, 4)
+ ].join('')
+ : hex
+}
+
+// Component to hex value
+function compToHex (comp) {
+ var hex = comp.toString(16)
+ return hex.length === 1 ? '0' + hex : hex
+}
+
+export default class Color {
+ constructor (...args) {
+ this.init(...args)
+ }
+
+ init (color, g, b) {
+ let match
+
+ // initialize defaults
+ this.r = 0
+ this.g = 0
+ this.b = 0
+
+ if (!color) return
+
+ // parse color
+ if (typeof color === 'string') {
+ if (isRgb.test(color)) {
+ // get rgb values
+ match = rgb.exec(color.replace(whitespace, ''))
+
+ // parse numeric values
+ this.r = parseInt(match[1])
+ this.g = parseInt(match[2])
+ this.b = parseInt(match[3])
+ } else if (isHex.test(color)) {
+ // get hex values
+ match = hex.exec(fullHex(color))
+
+ // parse numeric values
+ this.r = parseInt(match[1], 16)
+ this.g = parseInt(match[2], 16)
+ this.b = parseInt(match[3], 16)
+ }
+ } else if (Array.isArray(color)) {
+ this.r = color[0]
+ this.g = color[1]
+ this.b = color[2]
+ } else if (typeof color === 'object') {
+ this.r = color.r
+ this.g = color.g
+ this.b = color.b
+ } else if (arguments.length === 3) {
+ this.r = color
+ this.g = g
+ this.b = b
+ }
+ }
+
+ // Default to hex conversion
+ toString () {
+ return this.toHex()
+ }
+
+ toArray () {
+ return [this.r, this.g, this.b]
+ }
+
+ // Build hex value
+ toHex () {
+ return '#' +
+ compToHex(Math.round(this.r)) +
+ compToHex(Math.round(this.g)) +
+ compToHex(Math.round(this.b))
+ }
+
+ // Build rgb value
+ toRgb () {
+ return 'rgb(' + [this.r, this.g, this.b].join() + ')'
+ }
+
+ // Calculate true brightness
+ brightness () {
+ return (this.r / 255 * 0.30) +
+ (this.g / 255 * 0.59) +
+ (this.b / 255 * 0.11)
+ }
+
+ // Testers
+
+ // Test if given value is a color string
+ static test (color) {
+ color += ''
+ return isHex.test(color) || isRgb.test(color)
+ }
+
+ // Test if given value is a rgb object
+ static isRgb (color) {
+ return color && typeof color.r === 'number' &&
+ typeof color.g === 'number' &&
+ typeof color.b === 'number'
+ }
+
+ // Test if given value is a color
+ static isColor (color) {
+ return this.isRgb(color) || this.test(color)
+ }
+}
diff --git a/src/types/EventTarget.js b/src/types/EventTarget.js
new file mode 100644
index 0000000..a32a1f1
--- /dev/null
+++ b/src/types/EventTarget.js
@@ -0,0 +1,90 @@
+import { dispatch, off, on } from '../modules/core/event.js'
+import { registerMethods } from '../utils/methods.js'
+import Base from './Base.js'
+
+export default class EventTarget extends Base {
+ constructor ({ events = {} } = {}) {
+ super()
+ this.events = events
+ }
+
+ addEventListener () {}
+
+ // Bind given event to listener
+ on (event, listener, binding, options) {
+ on(this, event, listener, binding, options)
+ return this
+ }
+
+ // Unbind event from listener
+ off (event, listener) {
+ off(this, event, listener)
+ return this
+ }
+
+ dispatch (event, data) {
+ return dispatch(this, event, data)
+ }
+
+ dispatchEvent (event) {
+ const bag = this.getEventHolder().events
+ if (!bag) return true
+
+ const events = bag[event.type]
+
+ for (let i in events) {
+ for (let j in events[i]) {
+ events[i][j](event)
+ }
+ }
+
+ return !event.defaultPrevented
+ }
+
+ // Fire given event
+ fire (event, data) {
+ this.dispatch(event, data)
+ return this
+ }
+
+ getEventHolder () {
+ return this
+ }
+
+ getEventTarget () {
+ return this
+ }
+
+ removeEventListener () {}
+}
+
+// Add events to elements
+const methods = [ 'click',
+ 'dblclick',
+ 'mousedown',
+ 'mouseup',
+ 'mouseover',
+ 'mouseout',
+ 'mousemove',
+ 'mouseenter',
+ 'mouseleave',
+ 'touchstart',
+ 'touchmove',
+ 'touchleave',
+ 'touchend',
+ 'touchcancel' ].reduce(function (last, event) {
+ // add event to Element
+ const fn = function (f) {
+ if (f === null) {
+ off(this, event)
+ } else {
+ on(this, event, f)
+ }
+ return this
+ }
+
+ last[event] = fn
+ return last
+}, {})
+
+registerMethods('Element', methods)
diff --git a/src/types/Matrix.js b/src/types/Matrix.js
new file mode 100644
index 0000000..963fd1a
--- /dev/null
+++ b/src/types/Matrix.js
@@ -0,0 +1,522 @@
+import { delimiter } from '../modules/core/regex.js'
+import { radians } from '../utils/utils.js'
+import { registerMethods } from '../utils/methods.js'
+import Element from '../elements/Element.js'
+import Point from './Point.js'
+import parser from '../modules/core/parser.js'
+
+// Create matrix array for looping
+const abcdef = 'abcdef'.split('')
+
+function closeEnough (a, b, threshold) {
+ return Math.abs(b - a) < (threshold || 1e-6)
+}
+
+export default class Matrix {
+ constructor (...args) {
+ this.init(...args)
+ }
+
+ // Initialize
+ init (source) {
+ var base = Matrix.fromArray([1, 0, 0, 1, 0, 0])
+
+ // ensure source as object
+ source = source instanceof Element ? source.matrixify()
+ : typeof source === 'string' ? Matrix.fromArray(source.split(delimiter).map(parseFloat))
+ : Array.isArray(source) ? Matrix.fromArray(source)
+ : (typeof source === 'object' && Matrix.isMatrixLike(source)) ? source
+ : (typeof source === 'object') ? new Matrix().transform(source)
+ : arguments.length === 6 ? Matrix.fromArray([].slice.call(arguments))
+ : base
+
+ // Merge the source matrix with the base matrix
+ this.a = source.a != null ? source.a : base.a
+ this.b = source.b != null ? source.b : base.b
+ this.c = source.c != null ? source.c : base.c
+ this.d = source.d != null ? source.d : base.d
+ this.e = source.e != null ? source.e : base.e
+ this.f = source.f != null ? source.f : base.f
+ }
+
+ // Clones this matrix
+ clone () {
+ return new Matrix(this)
+ }
+
+ // Transform a matrix into another matrix by manipulating the space
+ transform (o) {
+ // Check if o is a matrix and then left multiply it directly
+ if (Matrix.isMatrixLike(o)) {
+ var matrix = new Matrix(o)
+ return matrix.multiplyO(this)
+ }
+
+ // Get the proposed transformations and the current transformations
+ var t = Matrix.formatTransforms(o)
+ var current = this
+ let { x: ox, y: oy } = new Point(t.ox, t.oy).transform(current)
+
+ // Construct the resulting matrix
+ var transformer = new Matrix()
+ .translateO(t.rx, t.ry)
+ .lmultiplyO(current)
+ .translateO(-ox, -oy)
+ .scaleO(t.scaleX, t.scaleY)
+ .skewO(t.skewX, t.skewY)
+ .shearO(t.shear)
+ .rotateO(t.theta)
+ .translateO(ox, oy)
+
+ // If we want the origin at a particular place, we force it there
+ if (isFinite(t.px) || isFinite(t.py)) {
+ const origin = new Point(ox, oy).transform(transformer)
+ // TODO: Replace t.px with isFinite(t.px)
+ const dx = t.px ? t.px - origin.x : 0
+ const dy = t.py ? t.py - origin.y : 0
+ transformer.translateO(dx, dy)
+ }
+
+ // Translate now after positioning
+ transformer.translateO(t.tx, t.ty)
+ return transformer
+ }
+
+ // Applies a matrix defined by its affine parameters
+ compose (o) {
+ if (o.origin) {
+ o.originX = o.origin[0]
+ o.originY = o.origin[1]
+ }
+ // Get the parameters
+ var ox = o.originX || 0
+ var oy = o.originY || 0
+ var sx = o.scaleX || 1
+ var sy = o.scaleY || 1
+ var lam = o.shear || 0
+ var theta = o.rotate || 0
+ var tx = o.translateX || 0
+ var ty = o.translateY || 0
+
+ // Apply the standard matrix
+ var result = new Matrix()
+ .translateO(-ox, -oy)
+ .scaleO(sx, sy)
+ .shearO(lam)
+ .rotateO(theta)
+ .translateO(tx, ty)
+ .lmultiplyO(this)
+ .translateO(ox, oy)
+ return result
+ }
+
+ // Decomposes this matrix into its affine parameters
+ decompose (cx = 0, cy = 0) {
+ // Get the parameters from the matrix
+ var a = this.a
+ var b = this.b
+ var c = this.c
+ var d = this.d
+ var e = this.e
+ var f = this.f
+
+ // Figure out if the winding direction is clockwise or counterclockwise
+ var determinant = a * d - b * c
+ var ccw = determinant > 0 ? 1 : -1
+
+ // Since we only shear in x, we can use the x basis to get the x scale
+ // and the rotation of the resulting matrix
+ var sx = ccw * Math.sqrt(a * a + b * b)
+ var thetaRad = Math.atan2(ccw * b, ccw * a)
+ var theta = 180 / Math.PI * thetaRad
+ var ct = Math.cos(thetaRad)
+ var st = Math.sin(thetaRad)
+
+ // We can then solve the y basis vector simultaneously to get the other
+ // two affine parameters directly from these parameters
+ var lam = (a * c + b * d) / determinant
+ var sy = ((c * sx) / (lam * a - b)) || ((d * sx) / (lam * b + a))
+
+ // Use the translations
+ let tx = e - cx + cx * ct * sx + cy * (lam * ct * sx - st * sy)
+ let ty = f - cy + cx * st * sx + cy * (lam * st * sx + ct * sy)
+
+ // Construct the decomposition and return it
+ return {
+ // Return the affine parameters
+ scaleX: sx,
+ scaleY: sy,
+ shear: lam,
+ rotate: theta,
+ translateX: tx,
+ translateY: ty,
+ originX: cx,
+ originY: cy,
+
+ // Return the matrix parameters
+ a: this.a,
+ b: this.b,
+ c: this.c,
+ d: this.d,
+ e: this.e,
+ f: this.f
+ }
+ }
+
+ // Left multiplies by the given matrix
+ multiply (matrix) {
+ return this.clone().multiplyO(matrix)
+ }
+
+ multiplyO (matrix) {
+ // Get the matrices
+ var l = this
+ var r = matrix instanceof Matrix
+ ? matrix
+ : new Matrix(matrix)
+
+ return Matrix.matrixMultiply(l, r, this)
+ }
+
+ lmultiply (matrix) {
+ return this.clone().lmultiplyO(matrix)
+ }
+
+ lmultiplyO (matrix) {
+ var r = this
+ var l = matrix instanceof Matrix
+ ? matrix
+ : new Matrix(matrix)
+
+ return Matrix.matrixMultiply(l, r, this)
+ }
+
+ // Inverses matrix
+ inverseO () {
+ // Get the current parameters out of the matrix
+ var a = this.a
+ var b = this.b
+ var c = this.c
+ var d = this.d
+ var e = this.e
+ var f = this.f
+
+ // Invert the 2x2 matrix in the top left
+ var det = a * d - b * c
+ if (!det) throw new Error('Cannot invert ' + this)
+
+ // Calculate the top 2x2 matrix
+ var na = d / det
+ var nb = -b / det
+ var nc = -c / det
+ var nd = a / det
+
+ // Apply the inverted matrix to the top right
+ var ne = -(na * e + nc * f)
+ var nf = -(nb * e + nd * f)
+
+ // Construct the inverted matrix
+ this.a = na
+ this.b = nb
+ this.c = nc
+ this.d = nd
+ this.e = ne
+ this.f = nf
+
+ return this
+ }
+
+ inverse () {
+ return this.clone().inverseO()
+ }
+
+ // Translate matrix
+ translate (x, y) {
+ return this.clone().translateO(x, y)
+ }
+
+ translateO (x, y) {
+ this.e += x || 0
+ this.f += y || 0
+ return this
+ }
+
+ // Scale matrix
+ scale (x, y, cx, cy) {
+ return this.clone().scaleO(...arguments)
+ }
+
+ scaleO (x, y = x, cx = 0, cy = 0) {
+ // Support uniform scaling
+ if (arguments.length === 3) {
+ cy = cx
+ cx = y
+ y = x
+ }
+
+ let { a, b, c, d, e, f } = this
+
+ this.a = a * x
+ this.b = b * y
+ this.c = c * x
+ this.d = d * y
+ this.e = e * x - cx * x + cx
+ this.f = f * y - cy * y + cy
+
+ return this
+ }
+
+ // Rotate matrix
+ rotate (r, cx, cy) {
+ return this.clone().rotateO(r, cx, cy)
+ }
+
+ rotateO (r, cx = 0, cy = 0) {
+ // Convert degrees to radians
+ r = radians(r)
+
+ let cos = Math.cos(r)
+ let sin = Math.sin(r)
+
+ let { a, b, c, d, e, f } = this
+
+ this.a = a * cos - b * sin
+ this.b = b * cos + a * sin
+ this.c = c * cos - d * sin
+ this.d = d * cos + c * sin
+ this.e = e * cos - f * sin + cy * sin - cx * cos + cx
+ this.f = f * cos + e * sin - cx * sin - cy * cos + cy
+
+ return this
+ }
+
+ // Flip matrix on x or y, at a given offset
+ flip (axis, around) {
+ return this.clone().flipO(axis, around)
+ }
+
+ flipO (axis, around) {
+ return axis === 'x' ? this.scaleO(-1, 1, around, 0)
+ : axis === 'y' ? this.scaleO(1, -1, 0, around)
+ : this.scaleO(-1, -1, axis, around || axis) // Define an x, y flip point
+ }
+
+ // Shear matrix
+ shear (a, cx, cy) {
+ return this.clone().shearO(a, cx, cy)
+ }
+
+ shearO (lx, cx = 0, cy = 0) {
+ let { a, b, c, d, e, f } = this
+
+ this.a = a + b * lx
+ this.c = c + d * lx
+ this.e = e + f * lx - cy * lx
+
+ return this
+ }
+
+ // Skew Matrix
+ skew (x, y, cx, cy) {
+ return this.clone().skewO(...arguments)
+ }
+
+ skewO (x, y = x, cx = 0, cy = 0) {
+ // support uniformal skew
+ if (arguments.length === 3) {
+ cy = cx
+ cx = y
+ y = x
+ }
+
+ // Convert degrees to radians
+ x = radians(x)
+ y = radians(y)
+
+ let lx = Math.tan(x)
+ let ly = Math.tan(y)
+
+ let { a, b, c, d, e, f } = this
+
+ this.a = a + b * lx
+ this.b = b + a * ly
+ this.c = c + d * lx
+ this.d = d + c * ly
+ this.e = e + f * lx - cy * lx
+ this.f = f + e * ly - cx * ly
+
+ return this
+ }
+
+ // SkewX
+ skewX (x, cx, cy) {
+ return this.skew(x, 0, cx, cy)
+ }
+
+ skewXO (x, cx, cy) {
+ return this.skewO(x, 0, cx, cy)
+ }
+
+ // SkewY
+ skewY (y, cx, cy) {
+ return this.skew(0, y, cx, cy)
+ }
+
+ skewYO (y, cx, cy) {
+ return this.skewO(0, y, cx, cy)
+ }
+
+ // Transform around a center point
+ aroundO (cx, cy, matrix) {
+ var dx = cx || 0
+ var dy = cy || 0
+ return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy)
+ }
+
+ around (cx, cy, matrix) {
+ return this.clone().aroundO(cx, cy, matrix)
+ }
+
+ // Convert to native SVGMatrix
+ native () {
+ // create new matrix
+ var matrix = parser().svg.node.createSVGMatrix()
+
+ // update with current values
+ for (var i = abcdef.length - 1; i >= 0; i--) {
+ matrix[abcdef[i]] = this[abcdef[i]]
+ }
+
+ return matrix
+ }
+
+ // Check if two matrices are equal
+ equals (other) {
+ var comp = new Matrix(other)
+ return closeEnough(this.a, comp.a) && closeEnough(this.b, comp.b) &&
+ closeEnough(this.c, comp.c) && closeEnough(this.d, comp.d) &&
+ closeEnough(this.e, comp.e) && closeEnough(this.f, comp.f)
+ }
+
+ // Convert matrix to string
+ toString () {
+ return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')'
+ }
+
+ toArray () {
+ return [this.a, this.b, this.c, this.d, this.e, this.f]
+ }
+
+ valueOf () {
+ return {
+ a: this.a,
+ b: this.b,
+ c: this.c,
+ d: this.d,
+ e: this.e,
+ f: this.f
+ }
+ }
+
+ static fromArray (a) {
+ return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] }
+ }
+
+ static isMatrixLike (o) {
+ return (
+ o.a != null ||
+ o.b != null ||
+ o.c != null ||
+ o.d != null ||
+ o.e != null ||
+ o.f != null
+ )
+ }
+
+ static formatTransforms (o) {
+ // Get all of the parameters required to form the matrix
+ var flipBoth = o.flip === 'both' || o.flip === true
+ var flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1
+ var flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1
+ var skewX = o.skew && o.skew.length ? o.skew[0]
+ : isFinite(o.skew) ? o.skew
+ : isFinite(o.skewX) ? o.skewX
+ : 0
+ var skewY = o.skew && o.skew.length ? o.skew[1]
+ : isFinite(o.skew) ? o.skew
+ : isFinite(o.skewY) ? o.skewY
+ : 0
+ var scaleX = o.scale && o.scale.length ? o.scale[0] * flipX
+ : isFinite(o.scale) ? o.scale * flipX
+ : isFinite(o.scaleX) ? o.scaleX * flipX
+ : flipX
+ var scaleY = o.scale && o.scale.length ? o.scale[1] * flipY
+ : isFinite(o.scale) ? o.scale * flipY
+ : isFinite(o.scaleY) ? o.scaleY * flipY
+ : flipY
+ var shear = o.shear || 0
+ var theta = o.rotate || o.theta || 0
+ var origin = new Point(o.origin || o.around || o.ox || o.originX, o.oy || o.originY)
+ var ox = origin.x
+ var oy = origin.y
+ var position = new Point(o.position || o.px || o.positionX, o.py || o.positionY)
+ var px = position.x
+ var py = position.y
+ var translate = new Point(o.translate || o.tx || o.translateX, o.ty || o.translateY)
+ var tx = translate.x
+ var ty = translate.y
+ var relative = new Point(o.relative || o.rx || o.relativeX, o.ry || o.relativeY)
+ var rx = relative.x
+ var ry = relative.y
+
+ // Populate all of the values
+ return {
+ scaleX, scaleY, skewX, skewY, shear, theta, rx, ry, tx, ty, ox, oy, px, py
+ }
+ }
+
+ // left matrix, right matrix, target matrix which is overwritten
+ static matrixMultiply (l, r, o) {
+ // Work out the product directly
+ var a = l.a * r.a + l.c * r.b
+ var b = l.b * r.a + l.d * r.b
+ var c = l.a * r.c + l.c * r.d
+ var d = l.b * r.c + l.d * r.d
+ var e = l.e + l.a * r.e + l.c * r.f
+ var f = l.f + l.b * r.e + l.d * r.f
+
+ // make sure to use local variables because l/r and o could be the same
+ o.a = a
+ o.b = b
+ o.c = c
+ o.d = d
+ o.e = e
+ o.f = f
+
+ return o
+ }
+}
+
+registerMethods({
+ Element: {
+ // Get current matrix
+ ctm () {
+ return new Matrix(this.node.getCTM())
+ },
+
+ // Get current screen matrix
+ screenCTM () {
+ /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537
+ This is needed because FF does not return the transformation matrix
+ for the inner coordinate system when getScreenCTM() is called on nested svgs.
+ However all other Browsers do that */
+ if (typeof this.isRoot === 'function' && !this.isRoot()) {
+ var rect = this.rect(1, 1)
+ var m = rect.node.getScreenCTM()
+ rect.remove()
+ return new Matrix(m)
+ }
+ return new Matrix(this.node.getScreenCTM())
+ }
+ }
+})
diff --git a/src/types/Morphable.js b/src/types/Morphable.js
new file mode 100644
index 0000000..2b12375
--- /dev/null
+++ b/src/types/Morphable.js
@@ -0,0 +1,244 @@
+import { Ease } from '../animation/Controller.js'
+import {
+ delimiter,
+ numberAndUnit,
+ pathLetters
+} from '../modules/core/regex.js'
+import { extend } from '../utils/adopter.js'
+import Color from './Color.js'
+import PathArray from './PathArray.js'
+import SVGArray from './SVGArray.js'
+import SVGNumber from './SVGNumber.js'
+
+export default class Morphable {
+ constructor (stepper) {
+ // FIXME: the default stepper does not know about easing
+ this._stepper = stepper || new Ease('-')
+
+ this._from = null
+ this._to = null
+ this._type = null
+ this._context = null
+ this._morphObj = null
+ }
+
+ from (val) {
+ if (val == null) {
+ return this._from
+ }
+
+ this._from = this._set(val)
+ return this
+ }
+
+ to (val) {
+ if (val == null) {
+ return this._to
+ }
+
+ this._to = this._set(val)
+ return this
+ }
+
+ type (type) {
+ // getter
+ if (type == null) {
+ return this._type
+ }
+
+ // setter
+ this._type = type
+ return this
+ }
+
+ _set (value) {
+ if (!this._type) {
+ var type = typeof value
+
+ if (type === 'number') {
+ this.type(SVGNumber)
+ } else if (type === 'string') {
+ if (Color.isColor(value)) {
+ this.type(Color)
+ } else if (delimiter.test(value)) {
+ this.type(pathLetters.test(value)
+ ? PathArray
+ : SVGArray
+ )
+ } else if (numberAndUnit.test(value)) {
+ this.type(SVGNumber)
+ } else {
+ this.type(NonMorphable)
+ }
+ } else if (morphableTypes.indexOf(value.constructor) > -1) {
+ this.type(value.constructor)
+ } else if (Array.isArray(value)) {
+ this.type(SVGArray)
+ } else if (type === 'object') {
+ this.type(ObjectBag)
+ } else {
+ this.type(NonMorphable)
+ }
+ }
+
+ var result = (new this._type(value)).toArray()
+ this._morphObj = this._morphObj || new this._type()
+ this._context = this._context ||
+ Array.apply(null, Array(result.length)).map(Object)
+ return result
+ }
+
+ stepper (stepper) {
+ if (stepper == null) return this._stepper
+ this._stepper = stepper
+ return this
+ }
+
+ done () {
+ var complete = this._context
+ .map(this._stepper.done)
+ .reduce(function (last, curr) {
+ return last && curr
+ }, true)
+ return complete
+ }
+
+ at (pos) {
+ var _this = this
+
+ return this._morphObj.fromArray(
+ this._from.map(function (i, index) {
+ return _this._stepper.step(i, _this._to[index], pos, _this._context[index], _this._context)
+ })
+ )
+ }
+}
+
+export class NonMorphable {
+ constructor (...args) {
+ this.init(...args)
+ }
+
+ init (val) {
+ val = Array.isArray(val) ? val[0] : val
+ this.value = val
+ }
+
+ valueOf () {
+ return this.value
+ }
+
+ toArray () {
+ return [this.value]
+ }
+}
+
+export class TransformBag {
+ constructor (...args) {
+ this.init(...args)
+ }
+
+ init (obj) {
+ if (Array.isArray(obj)) {
+ obj = {
+ scaleX: obj[0],
+ scaleY: obj[1],
+ shear: obj[2],
+ rotate: obj[3],
+ translateX: obj[4],
+ translateY: obj[5],
+ originX: obj[6],
+ originY: obj[7]
+ }
+ }
+
+ Object.assign(this, TransformBag.defaults, obj)
+ }
+
+ toArray () {
+ var v = this
+
+ return [
+ v.scaleX,
+ v.scaleY,
+ v.shear,
+ v.rotate,
+ v.translateX,
+ v.translateY,
+ v.originX,
+ v.originY
+ ]
+ }
+}
+
+TransformBag.defaults = {
+ scaleX: 1,
+ scaleY: 1,
+ shear: 0,
+ rotate: 0,
+ translateX: 0,
+ translateY: 0,
+ originX: 0,
+ originY: 0
+}
+
+export class ObjectBag {
+ constructor (...args) {
+ this.init(...args)
+ }
+
+ init (objOrArr) {
+ this.values = []
+
+ if (Array.isArray(objOrArr)) {
+ this.values = objOrArr
+ return
+ }
+
+ var entries = Object.entries(objOrArr || {}).sort((a, b) => {
+ return a[0] - b[0]
+ })
+
+ this.values = entries.reduce((last, curr) => last.concat(curr), [])
+ }
+
+ valueOf () {
+ var obj = {}
+ var arr = this.values
+
+ for (var i = 0, len = arr.length; i < len; i += 2) {
+ obj[arr[i]] = arr[i + 1]
+ }
+
+ return obj
+ }
+
+ toArray () {
+ return this.values
+ }
+}
+
+const morphableTypes = [
+ NonMorphable,
+ TransformBag,
+ ObjectBag
+]
+
+export function registerMorphableType (type = []) {
+ morphableTypes.push(...[].concat(type))
+}
+
+export function makeMorphable () {
+ extend(morphableTypes, {
+ to (val, args) {
+ return new Morphable()
+ .type(this.constructor)
+ .from(this.valueOf())
+ .to(val, args)
+ },
+ fromArray (arr) {
+ this.init(arr)
+ return this
+ }
+ })
+}
diff --git a/src/patharray.js b/src/types/PathArray.js
index 4432df3..989cd8f 100644
--- a/src/patharray.js
+++ b/src/types/PathArray.js
@@ -1,6 +1,62 @@
-/* globals arrayToString, pathRegReplace */
+import {
+ delimiter,
+ dots,
+ hyphen,
+ isPathLetter,
+ numbersWithDots,
+ pathLetters
+} from '../modules/core/regex.js'
+import { extend } from '../utils/adopter.js'
+import { subClassArray } from './ArrayPolyfill.js'
+import Point from './Point.js'
+import SVGArray from './SVGArray.js'
+import parser from '../modules/core/parser.js'
+
+const PathArray = subClassArray('PathArray', SVGArray)
+
+export default PathArray
+
+export function pathRegReplace (a, b, c, d) {
+ return c + d.replace(dots, ' .')
+}
-var pathHandlers = {
+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 + ' '
+}
+
+const pathHandlers = {
M: function (c, p, p0) {
p.x = p0.x = c[0]
p.y = p0.y = c[1]
@@ -52,7 +108,7 @@ var pathHandlers = {
}
}
-var mlhvqtcsaz = 'mlhvqtcsaz'.split('')
+let mlhvqtcsaz = 'mlhvqtcsaz'.split('')
for (var i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
pathHandlers[mlhvqtcsaz[i]] = (function (i) {
@@ -73,27 +129,14 @@ for (var i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
})(mlhvqtcsaz[i].toUpperCase())
}
-// Path points array
-SVG.PathArray = function (array, fallback) {
- SVG.Array.call(this, array, fallback || [['M', 0, 0]])
-}
-
-// Inherit from SVG.Array
-SVG.PathArray.prototype = new SVG.Array()
-SVG.PathArray.prototype.constructor = SVG.PathArray
-
-SVG.extend(SVG.PathArray, {
+extend(PathArray, {
// Convert array to string
- toString: function () {
- return arrayToString(this.value)
- },
- toArray: function () {
- return this.value.reduce(function (prev, curr) {
- return [].concat.call(prev, curr)
- }, [])
+ toString () {
+ return arrayToString(this)
},
+
// Move path string
- move: function (x, y) {
+ move (x, y) {
// get bounding box of current situation
var box = this.bbox()
@@ -103,91 +146,94 @@ SVG.extend(SVG.PathArray, {
if (!isNaN(x) && !isNaN(y)) {
// move every point
- for (var l, i = this.value.length - 1; i >= 0; i--) {
- l = this.value[i][0]
+ for (var l, i = this.length - 1; i >= 0; i--) {
+ l = this[i][0]
if (l === 'M' || l === 'L' || l === 'T') {
- this.value[i][1] += x
- this.value[i][2] += y
+ this[i][1] += x
+ this[i][2] += y
} else if (l === 'H') {
- this.value[i][1] += x
+ this[i][1] += x
} else if (l === 'V') {
- this.value[i][1] += y
+ this[i][1] += y
} else if (l === 'C' || l === 'S' || l === 'Q') {
- this.value[i][1] += x
- this.value[i][2] += y
- this.value[i][3] += x
- this.value[i][4] += y
+ this[i][1] += x
+ this[i][2] += y
+ this[i][3] += x
+ this[i][4] += y
if (l === 'C') {
- this.value[i][5] += x
- this.value[i][6] += y
+ this[i][5] += x
+ this[i][6] += y
}
} else if (l === 'A') {
- this.value[i][6] += x
- this.value[i][7] += y
+ this[i][6] += x
+ this[i][7] += y
}
}
}
return this
},
+
// Resize path string
- size: function (width, height) {
+ size (width, height) {
// get bounding box of current situation
var box = this.bbox()
var i, l
// recalculate position of all points according to new size
- for (i = this.value.length - 1; i >= 0; i--) {
- l = this.value[i][0]
+ for (i = this.length - 1; i >= 0; i--) {
+ l = this[i][0]
if (l === 'M' || l === 'L' || l === 'T') {
- this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
- this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y
+ this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x
+ this[i][2] = ((this[i][2] - box.y) * height) / box.height + box.y
} else if (l === 'H') {
- this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
+ this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x
} else if (l === 'V') {
- this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y
+ this[i][1] = ((this[i][1] - box.y) * height) / box.height + box.y
} else if (l === 'C' || l === 'S' || l === 'Q') {
- this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
- this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y
- this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x
- this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y
+ this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x
+ this[i][2] = ((this[i][2] - box.y) * height) / box.height + box.y
+ this[i][3] = ((this[i][3] - box.x) * width) / box.width + box.x
+ this[i][4] = ((this[i][4] - box.y) * height) / box.height + box.y
if (l === 'C') {
- this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x
- this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y
+ this[i][5] = ((this[i][5] - box.x) * width) / box.width + box.x
+ this[i][6] = ((this[i][6] - box.y) * height) / box.height + box.y
}
} else if (l === 'A') {
// resize radii
- this.value[i][1] = (this.value[i][1] * width) / box.width
- this.value[i][2] = (this.value[i][2] * height) / box.height
+ this[i][1] = (this[i][1] * width) / box.width
+ this[i][2] = (this[i][2] * height) / box.height
// move position values
- this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x
- this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y
+ this[i][6] = ((this[i][6] - box.x) * width) / box.width + box.x
+ this[i][7] = ((this[i][7] - box.y) * height) / box.height + box.y
}
}
return this
},
+
// Test if the passed path array use the same path data commands as this path array
- equalCommands: function (pathArray) {
+ equalCommands (pathArray) {
var i, il, equalCommands
- pathArray = new SVG.PathArray(pathArray)
+ pathArray = new PathArray(pathArray)
- equalCommands = this.value.length === pathArray.value.length
- for (i = 0, il = this.value.length; equalCommands && i < il; i++) {
- equalCommands = this.value[i][0] === pathArray.value[i][0]
+ equalCommands = this.length === pathArray.length
+ for (i = 0, il = this.length; equalCommands && i < il; i++) {
+ equalCommands = this[i][0] === pathArray[i][0]
}
return equalCommands
},
+
// Make path array morphable
- morph: function (pathArray) {
- pathArray = new SVG.PathArray(pathArray)
+ morph (pathArray) {
+ pathArray = new PathArray(pathArray)
if (this.equalCommands(pathArray)) {
this.destination = pathArray
@@ -197,15 +243,16 @@ SVG.extend(SVG.PathArray, {
return this
},
+
// Get morphed path array at given position
- at: function (pos) {
+ at (pos) {
// make sure a destination is defined
if (!this.destination) return this
- var sourceArray = this.value
+ var sourceArray = this
var destinationArray = this.destination.value
var array = []
- var pathArray = new SVG.PathArray()
+ var pathArray = new PathArray()
var i, il, j, jl
// Animate has specified in the SVG spec
@@ -230,10 +277,11 @@ SVG.extend(SVG.PathArray, {
pathArray.value = array
return pathArray
},
+
// Absolutize and parse path to array
- parse: function (array) {
+ parse (array = [['M', 0, 0]]) {
// if it's already a patharray, no need to parse it
- if (array instanceof SVG.PathArray) return array.valueOf()
+ if (array instanceof PathArray) return array
// prepare for parsing
var s
@@ -241,11 +289,11 @@ SVG.extend(SVG.PathArray, {
if (typeof array === 'string') {
array = array
- .replace(SVG.regex.numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123
- .replace(SVG.regex.pathLetters, ' $& ') // put some room between letters and numbers
- .replace(SVG.regex.hyphen, '$1 -') // add space before hyphen
- .trim() // trim
- .split(SVG.regex.delimiter) // split into array
+ .replace(numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123
+ .replace(pathLetters, ' $& ') // put some room between letters and numbers
+ .replace(hyphen, '$1 -') // add space before hyphen
+ .trim() // trim
+ .split(delimiter) // split into array
} else {
array = array.reduce(function (prev, curr) {
return [].concat.call(prev, curr)
@@ -254,14 +302,14 @@ SVG.extend(SVG.PathArray, {
// array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...]
var result = []
- var p = new SVG.Point()
- var p0 = new SVG.Point()
+ var p = new Point()
+ var p0 = new Point()
var index = 0
var len = array.length
do {
// Test if we have a path letter
- if (SVG.regex.isPathLetter.test(array[index])) {
+ if (isPathLetter.test(array[index])) {
s = array[index]
++index
// If last letter was a move command and we got no new, it defaults to [L]ine
@@ -272,18 +320,18 @@ SVG.extend(SVG.PathArray, {
}
result.push(pathHandlers[s].call(null,
- array.slice(index, (index = index + paramCnt[s.toUpperCase()])).map(parseFloat),
- p, p0
- )
+ array.slice(index, (index = index + paramCnt[s.toUpperCase()])).map(parseFloat),
+ p, p0
+ )
)
} while (len > index)
return result
},
+
// Get bounding box of path
- bbox: function () {
- SVG.parser().path.setAttribute('d', this.toString())
- return SVG.parser.nodes.path.getBBox()
+ bbox () {
+ parser().path.setAttribute('d', this.toString())
+ return parser.nodes.path.getBBox()
}
-
})
diff --git a/src/types/Point.js b/src/types/Point.js
new file mode 100644
index 0000000..0adcd90
--- /dev/null
+++ b/src/types/Point.js
@@ -0,0 +1,54 @@
+import { registerMethods } from '../utils/methods.js'
+import parser from '../modules/core/parser.js'
+
+export default class Point {
+ // Initialize
+ constructor (x, y, base) {
+ let source
+ base = base || { x: 0, y: 0 }
+
+ // ensure source as object
+ source = Array.isArray(x) ? { x: x[0], y: x[1] }
+ : typeof x === 'object' ? { x: x.x, y: x.y }
+ : { x: x, y: y }
+
+ // merge source
+ this.x = source.x == null ? base.x : source.x
+ this.y = source.y == null ? base.y : source.y
+ }
+
+ // Clone point
+ clone () {
+ return new Point(this)
+ }
+
+ // Convert to native SVGPoint
+ native () {
+ // create new point
+ var point = parser().svg.node.createSVGPoint()
+
+ // update with current values
+ point.x = this.x
+ point.y = this.y
+ return point
+ }
+
+ // transform point with matrix
+ transform (m) {
+ // Perform the matrix multiplication
+ var x = m.a * this.x + m.c * this.y + m.e
+ var y = m.b * this.x + m.d * this.y + m.f
+
+ // Return the required point
+ return new Point(x, y)
+ }
+}
+
+registerMethods({
+ Element: {
+ // Get point
+ point: function (x, y) {
+ return new Point(x, y).transform(this.screenCTM().inverse())
+ }
+ }
+})
diff --git a/src/types/PointArray.js b/src/types/PointArray.js
new file mode 100644
index 0000000..b246b2f
--- /dev/null
+++ b/src/types/PointArray.js
@@ -0,0 +1,120 @@
+import { delimiter } from '../modules/core/regex.js'
+import { extend } from '../utils/adopter.js'
+import { subClassArray } from './ArrayPolyfill.js'
+import SVGArray from './SVGArray.js'
+
+const PointArray = subClassArray('PointArray', SVGArray)
+
+export default PointArray
+
+extend(PointArray, {
+ // Convert array to string
+ toString () {
+ // convert to a poly point string
+ for (var i = 0, il = this.length, array = []; i < il; i++) {
+ array.push(this[i].join(','))
+ }
+
+ return array.join(' ')
+ },
+
+ // Convert array to line object
+ toLine () {
+ return {
+ x1: this[0][0],
+ y1: this[0][1],
+ x2: this[1][0],
+ y2: this[1][1]
+ }
+ },
+
+ // Get morphed array at given position
+ at (pos) {
+ // make sure a destination is defined
+ if (!this.destination) return this
+
+ // generate morphed point string
+ for (var i = 0, il = this.length, array = []; i < il; i++) {
+ array.push([
+ this[i][0] + (this.destination[i][0] - this[i][0]) * pos,
+ this[i][1] + (this.destination[i][1] - this[i][1]) * pos
+ ])
+ }
+
+ return new PointArray(array)
+ },
+
+ // Parse point string and flat array
+ parse (array = [[0, 0]]) {
+ var points = []
+
+ // if it is an array
+ if (array instanceof Array) {
+ // and it is not flat, there is no need to parse it
+ if (array[0] instanceof Array) {
+ return array
+ }
+ } else { // Else, it is considered as a string
+ // parse points
+ array = array.trim().split(delimiter).map(parseFloat)
+ }
+
+ // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints
+ // Odd number of coordinates is an error. In such cases, drop the last odd coordinate.
+ if (array.length % 2 !== 0) array.pop()
+
+ // wrap points in two-tuples and parse points as floats
+ for (var i = 0, len = array.length; i < len; i = i + 2) {
+ points.push([ array[i], array[i + 1] ])
+ }
+
+ return points
+ },
+
+ // Move point string
+ move (x, y) {
+ var box = this.bbox()
+
+ // get relative offset
+ x -= box.x
+ y -= box.y
+
+ // move every point
+ if (!isNaN(x) && !isNaN(y)) {
+ for (var i = this.length - 1; i >= 0; i--) {
+ this[i] = [this[i][0] + x, this[i][1] + y]
+ }
+ }
+
+ return this
+ },
+
+ // Resize poly string
+ size (width, height) {
+ var i
+ var box = this.bbox()
+
+ // recalculate position of all points according to new size
+ for (i = this.length - 1; i >= 0; i--) {
+ if (box.width) this[i][0] = ((this[i][0] - box.x) * width) / box.width + box.x
+ if (box.height) this[i][1] = ((this[i][1] - box.y) * height) / box.height + box.y
+ }
+
+ return this
+ },
+
+ // Get bounding box of points
+ bbox () {
+ var maxX = -Infinity
+ var maxY = -Infinity
+ var minX = Infinity
+ var minY = Infinity
+ this.forEach(function (el) {
+ maxX = Math.max(el[0], maxX)
+ maxY = Math.max(el[1], maxY)
+ minX = Math.min(el[0], minX)
+ minY = Math.min(el[1], minY)
+ })
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }
+ }
+})
diff --git a/src/types/SVGArray.js b/src/types/SVGArray.js
new file mode 100644
index 0000000..3894b22
--- /dev/null
+++ b/src/types/SVGArray.js
@@ -0,0 +1,47 @@
+import { delimiter } from '../modules/core/regex.js'
+import { extend } from '../utils/adopter.js'
+import { subClassArray } from './ArrayPolyfill.js'
+
+const SVGArray = subClassArray('SVGArray', Array, function (arr) {
+ this.init(arr)
+})
+
+export default SVGArray
+
+extend(SVGArray, {
+ init (arr) {
+ this.length = 0
+ this.push(...this.parse(arr))
+ },
+
+ toArray () {
+ return Array.prototype.concat.apply([], this)
+ },
+
+ toString () {
+ return this.join(' ')
+ },
+
+ // Flattens the array if needed
+ valueOf () {
+ const ret = []
+ ret.push(...this)
+ return ret
+ },
+
+ // Parse whitespace separated string
+ parse (array = []) {
+ // If already is an array, no need to parse it
+ if (array instanceof Array) return array
+
+ return array.trim().split(delimiter).map(parseFloat)
+ },
+
+ clone () {
+ return new this.constructor(this)
+ },
+
+ toSet () {
+ return new Set(this)
+ }
+})
diff --git a/src/types/SVGNumber.js b/src/types/SVGNumber.js
new file mode 100644
index 0000000..bba9741
--- /dev/null
+++ b/src/types/SVGNumber.js
@@ -0,0 +1,87 @@
+import { numberAndUnit } from '../modules/core/regex.js'
+
+// Module for unit convertions
+export default class SVGNumber {
+ // Initialize
+ constructor (...args) {
+ this.init(...args)
+ }
+
+ init (value, unit) {
+ unit = Array.isArray(value) ? value[1] : unit
+ value = Array.isArray(value) ? value[0] : value
+
+ // initialize defaults
+ this.value = 0
+ this.unit = unit || ''
+
+ // parse value
+ if (typeof value === 'number') {
+ // ensure a valid numeric value
+ this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value
+ } else if (typeof value === 'string') {
+ unit = value.match(numberAndUnit)
+
+ if (unit) {
+ // make value numeric
+ this.value = parseFloat(unit[1])
+
+ // normalize
+ if (unit[5] === '%') { this.value /= 100 } else if (unit[5] === 's') {
+ this.value *= 1000
+ }
+
+ // store unit
+ this.unit = unit[5]
+ }
+ } else {
+ if (value instanceof SVGNumber) {
+ this.value = value.valueOf()
+ this.unit = value.unit
+ }
+ }
+ }
+
+ toString () {
+ return (this.unit === '%' ? ~~(this.value * 1e8) / 1e6
+ : this.unit === 's' ? this.value / 1e3
+ : this.value
+ ) + this.unit
+ }
+
+ toJSON () {
+ return this.toString()
+ }
+
+ toArray () {
+ return [this.value, this.unit]
+ }
+
+ valueOf () {
+ return this.value
+ }
+
+ // Add number
+ plus (number) {
+ number = new SVGNumber(number)
+ return new SVGNumber(this + number, this.unit || number.unit)
+ }
+
+ // Subtract number
+ minus (number) {
+ number = new SVGNumber(number)
+ return new SVGNumber(this - number, this.unit || number.unit)
+ }
+
+ // Multiply number
+ times (number) {
+ number = new SVGNumber(number)
+ return new SVGNumber(this * number, this.unit || number.unit)
+ }
+
+ // Divide number
+ divide (number) {
+ number = new SVGNumber(number)
+ return new SVGNumber(this / number, this.unit || number.unit)
+ }
+}
diff --git a/src/types/set.js b/src/types/set.js
new file mode 100644
index 0000000..c755c2c
--- /dev/null
+++ b/src/types/set.js
@@ -0,0 +1,18 @@
+/* eslint no-unused-vars: "off" */
+class SVGSet extends Set {
+ // constructor (arr) {
+ // super(arr)
+ // }
+
+ each (cbOrName, ...args) {
+ if (typeof cbOrName === 'function') {
+ this.forEach((el) => { cbOrName.call(el, el) })
+ } else {
+ this.forEach((el) => {
+ el[cbOrName](...args)
+ })
+ }
+
+ return this
+ }
+}
diff --git a/src/umd.js b/src/umd.js
deleted file mode 100644
index bb8e300..0000000
--- a/src/umd.js
+++ /dev/null
@@ -1,28 +0,0 @@
-
-(function(root, factory) {
- /* istanbul ignore next */
- if (typeof define === 'function' && define.amd) {
- define(function(){
- return factory(root, root.document)
- })
- } else if (typeof exports === 'object') {
- module.exports = root.document ? factory(root, root.document) : function(w){ return factory(w, w.document) }
- } else {
- root.SVG = factory(root, root.document)
- }
-}(typeof window !== "undefined" ? window : this, function(window, document) {
-
-// Check that our browser supports svg
-var supported = !! document.createElementNS &&
- !! document.createElementNS('http://www.w3.org/2000/svg','svg').createSVGRect
-
-// If we don't support svg, just exit without doing anything
-if (!supported)
- return {supported: false}
-
-// Otherwise, the library will be here
-<%= contents %>
-
-return SVG
-
-}));
diff --git a/src/use.js b/src/use.js
deleted file mode 100644
index 2b8e65e..0000000
--- a/src/use.js
+++ /dev/null
@@ -1,25 +0,0 @@
-
-SVG.Use = SVG.invent({
- // Initialize node
- create: 'use',
-
- // Inherit from
- inherit: SVG.Shape,
-
- // Add class methods
- extend: {
- // Use element as a reference
- element: function (element, file) {
- // Set lined element
- return this.attr('href', (file || '') + '#' + element, SVG.xlink)
- }
- },
-
- // Add parent method
- construct: {
- // Create a use element
- use: function (element, file) {
- return this.put(new SVG.Use()).element(element, file)
- }
- }
-})
diff --git a/src/utilities.js b/src/utilities.js
deleted file mode 100644
index 01b8ba5..0000000
--- a/src/utilities.js
+++ /dev/null
@@ -1,43 +0,0 @@
-
-SVG.utils = {
- // Map function
- map: function (array, block) {
- var i
- var il = array.length
- var result = []
-
- for (i = 0; i < il; i++) {
- result.push(block(array[i]))
- }
-
- return result
- },
-
- // Filter function
- filter: function (array, block) {
- var i
- var il = array.length
- var result = []
-
- for (i = 0; i < il; i++) {
- if (block(array[i])) { result.push(array[i]) }
- }
-
- return result
- },
-
- // Degrees to radians
- radians: function (d) {
- return d % 360 * Math.PI / 180
- },
-
- // Radians to degrees
- degrees: function (r) {
- return r * 180 / Math.PI % 360
- },
-
- filterSVGElements: function (nodes) {
- return this.filter(nodes, function (el) { return el instanceof window.SVGElement })
- }
-
-}
diff --git a/src/utils/adopter.js b/src/utils/adopter.js
new file mode 100644
index 0000000..8017359
--- /dev/null
+++ b/src/utils/adopter.js
@@ -0,0 +1,115 @@
+import { capitalize } from './utils.js'
+import { ns } from '../modules/core/namespaces.js'
+import Base from '../types/Base.js'
+
+const elements = {}
+export const root = Symbol('root')
+
+// Method for element creation
+export function makeNode (name) {
+ // create element
+ return document.createElementNS(ns, name)
+}
+
+export function makeInstance (element) {
+ if (element instanceof Base) return element
+
+ if (typeof element === 'object') {
+ return adopt(element)
+ }
+
+ if (element == null) {
+ return new elements[root]()
+ }
+
+ if (typeof element === 'string' && element.charAt(0) !== '<') {
+ return adopt(document.querySelector(element))
+ }
+
+ var node = makeNode('svg')
+ node.innerHTML = element
+
+ // We can use firstChild here because we know,
+ // that the first char is < and thus an element
+ element = adopt(node.firstChild)
+
+ return element
+}
+
+export function nodeOrNew (name, node) {
+ return node || makeNode(name)
+}
+
+// Adopt existing svg elements
+export function adopt (node) {
+ // check for presence of node
+ if (!node) return null
+
+ // make sure a node isn't already adopted
+ if (node.instance instanceof Base) return node.instance
+
+ if (!(node instanceof window.SVGElement)) {
+ return new elements.HtmlNode(node)
+ }
+
+ // initialize variables
+ var element
+
+ // adopt with element-specific settings
+ if (node.nodeName === 'svg') {
+ element = new elements[root](node)
+ } else if (node.nodeName === 'linearGradient' || node.nodeName === 'radialGradient') {
+ element = new elements.Gradient(node)
+ } else if (elements[capitalize(node.nodeName)]) {
+ element = new elements[capitalize(node.nodeName)](node)
+ } else {
+ element = new elements.Bare(node)
+ }
+
+ return element
+}
+
+export function register (element, name = element.name, asRoot = false) {
+ elements[name] = element
+ if (asRoot) elements[root] = element
+ return element
+}
+
+export function getClass (name) {
+ return elements[name]
+}
+
+// Element id sequence
+let did = 1000
+
+// Get next named element id
+export function eid (name) {
+ return 'Svgjs' + capitalize(name) + (did++)
+}
+
+// Deep new id assignment
+export function assignNewId (node) {
+ // do the same for SVG child nodes as well
+ for (var i = node.children.length - 1; i >= 0; i--) {
+ assignNewId(node.children[i])
+ }
+
+ if (node.id) {
+ return adopt(node).id(eid(node.nodeName))
+ }
+
+ return adopt(node)
+}
+
+// Method for extending objects
+export function extend (modules, methods) {
+ var key, i
+
+ modules = Array.isArray(modules) ? modules : [modules]
+
+ for (i = modules.length - 1; i >= 0; i--) {
+ for (key in methods) {
+ modules[i].prototype[key] = methods[key]
+ }
+ }
+}
diff --git a/src/utils/methods.js b/src/utils/methods.js
new file mode 100644
index 0000000..2373445
--- /dev/null
+++ b/src/utils/methods.js
@@ -0,0 +1,32 @@
+const methods = {}
+const constructors = {}
+
+export function registerMethods (name, m) {
+ if (Array.isArray(name)) {
+ for (let _name of name) {
+ registerMethods(_name, m)
+ }
+ return
+ }
+
+ if (typeof name === 'object') {
+ for (let [_name, _m] of Object.entries(name)) {
+ registerMethods(_name, _m)
+ }
+ return
+ }
+
+ methods[name] = Object.assign(methods[name] || {}, m)
+}
+
+export function getMethodsFor (name) {
+ return methods[name] || {}
+}
+
+export function registerConstructor (name, setup) {
+ constructors[name] = setup
+}
+
+export function getConstructor (name) {
+ return constructors[name] ? { setup: constructors[name], name } : {}
+}
diff --git a/src/utils/utils.js b/src/utils/utils.js
new file mode 100644
index 0000000..e3c9111
--- /dev/null
+++ b/src/utils/utils.js
@@ -0,0 +1,96 @@
+// Map function
+export function map (array, block) {
+ var i
+ var il = array.length
+ var result = []
+
+ for (i = 0; i < il; i++) {
+ result.push(block(array[i]))
+ }
+
+ return result
+}
+
+// Filter function
+export function filter (array, block) {
+ var i
+ var il = array.length
+ var result = []
+
+ for (i = 0; i < il; i++) {
+ if (block(array[i])) { result.push(array[i]) }
+ }
+
+ return result
+}
+
+// Degrees to radians
+export function radians (d) {
+ return d % 360 * Math.PI / 180
+}
+
+// Radians to degrees
+export function degrees (r) {
+ return r * 180 / Math.PI % 360
+}
+
+// Convert dash-separated-string to camelCase
+export function camelCase (s) {
+ return s.toLowerCase().replace(/-(.)/g, function (m, g) {
+ return g.toUpperCase()
+ })
+}
+
+// Capitalize first letter of a string
+export function capitalize (s) {
+ return s.charAt(0).toUpperCase() + s.slice(1)
+}
+
+// Calculate proportional width and height values when necessary
+export function proportionalSize (element, width, height) {
+ if (width == null || height == null) {
+ var box = element.bbox()
+
+ if (width == null) {
+ width = box.width / box.height * height
+ } else if (height == null) {
+ height = box.height / box.width * width
+ }
+ }
+
+ return {
+ width: width,
+ height: height
+ }
+}
+
+export function getOrigin (o, element) {
+ // Allow origin or around as the names
+ let origin = o.origin // o.around == null ? o.origin : o.around
+ let ox, oy
+
+ // Allow the user to pass a string to rotate around a given point
+ if (typeof origin === 'string' || origin == null) {
+ // Get the bounding box of the element with no transformations applied
+ const string = (origin || 'center').toLowerCase().trim()
+ const { height, width, x, y } = element.bbox()
+
+ // Calculate the transformed x and y coordinates
+ let bx = string.includes('left') ? x
+ : string.includes('right') ? x + width
+ : x + width / 2
+ let by = string.includes('top') ? y
+ : string.includes('bottom') ? y + height
+ : y + height / 2
+
+ // Set the bounds eg : "bottom-left", "Top right", "middle" etc...
+ ox = o.ox != null ? o.ox : bx
+ oy = o.oy != null ? o.oy : by
+ } else {
+ ox = origin[0]
+ oy = origin[1]
+ }
+
+ // Return the origin as it is if it wasn't a string
+ return [ ox, oy ]
+}