--- /dev/null
+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)
+ }
+
+})
--- /dev/null
+/* 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\r
+ 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
+ }
+}
--- /dev/null
+
+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))
+ }
+})
--- /dev/null
+/* 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))
+ }
+})
--- /dev/null
+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')
+ }
+
+})
--- /dev/null
+/* 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)
+}
--- /dev/null
+SVG.Container = SVG.invent({
+ // Initialize node
+ create: function (node) {
+ SVG.Element.call(this, node)
+ },
+
+ // Inherit from
+ inherit: SVG.Parent
+})
--- /dev/null
+
+// 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')
+ }
+})
--- /dev/null
+SVG.Defs = SVG.invent({
+ // Initialize node
+ create: 'defs',
+
+ // Inherit from
+ inherit: SVG.Container
+})
--- /dev/null
+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())
+ }
+ }
+})
--- /dev/null
+/* 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 (svg && 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 {
+ // 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
+ }
+ }
+})
--- /dev/null
+/* 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))
+ }
+})
--- /dev/null
+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
+ }
+ }
+})
--- /dev/null
+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())
+ }
+ }
+})
--- /dev/null
+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
+ }
+ }
+})
--- /dev/null
+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)
+ }
+ }
+})
--- /dev/null
+/* 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]
+ )
+ }
+ }
+})
--- /dev/null
+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)
+ }
+})
--- /dev/null
+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')
+ }
+})
--- /dev/null
+/* 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)
+// }
--- /dev/null
+
+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
+ }
+})
--- /dev/null
+/* 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
+ }
+ }
+
+})
--- /dev/null
+/* 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())
+ }
+ }
+})
--- /dev/null
+/* globals arrayToString, pathRegReplace */
+
+var pathHandlers = {
+ M: function (c, p, p0) {
+ p.x = p0.x = c[0]
+ p.y = p0.y = c[1]
+
+ return ['M', p.x, p.y]
+ },
+ L: function (c, p) {
+ p.x = c[0]
+ p.y = c[1]
+ return ['L', c[0], c[1]]
+ },
+ H: function (c, p) {
+ p.x = c[0]
+ return ['H', c[0]]
+ },
+ V: function (c, p) {
+ p.y = c[0]
+ return ['V', c[0]]
+ },
+ C: function (c, p) {
+ p.x = c[4]
+ p.y = c[5]
+ return ['C', c[0], c[1], c[2], c[3], c[4], c[5]]
+ },
+ S: function (c, p) {
+ p.x = c[2]
+ p.y = c[3]
+ return ['S', c[0], c[1], c[2], c[3]]
+ },
+ Q: function (c, p) {
+ p.x = c[2]
+ p.y = c[3]
+ return ['Q', c[0], c[1], c[2], c[3]]
+ },
+ T: function (c, p) {
+ p.x = c[0]
+ p.y = c[1]
+ return ['T', c[0], c[1]]
+ },
+ Z: function (c, p, p0) {
+ p.x = p0.x
+ p.y = p0.y
+ return ['Z']
+ },
+ A: function (c, p) {
+ p.x = c[5]
+ p.y = c[6]
+ return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]]
+ }
+}
+
+var mlhvqtcsaz = 'mlhvqtcsaz'.split('')
+
+for (var i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
+ pathHandlers[mlhvqtcsaz[i]] = (function (i) {
+ return function (c, p, p0) {
+ if (i === 'H') c[0] = c[0] + p.x
+ else if (i === 'V') c[0] = c[0] + p.y
+ else if (i === 'A') {
+ c[5] = c[5] + p.x
+ c[6] = c[6] + p.y
+ } else {
+ for (var j = 0, jl = c.length; j < jl; ++j) {
+ c[j] = c[j] + (j % 2 ? p.y : p.x)
+ }
+ }
+
+ return pathHandlers[i](c, p, p0)
+ }
+ })(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, {
+ // Convert array to string
+ toString: function () {
+ return arrayToString(this.value)
+ },
+ toArray: function () {
+ return this.value.reduce(function (prev, curr) {
+ return [].concat.call(prev, curr)
+ }, [])
+ },
+ // Move path string
+ move: function (x, y) {
+ // get bounding box of current situation
+ var box = this.bbox()
+
+ // get relative offset
+ x -= box.x
+ y -= box.y
+
+ if (!isNaN(x) && !isNaN(y)) {
+ // move every point
+ for (var l, i = this.value.length - 1; i >= 0; i--) {
+ l = this.value[i][0]
+
+ if (l === 'M' || l === 'L' || l === 'T') {
+ this.value[i][1] += x
+ this.value[i][2] += y
+ } else if (l === 'H') {
+ this.value[i][1] += x
+ } else if (l === 'V') {
+ this.value[i][1] += y
+ } else if (l === 'C' || l === 'S' || l === 'Q') {
+ this.value[i][1] += x
+ this.value[i][2] += y
+ this.value[i][3] += x
+ this.value[i][4] += y
+
+ if (l === 'C') {
+ this.value[i][5] += x
+ this.value[i][6] += y
+ }
+ } else if (l === 'A') {
+ this.value[i][6] += x
+ this.value[i][7] += y
+ }
+ }
+ }
+
+ return this
+ },
+ // Resize path string
+ size: function (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]
+
+ if (l === 'M' || l === 'L' || l === 'T') {
+ this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
+ this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y
+ } else if (l === 'H') {
+ this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
+ } else if (l === 'V') {
+ this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y
+ } else if (l === 'C' || l === 'S' || l === 'Q') {
+ this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
+ this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y
+ this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x
+ this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y
+
+ if (l === 'C') {
+ this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x
+ this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y
+ }
+ } else if (l === 'A') {
+ // resize radii
+ this.value[i][1] = (this.value[i][1] * width) / box.width
+ this.value[i][2] = (this.value[i][2] * height) / box.height
+
+ // move position values
+ this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x
+ this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y
+ }
+ }
+
+ return this
+ },
+ // Test if the passed path array use the same path data commands as this path array
+ equalCommands: function (pathArray) {
+ var i, il, equalCommands
+
+ pathArray = new SVG.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]
+ }
+
+ return equalCommands
+ },
+ // Make path array morphable
+ morph: function (pathArray) {
+ pathArray = new SVG.PathArray(pathArray)
+
+ if (this.equalCommands(pathArray)) {
+ this.destination = pathArray
+ } else {
+ this.destination = null
+ }
+
+ return this
+ },
+ // Get morphed path array at given position
+ at: function (pos) {
+ // make sure a destination is defined
+ if (!this.destination) return this
+
+ var sourceArray = this.value
+ var destinationArray = this.destination.value
+ var array = []
+ var pathArray = new SVG.PathArray()
+ var i, il, j, jl
+
+ // Animate has specified in the SVG spec
+ // See: https://www.w3.org/TR/SVG11/paths.html#PathElement
+ for (i = 0, il = sourceArray.length; i < il; i++) {
+ array[i] = [sourceArray[i][0]]
+ for (j = 1, jl = sourceArray[i].length; j < jl; j++) {
+ array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos
+ }
+ // For the two flags of the elliptical arc command, the SVG spec say:
+ // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true
+ // Elliptical arc command as an array followed by corresponding indexes:
+ // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
+ // 0 1 2 3 4 5 6 7
+ if (array[i][0] === 'A') {
+ array[i][4] = +(array[i][4] !== 0)
+ array[i][5] = +(array[i][5] !== 0)
+ }
+ }
+
+ // Directly modify the value of a path array, this is done this way for performance
+ pathArray.value = array
+ return pathArray
+ },
+ // Absolutize and parse path to array
+ parse: function (array) {
+ // if it's already a patharray, no need to parse it
+ if (array instanceof SVG.PathArray) return array.valueOf()
+
+ // prepare for parsing
+ var s
+ var paramCnt = { 'M': 2, 'L': 2, 'H': 1, 'V': 1, 'C': 6, 'S': 4, 'Q': 4, 'T': 2, 'A': 7, 'Z': 0 }
+
+ 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
+ } else {
+ array = array.reduce(function (prev, curr) {
+ return [].concat.call(prev, curr)
+ }, [])
+ }
+
+ // 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 index = 0
+ var len = array.length
+
+ do {
+ // Test if we have a path letter
+ if (SVG.regex.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
+ } else if (s === 'M') {
+ s = 'L'
+ } else if (s === 'm') {
+ s = 'l'
+ }
+
+ result.push(pathHandlers[s].call(null,
+ 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()
+ }
+
+})
--- /dev/null
+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'
+ })
+ }
+
+})
--- /dev/null
+
+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())
+ }
+})
--- /dev/null
+
+// 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}
+ }
+})
--- /dev/null
+/* 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))
+ }
+})
--- /dev/null
+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
+ }
+ }
+})
--- /dev/null
+
+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)
+ }
+ }
+})
--- /dev/null
+/* 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
+ }
+})
--- /dev/null
+/* 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
+ }
+})
--- /dev/null
+
+// 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)
+ }
+ }
+})
--- /dev/null
+
+SVG.Shape = SVG.invent({
+ // Initialize node
+ create: function (node) {
+ SVG.Element.call(this, node)
+ },
+
+ // Inherit from
+ inherit: SVG.Element
+})
--- /dev/null
+
+SVG.Symbol = SVG.invent({
+ // Initialize node
+ create: 'symbol',
+
+ // Inherit from
+ inherit: SVG.Container,
+
+ construct: {
+ // create symbol
+ symbol: function () {
+ return this.put(new SVG.Symbol())
+ }
+ }
+})
--- /dev/null
+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()
+ }
+})
--- /dev/null
+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
+})
--- /dev/null
+
+// 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
+ }
+ }
+})
+++ /dev/null
-/* 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\r
- 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
- }
-}
+++ /dev/null
-/* 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
- }
-})
+++ /dev/null
-
-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))
- }
-})
+++ /dev/null
-/* 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))
- }
-})
+++ /dev/null
-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')
- }
-
-})
+++ /dev/null
-/* 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)
-}
+++ /dev/null
-SVG.Container = SVG.invent({
- // Initialize node
- create: function (node) {
- SVG.Element.call(this, node)
- },
-
- // Inherit from
- inherit: SVG.Parent
-})
+++ /dev/null
-
-// 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')
- }
-})
+++ /dev/null
-SVG.Defs = SVG.invent({
- // Initialize node
- create: 'defs',
-
- // Inherit from
- inherit: SVG.Container
-})
+++ /dev/null
-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())
- }
- }
-})
+++ /dev/null
-/* 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 (svg && 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 {
- // 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
- }
- }
-})
+++ /dev/null
-/* 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))
- }
-})
+++ /dev/null
-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
- }
- }
-})
+++ /dev/null
-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
- }
- }
-})
+++ /dev/null
-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())
- }
- }
-})
+++ /dev/null
-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)
- }
-
-})
+++ /dev/null
-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)
- }
- }
-})
+++ /dev/null
-/* 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]
- )
- }
- }
-})
+++ /dev/null
-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)
- }
-})
+++ /dev/null
-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')
- }
-})
+++ /dev/null
-/* 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)
-// }
+++ /dev/null
-
-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
- }
-})
+++ /dev/null
-
-// 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)
- }
- }
-})
+++ /dev/null
-/* 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
- }
- }
-
-})
+++ /dev/null
-/* 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())
- }
- }
-})
+++ /dev/null
-/* globals arrayToString, pathRegReplace */
-
-var pathHandlers = {
- M: function (c, p, p0) {
- p.x = p0.x = c[0]
- p.y = p0.y = c[1]
-
- return ['M', p.x, p.y]
- },
- L: function (c, p) {
- p.x = c[0]
- p.y = c[1]
- return ['L', c[0], c[1]]
- },
- H: function (c, p) {
- p.x = c[0]
- return ['H', c[0]]
- },
- V: function (c, p) {
- p.y = c[0]
- return ['V', c[0]]
- },
- C: function (c, p) {
- p.x = c[4]
- p.y = c[5]
- return ['C', c[0], c[1], c[2], c[3], c[4], c[5]]
- },
- S: function (c, p) {
- p.x = c[2]
- p.y = c[3]
- return ['S', c[0], c[1], c[2], c[3]]
- },
- Q: function (c, p) {
- p.x = c[2]
- p.y = c[3]
- return ['Q', c[0], c[1], c[2], c[3]]
- },
- T: function (c, p) {
- p.x = c[0]
- p.y = c[1]
- return ['T', c[0], c[1]]
- },
- Z: function (c, p, p0) {
- p.x = p0.x
- p.y = p0.y
- return ['Z']
- },
- A: function (c, p) {
- p.x = c[5]
- p.y = c[6]
- return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]]
- }
-}
-
-var mlhvqtcsaz = 'mlhvqtcsaz'.split('')
-
-for (var i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
- pathHandlers[mlhvqtcsaz[i]] = (function (i) {
- return function (c, p, p0) {
- if (i === 'H') c[0] = c[0] + p.x
- else if (i === 'V') c[0] = c[0] + p.y
- else if (i === 'A') {
- c[5] = c[5] + p.x
- c[6] = c[6] + p.y
- } else {
- for (var j = 0, jl = c.length; j < jl; ++j) {
- c[j] = c[j] + (j % 2 ? p.y : p.x)
- }
- }
-
- return pathHandlers[i](c, p, p0)
- }
- })(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, {
- // Convert array to string
- toString: function () {
- return arrayToString(this.value)
- },
- toArray: function () {
- return this.value.reduce(function (prev, curr) {
- return [].concat.call(prev, curr)
- }, [])
- },
- // Move path string
- move: function (x, y) {
- // get bounding box of current situation
- var box = this.bbox()
-
- // get relative offset
- x -= box.x
- y -= box.y
-
- if (!isNaN(x) && !isNaN(y)) {
- // move every point
- for (var l, i = this.value.length - 1; i >= 0; i--) {
- l = this.value[i][0]
-
- if (l === 'M' || l === 'L' || l === 'T') {
- this.value[i][1] += x
- this.value[i][2] += y
- } else if (l === 'H') {
- this.value[i][1] += x
- } else if (l === 'V') {
- this.value[i][1] += y
- } else if (l === 'C' || l === 'S' || l === 'Q') {
- this.value[i][1] += x
- this.value[i][2] += y
- this.value[i][3] += x
- this.value[i][4] += y
-
- if (l === 'C') {
- this.value[i][5] += x
- this.value[i][6] += y
- }
- } else if (l === 'A') {
- this.value[i][6] += x
- this.value[i][7] += y
- }
- }
- }
-
- return this
- },
- // Resize path string
- size: function (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]
-
- if (l === 'M' || l === 'L' || l === 'T') {
- this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
- this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y
- } else if (l === 'H') {
- this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
- } else if (l === 'V') {
- this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y
- } else if (l === 'C' || l === 'S' || l === 'Q') {
- this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
- this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y
- this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x
- this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y
-
- if (l === 'C') {
- this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x
- this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y
- }
- } else if (l === 'A') {
- // resize radii
- this.value[i][1] = (this.value[i][1] * width) / box.width
- this.value[i][2] = (this.value[i][2] * height) / box.height
-
- // move position values
- this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x
- this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y
- }
- }
-
- return this
- },
- // Test if the passed path array use the same path data commands as this path array
- equalCommands: function (pathArray) {
- var i, il, equalCommands
-
- pathArray = new SVG.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]
- }
-
- return equalCommands
- },
- // Make path array morphable
- morph: function (pathArray) {
- pathArray = new SVG.PathArray(pathArray)
-
- if (this.equalCommands(pathArray)) {
- this.destination = pathArray
- } else {
- this.destination = null
- }
-
- return this
- },
- // Get morphed path array at given position
- at: function (pos) {
- // make sure a destination is defined
- if (!this.destination) return this
-
- var sourceArray = this.value
- var destinationArray = this.destination.value
- var array = []
- var pathArray = new SVG.PathArray()
- var i, il, j, jl
-
- // Animate has specified in the SVG spec
- // See: https://www.w3.org/TR/SVG11/paths.html#PathElement
- for (i = 0, il = sourceArray.length; i < il; i++) {
- array[i] = [sourceArray[i][0]]
- for (j = 1, jl = sourceArray[i].length; j < jl; j++) {
- array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos
- }
- // For the two flags of the elliptical arc command, the SVG spec say:
- // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true
- // Elliptical arc command as an array followed by corresponding indexes:
- // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
- // 0 1 2 3 4 5 6 7
- if (array[i][0] === 'A') {
- array[i][4] = +(array[i][4] !== 0)
- array[i][5] = +(array[i][5] !== 0)
- }
- }
-
- // Directly modify the value of a path array, this is done this way for performance
- pathArray.value = array
- return pathArray
- },
- // Absolutize and parse path to array
- parse: function (array) {
- // if it's already a patharray, no need to parse it
- if (array instanceof SVG.PathArray) return array.valueOf()
-
- // prepare for parsing
- var s
- var paramCnt = { 'M': 2, 'L': 2, 'H': 1, 'V': 1, 'C': 6, 'S': 4, 'Q': 4, 'T': 2, 'A': 7, 'Z': 0 }
-
- 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
- } else {
- array = array.reduce(function (prev, curr) {
- return [].concat.call(prev, curr)
- }, [])
- }
-
- // 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 index = 0
- var len = array.length
-
- do {
- // Test if we have a path letter
- if (SVG.regex.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
- } else if (s === 'M') {
- s = 'L'
- } else if (s === 'm') {
- s = 'l'
- }
-
- result.push(pathHandlers[s].call(null,
- 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()
- }
-
-})
+++ /dev/null
-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'
- })
- }
-
-})
+++ /dev/null
-
-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())
- }
-})
+++ /dev/null
-
-// 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}
- }
-})
+++ /dev/null
-/* 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))
- }
-})
+++ /dev/null
-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
- }
- }
-})
+++ /dev/null
-
-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)
- }
- }
-})
+++ /dev/null
-/* 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
- }
-})
+++ /dev/null
-
-SVG.Shape = SVG.invent({
- // Initialize node
- create: function (node) {
- SVG.Element.call(this, node)
- },
-
- // Inherit from
- inherit: SVG.Element
-})
+++ /dev/null
-
-SVG.Symbol = SVG.invent({
- // Initialize node
- create: 'symbol',
-
- // Inherit from
- inherit: SVG.Container,
-
- construct: {
- // create symbol
- symbol: function () {
- return this.put(new SVG.Symbol())
- }
- }
-})
+++ /dev/null
-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()
- }
-})
+++ /dev/null
-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
-})
+++ /dev/null
-
-// 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
- }
- }
-})