summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlrich-Matthias Schäfer <ulima.ums@googlemail.com>2020-06-01 18:14:34 +1000
committerGitHub <noreply@github.com>2020-06-01 18:14:34 +1000
commit5bd6906f48ad528cb3d3ee980c5bd537b703d953 (patch)
treec686546c3cc19afd56970aeef7ca3d84d98f8e8c
parent09ad1754ae8e8962272d6b68742abe783f2f5b8a (diff)
parentf9055655cec603e2cb7d01c2307a1eafab1b3f2e (diff)
downloadsvg.js-5bd6906f48ad528cb3d3ee980c5bd537b703d953.tar.gz
svg.js-5bd6906f48ad528cb3d3ee980c5bd537b703d953.zip
Merge branch 'master' into some-more-typings
-rw-r--r--.eslintrc.json85
-rw-r--r--.eslintrc.yaml10
-rw-r--r--.github/FUNDING.yml3
-rw-r--r--CHANGELOG.md1
-rw-r--r--README.md14
-rw-r--r--package.json18
-rw-r--r--spec/spec/elements/Dom.js2
-rw-r--r--spec/spec/elements/G.js269
-rw-r--r--spec/spec/modules/core/circled.js2
-rw-r--r--spec/spec/modules/core/containerGeometry.js271
-rw-r--r--spec/spec/modules/core/selector.js8
-rw-r--r--spec/spec/utils/adopter.js2
-rw-r--r--src/animation/Controller.js7
-rw-r--r--src/animation/Morphable.js85
-rw-r--r--src/animation/Queue.js45
-rw-r--r--src/animation/Runner.js416
-rw-r--r--src/animation/Timeline.js179
-rw-r--r--src/elements/A.js14
-rw-r--r--src/elements/Element.js12
-rw-r--r--src/elements/G.js74
-rw-r--r--src/elements/Marker.js17
-rw-r--r--src/elements/Text.js95
-rw-r--r--src/elements/TextPath.js2
-rw-r--r--src/elements/Tspan.js31
-rw-r--r--src/modules/core/containerGeometry.js69
-rw-r--r--src/types/Box.js37
-rw-r--r--src/types/Color.js387
-rw-r--r--src/types/Matrix.js419
-rw-r--r--src/types/PathArray.js107
-rw-r--r--src/types/Point.js13
-rw-r--r--src/types/PointArray.js123
-rw-r--r--src/types/SVGArray.js31
-rw-r--r--src/types/SVGNumber.js59
-rw-r--r--svg.js.d.ts24
34 files changed, 1529 insertions, 1402 deletions
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..b6f110d
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,85 @@
+{
+ "extends": "standard",
+ "plugins": [ "sort-class-members" ],
+ "rules": {
+ "operator-linebreak": [ "error", "before" ],
+ "object-curly-spacing": [ "error", "always" ],
+ "array-bracket-spacing": [ "error", "always" ],
+ "indent": [ "error", 2, { "flatTernaryExpressions": true } ],
+ "padded-blocks": "off",
+ "sort-class-members/sort-class-members": [ 2, {
+ "order": [
+ "[static-properties]",
+ "[properties]",
+ "[conventional-private-properties]",
+ "constructor",
+ "[static-methods]",
+ "[methods]",
+ "[conventional-private-methods]",
+ "[accessor-pairs]",
+ "[getters]",
+ "[setters]",
+ "[everything-else]"
+ ],
+ "groups": {
+ "constructor": {
+ "name": "constructor",
+ "type": "method",
+ "sort": "alphabetical"
+ },
+ "properties": {
+ "type": "property",
+ "sort": "alphabetical"
+ },
+ "getters": {
+ "kind": "get",
+ "sort": "alphabetical"
+ },
+ "setters": {
+ "kind": "set",
+ "sort": "alphabetical"
+ },
+ "accessor-pairs": {
+ "accessorPair": true,
+ "sort": "alphabetical"
+ },
+ "static-properties": {
+ "type": "property",
+ "static": true,
+ "sort": "alphabetical"
+ },
+ "conventional-private-properties": {
+ "type": "property",
+ "name": "/_.+/",
+ "sort": "alphabetical"
+ },
+ "arrow-function-properties": {
+ "propertyType": "ArrowFunctionExpression",
+ "sort": "alphabetical"
+ },
+ "methods": {
+ "type": "method",
+ "sort": "alphabetical"
+ },
+ "static-methods": {
+ "type": "method",
+ "static": true,
+ "sort": "alphabetical"
+ },
+ "async-methods": {
+ "type": "method",
+ "async": true,
+ "sort": "alphabetical"
+ },
+ "conventional-private-methods": {
+ "type": "method",
+ "name": "/_.+/",
+ "sort": "alphabetical"
+ },
+ "everything-else": {
+ "sort": "alphabetical"
+ }
+ }
+ }]
+ }
+} \ No newline at end of file
diff --git a/.eslintrc.yaml b/.eslintrc.yaml
deleted file mode 100644
index cc037d8..0000000
--- a/.eslintrc.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-extends: standard
-rules:
- operator-linebreak: [ error, before ]
- object-curly-spacing: [ error, always ]
- array-bracket-spacing: [ error, always ]
- indent: [ error, 2, {
- flatTernaryExpressions: true
- }]
- padded-blocks: off
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..2f88022
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+# These are supported funding model platforms
+
+github: [fuzzyma]
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90bd0f5..20b163c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -54,6 +54,7 @@ The document follows the conventions described in [“Keep a CHANGELOG”](http:
- added possibility to pass a transform object to `PointArray.transform()` similar to Point
- added `with-last` as `when` to `animate` and `schedule` to let an animation start with the start of the last one in the timeline
- added lots of tests in es6 format
+ - added geometry and positioning methods to `A` (#1110)
### Deleted
- deleted undocumented `Matrix.compose()` method which did the same as `new Matrix()` or `Matrix.transform()`
diff --git a/README.md b/README.md
index 744d712..55c0879 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,12 @@
[![Build Status](https://travis-ci.org/svgdotjs/svg.js.svg?branch=master)](https://travis-ci.org/svgdotjs/svg.js)
[![Coverage Status](https://coveralls.io/repos/github/svgdotjs/svg.js/badge.svg?branch=master)](https://coveralls.io/github/svgdotjs/svg.js?branch=master)
-[![CDNJS](https://img.shields.io/cdnjs/v/svg.js.svg)](https://cdnjs.com/libraries/svg.js)
+[![Cdnjs](https://img.shields.io/cdnjs/v/svg.js.svg)](https://cdnjs.com/libraries/svg.js)
+[![jsdelivr](https://badgen.net/jsdelivr/v/npm/@svgdotjs/svg.js)](https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js)
[![Join the chat at https://gitter.im/svgdotjs/svg.js](https://badges.gitter.im/svgdotjs/svg.js.svg)](https://gitter.im/svgdotjs/svg.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![Twitter](https://img.shields.io/badge/Twitter-@svg__js-green.svg)](https://twitter.com/svg_js)
+
+
__A lightweight library for manipulating and animating SVG, without any dependencies.__
@@ -19,11 +23,13 @@ SVG.js is licensed under the terms of the MIT License.
`yarn add @svgdotjs/svg.js`
-#### Cdnjs:
+#### CDNs:
-[https://cdnjs.com/libraries/svg.js](https://cdnjs.com/libraries/svg.js)
+[https://cdnjs.com/libraries/svg.js](https://cdnjs.com/libraries/svg.js)
+[https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js](https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js)
+[https://unpkg.com/@svgdotjs/svg.js](https://unpkg.com/@svgdotjs/svg.js)
## Documentation
Check [svgjs.com](https://svgjs.com/docs/3.0/) to learn more.
-[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=ulima.ums%40googlemail.com&lc=US&item_name=SVG.JS&currency_code=EUR&bn=PP-DonationsBF%3Abtn_donate_74x21.png%3ANonHostedGuest)
+[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=ulima.ums%40googlemail.com&lc=US&item_name=SVG.JS&currency_code=EUR&bn=PP-DonationsBF%3Abtn_donate_74x21.png%3ANonHostedGuest) or [![Sponsor](https://img.shields.io/badge/Sponsor-svg.js-green.svg)](https://github.com/sponsors/Fuzzyma)
diff --git a/package.json b/package.json
index f5e3921..5b367a2 100644
--- a/package.json
+++ b/package.json
@@ -55,6 +55,10 @@
},
"github": "https://github.com/svgdotjs/svg.js",
"license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Fuzzyma"
+ },
"typings": "./svg.js.d.ts",
"scripts": {
"build": "npm run fix && npm run rollup",
@@ -89,34 +93,34 @@
"babel-eslint": "^10.1.0",
"core-js": "^3.6.5",
"coveralls": "^3.1.0",
- "eslint": "^6.8.0",
+ "eslint": "^7.0.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
+ "eslint-plugin-sort-class-members": "^1.7.0",
"eslint-plugin-standard": "^4.0.1",
"esm": "^3.2.25",
"http-server": "^0.12.3",
"jasmine": "^3.5.0",
"jasmine-core": "^3.5.0",
- "karma": "^5.0.4",
+ "karma": "^5.0.5",
"karma-chrome-launcher": "^3.1.0",
"karma-coverage": "^2.0.2",
"karma-firefox-launcher": "^1.3.0",
"karma-jasmine": "^3.1.1",
"karma-sauce-launcher": "^4.1.4",
- "rollup": "^2.7.6",
+ "rollup": "^2.9.0",
"rollup-plugin-filesize": "^8.0.2",
"rollup-plugin-terser": "^5.3.0",
- "svgdom": "^0.1.3",
"typescript": "^3.8.3",
- "yargs": "^15.3.1"
+ "yargs": "^15.3.1",
+ "svgdom": "^0.1.8"
},
"browserslist": [
"last 1 version",
"> 0.25%",
"not maintained node versions",
"not dead"
- ],
- "dependencies": {}
+ ]
}
diff --git a/spec/spec/elements/Dom.js b/spec/spec/elements/Dom.js
index bcf0ed5..2e36c92 100644
--- a/spec/spec/elements/Dom.js
+++ b/spec/spec/elements/Dom.js
@@ -656,7 +656,7 @@ describe('Dom.js', function () {
it('works without a parent', () => {
const canvas = new Svg()
- expect(canvas.xml('<rect /><circle>', undefined, svg)).toBe(canvas)
+ expect(canvas.xml('<rect /><circle />', undefined, svg)).toBe(canvas)
})
})
diff --git a/spec/spec/elements/G.js b/spec/spec/elements/G.js
index 2cd73fe..58dc07d 100644
--- a/spec/spec/elements/G.js
+++ b/spec/spec/elements/G.js
@@ -1,8 +1,8 @@
-/* globals describe, expect, it, jasmine, spyOn, container */
+/* globals describe, expect, it, jasmine, container */
-import { Box, G, Rect, SVG } from '../../../src/main.js'
+import { G, SVG } from '../../../src/main.js'
-const { any, objectContaining } = jasmine
+const { any } = jasmine
describe('G.js', () => {
@@ -26,267 +26,4 @@ describe('G.js', () => {
})
})
})
-
- describe('dmove()', () => {
- it('moves the bbox of the group by a certain amount (1)', () => {
- const canvas = SVG().addTo(container)
- const g = canvas.group()
-
- g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
- g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
-
- g.dmove(10, 10)
-
- const box = g.bbox()
- expect(box).toEqual(objectContaining({
- x: 20, y: 30, width: box.width, height: box.height
- }))
- })
-
- it('moves the bbox of the group by a certain amount (2)', () => {
- const canvas = SVG().addTo(container)
- const g = canvas.group()
-
- g.rect(400, 200).move(123, 312).rotate(34).skew(12)
- g.rect(100, 50).move(11, 43).translate(123, 32).skew(-12)
- g.rect(400, 200).rotate(90)
- g.group().rotate(23).group().skew(32).rect(100, 40).skew(11).rotate(12)
-
- const oldBox = g.bbox()
-
- g.dmove(10, 10)
-
- const newBox = g.bbox()
-
- expect(newBox.x).toBeCloseTo(oldBox.x + 10, 4)
- expect(newBox.y).toBeCloseTo(oldBox.y + 10, 4)
- expect(newBox.w).toBeCloseTo(oldBox.w, 4)
- expect(newBox.h).toBeCloseTo(oldBox.h, 4)
- })
- })
-
- describe('dx()', () => {
- it('calls dmove with dy=0 and returns itself', () => {
- const canvas = SVG().addTo(container)
- const g = canvas.group()
- const spy = spyOn(g, 'dmove').and.callThrough()
- expect(g.dx(10)).toBe(g)
- expect(spy).toHaveBeenCalledWith(10, 0)
- })
- })
-
- describe('dy()', () => {
- it('calls dmove with dx=0 and returns itself', () => {
- const canvas = SVG().addTo(container)
- const g = canvas.group()
- const spy = spyOn(g, 'dmove').and.callThrough()
- expect(g.dy(10)).toBe(g)
- expect(spy).toHaveBeenCalledWith(0, 10)
- })
- })
-
- describe('move()', () => {
- it('calls dmove() with the correct difference', () => {
- const canvas = SVG().addTo(container)
- const g = canvas.group()
- g.rect(100, 200).move(111, 223)
-
- spyOn(g, 'dmove')
-
- g.move(100, 150)
- expect(g.dmove).toHaveBeenCalledWith(-11, -73)
- })
-
- it('defaults to x=0 and y=0', () => {
- const canvas = SVG().addTo(container)
- const g = canvas.group()
- g.rect(100, 200).move(111, 223)
-
- spyOn(g, 'dmove')
-
- g.move()
- expect(g.dmove).toHaveBeenCalledWith(-111, -223)
- })
- })
-
- describe('x()', () => {
- it('gets the x value of the bbox', () => {
- const canvas = SVG().addTo(container)
-
- const g = new G()
- g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
- g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
-
- g.addTo(canvas)
-
- expect(g.x()).toBe(g.bbox().x)
- expect(g.x()).toBe(10)
- })
- it('calls move with the paramater as x', () => {
- const canvas = SVG().addTo(container)
- const g = canvas.group()
- g.rect(100, 200).move(111, 223)
-
- spyOn(g, 'move')
-
- g.x(100)
- expect(g.move).toHaveBeenCalledWith(100, g.bbox().y, any(Box))
- })
- })
-
- describe('y()', () => {
- it('gets the y value of the bbox', () => {
- const canvas = SVG().addTo(container)
-
- const g = new G()
- g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
- g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
-
- g.addTo(canvas)
-
- expect(g.y()).toBe(g.bbox().y)
- expect(g.y()).toBe(20)
- })
-
- it('calls move with the paramater as y', () => {
- const canvas = SVG().addTo(container)
- const g = canvas.group()
- g.rect(100, 200).move(111, 223)
-
- spyOn(g, 'move')
-
- g.y(100)
- expect(g.move).toHaveBeenCalledWith(g.bbox().x, 100, any(Box))
- })
- })
-
- describe('size()', () => {
- it('changes the dimensions of the bbox (1)', () => {
- const canvas = SVG().addTo(container)
-
- const g = new G()
- g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
- g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
-
- g.addTo(canvas)
-
- const oldBox = g.bbox()
-
- expect(g.size(100, 100)).toBe(g)
-
- const newBox = g.bbox()
-
- expect(newBox.x).toBeCloseTo(oldBox.x, 4)
- expect(newBox.y).toBeCloseTo(oldBox.y, 4)
- expect(newBox.w).toBeCloseTo(100, 4)
- expect(newBox.h).toBeCloseTo(100, 4)
-
- const rbox1 = g.children()[0].rbox()
- const rbox2 = g.children()[1].rbox()
-
- expect(rbox1.width).toBeCloseTo(90.9, 1)
- expect(Math.floor(rbox2.width * 10) / 10).toBeCloseTo(63.6, 1) // Browsers have different opinion on this one (chrome: 63.6, ff: 63.7)
-
- expect(rbox1.x).toBeCloseTo(10, 1)
- expect(rbox2.x).toBeCloseTo(46.4, 1)
- expect(rbox1.height).toBeCloseTo(85.7, 1)
- expect(rbox2.height).toBeCloseTo(71.4, 1)
- expect(rbox1.y).toBeCloseTo(20, 1)
- expect(rbox2.y).toBeCloseTo(48.6, 1)
- })
-
- it('changes the dimensions of the bbox (2)', () => {
- const canvas = SVG().addTo(container)
- const g = canvas.group()
-
- g.rect(400, 200).move(123, 312).rotate(34).skew(12)
- g.rect(100, 50).move(11, 43).translate(123, 32).skew(-12)
- g.rect(400, 200).rotate(90)
- g.group().rotate(23).group().skew(32).rect(100, 40).skew(11).rotate(12)
-
- const oldBox = g.bbox()
-
- g.size(100, 100)
-
- const newBox = g.bbox()
-
- expect(newBox.x).toBeCloseTo(oldBox.x, 4)
- expect(newBox.y).toBeCloseTo(oldBox.y, 4)
- expect(newBox.w).toBeCloseTo(100, 4)
- expect(newBox.h).toBeCloseTo(100, 4)
- })
-
- })
-
- describe('width()', () => {
- it('gets the width value of the bbox', () => {
- const canvas = SVG().addTo(container)
-
- const g = new G()
- g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
- g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
-
- g.addTo(canvas)
-
- expect(g.width()).toBe(g.bbox().width)
- expect(g.width()).toBe(110)
- })
- it('sets the width value of the bbox by moving all children', () => {
- const canvas = SVG().addTo(container)
-
- const g = new G()
- g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
- g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
-
- g.addTo(canvas)
-
- expect(g.width(100)).toBe(g)
- expect(g.bbox().width).toBe(100)
-
- const rbox1 = g.children()[0].rbox()
- const rbox2 = g.children()[1].rbox()
-
- expect(rbox1.width).toBeCloseTo(90.9, 1)
- expect(Math.floor(rbox2.width * 10) / 10).toBeCloseTo(63.6, 1) // Browsers have different opinion on this one (chrome: 63.6, ff: 63.7)
-
- expect(rbox1.x).toBeCloseTo(10, 3)
- expect(rbox2.x).toBeCloseTo(46.4, 1)
- })
- })
-
- describe('height()', () => {
- it('gets the height value of the bbox', () => {
- const canvas = SVG().addTo(container)
-
- const g = new G()
- g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
- g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
-
- g.addTo(canvas)
-
- expect(g.height()).toBe(g.bbox().height)
- expect(g.height()).toBe(140)
- })
- it('sets the height value of the bbox by moving all children', () => {
- const canvas = SVG().addTo(container)
-
- const g = new G()
- g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
- g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
-
- g.addTo(canvas)
-
- expect(g.height(100)).toBe(g)
- expect(g.bbox().height).toBeCloseTo(100, 3)
-
- const rbox1 = g.children()[0].rbox()
- const rbox2 = g.children()[1].rbox()
-
- expect(rbox1.height).toBeCloseTo(85.7, 1)
- expect(rbox2.height).toBeCloseTo(71.4, 1)
-
- expect(rbox1.y).toBeCloseTo(20, 3)
- expect(rbox2.y).toBeCloseTo(48.6, 1)
- })
- })
})
diff --git a/spec/spec/modules/core/circled.js b/spec/spec/modules/core/circled.js
index f74332a..a6324d8 100644
--- a/spec/spec/modules/core/circled.js
+++ b/spec/spec/modules/core/circled.js
@@ -8,7 +8,7 @@ describe('circled.js', () => {
let element
beforeEach(() => {
- element = new Ellipse(50, 50)
+ element = new Ellipse().size(50, 50)
})
describe('rx()', () => {
diff --git a/spec/spec/modules/core/containerGeometry.js b/spec/spec/modules/core/containerGeometry.js
new file mode 100644
index 0000000..3bdb109
--- /dev/null
+++ b/spec/spec/modules/core/containerGeometry.js
@@ -0,0 +1,271 @@
+/* globals describe, expect, it, jasmine, spyOn, container */
+
+import { Box, G, Rect, SVG } from '../../../../src/main.js'
+
+const { any, objectContaining } = jasmine
+
+describe('containerGeometry.js', () => {
+
+ describe('dmove()', () => {
+ it('moves the bbox of the group by a certain amount (1)', () => {
+ const canvas = SVG().addTo(container)
+ const g = canvas.group()
+
+ g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
+ g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
+
+ g.dmove(10, 10)
+
+ const box = g.bbox()
+ expect(box).toEqual(objectContaining({
+ x: 20, y: 30, width: box.width, height: box.height
+ }))
+ })
+
+ it('moves the bbox of the group by a certain amount (2)', () => {
+ const canvas = SVG().addTo(container)
+ const g = canvas.group()
+
+ g.rect(400, 200).move(123, 312).rotate(34).skew(12)
+ g.rect(100, 50).move(11, 43).translate(123, 32).skew(-12)
+ g.rect(400, 200).rotate(90)
+ g.group().rotate(23).group().skew(32).rect(100, 40).skew(11).rotate(12)
+
+ const oldBox = g.bbox()
+
+ g.dmove(10, 10)
+
+ const newBox = g.bbox()
+
+ expect(newBox.x).toBeCloseTo(oldBox.x + 10, 4)
+ expect(newBox.y).toBeCloseTo(oldBox.y + 10, 4)
+ expect(newBox.w).toBeCloseTo(oldBox.w, 4)
+ expect(newBox.h).toBeCloseTo(oldBox.h, 4)
+ })
+ })
+
+ describe('dx()', () => {
+ it('calls dmove with dy=0 and returns itself', () => {
+ const canvas = SVG().addTo(container)
+ const g = canvas.group()
+ const spy = spyOn(g, 'dmove').and.callThrough()
+ expect(g.dx(10)).toBe(g)
+ expect(spy).toHaveBeenCalledWith(10, 0)
+ })
+ })
+
+ describe('dy()', () => {
+ it('calls dmove with dx=0 and returns itself', () => {
+ const canvas = SVG().addTo(container)
+ const g = canvas.group()
+ const spy = spyOn(g, 'dmove').and.callThrough()
+ expect(g.dy(10)).toBe(g)
+ expect(spy).toHaveBeenCalledWith(0, 10)
+ })
+ })
+
+ describe('move()', () => {
+ it('calls dmove() with the correct difference', () => {
+ const canvas = SVG().addTo(container)
+ const g = canvas.group()
+ g.rect(100, 200).move(111, 223)
+
+ spyOn(g, 'dmove')
+
+ g.move(100, 150)
+ expect(g.dmove).toHaveBeenCalledWith(-11, -73)
+ })
+
+ it('defaults to x=0 and y=0', () => {
+ const canvas = SVG().addTo(container)
+ const g = canvas.group()
+ g.rect(100, 200).move(111, 223)
+
+ spyOn(g, 'dmove')
+
+ g.move()
+ expect(g.dmove).toHaveBeenCalledWith(-111, -223)
+ })
+ })
+
+ describe('x()', () => {
+ it('gets the x value of the bbox', () => {
+ const canvas = SVG().addTo(container)
+
+ const g = new G()
+ g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
+ g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
+
+ g.addTo(canvas)
+
+ expect(g.x()).toBe(g.bbox().x)
+ expect(g.x()).toBe(10)
+ })
+ it('calls move with the paramater as x', () => {
+ const canvas = SVG().addTo(container)
+ const g = canvas.group()
+ g.rect(100, 200).move(111, 223)
+
+ spyOn(g, 'move')
+
+ g.x(100)
+ expect(g.move).toHaveBeenCalledWith(100, g.bbox().y, any(Box))
+ })
+ })
+
+ describe('y()', () => {
+ it('gets the y value of the bbox', () => {
+ const canvas = SVG().addTo(container)
+
+ const g = new G()
+ g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
+ g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
+
+ g.addTo(canvas)
+
+ expect(g.y()).toBe(g.bbox().y)
+ expect(g.y()).toBe(20)
+ })
+
+ it('calls move with the paramater as y', () => {
+ const canvas = SVG().addTo(container)
+ const g = canvas.group()
+ g.rect(100, 200).move(111, 223)
+
+ spyOn(g, 'move')
+
+ g.y(100)
+ expect(g.move).toHaveBeenCalledWith(g.bbox().x, 100, any(Box))
+ })
+ })
+
+ describe('size()', () => {
+ it('changes the dimensions of the bbox (1)', () => {
+ const canvas = SVG().addTo(container)
+
+ const g = new G()
+ g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
+ g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
+
+ g.addTo(canvas)
+
+ const oldBox = g.bbox()
+
+ expect(g.size(100, 100)).toBe(g)
+
+ const newBox = g.bbox()
+
+ expect(newBox.x).toBeCloseTo(oldBox.x, 4)
+ expect(newBox.y).toBeCloseTo(oldBox.y, 4)
+ expect(newBox.w).toBeCloseTo(100, 4)
+ expect(newBox.h).toBeCloseTo(100, 4)
+
+ const rbox1 = g.children()[0].rbox()
+ const rbox2 = g.children()[1].rbox()
+
+ expect(rbox1.width).toBeCloseTo(90.9, 1)
+ expect(Math.floor(rbox2.width * 10) / 10).toBeCloseTo(63.6, 1) // Browsers have different opinion on this one (chrome: 63.6, ff: 63.7)
+
+ expect(rbox1.x).toBeCloseTo(10, 1)
+ expect(rbox2.x).toBeCloseTo(46.4, 1)
+ expect(rbox1.height).toBeCloseTo(85.7, 1)
+ expect(rbox2.height).toBeCloseTo(71.4, 1)
+ expect(rbox1.y).toBeCloseTo(20, 1)
+ expect(rbox2.y).toBeCloseTo(48.6, 1)
+ })
+
+ it('changes the dimensions of the bbox (2)', () => {
+ const canvas = SVG().addTo(container)
+ const g = canvas.group()
+
+ g.rect(400, 200).move(123, 312).rotate(34).skew(12)
+ g.rect(100, 50).move(11, 43).translate(123, 32).skew(-12)
+ g.rect(400, 200).rotate(90)
+ g.group().rotate(23).group().skew(32).rect(100, 40).skew(11).rotate(12)
+
+ const oldBox = g.bbox()
+
+ g.size(100, 100)
+
+ const newBox = g.bbox()
+
+ expect(newBox.x).toBeCloseTo(oldBox.x, 4)
+ expect(newBox.y).toBeCloseTo(oldBox.y, 4)
+ expect(newBox.w).toBeCloseTo(100, 4)
+ expect(newBox.h).toBeCloseTo(100, 4)
+ })
+
+ })
+
+ describe('width()', () => {
+ it('gets the width value of the bbox', () => {
+ const canvas = SVG().addTo(container)
+
+ const g = new G()
+ g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
+ g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
+
+ g.addTo(canvas)
+
+ expect(g.width()).toBe(g.bbox().width)
+ expect(g.width()).toBe(110)
+ })
+ it('sets the width value of the bbox by moving all children', () => {
+ const canvas = SVG().addTo(container)
+
+ const g = new G()
+ g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
+ g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
+
+ g.addTo(canvas)
+
+ expect(g.width(100)).toBe(g)
+ expect(g.bbox().width).toBe(100)
+
+ const rbox1 = g.children()[0].rbox()
+ const rbox2 = g.children()[1].rbox()
+
+ expect(rbox1.width).toBeCloseTo(90.9, 1)
+ expect(Math.floor(rbox2.width * 10) / 10).toBeCloseTo(63.6, 1) // Browsers have different opinion on this one (chrome: 63.6, ff: 63.7)
+
+ expect(rbox1.x).toBeCloseTo(10, 3)
+ expect(rbox2.x).toBeCloseTo(46.4, 1)
+ })
+ })
+
+ describe('height()', () => {
+ it('gets the height value of the bbox', () => {
+ const canvas = SVG().addTo(container)
+
+ const g = new G()
+ g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
+ g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
+
+ g.addTo(canvas)
+
+ expect(g.height()).toBe(g.bbox().height)
+ expect(g.height()).toBe(140)
+ })
+ it('sets the height value of the bbox by moving all children', () => {
+ const canvas = SVG().addTo(container)
+
+ const g = new G()
+ g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))
+ g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))
+
+ g.addTo(canvas)
+
+ expect(g.height(100)).toBe(g)
+ expect(g.bbox().height).toBeCloseTo(100, 3)
+
+ const rbox1 = g.children()[0].rbox()
+ const rbox2 = g.children()[1].rbox()
+
+ expect(rbox1.height).toBeCloseTo(85.7, 1)
+ expect(rbox2.height).toBeCloseTo(71.4, 1)
+
+ expect(rbox1.y).toBeCloseTo(20, 3)
+ expect(rbox2.y).toBeCloseTo(48.6, 1)
+ })
+ })
+})
diff --git a/spec/spec/modules/core/selector.js b/spec/spec/modules/core/selector.js
index 7a17466..1336059 100644
--- a/spec/spec/modules/core/selector.js
+++ b/spec/spec/modules/core/selector.js
@@ -6,9 +6,9 @@ import { getWindow } from '../../../../src/utils/window.js'
describe('selector.js', () => {
describe('baseFind()', () => {
it('finds all elements of a selector in the document', () => {
- const div = SVG('<div>', true).id('foo').addTo(container)
- const span = SVG('<span>', true).addClass('bar').addTo(div)
- const span2 = SVG('<span>', true).addTo(div)
+ const div = SVG('<div />', true).id('foo').addTo(container)
+ const span = SVG('<span />', true).addClass('bar').addTo(div)
+ const span2 = SVG('<span />', true).addTo(div)
expect(find('#canvas').map(el => el.node)).toEqual([ container ])
expect(find('span')).toEqual([ span, span2 ])
@@ -17,7 +17,7 @@ describe('selector.js', () => {
})
it('finds all elements of a selector scoped to an element', () => {
- const div = SVG('<div>', true).id('foo').addTo(container)
+ const div = SVG('<div />', true).id('foo').addTo(container)
expect(find('#canvas', getWindow().document)[0].node).toBe(container)
expect(find('#foo', container)).toEqual([ div ])
diff --git a/spec/spec/utils/adopter.js b/spec/spec/utils/adopter.js
index 5ed6713..60c1642 100644
--- a/spec/spec/utils/adopter.js
+++ b/spec/spec/utils/adopter.js
@@ -75,7 +75,7 @@ describe('adopter.js', () => {
})
it('creates an element in the html namespace from passed html string', () => {
- const div = makeInstance('<div>', true)
+ const div = makeInstance('<div />', true)
expect(adoptSpy).toHaveBeenCalledWith(any(Node))
expect(adoptSpy).toHaveBeenCalledWith(objectContaining({ nodeName: 'DIV', namespaceURI: 'http://www.w3.org/1999/xhtml' }))
diff --git a/src/animation/Controller.js b/src/animation/Controller.js
index 972679e..ae49de9 100644
--- a/src/animation/Controller.js
+++ b/src/animation/Controller.js
@@ -128,13 +128,14 @@ export class Controller extends Stepper {
this.stepper = fn
}
+ done (c) {
+ return c.done
+ }
+
step (current, target, dt, c) {
return this.stepper(current, target, dt, c)
}
- done (c) {
- return c.done
- }
}
function recalculate () {
diff --git a/src/animation/Morphable.js b/src/animation/Morphable.js
index 2d48e10..46dd166 100644
--- a/src/animation/Morphable.js
+++ b/src/animation/Morphable.js
@@ -49,6 +49,25 @@ export default class Morphable {
this._morphObj = null
}
+ at (pos) {
+ var _this = this
+
+ return this._morphObj.fromArray(
+ this._from.map(function (i, index) {
+ return _this._stepper.step(i, _this._to[index], pos, _this._context[index], _this._context)
+ })
+ )
+ }
+
+ done () {
+ var complete = this._context
+ .map(this._stepper.done)
+ .reduce(function (last, curr) {
+ return last && curr
+ }, true)
+ return complete
+ }
+
from (val) {
if (val == null) {
return this._from
@@ -58,6 +77,12 @@ export default class Morphable {
return this
}
+ stepper (stepper) {
+ if (stepper == null) return this._stepper
+ this._stepper = stepper
+ return this
+ }
+
to (val) {
if (val == null) {
return this._to
@@ -109,30 +134,6 @@ export default class Morphable {
return result
}
- stepper (stepper) {
- if (stepper == null) return this._stepper
- this._stepper = stepper
- return this
- }
-
- done () {
- var complete = this._context
- .map(this._stepper.done)
- .reduce(function (last, curr) {
- return last && curr
- }, true)
- return complete
- }
-
- at (pos) {
- var _this = this
-
- return this._morphObj.fromArray(
- this._from.map(function (i, index) {
- return _this._stepper.step(i, _this._to[index], pos, _this._context[index], _this._context)
- })
- )
- }
}
export class NonMorphable {
@@ -146,13 +147,14 @@ export class NonMorphable {
return this
}
+ toArray () {
+ return [ this.value ]
+ }
+
valueOf () {
return this.value
}
- toArray () {
- return [ this.value ]
- }
}
export class TransformBag {
@@ -214,6 +216,17 @@ export class ObjectBag {
this.init(...args)
}
+ align (other) {
+ for (let i = 0, il = this.values.length; i < il; ++i) {
+ if (this.values[i] === Color) {
+ const space = other[i + 6]
+ const color = new Color(this.values.splice(i + 2, 5))[space]().toArray()
+ this.values.splice(i + 2, 0, ...color)
+ }
+ }
+ return this
+ }
+
init (objOrArr) {
this.values = []
@@ -237,6 +250,10 @@ export class ObjectBag {
return this
}
+ toArray () {
+ return this.values
+ }
+
valueOf () {
var obj = {}
var arr = this.values
@@ -253,20 +270,6 @@ export class ObjectBag {
return obj
}
- toArray () {
- return this.values
- }
-
- align (other) {
- for (let i = 0, il = this.values.length; i < il; ++i) {
- if (this.values[i] === Color) {
- const space = other[i + 6]
- const color = new Color(this.values.splice(i + 2, 5))[space]().toArray()
- this.values.splice(i + 2, 0, ...color)
- }
- }
- return this
- }
}
const morphableTypes = [
diff --git a/src/animation/Queue.js b/src/animation/Queue.js
index 1858b99..e01c3d6 100644
--- a/src/animation/Queue.js
+++ b/src/animation/Queue.js
@@ -4,6 +4,16 @@ export default class Queue {
this._last = null
}
+ // Shows us the first item in the list
+ first () {
+ return this._first && this._first.value
+ }
+
+ // Shows us the last item in the list
+ last () {
+ return this._last && this._last.value
+ }
+
push (value) {
// An item stores an id and the provided value
var item = typeof value.next !== 'undefined' ? value : { value: value, next: null, prev: null }
@@ -22,28 +32,6 @@ export default class Queue {
return item
}
- shift () {
- // Check if we have a value
- var remove = this._first
- if (!remove) return null
-
- // If we do, remove it and relink things
- this._first = remove.next
- if (this._first) this._first.prev = null
- this._last = this._first ? this._last : null
- return remove.value
- }
-
- // Shows us the first item in the list
- first () {
- return this._first && this._first.value
- }
-
- // Shows us the last item in the list
- last () {
- return this._last && this._last.value
- }
-
// Removes the item that was returned from the push
remove (item) {
// Relink the previous item
@@ -56,4 +44,17 @@ export default class Queue {
item.prev = null
item.next = null
}
+
+ shift () {
+ // Check if we have a value
+ var remove = this._first
+ if (!remove) return null
+
+ // If we do, remove it and relink things
+ this._first = remove.next
+ if (this._first) this._first.prev = null
+ this._last = this._first ? this._last : null
+ return remove.value
+ }
+
}
diff --git a/src/animation/Runner.js b/src/animation/Runner.js
index e0ac5a8..bd60915 100644
--- a/src/animation/Runner.js
+++ b/src/animation/Runner.js
@@ -71,27 +71,55 @@ export default class Runner extends EventTarget {
this._persist = this._isDeclarative ? true : null
}
- /*
- Runner Definitions
- ==================
- These methods help us define the runtime behaviour of the Runner or they
- help us make new runners from the current runner
- */
+ static sanitise (duration, delay, when) {
+ // Initialise the default parameters
+ var times = 1
+ var swing = false
+ var wait = 0
+ duration = duration || timeline.duration
+ delay = delay || timeline.delay
+ when = when || 'last'
- element (element) {
- if (element == null) return this._element
- this._element = element
- element._prepareRunner()
+ // If we have an object, unpack the values
+ if (typeof duration === 'object' && !(duration instanceof Stepper)) {
+ delay = duration.delay || delay
+ when = duration.when || when
+ swing = duration.swing || swing
+ times = duration.times || times
+ wait = duration.wait || wait
+ duration = duration.duration || timeline.duration
+ }
+
+ return {
+ duration: duration,
+ delay: delay,
+ swing: swing,
+ times: times,
+ wait: wait,
+ when: when
+ }
+ }
+
+ active (enabled) {
+ if (enabled == null) return this.enabled
+ this.enabled = enabled
return this
}
- timeline (timeline) {
- // check explicitly for undefined so we can set the timeline to null
- if (typeof timeline === 'undefined') return this._timeline
- this._timeline = timeline
+ /*
+ Private Methods
+ ===============
+ Methods that shouldn't be used externally
+ */
+ addTransform (transform, index) {
+ this.transforms.lmultiplyO(transform)
return this
}
+ after (fn) {
+ return this.on('finished', fn)
+ }
+
animate (duration, delay, when) {
var o = Runner.sanitise(duration, delay, when)
var runner = new Runner(o.duration)
@@ -100,30 +128,54 @@ export default class Runner extends EventTarget {
return runner.loop(o).schedule(o.delay, o.when)
}
- schedule (timeline, delay, when) {
- // The user doesn't need to pass a timeline if we already have one
- if (!(timeline instanceof Timeline)) {
- when = delay
- delay = timeline
- timeline = this.timeline()
- }
+ clearTransform () {
+ this.transforms = new Matrix()
+ return this
+ }
- // If there is no timeline, yell at the user...
- if (!timeline) {
- throw Error('Runner cannot be scheduled without timeline')
+ // TODO: Keep track of all transformations so that deletion is faster
+ clearTransformsFromQueue () {
+ if (!this.done || !this._timeline || !this._timeline._runnerIds.includes(this.id)) {
+ this._queue = this._queue.filter((item) => {
+ return !item.isTransform
+ })
}
+ }
- // Schedule the runner on the timeline provided
- timeline.schedule(this, delay, when)
+ delay (delay) {
+ return this.animate(0, delay)
+ }
+
+ duration () {
+ return this._times * (this._wait + this._duration) - this._wait
+ }
+
+ during (fn) {
+ return this.queue(null, fn)
+ }
+
+ ease (fn) {
+ this._stepper = new Ease(fn)
return this
}
+ /*
+ Runner Definitions
+ ==================
+ These methods help us define the runtime behaviour of the Runner or they
+ help us make new runners from the current runner
+ */
- unschedule () {
- var timeline = this.timeline()
- timeline && timeline.unschedule(this)
+ element (element) {
+ if (element == null) return this._element
+ this._element = element
+ element._prepareRunner()
return this
}
+ finish () {
+ return this.step(Infinity)
+ }
+
loop (times, swing, wait) {
// Deal with the user passing in an object
if (typeof times === 'object') {
@@ -143,57 +195,6 @@ export default class Runner extends EventTarget {
return this
}
- delay (delay) {
- return this.animate(0, delay)
- }
-
- /*
- Basic Functionality
- ===================
- These methods allow us to attach basic functions to the runner directly
- */
-
- queue (initFn, runFn, retargetFn, isTransform) {
- this._queue.push({
- initialiser: initFn || noop,
- runner: runFn || noop,
- retarget: retargetFn,
- isTransform: isTransform,
- initialised: false,
- finished: false
- })
- var timeline = this.timeline()
- timeline && this.timeline()._continue()
- return this
- }
-
- during (fn) {
- return this.queue(null, fn)
- }
-
- after (fn) {
- return this.on('finished', fn)
- }
-
- /*
- Runner animation methods
- ========================
- Control how the animation plays
- */
-
- time (time) {
- if (time == null) {
- return this._time
- }
- const dt = time - this._time
- this.step(dt)
- return this
- }
-
- duration () {
- return this._times * (this._wait + this._duration) - this._wait
- }
-
loops (p) {
var loopDuration = this._duration + this._wait
if (p == null) {
@@ -264,6 +265,55 @@ export default class Runner extends EventTarget {
return this.time(p * this.duration())
}
+ /*
+ Basic Functionality
+ ===================
+ These methods allow us to attach basic functions to the runner directly
+ */
+ queue (initFn, runFn, retargetFn, isTransform) {
+ this._queue.push({
+ initialiser: initFn || noop,
+ runner: runFn || noop,
+ retarget: retargetFn,
+ isTransform: isTransform,
+ initialised: false,
+ finished: false
+ })
+ var timeline = this.timeline()
+ timeline && this.timeline()._continue()
+ return this
+ }
+
+ reset () {
+ if (this._reseted) return this
+ this.time(0)
+ this._reseted = true
+ return this
+ }
+
+ reverse (reverse) {
+ this._reverse = reverse == null ? !this._reverse : reverse
+ return this
+ }
+
+ schedule (timeline, delay, when) {
+ // The user doesn't need to pass a timeline if we already have one
+ if (!(timeline instanceof Timeline)) {
+ when = delay
+ delay = timeline
+ timeline = this.timeline()
+ }
+
+ // If there is no timeline, yell at the user...
+ if (!timeline) {
+ throw Error('Runner cannot be scheduled without timeline')
+ }
+
+ // Schedule the runner on the timeline provided
+ timeline.schedule(this, delay, when)
+ return this
+ }
+
step (dt) {
// If we are inactive, this stepper just gets skipped
if (!this.enabled) return this
@@ -315,38 +365,54 @@ export default class Runner extends EventTarget {
return this
}
- reset () {
- if (this._reseted) return this
- this.time(0)
- this._reseted = true
+ /*
+ Runner animation methods
+ ========================
+ Control how the animation plays
+ */
+ time (time) {
+ if (time == null) {
+ return this._time
+ }
+ const dt = time - this._time
+ this.step(dt)
return this
}
- finish () {
- return this.step(Infinity)
- }
-
- reverse (reverse) {
- this._reverse = reverse == null ? !this._reverse : reverse
+ timeline (timeline) {
+ // check explicitly for undefined so we can set the timeline to null
+ if (typeof timeline === 'undefined') return this._timeline
+ this._timeline = timeline
return this
}
- ease (fn) {
- this._stepper = new Ease(fn)
+ unschedule () {
+ var timeline = this.timeline()
+ timeline && timeline.unschedule(this)
return this
}
- active (enabled) {
- if (enabled == null) return this.enabled
- this.enabled = enabled
- return this
- }
+ // Run each initialise function in the runner if required
+ _initialise (running) {
+ // If we aren't running, we shouldn't initialise when not declarative
+ if (!running && !this._isDeclarative) return
- /*
- Private Methods
- ===============
- Methods that shouldn't be used externally
- */
+ // Loop through all of the initialisers
+ for (var i = 0, len = this._queue.length; i < len; ++i) {
+ // Get the current initialiser
+ var current = this._queue[i]
+
+ // Determine whether we need to initialise
+ var needsIt = this._isDeclarative || (!current.initialised && running)
+ running = !current.finished
+
+ // Call the initialiser if we need to
+ if (needsIt && running) {
+ current.initialiser.call(this)
+ current.initialised = true
+ }
+ }
+ }
// Save a morpher to the morpher list so that we can retarget it later
_rememberMorpher (method, morpher) {
@@ -368,6 +434,25 @@ export default class Runner extends EventTarget {
}
// Try to set the target for a morpher if the morpher exists, otherwise
+ // Run each run function for the position or dt given
+ _run (positionOrDt) {
+ // Run all of the _queue directly
+ var allfinished = true
+ for (var i = 0, len = this._queue.length; i < len; ++i) {
+ // Get the current function to run
+ var current = this._queue[i]
+
+ // Run the function if its not finished, we keep track of the finished
+ // flag for the sake of declarative _queue
+ var converged = current.runner.call(this, positionOrDt)
+ current.finished = current.finished || (converged === true)
+ allfinished = allfinished && current.finished
+ }
+
+ // We report when all of the constructors are finished
+ return allfinished
+ }
+
// do nothing and return false
_tryRetarget (method, target, extra) {
if (this._history[method]) {
@@ -395,94 +480,6 @@ export default class Runner extends EventTarget {
return false
}
- // Run each initialise function in the runner if required
- _initialise (running) {
- // If we aren't running, we shouldn't initialise when not declarative
- if (!running && !this._isDeclarative) return
-
- // Loop through all of the initialisers
- for (var i = 0, len = this._queue.length; i < len; ++i) {
- // Get the current initialiser
- var current = this._queue[i]
-
- // Determine whether we need to initialise
- var needsIt = this._isDeclarative || (!current.initialised && running)
- running = !current.finished
-
- // Call the initialiser if we need to
- if (needsIt && running) {
- current.initialiser.call(this)
- current.initialised = true
- }
- }
- }
-
- // Run each run function for the position or dt given
- _run (positionOrDt) {
- // Run all of the _queue directly
- var allfinished = true
- for (var i = 0, len = this._queue.length; i < len; ++i) {
- // Get the current function to run
- var current = this._queue[i]
-
- // Run the function if its not finished, we keep track of the finished
- // flag for the sake of declarative _queue
- var converged = current.runner.call(this, positionOrDt)
- current.finished = current.finished || (converged === true)
- allfinished = allfinished && current.finished
- }
-
- // We report when all of the constructors are finished
- return allfinished
- }
-
- addTransform (transform, index) {
- this.transforms.lmultiplyO(transform)
- return this
- }
-
- clearTransform () {
- this.transforms = new Matrix()
- return this
- }
-
- // TODO: Keep track of all transformations so that deletion is faster
- clearTransformsFromQueue () {
- if (!this.done || !this._timeline || !this._timeline._runnerIds.includes(this.id)) {
- this._queue = this._queue.filter((item) => {
- return !item.isTransform
- })
- }
- }
-
- static sanitise (duration, delay, when) {
- // Initialise the default parameters
- var times = 1
- var swing = false
- var wait = 0
- duration = duration || timeline.duration
- delay = delay || timeline.delay
- when = when || 'last'
-
- // If we have an object, unpack the values
- if (typeof duration === 'object' && !(duration instanceof Stepper)) {
- delay = duration.delay || delay
- when = duration.when || when
- swing = duration.swing || swing
- times = duration.times || times
- wait = duration.wait || wait
- duration = duration.duration || timeline.duration
- }
-
- return {
- duration: duration,
- delay: delay,
- swing: swing,
- times: times,
- wait: wait,
- when: when
- }
- }
}
Runner.id = 0
@@ -543,17 +540,29 @@ export class RunnerArray {
return this
}
- getByID (id) {
- return this.runners[this.ids.indexOf(id + 1)]
+ clearBefore (id) {
+ const deleteCnt = this.ids.indexOf(id + 1) || 1
+ this.ids.splice(0, deleteCnt, 0)
+ this.runners.splice(0, deleteCnt, new FakeRunner())
+ .forEach((r) => r.clearTransformsFromQueue())
+ return this
}
- remove (id) {
+ edit (id, newRunner) {
const index = this.ids.indexOf(id + 1)
- this.ids.splice(index, 1)
- this.runners.splice(index, 1)
+ this.ids.splice(index, 1, id + 1)
+ this.runners.splice(index, 1, newRunner)
return this
}
+ getByID (id) {
+ return this.runners[this.ids.indexOf(id + 1)]
+ }
+
+ length () {
+ return this.ids.length
+ }
+
merge () {
let lastRunner = null
for (let i = 0; i < this.runners.length; ++i) {
@@ -580,24 +589,13 @@ export class RunnerArray {
return this
}
- edit (id, newRunner) {
+ remove (id) {
const index = this.ids.indexOf(id + 1)
- this.ids.splice(index, 1, id + 1)
- this.runners.splice(index, 1, newRunner)
+ this.ids.splice(index, 1)
+ this.runners.splice(index, 1)
return this
}
- length () {
- return this.ids.length
- }
-
- clearBefore (id) {
- const deleteCnt = this.ids.indexOf(id + 1) || 1
- this.ids.splice(0, deleteCnt, 0)
- this.runners.splice(0, deleteCnt, new FakeRunner())
- .forEach((r) => r.clearTransformsFromQueue())
- return this
- }
}
registerMethods({
diff --git a/src/animation/Timeline.js b/src/animation/Timeline.js
index d175ae6..a29d683 100644
--- a/src/animation/Timeline.js
+++ b/src/animation/Timeline.js
@@ -44,6 +44,62 @@ export default class Timeline extends EventTarget {
this._stepImmediate = this._stepFn.bind(this, true)
}
+ active () {
+ return !!this._nextFrame
+ }
+
+ finish () {
+ // Go to end and pause
+ this.time(this.getEndTimeOfTimeline() + 1)
+ return this.pause()
+ }
+
+ // Calculates the end of the timeline
+ getEndTime () {
+ var lastRunnerInfo = this.getLastRunnerInfo()
+ var lastDuration = lastRunnerInfo ? lastRunnerInfo.runner.duration() : 0
+ var lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time
+ return lastStartTime + lastDuration
+ }
+
+ getEndTimeOfTimeline () {
+ const endTimes = this._runners.map((i) => i.start + i.runner.duration())
+ return Math.max(0, ...endTimes)
+ }
+
+ getLastRunnerInfo () {
+ return this.getRunnerInfoById(this._lastRunnerId)
+ }
+
+ getRunnerInfoById (id) {
+ return this._runners[this._runnerIds.indexOf(id)] || null
+ }
+
+ pause () {
+ this._paused = true
+ return this._continue()
+ }
+
+ persist (dtOrForever) {
+ if (dtOrForever == null) return this._persist
+ this._persist = dtOrForever
+ return this
+ }
+
+ play () {
+ // Now make sure we are not paused and continue the animation
+ this._paused = false
+ return this.updateTime()._continue()
+ }
+
+ reverse (yes) {
+ var currentSpeed = this.speed()
+ if (yes == null) return this.speed(-currentSpeed)
+
+ var positive = Math.abs(currentSpeed)
+ return this.speed(yes ? -positive : positive)
+ }
+
// schedules a runner on the timeline
schedule (runner, delay, when) {
if (runner == null) {
@@ -102,56 +158,20 @@ export default class Timeline extends EventTarget {
return this
}
- // Remove the runner from this timeline
- unschedule (runner) {
- var index = this._runnerIds.indexOf(runner.id)
- if (index < 0) return this
-
- this._runners.splice(index, 1)
- this._runnerIds.splice(index, 1)
-
- runner.timeline(null)
- return this
- }
-
- getRunnerInfoById (id) {
- return this._runners[this._runnerIds.indexOf(id)] || null
- }
-
- getLastRunnerInfo () {
- return this.getRunnerInfoById(this._lastRunnerId)
- }
-
- // Calculates the end of the timeline
- getEndTime () {
- var lastRunnerInfo = this.getLastRunnerInfo()
- var lastDuration = lastRunnerInfo ? lastRunnerInfo.runner.duration() : 0
- var lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time
- return lastStartTime + lastDuration
- }
-
- getEndTimeOfTimeline () {
- const endTimes = this._runners.map((i) => i.start + i.runner.duration())
- return Math.max(0, ...endTimes)
+ seek (dt) {
+ return this.time(this._time + dt)
}
- // Makes sure, that after pausing the time doesn't jump
- updateTime () {
- if (!this.active()) {
- this._lastSourceTime = this._timeSource()
- }
+ source (fn) {
+ if (fn == null) return this._timeSource
+ this._timeSource = fn
return this
}
- play () {
- // Now make sure we are not paused and continue the animation
- this._paused = false
- return this.updateTime()._continue()
- }
-
- pause () {
- this._paused = true
- return this._continue()
+ speed (speed) {
+ if (speed == null) return this._speed
+ this._speed = speed
+ return this
}
stop () {
@@ -160,45 +180,41 @@ export default class Timeline extends EventTarget {
return this.pause()
}
- finish () {
- // Go to end and pause
- this.time(this.getEndTimeOfTimeline() + 1)
- return this.pause()
+ time (time) {
+ if (time == null) return this._time
+ this._time = time
+ return this._continue(true)
}
- speed (speed) {
- if (speed == null) return this._speed
- this._speed = speed
- return this
- }
+ // Remove the runner from this timeline
+ unschedule (runner) {
+ var index = this._runnerIds.indexOf(runner.id)
+ if (index < 0) return this
- reverse (yes) {
- var currentSpeed = this.speed()
- if (yes == null) return this.speed(-currentSpeed)
+ this._runners.splice(index, 1)
+ this._runnerIds.splice(index, 1)
- var positive = Math.abs(currentSpeed)
- return this.speed(yes ? -positive : positive)
+ runner.timeline(null)
+ return this
}
- seek (dt) {
- return this.time(this._time + dt)
+ // Makes sure, that after pausing the time doesn't jump
+ updateTime () {
+ if (!this.active()) {
+ this._lastSourceTime = this._timeSource()
+ }
+ return this
}
- time (time) {
- if (time == null) return this._time
- this._time = time
- return this._continue(true)
- }
+ // Checks if we are running and continues the animation
+ _continue (immediateStep = false) {
+ Animator.cancelFrame(this._nextFrame)
+ this._nextFrame = null
- persist (dtOrForever) {
- if (dtOrForever == null) return this._persist
- this._persist = dtOrForever
- return this
- }
+ if (immediateStep) return this._stepImmediate()
+ if (this._paused) return this
- source (fn) {
- if (fn == null) return this._timeSource
- this._timeSource = fn
+ this._nextFrame = Animator.frame(this._step)
return this
}
@@ -303,21 +319,6 @@ export default class Timeline extends EventTarget {
return this
}
- // Checks if we are running and continues the animation
- _continue (immediateStep = false) {
- Animator.cancelFrame(this._nextFrame)
- this._nextFrame = null
-
- if (immediateStep) return this._stepImmediate()
- if (this._paused) return this
-
- this._nextFrame = Animator.frame(this._step)
- return this
- }
-
- active () {
- return !!this._nextFrame
- }
}
registerMethods({
diff --git a/src/elements/A.js b/src/elements/A.js
index 6f9bec2..478c36d 100644
--- a/src/elements/A.js
+++ b/src/elements/A.js
@@ -1,24 +1,28 @@
-import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'
+import { nodeOrNew, register, wrapWithAttrCheck, extend } from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import { xlink } from '../modules/core/namespaces.js'
import Container from './Container.js'
+import * as containerGeometry from '../modules/core/containerGeometry.js'
export default class A extends Container {
constructor (node, attrs = node) {
super(nodeOrNew('a', node), attrs)
}
+ // Link target attribute
+ target (target) {
+ return this.attr('target', target)
+ }
+
// Link url
to (url) {
return this.attr('href', url, xlink)
}
- // Link target attribute
- target (target) {
- return this.attr('target', target)
- }
}
+extend(A, containerGeometry)
+
registerMethods({
Container: {
// Create a hyperlink element
diff --git a/src/elements/Element.js b/src/elements/Element.js
index bb11611..f39f777 100644
--- a/src/elements/Element.js
+++ b/src/elements/Element.js
@@ -71,12 +71,6 @@ export default class Element extends Dom {
return this.y(new SVGNumber(y).plus(this.y()))
}
- // Get parent document
- root () {
- const p = this.parent(getClass(root))
- return p && p.root()
- }
-
getEventHolder () {
return this
}
@@ -121,6 +115,12 @@ export default class Element extends Dom {
return m ? makeInstance(m[1]) : null
}
+ // Get parent document
+ root () {
+ const p = this.parent(getClass(root))
+ return p && p.root()
+ }
+
// set given data to the elements data property
setData (o) {
this.dom = o
diff --git a/src/elements/G.js b/src/elements/G.js
index 7677b92..b3a999e 100644
--- a/src/elements/G.js
+++ b/src/elements/G.js
@@ -1,82 +1,16 @@
-import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'
-import { proportionalSize } from '../utils/utils.js'
+import { nodeOrNew, register, wrapWithAttrCheck, extend } from '../utils/adopter.js'
import { registerMethods } from '../utils/methods.js'
import Container from './Container.js'
-import Matrix from '../types/Matrix.js'
-import Point from '../types/Point.js'
+import * as containerGeometry from '../modules/core/containerGeometry.js'
export default class G extends Container {
constructor (node, attrs = node) {
super(nodeOrNew('g', node), attrs)
}
-
- x (x, box = this.bbox()) {
- if (x == null) return box.x
- return this.move(x, box.y, box)
- }
-
- y (y, box = this.bbox()) {
- if (y == null) return box.y
- return this.move(box.x, y, box)
- }
-
- move (x = 0, y = 0, box = this.bbox()) {
- const dx = x - box.x
- const dy = y - box.y
-
- return this.dmove(dx, dy)
- }
-
- dx (dx) {
- return this.dmove(dx, 0)
- }
-
- dy (dy) {
- return this.dmove(0, dy)
- }
-
- dmove (dx, dy) {
- this.children().forEach((child, i) => {
- // Get the childs bbox
- const bbox = child.bbox()
- // Get childs matrix
- const m = new Matrix(child)
- // Translate childs matrix by amount and
- // transform it back into parents space
- const matrix = m.translate(dx, dy).transform(m.inverse())
- // Calculate new x and y from old box
- const p = new Point(bbox.x, bbox.y).transform(matrix)
- // Move element
- child.move(p.x, p.y)
- })
-
- return this
- }
-
- width (width, box = this.bbox()) {
- if (width == null) return box.width
- return this.size(width, box.height, box)
- }
-
- height (height, box = this.bbox()) {
- if (height == null) return box.height
- return this.size(box.width, height, box)
- }
-
- size (width, height, box = this.bbox()) {
- const p = proportionalSize(this, width, height, box)
- const scaleX = p.width / box.width
- const scaleY = p.height / box.height
-
- this.children().forEach((child, i) => {
- const o = new Point(box).transform(new Matrix(child).inverse())
- child.scale(scaleX, scaleY, o.x, o.y)
- })
-
- return this
- }
}
+extend(G, containerGeometry)
+
registerMethods({
Container: {
// Create a group element
diff --git a/src/elements/Marker.js b/src/elements/Marker.js
index b3077b1..56ac3e8 100644
--- a/src/elements/Marker.js
+++ b/src/elements/Marker.js
@@ -8,11 +8,6 @@ export default class Marker extends Container {
super(nodeOrNew('marker', node), attrs)
}
- // Set width of element
- width (width) {
- return this.attr('markerWidth', width)
- }
-
// Set height of element
height (height) {
return this.attr('markerHeight', height)
@@ -27,6 +22,11 @@ export default class Marker extends Container {
return this.attr('refX', x).attr('refY', y)
}
+ // Return the fill id
+ toString () {
+ return 'url(#' + this.id() + ')'
+ }
+
// Update marker
update (block) {
// remove all content
@@ -40,10 +40,11 @@ export default class Marker extends Container {
return this
}
- // Return the fill id
- toString () {
- return 'url(#' + this.id() + ')'
+ // Set width of element
+ width (width) {
+ return this.attr('markerWidth', width)
}
+
}
registerMethods({
diff --git a/src/elements/Text.js b/src/elements/Text.js
index 2951c2f..cc8db4a 100644
--- a/src/elements/Text.js
+++ b/src/elements/Text.js
@@ -21,53 +21,6 @@ export default class Text extends Shape {
this._build = false // disable build mode for adding multiple lines
}
- // Set the text content
- text (text) {
- // act as getter
- if (text === undefined) {
- var children = this.node.childNodes
- var firstLine = 0
- text = ''
-
- for (var i = 0, len = children.length; i < len; ++i) {
- // skip textPaths - they are no lines
- if (children[i].nodeName === 'textPath') {
- if (i === 0) firstLine = 1
- continue
- }
-
- // add newline if its not the first child and newLined is set to true
- if (i !== firstLine && children[i].nodeType !== 3 && adopt(children[i]).dom.newLined === true) {
- text += '\n'
- }
-
- // add content of this node
- text += children[i].textContent
- }
-
- return text
- }
-
- // remove existing content
- this.clear().build(true)
-
- if (typeof text === 'function') {
- // call block
- text.call(this, this)
- } else {
- // store text and make sure text is not blank
- text = (text + '').split('\n')
-
- // build new lines
- for (var j = 0, jl = text.length; j < jl; j++) {
- this.newLine(text[j])
- }
- }
-
- // disable build mode and rebuild lines
- return this.build(false).rebuild()
- }
-
// Set / get leading
leading (value) {
// act as getter
@@ -124,6 +77,54 @@ export default class Text extends Shape {
this.dom.leading = new SVGNumber(o.leading || 1.3)
return this
}
+
+ // Set the text content
+ text (text) {
+ // act as getter
+ if (text === undefined) {
+ var children = this.node.childNodes
+ var firstLine = 0
+ text = ''
+
+ for (var i = 0, len = children.length; i < len; ++i) {
+ // skip textPaths - they are no lines
+ if (children[i].nodeName === 'textPath') {
+ if (i === 0) firstLine = 1
+ continue
+ }
+
+ // add newline if its not the first child and newLined is set to true
+ if (i !== firstLine && children[i].nodeType !== 3 && adopt(children[i]).dom.newLined === true) {
+ text += '\n'
+ }
+
+ // add content of this node
+ text += children[i].textContent
+ }
+
+ return text
+ }
+
+ // remove existing content
+ this.clear().build(true)
+
+ if (typeof text === 'function') {
+ // call block
+ text.call(this, this)
+ } else {
+ // store text and make sure text is not blank
+ text = (text + '').split('\n')
+
+ // build new lines
+ for (var j = 0, jl = text.length; j < jl; j++) {
+ this.newLine(text[j])
+ }
+ }
+
+ // disable build mode and rebuild lines
+ return this.build(false).rebuild()
+ }
+
}
extend(Text, textable)
diff --git a/src/elements/TextPath.js b/src/elements/TextPath.js
index 9ed4a71..edac399 100644
--- a/src/elements/TextPath.js
+++ b/src/elements/TextPath.js
@@ -93,7 +93,7 @@ registerMethods({
targets () {
return baseFind('svg textPath').filter((node) => {
- return node.attr('href').includes(this.id())
+ return (node.attr('href') || '').includes(this.id())
})
// Does not work in IE11. Use when IE support is dropped
diff --git a/src/elements/Tspan.js b/src/elements/Tspan.js
index 00934ab..59860f7 100644
--- a/src/elements/Tspan.js
+++ b/src/elements/Tspan.js
@@ -18,21 +18,6 @@ export default class Tspan extends Shape {
this._build = false // disable build mode for adding multiple lines
}
- // Set text content
- text (text) {
- if (text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '')
-
- if (typeof text === 'function') {
- this.clear().build(true)
- text.call(this, this)
- this.build(false)
- } else {
- this.plain(text)
- }
-
- return this
- }
-
// Shortcut dx
dx (dx) {
return this.attr('dx', dx)
@@ -65,6 +50,22 @@ export default class Tspan extends Shape {
// apply new position
return this.dy(i ? dy : 0).attr('x', text.x())
}
+
+ // Set text content
+ text (text) {
+ if (text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '')
+
+ if (typeof text === 'function') {
+ this.clear().build(true)
+ text.call(this, this)
+ this.build(false)
+ } else {
+ this.plain(text)
+ }
+
+ return this
+ }
+
}
extend(Tspan, textable)
diff --git a/src/modules/core/containerGeometry.js b/src/modules/core/containerGeometry.js
new file mode 100644
index 0000000..50342b8
--- /dev/null
+++ b/src/modules/core/containerGeometry.js
@@ -0,0 +1,69 @@
+import Matrix from '../../types/Matrix.js'
+import Point from '../../types/Point.js'
+import { proportionalSize } from '../../utils/utils.js'
+
+export function dmove (dx, dy) {
+ this.children().forEach((child, i) => {
+ // Get the childs bbox
+ const bbox = child.bbox()
+ // Get childs matrix
+ const m = new Matrix(child)
+ // Translate childs matrix by amount and
+ // transform it back into parents space
+ const matrix = m.translate(dx, dy).transform(m.inverse())
+ // Calculate new x and y from old box
+ const p = new Point(bbox.x, bbox.y).transform(matrix)
+ // Move element
+ child.move(p.x, p.y)
+ })
+
+ return this
+}
+
+export function dx (dx) {
+ return this.dmove(dx, 0)
+}
+
+export function dy (dy) {
+ return this.dmove(0, dy)
+}
+
+export function height (height, box = this.bbox()) {
+ if (height == null) return box.height
+ return this.size(box.width, height, box)
+}
+
+export function move (x = 0, y = 0, box = this.bbox()) {
+ const dx = x - box.x
+ const dy = y - box.y
+
+ return this.dmove(dx, dy)
+}
+
+export function size (width, height, box = this.bbox()) {
+ const p = proportionalSize(this, width, height, box)
+ const scaleX = p.width / box.width
+ const scaleY = p.height / box.height
+
+ this.children().forEach((child, i) => {
+ const o = new Point(box).transform(new Matrix(child).inverse())
+ child.scale(scaleX, scaleY, o.x, o.y)
+ })
+
+ return this
+}
+
+export function width (width, box = this.bbox()) {
+ if (width == null) return box.width
+ return this.size(width, box.height, box)
+}
+
+export function x (x, box = this.bbox()) {
+ if (x == null) return box.x
+ return this.move(x, box.y, box)
+}
+
+export function y (y, box = this.bbox()) {
+ if (y == null) return box.y
+ return this.move(box.x, y, box)
+}
diff --git a/src/types/Box.js b/src/types/Box.js
index 3e91c35..9707b7f 100644
--- a/src/types/Box.js
+++ b/src/types/Box.js
@@ -26,6 +26,13 @@ export default class Box {
this.init(...args)
}
+ addOffset () {
+ // offset by window scroll position, because getBoundingClientRect changes when window is scrolled
+ this.x += globals.window.pageXOffset
+ this.y += globals.window.pageYOffset
+ return new Box(this)
+ }
+
init (source) {
var base = [ 0, 0, 0, 0 ]
source = typeof source === 'string' ? source.split(delimiter).map(parseFloat)
@@ -49,6 +56,10 @@ export default class Box {
return this
}
+ isNulled () {
+ return isNulledBox(this)
+ }
+
// Merge rect box with another, return a new instance
merge (box) {
const x = Math.min(this.x, box.x)
@@ -59,6 +70,14 @@ export default class Box {
return new Box(x, y, width, height)
}
+ toArray () {
+ return [ this.x, this.y, this.width, this.height ]
+ }
+
+ toString () {
+ return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height
+ }
+
transform (m) {
if (!(m instanceof Matrix)) {
m = new Matrix(m)
@@ -91,24 +110,6 @@ export default class Box {
)
}
- addOffset () {
- // offset by window scroll position, because getBoundingClientRect changes when window is scrolled
- this.x += globals.window.pageXOffset
- this.y += globals.window.pageYOffset
- return new Box(this)
- }
-
- toString () {
- return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height
- }
-
- toArray () {
- return [ this.x, this.y, this.width, this.height ]
- }
-
- isNulled () {
- return isNulledBox(this)
- }
}
function getBox (el, getBBoxFn, retry) {
diff --git a/src/types/Color.js b/src/types/Color.js
index eb6168f..d5f0efa 100644
--- a/src/types/Color.js
+++ b/src/types/Color.js
@@ -62,6 +62,152 @@ export default class Color {
this.init(...inputs)
}
+ // Test if given value is a color
+ static isColor (color) {
+ return color && (
+ color instanceof Color
+ || this.isRgb(color)
+ || this.test(color)
+ )
+ }
+
+ // Test if given value is an rgb object
+ static isRgb (color) {
+ return color && typeof color.r === 'number'
+ && typeof color.g === 'number'
+ && typeof color.b === 'number'
+ }
+
+ /*
+ Generating random colors
+ */
+ static random (mode = 'vibrant', t, u) {
+
+ // Get the math modules
+ const { random, round, sin, PI: pi } = Math
+
+ // Run the correct generator
+ if (mode === 'vibrant') {
+
+ const l = (81 - 57) * random() + 57
+ const c = (83 - 45) * random() + 45
+ const h = 360 * random()
+ const color = new Color(l, c, h, 'lch')
+ return color
+
+ } else if (mode === 'sine') {
+
+ t = t == null ? random() : t
+ const r = round(80 * sin(2 * pi * t / 0.5 + 0.01) + 150)
+ const g = round(50 * sin(2 * pi * t / 0.5 + 4.6) + 200)
+ const b = round(100 * sin(2 * pi * t / 0.5 + 2.3) + 150)
+ const color = new Color(r, g, b)
+ return color
+
+ } else if (mode === 'pastel') {
+
+ const l = (94 - 86) * random() + 86
+ const c = (26 - 9) * random() + 9
+ const h = 360 * random()
+ const color = new Color(l, c, h, 'lch')
+ return color
+
+ } else if (mode === 'dark') {
+
+ const l = 10 + 10 * random()
+ const c = (125 - 75) * random() + 86
+ const h = 360 * random()
+ const color = new Color(l, c, h, 'lch')
+ return color
+
+ } else if (mode === 'rgb') {
+
+ const r = 255 * random()
+ const g = 255 * random()
+ const b = 255 * random()
+ const color = new Color(r, g, b)
+ return color
+
+ } else if (mode === 'lab') {
+
+ const l = 100 * random()
+ const a = 256 * random() - 128
+ const b = 256 * random() - 128
+ const color = new Color(l, a, b, 'lab')
+ return color
+
+ } else if (mode === 'grey') {
+
+ const grey = 255 * random()
+ const color = new Color(grey, grey, grey)
+ return color
+
+ } else {
+
+ throw new Error('Unsupported random color mode')
+
+ }
+ }
+
+ // Test if given value is a color string
+ static test (color) {
+ return (typeof color === 'string')
+ && (isHex.test(color) || isRgb.test(color))
+ }
+
+ cmyk () {
+
+ // Get the rgb values for the current color
+ const { _a, _b, _c } = this.rgb()
+ const [ r, g, b ] = [ _a, _b, _c ].map(v => v / 255)
+
+ // Get the cmyk values in an unbounded format
+ const k = Math.min(1 - r, 1 - g, 1 - b)
+
+ if (k === 1) {
+ // Catch the black case
+ return new Color(0, 0, 0, 1, 'cmyk')
+ }
+
+ const c = (1 - r - k) / (1 - k)
+ const m = (1 - g - k) / (1 - k)
+ const y = (1 - b - k) / (1 - k)
+
+ // Construct the new color
+ const color = new Color(c, m, y, k, 'cmyk')
+ return color
+ }
+
+ hsl () {
+
+ // Get the rgb values
+ const { _a, _b, _c } = this.rgb()
+ const [ r, g, b ] = [ _a, _b, _c ].map(v => v / 255)
+
+ // Find the maximum and minimum values to get the lightness
+ const max = Math.max(r, g, b)
+ const min = Math.min(r, g, b)
+ const l = (max + min) / 2
+
+ // If the r, g, v values are identical then we are grey
+ const isGrey = max === min
+
+ // Calculate the hue and saturation
+ const delta = max - min
+ const s = isGrey ? 0
+ : l > 0.5 ? delta / (2 - max - min)
+ : delta / (max + min)
+ const h = isGrey ? 0
+ : max === r ? ((g - b) / delta + (g < b ? 6 : 0)) / 6
+ : max === g ? ((b - r) / delta + 2) / 6
+ : max === b ? ((r - g) / delta + 4) / 6
+ : 0
+
+ // Construct and return the new color
+ const color = new Color(360 * h, 100 * s, 100 * l, 'hsl')
+ return color
+ }
+
init (a = 0, b = 0, c = 0, d = 0, space = 'rgb') {
// This catches the case when a falsy value is passed like ''
a = !a ? 0 : a
@@ -113,6 +259,37 @@ export default class Color {
Object.assign(this, components)
}
+ lab () {
+ // Get the xyz color
+ const { x, y, z } = this.xyz()
+
+ // Get the lab components
+ const l = (116 * y) - 16
+ const a = 500 * (x - y)
+ const b = 200 * (y - z)
+
+ // Construct and return a new color
+ const color = new Color(l, a, b, 'lab')
+ return color
+ }
+
+ lch () {
+
+ // Get the lab color directly
+ const { l, a, b } = this.lab()
+
+ // Get the chromaticity and the hue using polar coordinates
+ const c = Math.sqrt(a ** 2 + b ** 2)
+ let h = 180 * Math.atan2(b, a) / Math.PI
+ if (h < 0) {
+ h *= -1
+ h = 360 - h
+ }
+
+ // Make a new color and return it
+ const color = new Color(l, c, h, 'lch')
+ return color
+ }
/*
Conversion Methods
*/
@@ -207,18 +384,24 @@ export default class Color {
}
}
- lab () {
- // Get the xyz color
- const { x, y, z } = this.xyz()
+ toArray () {
+ const { _a, _b, _c, _d, space } = this
+ return [ _a, _b, _c, _d, space ]
+ }
- // Get the lab components
- const l = (116 * y) - 16
- const a = 500 * (x - y)
- const b = 200 * (y - z)
+ toHex () {
+ const [ r, g, b ] = this._clamped().map(componentHex)
+ return `#${r}${g}${b}`
+ }
- // Construct and return a new color
- const color = new Color(l, a, b, 'lab')
- return color
+ toRgb () {
+ const [ rV, gV, bV ] = this._clamped()
+ const string = `rgb(${rV},${gV},${bV})`
+ return string
+ }
+
+ toString () {
+ return this.toHex()
}
xyz () {
@@ -247,77 +430,6 @@ export default class Color {
return color
}
- lch () {
-
- // Get the lab color directly
- const { l, a, b } = this.lab()
-
- // Get the chromaticity and the hue using polar coordinates
- const c = Math.sqrt(a ** 2 + b ** 2)
- let h = 180 * Math.atan2(b, a) / Math.PI
- if (h < 0) {
- h *= -1
- h = 360 - h
- }
-
- // Make a new color and return it
- const color = new Color(l, c, h, 'lch')
- return color
- }
-
- hsl () {
-
- // Get the rgb values
- const { _a, _b, _c } = this.rgb()
- const [ r, g, b ] = [ _a, _b, _c ].map(v => v / 255)
-
- // Find the maximum and minimum values to get the lightness
- const max = Math.max(r, g, b)
- const min = Math.min(r, g, b)
- const l = (max + min) / 2
-
- // If the r, g, v values are identical then we are grey
- const isGrey = max === min
-
- // Calculate the hue and saturation
- const delta = max - min
- const s = isGrey ? 0
- : l > 0.5 ? delta / (2 - max - min)
- : delta / (max + min)
- const h = isGrey ? 0
- : max === r ? ((g - b) / delta + (g < b ? 6 : 0)) / 6
- : max === g ? ((b - r) / delta + 2) / 6
- : max === b ? ((r - g) / delta + 4) / 6
- : 0
-
- // Construct and return the new color
- const color = new Color(360 * h, 100 * s, 100 * l, 'hsl')
- return color
- }
-
- cmyk () {
-
- // Get the rgb values for the current color
- const { _a, _b, _c } = this.rgb()
- const [ r, g, b ] = [ _a, _b, _c ].map(v => v / 255)
-
- // Get the cmyk values in an unbounded format
- const k = Math.min(1 - r, 1 - g, 1 - b)
-
- if (k === 1) {
- // Catch the black case
- return new Color(0, 0, 0, 1, 'cmyk')
- }
-
- const c = (1 - r - k) / (1 - k)
- const m = (1 - g - k) / (1 - k)
- const y = (1 - b - k) / (1 - k)
-
- // Construct the new color
- const color = new Color(c, m, y, k, 'cmyk')
- return color
- }
-
/*
Input and Output methods
*/
@@ -329,121 +441,8 @@ export default class Color {
return [ _a, _b, _c ].map(format)
}
- toHex () {
- const [ r, g, b ] = this._clamped().map(componentHex)
- return `#${r}${g}${b}`
- }
-
- toString () {
- return this.toHex()
- }
-
- toRgb () {
- const [ rV, gV, bV ] = this._clamped()
- const string = `rgb(${rV},${gV},${bV})`
- return string
- }
-
- toArray () {
- const { _a, _b, _c, _d, space } = this
- return [ _a, _b, _c, _d, space ]
- }
-
- /*
- Generating random colors
- */
-
- static random (mode = 'vibrant', t, u) {
-
- // Get the math modules
- const { random, round, sin, PI: pi } = Math
-
- // Run the correct generator
- if (mode === 'vibrant') {
-
- const l = (81 - 57) * random() + 57
- const c = (83 - 45) * random() + 45
- const h = 360 * random()
- const color = new Color(l, c, h, 'lch')
- return color
-
- } else if (mode === 'sine') {
-
- t = t == null ? random() : t
- const r = round(80 * sin(2 * pi * t / 0.5 + 0.01) + 150)
- const g = round(50 * sin(2 * pi * t / 0.5 + 4.6) + 200)
- const b = round(100 * sin(2 * pi * t / 0.5 + 2.3) + 150)
- const color = new Color(r, g, b)
- return color
-
- } else if (mode === 'pastel') {
-
- const l = (94 - 86) * random() + 86
- const c = (26 - 9) * random() + 9
- const h = 360 * random()
- const color = new Color(l, c, h, 'lch')
- return color
-
- } else if (mode === 'dark') {
-
- const l = 10 + 10 * random()
- const c = (125 - 75) * random() + 86
- const h = 360 * random()
- const color = new Color(l, c, h, 'lch')
- return color
-
- } else if (mode === 'rgb') {
-
- const r = 255 * random()
- const g = 255 * random()
- const b = 255 * random()
- const color = new Color(r, g, b)
- return color
-
- } else if (mode === 'lab') {
-
- const l = 100 * random()
- const a = 256 * random() - 128
- const b = 256 * random() - 128
- const color = new Color(l, a, b, 'lab')
- return color
-
- } else if (mode === 'grey') {
-
- const grey = 255 * random()
- const color = new Color(grey, grey, grey)
- return color
-
- } else {
-
- throw new Error('Unsupported random color mode')
-
- }
- }
-
/*
Constructing colors
*/
- // Test if given value is a color string
- static test (color) {
- return (typeof color === 'string')
- && (isHex.test(color) || isRgb.test(color))
- }
-
- // Test if given value is an rgb object
- static isRgb (color) {
- return color && typeof color.r === 'number'
- && typeof color.g === 'number'
- && typeof color.b === 'number'
- }
-
- // Test if given value is a color
- static isColor (color) {
- return color && (
- color instanceof Color
- || this.isRgb(color)
- || this.test(color)
- )
- }
}
diff --git a/src/types/Matrix.js b/src/types/Matrix.js
index 9b783da..d4f516c 100644
--- a/src/types/Matrix.js
+++ b/src/types/Matrix.js
@@ -13,72 +13,99 @@ export default class Matrix {
this.init(...args)
}
- // Initialize
- init (source) {
- var base = Matrix.fromArray([ 1, 0, 0, 1, 0, 0 ])
-
- // ensure source as object
- source = source instanceof Element ? source.matrixify()
- : typeof source === 'string' ? Matrix.fromArray(source.split(delimiter).map(parseFloat))
- : Array.isArray(source) ? Matrix.fromArray(source)
- : (typeof source === 'object' && Matrix.isMatrixLike(source)) ? source
- : (typeof source === 'object') ? new Matrix().transform(source)
- : arguments.length === 6 ? Matrix.fromArray([].slice.call(arguments))
- : base
+ static formatTransforms (o) {
+ // Get all of the parameters required to form the matrix
+ var flipBoth = o.flip === 'both' || o.flip === true
+ var flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1
+ var flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1
+ var skewX = o.skew && o.skew.length ? o.skew[0]
+ : isFinite(o.skew) ? o.skew
+ : isFinite(o.skewX) ? o.skewX
+ : 0
+ var skewY = o.skew && o.skew.length ? o.skew[1]
+ : isFinite(o.skew) ? o.skew
+ : isFinite(o.skewY) ? o.skewY
+ : 0
+ var scaleX = o.scale && o.scale.length ? o.scale[0] * flipX
+ : isFinite(o.scale) ? o.scale * flipX
+ : isFinite(o.scaleX) ? o.scaleX * flipX
+ : flipX
+ var scaleY = o.scale && o.scale.length ? o.scale[1] * flipY
+ : isFinite(o.scale) ? o.scale * flipY
+ : isFinite(o.scaleY) ? o.scaleY * flipY
+ : flipY
+ var shear = o.shear || 0
+ var theta = o.rotate || o.theta || 0
+ var origin = new Point(o.origin || o.around || o.ox || o.originX, o.oy || o.originY)
+ var ox = origin.x
+ var oy = origin.y
+ // We need Point to be invalid if nothing was passed because we cannot default to 0 here. Thats why NaN
+ var position = new Point(o.position || o.px || o.positionX || NaN, o.py || o.positionY || NaN)
+ var px = position.x
+ var py = position.y
+ var translate = new Point(o.translate || o.tx || o.translateX, o.ty || o.translateY)
+ var tx = translate.x
+ var ty = translate.y
+ var relative = new Point(o.relative || o.rx || o.relativeX, o.ry || o.relativeY)
+ var rx = relative.x
+ var ry = relative.y
- // Merge the source matrix with the base matrix
- this.a = source.a != null ? source.a : base.a
- this.b = source.b != null ? source.b : base.b
- this.c = source.c != null ? source.c : base.c
- this.d = source.d != null ? source.d : base.d
- this.e = source.e != null ? source.e : base.e
- this.f = source.f != null ? source.f : base.f
+ // Populate all of the values
+ return {
+ scaleX, scaleY, skewX, skewY, shear, theta, rx, ry, tx, ty, ox, oy, px, py
+ }
+ }
- return this
+ static fromArray (a) {
+ return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] }
}
- // Clones this matrix
- clone () {
- return new Matrix(this)
+ static isMatrixLike (o) {
+ return (
+ o.a != null
+ || o.b != null
+ || o.c != null
+ || o.d != null
+ || o.e != null
+ || o.f != null
+ )
}
- // Transform a matrix into another matrix by manipulating the space
- transform (o) {
- // Check if o is a matrix and then left multiply it directly
- if (Matrix.isMatrixLike(o)) {
- var matrix = new Matrix(o)
- return matrix.multiplyO(this)
- }
+ // left matrix, right matrix, target matrix which is overwritten
+ static matrixMultiply (l, r, o) {
+ // Work out the product directly
+ var a = l.a * r.a + l.c * r.b
+ var b = l.b * r.a + l.d * r.b
+ var c = l.a * r.c + l.c * r.d
+ var d = l.b * r.c + l.d * r.d
+ var e = l.e + l.a * r.e + l.c * r.f
+ var f = l.f + l.b * r.e + l.d * r.f
- // Get the proposed transformations and the current transformations
- var t = Matrix.formatTransforms(o)
- var current = this
- const { x: ox, y: oy } = new Point(t.ox, t.oy).transform(current)
+ // make sure to use local variables because l/r and o could be the same
+ o.a = a
+ o.b = b
+ o.c = c
+ o.d = d
+ o.e = e
+ o.f = f
- // Construct the resulting matrix
- var transformer = new Matrix()
- .translateO(t.rx, t.ry)
- .lmultiplyO(current)
- .translateO(-ox, -oy)
- .scaleO(t.scaleX, t.scaleY)
- .skewO(t.skewX, t.skewY)
- .shearO(t.shear)
- .rotateO(t.theta)
- .translateO(ox, oy)
+ return o
+ }
- // If we want the origin at a particular place, we force it there
- if (isFinite(t.px) || isFinite(t.py)) {
- const origin = new Point(ox, oy).transform(transformer)
- // TODO: Replace t.px with isFinite(t.px)
- // Doesnt work because t.px is also 0 if it wasnt passed
- const dx = isFinite(t.px) ? t.px - origin.x : 0
- const dy = isFinite(t.py) ? t.py - origin.y : 0
- transformer.translateO(dx, dy)
- }
+ around (cx, cy, matrix) {
+ return this.clone().aroundO(cx, cy, matrix)
+ }
- // Translate now after positioning
- transformer.translateO(t.tx, t.ty)
- return transformer
+ // Transform around a center point
+ aroundO (cx, cy, matrix) {
+ var dx = cx || 0
+ var dy = cy || 0
+ return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy)
+ }
+
+ // Clones this matrix
+ clone () {
+ return new Matrix(this)
}
// Decomposes this matrix into its affine parameters
@@ -134,32 +161,52 @@ export default class Matrix {
}
}
- // Left multiplies by the given matrix
- multiply (matrix) {
- return this.clone().multiplyO(matrix)
+ // Check if two matrices are equal
+ equals (other) {
+ if (other === this) return true
+ var comp = new Matrix(other)
+ return closeEnough(this.a, comp.a) && closeEnough(this.b, comp.b)
+ && closeEnough(this.c, comp.c) && closeEnough(this.d, comp.d)
+ && closeEnough(this.e, comp.e) && closeEnough(this.f, comp.f)
}
- multiplyO (matrix) {
- // Get the matrices
- var l = this
- var r = matrix instanceof Matrix
- ? matrix
- : new Matrix(matrix)
-
- return Matrix.matrixMultiply(l, r, this)
+ // Flip matrix on x or y, at a given offset
+ flip (axis, around) {
+ return this.clone().flipO(axis, around)
}
- lmultiply (matrix) {
- return this.clone().lmultiplyO(matrix)
+ flipO (axis, around) {
+ return axis === 'x' ? this.scaleO(-1, 1, around, 0)
+ : axis === 'y' ? this.scaleO(1, -1, 0, around)
+ : this.scaleO(-1, -1, axis, around || axis) // Define an x, y flip point
}
- lmultiplyO (matrix) {
- var r = this
- var l = matrix instanceof Matrix
- ? matrix
- : new Matrix(matrix)
+ // Initialize
+ init (source) {
+ var base = Matrix.fromArray([ 1, 0, 0, 1, 0, 0 ])
- return Matrix.matrixMultiply(l, r, this)
+ // ensure source as object
+ source = source instanceof Element ? source.matrixify()
+ : typeof source === 'string' ? Matrix.fromArray(source.split(delimiter).map(parseFloat))
+ : Array.isArray(source) ? Matrix.fromArray(source)
+ : (typeof source === 'object' && Matrix.isMatrixLike(source)) ? source
+ : (typeof source === 'object') ? new Matrix().transform(source)
+ : arguments.length === 6 ? Matrix.fromArray([].slice.call(arguments))
+ : base
+
+ // Merge the source matrix with the base matrix
+ this.a = source.a != null ? source.a : base.a
+ this.b = source.b != null ? source.b : base.b
+ this.c = source.c != null ? source.c : base.c
+ this.d = source.d != null ? source.d : base.d
+ this.e = source.e != null ? source.e : base.e
+ this.f = source.f != null ? source.f : base.f
+
+ return this
+ }
+
+ inverse () {
+ return this.clone().inverseO()
}
// Inverses matrix
@@ -197,44 +244,32 @@ export default class Matrix {
return this
}
- inverse () {
- return this.clone().inverseO()
+ lmultiply (matrix) {
+ return this.clone().lmultiplyO(matrix)
}
- // Translate matrix
- translate (x, y) {
- return this.clone().translateO(x, y)
- }
+ lmultiplyO (matrix) {
+ var r = this
+ var l = matrix instanceof Matrix
+ ? matrix
+ : new Matrix(matrix)
- translateO (x, y) {
- this.e += x || 0
- this.f += y || 0
- return this
+ return Matrix.matrixMultiply(l, r, this)
}
- // Scale matrix
- scale (x, y, cx, cy) {
- return this.clone().scaleO(...arguments)
+ // Left multiplies by the given matrix
+ multiply (matrix) {
+ return this.clone().multiplyO(matrix)
}
- scaleO (x, y = x, cx = 0, cy = 0) {
- // Support uniform scaling
- if (arguments.length === 3) {
- cy = cx
- cx = y
- y = x
- }
-
- const { a, b, c, d, e, f } = this
-
- this.a = a * x
- this.b = b * y
- this.c = c * x
- this.d = d * y
- this.e = e * x - cx * x + cx
- this.f = f * y - cy * y + cy
+ multiplyO (matrix) {
+ // Get the matrices
+ var l = this
+ var r = matrix instanceof Matrix
+ ? matrix
+ : new Matrix(matrix)
- return this
+ return Matrix.matrixMultiply(l, r, this)
}
// Rotate matrix
@@ -261,15 +296,29 @@ export default class Matrix {
return this
}
- // Flip matrix on x or y, at a given offset
- flip (axis, around) {
- return this.clone().flipO(axis, around)
+ // Scale matrix
+ scale (x, y, cx, cy) {
+ return this.clone().scaleO(...arguments)
}
- flipO (axis, around) {
- return axis === 'x' ? this.scaleO(-1, 1, around, 0)
- : axis === 'y' ? this.scaleO(1, -1, 0, around)
- : this.scaleO(-1, -1, axis, around || axis) // Define an x, y flip point
+ scaleO (x, y = x, cx = 0, cy = 0) {
+ // Support uniform scaling
+ if (arguments.length === 3) {
+ cy = cx
+ cx = y
+ y = x
+ }
+
+ const { a, b, c, d, e, f } = this
+
+ this.a = a * x
+ this.b = b * y
+ this.c = c * x
+ this.d = d * y
+ this.e = e * x - cx * x + cx
+ this.f = f * y - cy * y + cy
+
+ return this
}
// Shear matrix
@@ -329,33 +378,63 @@ export default class Matrix {
return this.skew(0, y, cx, cy)
}
- // Transform around a center point
- aroundO (cx, cy, matrix) {
- var dx = cx || 0
- var dy = cy || 0
- return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy)
+ toArray () {
+ return [ this.a, this.b, this.c, this.d, this.e, this.f ]
}
- around (cx, cy, matrix) {
- return this.clone().aroundO(cx, cy, matrix)
+ // Convert matrix to string
+ toString () {
+ return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')'
}
- // Check if two matrices are equal
- equals (other) {
- if (other === this) return true
- var comp = new Matrix(other)
- return closeEnough(this.a, comp.a) && closeEnough(this.b, comp.b)
- && closeEnough(this.c, comp.c) && closeEnough(this.d, comp.d)
- && closeEnough(this.e, comp.e) && closeEnough(this.f, comp.f)
+ // Transform a matrix into another matrix by manipulating the space
+ transform (o) {
+ // Check if o is a matrix and then left multiply it directly
+ if (Matrix.isMatrixLike(o)) {
+ var matrix = new Matrix(o)
+ return matrix.multiplyO(this)
+ }
+
+ // Get the proposed transformations and the current transformations
+ var t = Matrix.formatTransforms(o)
+ var current = this
+ const { x: ox, y: oy } = new Point(t.ox, t.oy).transform(current)
+
+ // Construct the resulting matrix
+ var transformer = new Matrix()
+ .translateO(t.rx, t.ry)
+ .lmultiplyO(current)
+ .translateO(-ox, -oy)
+ .scaleO(t.scaleX, t.scaleY)
+ .skewO(t.skewX, t.skewY)
+ .shearO(t.shear)
+ .rotateO(t.theta)
+ .translateO(ox, oy)
+
+ // If we want the origin at a particular place, we force it there
+ if (isFinite(t.px) || isFinite(t.py)) {
+ const origin = new Point(ox, oy).transform(transformer)
+ // TODO: Replace t.px with isFinite(t.px)
+ // Doesnt work because t.px is also 0 if it wasnt passed
+ const dx = isFinite(t.px) ? t.px - origin.x : 0
+ const dy = isFinite(t.py) ? t.py - origin.y : 0
+ transformer.translateO(dx, dy)
+ }
+
+ // Translate now after positioning
+ transformer.translateO(t.tx, t.ty)
+ return transformer
}
- // Convert matrix to string
- toString () {
- return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')'
+ // Translate matrix
+ translate (x, y) {
+ return this.clone().translateO(x, y)
}
- toArray () {
- return [ this.a, this.b, this.c, this.d, this.e, this.f ]
+ translateO (x, y) {
+ this.e += x || 0
+ this.f += y || 0
+ return this
}
valueOf () {
@@ -369,84 +448,6 @@ export default class Matrix {
}
}
- static fromArray (a) {
- return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] }
- }
-
- static isMatrixLike (o) {
- return (
- o.a != null
- || o.b != null
- || o.c != null
- || o.d != null
- || o.e != null
- || o.f != null
- )
- }
-
- static formatTransforms (o) {
- // Get all of the parameters required to form the matrix
- var flipBoth = o.flip === 'both' || o.flip === true
- var flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1
- var flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1
- var skewX = o.skew && o.skew.length ? o.skew[0]
- : isFinite(o.skew) ? o.skew
- : isFinite(o.skewX) ? o.skewX
- : 0
- var skewY = o.skew && o.skew.length ? o.skew[1]
- : isFinite(o.skew) ? o.skew
- : isFinite(o.skewY) ? o.skewY
- : 0
- var scaleX = o.scale && o.scale.length ? o.scale[0] * flipX
- : isFinite(o.scale) ? o.scale * flipX
- : isFinite(o.scaleX) ? o.scaleX * flipX
- : flipX
- var scaleY = o.scale && o.scale.length ? o.scale[1] * flipY
- : isFinite(o.scale) ? o.scale * flipY
- : isFinite(o.scaleY) ? o.scaleY * flipY
- : flipY
- var shear = o.shear || 0
- var theta = o.rotate || o.theta || 0
- var origin = new Point(o.origin || o.around || o.ox || o.originX, o.oy || o.originY)
- var ox = origin.x
- var oy = origin.y
- // We need Point to be invalid if nothing was passed because we cannot default to 0 here. Thats why NaN
- var position = new Point(o.position || o.px || o.positionX || NaN, o.py || o.positionY || NaN)
- var px = position.x
- var py = position.y
- var translate = new Point(o.translate || o.tx || o.translateX, o.ty || o.translateY)
- var tx = translate.x
- var ty = translate.y
- var relative = new Point(o.relative || o.rx || o.relativeX, o.ry || o.relativeY)
- var rx = relative.x
- var ry = relative.y
-
- // Populate all of the values
- return {
- scaleX, scaleY, skewX, skewY, shear, theta, rx, ry, tx, ty, ox, oy, px, py
- }
- }
-
- // left matrix, right matrix, target matrix which is overwritten
- static matrixMultiply (l, r, o) {
- // Work out the product directly
- var a = l.a * r.a + l.c * r.b
- var b = l.b * r.a + l.d * r.b
- var c = l.a * r.c + l.c * r.d
- var d = l.b * r.c + l.d * r.d
- var e = l.e + l.a * r.e + l.c * r.f
- var f = l.f + l.b * r.e + l.d * r.f
-
- // make sure to use local variables because l/r and o could be the same
- o.a = a
- o.b = b
- o.c = c
- o.d = d
- o.e = e
- o.f = f
-
- return o
- }
}
export function ctm () {
diff --git a/src/types/PathArray.js b/src/types/PathArray.js
index d9c1eb2..b7c3c33 100644
--- a/src/types/PathArray.js
+++ b/src/types/PathArray.js
@@ -125,9 +125,10 @@ for (var i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
}
export default class PathArray extends SVGArray {
- // Convert array to string
- toString () {
- return arrayToString(this)
+ // Get bounding box of path
+ bbox () {
+ parser().path.setAttribute('d', this.toString())
+ return new Box(parser.nodes.path.getBBox())
}
// Move path string
@@ -171,52 +172,6 @@ export default class PathArray extends SVGArray {
return this
}
- // Resize path string
- size (width, height) {
- // get bounding box of current situation
- var box = this.bbox()
- var i, l
-
- // If the box width or height is 0 then we ignore
- // transformations on the respective axis
- box.width = box.width === 0 ? 1 : box.width
- box.height = box.height === 0 ? 1 : box.height
-
- // recalculate position of all points according to new size
- for (i = this.length - 1; i >= 0; i--) {
- l = this[i][0]
-
- if (l === 'M' || l === 'L' || l === 'T') {
- 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[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x
- } else if (l === 'V') {
- this[i][1] = ((this[i][1] - box.y) * height) / box.height + box.y
- } else if (l === 'C' || l === 'S' || l === 'Q') {
- 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[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[i][1] = (this[i][1] * width) / box.width
- this[i][2] = (this[i][2] * height) / box.height
-
- // move position values
- this[i][6] = ((this[i][6] - box.x) * width) / box.width + box.x
- this[i][7] = ((this[i][7] - box.y) * height) / box.height + box.y
- }
- }
-
- return this
- }
-
// Absolutize and parse path to array
parse (array = [ 'M', 0, 0 ]) {
// prepare for parsing
@@ -264,9 +219,55 @@ export default class PathArray extends SVGArray {
return result
}
- // Get bounding box of path
- bbox () {
- parser().path.setAttribute('d', this.toString())
- return new Box(parser.nodes.path.getBBox())
+ // Resize path string
+ size (width, height) {
+ // get bounding box of current situation
+ var box = this.bbox()
+ var i, l
+
+ // If the box width or height is 0 then we ignore
+ // transformations on the respective axis
+ box.width = box.width === 0 ? 1 : box.width
+ box.height = box.height === 0 ? 1 : box.height
+
+ // recalculate position of all points according to new size
+ for (i = this.length - 1; i >= 0; i--) {
+ l = this[i][0]
+
+ if (l === 'M' || l === 'L' || l === 'T') {
+ 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[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x
+ } else if (l === 'V') {
+ this[i][1] = ((this[i][1] - box.y) * height) / box.height + box.y
+ } else if (l === 'C' || l === 'S' || l === 'Q') {
+ 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[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[i][1] = (this[i][1] * width) / box.width
+ this[i][2] = (this[i][2] * height) / box.height
+
+ // move position values
+ this[i][6] = ((this[i][6] - box.x) * width) / box.width + box.x
+ this[i][7] = ((this[i][7] - box.y) * height) / box.height + box.y
+ }
+ }
+
+ return this
}
+
+ // Convert array to string
+ toString () {
+ return arrayToString(this)
+ }
+
}
diff --git a/src/types/Point.js b/src/types/Point.js
index 634ffff..cb09bf3 100644
--- a/src/types/Point.js
+++ b/src/types/Point.js
@@ -6,6 +6,11 @@ export default class Point {
this.init(...args)
}
+ // Clone point
+ clone () {
+ return new Point(this)
+ }
+
init (x, y) {
const base = { x: 0, y: 0 }
@@ -21,9 +26,8 @@ export default class Point {
return this
}
- // Clone point
- clone () {
- return new Point(this)
+ toArray () {
+ return [ this.x, this.y ]
}
transform (m) {
@@ -45,9 +49,6 @@ export default class Point {
return this
}
- toArray () {
- return [ this.x, this.y ]
- }
}
export function point (x, y) {
diff --git a/src/types/PointArray.js b/src/types/PointArray.js
index 27a2076..28e649c 100644
--- a/src/types/PointArray.js
+++ b/src/types/PointArray.js
@@ -4,24 +4,37 @@ import Box from './Box.js'
import Matrix from './Matrix.js'
export default class PointArray extends SVGArray {
- // Convert array to string
- toString () {
- // convert to a poly point string
- for (var i = 0, il = this.length, array = []; i < il; i++) {
- array.push(this[i].join(','))
- }
-
- return array.join(' ')
+ // Get bounding box of points
+ bbox () {
+ var maxX = -Infinity
+ var maxY = -Infinity
+ var minX = Infinity
+ var minY = Infinity
+ this.forEach(function (el) {
+ maxX = Math.max(el[0], maxX)
+ maxY = Math.max(el[1], maxY)
+ minX = Math.min(el[0], minX)
+ minY = Math.min(el[1], minY)
+ })
+ return new Box(minX, minY, maxX - minX, maxY - minY)
}
- // Convert array to line object
- toLine () {
- return {
- x1: this[0][0],
- y1: this[0][1],
- x2: this[1][0],
- y2: this[1][1]
+ // Move point string
+ move (x, y) {
+ var box = this.bbox()
+
+ // get relative offset
+ x -= box.x
+ y -= box.y
+
+ // move every point
+ if (!isNaN(x) && !isNaN(y)) {
+ for (var i = this.length - 1; i >= 0; i--) {
+ this[i] = [ this[i][0] + x, this[i][1] + y ]
+ }
}
+
+ return this
}
// Parse point string and flat array
@@ -48,6 +61,40 @@ export default class PointArray extends SVGArray {
return points
}
+ // Resize poly string
+ size (width, height) {
+ var i
+ var box = this.bbox()
+
+ // recalculate position of all points according to new size
+ for (i = this.length - 1; i >= 0; i--) {
+ if (box.width) this[i][0] = ((this[i][0] - box.x) * width) / box.width + box.x
+ if (box.height) this[i][1] = ((this[i][1] - box.y) * height) / box.height + box.y
+ }
+
+ return this
+ }
+
+ // Convert array to line object
+ toLine () {
+ return {
+ x1: this[0][0],
+ y1: this[0][1],
+ x2: this[1][0],
+ y2: this[1][1]
+ }
+ }
+
+ // Convert array to string
+ toString () {
+ // convert to a poly point string
+ for (var i = 0, il = this.length, array = []; i < il; i++) {
+ array.push(this[i].join(','))
+ }
+
+ return array.join(' ')
+ }
+
transform (m) {
return this.clone().transformO(m)
}
@@ -68,50 +115,4 @@ export default class PointArray extends SVGArray {
return this
}
- // Move point string
- move (x, y) {
- var box = this.bbox()
-
- // get relative offset
- x -= box.x
- y -= box.y
-
- // move every point
- if (!isNaN(x) && !isNaN(y)) {
- for (var i = this.length - 1; i >= 0; i--) {
- this[i] = [ this[i][0] + x, this[i][1] + y ]
- }
- }
-
- return this
- }
-
- // Resize poly string
- size (width, height) {
- var i
- var box = this.bbox()
-
- // recalculate position of all points according to new size
- for (i = this.length - 1; i >= 0; i--) {
- if (box.width) this[i][0] = ((this[i][0] - box.x) * width) / box.width + box.x
- if (box.height) this[i][1] = ((this[i][1] - box.y) * height) / box.height + box.y
- }
-
- return this
- }
-
- // Get bounding box of points
- bbox () {
- var maxX = -Infinity
- var maxY = -Infinity
- var minX = Infinity
- var minY = Infinity
- this.forEach(function (el) {
- maxX = Math.max(el[0], maxX)
- maxY = Math.max(el[1], maxY)
- minX = Math.min(el[0], minX)
- minY = Math.min(el[1], minY)
- })
- return new Box(minX, minY, maxX - minX, maxY - minY)
- }
}
diff --git a/src/types/SVGArray.js b/src/types/SVGArray.js
index dafa2d4..6ce024a 100644
--- a/src/types/SVGArray.js
+++ b/src/types/SVGArray.js
@@ -6,6 +6,10 @@ export default class SVGArray extends Array {
this.init(...args)
}
+ clone () {
+ return new this.constructor(this)
+ }
+
init (arr) {
// This catches the case, that native map tries to create an array with new Array(1)
if (typeof arr === 'number') return this
@@ -14,10 +18,22 @@ export default class SVGArray extends Array {
return this
}
+ // Parse whitespace separated string
+ parse (array = []) {
+ // If already is an array, no need to parse it
+ if (array instanceof Array) return array
+
+ return array.trim().split(delimiter).map(parseFloat)
+ }
+
toArray () {
return Array.prototype.concat.apply([], this)
}
+ toSet () {
+ return new Set(this)
+ }
+
toString () {
return this.join(' ')
}
@@ -29,19 +45,4 @@ export default class SVGArray extends Array {
return ret
}
- // Parse whitespace separated string
- parse (array = []) {
- // If already is an array, no need to parse it
- if (array instanceof Array) return array
-
- return array.trim().split(delimiter).map(parseFloat)
- }
-
- clone () {
- return new this.constructor(this)
- }
-
- toSet () {
- return new Set(this)
- }
}
diff --git a/src/types/SVGNumber.js b/src/types/SVGNumber.js
index fedb00e..914919e 100644
--- a/src/types/SVGNumber.js
+++ b/src/types/SVGNumber.js
@@ -7,6 +7,16 @@ export default class SVGNumber {
this.init(...args)
}
+ convert (unit) {
+ return new SVGNumber(this.value, unit)
+ }
+
+ // Divide number
+ divide (number) {
+ number = new SVGNumber(number)
+ return new SVGNumber(this / number, this.unit || number.unit)
+ }
+
init (value, unit) {
unit = Array.isArray(value) ? value[1] : unit
value = Array.isArray(value) ? value[0] : value
@@ -46,23 +56,10 @@ export default class SVGNumber {
return this
}
- toString () {
- return (this.unit === '%' ? ~~(this.value * 1e8) / 1e6
- : this.unit === 's' ? this.value / 1e3
- : this.value
- ) + this.unit
- }
-
- toJSON () {
- return this.toString()
- }
-
- toArray () {
- return [ this.value, this.unit ]
- }
-
- valueOf () {
- return this.value
+ // Subtract number
+ minus (number) {
+ number = new SVGNumber(number)
+ return new SVGNumber(this - number, this.unit || number.unit)
}
// Add number
@@ -71,25 +68,29 @@ export default class SVGNumber {
return new SVGNumber(this + number, this.unit || number.unit)
}
- // Subtract number
- minus (number) {
- number = new SVGNumber(number)
- return new SVGNumber(this - number, this.unit || number.unit)
- }
-
// Multiply number
times (number) {
number = new SVGNumber(number)
return new SVGNumber(this * number, this.unit || number.unit)
}
- // Divide number
- divide (number) {
- number = new SVGNumber(number)
- return new SVGNumber(this / number, this.unit || number.unit)
+ toArray () {
+ return [ this.value, this.unit ]
}
- convert (unit) {
- return new SVGNumber(this.value, unit)
+ toJSON () {
+ return this.toString()
+ }
+
+ toString () {
+ return (this.unit === '%' ? ~~(this.value * 1e8) / 1e6
+ : this.unit === 's' ? this.value / 1e3
+ : this.value
+ ) + this.unit
}
+
+ valueOf () {
+ return this.value
+ }
+
}
diff --git a/svg.js.d.ts b/svg.js.d.ts
index 5b7b3fa..2264c66 100644
--- a/svg.js.d.ts
+++ b/svg.js.d.ts
@@ -4,6 +4,9 @@
// trick to keep reference to Array build-in type
declare class BuiltInArray<T> extends Array<T> { }
+// trick to have nice attribute list for CSS
+declare type CSSStyleName = Exclude<keyof CSSStyleDeclaration, "parentRule" | "length" >
+
declare module "@svgdotjs/svg.js" {
function SVG(): Svg;
@@ -771,11 +774,19 @@ declare module "@svgdotjs/svg.js" {
}
// Timeline.js
+ interface ScheduledRunnerInfo {
+ start: number
+ duration: number
+ end: number
+ runner: Runner
+ }
+
class Timeline extends EventTarget {
constructor()
constructor(fn: Function)
- schedule(runner?: Runner, delay?: number, when?: string): this
+ schedule(runner: Runner, delay?: number, when?: string): this
+ schedule(): ScheduledRunnerInfo[]
unschedule(runner: Runner): this
getEndTime(): number
updateTime(): this
@@ -812,7 +823,6 @@ declare module "@svgdotjs/svg.js" {
constructor();
constructor(options: Function);
constructor(options: number);
- constructor(options: object);
constructor(options: Controller);
static sanitise: (duration?: TimeLike, delay?: number, when?: string) => object
@@ -1006,11 +1016,11 @@ declare module "@svgdotjs/svg.js" {
toggleClass(name: string): this
// prototype method register in css.js
- css(): object;
- css(style: string): string
- css(style: string[]): object;
- css(style: string, val: any): this
- css(style: object): this
+ css(): Partial<CSSStyleDeclaration>
+ css<T extends CSSStyleName>(style: T): CSSStyleDeclaration[T]
+ css<T extends CSSStyleName[]>(style: T): Partial<CSSStyleDeclaration>
+ css<T extends CSSStyleName>(style: T, val: CSSStyleDeclaration[T]): this
+ css(style: Partial<CSSStyleDeclaration>): this
show(): this
hide(): this
visible(): boolean