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 againtags/3.0.0
@@ -7,4 +7,3 @@ rules: | |||
flatTernaryExpressions: true | |||
}] | |||
padded-blocks: off | |||
space-in-parens: [ error, always ] |
@@ -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", |
@@ -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 | |||
} | |||
} | |||
@@ -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') | |||
}) |
@@ -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 | |||
} | |||
} |
@@ -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 | |||
} | |||
} | |||
} ) | |||
}) |
@@ -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) |
@@ -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)) | |||
}) | |||
}) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -3,4 +3,4 @@ import Dom from './Dom.js' | |||
export default class HtmlNode extends Dom {} | |||
register( HtmlNode ) | |||
register(HtmlNode) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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,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) |
@@ -3,4 +3,4 @@ import Element from './Element.js' | |||
export default class Shape extends Element {} | |||
register( Shape ) | |||
register(Shape) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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) |
@@ -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() |
@@ -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 | |||
} |
@@ -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)) | |||
} |
@@ -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 | |||
} |
@@ -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) }) | |||
} |
@@ -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 | |||
} |
@@ -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) | |||
} |
@@ -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)) | |||
} |
@@ -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) | |||
} |
@@ -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() | |||
} |
@@ -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 | |||
} ) | |||
}) |
@@ -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 | |||
} ) | |||
}) |
@@ -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 | |||
} ) | |||
}) |
@@ -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 }) |
@@ -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 }) |
@@ -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) |
@@ -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,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 |
@@ -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 | |||
} | |||
} | |||
} )() | |||
})() |
@@ -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)) | |||
} | |||
} | |||
} ) | |||
}) |
@@ -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 () {} | |||
} |
@@ -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) | |||
} |
@@ -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()) | |||
} |
@@ -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 | |||
} | |||
} ) | |||
}) | |||
} |
@@ -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() | |||
} | |||
} ) | |||
}) |
@@ -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()) | |||
} |
@@ -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 } | |||
} | |||
} ) | |||
}) |
@@ -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) | |||
} | |||
} ) | |||
}) |
@@ -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) | |||
} | |||
} |
@@ -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) | |||
} | |||
} | |||
} |
@@ -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 } : {} | |||
} |
@@ -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 ] | |||
} |
@@ -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 | |||
} |