diff options
author | Ulrich-Matthias Schäfer <ulima.ums@googlemail.com> | 2018-10-27 20:43:35 +0200 |
---|---|---|
committer | Ulrich-Matthias Schäfer <ulima.ums@googlemail.com> | 2018-10-27 20:43:35 +0200 |
commit | 1c75fcaf02ceb144152d59557643c6fdd7264065 (patch) | |
tree | 5184af75f2fd27ca6b81c24a06b1676d17ca2c76 | |
parent | b1b776a710d0ce0a6259043b8ce0665e205195fa (diff) | |
download | svg.js-1c75fcaf02ceb144152d59557643c6fdd7264065.tar.gz svg.js-1c75fcaf02ceb144152d59557643c6fdd7264065.zip |
resolve circular references and make example working again
-rw-r--r-- | abilities | 156 | ||||
-rw-r--r-- | dirty.html | 15 | ||||
-rw-r--r-- | package.json | 3 | ||||
-rw-r--r-- | src/A.js | 39 | ||||
-rw-r--r-- | src/Animator.js | 4 | ||||
-rw-r--r-- | src/Bare.js | 23 | ||||
-rw-r--r-- | src/Base.js | 14 | ||||
-rw-r--r-- | src/Box.js | 41 | ||||
-rw-r--r-- | src/Circle.js | 25 | ||||
-rw-r--r-- | src/ClipPath.js | 62 | ||||
-rw-r--r-- | src/Color.js | 9 | ||||
-rw-r--r-- | src/Container.js | 2 | ||||
-rw-r--r-- | src/Controller.js | 1 | ||||
-rw-r--r-- | src/Defs.js | 9 | ||||
-rw-r--r-- | src/Doc.js | 134 | ||||
-rw-r--r-- | src/Element.js | 395 | ||||
-rw-r--r-- | src/Ellipse.js | 19 | ||||
-rw-r--r-- | src/EventTarget.js | 38 | ||||
-rw-r--r-- | src/G.js | 19 | ||||
-rw-r--r-- | src/Gradient.js | 37 | ||||
-rw-r--r-- | src/HtmlNode.js | 7 | ||||
-rw-r--r-- | src/Image.js | 21 | ||||
-rw-r--r-- | src/Line.js | 29 | ||||
-rw-r--r-- | src/Marker.js | 82 | ||||
-rw-r--r-- | src/Mask.js | 58 | ||||
-rw-r--r-- | src/Matrix.js | 53 | ||||
-rw-r--r-- | src/Morphable.js | 25 | ||||
-rw-r--r-- | src/Parent.js | 194 | ||||
-rw-r--r-- | src/Path.js | 22 | ||||
-rw-r--r-- | src/PathArray.js | 85 | ||||
-rw-r--r-- | src/Pattern.js | 47 | ||||
-rw-r--r-- | src/Point.js | 13 | ||||
-rw-r--r-- | src/Polygon.js | 59 | ||||
-rw-r--r-- | src/Polyline.js | 28 | ||||
-rw-r--r-- | src/Rect.js | 25 | ||||
-rw-r--r-- | src/Runner.js | 108 | ||||
-rw-r--r-- | src/SVGArray.js | 8 | ||||
-rw-r--r-- | src/SVGNumber.js | 6 | ||||
-rw-r--r-- | src/Shape.js | 3 | ||||
-rw-r--r-- | src/Stop.js | 6 | ||||
-rw-r--r-- | src/Symbol.js | 17 | ||||
-rw-r--r-- | src/Text.js | 103 | ||||
-rw-r--r-- | src/TextPath.js | 77 | ||||
-rw-r--r-- | src/Timeline.js | 17 | ||||
-rw-r--r-- | src/Tspan.js | 43 | ||||
-rw-r--r-- | src/Use.js | 18 | ||||
-rw-r--r-- | src/adopter.js | 56 | ||||
-rw-r--r-- | src/attr.js | 15 | ||||
-rw-r--r-- | src/classes.js | 5 | ||||
-rw-r--r-- | src/containers.js | 11 | ||||
-rw-r--r-- | src/css.js | 100 | ||||
-rw-r--r-- | src/data.js | 45 | ||||
-rw-r--r-- | src/default.js | 51 | ||||
-rw-r--r-- | src/elements.js | 24 | ||||
-rw-r--r-- | src/event.js | 16 | ||||
-rw-r--r-- | src/flatten.js | 42 | ||||
-rw-r--r-- | src/fx.js | 1368 | ||||
-rw-r--r-- | src/helpers.js | 30 | ||||
-rw-r--r-- | src/memory.js | 61 | ||||
-rw-r--r-- | src/parser.js | 32 | ||||
-rw-r--r-- | src/poly.js | 30 | ||||
-rw-r--r-- | src/selector.js | 11 | ||||
-rw-r--r-- | src/set.js | 17 | ||||
-rw-r--r-- | src/svg.js | 87 | ||||
-rw-r--r-- | src/textable.js | 35 | ||||
-rw-r--r-- | src/tools.js | 48 | ||||
-rw-r--r-- | src/utilities.js | 43 |
67 files changed, 1641 insertions, 2685 deletions
diff --git a/abilities b/abilities new file mode 100644 index 0000000..4d5aa11 --- /dev/null +++ b/abilities @@ -0,0 +1,156 @@ + + +>> Element << + Stop + Bare + >> Parent << + Text + TextPath + >> Container<< + Doc + G + Symbol + Defs + ClipPath + Mask + A + Gradient + Shape + Rect + Circle + Path + Ellipse + Polygon + Polyline + Line + Image + +Animate +Box +Color +Controller + + +===================================================================== + + +classes: + + // MetaData + Title + + // Parents + SVG + G + + // Elements + Rect + Circle + Path + Ellipse + Polygon + Polyline + Line + Image + + // Text Stuff + TSpan + TextPath + + // Data Type + Box + Matrix + SVGNumber + SVGArray + PointArray + PathArray + Color + Controller + +abilities: + Animate + Container + Event + Doc + Movements + Dom + Create + + + + +// DocAbility.js + +import SVG from 'Svg.js' +export default function ( ...worksOn ) { + let workSet = new Set(worksOn) + return { + + doc: function () { + return this.parent(SVG) + } + + } +} + +-> svg.js -> DocAbility.js -> 'Svg.js' + +extend ( [ Rect ], DocAbility() ) + + + +// DomAbility.js + +import {makeInstance} from 'helpers.js' + +export default function ( ...worksOn ) { + let workSet = new Set(worksOn) + let maker = makeInstance(workSet) + + return { + + addTo (parent) { + return maker(parent).put(this) + } + + } +} + +class Rect { + + + static tagName = 'Rect' +} + + + + +new SVG[capitalize[node.nodeName]] + + + +new SVG.Doc() -> <svg xml="">......</svg> + +// SVG.js + +extend( [ ...Parents ], Container(...Elements, ...Parents) ) ) +extend( [ Text ], Container( TSpan, TextPath ) ) + + + +Element.js + +new Element () + +export default class Element { + + static + + static + +} + +Rect.js + +import Element from Element.js +export default class Rect extends Element @@ -4,14 +4,7 @@ <head> <meta charset="utf-8"> <title></title> - <script type="text/javascript" src="dist/svg.js"></script> - <script type="text/javascript" src="src/helpers.js"></script> - <script type="text/javascript" src="src/transform.js"></script> - <script type="text/javascript" src="src/matrix.js"></script> - <script type="text/javascript" src="src/morph.js"></script> - <script type="text/javascript" src="src/runner.js"></script> - <script type="text/javascript" src="src/timeline.js"></script> - <script type="text/javascript" src="src/controller.js"></script> + <!--<script type="text/javascript" src="dist/svg.js"></script>--> @@ -30,7 +23,11 @@ </svg> <!-- Modifying the svg --> -<script> +<script type="module"> + +import SVG from './src/svg.js' + +window.SVG = SVG let rect = SVG('rect').hide() let {sin, PI: pi, round, sqrt} = Math diff --git a/package.json b/package.json index c718bec..645cdd3 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,8 @@ "build:dev": "gulp --dont-break", "test": "karma start .config/karma.conf.js --single-run", "test:dots": "karma start .config/karma.conf.js --single-run --reporters dots", - "test:quick": "karma start .config/karma.quick.js" + "test:quick": "karma start .config/karma.quick.js", + "server": "http-server ./ -d" }, "devDependencies": { "@babel/core": "^7.1.2", @@ -1,10 +1,10 @@ -import {Container, Element} from './classes.js' -import {nodeOrNew, addFactory} from './tools.js' +import Base from './Base.js' +import {nodeOrNew} from './tools.js' import {xlink} from './namespaces.js' -export default class A extends Container { +export default class A extends Base{ constructor (node) { - super(nodeOrNew('a', node)) + super(nodeOrNew('a', node), A) } // Link url @@ -18,22 +18,23 @@ export default class A extends Container { } } -addFactory(Container, { - // Create a hyperlink element - link: function (url) { - return this.put(new A()).to(url) - } -}) +A.constructors = { + Container: { + // Create a hyperlink element + link: function (url) { + return this.put(new A()).to(url) + } + }, + Element: { + // Create a hyperlink element + linkTo: function (url) { + var link = new A() -addFactory(Element, { - // Create a hyperlink element - linkTo: function (url) { - var link = new A() + if (typeof url === 'function') { url.call(link, link) } else { + link.to(url) + } - if (typeof url === 'function') { url.call(link, link) } else { - link.to(url) + return this.parent().put(link).put(this) } - - return this.parent().put(link).put(this) } -}) +} diff --git a/src/Animator.js b/src/Animator.js index d49ab12..eca6ee3 100644 --- a/src/Animator.js +++ b/src/Animator.js @@ -1,6 +1,6 @@ import Queue from './Queue.js' -export default { +const Animator = { nextDraw: null, frames: new Queue(), timeouts: new Queue(), @@ -81,3 +81,5 @@ export default { : null } } + +export default Animator diff --git a/src/Bare.js b/src/Bare.js index 783fa6a..b70f329 100644 --- a/src/Bare.js +++ b/src/Bare.js @@ -1,10 +1,9 @@ import {nodeOrNew} from './tools.js' -import Parent from './Parent.js' -export default function Bare (element, inherit) { - return class Custom extends inherit { +export default function Bare (element, inherit = {}) { + let custom = class Custom extends inherit { constructor (node) { - super(nodeOrNew(element, node)) + super(nodeOrNew(element, node), Custom) } words (text) { @@ -19,16 +18,18 @@ export default function Bare (element, inherit) { return this } } -} -export let constructors = { - // Create an element that is not described by SVG.js - element: function (element, inherit) { - let custom = createCustom(element, inherit) - return this.put(new custom()) - } + extend(custom, inherit) } +// export let constructors = { +// // Create an element that is not described by SVG.js +// element: function (element, inherit) { +// let custom = createCustom(element, inherit) +// return this.put(new custom()) +// } +// } + // extend(Parent, { // // Create an element that is not described by SVG.js // element: function (element, inherit) { diff --git a/src/Base.js b/src/Base.js new file mode 100644 index 0000000..6b1242b --- /dev/null +++ b/src/Base.js @@ -0,0 +1,14 @@ +export default class Base { + constructor (node, {extensions = []}) { + this.tags = [] + + for (let extension of extensions) { + extension.setup.call(this, node) + this.tags.push(extension.name) + } + } + + is (ability) { + return this.tags.includes(ability) + } +} @@ -1,11 +1,16 @@ -import {Parent, Doc, Symbol, Image, Pattern, Marker, Point} from './classes.js' +//import {Parent, Doc, Symbol, Image, Pattern, Marker, Point} from './classes.js' +import Point from './Point.js' import parser from './parser.js' import {fullBox, domContains, isNulledBox} from './helpers.js' import {extend} from './tools.js' import {delimiter} from './regex.js' export default class Box { - constructor (source) { + constructor (...args) { + this.init(...args) + } + + init (source) { var base = [0, 0, 0, 0] source = typeof source === 'string' ? source.split(delimiter).map(parseFloat) : Array.isArray(source) ? source @@ -77,20 +82,6 @@ export default class Box { } } - -extend(Parent, { - // Get bounding box - bbox () { - return new Box(getBox((node) => node.getBBox())) - }, - - rbox (el) { - let box = new Box(getBox((node) => node.getBoundingClientRect())) - if (el) return box.transform(el.screenCTM().inverse()) - return box.addOffset() - } -}) - function getBox(cb) { let box @@ -106,14 +97,26 @@ function getBox(cb) { box = cb(clone.node) clone.remove() } catch (e) { + throw (e) console.warn('Getting a bounding box of this element is not possible') } } return box } - -extend([Doc, Symbol, Image, Pattern, Marker], { +Box.constructors = { + Element: { + // Get bounding box + bbox () { + return new Box(getBox.call(this, (node) => node.getBBox())) + }, + + rbox (el) { + let box = new Box(getBox.call(this, (node) => node.getBoundingClientRect())) + if (el) return box.transform(el.screenCTM().inverse()) + return box.addOffset() + } + }, viewbox: function (x, y, width, height) { // act as getter if (x == null) return new Box(this.attr('viewBox')) @@ -121,4 +124,4 @@ extend([Doc, Symbol, Image, Pattern, Marker], { // act as setter return this.attr('viewBox', new Box(x, y, width, height)) } -}) +} diff --git a/src/Circle.js b/src/Circle.js index fc8be72..6d4b12b 100644 --- a/src/Circle.js +++ b/src/Circle.js @@ -1,10 +1,11 @@ -import SVGNumber from './SVGNumber.js' -import Parent from './Parent.js' +import Base from './Base.js' +import {nodeOrNew, extend} from './tools.js' import {x, y, cx, cy, width, height, size} from './circled.js' +import SVGNumber from './SVGNumber.js' -export default class Circle extends Shape { +export default class Circle extends Base { constructor (node) { - super(nodeOrNew('circle', node)) + super(nodeOrNew('circle', node), Circle) } radius (r) { @@ -24,11 +25,13 @@ export default class Circle extends Shape { extend(Circle, {x, y, cx, cy, width, height, size}) -addFactory(Parent, { - // Create circle element - circle (size) { - return this.put(new Circle()) - .radius(new SVGNumber(size).divide(2)) - .move(0, 0) +Circle.constructors = { + Element: { + // Create circle element + circle (size) { + return this.put(new Circle()) + .radius(new SVGNumber(size).divide(2)) + .move(0, 0) + } } -}) +} diff --git a/src/ClipPath.js b/src/ClipPath.js index ef820e5..486b30c 100644 --- a/src/ClipPath.js +++ b/src/ClipPath.js @@ -1,11 +1,11 @@ -import Container from './Container.js' -import Element from './Element.js' +import Base from './Base.js' import {nodeOrNew, extend} from './tools.js' import find from './selector.js' +import {remove} from './Element.js' -export default class ClipPath extends Container { +export default class ClipPath extends Base { constructor (node) { - super(nodeOrNew('clipPath', node)) + super(nodeOrNew('clipPath', node), ClipPath) } // Unclip all clipped elements and remove itself @@ -16,7 +16,7 @@ export default class ClipPath extends Container { }) // remove clipPath from parent - return super.remove() + return remove.call(this) } targets () { @@ -24,31 +24,33 @@ export default class ClipPath extends Container { } } -addFactory(Container, { - // Create clipping element - clip: function() { - return this.defs().put(new ClipPath) - } -}) - -extend(Element, { - // Distribute clipPath to svg element - clipWith (element) { - // use given clip or create a new one - let clipper = element instanceof ClipPath - ? element - : this.parent().clip().add(element) - - // apply mask - return this.attr('clip-path', 'url("#' + clipper.id() + '")') - }, - // Unclip element - unclip () { - return this.attr('clip-path', null) +ClipPath.constructors = { + Container: { + // Create clipping element + clip: function() { + return this.defs().put(new ClipPath) + } }, - - clipper () { - return this.reference('clip-path') + Element: { + // Distribute clipPath to svg element + clipWith (element) { + // use given clip or create a new one + let clipper = element instanceof ClipPath + ? element + : this.parent().clip().add(element) + + // apply mask + return this.attr('clip-path', 'url("#' + clipper.id() + '")') + }, + + // Unclip element + unclip () { + return this.attr('clip-path', null) + }, + + clipper () { + return this.reference('clip-path') + } } -}) +} diff --git a/src/Color.js b/src/Color.js index 1e2befb..de65750 100644 --- a/src/Color.js +++ b/src/Color.js @@ -29,10 +29,15 @@ SVG.hsl() SVG.lab('rgb(100, 100, 100)') */ -import {isHex, isRgb, whitespace, rgb} from './regex.js' +import {isHex, isRgb, whitespace, rgb, hex} from './regex.js' +import {fullHex, compToHex} from './helpers.js' export default class Color { - constructor (color, g, b) { + constructor (...args) { + this.init(...args) + } + + init (color, g, b) { let match // initialize defaults diff --git a/src/Container.js b/src/Container.js deleted file mode 100644 index 5d6dc43..0000000 --- a/src/Container.js +++ /dev/null @@ -1,2 +0,0 @@ -import Parent from './Parent.js' -export default class Container extends Parent {} diff --git a/src/Controller.js b/src/Controller.js index 81f6721..a48d946 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -1,5 +1,6 @@ import {timeline} from './defaults.js' +import {extend} from './tools.js' /*** Base Class diff --git a/src/Defs.js b/src/Defs.js index 21b6703..e8c5ac2 100644 --- a/src/Defs.js +++ b/src/Defs.js @@ -1,8 +1,11 @@ -import Container from './Container.js' +import Base from './Base.js' import {nodeOrNew} from './tools.js' -export default class Defs extends Container { +export default class Defs extends Base { constructor (node) { - super(nodeOrNew('defs', node)) + super(nodeOrNew('defs', node), Defs) } + + flatten () { return this } + ungroup () { return this } } @@ -1,75 +1,87 @@ -import Container from './Container.js' -import Parent from './Parent.js' -import {adopt, extend} from './tools.js' -import {ns, xlink, xmlns, svgjs} from './namespaces.js' +import Base from './Base.js' +import Defs from './Defs.js' +import { extend, nodeOrNew } from './tools.js' +import { ns, xlink, xmlns, svgjs } from './namespaces.js' +import {adopt} from './adopter.js' +import * as EventTarget from './EventTarget.js' +import * as Element from './Element.js' +import * as Parent from './Parent.js' -export default class Doc extends Container { - constructor (node) { - super(nodeOrNew('svg', node)) - this.namespace() - } +export default class Doc extends Base { + constructor(node) { + super(nodeOrNew('svg', node), Doc) + this.namespace() + } + + isRoot() { + return !this.node.parentNode + || !(this.node.parentNode instanceof window.SVGElement) + || this.node.parentNode.nodeName === '#document' + } - isRoot () { - return !this.node.parentNode || !(this.node.parentNode instanceof window.SVGElement) || this.node.parentNode.nodeName === '#document' - } + // Check if this is a root svg + // If not, call docs from this element + doc() { + if (this.isRoot()) return this + return Element.doc.call(this) + } - // Check if this is a root svg - // If not, call docs from this element - doc () { - if (this.isRoot()) return this - return super.doc() - } + // Add namespaces + namespace() { + if (!this.isRoot()) return this.doc().namespace() + return this + .attr({ xmlns: ns, version: '1.1' }) + .attr('xmlns:xlink', xlink, xmlns) + .attr('xmlns:svgjs', svgjs, xmlns) + } - // Add namespaces - namespace () { - if (!this.isRoot()) return this.doc().namespace() - return this - .attr({ xmlns: ns, version: '1.1' }) - .attr('xmlns:xlink', xlink, xmlns) - .attr('xmlns:svgjs', svgjs, xmlns) - } + // Creates and returns defs element + defs() { + if (!this.isRoot()) return this.doc().defs() - // Creates and returns defs element - defs () { - if (!this.isRoot()) return this.doc().defs() - return adopt(this.node.getElementsByTagName('defs')[0]) || + if (!this.isRoot()) return this.doc().defs() + return adopt(this.node.getElementsByTagName('defs')[0]) || this.put(new Defs()) - } + } - // custom parent method - parent (type) { - if (this.isRoot()) { - return this.node.parentNode.nodeName === '#document' ? null : this.node.parentNode - } + // custom parent method + parent(type) { + if (this.isRoot()) { + return this.node.parentNode.nodeName === '#document' + ? null + : this.node.parentNode + } - return super.parent(type) - } + return Element.parent.call(this, type) + } - // Removes the doc from the DOM - remove () { - if (!this.isRoot()) { - return super.remove() - } + // Removes the doc from the DOM + remove() { + if (!this.isRoot()) { + return Element.remove.call(this) + } - if (this.parent()) { - this.parent().removeChild(this.node) - } + if (this.parent()) { + this.parent().removeChild(this.node) + } - return this - } + return this + } - clear () { - // remove children - while (this.node.hasChildNodes()) { - this.node.removeChild(this.node.lastChild) - } - return this - } + clear() { + // remove children + while (this.node.hasChildNodes()) { + this.node.removeChild(this.node.lastChild) + } + return this + } } -addFactory(Container, { - // Create nested svg document - nested () { - return this.put(new Doc()) - } -}) +extend(Doc, [EventTarget, Element, Parent]) + +// addFactory(Container, { +// // Create nested svg document +// nested() { +// return this.put(new Doc()) +// } +// }) diff --git a/src/Element.js b/src/Element.js index 7667563..bed762c 100644 --- a/src/Element.js +++ b/src/Element.js @@ -1,302 +1,271 @@ -import {proportionalSize, assignNewId, makeInstance, matches} from './helpers.js' +import {proportionalSize, assignNewId, matcher} from './helpers.js' +import {makeInstance, adopt} from './adopter.js' import {eid} from './tools.js' import {delimiter} from './regex.js' import {ns} from './namespaces.js' -import {adopt} from './tools.js' -// import {Doc, EventTarget, Parent} from './classes.js' -import EventTarget from './EventTarget.js' import Doc from './Doc.js' -import Parent from './Parent.js' - -export default class Element extends EventTarget { - constructor (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')) || {}) - } - } +import SVGNumber from './SVGNumber.js' + +export const name = 'Element' + +export function setup (node) { + // initialize data object + this.dom = {} + + // create circular reference + this.node = node + + this.type = node.nodeName + this.node.instance = this + + if (node.hasAttribute('svgjs:data')) { + // pull svgjs data from the dom (getAttributeNS doesn't work in html5) + this.setData(JSON.parse(node.getAttribute('svgjs:data')) || {}) } +} // Move over x-axis - x (x) { - return this.attr('x', x) - } +export function x (x) { + return this.attr('x', x) +} // Move over y-axis - y (y) { - return this.attr('y', y) - } +export function y (y) { + return this.attr('y', y) +} // Move by center over x-axis - cx (x) { - return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2) - } +export function cx (x) { + return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2) +} // Move by center over y-axis - cy (y) { - return y == null - ? this.y() + this.height() / 2 - : this.y(y - this.height() / 2) - } +export function cy (y) { + return y == null + ? this.y() + this.height() / 2 + : this.y(y - this.height() / 2) +} // Move element to given x and y values - move (x, y) { - return this.x(x).y(y) - } +export function move (x, y) { + return this.x(x).y(y) +} // Move element by its center - center (x, y) { - return this.cx(x).cy(y) - } +export function center (x, y) { + return this.cx(x).cy(y) +} // Set width of element - width (width) { - return this.attr('width', width) - } +export function width (width) { + return this.attr('width', width) +} // Set height of element - height (height) { - return this.attr('height', height) - } +export function height (height) { + return this.attr('height', height) +} // Set element size to given width and height - size (width, height) { - let p = proportionalSize(this, width, height) +export function size (width, height) { + let p = proportionalSize(this, width, height) - return this - .width(new SVGNumber(p.width)) - .height(new SVGNumber(p.height)) - } + return this + .width(new SVGNumber(p.width)) + .height(new SVGNumber(p.height)) +} // Clone element - clone (parent) { - // write dom data to the dom so the clone can pickup the data - this.writeDataToDom() +export function clone (parent) { + // write dom data to the dom so the clone can pickup the data + this.writeDataToDom() - // clone element and assign new id - let clone = assignNewId(this.node.cloneNode(true)) + // clone element and assign new id + let clone = assignNewId(this.node.cloneNode(true)) - // insert the clone in the given parent or after myself - if (parent) parent.add(clone) - else this.after(clone) + // insert the clone in the given parent or after myself + if (parent) parent.add(clone) + else this.after(clone) - return clone - } + return clone +} // Remove element - remove () { - if (this.parent()) { this.parent().removeElement(this) } +export function remove () { + if (this.parent()) { this.parent().removeElement(this) } - return this - } + return this +} // Replace element - replace (element) { - this.after(element).remove() +export function replace (element) { + this.after(element).remove() - return element - } + return element +} // Add element to given container and return self - addTo (parent) { - return makeInstance(parent).put(this) - } +export function addTo (parent) { + return makeInstance(parent).put(this) +} // Add element to given container and return container - putIn (parent) { - return makeInstance(parent).add(this) - } +export function putIn (parent) { + return makeInstance(parent).add(this) +} // Get / set id - id (id) { - // generate new id if no id set - if (typeof id === 'undefined' && !this.node.id) { - this.node.id = eid(this.type) - } - - // dont't set directly width this.node.id to make `null` work correctly - return this.attr('id', id) +export function id (id) { + // generate new id if no id set + if (typeof id === 'undefined' && !this.node.id) { + this.node.id = eid(this.type) } + // dont't set directly width this.node.id to make `null` work correctly + return this.attr('id', id) +} + // Checks whether the given point inside the bounding box of the element - inside (x, y) { - let box = this.bbox() +export function inside (x, y) { + let box = this.bbox() - return x > box.x && - y > box.y && - x < box.x + box.width && - y < box.y + box.height - } + return x > box.x && + y > box.y && + x < box.x + box.width && + y < box.y + box.height +} // Return id on string conversion - toString () { - return this.id() - } +export function toString () { + return this.id() +} // Return array of classes on the node - classes () { - var attr = this.attr('class') - return attr == null ? [] : attr.trim().split(delimiter) - } +export function classes () { + var attr = this.attr('class') + return attr == null ? [] : attr.trim().split(delimiter) +} // Return true if class exists on the node, false otherwise - hasClass (name) { - return this.classes().indexOf(name) !== -1 - } +export function hasClass (name) { + return this.classes().indexOf(name) !== -1 +} // Add class to the node - addClass (name) { - if (!this.hasClass(name)) { - var array = this.classes() - array.push(name) - this.attr('class', array.join(' ')) - } - - return this +export function addClass (name) { + if (!this.hasClass(name)) { + var array = this.classes() + array.push(name) + this.attr('class', array.join(' ')) } + return this +} + // Remove class from the node - removeClass (name) { - if (this.hasClass(name)) { - this.attr('class', this.classes().filter(function (c) { - return c !== name - }).join(' ')) - } - - return this +export function removeClass (name) { + if (this.hasClass(name)) { + this.attr('class', this.classes().filter(function (c) { + return c !== name + }).join(' ')) } + return this +} + // Toggle the presence of a class on the node - toggleClass (name) { - return this.hasClass(name) ? this.removeClass(name) : this.addClass(name) - } +export function toggleClass (name) { + return this.hasClass(name) ? this.removeClass(name) : this.addClass(name) +} - // FIXME: getIdFromReference - // Get referenced element form attribute value - reference (attr) { - return get(this.attr(attr)) - } +// FIXME: getIdFromReference +// Get referenced element form attribute value +export function reference (attr) { + return get(this.attr(attr)) +} // Returns the parent element instance - parent (type) { - var parent = this +export function parent (type) { + var parent = this - // check for parent - if (!parent.node.parentNode) return null + // check for parent + if (!parent.node.parentNode) return null - // get parent element - parent = adopt(parent.node.parentNode) + // get parent element + parent = adopt(parent.node.parentNode) - if (!type) return parent + if (!type) return parent - // loop trough ancestors if type is given - while (parent && parent.node instanceof window.SVGElement) { - if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent - parent = adopt(parent.node.parentNode) - } + // loop trough ancestors if type is given + while (parent && parent.node instanceof window.SVGElement) { + if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent + parent = adopt(parent.node.parentNode) } +} // Get parent document - doc () { - let p = this.parent(Doc) - return p && p.doc() - } +export function doc () { + let p = this.parent(Doc) + return p && p.doc() +} // Get defs - defs () { - return this.doc().defs() - } +export function defs () { + return this.doc().defs() +} // return array of all ancestors of given type up to the root svg - parents (type) { - let parents = [] - let parent = this +export function parents (type) { + let parents = [] + let parent = this - do { - parent = parent.parent(type) - if (!parent || !parent.node) break + do { + parent = parent.parent(type) + if (!parent || !parent.node) break - parents.push(parent) - } while (parent.parent) + parents.push(parent) + } while (parent.parent) - return parents - } + return parents +} // matches the element vs a css selector - matches (selector) { - return matches(this.node, selector) - } +export function matches (selector) { + return matches(this.node, selector) +} // Returns the svg node to call native svg methods on it - native () { - return this.node - } +export function native () { + return this.node +} // Import raw svg - svg (svg) { - var well, len - - // act as a setter if svg is given - if (svg && this instanceof Parent) { - // create temporary holder - well = document.createElementNS(ns, 'svg') - // dump raw svg - well.innerHTML = svg - - // transplant nodes - for (len = well.children.length; len--;) { - this.node.appendChild(well.firstElementChild) - } - - // otherwise act as a getter - } else { - // write svgjs data to the dom - this.writeDataToDom() - - return this.node.outerHTML - } - - return this - } +export function svg () { + // write svgjs data to the dom + this.writeDataToDom() + + return this.node.outerHTML +} // write svgjs data to the dom - writeDataToDom () { - // dump variables recursively - if (this.is(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 +export function 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 (o) { - this.dom = o - return this - } +export function setData (o) { + this.dom = o + return this +} - getEventTarget () { - return this.node - } +export function getEventTarget () { + return this.node } + +export {default as attr} from './attr.js' diff --git a/src/Ellipse.js b/src/Ellipse.js index 2a6fc51..cb025bb 100644 --- a/src/Ellipse.js +++ b/src/Ellipse.js @@ -1,17 +1,18 @@ -import Parent from './Parent.js' +import Base from './Base.js' import * as circled from './circled.js' +import {extend} from './tools.js' -export default class Ellipse extends Shape { +export default class Ellipse extends Base { constructor (node) { - super(nodeOrNew('ellipse', node)) + super(nodeOrNew('ellipse', node), Ellipse) } } extend(Ellipse, circled) -addFactory(Container, { - // Create an ellipse - ellipse: function (width, height) { - return this.put(new Ellipse()).size(width, height).move(0, 0) - } -}) +// addFactory(Container, { +// // Create an ellipse +// ellipse: function (width, height) { +// return this.put(new Ellipse()).size(width, height).move(0, 0) +// } +// }) diff --git a/src/EventTarget.js b/src/EventTarget.js index c762929..ce18d1f 100644 --- a/src/EventTarget.js +++ b/src/EventTarget.js @@ -1,25 +1,29 @@ -import {on, off, dispatch} from './event.js' +import {on as _on, off as _off, dispatch as _dispatch} from './event.js' + +export const name = 'EventTarget' + +export function setup (node = {}) { + this.events = node.events || {} +} -export default class EventTarget { // Bind given event to listener - on (event, listener, binding, options) { - on(this, event, listener, binding, options) - return this - } +export function on (event, listener, binding, options) { + _on(this, event, listener, binding, options) + return this +} // Unbind event from listener - off (event, listener) { - off(this, event, listener) - return this - } +export function off (event, listener) { + _off(this, event, listener) + return this +} - dispatch (event, data) { - return dispatch(this, event, data) - } +export function dispatch (event, data) { + return _dispatch(this, event, data) +} // Fire given event - fire (event, data) { - this.dispatch(event, data) - return this - } +export function fire (event, data) { + this.dispatch(event, data) + return this } @@ -1,15 +1,16 @@ -import Container from './Container.js' -import Parent from './Parent.js' +import Base from './Base.js' -export default class G extends Container { +export default class G extends Base { constructor (node) { - super(nodeorNew('group', node)) + super(nodeorNew('g', node), G) } } -addFactory(Parent, { - // Create a group element - group: function () { - return this.put(new G()) +G.constructors = { + Element: { + // Create a group element + group: function () { + return this.put(new G()) + } } -}) +} diff --git a/src/Gradient.js b/src/Gradient.js index da76666..ab4cc4f 100644 --- a/src/Gradient.js +++ b/src/Gradient.js @@ -1,10 +1,15 @@ import Stop from './Stop.js' +import Base from './Base.js' import * as gradiented from './gradiented.js' -import {nodeOrNew, extend, addFactory} from './tools.js' +import {nodeOrNew, extend} from './tools.js' +import attr from './attr.js' -export default class Gradient extends Container { +export default class Gradient extends Base { constructor (type) { - super(nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type)) + super( + nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type), + Gradient + ) } // Add a color stop @@ -38,23 +43,23 @@ export default class Gradient extends Container { // custom attr to handle transform attr (a, b, c) { if (a === 'transform') a = 'gradientTransform' - return super.attr(a, b, c) + return attr.call(this, a, b, c) } } extend(Gradient, gradiented) -addFactory(Parent, { - // Create gradient element in defs - gradient (type, block) { - return this.defs().gradient(type, block) - } -}) - -// Base gradient generation -addFactory(Defs, { +Gradient.constructors = { + Container: { + // Create gradient element in defs + gradient (type, block) { + return this.defs().gradient(type, block) + } + }, // define gradient - gradient: function (type, block) { - return this.put(new Gradient(type)).update(block) + Defs: { + gradient (type, block) { + return this.put(new Gradient(type)).update(block) + } } -}) +} diff --git a/src/HtmlNode.js b/src/HtmlNode.js index 4a12d3f..f674d0b 100644 --- a/src/HtmlNode.js +++ b/src/HtmlNode.js @@ -1,8 +1,9 @@ -import {makeInstance} from './helpers.js' -import EventTarget from './EventTarget.js' +import {makeInstance} from './adopter.js' +import Base from './Base.js' -export default class HtmlNode extends EventTarget { +export default class HtmlNode extends Base { constructor (element) { + super(element, HtmlNode) this.node = element } diff --git a/src/Image.js b/src/Image.js index c70be1d..389a38d 100644 --- a/src/Image.js +++ b/src/Image.js @@ -1,13 +1,12 @@ -import Shape from './Shape.js' -import Container from './Container.js' +import Base from './Base.js' import Pattern from './Pattern.js' import {on, off} from './event.js' -import {nodeOrNew, addFactory} from './tools.js' +import {nodeOrNew} from './tools.js' import {xlink} from './namespaces.js' -export default class Image extends Shape { +export default class Image extends Base { constructor (node) { - super(nodeOrNew('image', node)) + super(nodeOrNew('image', node), Image) } // (re)load image @@ -50,9 +49,11 @@ export default class Image extends Shape { } } -addFactory(Container, { - // create image element, load image and set its size - image (source, callback) { - return this.put(new Image()).size(0, 0).load(source, callback) +Image.constructors = { + Container: { + // create image element, load image and set its size + image (source, callback) { + return this.put(new Image()).size(0, 0).load(source, callback) + } } -}) +} diff --git a/src/Line.js b/src/Line.js index ddd00e9..930820b 100644 --- a/src/Line.js +++ b/src/Line.js @@ -1,11 +1,12 @@ import {proportionalSize} from './helpers.js' import {nodeOrNew} from './tools.js' -import {Shape, Container, PointArray} from './classes.js' +import PointArray from './PointArray.js' +import Base from './Base.js' -export default class Line extends Shape { +export default class Line extends Base { // Initialize node constructor (node) { - super(nodeOrNew('line', node)) + super(nodeOrNew('line', node), Line) } // Get array @@ -42,14 +43,16 @@ export default class Line extends Shape { } -addFactory(Container, { - // Create a line element - line (...args) { - // make sure plot is called as a setter - // x1 is not necessarily a number, it can also be an array, a string and a PointArray - return Line.prototype.plot.apply( - this.put(new Line()) - , args[0] != null ? args : [0, 0, 0, 0] - ) +Line.constructors = { + Container: { + // Create a line element + line (...args) { + // make sure plot is called as a setter + // x1 is not necessarily a number, it can also be an array, a string and a PointArray + return Line.prototype.plot.apply( + this.put(new Line()) + , args[0] != null ? args : [0, 0, 0, 0] + ) + } } -}) +} diff --git a/src/Marker.js b/src/Marker.js index 298ac6f..dfba967 100644 --- a/src/Marker.js +++ b/src/Marker.js @@ -1,14 +1,14 @@ -import Container from './Container.js' -import Defs from './Defs.js' -import Line from './Line.js' -import Polyline from './Polyline.js' -import Polygon from './Polygon.js' -import Path from './Path.js' +import Base from './Base.js' +// import Defs from './Defs.js' +// import Line from './Line.js' +// import Polyline from './Polyline.js' +// import Polygon from './Polygon.js' +// import Path from './Path.js' -export default class Marker extends Container { +export default class Marker extends Base { // Initialize node constructor (node) { - super(nodeOrNew('marker', node)) + super(nodeOrNew('marker', node), Marker) } // Set width of element @@ -43,40 +43,40 @@ export default class Marker extends Container { } } -addFactory(Container, { - marker (width, height, block) { - // Create marker element in defs - return this.defs().marker(width, height, block) - } -}) - -extend(Defs, { - // Create marker - marker (width, height, block) { - // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto - return this.put(new Marker()) - .size(width, height) - .ref(width / 2, height / 2) - .viewbox(0, 0, width, height) - .attr('orient', 'auto') - .update(block) - } -}) +Marker.constructors = { + Container: { + marker (width, height, block) { + // Create marker element in defs + return this.defs().marker(width, height, block) + } + }, + Defs: { + // Create marker + marker (width, height, block) { + // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto + return this.put(new Marker()) + .size(width, height) + .ref(width / 2, height / 2) + .viewbox(0, 0, width, height) + .attr('orient', 'auto') + .update(block) + } + }, + marker: { + // Create and attach markers + marker (marker, width, height, block) { + var attr = ['marker'] -extend([Line, Polyline, Polygon, Path], { - // Create and attach markers - marker (marker, width, height, block) { - var attr = ['marker'] + // Build attribute name + if (marker !== 'all') attr.push(marker) + attr = attr.join('-') - // Build attribute name - if (marker !== 'all') attr.push(marker) - attr = attr.join('-') + // Set marker attribute + marker = arguments[1] instanceof Marker + ? arguments[1] + : this.defs().marker(width, height, block) - // Set marker attribute - marker = arguments[1] instanceof Marker - ? arguments[1] - : this.doc().marker(width, height, block) - - return this.attr(attr, marker) + return this.attr(attr, marker) + } } -}) +} diff --git a/src/Mask.js b/src/Mask.js index bdd6086..969fe2c 100644 --- a/src/Mask.js +++ b/src/Mask.js @@ -1,9 +1,9 @@ -import Container from './Container.js' -import Element from './Element.js' +import Base from './Base.js' import {nodeOrNew} from './tools.js' import find from './selector.js' +import {remove} from './Element.js' -export default class Mask extends Container { +export default class Mask extends Base { // Initialize node constructor (node) { super(nodeOrNew('mask', node)) @@ -17,7 +17,7 @@ export default class Mask extends Container { }) // remove mask from parent - return super.remove() + return remove.call(this) } targets () { @@ -26,30 +26,32 @@ export default class Mask extends Container { } -addFactory(Container, { - mask () { - return this.defs().put(new Mask()) - } -}) - -extend(Element, { - // Distribute mask to svg element - maskWith (element) { - // use given mask or create a new one - var masker = element instanceof Mask - ? element - : this.parent().mask().add(element) - - // apply mask - return this.attr('mask', 'url("#' + masker.id() + '")') - }, - // Unmask element - unmask () { - return this.attr('mask', null) +Mask.constructors = { + Container: { + mask () { + return this.defs().put(new Mask()) + } }, - - masker () { - return this.reference('mask') + Element: { + // Distribute mask to svg element + maskWith (element) { + // use given mask or create a new one + var masker = element instanceof Mask + ? element + : this.parent().mask().add(element) + + // apply mask + return this.attr('mask', 'url("#' + masker.id() + '")') + }, + + // Unmask element + unmask () { + return this.attr('mask', null) + }, + + masker () { + return this.reference('mask') + } } -}) +} diff --git a/src/Matrix.js b/src/Matrix.js index 5edbc5c..d53db3c 100644 --- a/src/Matrix.js +++ b/src/Matrix.js @@ -1,16 +1,21 @@ import {abcdef, arrayToMatrix, closeEnough, formatTransforms, isMatrixLike, matrixMultiply} from './helpers.js' -import {Element, Point, Doc} from './classes.js' +import Point from './Point.js' import {delimiter} from './regex.js' import {radians} from './utils.js' import parser from './parser.js' +import Base from './Base.js' export default class Matrix { + constructor (...args) { + this.init(...args) + } + // Initialize - constructor (source) { + init (source) { var base = arrayToMatrix([1, 0, 0, 1, 0, 0]) // ensure source as object - source = source instanceof Element ? source.matrixify() + source = source instanceof Base && source.is('Element') ? source.matrixify() : typeof source === 'string' ? arrayToMatrix(source.split(delimiter).map(parseFloat)) : Array.isArray(source) ? arrayToMatrix(source) : (typeof source === 'object' && isMatrixLike(source)) ? source @@ -369,7 +374,7 @@ export default class Matrix { // Convert to native SVGMatrix native () { // create new matrix - var matrix = parser.nodes.node.createSVGMatrix() + var matrix = parser().node.createSVGMatrix() // update with current values for (var i = abcdef.length - 1; i >= 0; i--) { @@ -408,27 +413,29 @@ export default class Matrix { } } -extend(Element, { - // Get current matrix - ctm () { - return new Matrix(this.node.getCTM()) - }, - - // Get current screen matrix - screenCTM () { - /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537 - This is needed because FF does not return the transformation matrix - for the inner coordinate system when getScreenCTM() is called on nested svgs. - However all other Browsers do that */ - if (this instanceof Doc && !this.isRoot()) { - var rect = this.rect(1, 1) - var m = rect.node.getScreenCTM() - rect.remove() - return new Matrix(m) +Matrix.constructors = { + Element: { + // Get current matrix + ctm () { + return new Matrix(this.node.getCTM()) + }, + + // Get current screen matrix + screenCTM () { + /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537 + This is needed because FF does not return the transformation matrix + for the inner coordinate system when getScreenCTM() is called on nested svgs. + However all other Browsers do that */ + if (this instanceof Doc && !this.isRoot()) { + var rect = this.rect(1, 1) + var m = rect.node.getScreenCTM() + rect.remove() + return new Matrix(m) + } + return new Matrix(this.node.getScreenCTM()) } - return new Matrix(this.node.getScreenCTM()) } -}) +} // let extensions = {} // ['rotate'].forEach((method) => { diff --git a/src/Morphable.js b/src/Morphable.js index 1cb437e..c7e1c0f 100644 --- a/src/Morphable.js +++ b/src/Morphable.js @@ -1,8 +1,9 @@ import {extend} from './tools.js' -import {Color, SVGNumber, SVGArray, PathArray} from './classes.js' +import {Color, SVGNumber, SVGArray, PathArray, Box, Matrix, PointArray} from './classes.js' +import {Ease} from './Controller.js' export default class Morphable { - constructor () { + constructor (stepper) { // FIXME: the default stepper does not know about easing this._stepper = stepper || new Ease('-') @@ -106,7 +107,11 @@ export default class Morphable { } Morphable.NonMorphable = class { - constructor (val) { + constructor (...args) { + this.init(...args) + } + + init (val) { val = Array.isArray(val) ? val[0] : val this.value = val } @@ -121,7 +126,11 @@ Morphable.NonMorphable = class { } Morphable.TransformBag = class { - constructor (obj) { + constructor (...args) { + this.init(...args) + } + + init (obj) { if (Array.isArray(obj)) { obj = { scaleX: obj[0], @@ -166,7 +175,11 @@ Morphable.TransformBag.defaults = { } Morphable.ObjectBag = class { - constructor (objOrArr) { + constructor (...args) { + this.init(...args) + } + + init (objOrArr) { this.values = [] if (Array.isArray(objOrArr)) { @@ -218,7 +231,7 @@ extend(morphableTypes, { .to(val, args) }, fromArray (arr) { - this.constructor(arr) + this.init(arr) return this } }) diff --git a/src/Parent.js b/src/Parent.js index ce22f80..e2c725c 100644 --- a/src/Parent.js +++ b/src/Parent.js @@ -1,93 +1,157 @@ -import {makeInstance} from './helpers.js' -import Element from './Element.js' -import {adopt} from './tools.js' +import {makeInstance, adopt} from './adopter.js' import {map} from './utils.js' -export default class Parent extends Element { - // Returns all child elements - children () { - return map(this.node.children, function (node) { - return adopt(node) - }) + +// Returns all child elements +export function children () { + return map(this.node.children, function (node) { + return adopt(node) + }) +} + +// Add given element at a position +export function add (element, i) { + element = makeInstance(element) + + if (element.node !== this.node.children[i]) { + this.node.insertBefore(element.node, this.node.children[i] || null) } - // Add given element at a position - add (element, i) { - element = makeInstance(element) + return this +} + +// Basically does the same as `add()` but returns the added element instead +export function put (element, i) { + this.add(element, i) + return element.instance || element +} + +// Checks if the given element is a child +export function has (element) { + return this.index(element) >= 0 +} + +// Gets index of given element +export function index (element) { + return [].slice.call(this.node.children).indexOf(element.node) +} + +// Get a element at the given index +export function get (i) { + return adopt(this.node.children[i]) +} + +// Get first child +export function first () { + return this.get(0) +} - if (element.node !== this.node.children[i]) { - this.node.insertBefore(element.node, this.node.children[i] || null) +// Get the last child +export function last () { + return this.get(this.node.children.length - 1) +} + +// Iterates over all children and invokes a given block +export function each (block, deep) { + var children = this.children() + var i, il + + for (i = 0, il = children.length; i < il; i++) { + if (children[i] instanceof Base) { + block.apply(children[i], [i, children]) } - return this + if (deep && (children[i] instanceof Base && children[i].is('Parent'))) { + children[i].each(block, deep) + } } - // Basically does the same as `add()` but returns the added element instead - put (element, i) { - this.add(element, i) - return element.instance || element - } + return this +} - // Checks if the given element is a child - has (element) { - return this.index(element) >= 0 - } +// Remove a given child +export function removeElement (element) { + this.node.removeChild(element.node) - // Gets index of given element - index (element) { - return [].slice.call(this.node.children).indexOf(element.node) - } + return this +} - // Get a element at the given index - get (i) { - return adopt(this.node.children[i]) +// Remove all elements in this container +export function clear () { + // remove children + while (this.node.hasChildNodes()) { + this.node.removeChild(this.node.lastChild) } - // Get first child - first () { - return this.get(0) - } + // remove defs reference + delete this._defs - // Get the last child - last () { - return this.get(this.node.children.length - 1) - } + return this +} - // Iterates over all children and invokes a given block - each (block, deep) { - var children = this.children() - var i, il +// Import raw svg +export function svg (svg) { + var well, len - for (i = 0, il = children.length; i < il; i++) { - if (children[i] instanceof Element) { - block.apply(children[i], [i, children]) - } + // act as a setter if svg is given + if (svg) { + // create temporary holder + well = document.createElementNS(ns, 'svg') + // dump raw svg + well.innerHTML = svg - if (deep && (children[i] instanceof Parent)) { - children[i].each(block, deep) - } + // transplant nodes + for (len = well.children.length; len--;) { + this.node.appendChild(well.firstElementChild) } - return this + // otherwise act as a getter + } else { + // write svgjs data to the dom + this.writeDataToDom() + + return this.node.outerHTML } - // Remove a given child - removeElement (element) { - this.node.removeChild(element.node) + return this +} + +// write svgjs data to the dom +export function writeDataToDom () { + // dump variables recursively + this.each(function () { + this.writeDataToDom() + }) - return this + // 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 +} - // Remove all elements in this container - clear () { - // remove children - while (this.node.hasChildNodes()) { - this.node.removeChild(this.node.lastChild) - } +export function flatten (parent) { + this.each(function () { + if (this.is('Parent')) return this.flatten(parent).ungroup(parent) + return this.toParent(parent) + }) - // remove defs reference - delete this._defs + // we need this so that Doc does not get removed + this.node.firstElementChild || this.remove() - return this - } + return this +} + +export function ungroup (parent) { + parent = parent || this.parent() + + this.each(function () { + return this.toParent(parent) + }) + + this.remove() + + return this } diff --git a/src/Path.js b/src/Path.js index 7226977..b06da5b 100644 --- a/src/Path.js +++ b/src/Path.js @@ -1,12 +1,12 @@ import {proportionalSize} from './helpers.js' import {nodeOrNew} from './tools.js' -import Shape from './Shape.js' +import Base from './Base.js' import PathArray from './PathArray.js' -export default class Path extends Shape { +export default class Path extends Base { // Initialize node constructor (node) { - super(nodeOrNew('path', node)) + super(nodeOrNew('path', node), Path) } // Get array @@ -61,11 +61,13 @@ export default class Path extends Shape { // Define morphable array Path.prototype.MorphArray = PathArray - // Add parent method -addFactory(Container, { - // Create a wrapped path element - path (d) { - // make sure plot is called as a setter - return this.put(new Path()).plot(d || new PathArray()) +// Add parent method +Path.constructors = { + Container: { + // Create a wrapped path element + path (d) { + // make sure plot is called as a setter + return this.put(new Path()).plot(d || new PathArray()) + } } -}) +} diff --git a/src/PathArray.js b/src/PathArray.js index 24d8665..f6c22d1 100644 --- a/src/PathArray.js +++ b/src/PathArray.js @@ -2,6 +2,7 @@ import {arrayToString, pathRegReplace} from './helpers.js' import parser from './parser.js' import {numbersWithDots, pathLetters, hyphen, delimiter, isPathLetter} from './regex.js' import Point from './Point.js' +import SVGArray from './SVGArray.js' let pathHandlers = { M: function (c, p, p0) { @@ -83,11 +84,11 @@ export default class PathArray extends SVGArray { // Convert array to string toString () { - return arrayToString(this.value) + return arrayToString(this) } toArray () { - return this.value.reduce(function (prev, curr) { + return this.reduce(function (prev, curr) { return [].concat.call(prev, curr) }, []) } @@ -103,29 +104,29 @@ export default class PathArray extends SVGArray { if (!isNaN(x) && !isNaN(y)) { // move every point - for (var l, i = this.value.length - 1; i >= 0; i--) { - l = this.value[i][0] + for (var l, i = this.length - 1; i >= 0; i--) { + l = this[i][0] if (l === 'M' || l === 'L' || l === 'T') { - this.value[i][1] += x - this.value[i][2] += y + this[i][1] += x + this[i][2] += y } else if (l === 'H') { - this.value[i][1] += x + this[i][1] += x } else if (l === 'V') { - this.value[i][1] += y + this[i][1] += y } else if (l === 'C' || l === 'S' || l === 'Q') { - this.value[i][1] += x - this.value[i][2] += y - this.value[i][3] += x - this.value[i][4] += y + this[i][1] += x + this[i][2] += y + this[i][3] += x + this[i][4] += y if (l === 'C') { - this.value[i][5] += x - this.value[i][6] += y + this[i][5] += x + this[i][6] += y } } else if (l === 'A') { - this.value[i][6] += x - this.value[i][7] += y + this[i][6] += x + this[i][7] += y } } } @@ -140,34 +141,34 @@ export default class PathArray extends SVGArray { var i, l // recalculate position of all points according to new size - for (i = this.value.length - 1; i >= 0; i--) { - l = this.value[i][0] + for (i = this.length - 1; i >= 0; i--) { + l = this[i][0] if (l === 'M' || l === 'L' || l === 'T') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x - this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y + this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x + this[i][2] = ((this[i][2] - box.y) * height) / box.height + box.y } else if (l === 'H') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x + this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x } else if (l === 'V') { - this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y + this[i][1] = ((this[i][1] - box.y) * height) / box.height + box.y } else if (l === 'C' || l === 'S' || l === 'Q') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x - this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y - this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x - this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y + this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x + this[i][2] = ((this[i][2] - box.y) * height) / box.height + box.y + this[i][3] = ((this[i][3] - box.x) * width) / box.width + box.x + this[i][4] = ((this[i][4] - box.y) * height) / box.height + box.y if (l === 'C') { - this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x - this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y + this[i][5] = ((this[i][5] - box.x) * width) / box.width + box.x + this[i][6] = ((this[i][6] - box.y) * height) / box.height + box.y } } else if (l === 'A') { // resize radii - this.value[i][1] = (this.value[i][1] * width) / box.width - this.value[i][2] = (this.value[i][2] * height) / box.height + this[i][1] = (this[i][1] * width) / box.width + this[i][2] = (this[i][2] * height) / box.height // move position values - this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x - this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y + this[i][6] = ((this[i][6] - box.x) * width) / box.width + box.x + this[i][7] = ((this[i][7] - box.y) * height) / box.height + box.y } } @@ -180,9 +181,9 @@ export default class PathArray extends SVGArray { pathArray = new PathArray(pathArray) - equalCommands = this.value.length === pathArray.value.length - for (i = 0, il = this.value.length; equalCommands && i < il; i++) { - equalCommands = this.value[i][0] === pathArray.value[i][0] + equalCommands = this.length === pathArray.value.length + for (i = 0, il = this.length; equalCommands && i < il; i++) { + equalCommands = this[i][0] === pathArray.value[i][0] } return equalCommands @@ -206,7 +207,7 @@ export default class PathArray extends SVGArray { // make sure a destination is defined if (!this.destination) return this - var sourceArray = this.value + var sourceArray = this var destinationArray = this.destination.value var array = [] var pathArray = new PathArray() @@ -246,11 +247,11 @@ export default class PathArray extends SVGArray { if (typeof array === 'string') { array = array - .replace(regex.numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123 - .replace(regex.pathLetters, ' $& ') // put some room between letters and numbers - .replace(regex.hyphen, '$1 -') // add space before hyphen + .replace(numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123 + .replace(pathLetters, ' $& ') // put some room between letters and numbers + .replace(hyphen, '$1 -') // add space before hyphen .trim() // trim - .split(regex.delimiter) // split into array + .split(delimiter) // split into array } else { array = array.reduce(function (prev, curr) { return [].concat.call(prev, curr) @@ -266,7 +267,7 @@ export default class PathArray extends SVGArray { do { // Test if we have a path letter - if (regex.isPathLetter.test(array[index])) { + if (isPathLetter.test(array[index])) { s = array[index] ++index // If last letter was a move command and we got no new, it defaults to [L]ine @@ -286,7 +287,7 @@ export default class PathArray extends SVGArray { return result } - // Get bounding box of path + Get bounding box of path bbox () { parser().path.setAttribute('d', this.toString()) return parser.nodes.path.getBBox() diff --git a/src/Pattern.js b/src/Pattern.js index 00f9de5..add8b71 100644 --- a/src/Pattern.js +++ b/src/Pattern.js @@ -1,7 +1,8 @@ -import {Container, Defs} from './classes.js' +import Base from './Base.js' import {nodeOrNew} from './tools.js' +import attr from './attr.js' -export default class Pattern extends Container { +export default class Pattern extends Base { // Initialize node constructor (node) { super(nodeOrNew('pattern', node)) @@ -11,6 +12,7 @@ export default class Pattern extends Container { url () { return 'url(#' + this.id() + ')' } + // Update pattern by rebuilding update (block) { // remove content @@ -23,34 +25,35 @@ export default class Pattern extends Container { return this } + // Alias string convertion to fill toString () { return this.url() } + // custom attr to handle transform attr (a, b, c) { if (a === 'transform') a = 'patternTransform' - return super.attr(a, b, c) + return attr.call(this, a, b, c) } } - // Add parent method -addFactory(Container, { - // Create pattern element in defs - pattern (width, height, block) { - return this.defs().pattern(width, height, block) - } -}) - -extend(Defs, { - // Define gradient - pattern (width, height, block) { - return this.put(new Pattern()).update(block).attr({ - x: 0, - y: 0, - width: width, - height: height, - patternUnits: 'userSpaceOnUse' - }) +Pattern.constructors = { + Container: { + // Create pattern element in defs + pattern (width, height, block) { + return this.defs().pattern(width, height, block) + } + }, + Defs: { + pattern (width, height, block) { + return this.put(new Pattern()).update(block).attr({ + x: 0, + y: 0, + width: width, + height: height, + patternUnits: 'userSpaceOnUse' + }) + } } -}) +} diff --git a/src/Point.js b/src/Point.js index ff18473..5880168 100644 --- a/src/Point.js +++ b/src/Point.js @@ -1,5 +1,4 @@ import parser from './parser.js' -import Element from './Element.js' export default class Point { // Initialize @@ -44,9 +43,11 @@ export default class Point { } } -extend(Element, { - // Get point - point: function (x, y) { - return new Point(x, y).transform(this.screenCTM().inverse()) +Point.constructors = { + Element: { + // Get point + point: function (x, y) { + return new Point(x, y).transform(this.screenCTM().inverse()) + } } -}) +} diff --git a/src/Polygon.js b/src/Polygon.js index 112d33b..3f7f948 100644 --- a/src/Polygon.js +++ b/src/Polygon.js @@ -1,55 +1,26 @@ import {proportionalSize} from './helpers.js' -import Shape from './Shape.js' -import {nodeOrNew} from './tools.js' +import Base from './Base.js' +import {nodeOrNew, extend} from './tools.js' import * as pointed from './pointed.js' +import * as poly from './poly.js' import PointArray from './PointArray.js' -export default class Polygon extends Shape { +export default class Polygon extends Base { // Initialize node constructor (node) { - super(nodeOrNew('polygon', node)) + super(nodeOrNew('polygon', node), Polygon) } } -addFactory(Parent, { - // Create a wrapped polygon element - polygon (p) { - // make sure plot is called as a setter - return this.put(new Polygon()).plot(p || new PointArray()) +Polygon.constructors = { + Parent: { + // Create a wrapped polygon element + polygon (p) { + // make sure plot is called as a setter + return this.put(new Polygon()).plot(p || new PointArray()) + } } -}) - +} -// // Add polygon-specific functions -// extend([Polyline, Polygon], { -// // Get array -// array: function () { -// return this._array || (this._array = new 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 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) { -// let p = proportionalSize(this, width, height) -// return this.attr('points', this.array().size(p.width, p.height)) -// } -// }) -// -// extend([Polyline, Polygon], pointed) +extend(Polygon, pointed) +extend(Polygon, poly) diff --git a/src/Polyline.js b/src/Polyline.js index 9c28438..c3c8c0c 100644 --- a/src/Polyline.js +++ b/src/Polyline.js @@ -1,19 +1,25 @@ -import Shape from './Shape.js' -import {nodeOrNew} from './tools.js' +import Base from './Base.js' +import {nodeOrNew, extend} from './tools.js' import PointArray from './PointArray.js' +import * as pointed from './pointed.js' +import * as poly from './poly.js' -export default class Polyline extends Shape { +export default class Polyline extends Base { // Initialize node constructor (node) { - super(nodeOrNew('polyline', node)) + super(nodeOrNew('polyline', node), Polyline) } } -// Add parent method -addFactory (Parent, { - // Create a wrapped polyline element - polyline (p) { - // make sure plot is called as a setter - return this.put(new Polyline()).plot(p || new PointArray()) +Polyline.constructors = { + Parent: { + // Create a wrapped polygon element + polyline (p) { + // make sure plot is called as a setter + return this.put(new Polyline()).plot(p || new PointArray()) + } } -}) +} + +extend(Polyline, pointed) +extend(Polyline, poly) diff --git a/src/Rect.js b/src/Rect.js index 825adeb..f83243c 100644 --- a/src/Rect.js +++ b/src/Rect.js @@ -1,16 +1,23 @@ -import Shape from './Shape.js' -import {nodeOrNew} from './tools.js' +import Base from './Base.js' +import {nodeOrNew, extend} from './tools.js' +import * as EventTarget from './EventTarget.js' +import * as Element from './Element.js' +import * as Parent from './Parent.js' -export default class Rect extends Shape { +export default class Rect extends Base { // Initialize node constructor (node) { - super(nodeOrNew('rect', node)) + super(nodeOrNew('rect', node), Rect) } } -addFactory(Parent, { - // Create a rect element - rect (width, height) { - return this.put(new Rect()).size(width, height) +extend(Rect, [EventTarget, Element, Parent]) + +Rect.constructors = { + Container: { + // Create a rect element + rect (width, height) { + return this.put(new Rect()).size(width, height) + } } -}) +} diff --git a/src/Runner.js b/src/Runner.js index c29c72c..9633c90 100644 --- a/src/Runner.js +++ b/src/Runner.js @@ -2,10 +2,12 @@ import {isMatrixLike, getOrigin} from './helpers.js' import Matrix from './Matrix.js' import Morphable from './Morphable.js' import SVGNumber from './SVGNumber.js' -import Element from './Element.js' import Timeline from './Timeline.js' import {Controller, Ease, Stepper} from './Controller.js' import {noop, timeline} from './defaults.js' +import {extend} from './tools.js' +import Animator from './Animator.js' +import Point from './Point.js' // FIXME: What is this doing here? // easing = { @@ -15,7 +17,7 @@ import {noop, timeline} from './defaults.js' // '<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 } // } -export default class Runner extends EventTarget { +export default class Runner { constructor (options) { // Store a unique id on the runner, so that we can identify it later this.id = Runner.id++ @@ -435,22 +437,6 @@ export default class Runner extends EventTarget { Runner.id = 0 -extend(Element, { - animate (duration, delay, when) { - var o = Runner.sanitise(duration, delay, when) - var timeline = this.timeline() - return new Runner(o.duration) - .loop(o) - .element(this) - .timeline(timeline) - .schedule(delay, when) - }, - - delay (by, when) { - return this.animate(0, by, when) - } -}) - class FakeRunner{ constructor (transforms = new Matrix(), id = -1, done = true) { this.transforms = transforms @@ -557,44 +543,60 @@ class RunnerArray { } } -extend(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 (currentRunner) { - this._transformationRunners.clearBefore(currentRunner.id) - }, - - _currentTransform (current) { - return this._transformationRunners.runners - // we need the equal sign here to make sure, that also transformations - // on the same runner which execute before the current transformation are - // taken into account - .filter((runner) => runner.id <= current.id) - .map(getRunnerTransform) - .reduce(lmultiply, new Matrix()) - }, - - addRunner (runner) { - this._transformationRunners.add(runner) - - Animator.transform_frame( - mergeTransforms.bind(this), this._frameId - ) - }, - - _prepareRunner () { - if (this._frameId == null) { - this._transformationRunners = new RunnerArray() - .add(new FakeRunner(new Matrix(this))) - - this._frameId = Element.frameId++ +let frameId = 0 +Runner.constructors = { + Element: { + animate (duration, delay, when) { + var o = Runner.sanitise(duration, delay, when) + var timeline = this.timeline() + return new Runner(o.duration) + .loop(o) + .element(this) + .timeline(timeline) + .schedule(delay, when) + }, + + delay (by, when) { + return this.animate(0, by, when) + }, + + // this function searches for all runners on the element and deletes the ones + // which run before the current one. This is because absolute transformations + // overwfrite anything anyway so there is no need to waste time computing + // other runners + _clearTransformRunnersBefore (currentRunner) { + this._transformationRunners.clearBefore(currentRunner.id) + }, + + _currentTransform (current) { + return this._transformationRunners.runners + // we need the equal sign here to make sure, that also transformations + // on the same runner which execute before the current transformation are + // taken into account + .filter((runner) => runner.id <= current.id) + .map(getRunnerTransform) + .reduce(lmultiply, new Matrix()) + }, + + addRunner (runner) { + this._transformationRunners.add(runner) + + Animator.transform_frame( + mergeTransforms.bind(this), this._frameId + ) + }, + + _prepareRunner () { + if (this._frameId == null) { + this._transformationRunners = new RunnerArray() + .add(new FakeRunner(new Matrix(this))) + + this._frameId = frameId++ + } } } -}) +} -Element.frameId = 0 extend(Runner, { attr (a, v) { diff --git a/src/SVGArray.js b/src/SVGArray.js index e7881df..9273833 100644 --- a/src/SVGArray.js +++ b/src/SVGArray.js @@ -12,8 +12,14 @@ let BaseArray = (function() { })() export default class SVGArray extends BaseArray { - constructor (array, fallback) { + constructor (...args) { super() + this.init(...args) + } + + init (array, fallback) { + //this.splice(0, this.length) + this.length = 0 this.push(...this.parse(array || fallback)) } diff --git a/src/SVGNumber.js b/src/SVGNumber.js index cb1fd28..e07b521 100644 --- a/src/SVGNumber.js +++ b/src/SVGNumber.js @@ -3,7 +3,11 @@ import {numberAndUnit} from './regex.js' // Module for unit convertions export default class SVGNumber { // Initialize - constructor (value, unit) { + constructor (...args) { + this.init(...args) + } + + init (value, unit) { unit = Array.isArray(value) ? value[1] : unit value = Array.isArray(value) ? value[0] : value diff --git a/src/Shape.js b/src/Shape.js deleted file mode 100644 index d73ffb6..0000000 --- a/src/Shape.js +++ /dev/null @@ -1,3 +0,0 @@ -import Element from './Element.js' - -export default class Shape extends Element { } diff --git a/src/Stop.js b/src/Stop.js index 6bce999..1631b4a 100644 --- a/src/Stop.js +++ b/src/Stop.js @@ -1,10 +1,10 @@ -import Element from './Element.js' +import Base from './Base.js' import SVGNumber from './SVGNumber.js' import {nodeOrNew} from './tools.js' -export default class Stop extends Element { +export default class Stop extends Base { constructor (node) { - super(nodeOrNew('stop', node)) + super(nodeOrNew('stop', node), Stop) } // add color stops diff --git a/src/Symbol.js b/src/Symbol.js index 98d10ef..e9936e2 100644 --- a/src/Symbol.js +++ b/src/Symbol.js @@ -1,16 +1,17 @@ -import Container from './Container.js' +import Base from './Base.js' import {nodeOrNew} from './tools.js' -export default class Symbol extends Container { +export default class Symbol extends Base { // Initialize node constructor (node) { - super(nodeOrNew('symbol', node)) + super(nodeOrNew('symbol', node), Symbol) } } -addFactory(Container, { - // create symbol - symbol () { - return this.put(new Symbol()) +Symbol.constructors = { + Container: { + symbol () { + return this.put(new Symbol()) + } } -}) +} diff --git a/src/Text.js b/src/Text.js index 3d9f074..3d7fd1d 100644 --- a/src/Text.js +++ b/src/Text.js @@ -1,12 +1,13 @@ -import Parent from './Parent.js' +import Base from './Base.js' import SVGNumber from './SVGNumber.js' -import {nodeOrNew, adopt} from './tools.js' +import {nodeOrNew, extend} from './tools.js' import {attrs} from './defaults.js' +import * as textable from './textable.js' -export default class Text extends Parent { +export default class Text extends Base { // Initialize node constructor (node) { - super(nodeOrNew('text', node)) + super(nodeOrNew('text', node), Text) this.dom.leading = new SVGNumber(1.3) // store leading value for rebuilding this._rebuild = true // enable automatic updating of dy values @@ -155,90 +156,18 @@ export default class Text extends Parent { } } +extend(Text, textable) -addFactory(Parent, { - // Create text element - text (text) { - return this.put(new Text()).text(text) - }, - - // Create plain text element - plain (text) { - return this.put(new Text()).plain(text) - } -}) +Text.constructors = { + Container: { + // Create text element + text (text) { + return this.put(new Text()).text(text) + }, - -class Tspan extends Parent { - // Initialize node - constructor (node) { - super(nodeOrNew('tspan', node)) - } - - // Set text content - text (text) { - if (text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '') - - typeof text === 'function' ? text.call(this, this) : this.plain(text) - - return this - } - - // Shortcut dx - dx (dx) { - return this.attr('dx', dx) - } - - // Shortcut dy - dy (dy) { - return this.attr('dy', dy) - } - - // Create new line - newLine () { - // fetch text parent - var t = this.parent(Text) - - // mark new line - this.dom.newLined = true - - // apply new position - return this.dy(t.dom.leading * t.attr('font-size')).attr('x', t.x()) - } -} - -extend([Text, 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 Tspan() - - // clear if build mode is disabled - if (!this._build) { - this.clear() + // Create plain text element + plain (text) { + return this.put(new Text()).plain(text) } - - // add new tspan - this.node.appendChild(tspan.node) - - return tspan.text(text) - }, - - // FIXME: Does this also work for textpath? - // Get length of text element - length: function () { - return this.node.getComputedTextLength() } -}) +} diff --git a/src/TextPath.js b/src/TextPath.js index cd1757e..c4685ec 100644 --- a/src/TextPath.js +++ b/src/TextPath.js @@ -33,48 +33,47 @@ export default class TextPath extends Text { } } -addFactory(Parent, { - textPath (text, path) { - return this.defs().path(path).text(text).addTo(this) - } -}) - - -extend([Text], { - // Create path for text to run on - path: function (track) { - var path = new TextPath() - - // if d is a path, reuse it - if (!(track instanceof Path)) { - // create path element - track = this.doc().defs().path(track) +TextPath.constructors = { + Container: { + textPath (text, path) { + return this.defs().path(path).text(text).addTo(this) } - - // link textPath to path and add content - path.attr('href', '#' + track, xlink) - - // add textPath element as child node and return textPath - return this.put(path) }, - - // FIXME: make this plural? - // Get the textPath children - textPath: function () { - return this.select('textPath') - } -}) - -extend([Path], { - // creates a textPath from this path - text: function (text) { - if (text instanceof Text) { - var txt = text.text() - return text.clear().path(this).text(txt) + Text: { + // Create path for text to run on + path: function (track) { + var path = new TextPath() + + // if d is a path, reuse it + if (!(track instanceof Path)) { + // create path element + track = this.doc().defs().path(track) + } + + // link textPath to path and add content + path.attr('href', '#' + track, xlink) + + // add textPath element as child node and return textPath + return this.put(path) + }, + + // FIXME: make this plural? + // Get the textPath children + textPath: function () { + return this.select('textPath') } - return this.parent().put(new Text()).path(this).text(text) + }, + Path: { + // creates a textPath from this path + text: function (text) { + if (text instanceof Text) { + var txt = text.text() + return text.clear().path(this).text(txt) + } + return this.parent().put(new Text()).path(this).text(text) + } + // FIXME: Maybe add `targets` to get all textPaths associated with this path } - // FIXME: Maybe add `targets` to get all textPaths associated with this path -}) +} TextPath.prototype.MorphArray = PathArray diff --git a/src/Timeline.js b/src/Timeline.js index 01a8e22..fc19db8 100644 --- a/src/Timeline.js +++ b/src/Timeline.js @@ -1,4 +1,3 @@ -import EventTarget from './EventTarget.js' import Animator from './Animator.js' var time = window.performance || Date @@ -10,14 +9,14 @@ var makeSchedule = function (runnerInfo) { return {start: start, duration: duration, end: end, runner: runnerInfo.runner} } -export default class Timeline extends EventTarget { +export default class Timeline { // Construct a new timeline on the given element constructor () { this._timeSource = function () { return time.now() } - this._dispatcher = document.makeInstance('div') + this._dispatcher = document.createElement('div') // Store the timing variables this._startTime = 0 @@ -261,9 +260,11 @@ export default class Timeline extends EventTarget { } } -extend(Element, { - timeline: function () { - this._timeline = (this._timeline || new Timeline()) - return this._timeline +Timeline.constructors = { + Element: { + timeline: function () { + this._timeline = (this._timeline || new Timeline()) + return this._timeline + } } -}) +} diff --git a/src/Tspan.js b/src/Tspan.js new file mode 100644 index 0000000..f5030c9 --- /dev/null +++ b/src/Tspan.js @@ -0,0 +1,43 @@ +import Base from './Base.js' +import {nodeOrNew, extend} from './tools.js' +import * as textable from './textable.js' + +export default class Tspan extends Base { + // Initialize node + constructor (node) { + super(nodeOrNew('tspan', node), Tspan) + } + + // Set text content + text (text) { + if (text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '') + + typeof text === 'function' ? text.call(this, this) : this.plain(text) + + return this + } + + // Shortcut dx + dx (dx) { + return this.attr('dx', dx) + } + + // Shortcut dy + dy (dy) { + return this.attr('dy', dy) + } + + // Create new line + newLine () { + // fetch text parent + var t = this.parent(Text) + + // mark new line + this.dom.newLined = true + + // apply new position + return this.dy(t.dom.leading * t.attr('font-size')).attr('x', t.x()) + } +} + +extend(Tspan, textable) @@ -1,9 +1,9 @@ -import {Shape, Container} from './classes.js' +import Base from './Base.js' import {xlink} from './namespaces.js' -export default class Use extends Shape { +export default class Use extends Base { constructor (node) { - super(nodeOrNew('use', node)) + super(nodeOrNew('use', node), Use) } // Use element as a reference @@ -13,9 +13,11 @@ export default class Use extends Shape { } } -addFactory(Container, { - // Create a use element - use: function (element, file) { - return this.put(new Use()).element(element, file) +Use.constructors = { + Container: { + // Create a use element + use: function (element, file) { + return this.put(new Use()).element(element, file) + } } -}) +} diff --git a/src/adopter.js b/src/adopter.js new file mode 100644 index 0000000..81969bd --- /dev/null +++ b/src/adopter.js @@ -0,0 +1,56 @@ +import Base from './Base.js' +import * as elements from './elements.js' +import {capitalize} from './helpers.js' +import HtmlNode from './HtmlNode.js' + +export function makeInstance (element) { + if (element instanceof Base) return element + + if (typeof element === 'object') { + return adopt(element) + } + + if (element == null) { + return new Doc() + } + + if (typeof element === 'string' && element.charAt(0) !== '<') { + return adopt(document.querySelector(element)) + } + + var node = makeNode('svg') + node.innerHTML = element + + element = adopt(node.firstElementChild) + + return element +} + +// Adopt existing svg elements +export function adopt (node) { + // check for presence of node + if (!node) return null + + // make sure a node isn't already adopted + if (node.instance instanceof Element) return node.instance + + if (!(node instanceof window.SVGElement)) { + return new HtmlNode(node) + } + + // initialize variables + var element + + // adopt with element-specific settings + if (node.nodeName === 'svg') { + element = new elements.Doc(node) + } else if (node.nodeName === 'linearGradient' || node.nodeName === 'radialGradient') { + element = new elements.Gradient(node) + } else if (elements[capitalize(node.nodeName)]) { + element = new elements[capitalize(node.nodeName)](node) + } else { + element = new elements.Bare(node) + } + + return element +} diff --git a/src/attr.js b/src/attr.js index a0c95b4..ddf4de2 100644 --- a/src/attr.js +++ b/src/attr.js @@ -1,6 +1,9 @@ -import {isNumer, isImage} from './regex.js' +import {isNumber, isImage} from './regex.js' import {attrs as defaults} from './defaults.js' -import {Color, SVGArray, Image} from './classes.js' +import Color from './Color.js' +import SVGArray from './SVGArray.js' +import Image from './Image.js' +import SVGNumber from './SVGNumber.js' // Set svg element attribute export default function attr (attr, val, ns) { @@ -21,8 +24,8 @@ export default function attr (attr, val, ns) { // FIXME: implement } else if (typeof attr === 'object') { // apply every attribute individually if an object is passed - for (val in a) this.attr(val, attr[val]) - }else if (val === null) { + for (val in attr) this.attr(val, attr[val]) + } else if (val === null) { // remove value this.node.removeAttribute(attr) } else if (val == null) { @@ -34,7 +37,7 @@ export default function attr (attr, val, ns) { } else { // convert image fill and stroke to patterns if (attr === 'fill' || attr === 'stroke') { - if (isImage.test(v)) { + if (isImage.test(val)) { val = this.doc().defs().image(val) } @@ -48,7 +51,7 @@ export default function attr (attr, val, ns) { // ensure correct numeric values (also accepts NaN and Infinity) if (typeof val === 'number') { val = new SVGNumber(val) - } else if (isColor(val)) { + } else if (Color.isColor(val)) { // ensure full hex color val = new Color(val) } else if (Array.isArray(val)) { diff --git a/src/classes.js b/src/classes.js index df65151..feebee2 100644 --- a/src/classes.js +++ b/src/classes.js @@ -1,13 +1,8 @@ -export {default as EventTarget} from './EventTarget.js' -export {default as Element} from './Element.js' export {default as HtmlNode} from './HtmlNode.js' -export {default as Parent} from './Parent.js' -export {default as Container} from './Container.js' export {default as Doc} from './Doc.js' export {default as Defs} from './Defs.js' export {default as G} from './G.js' export {default as Animator} from './Animator.js' -export {default as Shape} from './Shape.js' export {default as Bare} from './Bare.js' export {default as Circle} from './Circle.js' export {default as ClipPath} from './ClipPath.js' diff --git a/src/containers.js b/src/containers.js new file mode 100644 index 0000000..56287de --- /dev/null +++ b/src/containers.js @@ -0,0 +1,11 @@ +export {default as Bare} from './Bare.js' +export {default as ClipPath} from './ClipPath.js' +export {default as Defs} from './Defs.js' +export {default as Doc} from './Doc.js' +export {default as Gradient} from './Gradient.js' +export {default as G} from './G.js' +export {default as A} from './A.js' +export {default as Marker} from './Marker.js' +export {default as Mask} from './Mask.js' +export {default as Pattern} from './Pattern.js' +export {default as Symbol} from './Symbol.js' @@ -1,70 +1,66 @@ import {camelCase} from './helpers.js' -import Element from './Element.js' -import {extend} from './tools.js' import {isBlank} from './regex.js' -extend(Element, { // Dynamic style generator - css (style, val) { - let ret = {} - let i - if (arguments.length === 0) { - // get full style as object - this.node.style.cssText.split(/\s*;\s*/) - .filter(function (el) { return !!el.length }) - .forEach(function (el) { - let t = el.split(/\s*:\s*/) - ret[t[0]] = t[1] - }) - return ret - } +export function css (style, val) { + let ret = {} + let i + if (arguments.length === 0) { + // get full style as object + this.node.style.cssText.split(/\s*;\s*/) + .filter(function (el) { return !!el.length }) + .forEach(function (el) { + let t = el.split(/\s*:\s*/) + ret[t[0]] = t[1] + }) + return ret + } - if (arguments.length < 2) { - // get style properties in the array - if (Array.isArray(style)) { - for (let name of style) { - let cased = camelCase(name) - ret[cased] = this.node.style(cased) - } - return ret + if (arguments.length < 2) { + // get style properties in the array + if (Array.isArray(style)) { + for (let name of style) { + let cased = camelCase(name) + ret[cased] = this.node.style(cased) } + return ret + } - // get style for property - if (typeof style === 'string') { - return this.node.style[camelCase(style)] - } + // get style for property + if (typeof style === 'string') { + return this.node.style[camelCase(style)] + } - // set styles in object - if (typeof style === 'object') { - for (name in style) { - // set empty string if null/undefined/'' was given - this.node.style[camelCase(name)] = - (style[name] == null || isBlank.test(style[name])) ? '' : style[name] - } + // set styles in object + if (typeof style === 'object') { + for (name in style) { + // set empty string if null/undefined/'' was given + this.node.style[camelCase(name)] = + (style[name] == null || isBlank.test(style[name])) ? '' : style[name] } } + } - // set style for property - if (arguments.length === 2) { - this.node.style[camelCase(style)] = - (val == null || isBlank.test(val)) ? '' : val - } + // set style for property + if (arguments.length === 2) { + this.node.style[camelCase(style)] = + (val == null || isBlank.test(val)) ? '' : val + } - return this - }, + return this +} // Show element - show () { - return this.css('display', '') - }, +export function show () { + return this.css('display', '') +} // Hide element - hide () { - return this.css('display', 'none') - }, +export function hide () { + return this.css('display', 'none') +} // Is element visible? - visible () { - return this.css('display') !== 'none' - } -}) +export function visible () { + return this.css('display') !== 'none' +} diff --git a/src/data.js b/src/data.js index 530986d..c143772 100644 --- a/src/data.js +++ b/src/data.js @@ -1,27 +1,22 @@ -import Element from './Element.js' -import {extend} from './tools.js' - -extend(Element, { - // Store data values on svg nodes - data (a, v, r) { - if (typeof a === 'object') { - for (v in a) { - this.data(v, a[v]) - } - } else if (arguments.length < 2) { - try { - return JSON.parse(this.attr('data-' + a)) - } catch (e) { - return this.attr('data-' + a) - } - } else { - this.attr('data-' + a, - v === null ? null - : r === true || typeof v === 'string' || typeof v === 'number' ? v - : JSON.stringify(v) - ) +// Store data values on svg nodes +export function data (a, v, r) { + if (typeof a === 'object') { + for (v in a) { + this.data(v, a[v]) } - - return this + } else if (arguments.length < 2) { + try { + return JSON.parse(this.attr('data-' + a)) + } catch (e) { + return this.attr('data-' + a) + } + } else { + this.attr('data-' + a, + v === null ? null + : r === true || typeof v === 'string' || typeof v === 'number' ? v + : JSON.stringify(v) + ) } -}) + + return this +} diff --git a/src/default.js b/src/default.js deleted file mode 100644 index e82d1db..0000000 --- a/src/default.js +++ /dev/null @@ -1,51 +0,0 @@ - -SVG.void = function () {} - -SVG.defaults = { - - // Default animation values - timeline: { - duration: 400, - ease: '>', - delay: 0 - }, - - // Default attribute values - attrs: { - - // fill and stroke - 'fill-opacity': 1, - 'stroke-opacity': 1, - 'stroke-width': 0, - 'stroke-linejoin': 'miter', - 'stroke-linecap': 'butt', - fill: '#000000', - stroke: '#000000', - opacity: 1, - - // position - x: 0, - y: 0, - cx: 0, - cy: 0, - - // size - width: 0, - height: 0, - - // radius - r: 0, - rx: 0, - ry: 0, - - // gradient - offset: 0, - 'stop-opacity': 1, - 'stop-color': '#000000', - - // text - 'font-size': 16, - 'font-family': 'Helvetica, Arial, sans-serif', - 'text-anchor': 'start' - } -} diff --git a/src/elements.js b/src/elements.js new file mode 100644 index 0000000..4709c96 --- /dev/null +++ b/src/elements.js @@ -0,0 +1,24 @@ +export {default as Bare} from './Bare.js' +export {default as Circle} from './Circle.js' +export {default as ClipPath} from './ClipPath.js' +export {default as Defs} from './Defs.js' +export {default as Doc} from './Doc.js' +export {default as Ellipse} from './Ellipse.js' +export {default as Gradient} from './Gradient.js' +export {default as G} from './G.js' +export {default as HtmlNode} from './HtmlNode.js' +export {default as A} from './A.js' +export {default as Image} from './Image.js' +export {default as Line} from './Line.js' +export {default as Marker} from './Marker.js' +export {default as Mask} from './Mask.js' +export {default as Path} from './Path.js' +export {default as Pattern} from './Pattern.js' +export {default as Polygon} from './Polygon.js' +export {default as Polyline} from './Polyline.js' +export {default as Rect} from './Rect.js' +export {default as Stop} from './Stop.js' +export {default as Symbol} from './Symbol.js' +export {default as Text} from './Text.js' +export {default as TextPath} from './TextPath.js' +export {default as Use} from './Use.js' diff --git a/src/event.js b/src/event.js index 8d54782..c7832aa 100644 --- a/src/event.js +++ b/src/event.js @@ -1,5 +1,3 @@ -import EventTarget from './EventTarget.js' -import Element from './Element.js' import {delimiter} from './regex.js' // // Add events to elements @@ -30,10 +28,16 @@ import {delimiter} from './regex.js' let listenerId = 0 +function getEventTarget (node) { + return node instanceof Base && node.is('EventTarget') + ? node.getEventTarget() + : node +} + // Add event binder in the SVG namespace export function on (node, events, listener, binding, options) { var l = listener.bind(binding || node) - var n = node instanceof EventTarget ? node.getEventTarget() : node + var n = getEventTarget(node) // events can be an array of events or a string of events events = Array.isArray(events) ? events : events.split(delimiter) @@ -67,7 +71,9 @@ export function on (node, events, listener, binding, options) { // Add event unbinder in the SVG namespace export function off (node, events, listener, options) { - var n = node instanceof EventTarget ? node.getEventTarget() : node + var n = getEventTarget(node) + + // we cannot remove an event if its not an svg.js instance if (!n.instance) return // listener can be a function or a number @@ -126,7 +132,7 @@ export function off (node, events, listener, options) { } export function dispatch (node, event, data) { - var n = node instanceof EventTarget ? node.getEventTarget() : node + var n = getEventTarget(node) // Dispatch event if (event instanceof window.Event) { diff --git a/src/flatten.js b/src/flatten.js deleted file mode 100644 index 34a21bb..0000000 --- a/src/flatten.js +++ /dev/null @@ -1,42 +0,0 @@ -import {Doc, G, Parent, Defs} from './classes.js' - -export function flatten (parent) { - // flatten is only possible for svgs and groups - if (!(this instanceof G || this instanceof Doc)) { - return this - } - - parent = parent || - (this instanceof Doc && this.isRoot() - ? this - : this.parent(Parent)) - - this.each(function () { - if (this instanceof Defs) return this - if (this instanceof Parent) return this.flatten(parent) - return this.toParent(parent) - }) - - // we need this so that Doc does not get removed - this.node.firstElementChild || this.remove() - - return this -} - -export function ungroup (parent) { - // ungroup is only possible for nested svgs and groups - if (!(this instanceof G || (this instanceof Doc && !this.isRoot()))) { - return this - } - - parent = parent || this.parent(Parent) - - this.each(function () { - return this.toParent(parent) - }) - - // we need this so that Doc does not get removed - this.remove() - - return this -} diff --git a/src/fx.js b/src/fx.js deleted file mode 100644 index dd515df..0000000 --- a/src/fx.js +++ /dev/null @@ -1,1368 +0,0 @@ -SVG.easing = { - '-': function (pos) { return pos }, - '<>': function (pos) { return -Math.cos(pos * Math.PI) / 2 + 0.5 }, - '>': function (pos) { return Math.sin(pos * Math.PI / 2) }, - '<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 } -} - -SVG.morph = function (pos) { - return function (from, to) { - return new SVG.MorphObj(from, to).at(pos) - } -} - -let time = window.performance || window.Date - -SVG.Timeline = SVG.invent ({ - - create: function () { - - // Store all of the closures to animate - this._closures = [] - - this._startTime = time.now() - this._duration = 0 - - this._running = true - - }, - - extend: { - - animate (duration, ease, delay, epoch) { - - } - - loop (times, reverse) { - - } - - duration (time) { - this._duration = time - } - - delay (by, epoch) { - if (epoch) { - this._startTime = time.now() - } - this._duration = 0 - this._startTime += by - } - - ease (fn) { - - } - - play () - pause () - stop () - finish (all=true) - speed (newSpeed) - seek (dt) - persist (dt || forever) // 0 by default - reverse () - - - - - - - // fn is a function that takes a position in range [0, 1] - schedule (fn) { // fn can not take parameters - - - - - - -let declarative = rect.animate(300, '>', 200) - .loop().color('blue') - .animate(SVG.Spring(300)) - -onmousemove() { - declarative.x(mouseX).y(mouseY) -} - - SVG.MorphObj = SVG.invent({ - - create: function (from, to) { - // prepare color for morphing - if (SVG.Color.isColor(to)) return new SVG.Color(from).morph(to) - // prepare value list for morphing - if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to) - // prepare number for morphing - if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) - - // prepare for plain morphing - this.value = from - this.destination = to - }, - - extend: { - at: function (pos, real) { - return real < 1 ? this.value : this.destination - }, - - valueOf: function () { - return this.value - } - } - - }) - - -add('fill-color', val) - -add('x', val, 'animations') - -add('x', val, 'styles') - -add('line-cap', val, 'attrs') - -.style(name, val) { - - - styleAttr ('style', name, val) -} - -.animate(spring) - -onmousemove(() => { - el.animate(SVG.Spring(500)) - .move(event.pointX, event.pointY) - .finish() -}) - - - -Morphable () - -Controlable () - -new Controller(target, controller) - - - - -Number -Array -PathArray -ViewBox -PointArray -Color - - - - - - - - - - -SVG.Timeline = { - styleAttr (type, name, val) { - let morpher = new Morph(val).controller(this.controller) - queue ( - ()=> { - morpher = morpher.morph(element[type]('name')) - }, - morpher.at - ) - } -} - -.styleAttr (type, name, val) { - - let morpher = declarative ? new Controller(target) : new Morph().to(val) - queue ( - ()=> { - morpher = morpher.from(element[type](name)) - }, - () => { - this.element[type](name, morpher.at(pos)) - } - ) -} - -viewbox(box) { - new Box - let morpher = new Morph().to(box) // box: {width, heught, x, y} -} - - -new Morph(from, to) - - -new Morpg(from, to, controller = (from, to, pos) => {from + pos * (to - from)}) - - -// Something line -path = "a, b, c" - -SVG.color { - toArray: [r, g, b] - fromArray: new Color({r, g, b}) -} - - - - - - -morph: function (pathArray) { - pathArray = new SVG.PathArray(pathArray) - - if (this.equalCommands(pathArray)) { - this.destination = pathArray - } else { - this.destination = null - } - - return this -}, - -[['M', 3, 5], ['L', 5, 6]] - -['M', 3, 4, 'L', ...] - - - - -function detectSomething (item) { - if(from instanceof SVG.Morphable) return from.controller(controller) - // prepare color for morphing - if (SVG.Color.isColor(to)) return new SVG.Color(from, controller) - // prepare value list for morphing - if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to) - // prepare number for morphing - if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) - - return item -} - -foo->bar - - -all of these things implement - -interface Morphable { - from: (thing)=> {} - to: (thing)=> {} - at: (pos)=> {} - controller: (fn (nowOrFrom, target, pos))=> {} -} - - -new SVG.MorphObj(el.attr(name)) - -animate().attr('line-joint', 5) - -SVG.MorphObj = SVG.invent({ - - create: function (from, to) { - // prepare color for morphing - if (SVG.Color.isColor(to)) return new SVG.Color(from).morph(to) - // prepare value list for morphing - if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to) - // prepare number for morphing - if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) - - // prepare for plain morphing - this.value = from - this.destination = to - }, - - extend: { - at: function (pos, real) { - return real < 1 ? this.value : this.destination - }, - - valueOf: function () { - return this.value - } - } - -}) - - -// Only works with a single number -new MorphObj { - - constr: (control= (from, to, c)=> {from + pos * (to - from)}) { - } - - _detect: // Gets the user input and returns the right kind of object - - from: (from) => { - - if (SVG.Color.isColor(to)) return new SVG.Color(from).morph(to) - // prepare value list for morphing - if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to) - // prepare number for morphing - if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) - - // prepare for plain morphing - this.value = from - this.destination = to - } - - to: (val) => { - - } - at (pos) { - - let type = from.type - let from = from.toArray() - let to = to.toArray() - result = [] - for (i) - result[i] = this.controller(from[i], to[i], pos) : to[i] - - type.fromArray(result) - } -} - -if(declartive) { - mropher.init() - morpher.at(pos/fn) -} - - - -controller(currentPos, target) - - -morph interface -detect type function - - -if (mouse in box) - move box - animate(spring) - -zoom(level, point) { - let morpher = SVG.Number(level).controller(this.controller) - this.queue( - () => {morpher = morpher.from(element.zoom())}, - (pos) => {element.zoom(morpher.at(pos), point)} - ) -} - -x (x) { - -} - -this.queue(fn, morpher) - -new Morph(x(), xGiven) - - x: function (x, relative) { - if (this.target() instanceof SVG.G) { - this.transform({x: x}, relative) - return this - } - - var num = new SVG.Number(x) - num.relative = relative - return this.add('x', num) - }, - - - viewbox: function(box) { - var m = SVG.Box(box) - } - - - new Runner (function(time) { - - - }) - - - var closure = function (time) { - - // If it is time to do something, act now. - var running = start < time && time < end - if (running && this._running) { - closure.position = (time - closure.start) / closure.duration - fn (time) - } - - // If we are not paused or stopped, request another frame - if (this._running) SVG.Animator.frame(closure, this._startTime) - - // Tell the caller whether this animation is finished - closure.finished = !running - - }.bind(this) - - closure.stop() // toggles a stop flag - closure.pause() - closure.run(t) // If it was paused, it - - - closure.start = this._startTime - closure.end = this._startTime + this._duration - closure.positon = - var forwards = true // Decide if running forward based on looping - - - // TODO: Store a list of closures - - SVG.Animator.timeout(closure, this._startTime) - _continue() - } - - _step (dt) { - - } - - // Checks if we are running and continues the animation - _continue () { - , continue: function () { - if (this.paused) return - if (!this.nextFrame) - this.step() - return this - } - - } - }, - - - construct: { - animate: function(o, ease, delay, epoch) { - return (this.timeline = this.timeline || new SVG.Timeline(o, ease, delay, epoch)) - } - } -}) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// SVG.Situation = SVG.invent({ -// -// create: function (o) { -// this.init = false -// this.reversed = false -// this.reversing = false -// -// this.duration = new SVG.Number(o.duration).valueOf() -// this.delay = new SVG.Number(o.delay).valueOf() -// -// this.start = +new Date() + this.delay -// this.finish = this.start + this.duration -// this.ease = o.ease -// -// // this.loop is incremented from 0 to this.loops -// // it is also incremented when in an infinite loop (when this.loops is true) -// this.loop = 0 -// this.loops = false -// -// this.animations = { -// // functionToCall: [list of morphable objects] -// // e.g. move: [SVG.Number, SVG.Number] -// } -// -// this.attrs = { -// // holds all attributes which are not represented from a function svg.js provides -// // e.g. someAttr: SVG.Number -// } -// -// this.styles = { -// // holds all styles which should be animated -// // e.g. fill-color: SVG.Color -// } -// -// this.transforms = [ -// // holds all transformations as transformation objects -// // e.g. [SVG.Rotate, SVG.Translate, SVG.Matrix] -// ] -// -// this.once = { -// // functions to fire at a specific position -// // e.g. "0.5": function foo(){} -// } -// } -// -// }) -// -// SVG.Timeline = SVG.invent({ -// -// create: function (element) { -// this._target = element -// this.situations = [] -// this.active = false -// this.situation = null -// this.paused = false -// this.lastPos = 0 -// this.pos = 0 -// // The absolute position of an animation is its position in the context of its complete duration (including delay and loops) -// // When performing a delay, absPos is below 0 and when performing a loop, its value is above 1 -// this.absPos = 0 -// this._speed = 1 -// }, -// -// extend: { -// -// /** -// * sets or returns the target of this animation -// * @param o object || number In case of Object it holds all parameters. In case of number its the duration of the animation -// * @param ease function || string Function which should be used for easing or easing keyword -// * @param delay Number indicating the delay before the animation starts -// * @return target || this -// */ -// animate: function (o, ease, delay) { -// if (typeof o === 'object') { -// ease = o.ease -// delay = o.delay -// o = o.duration -// } -// -// var situation = new SVG.Situation({ -// duration: o || 1000, -// delay: delay || 0, -// ease: SVG.easing[ease || '-'] || ease -// }) -// -// this.queue(situation) -// -// return this -// }, -// -// /** -// * sets a delay before the next element of the queue is called -// * @param delay Duration of delay in milliseconds -// * @return this.target() -// */ -// delay: function (delay) { -// // The delay is performed by an empty situation with its duration -// // attribute set to the duration of the delay -// var situation = new SVG.Situation({ -// duration: delay, -// delay: 0, -// ease: SVG.easing['-'] -// }) -// -// return this.queue(situation) -// }, -// -// /** -// * sets or returns the target of this animation -// * @param null || target SVG.Element which should be set as new target -// * @return target || this -// */ -// target: function (target) { -// if (target && target instanceof SVG.Element) { -// this._target = target -// return this -// } -// -// return this._target -// }, -// -// // returns the absolute position at a given time -// timeToAbsPos: function (timestamp) { -// return (timestamp - this.situation.start) / (this.situation.duration / this._speed) -// }, -// -// // returns the timestamp from a given absolute positon -// absPosToTime: function (absPos) { -// return this.situation.duration / this._speed * absPos + this.situation.start -// }, -// -// // starts the animationloop -// startAnimFrame: function () { -// this.stopAnimFrame() -// this.animationFrame = window.requestAnimationFrame(function () { this.step() }.bind(this)) -// }, -// -// // cancels the animationframe -// stopAnimFrame: function () { -// window.cancelAnimationFrame(this.animationFrame) -// }, -// -// // kicks off the animation - only does something when the queue is currently not active and at least one situation is set -// start: function () { -// // dont start if already started -// if (!this.active && this.situation) { -// this.active = true -// this.startCurrent() -// } -// -// return this -// }, -// -// // start the current situation -// startCurrent: function () { -// this.situation.start = +new Date() + this.situation.delay / this._speed -// this.situation.finish = this.situation.start + this.situation.duration / this._speed -// return this.initAnimations().step() -// }, -// -// /** -// * adds a function / Situation to the animation queue -// * @param fn function / situation to add -// * @return this -// */ -// queue: function (fn) { -// if (typeof fn === 'function' || fn instanceof SVG.Situation) { -// this.situations.push(fn) -// } -// -// if (!this.situation) this.situation = this.situations.shift() -// -// return this -// }, -// -// /** -// * pulls next element from the queue and execute it -// * @return this -// */ -// dequeue: function () { -// // stop current animation -// this.stop() -// -// // get next animation from queue -// this.situation = this.situations.shift() -// -// if (this.situation) { -// if (this.situation instanceof SVG.Situation) { -// this.start() -// } else { -// // If it is not a SVG.Situation, then it is a function, we execute it -// this.situation(this) -// } -// } -// -// return this -// }, -// -// // updates all animations to the current state of the element -// // this is important when one property could be changed from another property -// initAnimations: function () { -// var i, j, source -// var s = this.situation -// -// if (s.init) return this -// -// for (i in s.animations) { -// source = this.target()[i]() -// -// if (!Array.isArray(source)) { -// source = [source] -// } -// -// if (!Array.isArray(s.animations[i])) { -// s.animations[i] = [s.animations[i]] -// } -// -// // if(s.animations[i].length > source.length) { -// // source.concat = source.concat(s.animations[i].slice(source.length, s.animations[i].length)) -// // } -// -// for (j = source.length; j--;) { -// // The condition is because some methods return a normal number instead -// // of a SVG.Number -// if (s.animations[i][j] instanceof SVG.Number) { -// source[j] = new SVG.Number(source[j]) -// } -// -// s.animations[i][j] = source[j].morph(s.animations[i][j]) -// } -// } -// -// for (i in s.attrs) { -// s.attrs[i] = new SVG.MorphObj(this.target().attr(i), s.attrs[i]) -// } -// -// for (i in s.styles) { -// s.styles[i] = new SVG.MorphObj(this.target().css(i), s.styles[i]) -// } -// -// s.initialTransformation = this.target().matrixify() -// -// s.init = true -// return this -// }, -// -// clearQueue: function () { -// this.situations = [] -// return this -// }, -// -// clearCurrent: function () { -// this.situation = null -// return this -// }, -// -// /** stops the animation immediately -// * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately. -// * @param clearQueue A Boolean indicating whether to remove queued animation as well. -// * @return this -// */ -// stop: function (jumpToEnd, clearQueue) { -// var active = this.active -// this.active = false -// -// if (clearQueue) { -// this.clearQueue() -// } -// -// if (jumpToEnd && this.situation) { -// // initialize the situation if it was not -// !active && this.startCurrent() -// this.atEnd() -// } -// -// this.stopAnimFrame() -// -// return this.clearCurrent() -// }, -// -// /** resets the element to the state where the current element has started -// * @return this -// */ -// reset: function () { -// if (this.situation) { -// var temp = this.situation -// this.stop() -// this.situation = temp -// this.atStart() -// } -// return this -// }, -// -// // Stop the currently-running animation, remove all queued animations, and complete all animations for the element. -// finish: function () { -// this.stop(true, false) -// -// while (this.dequeue().situation && this.stop(true, false)); -// -// this.clearQueue().clearCurrent() -// -// return this -// }, -// -// // set the internal animation pointer at the start position, before any loops, and updates the visualisation -// atStart: function () { -// return this.at(0, true) -// }, -// -// // set the internal animation pointer at the end position, after all the loops, and updates the visualisation -// atEnd: function () { -// if (this.situation.loops === true) { -// // If in a infinite loop, we end the current iteration -// this.situation.loops = this.situation.loop + 1 -// } -// -// if (typeof this.situation.loops === 'number') { -// // If performing a finite number of loops, we go after all the loops -// return this.at(this.situation.loops, true) -// } else { -// // If no loops, we just go at the end -// return this.at(1, true) -// } -// }, -// -// // set the internal animation pointer to the specified position and updates the visualisation -// // if isAbsPos is true, pos is treated as an absolute position -// at: function (pos, isAbsPos) { -// var durDivSpd = this.situation.duration / this._speed -// -// this.absPos = pos -// // If pos is not an absolute position, we convert it into one -// if (!isAbsPos) { -// if (this.situation.reversed) this.absPos = 1 - this.absPos -// this.absPos += this.situation.loop -// } -// -// this.situation.start = +new Date() - this.absPos * durDivSpd -// this.situation.finish = this.situation.start + durDivSpd -// -// return this.step(true) -// }, -// -// /** -// * sets or returns the speed of the animations -// * @param speed null || Number The new speed of the animations -// * @return Number || this -// */ -// speed: function (speed) { -// if (speed === 0) return this.pause() -// -// if (speed) { -// this._speed = speed -// // We use an absolute position here so that speed can affect the delay before the animation -// return this.at(this.absPos, true) -// } else return this._speed -// }, -// -// // Make loopable -// loop: function (times, reverse) { -// var c = this.last() -// -// // store total loops -// c.loops = (times != null) ? times : true -// c.loop = 0 -// -// if (reverse) c.reversing = true -// return this -// }, -// -// // pauses the animation -// pause: function () { -// this.paused = true -// this.stopAnimFrame() -// -// return this -// }, -// -// // unpause the animation -// play: function () { -// if (!this.paused) return this -// this.paused = false -// // We use an absolute position here so that the delay before the animation can be paused -// return this.at(this.absPos, true) -// }, -// -// /** -// * toggle or set the direction of the animation -// * true sets direction to backwards while false sets it to forwards -// * @param reversed Boolean indicating whether to reverse the animation or not (default: toggle the reverse status) -// * @return this -// */ -// reverse: function (reversed) { -// var c = this.last() -// -// if (typeof reversed === 'undefined') c.reversed = !c.reversed -// else c.reversed = reversed -// -// return this -// }, -// -// /** -// * returns a float from 0-1 indicating the progress of the current animation -// * @param eased Boolean indicating whether the returned position should be eased or not -// * @return number -// */ -// progress: function (easeIt) { -// return easeIt ? this.situation.ease(this.pos) : this.pos -// }, -// -// /** -// * adds a callback function which is called when the current animation is finished -// * @param fn Function which should be executed as callback -// * @return number -// */ -// after: function (fn) { -// var c = this.last() -// function wrapper (e) { -// if (e.detail.situation === c) { -// fn.call(this, c) -// this.off('finished.fx', wrapper) // prevent memory leak -// } -// } -// -// this.target().on('finished.fx', wrapper) -// -// return this._callStart() -// }, -// -// // adds a callback which is called whenever one animation step is performed -// during: function (fn) { -// var c = this.last() -// function wrapper (e) { -// if (e.detail.situation === c) { -// fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, c) -// } -// } -// -// // see above -// this.target().off('during.fx', wrapper).on('during.fx', wrapper) -// -// this.after(function () { -// this.off('during.fx', wrapper) -// }) -// -// return this._callStart() -// }, -// -// // calls after ALL animations in the queue are finished -// afterAll: function (fn) { -// var wrapper = function wrapper (e) { -// fn.call(this) -// this.off('allfinished.fx', wrapper) -// } -// -// // see above -// this.target().off('allfinished.fx', wrapper).on('allfinished.fx', wrapper) -// -// return this._callStart() -// }, -// -// // calls on every animation step for all animations -// duringAll: function (fn) { -// var wrapper = function (e) { -// fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, e.detail.situation) -// } -// -// this.target().off('during.fx', wrapper).on('during.fx', wrapper) -// -// this.afterAll(function () { -// this.off('during.fx', wrapper) -// }) -// -// return this._callStart() -// }, -// -// last: function () { -// return this.situations.length ? this.situations[this.situations.length - 1] : this.situation -// }, -// -// // adds one property to the animations -// add: function (method, args, type) { -// this.last()[type || 'animations'][method] = args -// return this._callStart() -// }, -// -// /** perform one step of the animation -// * @param ignoreTime Boolean indicating whether to ignore time and use position directly or recalculate position based on time -// * @return this -// */ -// step: function (ignoreTime) { -// // convert current time to an absolute position -// if (!ignoreTime) this.absPos = this.timeToAbsPos(+new Date()) -// -// // This part convert an absolute position to a position -// if (this.situation.loops !== false) { -// var absPos, absPosInt, lastLoop -// -// // If the absolute position is below 0, we just treat it as if it was 0 -// absPos = Math.max(this.absPos, 0) -// absPosInt = Math.floor(absPos) -// -// if (this.situation.loops === true || absPosInt < this.situation.loops) { -// this.pos = absPos - absPosInt -// lastLoop = this.situation.loop -// this.situation.loop = absPosInt -// } else { -// this.absPos = this.situation.loops -// this.pos = 1 -// // The -1 here is because we don't want to toggle reversed when all the loops have been completed -// lastLoop = this.situation.loop - 1 -// this.situation.loop = this.situation.loops -// } -// -// if (this.situation.reversing) { -// // Toggle reversed if an odd number of loops as occured since the last call of step -// this.situation.reversed = this.situation.reversed !== Boolean((this.situation.loop - lastLoop) % 2) -// } -// } else { -// // If there are no loop, the absolute position must not be above 1 -// this.absPos = Math.min(this.absPos, 1) -// this.pos = this.absPos -// } -// -// // while the absolute position can be below 0, the position must not be below 0 -// if (this.pos < 0) this.pos = 0 -// -// if (this.situation.reversed) this.pos = 1 - this.pos -// -// // apply easing -// var eased = this.situation.ease(this.pos) -// -// // call once-callbacks -// for (var i in this.situation.once) { -// if (i > this.lastPos && i <= eased) { -// this.situation.once[i].call(this.target(), this.pos, eased) -// delete this.situation.once[i] -// } -// } -// -// // fire during callback with position, eased position and current situation as parameter -// if (this.active) this.target().fire('during', {pos: this.pos, eased: eased, fx: this, situation: this.situation}) -// -// // the user may call stop or finish in the during callback -// // so make sure that we still have a valid situation -// if (!this.situation) { -// return this -// } -// -// // apply the actual animation to every property -// this.eachAt() -// -// // do final code when situation is finished -// if ((this.pos === 1 && !this.situation.reversed) || (this.situation.reversed && this.pos === 0)) { -// // stop animation callback -// this.stopAnimFrame() -// -// // fire finished callback with current situation as parameter -// this.target().fire('finished', {fx: this, situation: this.situation}) -// -// if (!this.situations.length) { -// this.target().fire('allfinished') -// -// // Recheck the length since the user may call animate in the afterAll callback -// if (!this.situations.length) { -// this.target().off('.fx') // there shouldnt be any binding left, but to make sure... -// this.active = false -// } -// } -// -// // start next animation -// if (this.active) this.dequeue() -// else this.clearCurrent() -// } else if (!this.paused && this.active) { -// // we continue animating when we are not at the end -// this.startAnimFrame() -// } -// -// // save last eased position for once callback triggering -// this.lastPos = eased -// return this -// }, -// -// // calculates the step for every property and calls block with it -// eachAt: function () { -// var i, at -// var self = this -// var target = this.target() -// var s = this.situation -// -// // apply animations which can be called trough a method -// for (i in s.animations) { -// at = [].concat(s.animations[i]).map(function (el) { -// return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el -// }) -// -// target[i].apply(target, at) -// } -// -// // apply animation which has to be applied with attr() -// for (i in s.attrs) { -// at = [i].concat(s.attrs[i]).map(function (el) { -// return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el -// }) -// -// target.attr.apply(target, at) -// } -// -// // apply animation which has to be applied with css() -// for (i in s.styles) { -// at = [i].concat(s.styles[i]).map(function (el) { -// return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el -// }) -// -// target.css.apply(target, at) -// } -// -// // animate initialTransformation which has to be chained -// if (s.transforms.length) { -// -// // TODO: ANIMATE THE TRANSFORMS -// -// // // get initial initialTransformation -// // at = s.initialTransformation -// // for(i = 0, len = s.transforms.length; i < len; i++){ -// // -// // // get next transformation in chain -// // var a = s.transforms[i] -// // -// // // multiply matrix directly -// // if(a instanceof SVG.Matrix){ -// // -// // if(a.relative){ -// // at = at.multiply(new SVG.Matrix().morph(a).at(s.ease(this.pos))) -// // }else{ -// // at = at.morph(a).at(s.ease(this.pos)) -// // } -// // continue -// // } -// // -// // // when transformation is absolute we have to reset the needed transformation first -// // if(!a.relative) -// // a.undo(at.decompose()) -// // -// // // and reapply it after -// // at = at.multiply(a.at(s.ease(this.pos))) -// // -// // } -// // -// // // set new matrix on element -// // target.matrix(at) -// } -// -// return this -// }, -// -// // adds an once-callback which is called at a specific position and never again -// once: function (pos, fn, isEased) { -// var c = this.last() -// if (!isEased) pos = c.ease(pos) -// -// c.once[pos] = fn -// -// return this -// }, -// -// _callStart: function () { -// setTimeout(function () { this.start() }.bind(this), 0) -// return this -// } -// -// }, -// -// parent: SVG.Element, -// -// // Add method to parent elements -// construct: { -// // Get fx module or create a new one, then animate with given duration and ease -// animate: function (o, ease, delay) { -// return (this.fx || (this.fx = new SVG.Timeline(this))).animate(o, ease, delay) -// }, -// delay: function (delay) { -// return (this.fx || (this.fx = new SVG.Timeline(this))).delay(delay) -// }, -// stop: function (jumpToEnd, clearQueue) { -// if (this.fx) { -// this.fx.stop(jumpToEnd, clearQueue) -// } -// -// return this -// }, -// finish: function () { -// if (this.fx) { -// this.fx.finish() -// } -// -// return this -// }, -// // Pause current animation -// pause: function () { -// if (this.fx) { -// this.fx.pause() -// } -// -// return this -// }, -// // Play paused current animation -// play: function () { -// if (this.fx) { this.fx.play() } -// -// return this -// }, -// // Set/Get the speed of the animations -// speed: function (speed) { -// if (this.fx) { -// if (speed == null) { return this.fx.speed() } else { this.fx.speed(speed) } -// } -// -// return this -// } -// } -// -// }) -// -// // MorphObj is used whenever no morphable object is given -// SVG.MorphObj = SVG.invent({ -// -// create: function (from, to) { -// // prepare color for morphing -// if (SVG.Color.isColor(to)) return new SVG.Color(from).morph(to) -// // prepare value list for morphing -// if (SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to) -// // prepare number for morphing -// if (SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) -// -// // prepare for plain morphing -// this.value = from -// this.destination = to -// }, -// -// extend: { -// at: function (pos, real) { -// return real < 1 ? this.value : this.destination -// }, -// -// valueOf: function () { -// return this.value -// } -// } -// -// }) -// -// SVG.extend(SVG.Timeline, { -// // Add animatable attributes -// attr: function (a, v, relative) { -// // apply attributes individually -// if (typeof a === 'object') { -// for (var key in a) { -// this.attr(key, a[key]) -// } -// } else { -// this.add(a, v, 'attrs') -// } -// -// return this -// }, -// // Add animatable styles -// css: function (s, v) { -// if (typeof s === 'object') { -// for (var key in s) { -// this.css(key, s[key]) -// } -// } else { -// this.add(s, v, 'styles') -// } -// -// return this -// }, -// // Animatable x-axis -// x: function (x, relative) { -// if (this.target() instanceof SVG.G) { -// this.transform({x: x}, relative) -// return this -// } -// -// var num = new SVG.Number(x) -// num.relative = relative -// return this.add('x', num) -// }, -// // Animatable y-axis -// y: function (y, relative) { -// if (this.target() instanceof SVG.G) { -// this.transform({y: y}, relative) -// return this -// } -// -// var num = new SVG.Number(y) -// num.relative = relative -// return this.add('y', num) -// }, -// // Animatable center x-axis -// cx: function (x) { -// return this.add('cx', new SVG.Number(x)) -// }, -// // Animatable center y-axis -// cy: function (y) { -// return this.add('cy', new SVG.Number(y)) -// }, -// // Add animatable move -// move: function (x, y) { -// return this.x(x).y(y) -// }, -// // Add animatable center -// center: function (x, y) { -// return this.cx(x).cy(y) -// }, -// // Add animatable size -// size: function (width, height) { -// if (this.target() instanceof SVG.Text) { -// // animate font size for Text elements -// this.attr('font-size', width) -// } else { -// // animate bbox based size for all other elements -// var box -// -// if (!width || !height) { -// box = this.target().bbox() -// } -// -// if (!width) { -// width = box.width / box.height * height -// } -// -// if (!height) { -// height = box.height / box.width * width -// } -// -// this.add('width', new SVG.Number(width)) -// .add('height', new SVG.Number(height)) -// } -// -// return this -// }, -// // Add animatable width -// width: function (width) { -// return this.add('width', new SVG.Number(width)) -// }, -// // Add animatable height -// height: function (height) { -// return this.add('height', new SVG.Number(height)) -// }, -// // Add animatable plot -// plot: function (a, b, c, d) { -// // Lines can be plotted with 4 arguments -// if (arguments.length === 4) { -// return this.plot([a, b, c, d]) -// } -// -// return this.add('plot', new (this.target().MorphArray)(a)) -// }, -// // Add leading method -// leading: function (value) { -// return this.target().leading -// ? this.add('leading', new SVG.Number(value)) -// : this -// }, -// // Add animatable viewbox -// viewbox: function (x, y, width, height) { -// if (this.target() instanceof SVG.Container) { -// this.add('viewbox', new SVG.Box(x, y, width, height)) -// } -// -// return this -// }, -// update: function (o) { -// if (this.target() instanceof SVG.Stop) { -// if (typeof o === 'number' || o instanceof SVG.Number) { -// return this.update({ -// offset: arguments[0], -// color: arguments[1], -// opacity: arguments[2] -// }) -// } -// -// if (o.opacity != null) this.attr('stop-opacity', o.opacity) -// if (o.color != null) this.attr('stop-color', o.color) -// if (o.offset != null) this.attr('offset', o.offset) -// } -// -// return this -// } -// }) diff --git a/src/helpers.js b/src/helpers.js index b4bddf9..289b59d 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,29 +1,7 @@ -import {Doc, Point, Element} from './classes.js' -import {adopt, eid, makeNode} from './tools.js' +import Point from './Point.js' +import {eid, makeNode} from './tools.js' import {dots, reference} from './regex.js' - -export function makeInstance (element, makeNested) { - if (element instanceof Element) return element - - if (typeof element === 'object') { - return adopt(element) - } - - if (element == null) { - return new Doc() - } - - if (typeof element === 'string' && element.charAt(0) !== '<') { - return adopt(document.querySelector(element)) - } - - var node = makeNode('svg') - node.innerHTML = element - - element = adopt(node.firstElementChild) - - return element -} +import {adopt} from './adopter.js' export function isNulledBox (box) { return !box.w && !box.h && !box.x && !box.y @@ -55,7 +33,7 @@ export function arrayClone (arr) { } // tests if a given selector matches an element -export function matches (el, selector) { +export function matcher (el, selector) { return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector) } diff --git a/src/memory.js b/src/memory.js index bc13196..76bcfa6 100644 --- a/src/memory.js +++ b/src/memory.js @@ -1,38 +1,41 @@ -import Element from './Element.js' -extend(Element, { - // Remember arbitrary data - remember (k, v) { - // remember every item in an object individually - if (typeof arguments[0] === 'object') { - for (var key in k) { - this.remember(key, k[key]) - } - } else if (arguments.length === 1) { - // retrieve memory - return this.memory()[k] - } else { - // store memory - this.memory()[k] = v +export const name = 'Memory' + +export function setup (node) { + this._memory = {} +} + +// Remember arbitrary data +export function remember (k, v) { + // remember every item in an object individually + if (typeof arguments[0] === 'object') { + for (var key in k) { + this.remember(key, k[key]) } + } else if (arguments.length === 1) { + // retrieve memory + return this.memory()[k] + } else { + // store memory + this.memory()[k] = v + } - return this - }, + return this +}, // Erase a given memory - forget () { - if (arguments.length === 0) { - this._memory = {} - } else { - for (var i = arguments.length - 1; i >= 0; i--) { - delete this.memory()[arguments[i]] - } +export function forget () { + if (arguments.length === 0) { + this._memory = {} + } else { + for (var i = arguments.length - 1; i >= 0; i--) { + delete this.memory()[arguments[i]] } - return this } + return this +} // Initialize or return local memory object - memory () { - return this._memory || (this._memory = {}) - } -}) +export function memory () { + return this._memory +} diff --git a/src/parser.js b/src/parser.js index c51ad71..9a64dbc 100644 --- a/src/parser.js +++ b/src/parser.js @@ -1,6 +1,22 @@ import Doc from './Doc.js' -let parser = function () { +export default function parser () { + + // Reuse cached element if possible + if (!parser.nodes) { + let svg = new Doc().size(2, 0).css({ + opacity: 0, + position: 'absolute', + left: '-100%', + top: '-100%', + overflow: 'hidden' + }) + + let path = svg.path().node + + parser.nodes = {svg, path} + } + if (!parser.nodes.svg.node.parentNode) { let b = document.body || document.documentElement parser.nodes.svg.addTo(b) @@ -8,17 +24,3 @@ let parser = function () { return parser.nodes } - -parser.nodes = { - svg: new Doc().size(2, 0).css({ - opacity: 0, - position: 'absolute', - left: '-100%', - top: '-100%', - overflow: 'hidden' - }) -} - -parser.nodes.path = parser.nodes.svg.path().node - -export default parser diff --git a/src/poly.js b/src/poly.js new file mode 100644 index 0000000..e8edbed --- /dev/null +++ b/src/poly.js @@ -0,0 +1,30 @@ +// Add polygon-specific functions + +// Get array +export function array () { + return this._array || (this._array = new PointArray(this.attr('points'))) +} + +// Plot new path +export function plot (p) { + return (p == null) ? this.array() + : this.clear().attr('points', typeof p === 'string' ? p + : (this._array = new PointArray(p))) +} + +// Clear array cache +export function clear () { + delete this._array + return this +} + +// Move by left top corner +export function move (x, y) { + return this.attr('points', this.array().move(x, y)) +} + +// Set element size to given width and height +export function size (width, height) { + let p = proportionalSize(this, width, height) + return this.attr('points', this.array().size(p.width, p.height)) +} diff --git a/src/selector.js b/src/selector.js index a148b56..98a3e3b 100644 --- a/src/selector.js +++ b/src/selector.js @@ -1,6 +1,6 @@ import {idFromReference} from './helpers.js' import {map} from './utils.js' -import {adopt} from './tools.js' +import {adopt} from './adopter.js' // // Method for getting an element by id // SVG.get = function (id) { @@ -31,13 +31,12 @@ export default function find (query, parent) { }) } -export let mixings = { - // Scoped select method - select: function (query) { - return find(query, this.node) - } + +export function select (query) { + return find(query, this.node) } + // extend(SVG.Parent, { // // Scoped select method // select: function (query) { diff --git a/src/set.js b/src/set.js new file mode 100644 index 0000000..5352570 --- /dev/null +++ b/src/set.js @@ -0,0 +1,17 @@ +SVG.Set = class extends Set { + // constructor (arr) { + // super(arr) + // } + + each (cbOrName, ...args) { + if (typeof cbOrName === 'function') { + this.forEach((el) => { cbOrName.call(el, el) }) + } else { + this.forEach((el) => { + el[cbOrName](...args) + }) + } + + return this + } +} @@ -1,6 +1,90 @@ -import {makeInstance} from './helpers.js' +// import {extend} from './tools.js' +// import * as Element from './Element.js' +// import Defs from './Defs.js' +// +// extend(Defs, [EventTarget, Element, Parent]) + +import {makeInstance} from './adopter.js' import * as Classes from './classes.js' +import * as adopter from './adopter.js' import * as tools from './tools.js' +import * as containers from './containers.js' +import * as elements from './elements.js' +import * as arrange from './arrange.js' +import {select} from './selector.js' +import * as css from './css.js' +import * as transform from './transform.js' +const extend = tools.extend + +import * as EventTarget from './EventTarget.js' +import * as Element from './Element.js' +import * as Parent from './Parent.js' + +extend([ + Classes.Doc, + Classes.Symbol, + Classes.Image, + Classes.Pattern, + Classes.Marker +], {viewbox: Classes.Box.constructors.viewbox}) + +extend([Classes.Line, Classes.Polyline, Classes.Polygon, Classes.Path], { + ...Classes.Marker.constructors.marker +}) + +extend(Classes.Text, Classes.TextPath.constructors.Text) +extend(Classes.Path, Classes.TextPath.constructors.Path) + +extend(Classes.Defs, { + ...Classes.Gradient.constructors.Defs, + ...Classes.Marker.constructors.Defs, + ...Classes.Pattern.constructors.Defs, +}) + +for (let i in containers) { + extend(containers[i], { + ...Classes.A.constructors.Container, + ...Classes.ClipPath.constructors.Container, + ...Classes.G.constructors.Container, + ...Classes.Gradient.constructors.Container, + ...Classes.Line.constructors.Container, + ...Classes.Marker.constructors.Container, + ...Classes.Mask.constructors.Container, + ...Classes.Path.constructors.Container, + ...Classes.Pattern.constructors.Container, + ...Classes.Polygon.constructors.Container, + ...Classes.Polyline.constructors.Container, + ...Classes.Rect.constructors.Container, + select, + ...Classes.Symbol.constructors.Container, + ...Classes.Text.constructors.Container, + ...Classes.TextPath.constructors.Container, + ...Classes.Use.constructors.Container, + }) +} + +for (let i in elements) { + extend(elements[i], { + ...EventTarget, + ...Element, + ...Parent, + ...arrange, + ...Classes.A.constructors.Element, + ...Classes.Box.constructors.Element, + ...Classes.Circle.constructors.Element, + ...Classes.ClipPath.constructors.Element, + ...css, + ...Classes.Image.constructors.Element, + ...Classes.Mask.constructors.Element, + ...Classes.Matrix.constructors.Element, + ...Classes.Point.constructors.Element, + ...Classes.Runner.constructors.Element, + ...Classes.Timeline.constructors.Element, + ...transform, + }) +} + + // The main wrapping element export default function SVG (element) { @@ -9,3 +93,4 @@ export default function SVG (element) { Object.assign(SVG, Classes) Object.assign(SVG, tools) +Object.assign(SVG, adopter) diff --git a/src/textable.js b/src/textable.js new file mode 100644 index 0000000..f61f04a --- /dev/null +++ b/src/textable.js @@ -0,0 +1,35 @@ +import Tspan from './Tspan.js' + +// Create plain text node +export function plain (text) { + // clear if build mode is disabled + if (this._build === false) { + this.clear() + } + + // create text node + this.node.appendChild(document.createTextNode(text)) + + return this +} + + // Create a tspan +export function tspan (text) { + var tspan = new Tspan() + + // clear if build mode is disabled + if (!this._build) { + this.clear() + } + + // add new tspan + this.node.appendChild(tspan.node) + + return tspan.text(text) +} + +// FIXME: Does this also work for textpath? +// Get length of text element +export function length () { + return this.node.getComputedTextLength() +} diff --git a/src/tools.js b/src/tools.js index e77d653..2733af4 100644 --- a/src/tools.js +++ b/src/tools.js @@ -1,5 +1,5 @@ import {ns} from './namespaces.js' -import {Container, Element, HtmlNode, Doc, Gradient, Parent} from './classes.js' +import {capitalize} from './helpers.js' // Element id sequence let did = 1000 @@ -23,13 +23,22 @@ export function makeNode (name) { export function extend (modules, methods) { var key, i + if (Array.isArray(methods)) { + methods.forEach((method) => { + extend(modules, method) + }) + return + } + modules = Array.isArray(modules) ? modules : [modules] for (i = modules.length - 1; i >= 0; i--) { - if (modules[i]) { - for (key in methods) { - modules[i].prototype[key] = methods[key] - } + if (methods.name) { + modules[i].extensions = (modules[i].extensions || []).concat(methods) + } + for (key in methods) { + if (modules[i].prototype[key] || key == 'name' || key == 'setup') continue + modules[i].prototype[key] = methods[key] } } } @@ -63,32 +72,3 @@ export function invent (config) { return initializer } - -// Adopt existing svg elements -export function adopt (node) { - // check for presence of node - if (!node) return null - - // make sure a node isn't already adopted - if (node.instance instanceof Element) return node.instance - - if (!(node instanceof window.SVGElement)) { - return new HtmlNode(node) - } - - // initialize variables - var element - - // adopt with element-specific settings - if (node.nodeName === 'svg') { - element = new Doc(node) - } else if (node.nodeName === 'linearGradient' || node.nodeName === 'radialGradient') { - element = new Gradient(node) - } else if (SVG[capitalize(node.nodeName)]) { - element = new SVG[capitalize(node.nodeName)](node) - } else { - element = new Parent(node) - } - - return element -} diff --git a/src/utilities.js b/src/utilities.js deleted file mode 100644 index 01b8ba5..0000000 --- a/src/utilities.js +++ /dev/null @@ -1,43 +0,0 @@ - -SVG.utils = { - // Map function - map: function (array, block) { - var i - var il = array.length - var result = [] - - for (i = 0; i < il; i++) { - result.push(block(array[i])) - } - - return result - }, - - // Filter function - filter: function (array, block) { - var i - var il = array.length - var result = [] - - for (i = 0; i < il; i++) { - if (block(array[i])) { result.push(array[i]) } - } - - return result - }, - - // Degrees to radians - radians: function (d) { - return d % 360 * Math.PI / 180 - }, - - // Radians to degrees - degrees: function (r) { - return r * 180 / Math.PI % 360 - }, - - filterSVGElements: function (nodes) { - return this.filter(nodes, function (el) { return el instanceof window.SVGElement }) - } - -} |