Browse Source

Reverted some of the lint rules after chatting with fuzzy

This commit reverts some of the rules we added on the linter, it changed a lot of
code again... but these won't happen too often.

Changes
=======
- Modified the linter again
tags/3.0.0
Saivan 5 years ago
parent
commit
617aa12304
70 changed files with 2148 additions and 4221 deletions
  1. 0
    1
      .eslintrc.yaml
  2. 154
    226
      dist/svg.js
  3. 22
    48
      src/animation/Animator.js
  4. 77
    163
      src/animation/Controller.js
  5. 9
    27
      src/animation/Queue.js
  6. 293
    545
      src/animation/Runner.js
  7. 58
    136
      src/animation/Timeline.js
  8. 17
    33
      src/elements/A.js
  9. 12
    22
      src/elements/Bare.js
  10. 19
    33
      src/elements/Circle.js
  11. 16
    34
      src/elements/ClipPath.js
  12. 10
    20
      src/elements/Container.js
  13. 3
    11
      src/elements/Defs.js
  14. 85
    181
      src/elements/Dom.js
  15. 43
    87
      src/elements/Element.js
  16. 13
    21
      src/elements/Ellipse.js
  17. 8
    14
      src/elements/G.js
  18. 21
    45
      src/elements/Gradient.js
  19. 1
    1
      src/elements/HtmlNode.js
  20. 33
    63
      src/elements/Image.js
  21. 23
    43
      src/elements/Line.js
  22. 29
    51
      src/elements/Marker.js
  23. 16
    34
      src/elements/Mask.js
  24. 26
    52
      src/elements/Path.js
  25. 18
    40
      src/elements/Pattern.js
  26. 10
    16
      src/elements/Polygon.js
  27. 10
    16
      src/elements/Polyline.js
  28. 9
    15
      src/elements/Rect.js
  29. 1
    1
      src/elements/Shape.js
  30. 8
    16
      src/elements/Stop.js
  31. 23
    41
      src/elements/Style.js
  32. 23
    45
      src/elements/Svg.js
  33. 8
    14
      src/elements/Symbol.js
  34. 58
    116
      src/elements/Text.js
  35. 26
    52
      src/elements/TextPath.js
  36. 20
    36
      src/elements/Tspan.js
  37. 10
    18
      src/elements/Use.js
  38. 20
    20
      src/main.js
  39. 33
    69
      src/modules/core/attr.js
  40. 18
    34
      src/modules/core/circled.js
  41. 54
    106
      src/modules/core/event.js
  42. 8
    12
      src/modules/core/gradiented.js
  43. 5
    11
      src/modules/core/parser.js
  44. 8
    16
      src/modules/core/pointed.js
  45. 10
    20
      src/modules/core/poly.js
  46. 6
    12
      src/modules/core/selector.js
  47. 3
    9
      src/modules/core/textable.js
  48. 25
    57
      src/modules/optional/arrange.js
  49. 16
    32
      src/modules/optional/class.js
  50. 26
    54
      src/modules/optional/css.js
  51. 11
    25
      src/modules/optional/data.js
  52. 9
    29
      src/modules/optional/memory.js
  53. 83
    149
      src/modules/optional/sugar.js
  54. 28
    50
      src/modules/optional/transform.js
  55. 3
    5
      src/svg.js
  56. 11
    23
      src/types/ArrayPolyfill.js
  57. 48
    94
      src/types/Box.js
  58. 14
    36
      src/types/EventTarget.js
  59. 21
    41
      src/types/List.js
  60. 131
    227
      src/types/Matrix.js
  61. 67
    161
      src/types/Morphable.js
  62. 97
    217
      src/types/PathArray.js
  63. 9
    23
      src/types/Point.js
  64. 34
    68
      src/types/PointArray.js
  65. 16
    32
      src/types/SVGArray.js
  66. 27
    63
      src/types/SVGNumber.js
  67. 51
    105
      src/utils/adopter.js
  68. 15
    35
      src/utils/methods.js
  69. 28
    66
      src/utils/utils.js
  70. 1
    3
      src/utils/window.js

+ 0
- 1
.eslintrc.yaml View File

@@ -7,4 +7,3 @@ rules:
flatTernaryExpressions: true
}]
padded-blocks: off
space-in-parens: [ error, always ]

+ 154
- 226
dist/svg.js View File

@@ -6,7 +6,7 @@
* @copyright Wout Fierens <wout@mick-wout.com>
* @license MIT
*
* BUILT: Sun Nov 25 2018 16:17:19 GMT+1300 (New Zealand Daylight Time)
* BUILT: Mon Nov 26 2018 00:15:11 GMT+1300 (New Zealand Daylight Time)
*/;
var SVG = (function () {
'use strict';
@@ -112,36 +112,6 @@ var SVG = (function () {
return _setPrototypeOf(o, p);
}

function isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;

try {
Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
return true;
} catch (e) {
return false;
}
}

function _construct(Parent, args, Class) {
if (isNativeReflectConstruct()) {
_construct = Reflect.construct;
} else {
_construct = function _construct(Parent, args, Class) {
var a = [null];
a.push.apply(a, args);
var Constructor = Function.bind.apply(Parent, a);
var instance = new Constructor();
if (Class) _setPrototypeOf(instance, Class.prototype);
return instance;
};
}

return _construct.apply(null, arguments);
}

function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
@@ -1107,112 +1077,117 @@ var SVG = (function () {
/*#__PURE__*/
function () {
function Color() {
var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var c = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
var d = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
var space = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 'rgb';

_classCallCheck(this, Color);

// If the user gave us an array, make the color from it
if (typeof a === 'number') {
// Allow for the case that we don't need d...
space = typeof d === 'string' ? d : space;
d = typeof d === 'string' ? undefined : d; // Assign the values straight to the color

Object.assign(this, {
_a: a,
_b: b,
_c: c,
_d: d,
space: space
});
} else if (a instanceof Array) {
this.space = b || 'rgb';
Object.assign(this, {
_a: a[0],
_b: a[1],
_c: a[2],
_d: a[3]
});
} else if (a instanceof Object) {
// Set the object up and assign its values directly
var values = getParameters(a);
Object.assign(this, values);
} else if (typeof a === 'string') {
if (isRgb.test(a)) {
var noWhitespace = a.replace(whitespace, '');

var _rgb$exec$slice$map = rgb.exec(noWhitespace).slice(1, 4).map(function (v) {
return parseInt(v);
}),
_rgb$exec$slice$map2 = _slicedToArray(_rgb$exec$slice$map, 3),
_a2 = _rgb$exec$slice$map2[0],
_b2 = _rgb$exec$slice$map2[1],
_c2 = _rgb$exec$slice$map2[2];
this.init.apply(this, arguments);
}

_createClass(Color, [{
key: "init",
value: function init() {
var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var b = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var c = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
var d = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
var space = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 'rgb';

// If the user gave us an array, make the color from it
if (typeof a === 'number') {
// Allow for the case that we don't need d...
space = typeof d === 'string' ? d : space;
d = typeof d === 'string' ? undefined : d; // Assign the values straight to the color

Object.assign(this, {
_a: _a2,
_b: _b2,
_c: _c2,
space: 'rgb'
_a: a,
_b: b,
_c: c,
_d: d,
space: space
});
} else if (isHex.test(a)) {
var hexParse = function hexParse(v) {
return parseInt(v, 16);
};

var _hex$exec$map = hex.exec(sixDigitHex(a)).map(hexParse),
_hex$exec$map2 = _slicedToArray(_hex$exec$map, 4),
_a3 = _hex$exec$map2[1],
_b3 = _hex$exec$map2[2],
_c3 = _hex$exec$map2[3];

} else if (a instanceof Array) {
this.space = b || 'rgb';
Object.assign(this, {
_a: _a3,
_b: _b3,
_c: _c3,
space: 'rgb'
_a: a[0],
_b: a[1],
_c: a[2],
_d: a[3]
});
} else throw Error("Unsupported string format, can't construct Color");
} // Now add the components as a convenience


var _a = this._a,
_b = this._b,
_c = this._c,
_d = this._d;
var components = this.space === 'rgb' ? {
r: _a,
g: _b,
b: _c
} : this.space === 'xyz' ? {
x: _a,
y: _b,
z: _c
} : this.space === 'hsl' ? {
h: _a,
s: _b,
l: _c
} : this.space === 'lab' ? {
l: _a,
a: _b,
b: _c
} : this.space === 'lch' ? {
l: _a,
c: _b,
h: _c
} : this.space === 'cmyk' ? {
c: _a,
y: _b,
m: _c,
k: _d
} : {};
Object.assign(this, components);
}
} else if (a instanceof Object) {
// Set the object up and assign its values directly
var values = getParameters(a);
Object.assign(this, values);
} else if (typeof a === 'string') {
if (isRgb.test(a)) {
var noWhitespace = a.replace(whitespace, '');

var _rgb$exec$slice$map = rgb.exec(noWhitespace).slice(1, 4).map(function (v) {
return parseInt(v);
}),
_rgb$exec$slice$map2 = _slicedToArray(_rgb$exec$slice$map, 3),
_a2 = _rgb$exec$slice$map2[0],
_b2 = _rgb$exec$slice$map2[1],
_c2 = _rgb$exec$slice$map2[2];

Object.assign(this, {
_a: _a2,
_b: _b2,
_c: _c2,
space: 'rgb'
});
} else if (isHex.test(a)) {
var hexParse = function hexParse(v) {
return parseInt(v, 16);
};

var _hex$exec$map = hex.exec(sixDigitHex(a)).map(hexParse),
_hex$exec$map2 = _slicedToArray(_hex$exec$map, 4),
_a3 = _hex$exec$map2[1],
_b3 = _hex$exec$map2[2],
_c3 = _hex$exec$map2[3];

Object.assign(this, {
_a: _a3,
_b: _b3,
_c: _c3,
space: 'rgb'
});
} else throw Error("Unsupported string format, can't construct Color");
} // Now add the components as a convenience

_createClass(Color, [{

var _a = this._a,
_b = this._b,
_c = this._c,
_d = this._d;
var components = this.space === 'rgb' ? {
r: _a,
g: _b,
b: _c
} : this.space === 'xyz' ? {
x: _a,
y: _b,
z: _c
} : this.space === 'hsl' ? {
h: _a,
s: _b,
l: _c
} : this.space === 'lab' ? {
l: _a,
a: _b,
b: _c
} : this.space === 'lch' ? {
l: _a,
c: _b,
h: _c
} : this.space === 'cmyk' ? {
c: _a,
m: _b,
y: _c,
k: _d
} : {};
Object.assign(this, components);
}
}, {
key: "opacity",
value: function opacity() {
var _opacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
@@ -1264,8 +1239,8 @@ var SVG = (function () {


var yL = (l + 16) / 116;
var xL = a / 500 + y;
var zL = y - _b4 / 200; // Get the xyz values
var xL = a / 500 + yL;
var zL = yL - _b4 / 200; // Get the xyz values

var ct = 16 / 116;
var mx = 0.008856;
@@ -1286,14 +1261,17 @@ var SVG = (function () {
var g = gU > bd ? 1.055 * pow(gU, 1 / 2.4) - 0.055 : 12.92 * gU;
var b = bU > bd ? 1.055 * pow(bU, 1 / 2.4) - 0.055 : 12.92 * bU; // Make and return the color

var color = new Color(r, g, b);
var color = new Color(255 * r, 255 * g, 255 * b);
return color;
} else if (this.space === 'hsl') {
// stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
// https://bgrins.github.io/TinyColor/docs/tinycolor.html
// Get the current hsl values
var _h = this.h,
s = this.s,
_l = this.l; // If we are grey, then just make the color directly
_l = this.l;
_h /= 360;
s /= 100;
_l /= 100; // If we are grey, then just make the color directly

if (s === 0) {
var _color2 = new Color(_l, _l, _l);
@@ -1305,11 +1283,11 @@ var SVG = (function () {
var q = _l < 0.5 ? _l * (1 + s) : _l + s - _l * s;
var p = 2 * _l - q; // Get the rgb values

var _r = hueToRgb(p, q, _h + 1 / 3);
var _r = 255 * hueToRgb(p, q, _h + 1 / 3);

var _g = hueToRgb(p, q, _h);
var _g = 255 * hueToRgb(p, q, _h);

var _b5 = hueToRgb(p, q, _h - 1 / 3); // Make a new color
var _b5 = 255 * hueToRgb(p, q, _h - 1 / 3); // Make a new color


var _color = new Color(_r, _g, _b5);
@@ -1318,26 +1296,16 @@ var SVG = (function () {
} else if (this.space === 'cmyk') {
// https://gist.github.com/felipesabino/5066336
// Get the normalised cmyk values
var _a = this._a,
_b = this._b,
_c = this._c,
_d = this._d;

var _map = [_a, _b, _c, _d].map(function (v) {
return v / 100;
}),
_map2 = _slicedToArray(_map, 4),
_c4 = _map2[0],
m = _map2[1],
_y = _map2[2],
k = _map2[3]; // Get the rgb values

var _c4 = this.c,
m = this.m,
_y = this.y,
k = this.k; // Get the rgb values

var _r2 = 1 - Math.min(1, _c4 * (1 - k) + k);
var _r2 = 255 * (1 - Math.min(1, _c4 * (1 - k) + k));

var _g2 = 1 - Math.min(1, m * (1 - k) + k);
var _g2 = 255 * (1 - Math.min(1, m * (1 - k) + k));

var _b6 = 1 - Math.min(1, _y * (1 - k) + k); // Form the color and return it
var _b6 = 255 * (1 - Math.min(1, _y * (1 - k) + k)); // Form the color and return it


var _color3 = new Color(_r2, _g2, _b6);
@@ -1373,13 +1341,13 @@ var SVG = (function () {
g255 = _this$rgb2._b,
b255 = _this$rgb2._c;

var _map3 = [r255, g255, b255].map(function (v) {
var _map = [r255, g255, b255].map(function (v) {
return v / 255;
}),
_map4 = _slicedToArray(_map3, 3),
r = _map4[0],
g = _map4[1],
b = _map4[2]; // Convert to the lab rgb space
_map2 = _slicedToArray(_map, 3),
r = _map2[0],
g = _map2[1],
b = _map2[2]; // Convert to the lab rgb space


var rL = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
@@ -1428,13 +1396,13 @@ var SVG = (function () {
_b = _this$rgb3._b,
_c = _this$rgb3._c;

var _map5 = [_a, _b, _c].map(function (v) {
var _map3 = [_a, _b, _c].map(function (v) {
return v / 255;
}),
_map6 = _slicedToArray(_map5, 3),
r = _map6[0],
g = _map6[1],
b = _map6[2]; // Find the maximum and minimum values to get the lightness
_map4 = _slicedToArray(_map3, 3),
r = _map4[0],
g = _map4[1],
b = _map4[2]; // Find the maximum and minimum values to get the lightness


var max = Math.max(r, g, b);
@@ -1447,7 +1415,7 @@ var SVG = (function () {
var s = isGrey ? 0 : l > 0.5 ? delta / (2 - max - min) : delta / (max + min);
var 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

var color = new Color(h, s, l, 'hsl');
var color = new Color(360 * h, 100 * s, 100 * l, 'hsl');
return color;
}
}, {
@@ -1459,54 +1427,24 @@ var SVG = (function () {
_b = _this$rgb4._b,
_c = _this$rgb4._c;

var _map7 = [_a, _b, _c].map(function (v) {
var _map5 = [_a, _b, _c].map(function (v) {
return v / 255;
}),
_map8 = _slicedToArray(_map7, 3),
r = _map8[0],
g = _map8[1],
b = _map8[2]; // Get the cmyk values in an unbounded format
_map6 = _slicedToArray(_map5, 3),
r = _map6[0],
g = _map6[1],
b = _map6[2]; // Get the cmyk values in an unbounded format


var k = 100 * Math.min(1 - r, 1 - g, 1 - b);
var c = 100 * (1 - r - k) / (1 - k);
var m = 100 * (1 - g - k) / (1 - k);
var y = 100 * (1 - b - k) / (1 - k); // Construct the new color
var k = Math.min(1 - r, 1 - g, 1 - b);
var c = (1 - r - k) / (1 - k);
var m = (1 - g - k) / (1 - k);
var y = (1 - b - k) / (1 - k); // Construct the new color

var color = new Color(c, m, y, k, 'cmyk');
return color;
}
/*
Modifying the color
*/

}, {
key: "brighten",
value: function brighten() {
}
}, {
key: "darken",
value: function darken() {
}
/*
Mixing methods
*/

}, {
key: "to",
value: function to(otherColor, space) {
// Force both colors to the color of this space (or let the user decide)
space = space || this.space; // Get the starting and ending colors
// let start = this[ space ]()
// let end = otherColor[ space ]()
// Return a function that blends between the two colors

return function (t) {};
}
}, {
key: "avearge",
value: function avearge(otherColor, space) {}
/*
Input and Output methods
*/

@@ -1518,11 +1456,11 @@ var SVG = (function () {
_b = _this$rgb5._b,
_c = _this$rgb5._c;

var _map9 = [_a, _b, _c].map(componentHex),
_map10 = _slicedToArray(_map9, 3),
r = _map10[0],
g = _map10[1],
b = _map10[2];
var _map7 = [_a, _b, _c].map(componentHex),
_map8 = _slicedToArray(_map7, 3),
r = _map8[0],
g = _map8[1],
b = _map8[2];

return "#".concat(r).concat(g).concat(b);
}
@@ -1547,11 +1485,11 @@ var SVG = (function () {
return max(0, min(round(v), 255));
};

var _map11 = [r, g, b].map(format),
_map12 = _slicedToArray(_map11, 3),
rV = _map12[0],
gV = _map12[1],
bV = _map12[2];
var _map9 = [r, g, b].map(format),
_map10 = _slicedToArray(_map9, 3),
rV = _map10[0],
gV = _map10[1],
bV = _map10[2];

var string = "rgb(".concat(rV, ",").concat(gV, ",").concat(bV, ")");
return string;
@@ -1566,18 +1504,11 @@ var SVG = (function () {
space = this.space;
return [_a, _b, _c, _d, space];
}
}], [{
key: "fromArray",
value: function fromArray(array) {
var newColor = _construct(Color, _toConsumableArray(array));

return newColor;
}
/*
Generating random colors
*/

}, {
}], [{
key: "random",
value: function random() {
'sine';
@@ -1591,10 +1522,7 @@ var SVG = (function () {
/*
Constructing colors
*/

}, {
key: "temperature",
value: function temperature(kelvin) {} // Test if given value is a color string
// Test if given value is a color string

}, {
key: "test",

+ 22
- 48
src/animation/Animator.js View File

@@ -8,106 +8,80 @@ const Animator = {
timer: globals.window.performance || globals.window.Date,
transforms: [],

frame ( fn ) {

frame (fn) {
// Store the node
var node = Animator.frames.push( { run: fn } )
var node = Animator.frames.push({ run: fn })

// Request an animation frame if we don't have one
if ( Animator.nextDraw === null ) {

Animator.nextDraw = globals.window.requestAnimationFrame( Animator._draw )

if (Animator.nextDraw === null) {
Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw)
}

// Return the node so we can remove it easily
return node

},

transform_frame ( fn, id ) {

transform_frame (fn, id) {
Animator.transforms[id] = fn

},

timeout ( fn, delay ) {

timeout (fn, delay) {
delay = delay || 0

// Work out when the event should fire
var time = Animator.timer.now() + delay

// Add the timeout to the end of the queue
var node = Animator.timeouts.push( { run: fn, time: time } )
var node = Animator.timeouts.push({ run: fn, time: time })

// Request another animation frame if we need one
if ( Animator.nextDraw === null ) {

Animator.nextDraw = globals.window.requestAnimationFrame( Animator._draw )

if (Animator.nextDraw === null) {
Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw)
}

return node

},

cancelFrame ( node ) {

Animator.frames.remove( node )

cancelFrame (node) {
Animator.frames.remove(node)
},

clearTimeout ( node ) {

Animator.timeouts.remove( node )

clearTimeout (node) {
Animator.timeouts.remove(node)
},

_draw ( now ) {

_draw (now) {
// Run all the timeouts we can run, if they are not ready yet, add them
// to the end of the queue immediately! (bad timeouts!!! [sarcasm])
var nextTimeout = null
var lastTimeout = Animator.timeouts.last()
while ( ( nextTimeout = Animator.timeouts.shift() ) ) {

while ((nextTimeout = Animator.timeouts.shift())) {
// Run the timeout if its time, or push it to the end
if ( now >= nextTimeout.time ) {

if (now >= nextTimeout.time) {
nextTimeout.run()

} else {

Animator.timeouts.push( nextTimeout )

Animator.timeouts.push(nextTimeout)
}

// If we hit the last item, we should stop shifting out more items
if ( nextTimeout === lastTimeout ) break

if (nextTimeout === lastTimeout) break
}

// Run all of the animation frames
var nextFrame = null
var lastFrame = Animator.frames.last()
while ( ( nextFrame !== lastFrame ) && ( nextFrame = Animator.frames.shift() ) ) {

while ((nextFrame !== lastFrame) && (nextFrame = Animator.frames.shift())) {
nextFrame.run()

}

Animator.transforms.forEach( function ( el ) {

Animator.transforms.forEach(function (el) {
el()

} )
})

// If we have remaining timeouts or frames, draw until we don't anymore
Animator.nextDraw = Animator.timeouts.first() || Animator.frames.first()
? globals.window.requestAnimationFrame( Animator._draw )
? globals.window.requestAnimationFrame(Animator._draw)
: null

}
}


+ 77
- 163
src/animation/Controller.js View File

@@ -7,149 +7,95 @@ Base Class
The base stepper class that will be
***/

function makeSetterGetter ( k, f ) {

return function ( v ) {

if ( v == null ) return this[v]
function makeSetterGetter (k, f) {
return function (v) {
if (v == null) return this[v]
this[k] = v
if ( f ) f.call( this )
if (f) f.call(this)
return this

}

}

export let easing = {
'-': function ( pos ) {

'-': function (pos) {
return pos

},
'<>': function ( pos ) {

return -Math.cos( pos * Math.PI ) / 2 + 0.5

'<>': function (pos) {
return -Math.cos(pos * Math.PI) / 2 + 0.5
},
'>': function ( pos ) {

return Math.sin( pos * Math.PI / 2 )

'>': function (pos) {
return Math.sin(pos * Math.PI / 2)
},
'<': function ( pos ) {

return -Math.cos( pos * Math.PI / 2 ) + 1

'<': function (pos) {
return -Math.cos(pos * Math.PI / 2) + 1
},
bezier: function ( x1, y1, x2, y2 ) {

bezier: function (x1, y1, x2, y2) {
// see https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo
return function ( t ) {

if ( t < 0 ) {

if ( x1 > 0 ) {

return function (t) {
if (t < 0) {
if (x1 > 0) {
return y1 / x1 * t

} else if ( x2 > 0 ) {

} else if (x2 > 0) {
return y2 / x2 * t

} else {

return 0

}

} else if ( t > 1 ) {

if ( x2 < 1 ) {

return ( 1 - y2 ) / ( 1 - x2 ) * t + ( y2 - x2 ) / ( 1 - x2 )

} else if ( x1 < 1 ) {

return ( 1 - y1 ) / ( 1 - x1 ) * t + ( y1 - x1 ) / ( 1 - x1 )

} else if (t > 1) {
if (x2 < 1) {
return (1 - y2) / (1 - x2) * t + (y2 - x2) / (1 - x2)
} else if (x1 < 1) {
return (1 - y1) / (1 - x1) * t + (y1 - x1) / (1 - x1)
} else {

return 1

}

} else {

return 3 * t * ( 1 - t ) ** 2 * y1 + 3 * t ** 2 * ( 1 - t ) * y2 + t ** 3

return 3 * t * (1 - t) ** 2 * y1 + 3 * t ** 2 * (1 - t) * y2 + t ** 3
}

}

},
// see https://www.w3.org/TR/css-easing-1/#step-timing-function-algo
steps: function ( steps, stepPosition = 'end' ) {

steps: function (steps, stepPosition = 'end') {
// deal with "jump-" prefix
stepPosition = stepPosition.split( '-' ).reverse()[0]
stepPosition = stepPosition.split('-').reverse()[0]

let jumps = steps
if ( stepPosition === 'none' ) {

if (stepPosition === 'none') {
--jumps

} else if ( stepPosition === 'both' ) {

} else if (stepPosition === 'both') {
++jumps

}

// The beforeFlag is essentially useless
return ( t, beforeFlag = false ) => {

return (t, beforeFlag = false) => {
// Step is called currentStep in referenced url
let step = Math.floor( t * steps )
const jumping = ( t * step ) % 1 === 0

if ( stepPosition === 'start' || stepPosition === 'both' ) {
let step = Math.floor(t * steps)
const jumping = (t * step) % 1 === 0

if (stepPosition === 'start' || stepPosition === 'both') {
++step

}

if ( beforeFlag && jumping ) {

if (beforeFlag && jumping) {
--step

}

if ( t >= 0 && step < 0 ) {

if (t >= 0 && step < 0) {
step = 0

}

if ( t <= 1 && step > jumps ) {

if (t <= 1 && step > jumps) {
step = jumps

}

return step / jumps

}

}
}

export class Stepper {

done () {

return false

}

}

/***
@@ -158,25 +104,17 @@ Easing Functions
***/

export class Ease extends Stepper {

constructor ( fn ) {

constructor (fn) {
super()
this.ease = easing[fn || timeline.ease] || fn

}

step ( from, to, pos ) {

if ( typeof from !== 'number' ) {

step (from, to, pos) {
if (typeof from !== 'number') {
return pos < 1 ? from : to

}
return from + ( to - from ) * this.ease( pos )

return from + (to - from) * this.ease(pos)
}

}

/***
@@ -185,65 +123,51 @@ Controller Types
***/

export class Controller extends Stepper {

constructor ( fn ) {

constructor (fn) {
super()
this.stepper = fn

}

step ( current, target, dt, c ) {

return this.stepper( current, target, dt, c )

step (current, target, dt, c) {
return this.stepper(current, target, dt, c)
}

done ( c ) {

done (c) {
return c.done

}

}

function recalculate () {

// Apply the default parameters
var duration = ( this._duration || 500 ) / 1000
var duration = (this._duration || 500) / 1000
var overshoot = this._overshoot || 0

// Calculate the PID natural response
var eps = 1e-10
var pi = Math.PI
var os = Math.log( overshoot / 100 + eps )
var zeta = -os / Math.sqrt( pi * pi + os * os )
var wn = 3.9 / ( zeta * duration )
var os = Math.log(overshoot / 100 + eps)
var zeta = -os / Math.sqrt(pi * pi + os * os)
var wn = 3.9 / (zeta * duration)

// Calculate the Spring values
this.d = 2 * zeta * wn
this.k = wn * wn

}

export class Spring extends Controller {

constructor ( duration, overshoot ) {

constructor (duration, overshoot) {
super()
this.duration( duration || 500 )
.overshoot( overshoot || 0 )

this.duration(duration || 500)
.overshoot(overshoot || 0)
}

step ( current, target, dt, c ) {

if ( typeof current === 'string' ) return current
step (current, target, dt, c) {
if (typeof current === 'string') return current
c.done = dt === Infinity
if ( dt === Infinity ) return target
if ( dt === 0 ) return current
if (dt === Infinity) return target
if (dt === 0) return current

if ( dt > 100 ) dt = 16
if (dt > 100) dt = 16

dt /= 1000

@@ -251,7 +175,7 @@ export class Spring extends Controller {
var velocity = c.velocity || 0

// Apply the control to get the new position and store it
var acceleration = -this.d * velocity - this.k * ( current - target )
var acceleration = -this.d * velocity - this.k * (current - target)
var newPosition = current
+ velocity * dt
+ acceleration * dt * dt / 2
@@ -260,66 +184,56 @@ export class Spring extends Controller {
c.velocity = velocity + acceleration * dt

// Figure out if we have converged, and if so, pass the value
c.done = Math.abs( target - newPosition ) + Math.abs( velocity ) < 0.002
c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002
return c.done ? target : newPosition

}

}

extend( Spring, {
duration: makeSetterGetter( '_duration', recalculate ),
overshoot: makeSetterGetter( '_overshoot', recalculate )
} )
extend(Spring, {
duration: makeSetterGetter('_duration', recalculate),
overshoot: makeSetterGetter('_overshoot', recalculate)
})

export class PID extends Controller {

constructor ( p, i, d, windup ) {

constructor (p, i, d, windup) {
super()

p = p == null ? 0.1 : p
i = i == null ? 0.01 : i
d = d == null ? 0 : d
windup = windup == null ? 1000 : windup
this.p( p ).i( i ).d( d ).windup( windup )

this.p(p).i(i).d(d).windup(windup)
}

step ( current, target, dt, c ) {

if ( typeof current === 'string' ) return current
step (current, target, dt, c) {
if (typeof current === 'string') return current
c.done = dt === Infinity

if ( dt === Infinity ) return target
if ( dt === 0 ) return current
if (dt === Infinity) return target
if (dt === 0) return current

var p = target - current
var i = ( c.integral || 0 ) + p * dt
var d = ( p - ( c.error || 0 ) ) / dt
var i = (c.integral || 0) + p * dt
var d = (p - (c.error || 0)) / dt
var windup = this.windup

// antiwindup
if ( windup !== false ) {

i = Math.max( -windup, Math.min( i, windup ) )

if (windup !== false) {
i = Math.max(-windup, Math.min(i, windup))
}

c.error = p
c.integral = i

c.done = Math.abs( p ) < 0.001

return c.done ? target : current + ( this.P * p + this.I * i + this.D * d )
c.done = Math.abs(p) < 0.001

return c.done ? target : current + (this.P * p + this.I * i + this.D * d)
}

}

extend( PID, {
windup: makeSetterGetter( 'windup' ),
p: makeSetterGetter( 'P' ),
i: makeSetterGetter( 'I' ),
d: makeSetterGetter( 'D' )
} )
extend(PID, {
windup: makeSetterGetter('windup'),
p: makeSetterGetter('P'),
i: makeSetterGetter('I'),
d: makeSetterGetter('D')
})

+ 9
- 27
src/animation/Queue.js View File

@@ -1,77 +1,59 @@
export default class Queue {

constructor () {

this._first = null
this._last = null

}

push ( value ) {

push (value) {
// An item stores an id and the provided value
var item = value.next ? value : { value: value, next: null, prev: null }

// Deal with the queue being empty or populated
if ( this._last ) {

if (this._last) {
item.prev = this._last
this._last.next = item
this._last = item

} else {

this._last = item
this._first = item

}

// Update the length and return the current item
return item

}

shift () {

// Check if we have a value
var remove = this._first
if ( !remove ) return null
if (!remove) return null

// If we do, remove it and relink things
this._first = remove.next
if ( this._first ) this._first.prev = null
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 ) {

remove (item) {
// Relink the previous item
if ( item.prev ) item.prev.next = item.next
if ( item.next ) item.next.prev = item.prev
if ( item === this._last ) this._last = item.prev
if ( item === this._first ) this._first = item.next
if (item.prev) item.prev.next = item.next
if (item.next) item.next.prev = item.prev
if (item === this._last) this._last = item.prev
if (item === this._first) this._first = item.next

// Invalidate item
item.prev = null
item.next = null

}

}

+ 293
- 545
src/animation/Runner.js
File diff suppressed because it is too large
View File


+ 58
- 136
src/animation/Timeline.js View File

@@ -3,27 +3,21 @@ import { registerMethods } from '../utils/methods.js'
import Animator from './Animator.js'
import EventTarget from '../types/EventTarget.js'

var makeSchedule = function ( runnerInfo ) {

var makeSchedule = function (runnerInfo) {
var start = runnerInfo.start
var duration = runnerInfo.runner.duration()
var end = start + duration
return { start: start, duration: duration, end: end, runner: runnerInfo.runner }

}

export default class Timeline extends EventTarget {

// Construct a new timeline on the given element
constructor () {

super()

this._timeSource = function () {

let w = globals.window
return ( w.performance || w.Date ).now()

return (w.performance || w.Date).now()
}

// Store the timing variables
@@ -42,7 +36,6 @@ export default class Timeline extends EventTarget {
this._time = 0
this._lastSourceTime = 0
this._lastStepTime = 0

}

/**
@@ -50,28 +43,19 @@ export default class Timeline extends EventTarget {
*/

// schedules a runner on the timeline
schedule ( runner, delay, when ) {

schedule (runner, delay, when) {
// FIXME: how to sort? maybe by runner id?
if ( runner == null ) {

return this._runners.map( makeSchedule ).sort( function ( a, b ) {

return ( a.start - b.start ) || ( a.duration - b.duration )

} )

if (runner == null) {
return this._runners.map(makeSchedule).sort(function (a, b) {
return (a.start - b.start) || (a.duration - b.duration)
})
}

if ( !this.active() ) {

if (!this.active()) {
this._step()
if ( when == null ) {

if (when == null) {
when = 'now'

}

}

// The start time for the next animation can either be given explicitly,
@@ -81,40 +65,28 @@ export default class Timeline extends EventTarget {
delay = delay || 0

// Work out when to start the animation
if ( when == null || when === 'last' || when === 'after' ) {

if (when == null || when === 'last' || when === 'after') {
// Take the last time and increment
absoluteStartTime = this._startTime

} else if ( when === 'absolute' || when === 'start' ) {

} else if (when === 'absolute' || when === 'start') {
absoluteStartTime = delay
delay = 0

} else if ( when === 'now' ) {

} else if (when === 'now') {
absoluteStartTime = this._time

} else if ( when === 'relative' ) {

} else if (when === 'relative') {
let runnerInfo = this._runners[runner.id]
if ( runnerInfo ) {

if (runnerInfo) {
absoluteStartTime = runnerInfo.start + delay
delay = 0

}

} else {

throw new Error( 'Invalid value for the "when" parameter' )

throw new Error('Invalid value for the "when" parameter')
}

// Manage runner
runner.unschedule()
runner.timeline( this )
runner.time( -delay )
runner.timeline(this)
runner.time(-delay)

// Save startTime for next runner
this._startTime = absoluteStartTime + runner.duration() + delay
@@ -127,115 +99,91 @@ export default class Timeline extends EventTarget {
}

// Save order and continue
this._order.push( runner.id )
this._order.push(runner.id)
this._continue()
return this

}

// Remove the runner from this timeline
unschedule ( runner ) {

var index = this._order.indexOf( runner.id )
if ( index < 0 ) return this
unschedule (runner) {
var index = this._order.indexOf(runner.id)
if (index < 0) return this

delete this._runners[runner.id]
this._order.splice( index, 1 )
runner.timeline( null )
this._order.splice(index, 1)
runner.timeline(null)
return this

}

play () {

// Now make sure we are not paused and continue the animation
this._paused = false
return this._continue()

}

pause () {

// Cancel the next animation frame and pause
this._nextFrame = null
this._paused = true
return this

}

stop () {

// Cancel the next animation frame and go to start
this.seek( -this._time )
this.seek(-this._time)
return this.pause()

}

finish () {

this.seek( Infinity )
this.seek(Infinity)
return this.pause()

}

speed ( speed ) {

if ( speed == null ) return this._speed
speed (speed) {
if (speed == null) return this._speed
this._speed = speed
return this

}

reverse ( yes ) {

reverse (yes) {
var currentSpeed = this.speed()
if ( yes == null ) return this.speed( -currentSpeed )

var positive = Math.abs( currentSpeed )
return this.speed( yes ? positive : -positive )
if (yes == null) return this.speed(-currentSpeed)

var positive = Math.abs(currentSpeed)
return this.speed(yes ? positive : -positive)
}

seek ( dt ) {

seek (dt) {
this._time += dt
return this._continue()

}

time ( time ) {

if ( time == null ) return this._time
time (time) {
if (time == null) return this._time
this._time = time
return this

}

persist ( dtOrForever ) {

if ( dtOrForever == null ) return this._persist
persist (dtOrForever) {
if (dtOrForever == null) return this._persist
this._persist = dtOrForever
return this

}

source ( fn ) {

if ( fn == null ) return this._timeSource
source (fn) {
if (fn == null) return this._timeSource
this._timeSource = fn
return this

}

_step () {

// If the timeline is paused, just do nothing
if ( this._paused ) return
if (this._paused) return

// Get the time delta from the last time and update the time
var time = this._timeSource()
var dtSource = time - this._lastSourceTime
var dtTime = this._speed * dtSource + ( this._time - this._lastStepTime )
var dtTime = this._speed * dtSource + (this._time - this._lastStepTime)
this._lastSourceTime = time

// Update the time
@@ -245,8 +193,7 @@ export default class Timeline extends EventTarget {

// Run all of the runners directly
var runnersLeft = false
for ( var i = 0, len = this._order.length; i < len; i++ ) {

for (var i = 0, len = this._order.length; i < len; i++) {
// Get and run the current runner and ignore it if its inactive
var runnerInfo = this._runners[this._order[i]]
var runner = runnerInfo.runner
@@ -257,89 +204,64 @@ export default class Timeline extends EventTarget {
let dtToStart = this._time - runnerInfo.start

// Dont run runner if not started yet
if ( dtToStart < 0 ) {

if (dtToStart < 0) {
runnersLeft = true
continue

} else if ( dtToStart < dt ) {

} else if (dtToStart < dt) {
// Adjust dt to make sure that animation is on point
dt = dtToStart

}

if ( !runner.active() ) continue
if (!runner.active()) continue

// If this runner is still going, signal that we need another animation
// frame, otherwise, remove the completed runner
var finished = runner.step( dt ).done
if ( !finished ) {

var finished = runner.step(dt).done
if (!finished) {
runnersLeft = true
// continue

} else if ( runnerInfo.persist !== true ) {

} else if (runnerInfo.persist !== true) {
// runner is finished. And runner might get removed

var endTime = runner.duration() - runner.time() + this._time

if ( endTime + this._persist < this._time ) {

if (endTime + this._persist < this._time) {
// Delete runner and correct index
delete this._runners[this._order[i]]
this._order.splice( i--, 1 ) && --len
runner.timeline( null )

this._order.splice(i--, 1) && --len
runner.timeline(null)
}

}

}

// Get the next animation frame to keep the simulation going
if ( runnersLeft ) {

this._nextFrame = Animator.frame( this._step.bind( this ) )

if (runnersLeft) {
this._nextFrame = Animator.frame(this._step.bind(this))
} else {

this._nextFrame = null

}
return this

}

// Checks if we are running and continues the animation
_continue () {

if ( this._paused ) return this
if ( !this._nextFrame ) {

this._nextFrame = Animator.frame( this._step.bind( this ) )

if (this._paused) return this
if (!this._nextFrame) {
this._nextFrame = Animator.frame(this._step.bind(this))
}
return this

}

active () {

return !!this._nextFrame

}

}

registerMethods( {
registerMethods({
Element: {
timeline: function () {

this._timeline = ( this._timeline || new Timeline() )
this._timeline = (this._timeline || new Timeline())
return this._timeline

}
}
} )
})

+ 17
- 33
src/elements/A.js View File

@@ -4,58 +4,42 @@ import { xlink } from '../modules/core/namespaces.js'
import Container from './Container.js'

export default class A extends Container {

constructor ( node ) {

super( nodeOrNew( 'a', node ), node )

constructor (node) {
super(nodeOrNew('a', node), node)
}

// Link url
to ( url ) {

return this.attr( 'href', url, xlink )

to (url) {
return this.attr('href', url, xlink)
}

// Link target attribute
target ( target ) {

return this.attr( 'target', target )

target (target) {
return this.attr('target', target)
}

}

registerMethods( {
registerMethods({
Container: {
// Create a hyperlink element
link: wrapWithAttrCheck( function ( url ) {

return this.put( new A() ).to( url )

} )
link: wrapWithAttrCheck(function (url) {
return this.put(new A()).to(url)
})
},
Element: {
// Create a hyperlink element
linkTo: function ( url ) {

linkTo: function (url) {
var link = new A()

if ( typeof url === 'function' ) {

url.call( link, link )

if (typeof url === 'function') {
url.call(link, link)
} else {

link.to( url )

link.to(url)
}

return this.parent().put( link ).put( this )

return this.parent().put(link).put(this)
}
}
} )
})

register( A )
register(A)

+ 12
- 22
src/elements/Bare.js View File

@@ -4,38 +4,28 @@ import Container from './Container.js'
import { globals } from '../utils/window.js'

export default class Bare extends Container {

constructor ( node, attrs ) {

super( nodeOrNew( node, typeof node === 'string' ? null : node ), attrs )

constructor (node, attrs) {
super(nodeOrNew(node, typeof node === 'string' ? null : node), attrs)
}

words ( text ) {

words (text) {
// remove contents
while ( this.node.hasChildNodes() ) {

this.node.removeChild( this.node.lastChild )

while (this.node.hasChildNodes()) {
this.node.removeChild(this.node.lastChild)
}

// create text node
this.node.appendChild( globals.document.createTextNode( text ) )
this.node.appendChild(globals.document.createTextNode(text))

return this

}

}

register( Bare )
register(Bare)

registerMethods( 'Container', {
registerMethods('Container', {
// Create an element that is not described by SVG.js
element: wrapWithAttrCheck( function ( node ) {

return this.put( new Bare( node ) )

} )
} )
element: wrapWithAttrCheck(function (node) {
return this.put(new Bare(node))
})
})

+ 19
- 33
src/elements/Circle.js View File

@@ -10,54 +10,40 @@ import SVGNumber from '../types/SVGNumber.js'
import Shape from './Shape.js'

export default class Circle extends Shape {

constructor ( node ) {

super( nodeOrNew( 'circle', node ), node )

constructor (node) {
super(nodeOrNew('circle', node), node)
}

radius ( r ) {

return this.attr( 'r', r )

radius (r) {
return this.attr('r', r)
}

// Radius x value
rx ( rx ) {

return this.attr( 'r', rx )

rx (rx) {
return this.attr('r', rx)
}

// Alias radius x value
ry ( ry ) {

return this.rx( ry )

ry (ry) {
return this.rx(ry)
}

size ( size ) {

return this.radius( new SVGNumber( size ).divide( 2 ) )

size (size) {
return this.radius(new SVGNumber(size).divide(2))
}

}

extend( Circle, { x, y, cx, cy, width, height } )
extend(Circle, { x, y, cx, cy, width, height })

registerMethods( {
registerMethods({
Element: {
// Create circle element
circle: wrapWithAttrCheck( function ( size ) {

return this.put( new Circle() )
.size( size )
.move( 0, 0 )

} )
circle: wrapWithAttrCheck(function (size) {
return this.put(new Circle())
.size(size)
.move(0, 0)
})
}
} )
})

register( Circle )
register(Circle)

+ 16
- 34
src/elements/ClipPath.js View File

@@ -4,72 +4,54 @@ import Container from './Container.js'
import baseFind from '../modules/core/selector.js'

export default class ClipPath extends Container {

constructor ( node ) {

super( nodeOrNew( 'clipPath', node ), node )

constructor (node) {
super(nodeOrNew('clipPath', node), node)
}

// Unclip all clipped elements and remove itself
remove () {

// unclip all targets
this.targets().forEach( function ( el ) {

this.targets().forEach(function (el) {
el.unclip()

} )
})

// remove clipPath from parent
return super.remove()

}

targets () {

return baseFind( 'svg [clip-path*="' + this.id() + '"]' )

return baseFind('svg [clip-path*="' + this.id() + '"]')
}

}

registerMethods( {
registerMethods({
Container: {
// Create clipping element
clip: wrapWithAttrCheck( function () {

return this.defs().put( new ClipPath() )

} )
clip: wrapWithAttrCheck(function () {
return this.defs().put(new ClipPath())
})
},
Element: {
// Distribute clipPath to svg element
clipWith ( element ) {

clipWith (element) {
// use given clip or create a new one
let clipper = element instanceof ClipPath
? element
: this.parent().clip().add( element )
: this.parent().clip().add(element)

// apply mask
return this.attr( 'clip-path', 'url("#' + clipper.id() + '")' )

return this.attr('clip-path', 'url("#' + clipper.id() + '")')
},

// Unclip element
unclip () {

return this.attr( 'clip-path', null )

return this.attr('clip-path', null)
},

clipper () {

return this.reference( 'clip-path' )

return this.reference('clip-path')
}
}
} )
})

register( ClipPath )
register(ClipPath)

+ 10
- 20
src/elements/Container.js View File

@@ -2,39 +2,29 @@ import { register } from '../utils/adopter.js'
import Element from './Element.js'

export default class Container extends Element {

flatten ( parent ) {

this.each( function () {

if ( this instanceof Container ) return this.flatten( parent ).ungroup( parent )
return this.toParent( parent )

} )
flatten (parent) {
this.each(function () {
if (this instanceof Container) return this.flatten(parent).ungroup(parent)
return this.toParent(parent)
})

// we need this so that the root does not get removed
this.node.firstElementChild || this.remove()

return this

}

ungroup ( parent ) {

ungroup (parent) {
parent = parent || this.parent()

this.each( function () {

return this.toParent( parent )

} )
this.each(function () {
return this.toParent(parent)
})

this.remove()

return this

}

}

register( Container )
register(Container)

+ 3
- 11
src/elements/Defs.js View File

@@ -2,24 +2,16 @@ import { nodeOrNew, register } from '../utils/adopter.js'
import Container from './Container.js'

export default class Defs extends Container {

constructor ( node ) {

super( nodeOrNew( 'defs', node ), node )

constructor (node) {
super(nodeOrNew('defs', node), node)
}

flatten () {

return this

}
ungroup () {

return this

}

}

register( Defs )
register(Defs)

+ 85
- 181
src/elements/Dom.js View File

@@ -15,294 +15,217 @@ import List from '../types/List.js'
import attr from '../modules/core/attr.js'

export default class Dom extends EventTarget {

constructor ( node, attrs ) {

super( node )
constructor (node, attrs) {
super(node)
this.node = node
this.type = node.nodeName

if ( attrs && node !== attrs ) {

this.attr( attrs )

if (attrs && node !== attrs) {
this.attr(attrs)
}

}

// Add given element at a position
add ( element, i ) {

element = makeInstance( element )

if ( i == null ) {

this.node.appendChild( element.node )

} else if ( element.node !== this.node.childNodes[i] ) {

this.node.insertBefore( element.node, this.node.childNodes[i] )
add (element, i) {
element = makeInstance(element)

if (i == null) {
this.node.appendChild(element.node)
} else if (element.node !== this.node.childNodes[i]) {
this.node.insertBefore(element.node, this.node.childNodes[i])
}

return this

}

// Add element to given container and return self
addTo ( parent ) {

return makeInstance( parent ).put( this )

addTo (parent) {
return makeInstance(parent).put(this)
}

// Returns all child elements
children () {

return new List( map( this.node.children, function ( node ) {

return adopt( node )

} ) )

return new List(map(this.node.children, function (node) {
return adopt(node)
}))
}

// Remove all elements in this container
clear () {

// remove children
while ( this.node.hasChildNodes() ) {

this.node.removeChild( this.node.lastChild )

while (this.node.hasChildNodes()) {
this.node.removeChild(this.node.lastChild)
}

// remove defs reference
delete this._defs

return this

}

// Clone element
clone () {

// write dom data to the dom so the clone can pickup the data
this.writeDataToDom()

// clone element and assign new id
return assignNewId( this.node.cloneNode( true ) )

return assignNewId(this.node.cloneNode(true))
}

// Iterates over all children and invokes a given block
each ( block, deep ) {

each (block, deep) {
var children = this.children()
var i, il

for ( i = 0, il = children.length; i < il; i++ ) {

block.apply( children[i], [ i, children ] )

if ( deep ) {

children[i].each( block, deep )
for (i = 0, il = children.length; i < il; i++) {
block.apply(children[i], [ i, children ])

if (deep) {
children[i].each(block, deep)
}

}

return this

}

// Get first child
first () {

return adopt( this.node.firstChild )

return adopt(this.node.firstChild)
}

// Get a element at the given index
get ( i ) {

return adopt( this.node.childNodes[i] )

get (i) {
return adopt(this.node.childNodes[i])
}

getEventHolder () {

return this.node

}

getEventTarget () {

return this.node

}

// Checks if the given element is a child
has ( element ) {

return this.index( element ) >= 0

has (element) {
return this.index(element) >= 0
}

// Get / set id
id ( id ) {

id (id) {
// generate new id if no id set
if ( typeof id === 'undefined' && !this.node.id ) {

this.node.id = eid( this.type )

if (typeof id === 'undefined' && !this.node.id) {
this.node.id = eid(this.type)
}

// dont't set directly width this.node.id to make `null` work correctly
return this.attr( 'id', id )

return this.attr('id', id)
}

// Gets index of given element
index ( element ) {

return [].slice.call( this.node.childNodes ).indexOf( element.node )

index (element) {
return [].slice.call(this.node.childNodes).indexOf(element.node)
}

// Get the last child
last () {

return adopt( this.node.lastChild )

return adopt(this.node.lastChild)
}

// matches the element vs a css selector
matches ( selector ) {

matches (selector) {
const el = this.node
return ( el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector ).call( el, selector )

return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector)
}

// Returns the parent element instance
parent ( type ) {

parent (type) {
var parent = this

// check for parent
if ( !parent.node.parentNode ) return null
if (!parent.node.parentNode) return null

// get parent element
parent = adopt( parent.node.parentNode )
parent = adopt(parent.node.parentNode)

if ( !type ) return parent
if (!type) return parent

// loop trough ancestors if type is given
while ( parent && parent.node instanceof globals.window.SVGElement ) { // FIXME: That shouldnt be neccessary

if ( typeof type === 'string' ? parent.matches( type ) : parent instanceof type ) return parent
parent = adopt( parent.node.parentNode )

while (parent && parent.node instanceof globals.window.SVGElement) { // FIXME: That shouldnt be neccessary
if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent
parent = adopt(parent.node.parentNode)
}

}

// Basically does the same as `add()` but returns the added element instead
put ( element, i ) {

this.add( element, i )
put (element, i) {
this.add(element, i)
return element

}

// Add element to given container and return container
putIn ( parent ) {

return makeInstance( parent ).add( this )

putIn (parent) {
return makeInstance(parent).add(this)
}

// Remove element
remove () {

if ( this.parent() ) {

this.parent().removeElement( this )

if (this.parent()) {
this.parent().removeElement(this)
}

return this

}

// Remove a given child
removeElement ( element ) {

this.node.removeChild( element.node )
removeElement (element) {
this.node.removeChild(element.node)

return this

}

// Replace this with element
replace ( element ) {

element = makeInstance( element )
this.node.parentNode.replaceChild( element.node, this.node )
replace (element) {
element = makeInstance(element)
this.node.parentNode.replaceChild(element.node, this.node)
return element

}

round ( precision = 2, map ) {

round (precision = 2, map) {
const factor = 10 ** precision
const attrs = this.attr()

// If we have no map, build one from attrs
if ( !map ) {

map = Object.keys( attrs )

if (!map) {
map = Object.keys(attrs)
}

// Holds rounded attributes
const newAttrs = {}
map.forEach( ( key ) => {
newAttrs[key] = Math.round( attrs[key] * factor ) / factor
map.forEach((key) => {
newAttrs[key] = Math.round(attrs[key] * factor) / factor
})

} )

this.attr( newAttrs )
this.attr(newAttrs)
return this

}

// Return id on string conversion
toString () {

return this.id()

}

// Import raw svg
svg ( svgOrFn, outerHTML ) {

svg (svgOrFn, outerHTML) {
var well, len, fragment

if ( svgOrFn === false ) {

if (svgOrFn === false) {
outerHTML = false
svgOrFn = null

}

// act as getter if no svg string is given
if ( svgOrFn == null || typeof svgOrFn === 'function' ) {

if (svgOrFn == null || typeof svgOrFn === 'function') {
// The default for exports is, that the outerNode is included
outerHTML = outerHTML == null ? true : outerHTML

@@ -311,49 +234,38 @@ export default class Dom extends EventTarget {
let current = this

// An export modifier was passed
if ( svgOrFn != null ) {

current = adopt( current.node.cloneNode( true ) )
if (svgOrFn != null) {
current = adopt(current.node.cloneNode(true))

// If the user wants outerHTML we need to process this node, too
if ( outerHTML ) {

let result = svgOrFn( current )
if (outerHTML) {
let result = svgOrFn(current)
current = result || current

// The user does not want this node? Well, then he gets nothing
if ( result === false ) return ''

if (result === false) return ''
}

// Deep loop through all children and apply modifier
current.each( function () {

let result = svgOrFn( this )
current.each(function () {
let result = svgOrFn(this)
let _this = result || this

// If modifier returns false, discard node
if ( result === false ) {

if (result === false) {
this.remove()

// If modifier returns new node, use it

} else if ( result && this !== _this ) {

this.replace( _this )

} else if (result && this !== _this) {
this.replace(_this)
}

}, true )

}, true)
}

// Return outer or inner content
return outerHTML
? current.node.outerHTML
: current.node.innerHTML

}

// Act as setter if we got a string
@@ -362,41 +274,33 @@ export default class Dom extends EventTarget {
outerHTML = outerHTML == null ? false : outerHTML

// Create temporary holder
well = globals.document.createElementNS( ns, 'svg' )
well = globals.document.createElementNS(ns, 'svg')
fragment = globals.document.createDocumentFragment()

// Dump raw svg
well.innerHTML = svgOrFn

// Transplant nodes into the fragment
for ( len = well.children.length; len--; ) {

fragment.appendChild( well.firstElementChild )

for (len = well.children.length; len--;) {
fragment.appendChild(well.firstElementChild)
}

// Add the whole fragment at once
return outerHTML
? this.replace( fragment )
: this.add( fragment )

? this.replace(fragment)
: this.add(fragment)
}

// write svgjs data to the dom
writeDataToDom () {

// dump variables recursively
this.each( function () {

this.each(function () {
this.writeDataToDom()

} )
})

return this

}

}

extend( Dom, { attr, find } )
register( Dom )
extend(Dom, { attr, find })
register(Dom)

+ 43
- 87
src/elements/Element.js View File

@@ -15,13 +15,11 @@ import Dom from './Dom.js'
import List from '../types/List.js'
import SVGNumber from '../types/SVGNumber.js'

const Svg = getClass( root )
const Svg = getClass(root)

export default class Element extends Dom {

constructor ( node, attrs ) {

super( node, attrs )
constructor (node, attrs) {
super(node, attrs)

// initialize data object
this.dom = {}
@@ -29,177 +27,135 @@ export default class Element extends Dom {
// create circular reference
this.node.instance = this

if ( node.hasAttribute( 'svgjs:data' ) ) {

if (node.hasAttribute('svgjs:data')) {
// pull svgjs data from the dom (getAttributeNS doesn't work in html5)
this.setData( JSON.parse( node.getAttribute( 'svgjs:data' ) ) || {} )

this.setData(JSON.parse(node.getAttribute('svgjs:data')) || {})
}

}

// Move element by its center
center ( x, y ) {

return this.cx( x ).cy( y )

center (x, y) {
return this.cx(x).cy(y)
}

// Move by center over x-axis
cx ( x ) {

return x == null ? this.x() + this.width() / 2 : this.x( x - this.width() / 2 )

cx (x) {
return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2)
}

// Move by center over y-axis
cy ( y ) {

cy (y) {
return y == null
? this.y() + this.height() / 2
: this.y( y - this.height() / 2 )

: this.y(y - this.height() / 2)
}

// Get defs
defs () {

return this.root().defs()

}

// Get parent document
root () {

let p = this.parent( Svg )
let p = this.parent(Svg)
return p && p.root()

}

getEventHolder () {

return this

}

// Set height of element
height ( height ) {

return this.attr( 'height', height )

height (height) {
return this.attr('height', height)
}

// Checks whether the given point inside the bounding box of the element
inside ( x, y ) {

inside (x, y) {
let box = this.bbox()

return x > box.x
&& y > box.y
&& x < box.x + box.width
&& y < box.y + box.height

}

// Move element to given x and y values
move ( x, y ) {

return this.x( x ).y( y )

move (x, y) {
return this.x(x).y(y)
}

// return array of all ancestors of given type up to the root svg
parents ( until = globals.document ) {

until = makeInstance( until )
parents (until = globals.document) {
until = makeInstance(until)
let parents = new List()
let parent = this

while (
( parent = parent.parent() )
(parent = parent.parent())
&& parent.node !== until.node
&& parent.node !== globals.document
) {

parents.push( parent )

parents.push(parent)
}

return parents

}

// Get referenced element form attribute value
reference ( attr ) {

attr = this.attr( attr )
if ( !attr ) return null

const m = attr.match( reference )
return m ? makeInstance( m[1] ) : null
reference (attr) {
attr = this.attr(attr)
if (!attr) return null

const m = attr.match(reference)
return m ? makeInstance(m[1]) : null
}

// set given data to the elements data property
setData ( o ) {

setData (o) {
this.dom = o
return this

}

// Set element size to given width and height
size ( width, height ) {

let p = proportionalSize( this, width, height )
size (width, height) {
let p = proportionalSize(this, width, height)

return this
.width( new SVGNumber( p.width ) )
.height( new SVGNumber( p.height ) )

.width(new SVGNumber(p.width))
.height(new SVGNumber(p.height))
}

// Set width of element
width ( width ) {

return this.attr( 'width', width )

width (width) {
return this.attr('width', width)
}

// write svgjs data to the dom
writeDataToDom () {

// remove previously set data
this.node.removeAttribute( 'svgjs:data' )

if ( Object.keys( this.dom ).length ) {

this.node.setAttribute( 'svgjs:data', JSON.stringify( this.dom ) ) // see #428
this.node.removeAttribute('svgjs:data')

if (Object.keys(this.dom).length) {
this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)) // see #428
}

return super.writeDataToDom()

}

// Move over x-axis
x ( x ) {

return this.attr( 'x', x )

x (x) {
return this.attr('x', x)
}

// Move over y-axis
y ( y ) {

return this.attr( 'y', y )

y (y) {
return this.attr('y', y)
}

}

extend( Element, {
extend(Element, {
bbox, rbox, point, ctm, screenCTM
} )
})

register( Element )
register(Element)

+ 13
- 21
src/elements/Ellipse.js View File

@@ -11,34 +11,26 @@ import Shape from './Shape.js'
import * as circled from '../modules/core/circled.js'

export default class Ellipse extends Shape {

constructor ( node ) {

super( nodeOrNew( 'ellipse', node ), node )

constructor (node) {
super(nodeOrNew('ellipse', node), node)
}

size ( width, height ) {

var p = proportionalSize( this, width, height )
size (width, height) {
var p = proportionalSize(this, width, height)

return this
.rx( new SVGNumber( p.width ).divide( 2 ) )
.ry( new SVGNumber( p.height ).divide( 2 ) )

.rx(new SVGNumber(p.width).divide(2))
.ry(new SVGNumber(p.height).divide(2))
}

}

extend( Ellipse, circled )
extend(Ellipse, circled)

registerMethods( 'Container', {
registerMethods('Container', {
// Create an ellipse
ellipse: wrapWithAttrCheck( function ( width, height ) {

return this.put( new Ellipse() ).size( width, height ).move( 0, 0 )

} )
} )
ellipse: wrapWithAttrCheck(function (width, height) {
return this.put(new Ellipse()).size(width, height).move(0, 0)
})
})

register( Ellipse )
register(Ellipse)

+ 8
- 14
src/elements/G.js View File

@@ -3,24 +3,18 @@ import { registerMethods } from '../utils/methods.js'
import Container from './Container.js'

export default class G extends Container {

constructor ( node ) {

super( nodeOrNew( 'g', node ), node )

constructor (node) {
super(nodeOrNew('g', node), node)
}

}

registerMethods( {
registerMethods({
Element: {
// Create a group element
group: wrapWithAttrCheck( function () {

return this.put( new G() )

} )
group: wrapWithAttrCheck(function () {
return this.put(new G())
})
}
} )
})

register( G )
register(G)

+ 21
- 45
src/elements/Gradient.js View File

@@ -12,95 +12,71 @@ import baseFind from '../modules/core/selector.js'
import * as gradiented from '../modules/core/gradiented.js'

export default class Gradient extends Container {

constructor ( type, attrs ) {

constructor (type, attrs) {
super(
nodeOrNew( type + 'Gradient', typeof type === 'string' ? null : type ),
nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type),
attrs
)

}

// Add a color stop
stop ( offset, color, opacity ) {

return this.put( new Stop() ).update( offset, color, opacity )

stop (offset, color, opacity) {
return this.put(new Stop()).update(offset, color, opacity)
}

// Update gradient
update ( block ) {

update (block) {
// remove all stops
this.clear()

// invoke passed block
if ( typeof block === 'function' ) {

block.call( this, this )

if (typeof block === 'function') {
block.call(this, this)
}

return this

}

// Return the fill id
url () {

return 'url(#' + this.id() + ')'

}

// Alias string convertion to fill
toString () {

return this.url()

}

// custom attr to handle transform
attr ( a, b, c ) {

if ( a === 'transform' ) a = 'gradientTransform'
return super.attr( a, b, c )

attr (a, b, c) {
if (a === 'transform') a = 'gradientTransform'
return super.attr(a, b, c)
}

targets () {

return baseFind( 'svg [fill*="' + this.id() + '"]' )

return baseFind('svg [fill*="' + this.id() + '"]')
}

bbox () {

return new Box()

}

}

extend( Gradient, gradiented )
extend(Gradient, gradiented)

registerMethods( {
registerMethods({
Container: {
// Create gradient element in defs
gradient: wrapWithAttrCheck( function ( type, block ) {

return this.defs().gradient( type, block )

} )
gradient: wrapWithAttrCheck(function (type, block) {
return this.defs().gradient(type, block)
})
},
// define gradient
Defs: {
gradient: wrapWithAttrCheck( function ( type, block ) {

return this.put( new Gradient( type ) ).update( block )

} )
gradient: wrapWithAttrCheck(function (type, block) {
return this.put(new Gradient(type)).update(block)
})
}
} )
})

register( Gradient )
register(Gradient)

+ 1
- 1
src/elements/HtmlNode.js View File

@@ -3,4 +3,4 @@ import Dom from './Dom.js'

export default class HtmlNode extends Dom {}

register( HtmlNode )
register(HtmlNode)

+ 33
- 63
src/elements/Image.js View File

@@ -9,99 +9,69 @@ import Shape from './Shape.js'
import { globals } from '../utils/window.js'

export default class Image extends Shape {

constructor ( node ) {

super( nodeOrNew( 'image', node ), node )

constructor (node) {
super(nodeOrNew('image', node), node)
}

// (re)load image
load ( url, callback ) {

if ( !url ) return this
load (url, callback) {
if (!url) return this

var img = new globals.window.Image()

on( img, 'load', function ( e ) {

var p = this.parent( Pattern )
on(img, 'load', function (e) {
var p = this.parent(Pattern)

// ensure image size
if ( this.width() === 0 && this.height() === 0 ) {

this.size( img.width, img.height )

if (this.width() === 0 && this.height() === 0) {
this.size(img.width, img.height)
}

if ( p instanceof Pattern ) {

if (p instanceof Pattern) {
// ensure pattern size if not set
if ( p.width() === 0 && p.height() === 0 ) {

p.size( this.width(), this.height() )

if (p.width() === 0 && p.height() === 0) {
p.size(this.width(), this.height())
}

}

if ( typeof callback === 'function' ) {

callback.call( this, e )

if (typeof callback === 'function') {
callback.call(this, e)
}
}, this)

}, this )

on( img, 'load error', function () {

on(img, 'load error', function () {
// dont forget to unbind memory leaking events
off( img )

} )

return this.attr( 'href', ( img.src = url ), xlink )
off(img)
})

return this.attr('href', (img.src = url), xlink)
}

}

registerAttrHook( function ( attr, val, _this ) {

registerAttrHook(function (attr, val, _this) {
// convert image fill and stroke to patterns
if ( attr === 'fill' || attr === 'stroke' ) {

if ( isImage.test( val ) ) {

val = _this.root().defs().image( val )

if (attr === 'fill' || attr === 'stroke') {
if (isImage.test(val)) {
val = _this.root().defs().image(val)
}

}

if ( val instanceof Image ) {

val = _this.root().defs().pattern( 0, 0, ( pattern ) => {

pattern.add( val )

} )

if (val instanceof Image) {
val = _this.root().defs().pattern(0, 0, (pattern) => {
pattern.add(val)
})
}

return val
})

} )

registerMethods( {
registerMethods({
Container: {
// create image element, load image and set its size
image: wrapWithAttrCheck( function ( source, callback ) {

return this.put( new Image() ).size( 0, 0 ).load( source, callback )

} )
image: wrapWithAttrCheck(function (source, callback) {
return this.put(new Image()).size(0, 0).load(source, callback)
})
}
} )
})

register( Image )
register(Image)

+ 23
- 43
src/elements/Line.js View File

@@ -11,78 +11,58 @@ import Shape from './Shape.js'
import * as pointed from '../modules/core/pointed.js'

export default class Line extends Shape {

// Initialize node
constructor ( node ) {

super( nodeOrNew( 'line', node ), node )

constructor (node) {
super(nodeOrNew('line', node), node)
}

// Get array
array () {

return new PointArray( [
[ this.attr( 'x1' ), this.attr( 'y1' ) ],
[ this.attr( 'x2' ), this.attr( 'y2' ) ]
] )

return new PointArray([
[ this.attr('x1'), this.attr('y1') ],
[ this.attr('x2'), this.attr('y2') ]
])
}

// Overwrite native plot() method
plot ( x1, y1, x2, y2 ) {

if ( x1 == null ) {

plot (x1, y1, x2, y2) {
if (x1 == null) {
return this.array()

} else if ( typeof y1 !== 'undefined' ) {

} else if (typeof y1 !== 'undefined') {
x1 = { x1: x1, y1: y1, x2: x2, y2: y2 }

} else {

x1 = new PointArray( x1 ).toLine()

x1 = new PointArray(x1).toLine()
}

return this.attr( x1 )

return this.attr(x1)
}

// Move by left top corner
move ( x, y ) {

return this.attr( this.array().move( x, y ).toLine() )

move (x, y) {
return this.attr(this.array().move(x, y).toLine())
}

// Set element size to given width and height
size ( width, height ) {

var p = proportionalSize( this, width, height )
return this.attr( this.array().size( p.width, p.height ).toLine() )

size (width, height) {
var p = proportionalSize(this, width, height)
return this.attr(this.array().size(p.width, p.height).toLine())
}

}

extend( Line, pointed )
extend(Line, pointed)

registerMethods( {
registerMethods({
Container: {
// Create a line element
line: wrapWithAttrCheck( function ( ...args ) {

line: wrapWithAttrCheck(function (...args) {
// make sure plot is called as a setter
// x1 is not necessarily a number, it can also be an array, a string and a PointArray
return Line.prototype.plot.apply(
this.put( new Line() )
this.put(new Line())
, args[0] != null ? args : [ 0, 0, 0, 0 ]
)

} )
})
}
} )
})

register( Line )
register(Line)

+ 29
- 51
src/elements/Marker.js View File

@@ -3,103 +3,81 @@ import { registerMethods } from '../utils/methods.js'
import Container from './Container.js'

export default class Marker extends Container {

// Initialize node
constructor ( node ) {

super( nodeOrNew( 'marker', node ), node )

constructor (node) {
super(nodeOrNew('marker', node), node)
}

// Set width of element
width ( width ) {

return this.attr( 'markerWidth', width )

width (width) {
return this.attr('markerWidth', width)
}

// Set height of element
height ( height ) {

return this.attr( 'markerHeight', height )

height (height) {
return this.attr('markerHeight', height)
}

// Set marker refX and refY
ref ( x, y ) {

return this.attr( 'refX', x ).attr( 'refY', y )

ref (x, y) {
return this.attr('refX', x).attr('refY', y)
}

// Update marker
update ( block ) {

update (block) {
// remove all content
this.clear()

// invoke passed block
if ( typeof block === 'function' ) {

block.call( this, this )

if (typeof block === 'function') {
block.call(this, this)
}

return this

}

// Return the fill id
toString () {

return 'url(#' + this.id() + ')'

}

}

registerMethods( {
registerMethods({
Container: {
marker ( ...args ) {

marker (...args) {
// Create marker element in defs
return this.defs().marker( ...args )

return this.defs().marker(...args)
}
},
Defs: {
// Create marker
marker: wrapWithAttrCheck( function ( width, height, block ) {

marker: wrapWithAttrCheck(function (width, height, block) {
// Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto
return this.put( new Marker() )
.size( width, height )
.ref( width / 2, height / 2 )
.viewbox( 0, 0, width, height )
.attr( 'orient', 'auto' )
.update( block )

} )
return this.put(new Marker())
.size(width, height)
.ref(width / 2, height / 2)
.viewbox(0, 0, width, height)
.attr('orient', 'auto')
.update(block)
})
},
marker: {
// Create and attach markers
marker ( marker, width, height, block ) {

marker (marker, width, height, block) {
var attr = [ 'marker' ]

// Build attribute name
if ( marker !== 'all' ) attr.push( marker )
attr = attr.join( '-' )
if (marker !== 'all') attr.push(marker)
attr = attr.join('-')

// Set marker attribute
marker = arguments[1] instanceof Marker
? arguments[1]
: this.defs().marker( width, height, block )

return this.attr( attr, marker )
: this.defs().marker(width, height, block)

return this.attr(attr, marker)
}
}
} )
})

register( Marker )
register(Marker)

+ 16
- 34
src/elements/Mask.js View File

@@ -4,72 +4,54 @@ import Container from './Container.js'
import baseFind from '../modules/core/selector.js'

export default class Mask extends Container {

// Initialize node
constructor ( node ) {

super( nodeOrNew( 'mask', node ), node )

constructor (node) {
super(nodeOrNew('mask', node), node)
}

// Unmask all masked elements and remove itself
remove () {

// unmask all targets
this.targets().forEach( function ( el ) {

this.targets().forEach(function (el) {
el.unmask()

} )
})

// remove mask from parent
return super.remove()

}

targets () {

return baseFind( 'svg [mask*="' + this.id() + '"]' )

return baseFind('svg [mask*="' + this.id() + '"]')
}

}

registerMethods( {
registerMethods({
Container: {
mask: wrapWithAttrCheck( function () {

return this.defs().put( new Mask() )

} )
mask: wrapWithAttrCheck(function () {
return this.defs().put(new Mask())
})
},
Element: {
// Distribute mask to svg element
maskWith ( element ) {

maskWith (element) {
// use given mask or create a new one
var masker = element instanceof Mask
? element
: this.parent().mask().add( element )
: this.parent().mask().add(element)

// apply mask
return this.attr( 'mask', 'url("#' + masker.id() + '")' )

return this.attr('mask', 'url("#' + masker.id() + '")')
},

// Unmask element
unmask () {

return this.attr( 'mask', null )

return this.attr('mask', null)
},

masker () {

return this.reference( 'mask' )

return this.reference('mask')
}
}
} )
})

register( Mask )
register(Mask)

+ 26
- 52
src/elements/Path.js View File

@@ -6,102 +6,76 @@ import Shape from './Shape.js'
import baseFind from '../modules/core/selector.js'

export default class Path extends Shape {

// Initialize node
constructor ( node ) {

super( nodeOrNew( 'path', node ), node )

constructor (node) {
super(nodeOrNew('path', node), node)
}

// Get array
array () {

return this._array || ( this._array = new PathArray( this.attr( 'd' ) ) )

return this._array || (this._array = new PathArray(this.attr('d')))
}

// Plot new path
plot ( d ) {

return ( d == null ) ? this.array()
: this.clear().attr( 'd', typeof d === 'string' ? d : ( this._array = new PathArray( d ) ) )

plot (d) {
return (d == null) ? this.array()
: this.clear().attr('d', typeof d === 'string' ? d : (this._array = new PathArray(d)))
}

// Clear array cache
clear () {

delete this._array
return this

}

// Move by left top corner
move ( x, y ) {

return this.attr( 'd', this.array().move( x, y ) )

move (x, y) {
return this.attr('d', this.array().move(x, y))
}

// Move by left top corner over x-axis
x ( x ) {

return x == null ? this.bbox().x : this.move( x, this.bbox().y )

x (x) {
return x == null ? this.bbox().x : this.move(x, this.bbox().y)
}

// Move by left top corner over y-axis
y ( y ) {

return y == null ? this.bbox().y : this.move( this.bbox().x, y )

y (y) {
return y == null ? this.bbox().y : this.move(this.bbox().x, y)
}

// Set element size to given width and height
size ( width, height ) {

var p = proportionalSize( this, width, height )
return this.attr( 'd', this.array().size( p.width, p.height ) )

size (width, height) {
var p = proportionalSize(this, width, height)
return this.attr('d', this.array().size(p.width, p.height))
}

// Set width of element
width ( width ) {

return width == null ? this.bbox().width : this.size( width, this.bbox().height )

width (width) {
return width == null ? this.bbox().width : this.size(width, this.bbox().height)
}

// Set height of element
height ( height ) {

return height == null ? this.bbox().height : this.size( this.bbox().width, height )

height (height) {
return height == null ? this.bbox().height : this.size(this.bbox().width, height)
}

targets () {

return baseFind( 'svg textpath [href*="' + this.id() + '"]' )

return baseFind('svg textpath [href*="' + this.id() + '"]')
}

}

// Define morphable array
Path.prototype.MorphArray = PathArray

// Add parent method
registerMethods( {
registerMethods({
Container: {
// Create a wrapped path element
path: wrapWithAttrCheck( function ( d ) {

path: wrapWithAttrCheck(function (d) {
// make sure plot is called as a setter
return this.put( new Path() ).plot( d || new PathArray() )

} )
return this.put(new Path()).plot(d || new PathArray())
})
}
} )
})

register( Path )
register(Path)

+ 18
- 40
src/elements/Pattern.js View File

@@ -5,89 +5,67 @@ import Container from './Container.js'
import baseFind from '../modules/core/selector.js'

export default class Pattern extends Container {

// Initialize node
constructor ( node ) {

super( nodeOrNew( 'pattern', node ), node )

constructor (node) {
super(nodeOrNew('pattern', node), node)
}

// Return the fill id
url () {

return 'url(#' + this.id() + ')'

}

// Update pattern by rebuilding
update ( block ) {

update (block) {
// remove content
this.clear()

// invoke passed block
if ( typeof block === 'function' ) {

block.call( this, this )

if (typeof block === 'function') {
block.call(this, this)
}

return this

}

// Alias string convertion to fill
toString () {

return this.url()

}

// custom attr to handle transform
attr ( a, b, c ) {

if ( a === 'transform' ) a = 'patternTransform'
return super.attr( a, b, c )

attr (a, b, c) {
if (a === 'transform') a = 'patternTransform'
return super.attr(a, b, c)
}

targets () {

return baseFind( 'svg [fill*="' + this.id() + '"]' )

return baseFind('svg [fill*="' + this.id() + '"]')
}

bbox () {

return new Box()

}

}

registerMethods( {
registerMethods({
Container: {
// Create pattern element in defs
pattern ( ...args ) {

return this.defs().pattern( ...args )

pattern (...args) {
return this.defs().pattern(...args)
}
},
Defs: {
pattern: wrapWithAttrCheck( function ( width, height, block ) {

return this.put( new Pattern() ).update( block ).attr( {
pattern: wrapWithAttrCheck(function (width, height, block) {
return this.put(new Pattern()).update(block).attr({
x: 0,
y: 0,
width: width,
height: height,
patternUnits: 'userSpaceOnUse'
} )

} )
})
})
}
} )
})

register( Pattern )
register(Pattern)

+ 10
- 16
src/elements/Polygon.js View File

@@ -11,28 +11,22 @@ import * as pointed from '../modules/core/pointed.js'
import * as poly from '../modules/core/poly.js'

export default class Polygon extends Shape {

// Initialize node
constructor ( node ) {

super( nodeOrNew( 'polygon', node ), node )

constructor (node) {
super(nodeOrNew('polygon', node), node)
}

}

registerMethods( {
registerMethods({
Container: {
// Create a wrapped polygon element
polygon: wrapWithAttrCheck( function ( p ) {

polygon: wrapWithAttrCheck(function (p) {
// make sure plot is called as a setter
return this.put( new Polygon() ).plot( p || new PointArray() )

} )
return this.put(new Polygon()).plot(p || new PointArray())
})
}
} )
})

extend( Polygon, pointed )
extend( Polygon, poly )
register( Polygon )
extend(Polygon, pointed)
extend(Polygon, poly)
register(Polygon)

+ 10
- 16
src/elements/Polyline.js View File

@@ -11,28 +11,22 @@ import * as pointed from '../modules/core/pointed.js'
import * as poly from '../modules/core/poly.js'

export default class Polyline extends Shape {

// Initialize node
constructor ( node ) {

super( nodeOrNew( 'polyline', node ), node )

constructor (node) {
super(nodeOrNew('polyline', node), node)
}

}

registerMethods( {
registerMethods({
Container: {
// Create a wrapped polygon element
polyline: wrapWithAttrCheck( function ( p ) {

polyline: wrapWithAttrCheck(function (p) {
// make sure plot is called as a setter
return this.put( new Polyline() ).plot( p || new PointArray() )

} )
return this.put(new Polyline()).plot(p || new PointArray())
})
}
} )
})

extend( Polyline, pointed )
extend( Polyline, poly )
register( Polyline )
extend(Polyline, pointed)
extend(Polyline, poly)
register(Polyline)

+ 9
- 15
src/elements/Rect.js View File

@@ -9,27 +9,21 @@ import { rx, ry } from '../modules/core/circled.js'
import Shape from './Shape.js'

export default class Rect extends Shape {

// Initialize node
constructor ( node ) {

super( nodeOrNew( 'rect', node ), node )

constructor (node) {
super(nodeOrNew('rect', node), node)
}

}

extend( Rect, { rx, ry } )
extend(Rect, { rx, ry })

registerMethods( {
registerMethods({
Container: {
// Create a rect element
rect: wrapWithAttrCheck( function ( width, height ) {

return this.put( new Rect() ).size( width, height )

} )
rect: wrapWithAttrCheck(function (width, height) {
return this.put(new Rect()).size(width, height)
})
}
} )
})

register( Rect )
register(Rect)

+ 1
- 1
src/elements/Shape.js View File

@@ -3,4 +3,4 @@ import Element from './Element.js'

export default class Shape extends Element {}

register( Shape )
register(Shape)

+ 8
- 16
src/elements/Stop.js View File

@@ -3,35 +3,27 @@ import Element from './Element.js'
import SVGNumber from '../types/SVGNumber.js'

export default class Stop extends Element {

constructor ( node ) {

super( nodeOrNew( 'stop', node ), node )

constructor (node) {
super(nodeOrNew('stop', node), node)
}

// add color stops
update ( o ) {

if ( typeof o === 'number' || o instanceof SVGNumber ) {

update (o) {
if (typeof o === 'number' || o instanceof SVGNumber) {
o = {
offset: arguments[0],
color: arguments[1],
opacity: arguments[2]
}

}

// set attributes
if ( o.opacity != null ) this.attr( 'stop-opacity', o.opacity )
if ( o.color != null ) this.attr( 'stop-color', o.color )
if ( o.offset != null ) this.attr( 'offset', new SVGNumber( o.offset ) )
if (o.opacity != null) this.attr('stop-opacity', o.opacity)
if (o.color != null) this.attr('stop-color', o.color)
if (o.offset != null) this.attr('offset', new SVGNumber(o.offset))

return this

}

}

register( Stop )
register(Stop)

+ 23
- 41
src/elements/Style.js View File

@@ -3,69 +3,51 @@ import { registerMethods } from '../utils/methods.js'
import { unCamelCase } from '../utils/utils.js'
import Element from './Element.js'

function cssRule ( selector, rule ) {

if ( !selector ) return ''
if ( !rule ) return selector
function cssRule (selector, rule) {
if (!selector) return ''
if (!rule) return selector

var ret = selector + '{'

for ( var i in rule ) {

ret += unCamelCase( i ) + ':' + rule[i] + ';'

for (var i in rule) {
ret += unCamelCase(i) + ':' + rule[i] + ';'
}

ret += '}'

return ret

}

export default class Style extends Element {

constructor ( node ) {

super( nodeOrNew( 'style', node ), node )

constructor (node) {
super(nodeOrNew('style', node), node)
}

words ( w ) {

this.node.textContent += ( w || '' )
words (w) {
this.node.textContent += (w || '')
return this

}

font ( name, src, params = {} ) {

return this.rule( '@font-face', {
font (name, src, params = {}) {
return this.rule('@font-face', {
fontFamily: name,
src: src,
...params
} )

})
}

rule ( selector, obj ) {

return this.words( cssRule( selector, obj ) )

rule (selector, obj) {
return this.words(cssRule(selector, obj))
}

}

registerMethods( 'Dom', {
style: wrapWithAttrCheck( function ( selector, obj ) {

return this.put( new Style() ).rule( selector, obj )

} ),
fontface: wrapWithAttrCheck( function ( name, src, params ) {

return this.put( new Style() ).font( name, src, params )

} )
} )
registerMethods('Dom', {
style: wrapWithAttrCheck(function (selector, obj) {
return this.put(new Style()).rule(selector, obj)
}),
fontface: wrapWithAttrCheck(function (name, src, params) {
return this.put(new Style()).font(name, src, params)
})
})

register( Style )
register(Style)

+ 23
- 45
src/elements/Svg.js View File

@@ -11,90 +11,68 @@ import Defs from './Defs.js'
import { globals } from '../utils/window.js'

export default class Svg extends Container {

constructor ( node ) {

super( nodeOrNew( 'svg', node ), node )
constructor (node) {
super(nodeOrNew('svg', node), node)
this.namespace()

}

isRoot () {

return !this.node.parentNode
|| !( this.node.parentNode instanceof globals.window.SVGElement )
|| !(this.node.parentNode instanceof globals.window.SVGElement)
|| this.node.parentNode.nodeName === '#document'

}

// Check if this is a root svg
// If not, call docs from this element
root () {

if ( this.isRoot() ) return this
if (this.isRoot()) return this
return super.root()

}

// Add namespaces
namespace () {

if ( !this.isRoot() ) return this.root().namespace()
if (!this.isRoot()) return this.root().namespace()
return this
.attr( { xmlns: ns, version: '1.1' } )
.attr( 'xmlns:xlink', xlink, xmlns )
.attr( 'xmlns:svgjs', svgjs, xmlns )

.attr({ xmlns: ns, version: '1.1' })
.attr('xmlns:xlink', xlink, xmlns)
.attr('xmlns:svgjs', svgjs, xmlns)
}

// Creates and returns defs element
defs () {
if (!this.isRoot()) return this.root().defs()

if ( !this.isRoot() ) return this.root().defs()

return adopt( this.node.getElementsByTagName( 'defs' )[0] )
|| this.put( new Defs() )

return adopt(this.node.getElementsByTagName('defs')[0])
|| this.put(new Defs())
}

// custom parent method
parent ( type ) {

if ( this.isRoot() ) {

parent (type) {
if (this.isRoot()) {
return this.node.parentNode.nodeName === '#document'
? null
: adopt( this.node.parentNode )

: adopt(this.node.parentNode)
}

return super.parent( type )

return super.parent(type)
}

clear () {

// remove children
while ( this.node.hasChildNodes() ) {

this.node.removeChild( this.node.lastChild )

while (this.node.hasChildNodes()) {
this.node.removeChild(this.node.lastChild)
}
return this

}

}

registerMethods( {
registerMethods({
Container: {
// Create nested svg document
nested: wrapWithAttrCheck( function () {

return this.put( new Svg() )

} )
nested: wrapWithAttrCheck(function () {
return this.put(new Svg())
})
}
} )
})

register( Svg, 'Svg', true )
register(Svg, 'Svg', true)

+ 8
- 14
src/elements/Symbol.js View File

@@ -3,24 +3,18 @@ import { registerMethods } from '../utils/methods.js'
import Container from './Container.js'

export default class Symbol extends Container {

// Initialize node
constructor ( node ) {

super( nodeOrNew( 'symbol', node ), node )

constructor (node) {
super(nodeOrNew('symbol', node), node)
}

}

registerMethods( {
registerMethods({
Container: {
symbol: wrapWithAttrCheck( function () {

return this.put( new Symbol() )

} )
symbol: wrapWithAttrCheck(function () {
return this.put(new Symbol())
})
}
} )
})

register( Symbol )
register(Symbol)

+ 58
- 116
src/elements/Text.js View File

@@ -13,233 +13,175 @@ import { globals } from '../utils/window.js'
import * as textable from '../modules/core/textable.js'

export default class Text extends Shape {

// Initialize node
constructor ( node ) {

super( nodeOrNew( 'text', node ), node )
constructor (node) {
super(nodeOrNew('text', node), node)

this.dom.leading = new SVGNumber( 1.3 ) // store leading value for rebuilding
this.dom.leading = new SVGNumber(1.3) // store leading value for rebuilding
this._rebuild = true // enable automatic updating of dy values
this._build = false // disable build mode for adding multiple lines

// set default font
this.attr( 'font-family', attrs['font-family'] )

this.attr('font-family', attrs['font-family'])
}

// Move over x-axis
x ( x ) {

x (x) {
// act as getter
if ( x == null ) {

return this.attr( 'x' )

if (x == null) {
return this.attr('x')
}

return this.attr( 'x', x )

return this.attr('x', x)
}

// Move over y-axis
y ( y ) {

var oy = this.attr( 'y' )
y (y) {
var oy = this.attr('y')
var o = typeof oy === 'number' ? oy - this.bbox().y : 0

// act as getter
if ( y == null ) {

if (y == null) {
return typeof oy === 'number' ? oy - o : oy

}

return this.attr( 'y', typeof y === 'number' ? y + o : y )

return this.attr('y', typeof y === 'number' ? y + o : y)
}

// Move center over x-axis
cx ( x ) {

return x == null ? this.bbox().cx : this.x( x - this.bbox().width / 2 )

cx (x) {
return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2)
}

// Move center over y-axis
cy ( y ) {

return y == null ? this.bbox().cy : this.y( y - this.bbox().height / 2 )

cy (y) {
return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2)
}

// Set the text content
text ( text ) {

text (text) {
// act as getter
if ( text === undefined ) {

if (text === undefined) {
var children = this.node.childNodes
var firstLine = 0
text = ''

for ( var i = 0, len = children.length; i < len; ++i ) {

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
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 ) {

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' ) {
this.clear().build(true)

if (typeof text === 'function') {
// call block
text.call( this, this )

text.call(this, this)
} else {

// store text and make sure text is not blank
text = text.split( '\n' )
text = text.split('\n')

// build new lines
for ( var j = 0, jl = text.length; j < jl; j++ ) {

this.tspan( text[j] ).newLine()

for (var j = 0, jl = text.length; j < jl; j++) {
this.tspan(text[j]).newLine()
}

}

// disable build mode and rebuild lines
return this.build( false ).rebuild()

return this.build(false).rebuild()
}

// Set / get leading
leading ( value ) {

leading (value) {
// act as getter
if ( value == null ) {

if (value == null) {
return this.dom.leading

}

// act as setter
this.dom.leading = new SVGNumber( value )
this.dom.leading = new SVGNumber(value)

return this.rebuild()

}

// Rebuild appearance type
rebuild ( rebuild ) {

rebuild (rebuild) {
// store new rebuild flag if given
if ( typeof rebuild === 'boolean' ) {

if (typeof rebuild === 'boolean') {
this._rebuild = rebuild

}

// define position of all lines
if ( this._rebuild ) {

if (this._rebuild) {
var self = this
var blankLineOffset = 0
var leading = this.dom.leading

this.each( function () {

var fontSize = globals.window.getComputedStyle( this.node )
.getPropertyValue( 'font-size' )
var dy = leading * new SVGNumber( fontSize )

if ( this.dom.newLined ) {
this.each(function () {
var fontSize = globals.window.getComputedStyle(this.node)
.getPropertyValue('font-size')
var dy = leading * new SVGNumber(fontSize)

this.attr( 'x', self.attr( 'x' ) )

if ( this.text() === '\n' ) {
if (this.dom.newLined) {
this.attr('x', self.attr('x'))

if (this.text() === '\n') {
blankLineOffset += dy

} else {

this.attr( 'dy', dy + blankLineOffset )
this.attr('dy', dy + blankLineOffset)
blankLineOffset = 0

}

}
})

} )

this.fire( 'rebuild' )

this.fire('rebuild')
}

return this

}

// Enable / disable build mode
build ( build ) {

build (build) {
this._build = !!build
return this

}

// overwrite method from parent to set data properly
setData ( o ) {

setData (o) {
this.dom = o
this.dom.leading = new SVGNumber( o.leading || 1.3 )
this.dom.leading = new SVGNumber(o.leading || 1.3)
return this

}

}

extend( Text, textable )
extend(Text, textable)

registerMethods( {
registerMethods({
Container: {
// Create text element
text: wrapWithAttrCheck( function ( text ) {

return this.put( new Text() ).text( text )

} ),
text: wrapWithAttrCheck(function (text) {
return this.put(new Text()).text(text)
}),

// Create plain text element
plain: wrapWithAttrCheck( function ( text ) {

return this.put( new Text() ).plain( text )

} )
plain: wrapWithAttrCheck(function (text) {
return this.put(new Text()).plain(text)
})
}
} )
})

register( Text )
register(Text)

+ 26
- 52
src/elements/TextPath.js View File

@@ -7,106 +7,80 @@ import Text from './Text.js'
import baseFind from '../modules/core/selector.js'

export default class TextPath extends Text {

// Initialize node
constructor ( node ) {

super( nodeOrNew( 'textPath', node ), node )

constructor (node) {
super(nodeOrNew('textPath', node), node)
}

// return the array of the path track element
array () {

var track = this.track()

return track ? track.array() : null

}

// Plot path if any
plot ( d ) {

plot (d) {
var track = this.track()
var pathArray = null

if ( track ) {

pathArray = track.plot( d )

if (track) {
pathArray = track.plot(d)
}

return ( d == null ) ? pathArray : this

return (d == null) ? pathArray : this
}

// Get the path element
track () {

return this.reference( 'href' )

return this.reference('href')
}

}

registerMethods( {
registerMethods({
Container: {
textPath: wrapWithAttrCheck( function ( text, path ) {

return this.defs().path( path ).text( text ).addTo( this )

} )
textPath: wrapWithAttrCheck(function (text, path) {
return this.defs().path(path).text(text).addTo(this)
})
},
Text: {
// Create path for text to run on
path: wrapWithAttrCheck( function ( track ) {

path: wrapWithAttrCheck(function (track) {
var path = new TextPath()

// if track is a path, reuse it
if ( !( track instanceof Path ) ) {

if (!(track instanceof Path)) {
// create path element
track = this.root().defs().path( track )

track = this.root().defs().path(track)
}

// link textPath to path and add content
path.attr( 'href', '#' + track, xlink )
path.attr('href', '#' + track, xlink)

// add textPath element as child node and return textPath
return this.put( path )

} ),
return this.put(path)
}),

// Get the textPath children
textPath () {

return this.find( 'textPath' )[0]

return this.find('textPath')[0]
}
},
Path: {
// creates a textPath from this path
text: wrapWithAttrCheck( function ( text ) {

if ( text instanceof Text ) {

text: wrapWithAttrCheck(function (text) {
if (text instanceof Text) {
var txt = text.text()
return text.clear().path( this ).text( txt )

return text.clear().path(this).text(txt)
}
return this.parent().put( new Text() ).path( this ).text( text )

} ),
return this.parent().put(new Text()).path(this).text(text)
}),

targets () {

return baseFind( 'svg [href*="' + this.id() + '"]' )

return baseFind('svg [href*="' + this.id() + '"]')
}
}
} )
})

TextPath.prototype.MorphArray = PathArray
register( TextPath )
register(TextPath)

+ 20
- 36
src/elements/Tspan.js View File

@@ -9,77 +9,61 @@ import Text from './Text.js'
import * as textable from '../modules/core/textable.js'

export default class Tspan extends Text {

// Initialize node
constructor ( node ) {

super( nodeOrNew( 'tspan', node ), node )

constructor (node) {
super(nodeOrNew('tspan', node), node)
}

// Set text content
text ( text ) {

if ( text == null ) return this.node.textContent + ( this.dom.newLined ? '\n' : '' )
text (text) {
if (text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '')

typeof text === 'function' ? text.call( this, this ) : this.plain( text )
typeof text === 'function' ? text.call(this, this) : this.plain(text)

return this

}

// Shortcut dx
dx ( dx ) {

return this.attr( 'dx', dx )

dx (dx) {
return this.attr('dx', dx)
}

// Shortcut dy
dy ( dy ) {

return this.attr( 'dy', dy )

dy (dy) {
return this.attr('dy', dy)
}

// Create new line
newLine () {

// fetch text parent
var t = this.parent( Text )
var t = this.parent(Text)

// mark new line
this.dom.newLined = true

// apply new position
return this.dy( t.dom.leading * t.attr( 'font-size' ) ).attr( 'x', t.x() )

return this.dy(t.dom.leading * t.attr('font-size')).attr('x', t.x())
}

}

extend( Tspan, textable )
extend(Tspan, textable)

registerMethods( {
registerMethods({
Tspan: {
tspan: wrapWithAttrCheck( function ( text ) {

tspan: wrapWithAttrCheck(function (text) {
var tspan = new Tspan()

// clear if build mode is disabled
if ( !this._build ) {

if (!this._build) {
this.clear()

}

// add new tspan
this.node.appendChild( tspan.node )

return tspan.text( text )
this.node.appendChild(tspan.node)

} )
return tspan.text(text)
})
}
} )
})

register( Tspan )
register(Tspan)

+ 10
- 18
src/elements/Use.js View File

@@ -4,32 +4,24 @@ import { xlink } from '../modules/core/namespaces.js'
import Shape from './Shape.js'

export default class Use extends Shape {

constructor ( node ) {

super( nodeOrNew( 'use', node ), node )

constructor (node) {
super(nodeOrNew('use', node), node)
}

// Use element as a reference
element ( element, file ) {

element (element, file) {
// Set lined element
return this.attr( 'href', ( file || '' ) + '#' + element, xlink )

return this.attr('href', (file || '') + '#' + element, xlink)
}

}

registerMethods( {
registerMethods({
Container: {
// Create a use element
use: wrapWithAttrCheck( function ( element, file ) {

return this.put( new Use() ).element( element, file )

} )
use: wrapWithAttrCheck(function (element, file) {
return this.put(new Use()).element(element, file)
})
}
} )
})

register( Use )
register(Use)

+ 20
- 20
src/main.js View File

@@ -116,50 +116,50 @@ export { default as TextPath } from './elements/TextPath.js'
export { default as Tspan } from './elements/Tspan.js'
export { default as Use } from './elements/Use.js'

extend( [
extend([
Svg,
Symbol,
Image,
Pattern,
Marker
], getMethodsFor( 'viewbox' ) )
], getMethodsFor('viewbox'))

extend( [
extend([
Line,
Polyline,
Polygon,
Path
], getMethodsFor( 'marker' ) )
], getMethodsFor('marker'))

extend( Text, getMethodsFor( 'Text' ) )
extend( Path, getMethodsFor( 'Path' ) )
extend(Text, getMethodsFor('Text'))
extend(Path, getMethodsFor('Path'))

extend( Defs, getMethodsFor( 'Defs' ) )
extend(Defs, getMethodsFor('Defs'))

extend( [
extend([
Text,
Tspan
], getMethodsFor( 'Tspan' ) )
], getMethodsFor('Tspan'))

extend( [
extend([
Rect,
Ellipse,
Circle,
Gradient
], getMethodsFor( 'radius' ) )
], getMethodsFor('radius'))

extend( EventTarget, getMethodsFor( 'EventTarget' ) )
extend( Dom, getMethodsFor( 'Dom' ) )
extend( Element, getMethodsFor( 'Element' ) )
extend( Shape, getMethodsFor( 'Shape' ) )
extend(EventTarget, getMethodsFor('EventTarget'))
extend(Dom, getMethodsFor('Dom'))
extend(Element, getMethodsFor('Element'))
extend(Shape, getMethodsFor('Shape'))
// extend(Element, getConstructor('Memory'))
extend( Container, getMethodsFor( 'Container' ) )
extend(Container, getMethodsFor('Container'))

extend( Runner, getMethodsFor( 'Runner' ) )
extend(Runner, getMethodsFor('Runner'))

List.extend( getMethodNames() )
List.extend(getMethodNames())

registerMorphableType( [
registerMorphableType([
SVGNumber,
Color,
Box,
@@ -167,6 +167,6 @@ registerMorphableType( [
SVGArray,
PointArray,
PathArray
] )
])

makeMorphable()

+ 33
- 69
src/modules/core/attr.js View File

@@ -5,113 +5,77 @@ import SVGArray from '../../types/SVGArray.js'
import SVGNumber from '../../types/SVGNumber.js'

const hooks = []
export function registerAttrHook ( fn ) {

hooks.push( fn )

export function registerAttrHook (fn) {
hooks.push(fn)
}

// Set svg element attribute
export default function attr ( attr, val, ns ) {

export default function attr (attr, val, ns) {
// act as full getter
if ( attr == null ) {

if (attr == null) {
// get an object of attributes
attr = {}
val = this.node.attributes

for ( let node of val ) {

attr[node.nodeName] = isNumber.test( node.nodeValue )
? parseFloat( node.nodeValue )
for (let node of val) {
attr[node.nodeName] = isNumber.test(node.nodeValue)
? parseFloat(node.nodeValue)
: node.nodeValue

}

return attr

} else if ( attr instanceof Array ) {

} else if (attr instanceof Array) {
// loop through array and get all values
return attr.reduce( ( last, curr ) => {

last[curr] = this.attr( curr )
return attr.reduce((last, curr) => {
last[curr] = this.attr(curr)
return last

}, {} )

} else if ( typeof attr === 'object' ) {

}, {})
} else if (typeof attr === 'object') {
// apply every attribute individually if an object is passed
for ( val in attr ) this.attr( val, attr[val] )

} else if ( val === null ) {

for (val in attr) this.attr(val, attr[val])
} else if (val === null) {
// remove value
this.node.removeAttribute( attr )

} else if ( val == null ) {

this.node.removeAttribute(attr)
} else if (val == null) {
// act as a getter if the first and only argument is not an object
val = this.node.getAttribute( attr )
val = this.node.getAttribute(attr)
return val == null ? defaults[attr]
: isNumber.test( val ) ? parseFloat( val )
: isNumber.test(val) ? parseFloat(val)
: val

} else {

// Loop through hooks and execute them to convert value
val = hooks.reduce( ( _val, hook ) => {

return hook( attr, _val, this )

}, val )
val = hooks.reduce((_val, hook) => {
return hook(attr, _val, this)
}, val)

// ensure correct numeric values (also accepts NaN and Infinity)
if ( typeof val === 'number' ) {

val = new SVGNumber( val )

} else if ( Color.isColor( val ) ) {

if (typeof val === 'number') {
val = new SVGNumber(val)
} else if (Color.isColor(val)) {
// ensure full hex color
val = new Color( val )

} else if ( val.constructor === Array ) {

val = new Color(val)
} else if (val.constructor === Array) {
// Check for plain arrays and parse array values
val = new SVGArray( val )

val = new SVGArray(val)
}

// if the passed attribute is leading...
if ( attr === 'leading' ) {

if (attr === 'leading') {
// ... call the leading method instead
if ( this.leading ) {

this.leading( val )

if (this.leading) {
this.leading(val)
}

} else {

// set given attribute on node
typeof ns === 'string' ? this.node.setAttributeNS( ns, attr, val.toString() )
: this.node.setAttribute( attr, val.toString() )

typeof ns === 'string' ? this.node.setAttributeNS(ns, attr, val.toString())
: this.node.setAttribute(attr, val.toString())
}

// rebuild if required
if ( this.rebuild && ( attr === 'font-size' || attr === 'x' ) ) {

if (this.rebuild && (attr === 'font-size' || attr === 'x')) {
this.rebuild()

}

}

return this

}

+ 18
- 34
src/modules/core/circled.js View File

@@ -1,69 +1,53 @@
import SVGNumber from '../../types/SVGNumber.js'

// Radius x value
export function rx ( rx ) {

return this.attr( 'rx', rx )

export function rx (rx) {
return this.attr('rx', rx)
}

// Radius y value
export function ry ( ry ) {

return this.attr( 'ry', ry )

export function ry (ry) {
return this.attr('ry', ry)
}

// Move over x-axis
export function x ( x ) {

export function x (x) {
return x == null
? this.cx() - this.rx()
: this.cx( x + this.rx() )

: this.cx(x + this.rx())
}

// Move over y-axis
export function y ( y ) {

export function y (y) {
return y == null
? this.cy() - this.ry()
: this.cy( y + this.ry() )

: this.cy(y + this.ry())
}

// Move by center over x-axis
export function cx ( x ) {

export function cx (x) {
return x == null
? this.attr( 'cx' )
: this.attr( 'cx', x )

? this.attr('cx')
: this.attr('cx', x)
}

// Move by center over y-axis
export function cy ( y ) {

export function cy (y) {
return y == null
? this.attr( 'cy' )
: this.attr( 'cy', y )

? this.attr('cy')
: this.attr('cy', y)
}

// Set width of element
export function width ( width ) {

export function width (width) {
return width == null
? this.rx() * 2
: this.rx( new SVGNumber( width ).divide( 2 ) )

: this.rx(new SVGNumber(width).divide(2))
}

// Set height of element
export function height ( height ) {

export function height (height) {
return height == null
? this.ry() * 2
: this.ry( new SVGNumber( height ).divide( 2 ) )

: this.ry(new SVGNumber(height).divide(2))
}

+ 54
- 106
src/modules/core/event.js View File

@@ -4,48 +4,38 @@ import { globals } from '../../utils/window.js'

let listenerId = 0

function getEvents ( node ) {

const n = makeInstance( node ).getEventHolder()
if ( !n.events ) n.events = {}
function getEvents (node) {
const n = makeInstance(node).getEventHolder()
if (!n.events) n.events = {}
return n.events

}

function getEventTarget ( node ) {

return makeInstance( node ).getEventTarget()

function getEventTarget (node) {
return makeInstance(node).getEventTarget()
}

function clearEvents ( node ) {

const n = makeInstance( node ).getEventHolder()
if ( n.events ) n.events = {}

function clearEvents (node) {
const n = makeInstance(node).getEventHolder()
if (n.events) n.events = {}
}

// Add event binder in the SVG namespace
export function on ( node, events, listener, binding, options ) {

var l = listener.bind( binding || node )
var bag = getEvents( node )
var n = getEventTarget( node )
export function on (node, events, listener, binding, options) {
var l = listener.bind(binding || node)
var bag = getEvents(node)
var n = getEventTarget(node)

// events can be an array of events or a string of events
events = Array.isArray( events ) ? events : events.split( delimiter )
events = Array.isArray(events) ? events : events.split(delimiter)

// add id to listener
if ( !listener._svgjsListenerId ) {

if (!listener._svgjsListenerId) {
listener._svgjsListenerId = ++listenerId

}

events.forEach( function ( event ) {

var ev = event.split( '.' )[0]
var ns = event.split( '.' )[1] || '*'
events.forEach(function (event) {
var ev = event.split('.')[0]
var ns = event.split('.')[1] || '*'

// ensure valid object
bag[ev] = bag[ev] || {}
@@ -55,126 +45,84 @@ export function on ( node, events, listener, binding, options ) {
bag[ev][ns][listener._svgjsListenerId] = l

// add listener
n.addEventListener( ev, l, options || false )

} )

n.addEventListener(ev, l, options || false)
})
}

// Add event unbinder in the SVG namespace
export function off ( node, events, listener, options ) {

var bag = getEvents( node )
var n = getEventTarget( node )
export function off (node, events, listener, options) {
var bag = getEvents(node)
var n = getEventTarget(node)

// listener can be a function or a number
if ( typeof listener === 'function' ) {

if (typeof listener === 'function') {
listener = listener._svgjsListenerId
if ( !listener ) return

if (!listener) return
}

// events can be an array of events or a string or undefined
events = Array.isArray( events ) ? events : ( events || '' ).split( delimiter )
events = Array.isArray(events) ? events : (events || '').split(delimiter)

events.forEach( function ( event ) {

var ev = event && event.split( '.' )[0]
var ns = event && event.split( '.' )[1]
events.forEach(function (event) {
var ev = event && event.split('.')[0]
var ns = event && event.split('.')[1]
var namespace, l

if ( listener ) {

if (listener) {
// remove listener reference
if ( bag[ev] && bag[ev][ns || '*'] ) {

if (bag[ev] && bag[ev][ns || '*']) {
// removeListener
n.removeEventListener( ev, bag[ev][ns || '*'][listener], options || false )
n.removeEventListener(ev, bag[ev][ns || '*'][listener], options || false)

delete bag[ev][ns || '*'][listener]

}

} else if ( ev && ns ) {

} else if (ev && ns) {
// remove all listeners for a namespaced event
if ( bag[ev] && bag[ev][ns] ) {

for ( l in bag[ev][ns] ) {

off( n, [ ev, ns ].join( '.' ), l )

if (bag[ev] && bag[ev][ns]) {
for (l in bag[ev][ns]) {
off(n, [ ev, ns ].join('.'), l)
}

delete bag[ev][ns]

}

} else if ( ns ) {

} else if (ns) {
// remove all listeners for a specific namespace
for ( event in bag ) {

for ( namespace in bag[event] ) {

if ( ns === namespace ) {

off( n, [ event, ns ].join( '.' ) )

for (event in bag) {
for (namespace in bag[event]) {
if (ns === namespace) {
off(n, [ event, ns ].join('.'))
}

}

}

} else if ( ev ) {

} else if (ev) {
// remove all listeners for the event
if ( bag[ev] ) {

for ( namespace in bag[ev] ) {

off( n, [ ev, namespace ].join( '.' ) )

if (bag[ev]) {
for (namespace in bag[ev]) {
off(n, [ ev, namespace ].join('.'))
}

delete bag[ev]

}

} else {

// remove all listeners on a given node
for ( event in bag ) {

off( n, event )

for (event in bag) {
off(n, event)
}

clearEvents( node )

clearEvents(node)
}

} )

})
}

export function dispatch ( node, event, data ) {

var n = getEventTarget( node )
export function dispatch (node, event, data) {
var n = getEventTarget(node)

// Dispatch event
if ( event instanceof globals.window.Event ) {

n.dispatchEvent( event )

if (event instanceof globals.window.Event) {
n.dispatchEvent(event)
} else {

event = new globals.window.CustomEvent( event, { detail: data, cancelable: true } )
n.dispatchEvent( event )

event = new globals.window.CustomEvent(event, { detail: data, cancelable: true })
n.dispatchEvent(event)
}
return event

}

+ 8
- 12
src/modules/core/gradiented.js View File

@@ -1,17 +1,13 @@
import SVGNumber from '../../types/SVGNumber.js'

export function from ( x, y ) {

return ( this._element || this ).type === 'radialGradient'
? this.attr( { fx: new SVGNumber( x ), fy: new SVGNumber( y ) } )
: this.attr( { x1: new SVGNumber( x ), y1: new SVGNumber( y ) } )

export function from (x, y) {
return (this._element || this).type === 'radialGradient'
? this.attr({ fx: new SVGNumber(x), fy: new SVGNumber(y) })
: this.attr({ x1: new SVGNumber(x), y1: new SVGNumber(y) })
}

export function to ( x, y ) {

return ( this._element || this ).type === 'radialGradient'
? this.attr( { cx: new SVGNumber( x ), cy: new SVGNumber( y ) } )
: this.attr( { x2: new SVGNumber( x ), y2: new SVGNumber( y ) } )

export function to (x, y) {
return (this._element || this).type === 'radialGradient'
? this.attr({ cx: new SVGNumber(x), cy: new SVGNumber(y) })
: this.attr({ x2: new SVGNumber(x), y2: new SVGNumber(y) })
}

+ 5
- 11
src/modules/core/parser.js View File

@@ -2,32 +2,26 @@ import { globals } from '../../utils/window.js'
import { makeInstance } from '../../utils/adopter.js'

export default function parser () {

// Reuse cached element if possible
if ( !parser.nodes ) {

let svg = makeInstance().size( 2, 0 )
if (!parser.nodes) {
let svg = makeInstance().size(2, 0)
svg.node.cssText = [
'opacity: 0',
'position: absolute',
'left: -100%',
'top: -100%',
'overflow: hidden'
].join( ';' )
].join(';')

let path = svg.path().node

parser.nodes = { svg, path }

}

if ( !parser.nodes.svg.node.parentNode ) {

if (!parser.nodes.svg.node.parentNode) {
let b = globals.document.body || globals.document.documentElement
parser.nodes.svg.addTo( b )

parser.nodes.svg.addTo(b)
}

return parser.nodes

}

+ 8
- 16
src/modules/core/pointed.js View File

@@ -3,31 +3,23 @@ import PointArray from '../../types/PointArray.js'
export let MorphArray = PointArray

// Move by left top corner over x-axis
export function x ( x ) {

return x == null ? this.bbox().x : this.move( x, this.bbox().y )

export function x (x) {
return x == null ? this.bbox().x : this.move(x, this.bbox().y)
}

// Move by left top corner over y-axis
export function y ( y ) {

return y == null ? this.bbox().y : this.move( this.bbox().x, y )

export function y (y) {
return y == null ? this.bbox().y : this.move(this.bbox().x, y)
}

// Set width of element
export function width ( width ) {

export function width (width) {
let b = this.bbox()
return width == null ? b.width : this.size( width, b.height )

return width == null ? b.width : this.size(width, b.height)
}

// Set height of element
export function height ( height ) {

export function height (height) {
let b = this.bbox()
return height == null ? b.height : this.size( b.width, height )

return height == null ? b.height : this.size(b.width, height)
}

+ 10
- 20
src/modules/core/poly.js View File

@@ -3,39 +3,29 @@ import PointArray from '../../types/PointArray.js'

// Get array
export function array () {

return this._array || ( this._array = new PointArray( this.attr( 'points' ) ) )

return this._array || (this._array = new PointArray(this.attr('points')))
}

// Plot new path
export function plot ( p ) {

return ( p == null ) ? this.array()
: this.clear().attr( 'points', typeof p === 'string' ? p
: ( this._array = new PointArray( p ) ) )

export function plot (p) {
return (p == null) ? this.array()
: this.clear().attr('points', typeof p === 'string' ? p
: (this._array = new PointArray(p)))
}

// Clear array cache
export function clear () {

delete this._array
return this

}

// Move by left top corner
export function move ( x, y ) {

return this.attr( 'points', this.array().move( x, y ) )

export function move (x, y) {
return this.attr('points', this.array().move(x, y))
}

// Set element size to given width and height
export function size ( width, height ) {

let p = proportionalSize( this, width, height )
return this.attr( 'points', this.array().size( p.width, p.height ) )

export function size (width, height) {
let p = proportionalSize(this, width, height)
return this.attr('points', this.array().size(p.width, p.height))
}

+ 6
- 12
src/modules/core/selector.js View File

@@ -3,19 +3,13 @@ import { globals } from '../../utils/window.js'
import { map } from '../../utils/utils.js'
import List from '../../types/List.js'

export default function baseFind ( query, parent ) {

return new List( map( ( parent || globals.document ).querySelectorAll( query ), function ( node ) {

return adopt( node )

} ) )

export default function baseFind (query, parent) {
return new List(map((parent || globals.document).querySelectorAll(query), function (node) {
return adopt(node)
}))
}

// Scoped find method
export function find ( query ) {

return baseFind( query, this.node )

export function find (query) {
return baseFind(query, this.node)
}

+ 3
- 9
src/modules/core/textable.js View File

@@ -1,25 +1,19 @@
import { globals } from '../../utils/window.js'

// Create plain text node
export function plain ( text ) {

export function plain (text) {
// clear if build mode is disabled
if ( this._build === false ) {

if (this._build === false) {
this.clear()

}

// create text node
this.node.appendChild( globals.document.createTextNode( text ) )
this.node.appendChild(globals.document.createTextNode(text))

return this

}

// Get length of text element
export function length () {

return this.node.getComputedTextLength()

}

+ 25
- 57
src/modules/optional/arrange.js View File

@@ -3,141 +3,109 @@ import { registerMethods } from '../../utils/methods.js'

// Get all siblings, including myself
export function siblings () {

return this.parent().children()

}

// Get the curent position siblings
export function position () {

return this.parent().index( this )

return this.parent().index(this)
}

// Get the next element (will return null if there is none)
export function next () {

return this.siblings()[this.position() + 1]

}

// Get the next element (will return null if there is none)
export function prev () {

return this.siblings()[this.position() - 1]

}

// Send given element one step forward
export function forward () {

var i = this.position() + 1
var p = this.parent()

// move node one step forward
p.removeElement( this ).add( this, i )
p.removeElement(this).add(this, i)

// make sure defs node is always at the top
if ( typeof p.isRoot === 'function' && p.isRoot() ) {

p.node.appendChild( p.defs().node )

if (typeof p.isRoot === 'function' && p.isRoot()) {
p.node.appendChild(p.defs().node)
}

return this

}

// Send given element one step backward
export function backward () {

var i = this.position()

if ( i > 0 ) {

this.parent().removeElement( this ).add( this, i - 1 )

if (i > 0) {
this.parent().removeElement(this).add(this, i - 1)
}

return this

}

// Send given element all the way to the front
export function front () {

var p = this.parent()

// Move node forward
p.node.appendChild( this.node )
p.node.appendChild(this.node)

// Make sure defs node is always at the top
if ( typeof p.isRoot === 'function' && p.isRoot() ) {

p.node.appendChild( p.defs().node )

if (typeof p.isRoot === 'function' && p.isRoot()) {
p.node.appendChild(p.defs().node)
}

return this

}

// Send given element all the way to the back
export function back () {

if ( this.position() > 0 ) {

this.parent().removeElement( this ).add( this, 0 )

if (this.position() > 0) {
this.parent().removeElement(this).add(this, 0)
}

return this

}

// Inserts a given element before the targeted element
export function before ( element ) {

element = makeInstance( element )
export function before (element) {
element = makeInstance(element)
element.remove()

var i = this.position()

this.parent().add( element, i )
this.parent().add(element, i)

return this

}

// Inserts a given element after the targeted element
export function after ( element ) {

element = makeInstance( element )
export function after (element) {
element = makeInstance(element)
element.remove()

var i = this.position()

this.parent().add( element, i + 1 )
this.parent().add(element, i + 1)

return this

}

export function insertBefore ( element ) {

element = makeInstance( element )
element.before( this )

export function insertBefore (element) {
element = makeInstance(element)
element.before(this)
}

export function insertAfter ( element ) {

element = makeInstance( element )
element.after( this )

export function insertAfter (element) {
element = makeInstance(element)
element.after(this)
}

registerMethods( 'Dom', {
registerMethods('Dom', {
siblings, position, next, prev, forward, backward, front, back, before, after
} )
})

+ 16
- 32
src/modules/optional/class.js View File

@@ -3,58 +3,42 @@ import { registerMethods } from '../../utils/methods.js'

// Return array of classes on the node
export function classes () {

var attr = this.attr( 'class' )
return attr == null ? [] : attr.trim().split( delimiter )

var attr = this.attr('class')
return attr == null ? [] : attr.trim().split(delimiter)
}

// Return true if class exists on the node, false otherwise
export function hasClass ( name ) {

return this.classes().indexOf( name ) !== -1

export function hasClass (name) {
return this.classes().indexOf(name) !== -1
}

// Add class to the node
export function addClass ( name ) {

if ( !this.hasClass( name ) ) {

export function addClass (name) {
if (!this.hasClass(name)) {
var array = this.classes()
array.push( name )
this.attr( 'class', array.join( ' ' ) )

array.push(name)
this.attr('class', array.join(' '))
}

return this

}

// Remove class from the node
export function removeClass ( name ) {

if ( this.hasClass( name ) ) {

this.attr( 'class', this.classes().filter( function ( c ) {

export function removeClass (name) {
if (this.hasClass(name)) {
this.attr('class', this.classes().filter(function (c) {
return c !== name

} ).join( ' ' ) )

}).join(' '))
}

return this

}

// Toggle the presence of a class on the node
export function toggleClass ( name ) {

return this.hasClass( name ) ? this.removeClass( name ) : this.addClass( name )

export function toggleClass (name) {
return this.hasClass(name) ? this.removeClass(name) : this.addClass(name)
}

registerMethods( 'Dom', {
registerMethods('Dom', {
classes, hasClass, addClass, removeClass, toggleClass
} )
})

+ 26
- 54
src/modules/optional/css.js View File

@@ -3,98 +3,70 @@ import { isBlank } from '../core/regex.js'
import { registerMethods } from '../../utils/methods.js'

// Dynamic style generator
export function css ( style, val ) {

export function css (style, val) {
let ret = {}
if ( arguments.length === 0 ) {

if (arguments.length === 0) {
// get full style as object
this.node.style.cssText.split( /\s*;\s*/ )
.filter( function ( el ) {

this.node.style.cssText.split(/\s*;\s*/)
.filter(function (el) {
return !!el.length

} )
.forEach( function ( el ) {

let t = el.split( /\s*:\s*/ )
})
.forEach(function (el) {
let t = el.split(/\s*:\s*/)
ret[t[0]] = t[1]

} )
})
return ret

}

if ( arguments.length < 2 ) {

if (arguments.length < 2) {
// get style properties in the array
if ( Array.isArray( style ) ) {

for ( let name of style ) {

let cased = camelCase( name )
if (Array.isArray(style)) {
for (let name of style) {
let cased = camelCase(name)
ret[cased] = this.node.style[cased]

}
return ret

}

// get style for property
if ( typeof style === 'string' ) {

return this.node.style[camelCase( style )]

if (typeof style === 'string') {
return this.node.style[camelCase(style)]
}

// set styles in object
if ( typeof style === 'object' ) {

for ( let name in style ) {

if (typeof style === 'object') {
for (let name in style) {
// set empty string if null/undefined/'' was given
this.node.style[camelCase( name )]
= ( style[name] == null || isBlank.test( style[name] ) ) ? '' : style[name]

this.node.style[camelCase(name)]
= (style[name] == null || isBlank.test(style[name])) ? '' : style[name]
}

}

}

// set style for property
if ( arguments.length === 2 ) {

this.node.style[camelCase( style )]
= ( val == null || isBlank.test( val ) ) ? '' : val

if (arguments.length === 2) {
this.node.style[camelCase(style)]
= (val == null || isBlank.test(val)) ? '' : val
}

return this

}

// Show element
export function show () {

return this.css( 'display', '' )

return this.css('display', '')
}

// Hide element
export function hide () {

return this.css( 'display', 'none' )

return this.css('display', 'none')
}

// Is element visible?
export function visible () {

return this.css( 'display' ) !== 'none'

return this.css('display') !== 'none'
}

registerMethods( 'Dom', {
registerMethods('Dom', {
css, show, hide, visible
} )
})

+ 11
- 25
src/modules/optional/data.js View File

@@ -1,40 +1,26 @@
import { registerMethods } from '../../utils/methods.js'

// Store data values on svg nodes
export function data ( a, v, r ) {

if ( typeof a === 'object' ) {

for ( v in a ) {

this.data( v, a[v] )

export function data (a, v, r) {
if (typeof a === 'object') {
for (v in a) {
this.data(v, a[v])
}

} else if ( arguments.length < 2 ) {

} else if (arguments.length < 2) {
try {

return JSON.parse( this.attr( 'data-' + a ) )

} catch ( e ) {

return this.attr( 'data-' + a )

return JSON.parse(this.attr('data-' + a))
} catch (e) {
return this.attr('data-' + a)
}

} else {

this.attr( 'data-' + a,
this.attr('data-' + a,
v === null ? null
: r === true || typeof v === 'string' || typeof v === 'number' ? v
: JSON.stringify( v )
: JSON.stringify(v)
)

}

return this

}

registerMethods( 'Dom', { data } )
registerMethods('Dom', { data })

+ 9
- 29
src/modules/optional/memory.js View File

@@ -1,60 +1,40 @@
import { registerMethods } from '../../utils/methods.js'

// Remember arbitrary data
export function remember ( k, v ) {

export function remember (k, v) {
// remember every item in an object individually
if ( typeof arguments[0] === 'object' ) {

for ( var key in k ) {

this.remember( key, k[key] )

if (typeof arguments[0] === 'object') {
for (var key in k) {
this.remember(key, k[key])
}

} else if ( arguments.length === 1 ) {

} else if (arguments.length === 1) {
// retrieve memory
return this.memory()[k]

} else {

// store memory
this.memory()[k] = v

}

return this

}

// Erase a given memory
export function forget () {

if ( arguments.length === 0 ) {

if (arguments.length === 0) {
this._memory = {}

} else {

for ( var i = arguments.length - 1; i >= 0; i-- ) {

for (var i = arguments.length - 1; i >= 0; i--) {
delete this.memory()[arguments[i]]

}

}
return this

}

// This triggers creation of a new hidden class which is not performant
// However, this function is not rarely used so it will not happen frequently
// Return local memory object
export function memory () {

return ( this._memory = this._memory || {} )

return (this._memory = this._memory || {})
}

registerMethods( 'Dom', { remember, forget, memory } )
registerMethods('Dom', { remember, forget, memory })

+ 83
- 149
src/modules/optional/sugar.js View File

@@ -10,225 +10,167 @@ import SVGNumber from '../../types/SVGNumber.js'
var sugar = {
stroke: [ 'color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset' ],
fill: [ 'color', 'opacity', 'rule' ],
prefix: function ( t, a ) {

prefix: function (t, a) {
return a === 'color' ? t : t + '-' + a

}
}

// Add sugar for fill and stroke
;[ 'fill', 'stroke' ].forEach( function ( m ) {

;[ 'fill', 'stroke' ].forEach(function (m) {
var extension = {}
var i

extension[m] = function ( o ) {

if ( typeof o === 'undefined' ) {

return this.attr( m )

extension[m] = function (o) {
if (typeof o === 'undefined') {
return this.attr(m)
}
if ( typeof o === 'string' || Color.isRgb( o ) || ( o instanceof Element ) ) {

this.attr( m, o )

if (typeof o === 'string' || Color.isRgb(o) || (o instanceof Element)) {
this.attr(m, o)
} else {

// set all attributes from sugar.fill and sugar.stroke list
for ( i = sugar[m].length - 1; i >= 0; i-- ) {

if ( o[sugar[m][i]] != null ) {

this.attr( sugar.prefix( m, sugar[m][i] ), o[sugar[m][i]] )

for (i = sugar[m].length - 1; i >= 0; i--) {
if (o[sugar[m][i]] != null) {
this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]])
}

}

}

return this

}

registerMethods( [ 'Shape', 'Runner' ], extension )
registerMethods([ 'Shape', 'Runner' ], extension)
})

} )

registerMethods( [ 'Element', 'Runner' ], {
registerMethods([ 'Element', 'Runner' ], {
// Let the user set the matrix directly
matrix: function ( mat, b, c, d, e, f ) {

matrix: function (mat, b, c, d, e, f) {
// Act as a getter
if ( mat == null ) {

return new Matrix( this )

if (mat == null) {
return new Matrix(this)
}

// Act as a setter, the user can pass a matrix or a set of numbers
return this.attr( 'transform', new Matrix( mat, b, c, d, e, f ) )

return this.attr('transform', new Matrix(mat, b, c, d, e, f))
},

// Map rotation to transform
rotate: function ( angle, cx, cy ) {

return this.transform( { rotate: angle, ox: cx, oy: cy }, true )

rotate: function (angle, cx, cy) {
return this.transform({ rotate: angle, ox: cx, oy: cy }, true)
},

// Map skew to transform
skew: function ( x, y, cx, cy ) {

skew: function (x, y, cx, cy) {
return arguments.length === 1 || arguments.length === 3
? this.transform( { skew: x, ox: y, oy: cx }, true )
: this.transform( { skew: [ x, y ], ox: cx, oy: cy }, true )

? this.transform({ skew: x, ox: y, oy: cx }, true)
: this.transform({ skew: [ x, y ], ox: cx, oy: cy }, true)
},

shear: function ( lam, cx, cy ) {

return this.transform( { shear: lam, ox: cx, oy: cy }, true )

shear: function (lam, cx, cy) {
return this.transform({ shear: lam, ox: cx, oy: cy }, true)
},

// Map scale to transform
scale: function ( x, y, cx, cy ) {

scale: function (x, y, cx, cy) {
return arguments.length === 1 || arguments.length === 3
? this.transform( { scale: x, ox: y, oy: cx }, true )
: this.transform( { scale: [ x, y ], ox: cx, oy: cy }, true )

? this.transform({ scale: x, ox: y, oy: cx }, true)
: this.transform({ scale: [ x, y ], ox: cx, oy: cy }, true)
},

// Map translate to transform
translate: function ( x, y ) {

return this.transform( { translate: [ x, y ] }, true )

translate: function (x, y) {
return this.transform({ translate: [ x, y ] }, true)
},

// Map relative translations to transform
relative: function ( x, y ) {

return this.transform( { relative: [ x, y ] }, true )

relative: function (x, y) {
return this.transform({ relative: [ x, y ] }, true)
},

// Map flip to transform
flip: function ( direction, around ) {

flip: function (direction, around) {
var directionString = typeof direction === 'string' ? direction
: isFinite( direction ) ? 'both'
: isFinite(direction) ? 'both'
: 'both'
var origin = ( direction === 'both' && isFinite( around ) ) ? [ around, around ]
: ( direction === 'x' ) ? [ around, 0 ]
: ( direction === 'y' ) ? [ 0, around ]
: isFinite( direction ) ? [ direction, direction ]
var origin = (direction === 'both' && isFinite(around)) ? [ around, around ]
: (direction === 'x') ? [ around, 0 ]
: (direction === 'y') ? [ 0, around ]
: isFinite(direction) ? [ direction, direction ]
: [ 0, 0 ]
this.transform( { flip: directionString, origin: origin }, true )

this.transform({ flip: directionString, origin: origin }, true)
},

// Opacity
opacity: function ( value ) {

return this.attr( 'opacity', value )

opacity: function (value) {
return this.attr('opacity', value)
},

// Relative move over x and y axes
dmove: function ( x, y ) {

return this.dx( x ).dy( y )

dmove: function (x, y) {
return this.dx(x).dy(y)
}
} )
})

registerMethods( 'Element', {
registerMethods('Element', {
// Relative move over x axis
dx: function ( x ) {

return this.x( new SVGNumber( x ).plus( this.x() ) )

dx: function (x) {
return this.x(new SVGNumber(x).plus(this.x()))
},

// Relative move over y axis
dy: function ( y ) {

return this.y( new SVGNumber( y ).plus( this.y() ) )

dy: function (y) {
return this.y(new SVGNumber(y).plus(this.y()))
}
} )
})

registerMethods( 'radius', {
registerMethods('radius', {
// Add x and y radius
radius: function ( x, y ) {

var type = ( this._element || this ).type
radius: function (x, y) {
var type = (this._element || this).type
return type === 'radialGradient' || type === 'radialGradient'
? this.attr( 'r', new SVGNumber( x ) )
: this.rx( x ).ry( y == null ? x : y )

? this.attr('r', new SVGNumber(x))
: this.rx(x).ry(y == null ? x : y)
}
} )
})

registerMethods( 'Path', {
registerMethods('Path', {
// Get path length
length: function () {

return this.node.getTotalLength()

},
// Get point at length
pointAt: function ( length ) {

return new Point( this.node.getPointAtLength( length ) )

pointAt: function (length) {
return new Point(this.node.getPointAtLength(length))
}
} )
})

registerMethods( [ 'Element', 'Runner' ], {
registerMethods([ 'Element', 'Runner' ], {
// Set font
font: function ( a, v ) {

if ( typeof a === 'object' ) {

for ( v in a ) this.font( v, a[v] )

font: function (a, v) {
if (typeof a === 'object') {
for (v in a) this.font(v, a[v])
}

return a === 'leading'
? this.leading( v )
? this.leading(v)
: a === 'anchor'
? this.attr( 'text-anchor', v )
? this.attr('text-anchor', v)
: a === 'size' || a === 'family' || a === 'weight' || a === 'stretch' || a === 'variant' || a === 'style'
? this.attr( 'font-' + a, v )
: this.attr( a, v )

? this.attr('font-' + a, v)
: this.attr(a, v)
}
} )

registerMethods( 'Text', {
ax ( x ) {

return this.attr( 'x', x )
})

registerMethods('Text', {
ax (x) {
return this.attr('x', x)
},
ay ( y ) {

return this.attr( 'y', y )

ay (y) {
return this.attr('y', y)
},
amove ( x, y ) {

return this.ax( x ).ay( y )

amove (x, y) {
return this.ax(x).ay(y)
}
} )
})

// Add events to elements
const methods = [ 'click',
@@ -244,27 +186,19 @@ const methods = [ 'click',
'touchmove',
'touchleave',
'touchend',
'touchcancel' ].reduce( function ( last, event ) {

'touchcancel' ].reduce(function (last, event) {
// add event to Element
const fn = function ( f ) {

if ( f === null ) {

off( this, event )

const fn = function (f) {
if (f === null) {
off(this, event)
} else {

on( this, event, f )

on(this, event, f)
}
return this

}

last[event] = fn
return last
}, {})

}, {} )

registerMethods( 'Element', methods )
registerMethods('Element', methods)

+ 28
- 50
src/modules/optional/transform.js View File

@@ -5,92 +5,70 @@ import Matrix from '../../types/Matrix.js'

// Reset all transformations
export function untransform () {

return this.attr( 'transform', null )

return this.attr('transform', null)
}

// merge the whole transformation chain into one matrix and returns it
export function matrixify () {

var matrix = ( this.attr( 'transform' ) || '' )
var matrix = (this.attr('transform') || '')
// split transformations
.split( transforms ).slice( 0, -1 ).map( function ( str ) {

.split(transforms).slice(0, -1).map(function (str) {
// generate key => value pairs
var kv = str.trim().split( '(' )
var kv = str.trim().split('(')
return [ kv[0],
kv[1].split( delimiter )
.map( function ( str ) {

return parseFloat( str )

} )
kv[1].split(delimiter)
.map(function (str) {
return parseFloat(str)
})
]

} )
})
.reverse()
// merge every transformation into one matrix
.reduce( function ( matrix, transform ) {

if ( transform[0] === 'matrix' ) {

return matrix.lmultiply( Matrix.fromArray( transform[1] ) )

.reduce(function (matrix, transform) {
if (transform[0] === 'matrix') {
return matrix.lmultiply(Matrix.fromArray(transform[1]))
}
return matrix[transform[0]].apply( matrix, transform[1] )

}, new Matrix() )
return matrix[transform[0]].apply(matrix, transform[1])
}, new Matrix())

return matrix

}

// add an element to another parent without changing the visual representation on the screen
export function toParent ( parent ) {

if ( this === parent ) return this
export function toParent (parent) {
if (this === parent) return this
var ctm = this.screenCTM()
var pCtm = parent.screenCTM().inverse()

this.addTo( parent ).untransform().transform( pCtm.multiply( ctm ) )
this.addTo(parent).untransform().transform(pCtm.multiply(ctm))

return this

}

// same as above with parent equals root-svg
export function toRoot () {

return this.toParent( this.root() )

return this.toParent(this.root())
}

// Add transformations
export function transform ( o, relative ) {

export function transform (o, relative) {
// Act as a getter if no object was passed
if ( o == null || typeof o === 'string' ) {

var decomposed = new Matrix( this ).decompose()
if (o == null || typeof o === 'string') {
var decomposed = new Matrix(this).decompose()
return decomposed[o] || decomposed

}

if ( !Matrix.isMatrixLike( o ) ) {

if (!Matrix.isMatrixLike(o)) {
// Set the origin according to the defined transform
o = { ...o, origin: getOrigin( o, this ) }

o = { ...o, origin: getOrigin(o, this) }
}

// The user can pass a boolean, an Element or an Matrix or nothing
var cleanRelative = relative === true ? this : ( relative || false )
var result = new Matrix( cleanRelative ).transform( o )
return this.attr( 'transform', result )

var cleanRelative = relative === true ? this : (relative || false)
var result = new Matrix(cleanRelative).transform(o)
return this.attr('transform', result)
}

registerMethods( 'Element', {
registerMethods('Element', {
untransform, matrixify, toParent, toRoot, transform
} )
})

+ 3
- 5
src/svg.js View File

@@ -3,13 +3,11 @@ import * as regex from './modules/core/regex.js'
import { makeInstance } from './utils/adopter'

// The main wrapping element
export default function SVG ( element ) {

return makeInstance( element )

export default function SVG (element) {
return makeInstance(element)
}

Object.assign( SVG, svgMembers )
Object.assign(SVG, svgMembers)

SVG.utils = SVG
SVG.regex = regex

+ 11
- 23
src/types/ArrayPolyfill.js View File

@@ -1,10 +1,8 @@
/* eslint no-new-func: "off" */
export const subClassArray = ( function () {

export const subClassArray = (function () {
try {

// try es6 subclassing
return Function( 'name', 'baseClass', '_constructor', [
return Function('name', 'baseClass', '_constructor', [
'baseClass = baseClass || Array',
'return {',
' [name]: class extends baseClass {',
@@ -14,35 +12,25 @@ export const subClassArray = ( function () {
' }',
' }',
'}[name]'
].join( '\n' ) )

} catch ( e ) {

].join('\n'))
} catch (e) {
// Use es5 approach
return ( name, baseClass = Array, _constructor ) => {

return (name, baseClass = Array, _constructor) => {
const Arr = function () {

baseClass.apply( this, arguments )
_constructor && _constructor.apply( this, arguments )

baseClass.apply(this, arguments)
_constructor && _constructor.apply(this, arguments)
}

Arr.prototype = Object.create( baseClass.prototype )
Arr.prototype = Object.create(baseClass.prototype)
Arr.prototype.constructor = Arr

Arr.prototype.map = function ( fn ) {

Arr.prototype.map = function (fn) {
const arr = new Arr()
arr.push.apply( arr, Array.prototype.map.call( this, fn ) )
arr.push.apply(arr, Array.prototype.map.call(this, fn))
return arr

}

return Arr

}

}

} )()
})()

+ 48
- 94
src/types/Box.js View File

@@ -4,44 +4,32 @@ import { globals } from '../utils/window.js'
import Point from './Point.js'
import parser from '../modules/core/parser.js'

function isNulledBox ( box ) {

function isNulledBox (box) {
return !box.w && !box.h && !box.x && !box.y

}

function domContains ( node ) {

return ( globals.document.documentElement.contains || function ( node ) {

function domContains (node) {
return (globals.document.documentElement.contains || function (node) {
// This is IE - it does not support contains() for top-level SVGs
while ( node.parentNode ) {

while (node.parentNode) {
node = node.parentNode

}
return node === document

} ).call( globals.document.documentElement, node )

}).call(globals.document.documentElement, node)
}

export default class Box {

constructor ( ...args ) {

this.init( ...args )

constructor (...args) {
this.init(...args)
}

init ( source ) {

init (source) {
var base = [ 0, 0, 0, 0 ]
source = typeof source === 'string' ? source.split( delimiter ).map( parseFloat )
: Array.isArray( source ) ? source
source = typeof source === 'string' ? source.split(delimiter).map(parseFloat)
: Array.isArray(source) ? source
: typeof source === 'object' ? [ source.left != null ? source.left
: source.x, source.top != null ? source.top : source.y, source.width, source.height ]
: arguments.length === 4 ? [].slice.call( arguments )
: arguments.length === 4 ? [].slice.call(arguments)
: base

this.x = source[0] || 0
@@ -56,139 +44,105 @@ export default class Box {
this.cy = this.y + this.h / 2

return this

}

// Merge rect box with another, return a new instance
merge ( box ) {

let x = Math.min( this.x, box.x )
let y = Math.min( this.y, box.y )
let width = Math.max( this.x + this.width, box.x + box.width ) - x
let height = Math.max( this.y + this.height, box.y + box.height ) - y

return new Box( x, y, width, height )
merge (box) {
let x = Math.min(this.x, box.x)
let y = Math.min(this.y, box.y)
let width = Math.max(this.x + this.width, box.x + box.width) - x
let height = Math.max(this.y + this.height, box.y + box.height) - y

return new Box(x, y, width, height)
}

transform ( m ) {

transform (m) {
let xMin = Infinity
let xMax = -Infinity
let yMin = Infinity
let yMax = -Infinity

let pts = [
new Point( this.x, this.y ),
new Point( this.x2, this.y ),
new Point( this.x, this.y2 ),
new Point( this.x2, this.y2 )
new Point(this.x, this.y),
new Point(this.x2, this.y),
new Point(this.x, this.y2),
new Point(this.x2, this.y2)
]

pts.forEach( function ( p ) {

p = p.transform( m )
xMin = Math.min( xMin, p.x )
xMax = Math.max( xMax, p.x )
yMin = Math.min( yMin, p.y )
yMax = Math.max( yMax, p.y )

} )
pts.forEach(function (p) {
p = p.transform(m)
xMin = Math.min(xMin, p.x)
xMax = Math.max(xMax, p.x)
yMin = Math.min(yMin, p.y)
yMax = Math.max(yMax, p.y)
})

return new Box(
xMin, yMin,
xMax - xMin,
yMax - yMin
)

}

addOffset () {

// offset by window scroll position, because getBoundingClientRect changes when window is scrolled
this.x += globals.window.pageXOffset
this.y += globals.window.pageYOffset
return this

}

toString () {

return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height

}

toArray () {

return [ this.x, this.y, this.width, this.height ]

}

isNulled () {

return isNulledBox( this )

return isNulledBox(this)
}

}

function getBox ( cb ) {

function getBox (cb) {
let box

try {
box = cb(this.node)

box = cb( this.node )

if ( isNulledBox( box ) && !domContains( this.node ) ) {

throw new Error( 'Element not in the dom' )

if (isNulledBox(box) && !domContains(this.node)) {
throw new Error('Element not in the dom')
}

} catch ( e ) {

} catch (e) {
try {

let clone = this.clone().addTo( parser().svg ).show()
box = cb( clone.node )
let clone = this.clone().addTo(parser().svg).show()
box = cb(clone.node)
clone.remove()

} catch ( e ) {

throw new Error( 'Getting a bounding box of element "' + this.node.nodeName + '" is not possible' )

} catch (e) {
throw new Error('Getting a bounding box of element "' + this.node.nodeName + '" is not possible')
}

}
return box

}

export function bbox () {

return new Box( getBox.call( this, ( node ) => node.getBBox() ) )

return new Box(getBox.call(this, (node) => node.getBBox()))
}

export function rbox ( el ) {

let box = new Box( getBox.call( this, ( node ) => node.getBoundingClientRect() ) )
if ( el ) return box.transform( el.screenCTM().inverse() )
export function rbox (el) {
let box = new Box(getBox.call(this, (node) => node.getBoundingClientRect()))
if (el) return box.transform(el.screenCTM().inverse())
return box.addOffset()

}

registerMethods( {
registerMethods({
viewbox: {
viewbox ( x, y, width, height ) {

viewbox (x, y, width, height) {
// act as getter
if ( x == null ) return new Box( this.attr( 'viewBox' ) )
if (x == null) return new Box(this.attr('viewBox'))

// act as setter
return this.attr( 'viewBox', new Box( x, y, width, height ) )

return this.attr('viewBox', new Box(x, y, width, height))
}
}
} )
})

+ 14
- 36
src/types/EventTarget.js View File

@@ -2,79 +2,57 @@ import { dispatch, off, on } from '../modules/core/event.js'
import Base from './Base.js'

export default class EventTarget extends Base {

constructor ( { events = {} } = {} ) {

constructor ({ events = {} } = {}) {
super()
this.events = events

}

addEventListener () {}

dispatch ( event, data ) {

return dispatch( this, event, data )

dispatch (event, data) {
return dispatch(this, event, data)
}

dispatchEvent ( event ) {

dispatchEvent (event) {
const bag = this.getEventHolder().events
if ( !bag ) return true
if (!bag) return true

const events = bag[event.type]

for ( let i in events ) {

for ( let j in events[i] ) {

events[i][j]( event )

for (let i in events) {
for (let j in events[i]) {
events[i][j](event)
}

}

return !event.defaultPrevented

}

// Fire given event
fire ( event, data ) {

this.dispatch( event, data )
fire (event, data) {
this.dispatch(event, data)
return this

}

getEventHolder () {

return this

}

getEventTarget () {

return this

}

// Unbind event from listener
off ( event, listener ) {

off( this, event, listener )
off (event, listener) {
off(this, event, listener)
return this

}

// Bind given event to listener
on ( event, listener, binding, options ) {

on( this, event, listener, binding, options )
on (event, listener, binding, options) {
on(this, event, listener, binding, options)
return this

}

removeEventListener () {}

}

+ 21
- 41
src/types/List.js View File

@@ -1,62 +1,42 @@
import { extend } from '../utils/adopter.js'
import { subClassArray } from './ArrayPolyfill.js'

const List = subClassArray( 'List', Array, function ( arr = [] ) {

const List = subClassArray('List', Array, function (arr = []) {
// This catches the case, that native map tries to create an array with new Array(1)
if ( typeof arr === 'number' ) return this
if (typeof arr === 'number') return this
this.length = 0
this.push( ...arr )

} )
this.push(...arr)
})

export default List

extend( List, {
each ( fnOrMethodName, ...args ) {

if ( typeof fnOrMethodName === 'function' ) {

this.forEach( ( el ) => {

fnOrMethodName.call( el, el )

} )

extend(List, {
each (fnOrMethodName, ...args) {
if (typeof fnOrMethodName === 'function') {
this.forEach((el) => {
fnOrMethodName.call(el, el)
})
} else {

return this.map( el => {

return el[fnOrMethodName]( ...args )

} )

return this.map(el => {
return el[fnOrMethodName](...args)
})
}

return this

},

toArray () {

return Array.prototype.concat.apply( [], this )

return Array.prototype.concat.apply([], this)
}
} )

List.extend = function ( methods ) {

methods = methods.reduce( ( obj, name ) => {

obj[name] = function ( ...attrs ) {

return this.each( name, ...attrs )
})

List.extend = function (methods) {
methods = methods.reduce((obj, name) => {
obj[name] = function (...attrs) {
return this.each(name, ...attrs)
}
return obj
}, {})

}, {} )

extend( List, methods )

extend(List, methods)
}

+ 131
- 227
src/types/Matrix.js View File

@@ -3,32 +3,26 @@ import { radians } from '../utils/utils.js'
import Element from '../elements/Element.js'
import Point from './Point.js'

function closeEnough ( a, b, threshold ) {

return Math.abs( b - a ) < ( threshold || 1e-6 )

function closeEnough (a, b, threshold) {
return Math.abs(b - a) < (threshold || 1e-6)
}

export default class Matrix {

constructor ( ...args ) {

this.init( ...args )

constructor (...args) {
this.init(...args)
}

// Initialize
init ( source ) {

var base = Matrix.fromArray( [ 1, 0, 0, 1, 0, 0 ] )
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 ) )
: 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
@@ -40,68 +34,56 @@ export default class Matrix {
this.f = source.f != null ? source.f : base.f

return this

}

// Clones this matrix
clone () {

return new Matrix( this )

return new Matrix(this)
}

// Transform a matrix into another matrix by manipulating the space
transform ( o ) {

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 )

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 t = Matrix.formatTransforms(o)
var current = this
let { x: ox, y: oy } = new Point( t.ox, t.oy ).transform( current )
let { x: ox, y: oy } = new Point(t.ox, t.oy).transform(current)

// Construct the resulting matrix
var transformer = new Matrix()
.translateO( t.rx, t.ry )
.lmultiplyO( current )
.translateO( -ox, -oy )
.scaleO( t.scaleX, t.scaleY )
.skewO( t.skewX, t.skewY )
.shearO( t.shear )
.rotateO( t.theta )
.translateO( ox, oy )
.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 )
if (isFinite(t.px) || isFinite(t.py)) {
const origin = new Point(ox, oy).transform(transformer)
// TODO: Replace t.px with isFinite(t.px)
const dx = t.px ? t.px - origin.x : 0
const dy = t.py ? t.py - origin.y : 0
transformer.translateO( dx, dy )

transformer.translateO(dx, dy)
}

// Translate now after positioning
transformer.translateO( t.tx, t.ty )
transformer.translateO(t.tx, t.ty)
return transformer

}

// Applies a matrix defined by its affine parameters
compose ( o ) {

if ( o.origin ) {

compose (o) {
if (o.origin) {
o.originX = o.origin[0]
o.originY = o.origin[1]

}
// Get the parameters
var ox = o.originX || 0
@@ -115,20 +97,18 @@ export default class Matrix {

// Apply the standard matrix
var result = new Matrix()
.translateO( -ox, -oy )
.scaleO( sx, sy )
.shearO( lam )
.rotateO( theta )
.translateO( tx, ty )
.lmultiplyO( this )
.translateO( ox, oy )
.translateO(-ox, -oy)
.scaleO(sx, sy)
.shearO(lam)
.rotateO(theta)
.translateO(tx, ty)
.lmultiplyO(this)
.translateO(ox, oy)
return result

}

// Decomposes this matrix into its affine parameters
decompose ( cx = 0, cy = 0 ) {

decompose (cx = 0, cy = 0) {
// Get the parameters from the matrix
var a = this.a
var b = this.b
@@ -143,20 +123,20 @@ export default class Matrix {

// Since we only shear in x, we can use the x basis to get the x scale
// and the rotation of the resulting matrix
var sx = ccw * Math.sqrt( a * a + b * b )
var thetaRad = Math.atan2( ccw * b, ccw * a )
var sx = ccw * Math.sqrt(a * a + b * b)
var thetaRad = Math.atan2(ccw * b, ccw * a)
var theta = 180 / Math.PI * thetaRad
var ct = Math.cos( thetaRad )
var st = Math.sin( thetaRad )
var ct = Math.cos(thetaRad)
var st = Math.sin(thetaRad)

// We can then solve the y basis vector simultaneously to get the other
// two affine parameters directly from these parameters
var lam = ( a * c + b * d ) / determinant
var sy = ( ( c * sx ) / ( lam * a - b ) ) || ( ( d * sx ) / ( lam * b + a ) )
var lam = (a * c + b * d) / determinant
var sy = ((c * sx) / (lam * a - b)) || ((d * sx) / (lam * b + a))

// Use the translations
let tx = e - cx + cx * ct * sx + cy * ( lam * ct * sx - st * sy )
let ty = f - cy + cx * st * sx + cy * ( lam * st * sx + ct * sy )
let tx = e - cx + cx * ct * sx + cy * (lam * ct * sx - st * sy)
let ty = f - cy + cx * st * sx + cy * (lam * st * sx + ct * sy)

// Construct the decomposition and return it
return {
@@ -178,48 +158,38 @@ export default class Matrix {
e: this.e,
f: this.f
}

}

// Left multiplies by the given matrix
multiply ( matrix ) {

return this.clone().multiplyO( matrix )

multiply (matrix) {
return this.clone().multiplyO(matrix)
}

multiplyO ( matrix ) {

multiplyO (matrix) {
// Get the matrices
var l = this
var r = matrix instanceof Matrix
? matrix
: new Matrix( matrix )

return Matrix.matrixMultiply( l, r, this )
: new Matrix(matrix)

return Matrix.matrixMultiply(l, r, this)
}

lmultiply ( matrix ) {

return this.clone().lmultiplyO( matrix )

lmultiply (matrix) {
return this.clone().lmultiplyO(matrix)
}

lmultiplyO ( matrix ) {

lmultiplyO (matrix) {
var r = this
var l = matrix instanceof Matrix
? matrix
: new Matrix( matrix )

return Matrix.matrixMultiply( l, r, this )
: new Matrix(matrix)

return Matrix.matrixMultiply(l, r, this)
}

// Inverses matrix
inverseO () {

// Get the current parameters out of the matrix
var a = this.a
var b = this.b
@@ -230,7 +200,7 @@ export default class Matrix {

// Invert the 2x2 matrix in the top left
var det = a * d - b * c
if ( !det ) throw new Error( 'Cannot invert ' + this )
if (!det) throw new Error('Cannot invert ' + this)

// Calculate the top 2x2 matrix
var na = d / det
@@ -239,8 +209,8 @@ export default class Matrix {
var nd = a / det

// Apply the inverted matrix to the top right
var ne = -( na * e + nc * f )
var nf = -( nb * e + nd * f )
var ne = -(na * e + nc * f)
var nf = -(nb * e + nd * f)

// Construct the inverted matrix
this.a = na
@@ -251,46 +221,34 @@ export default class Matrix {
this.f = nf

return this

}

inverse () {

return this.clone().inverseO()

}

// Translate matrix
translate ( x, y ) {

return this.clone().translateO( x, y )

translate (x, y) {
return this.clone().translateO(x, y)
}

translateO ( x, y ) {

translateO (x, y) {
this.e += x || 0
this.f += y || 0
return this

}

// Scale matrix
scale ( x, y, cx, cy ) {

return this.clone().scaleO( ...arguments )

scale (x, y, cx, cy) {
return this.clone().scaleO(...arguments)
}

scaleO ( x, y = x, cx = 0, cy = 0 ) {

scaleO (x, y = x, cx = 0, cy = 0) {
// Support uniform scaling
if ( arguments.length === 3 ) {

if (arguments.length === 3) {
cy = cx
cx = y
y = x

}

let { a, b, c, d, e, f } = this
@@ -303,23 +261,19 @@ export default class Matrix {
this.f = f * y - cy * y + cy

return this

}

// Rotate matrix
rotate ( r, cx, cy ) {

return this.clone().rotateO( r, cx, cy )

rotate (r, cx, cy) {
return this.clone().rotateO(r, cx, cy)
}

rotateO ( r, cx = 0, cy = 0 ) {

rotateO (r, cx = 0, cy = 0) {
// Convert degrees to radians
r = radians( r )
r = radians(r)

let cos = Math.cos( r )
let sin = Math.sin( r )
let cos = Math.cos(r)
let sin = Math.sin(r)

let { a, b, c, d, e, f } = this

@@ -331,33 +285,25 @@ export default class Matrix {
this.f = f * cos + e * sin - cx * sin - cy * cos + cy

return this

}

// Flip matrix on x or y, at a given offset
flip ( axis, around ) {

return this.clone().flipO( axis, around )

flip (axis, around) {
return this.clone().flipO(axis, around)
}

flipO ( axis, around ) {

return axis === 'x' ? this.scaleO( -1, 1, around, 0 )
: axis === 'y' ? this.scaleO( 1, -1, 0, around )
: this.scaleO( -1, -1, axis, around || axis ) // Define an x, y flip point

flipO (axis, around) {
return axis === 'x' ? this.scaleO(-1, 1, around, 0)
: axis === 'y' ? this.scaleO(1, -1, 0, around)
: this.scaleO(-1, -1, axis, around || axis) // Define an x, y flip point
}

// Shear matrix
shear ( a, cx, cy ) {

return this.clone().shearO( a, cx, cy )

shear (a, cx, cy) {
return this.clone().shearO(a, cx, cy)
}

shearO ( lx, cx = 0, cy = 0 ) {

shearO (lx, cx = 0, cy = 0) {
let { a, b, c, d, e, f } = this

this.a = a + b * lx
@@ -365,33 +311,27 @@ export default class Matrix {
this.e = e + f * lx - cy * lx

return this

}

// Skew Matrix
skew ( x, y, cx, cy ) {

return this.clone().skewO( ...arguments )

skew (x, y, cx, cy) {
return this.clone().skewO(...arguments)
}

skewO ( x, y = x, cx = 0, cy = 0 ) {

skewO (x, y = x, cx = 0, cy = 0) {
// support uniformal skew
if ( arguments.length === 3 ) {

if (arguments.length === 3) {
cy = cx
cx = y
y = x

}

// Convert degrees to radians
x = radians( x )
y = radians( y )
x = radians(x)
y = radians(y)

let lx = Math.tan( x )
let ly = Math.tan( y )
let lx = Math.tan(x)
let ly = Math.tan(y)

let { a, b, c, d, e, f } = this

@@ -403,75 +343,55 @@ export default class Matrix {
this.f = f + e * ly - cx * ly

return this

}

// SkewX
skewX ( x, cx, cy ) {

return this.skew( x, 0, cx, cy )

skewX (x, cx, cy) {
return this.skew(x, 0, cx, cy)
}

skewXO ( x, cx, cy ) {

return this.skewO( x, 0, cx, cy )

skewXO (x, cx, cy) {
return this.skewO(x, 0, cx, cy)
}

// SkewY
skewY ( y, cx, cy ) {

return this.skew( 0, y, cx, cy )

skewY (y, cx, cy) {
return this.skew(0, y, cx, cy)
}

skewYO ( y, cx, cy ) {

return this.skewO( 0, y, cx, cy )

skewYO (y, cx, cy) {
return this.skewO(0, y, cx, cy)
}

// Transform around a center point
aroundO ( cx, cy, matrix ) {

aroundO (cx, cy, matrix) {
var dx = cx || 0
var dy = cy || 0
return this.translateO( -dx, -dy ).lmultiplyO( matrix ).translateO( dx, dy )

return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy)
}

around ( cx, cy, matrix ) {

return this.clone().aroundO( cx, cy, matrix )

around (cx, cy, matrix) {
return this.clone().aroundO(cx, cy, matrix)
}

// Check if two matrices are equal
equals ( other ) {

var comp = new Matrix( other )
return closeEnough( this.a, comp.a ) && closeEnough( this.b, comp.b )
&& closeEnough( this.c, comp.c ) && closeEnough( this.d, comp.d )
&& closeEnough( this.e, comp.e ) && closeEnough( this.f, comp.f )

equals (other) {
var comp = new Matrix(other)
return closeEnough(this.a, comp.a) && closeEnough(this.b, comp.b)
&& closeEnough(this.c, comp.c) && closeEnough(this.d, comp.d)
&& closeEnough(this.e, comp.e) && closeEnough(this.f, comp.f)
}

// Convert matrix to string
toString () {

return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')'

}

toArray () {

return [ this.a, this.b, this.c, this.d, this.e, this.f ]

}

valueOf () {

return {
a: this.a,
b: this.b,
@@ -480,17 +400,13 @@ export default class Matrix {
e: this.e,
f: this.f
}

}

static fromArray ( a ) {

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 ) {

static isMatrixLike (o) {
return (
o.a != null
|| o.b != null
@@ -499,43 +415,41 @@ export default class Matrix {
|| o.e != null
|| o.f != null
)

}

static formatTransforms ( o ) {

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 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
: 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
: 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
: 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
: 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 origin = new Point(o.origin || o.around || o.ox || o.originX, o.oy || o.originY)
var ox = origin.x
var oy = origin.y
var position = new Point( o.position || o.px || o.positionX, o.py || o.positionY )
var position = new Point(o.position || o.px || o.positionX, o.py || o.positionY)
var px = position.x
var py = position.y
var translate = new Point( o.translate || o.tx || o.translateX, o.ty || o.translateY )
var 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 relative = new Point(o.relative || o.rx || o.relativeX, o.ry || o.relativeY)
var rx = relative.x
var ry = relative.y

@@ -543,12 +457,10 @@ export default class Matrix {
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 ) {

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
@@ -566,31 +478,23 @@ export default class Matrix {
o.f = f

return o

}

}

export function ctm () {

return new Matrix( this.node.getCTM() )

return new Matrix(this.node.getCTM())
}

export function screenCTM () {

/* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537
This is needed because FF does not return the transformation matrix
for the inner coordinate system when getScreenCTM() is called on nested svgs.
However all other Browsers do that */
if ( typeof this.isRoot === 'function' && !this.isRoot() ) {

var rect = this.rect( 1, 1 )
if (typeof this.isRoot === 'function' && !this.isRoot()) {
var rect = this.rect(1, 1)
var m = rect.node.getScreenCTM()
rect.remove()
return new Matrix( m )

return new Matrix(m)
}
return new Matrix( this.node.getScreenCTM() )

return new Matrix(this.node.getScreenCTM())
}

+ 67
- 161
src/types/Morphable.js View File

@@ -11,200 +11,135 @@ import SVGArray from './SVGArray.js'
import SVGNumber from './SVGNumber.js'

export default class Morphable {

constructor ( stepper ) {

this._stepper = stepper || new Ease( '-' )
constructor (stepper) {
this._stepper = stepper || new Ease('-')

this._from = null
this._to = null
this._type = null
this._context = null
this._morphObj = null

}

from ( val ) {

if ( val == null ) {

from (val) {
if (val == null) {
return this._from

}

this._from = this._set( val )
this._from = this._set(val)
return this

}

to ( val ) {

if ( val == null ) {

to (val) {
if (val == null) {
return this._to

}

this._to = this._set( val )
this._to = this._set(val)
return this

}

type ( type ) {

type (type) {
// getter
if ( type == null ) {

if (type == null) {
return this._type

}

// setter
this._type = type
return this

}

_set ( value ) {

if ( !this._type ) {

_set (value) {
if (!this._type) {
var type = typeof value

if ( type === 'number' ) {

this.type( SVGNumber )

} else if ( type === 'string' ) {

if ( Color.isColor( value ) ) {

this.type( Color )

} else if ( delimiter.test( value ) ) {

this.type( pathLetters.test( value )
if (type === 'number') {
this.type(SVGNumber)
} else if (type === 'string') {
if (Color.isColor(value)) {
this.type(Color)
} else if (delimiter.test(value)) {
this.type(pathLetters.test(value)
? PathArray
: SVGArray
)

} else if ( numberAndUnit.test( value ) ) {

this.type( SVGNumber )

} else if (numberAndUnit.test(value)) {
this.type(SVGNumber)
} else {

this.type( NonMorphable )

this.type(NonMorphable)
}

} else if ( morphableTypes.indexOf( value.constructor ) > -1 ) {

this.type( value.constructor )

} else if ( Array.isArray( value ) ) {

this.type( SVGArray )

} else if ( type === 'object' ) {

this.type( ObjectBag )

} else if (morphableTypes.indexOf(value.constructor) > -1) {
this.type(value.constructor)
} else if (Array.isArray(value)) {
this.type(SVGArray)
} else if (type === 'object') {
this.type(ObjectBag)
} else {

this.type( NonMorphable )

this.type(NonMorphable)
}

}

var result = ( new this._type( value ) ).toArray()
var result = (new this._type(value)).toArray()
this._morphObj = this._morphObj || new this._type()
this._context = this._context
|| Array.apply( null, Array( result.length ) ).map( Object )
|| Array.apply(null, Array(result.length)).map(Object)
return result

}

stepper ( stepper ) {

if ( stepper == null ) return this._stepper
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 ) {

.map(this._stepper.done)
.reduce(function (last, curr) {
return last && curr

}, true )
}, true)
return complete

}

at ( pos ) {

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 )

} )
this._from.map(function (i, index) {
return _this._stepper.step(i, _this._to[index], pos, _this._context[index], _this._context)
})
)

}

}

export class NonMorphable {

constructor ( ...args ) {

this.init( ...args )

constructor (...args) {
this.init(...args)
}

init ( val ) {

val = Array.isArray( val ) ? val[0] : val
init (val) {
val = Array.isArray(val) ? val[0] : val
this.value = val
return this

}

valueOf () {

return this.value

}

toArray () {

return [ this.value ]

}

}

export class TransformBag {

constructor ( ...args ) {

this.init( ...args )

constructor (...args) {
this.init(...args)
}

init ( obj ) {

if ( Array.isArray( obj ) ) {

init (obj) {
if (Array.isArray(obj)) {
obj = {
scaleX: obj[0],
scaleY: obj[1],
@@ -215,16 +150,13 @@ export class TransformBag {
originX: obj[6],
originY: obj[7]
}

}

Object.assign( this, TransformBag.defaults, obj )
Object.assign(this, TransformBag.defaults, obj)
return this

}

toArray () {

var v = this

return [
@@ -237,9 +169,7 @@ export class TransformBag {
v.originX,
v.originY
]

}

}

TransformBag.defaults = {
@@ -254,56 +184,40 @@ TransformBag.defaults = {
}

export class ObjectBag {

constructor ( ...args ) {

this.init( ...args )

constructor (...args) {
this.init(...args)
}

init ( objOrArr ) {

init (objOrArr) {
this.values = []

if ( Array.isArray( objOrArr ) ) {

if (Array.isArray(objOrArr)) {
this.values = objOrArr
return

}

var entries = Object.entries( objOrArr || {} ).sort( ( a, b ) => {

var entries = Object.entries(objOrArr || {}).sort((a, b) => {
return a[0] - b[0]
})

} )

this.values = entries.reduce( ( last, curr ) => last.concat( curr ), [] )
this.values = entries.reduce((last, curr) => last.concat(curr), [])
return this

}

valueOf () {

var obj = {}
var arr = this.values

for ( var i = 0, len = arr.length; i < len; i += 2 ) {

for (var i = 0, len = arr.length; i < len; i += 2) {
obj[arr[i]] = arr[i + 1]

}

return obj

}

toArray () {

return this.values

}

}

const morphableTypes = [
@@ -312,29 +226,21 @@ const morphableTypes = [
ObjectBag
]

export function registerMorphableType ( type = [] ) {

morphableTypes.push( ...[].concat( type ) )

export function registerMorphableType (type = []) {
morphableTypes.push(...[].concat(type))
}

export function makeMorphable () {

extend( morphableTypes, {
to ( val ) {

extend(morphableTypes, {
to (val) {
return new Morphable()
.type( this.constructor )
.from( this.valueOf() )
.to( val )

.type(this.constructor)
.from(this.valueOf())
.to(val)
},
fromArray ( arr ) {

this.init( arr )
fromArray (arr) {
this.init(arr)
return this

}
} )

})
}

+ 97
- 217
src/types/PathArray.js View File

@@ -12,182 +12,131 @@ import Point from './Point.js'
import SVGArray from './SVGArray.js'
import parser from '../modules/core/parser.js'

const PathArray = subClassArray( 'PathArray', SVGArray )
const PathArray = subClassArray('PathArray', SVGArray)

export default PathArray

export function pathRegReplace ( a, b, c, d ) {

return c + d.replace( dots, ' .' )

export function pathRegReplace (a, b, c, d) {
return c + d.replace(dots, ' .')
}

function arrayToString ( a ) {

for ( var i = 0, il = a.length, s = ''; i < il; i++ ) {

function arrayToString (a) {
for (var i = 0, il = a.length, s = ''; i < il; i++) {
s += a[i][0]

if ( a[i][1] != null ) {

if (a[i][1] != null) {
s += a[i][1]

if ( a[i][2] != null ) {

if (a[i][2] != null) {
s += ' '
s += a[i][2]

if ( a[i][3] != null ) {

if (a[i][3] != null) {
s += ' '
s += a[i][3]
s += ' '
s += a[i][4]

if ( a[i][5] != null ) {

if (a[i][5] != null) {
s += ' '
s += a[i][5]
s += ' '
s += a[i][6]

if ( a[i][7] != null ) {

if (a[i][7] != null) {
s += ' '
s += a[i][7]

}

}

}

}

}

}

return s + ' '

}

const pathHandlers = {
M: function ( c, p, p0 ) {

M: function (c, p, p0) {
p.x = p0.x = c[0]
p.y = p0.y = c[1]

return [ 'M', p.x, p.y ]

},
L: function ( c, p ) {

L: function (c, p) {
p.x = c[0]
p.y = c[1]
return [ 'L', c[0], c[1] ]

},
H: function ( c, p ) {

H: function (c, p) {
p.x = c[0]
return [ 'H', c[0] ]

},
V: function ( c, p ) {

V: function (c, p) {
p.y = c[0]
return [ 'V', c[0] ]

},
C: function ( c, p ) {

C: function (c, p) {
p.x = c[4]
p.y = c[5]
return [ 'C', c[0], c[1], c[2], c[3], c[4], c[5] ]

},
S: function ( c, p ) {

S: function (c, p) {
p.x = c[2]
p.y = c[3]
return [ 'S', c[0], c[1], c[2], c[3] ]

},
Q: function ( c, p ) {

Q: function (c, p) {
p.x = c[2]
p.y = c[3]
return [ 'Q', c[0], c[1], c[2], c[3] ]

},
T: function ( c, p ) {

T: function (c, p) {
p.x = c[0]
p.y = c[1]
return [ 'T', c[0], c[1] ]

},
Z: function ( c, p, p0 ) {

Z: function (c, p, p0) {
p.x = p0.x
p.y = p0.y
return [ 'Z' ]

},
A: function ( c, p ) {

A: function (c, p) {
p.x = c[5]
p.y = c[6]
return [ 'A', c[0], c[1], c[2], c[3], c[4], c[5], c[6] ]

}
}

let mlhvqtcsaz = 'mlhvqtcsaz'.split( '' )

for ( var i = 0, il = mlhvqtcsaz.length; i < il; ++i ) {

pathHandlers[mlhvqtcsaz[i]] = ( function ( i ) {

return function ( c, p, p0 ) {

if ( i === 'H' ) c[0] = c[0] + p.x
else if ( i === 'V' ) c[0] = c[0] + p.y
else if ( i === 'A' ) {
let mlhvqtcsaz = 'mlhvqtcsaz'.split('')

for (var i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
pathHandlers[mlhvqtcsaz[i]] = (function (i) {
return function (c, p, p0) {
if (i === 'H') c[0] = c[0] + p.x
else if (i === 'V') c[0] = c[0] + p.y
else if (i === 'A') {
c[5] = c[5] + p.x
c[6] = c[6] + p.y

} else {

for ( var j = 0, jl = c.length; j < jl; ++j ) {

c[j] = c[j] + ( j % 2 ? p.y : p.x )

for (var j = 0, jl = c.length; j < jl; ++j) {
c[j] = c[j] + (j % 2 ? p.y : p.x)
}

}

return pathHandlers[i]( c, p, p0 )

return pathHandlers[i](c, p, p0)
}

} )( mlhvqtcsaz[i].toUpperCase() )

})(mlhvqtcsaz[i].toUpperCase())
}

extend( PathArray, {
extend(PathArray, {
// Convert array to string
toString () {

return arrayToString( this )

return arrayToString(this)
},

// Move path string
move ( x, y ) {

move (x, y) {
// get bounding box of current situation
var box = this.bbox()

@@ -195,154 +144,110 @@ extend( PathArray, {
x -= box.x
y -= box.y

if ( !isNaN( x ) && !isNaN( y ) ) {

if (!isNaN(x) && !isNaN(y)) {
// move every point
for ( var l, i = this.length - 1; i >= 0; i-- ) {

for (var l, i = this.length - 1; i >= 0; i--) {
l = this[i][0]

if ( l === 'M' || l === 'L' || l === 'T' ) {

if (l === 'M' || l === 'L' || l === 'T') {
this[i][1] += x
this[i][2] += y

} else if ( l === 'H' ) {

} else if (l === 'H') {
this[i][1] += x

} else if ( l === 'V' ) {

} else if (l === 'V') {
this[i][1] += y

} else if ( l === 'C' || l === 'S' || l === 'Q' ) {

} else if (l === 'C' || l === 'S' || l === 'Q') {
this[i][1] += x
this[i][2] += y
this[i][3] += x
this[i][4] += y

if ( l === 'C' ) {

if (l === 'C') {
this[i][5] += x
this[i][6] += y

}

} else if ( l === 'A' ) {

} else if (l === 'A') {
this[i][6] += x
this[i][7] += y

}

}

}

return this

},

// Resize path string
size ( width, height ) {

size (width, height) {
// get bounding box of current situation
var box = this.bbox()
var i, l

// recalculate position of all points according to new size
for ( i = this.length - 1; i >= 0; i-- ) {

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

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' ) {

} else if (l === 'A') {
// resize radii
this[i][1] = ( this[i][1] * width ) / box.width
this[i][2] = ( this[i][2] * height ) / box.height
this[i][1] = (this[i][1] * width) / box.width
this[i][2] = (this[i][2] * height) / box.height

// move position values
this[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

this[i][6] = ((this[i][6] - box.x) * width) / box.width + box.x
this[i][7] = ((this[i][7] - box.y) * height) / box.height + box.y
}

}

return this

},

// Test if the passed path array use the same path data commands as this path array
equalCommands ( pathArray ) {

equalCommands (pathArray) {
var i, il, equalCommands

pathArray = new PathArray( pathArray )
pathArray = new PathArray(pathArray)

equalCommands = this.length === pathArray.length
for ( i = 0, il = this.length; equalCommands && i < il; i++ ) {

for (i = 0, il = this.length; equalCommands && i < il; i++) {
equalCommands = this[i][0] === pathArray[i][0]

}

return equalCommands

},

// Make path array morphable
morph ( pathArray ) {

pathArray = new PathArray( pathArray )

if ( this.equalCommands( pathArray ) ) {
morph (pathArray) {
pathArray = new PathArray(pathArray)

if (this.equalCommands(pathArray)) {
this.destination = pathArray

} else {

this.destination = null

}

return this

},

// Get morphed path array at given position
at ( pos ) {

at (pos) {
// make sure a destination is defined
if ( !this.destination ) return this
if (!this.destination) return this

var sourceArray = this
var destinationArray = this.destination.value
@@ -352,61 +257,47 @@ extend( PathArray, {

// Animate has specified in the SVG spec
// See: https://www.w3.org/TR/SVG11/paths.html#PathElement
for ( i = 0, il = sourceArray.length; i < il; i++ ) {

for (i = 0, il = sourceArray.length; i < il; i++) {
array[i] = [ sourceArray[i][0] ]
for ( j = 1, jl = sourceArray[i].length; j < jl; j++ ) {

array[i][j] = sourceArray[i][j] + ( destinationArray[i][j] - sourceArray[i][j] ) * pos

for (j = 1, jl = sourceArray[i].length; j < jl; j++) {
array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos
}
// For the two flags of the elliptical arc command, the SVG spec say:
// Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true
// Elliptical arc command as an array followed by corresponding indexes:
// ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
// 0 1 2 3 4 5 6 7
if ( array[i][0] === 'A' ) {

array[i][4] = +( array[i][4] !== 0 )
array[i][5] = +( array[i][5] !== 0 )

if (array[i][0] === 'A') {
array[i][4] = +(array[i][4] !== 0)
array[i][5] = +(array[i][5] !== 0)
}

}

// Directly modify the value of a path array, this is done this way for performance
pathArray.value = array
return pathArray

},

// Absolutize and parse path to array
parse ( array = [ [ 'M', 0, 0 ] ] ) {

parse (array = [ [ 'M', 0, 0 ] ]) {
// if it's already a patharray, no need to parse it
if ( array instanceof PathArray ) return array
if (array instanceof PathArray) return array

// prepare for parsing
var s
var paramCnt = { 'M': 2, 'L': 2, 'H': 1, 'V': 1, 'C': 6, 'S': 4, 'Q': 4, 'T': 2, 'A': 7, 'Z': 0 }

if ( typeof array === 'string' ) {

if (typeof array === 'string') {
array = array
.replace( numbersWithDots, pathRegReplace ) // convert 45.123.123 to 45.123 .123
.replace( pathLetters, ' $& ' ) // put some room between letters and numbers
.replace( hyphen, '$1 -' ) // add space before hyphen
.replace(numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123
.replace(pathLetters, ' $& ') // put some room between letters and numbers
.replace(hyphen, '$1 -') // add space before hyphen
.trim() // trim
.split( delimiter ) // split into array

.split(delimiter) // split into array
} else {

array = array.reduce( function ( prev, curr ) {

return [].concat.call( prev, curr )

}, [] )

array = array.reduce(function (prev, curr) {
return [].concat.call(prev, curr)
}, [])
}

// array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...]
@@ -417,41 +308,30 @@ extend( PathArray, {
var len = array.length

do {

// Test if we have a path letter
if ( isPathLetter.test( array[index] ) ) {

if (isPathLetter.test(array[index])) {
s = array[index]
++index
// If last letter was a move command and we got no new, it defaults to [L]ine

} else if ( s === 'M' ) {

} else if (s === 'M') {
s = 'L'

} else if ( s === 'm' ) {

} else if (s === 'm') {
s = 'l'

}

result.push( pathHandlers[s].call( null,
array.slice( index, ( index = index + paramCnt[s.toUpperCase()] ) ).map( parseFloat ),
result.push(pathHandlers[s].call(null,
array.slice(index, (index = index + paramCnt[s.toUpperCase()])).map(parseFloat),
p, p0
)
)

} while ( len > index )
} while (len > index)

return result

},

// Get bounding box of path
bbox () {

parser().path.setAttribute( 'd', this.toString() )
parser().path.setAttribute('d', this.toString())
return parser.nodes.path.getBBox()

}
} )
})

+ 9
- 23
src/types/Point.js View File

@@ -1,19 +1,15 @@
export default class Point {

// Initialize
constructor ( ...args ) {

this.init( ...args )

constructor (...args) {
this.init(...args)
}

init ( x, y ) {

init (x, y) {
let source
let base = { x: 0, y: 0 }

// ensure source as object
source = Array.isArray( x ) ? { x: x[0], y: x[1] }
source = Array.isArray(x) ? { x: x[0], y: x[1] }
: typeof x === 'object' ? { x: x.x, y: x.y }
: { x: x, y: y }

@@ -22,38 +18,28 @@ export default class Point {
this.y = source.y == null ? base.y : source.y

return this

}

// Clone point
clone () {

return new Point( this )

return new Point(this)
}

// transform point with matrix
transform ( m ) {

transform (m) {
// Perform the matrix multiplication
var x = m.a * this.x + m.c * this.y + m.e
var y = m.b * this.x + m.d * this.y + m.f

// Return the required point
return new Point( x, y )

return new Point(x, y)
}

toArray () {

return [ this.x, this.y ]

}

}

export function point ( x, y ) {

return new Point( x, y ).transform( this.screenCTM().inverse() )

export function point (x, y) {
return new Point(x, y).transform(this.screenCTM().inverse())
}

+ 34
- 68
src/types/PointArray.js View File

@@ -3,97 +3,76 @@ import { extend } from '../utils/adopter.js'
import { subClassArray } from './ArrayPolyfill.js'
import SVGArray from './SVGArray.js'

const PointArray = subClassArray( 'PointArray', SVGArray )
const PointArray = subClassArray('PointArray', SVGArray)

export default PointArray

extend( PointArray, {
extend(PointArray, {
// Convert array to string
toString () {

// convert to a poly point string
for ( var i = 0, il = this.length, array = []; i < il; i++ ) {

array.push( this[i].join( ',' ) )

for (var i = 0, il = this.length, array = []; i < il; i++) {
array.push(this[i].join(','))
}

return array.join( ' ' )

return array.join(' ')
},

// Convert array to line object
toLine () {

return {
x1: this[0][0],
y1: this[0][1],
x2: this[1][0],
y2: this[1][1]
}

},

// Get morphed array at given position
at ( pos ) {

at (pos) {
// make sure a destination is defined
if ( !this.destination ) return this
if (!this.destination) return this

// generate morphed point string
for ( var i = 0, il = this.length, array = []; i < il; i++ ) {

array.push( [
this[i][0] + ( this.destination[i][0] - this[i][0] ) * pos,
this[i][1] + ( this.destination[i][1] - this[i][1] ) * pos
] )

for (var i = 0, il = this.length, array = []; i < il; i++) {
array.push([
this[i][0] + (this.destination[i][0] - this[i][0]) * pos,
this[i][1] + (this.destination[i][1] - this[i][1]) * pos
])
}

return new PointArray( array )

return new PointArray(array)
},

// Parse point string and flat array
parse ( array = [ [ 0, 0 ] ] ) {

parse (array = [ [ 0, 0 ] ]) {
var points = []

// if it is an array
if ( array instanceof Array ) {

if (array instanceof Array) {
// and it is not flat, there is no need to parse it
if ( array[0] instanceof Array ) {

if (array[0] instanceof Array) {
return array

}

} else { // Else, it is considered as a string

// parse points
array = array.trim().split( delimiter ).map( parseFloat )

array = array.trim().split(delimiter).map(parseFloat)
}

// validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints
// Odd number of coordinates is an error. In such cases, drop the last odd coordinate.
if ( array.length % 2 !== 0 ) array.pop()
if (array.length % 2 !== 0) array.pop()

// wrap points in two-tuples and parse points as floats
for ( var i = 0, len = array.length; i < len; i = i + 2 ) {

points.push( [ array[i], array[i + 1] ] )

for (var i = 0, len = array.length; i < len; i = i + 2) {
points.push([ array[i], array[i + 1] ])
}

return points

},

// Move point string
move ( x, y ) {

move (x, y) {
var box = this.bbox()

// get relative offset
@@ -101,54 +80,41 @@ extend( PointArray, {
y -= box.y

// move every point
if ( !isNaN( x ) && !isNaN( y ) ) {

for ( var i = this.length - 1; i >= 0; i-- ) {

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 ) {

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

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 )

} )
this.forEach(function (el) {
maxX = Math.max(el[0], maxX)
maxY = Math.max(el[1], maxY)
minX = Math.min(el[0], minX)
minY = Math.min(el[1], minY)
})
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }

}
} )
})

+ 16
- 32
src/types/SVGArray.js View File

@@ -2,65 +2,49 @@ import { delimiter } from '../modules/core/regex.js'
import { extend } from '../utils/adopter.js'
import { subClassArray } from './ArrayPolyfill.js'

const SVGArray = subClassArray( 'SVGArray', Array, function ( arr ) {

this.init( arr )

} )
const SVGArray = subClassArray('SVGArray', Array, function (arr) {
this.init(arr)
})

export default SVGArray

extend( SVGArray, {
init ( arr ) {

extend(SVGArray, {
init (arr) {
// This catches the case, that native map tries to create an array with new Array(1)
if ( typeof arr === 'number' ) return this
if (typeof arr === 'number') return this
this.length = 0
this.push( ...this.parse( arr ) )
this.push(...this.parse(arr))
return this

},

toArray () {

return Array.prototype.concat.apply( [], this )

return Array.prototype.concat.apply([], this)
},

toString () {

return this.join( ' ' )

return this.join(' ')
},

// Flattens the array if needed
valueOf () {

const ret = []
ret.push( ...this )
ret.push(...this)
return ret

},

// Parse whitespace separated string
parse ( array = [] ) {

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 )
if (array instanceof Array) return array

return array.trim().split(delimiter).map(parseFloat)
},

clone () {

return new this.constructor( this )

return new this.constructor(this)
},

toSet () {

return new Set( this )

return new Set(this)
}
} )
})

+ 27
- 63
src/types/SVGNumber.js View File

@@ -2,126 +2,90 @@ import { numberAndUnit } from '../modules/core/regex.js'

// Module for unit convertions
export default class SVGNumber {

// Initialize
constructor ( ...args ) {

this.init( ...args )

constructor (...args) {
this.init(...args)
}

init ( value, unit ) {

unit = Array.isArray( value ) ? value[1] : unit
value = Array.isArray( value ) ? value[0] : value
init (value, unit) {
unit = Array.isArray(value) ? value[1] : unit
value = Array.isArray(value) ? value[0] : value

// initialize defaults
this.value = 0
this.unit = unit || ''

// parse value
if ( typeof value === 'number' ) {

if (typeof value === 'number') {
// ensure a valid numeric value
this.value = isNaN( value ) ? 0 : !isFinite( value ) ? ( value < 0 ? -3.4e+38 : +3.4e+38 ) : value

} else if ( typeof value === 'string' ) {

unit = value.match( numberAndUnit )

if ( unit ) {
this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value
} else if (typeof value === 'string') {
unit = value.match(numberAndUnit)

if (unit) {
// make value numeric
this.value = parseFloat( unit[1] )
this.value = parseFloat(unit[1])

// normalize
if ( unit[5] === '%' ) {

if (unit[5] === '%') {
this.value /= 100

} else if ( unit[5] === 's' ) {

} else if (unit[5] === 's') {
this.value *= 1000

}

// store unit
this.unit = unit[5]

}

} else {

if ( value instanceof SVGNumber ) {

if (value instanceof SVGNumber) {
this.value = value.valueOf()
this.unit = value.unit

}

}

return this

}

toString () {

return ( this.unit === '%' ? ~~( this.value * 1e8 ) / 1e6
return (this.unit === '%' ? ~~(this.value * 1e8) / 1e6
: this.unit === 's' ? this.value / 1e3
: this.value
) + this.unit

}

toJSON () {

return this.toString()

}

toArray () {

return [ this.value, this.unit ]

}

valueOf () {

return this.value

}

// Add number
plus ( number ) {

number = new SVGNumber( number )
return new SVGNumber( this + number, this.unit || number.unit )

plus (number) {
number = new SVGNumber(number)
return new SVGNumber(this + number, this.unit || number.unit)
}

// Subtract number
minus ( number ) {

number = new SVGNumber( number )
return new SVGNumber( this - number, this.unit || number.unit )

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 )

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 )

divide (number) {
number = new SVGNumber(number)
return new SVGNumber(this / number, this.unit || number.unit)
}

}

+ 51
- 105
src/utils/adopter.js View File

@@ -5,190 +5,136 @@ import { globals } from '../utils/window.js'
import Base from '../types/Base.js'

const elements = {}
export const root = Symbol( 'root' )
export const root = Symbol('root')

// Method for element creation
export function makeNode ( name ) {

export function makeNode (name) {
// create element
return globals.document.createElementNS( ns, name )

return globals.document.createElementNS(ns, name)
}

export function makeInstance ( element ) {

if ( element instanceof Base ) return element

if ( typeof element === 'object' ) {

return adopt( element )
export function makeInstance (element) {
if (element instanceof Base) return element

if (typeof element === 'object') {
return adopt(element)
}

if ( element == null ) {

if (element == null) {
return new elements[root]()

}

if ( typeof element === 'string' && element.charAt( 0 ) !== '<' ) {

return adopt( globals.document.querySelector( element ) )

if (typeof element === 'string' && element.charAt(0) !== '<') {
return adopt(globals.document.querySelector(element))
}

var node = makeNode( 'svg' )
var node = makeNode('svg')
node.innerHTML = element

// We can use firstChild here because we know,
// that the first char is < and thus an element
element = adopt( node.firstChild )
element = adopt(node.firstChild)

return element

}

export function nodeOrNew ( name, node ) {

return node instanceof globals.window.Node ? node : makeNode( name )

export function nodeOrNew (name, node) {
return node instanceof globals.window.Node ? node : makeNode(name)
}

// Adopt existing svg elements
export function adopt ( node ) {

export function adopt (node) {
// check for presence of node
if ( !node ) return null
if (!node) return null

// make sure a node isn't already adopted
if ( node.instance instanceof Base ) return node.instance

if ( !( node instanceof globals.window.SVGElement ) ) {

return new elements.HtmlNode( node )
if (node.instance instanceof Base) return node.instance

if (!(node instanceof globals.window.SVGElement)) {
return new elements.HtmlNode(node)
}

// initialize variables
var element

// adopt with element-specific settings
if ( node.nodeName === 'svg' ) {

element = new elements[root]( node )

} else if ( node.nodeName === 'linearGradient' || node.nodeName === 'radialGradient' ) {

element = new elements.Gradient( node )

} else if ( elements[capitalize( node.nodeName )] ) {

element = new elements[capitalize( node.nodeName )]( node )

if (node.nodeName === 'svg') {
element = new elements[root](node)
} else if (node.nodeName === 'linearGradient' || node.nodeName === 'radialGradient') {
element = new elements.Gradient(node)
} else if (elements[capitalize(node.nodeName)]) {
element = new elements[capitalize(node.nodeName)](node)
} else {

element = new elements.Bare( node )

element = new elements.Bare(node)
}

return element

}

export function register ( element, name = element.name, asRoot = false ) {

export function register (element, name = element.name, asRoot = false) {
elements[name] = element
if ( asRoot ) elements[root] = element
if (asRoot) elements[root] = element

addMethodNames( Object.keys( element.prototype ) )
addMethodNames(Object.keys(element.prototype))

return element

}

export function getClass ( name ) {

export function getClass (name) {
return elements[name]

}

// Element id sequence
let did = 1000

// Get next named element id
export function eid ( name ) {

return 'Svgjs' + capitalize( name ) + ( did++ )

export function eid (name) {
return 'Svgjs' + capitalize(name) + (did++)
}

// Deep new id assignment
export function assignNewId ( node ) {

export function assignNewId (node) {
// do the same for SVG child nodes as well
for ( var i = node.children.length - 1; i >= 0; i-- ) {

assignNewId( node.children[i] )

for (var i = node.children.length - 1; i >= 0; i--) {
assignNewId(node.children[i])
}

if ( node.id ) {

return adopt( node ).id( eid( node.nodeName ) )

if (node.id) {
return adopt(node).id(eid(node.nodeName))
}

return adopt( node )

return adopt(node)
}

// Method for extending objects
export function extend ( modules, methods, attrCheck ) {

export function extend (modules, methods, attrCheck) {
var key, i

modules = Array.isArray( modules ) ? modules : [ modules ]

for ( i = modules.length - 1; i >= 0; i-- ) {

for ( key in methods ) {
modules = Array.isArray(modules) ? modules : [ modules ]

for (i = modules.length - 1; i >= 0; i--) {
for (key in methods) {
let method = methods[key]
if ( attrCheck ) {

method = wrapWithAttrCheck( methods[key] )

if (attrCheck) {
method = wrapWithAttrCheck(methods[key])
}
modules[i].prototype[key] = method

}

}

}

export function extendWithAttrCheck ( ...args ) {

extend( ...args, true )

export function extendWithAttrCheck (...args) {
extend(...args, true)
}

export function wrapWithAttrCheck ( fn ) {

return function ( ...args ) {

export function wrapWithAttrCheck (fn) {
return function (...args) {
let o = args[args.length - 1]

if ( o && o.constructor === Object && !( o instanceof Array ) ) {

return fn.apply( this, args.slice( 0, -1 ) ).attr( o )

if (o && o.constructor === Object && !(o instanceof Array)) {
return fn.apply(this, args.slice(0, -1)).attr(o)
} else {

return fn.apply( this, args )

return fn.apply(this, args)
}

}

}

+ 15
- 35
src/utils/methods.js View File

@@ -2,61 +2,41 @@ const methods = {}
const constructors = {}
const names = []

export function registerMethods ( name, m ) {

if ( Array.isArray( name ) ) {

for ( let _name of name ) {

registerMethods( _name, m )

export function registerMethods (name, m) {
if (Array.isArray(name)) {
for (let _name of name) {
registerMethods(_name, m)
}
return

}

if ( typeof name === 'object' ) {

for ( let [ _name, _m ] of Object.entries( name ) ) {

registerMethods( _name, _m )

if (typeof name === 'object') {
for (let [ _name, _m ] of Object.entries(name)) {
registerMethods(_name, _m)
}
return

}

addMethodNames( Object.keys( m ) )
methods[name] = Object.assign( methods[name] || {}, m )

addMethodNames(Object.keys(m))
methods[name] = Object.assign(methods[name] || {}, m)
}

export function getMethodsFor ( name ) {

export function getMethodsFor (name) {
return methods[name] || {}

}

export function getMethodNames () {

return [ ...new Set( names ) ]

return [ ...new Set(names) ]
}

export function addMethodNames ( _names ) {

names.push( ..._names )

export function addMethodNames (_names) {
names.push(..._names)
}

export function registerConstructor ( name, setup ) {

export function registerConstructor (name, setup) {
constructors[name] = setup

}

export function getConstructor ( name ) {

export function getConstructor (name) {
return constructors[name] ? { setup: constructors[name], name } : {}

}

+ 28
- 66
src/utils/utils.js View File

@@ -1,143 +1,105 @@
// Map function
export function map ( array, block ) {

export function map (array, block) {
var i
var il = array.length
var result = []

for ( i = 0; i < il; i++ ) {

result.push( block( array[i] ) )

for (i = 0; i < il; i++) {
result.push(block(array[i]))
}

return result

}

// Filter function
export function filter ( array, block ) {

export function filter (array, block) {
var i
var il = array.length
var result = []

for ( i = 0; i < il; i++ ) {

if ( block( array[i] ) ) {

result.push( array[i] )

for (i = 0; i < il; i++) {
if (block(array[i])) {
result.push(array[i])
}

}

return result

}

// Degrees to radians
export function radians ( d ) {

export function radians (d) {
return d % 360 * Math.PI / 180

}

// Radians to degrees
export function degrees ( r ) {

export function degrees (r) {
return r * 180 / Math.PI % 360

}

// Convert dash-separated-string to camelCase
export function camelCase ( s ) {

return s.toLowerCase().replace( /-(.)/g, function ( m, g ) {

export function camelCase (s) {
return s.toLowerCase().replace(/-(.)/g, function (m, g) {
return g.toUpperCase()

} )

})
}

// Convert camel cased string to string seperated
export function unCamelCase ( s ) {

return s.replace( /([A-Z])/g, function ( m, g ) {

export function unCamelCase (s) {
return s.replace(/([A-Z])/g, function (m, g) {
return '-' + g.toLowerCase()

} )

})
}

// Capitalize first letter of a string
export function capitalize ( s ) {

return s.charAt( 0 ).toUpperCase() + s.slice( 1 )

export function capitalize (s) {
return s.charAt(0).toUpperCase() + s.slice(1)
}

// Calculate proportional width and height values when necessary
export function proportionalSize ( element, width, height ) {

if ( width == null || height == null ) {

export function proportionalSize (element, width, height) {
if (width == null || height == null) {
var box = element.bbox()

if ( width == null ) {

if (width == null) {
width = box.width / box.height * height

} else if ( height == null ) {

} else if (height == null) {
height = box.height / box.width * width

}

}

return {
width: width,
height: height
}

}

export function getOrigin ( o, element ) {

export function getOrigin (o, element) {
// Allow origin or around as the names
let origin = o.origin // o.around == null ? o.origin : o.around
let ox, oy

// Allow the user to pass a string to rotate around a given point
if ( typeof origin === 'string' || origin == null ) {

if (typeof origin === 'string' || origin == null) {
// Get the bounding box of the element with no transformations applied
const string = ( origin || 'center' ).toLowerCase().trim()
const string = (origin || 'center').toLowerCase().trim()
const { height, width, x, y } = element.bbox()

// Calculate the transformed x and y coordinates
let bx = string.includes( 'left' ) ? x
: string.includes( 'right' ) ? x + width
let bx = string.includes('left') ? x
: string.includes('right') ? x + width
: x + width / 2
let by = string.includes( 'top' ) ? y
: string.includes( 'bottom' ) ? y + height
let by = string.includes('top') ? y
: string.includes('bottom') ? y + height
: y + height / 2

// Set the bounds eg : "bottom-left", "Top right", "middle" etc...
ox = o.ox != null ? o.ox : bx
oy = o.oy != null ? o.oy : by

} else {

ox = origin[0]
oy = origin[1]

}

// Return the origin as it is if it wasn't a string
return [ ox, oy ]

}

+ 1
- 3
src/utils/window.js View File

@@ -3,9 +3,7 @@ export const globals = {
document: typeof document === 'undefined' ? null : document
}

export function registerWindow ( win = null, doc = null ) {

export function registerWindow (win = null, doc = null) {
globals.window = win
globals.document = doc

}

Loading…
Cancel
Save