diff options
author | Saivan <savian@me.com> | 2018-11-25 16:21:53 +1300 |
---|---|---|
committer | Saivan <savian@me.com> | 2018-11-25 16:21:53 +1300 |
commit | 62de7d0a1b994b69032a759b796b486e6bc382e3 (patch) | |
tree | 112b19f2903b4dc5b4cf61ebef0d021c6ca2f14d | |
parent | 2b37d7ba5b4267b39c86f9aba5fb14a1b376e846 (diff) | |
download | svg.js-62de7d0a1b994b69032a759b796b486e6bc382e3.tar.gz svg.js-62de7d0a1b994b69032a759b796b486e6bc382e3.zip |
Changed the esLint rules to avoid silly ternary operators, and to let code breathe!
This commit modifies some of the eslint rules, to allow our code to be a little bit more
readable. This came about because we had a particularly pesky problem, where the code
was indenting ternary operators. This fixes that, and makes it easy to add new rules
to eslint as we please in the future.
Changes
=======
- Rebuilt the library with new eslint rules
- Changed the eslintrc file to a yaml file by default
72 files changed, 4684 insertions, 2107 deletions
diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index e3578aa..0000000 --- a/.eslintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "standard" -} diff --git a/.eslintrc.yaml b/.eslintrc.yaml new file mode 100644 index 0000000..6243075 --- /dev/null +++ b/.eslintrc.yaml @@ -0,0 +1,10 @@ + +extends: standard +rules: + operator-linebreak: [ error, before ] + object-curly-spacing: [ error, always ] + indent: [ error, 2, { + flatTernaryExpressions: true + }] + padded-blocks: off + space-in-parens: [ error, always ] diff --git a/dist/svg.js b/dist/svg.js index 7b311f9..2072971 100644 --- a/dist/svg.js +++ b/dist/svg.js @@ -6,7 +6,7 @@ * @copyright Wout Fierens <wout@mick-wout.com> * @license MIT * -* BUILT: Mon Nov 19 2018 21:40:15 GMT+0100 (GMT+01:00) +* BUILT: Sun Nov 25 2018 16:17:19 GMT+1300 (New Zealand Daylight Time) */; var SVG = (function () { 'use strict'; @@ -112,6 +112,36 @@ 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"); @@ -974,99 +1004,599 @@ var SVG = (function () { return event; } - function fullHex(hex$$1) { + function sixDigitHex(hex$$1) { return hex$$1.length === 4 ? ['#', hex$$1.substring(1, 2), hex$$1.substring(1, 2), hex$$1.substring(2, 3), hex$$1.substring(2, 3), hex$$1.substring(3, 4), hex$$1.substring(3, 4)].join('') : hex$$1; - } // Component to hex value - + } - function compToHex(comp) { - var hex$$1 = comp.toString(16); + function componentHex(component) { + var integer = Math.round(component); + var hex$$1 = integer.toString(16); return hex$$1.length === 1 ? '0' + hex$$1 : hex$$1; } + function is(object, space) { + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = space[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var key = _step.value; + + if (object[key] == null) { + return false; + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return != null) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + return true; + } + + function getParameters(a) { + var params = is(a, 'rgb') ? { + _a: a.r, + _b: a.g, + _c: a.b, + space: 'rgb' + } : is(a, 'xyz') ? { + _a: a.x, + _b: a.y, + _c: a.z, + space: 'xyz' + } : is(a, 'hsl') ? { + _a: a.h, + _b: a.s, + _c: a.l, + space: 'hsl' + } : is(a, 'lab') ? { + _a: a.l, + _b: a.a, + _c: a.b, + space: 'lab' + } : is(a, 'lch') ? { + _a: a.l, + _b: a.c, + _c: a.h, + space: 'lch' + } : is(a, 'cmyk') ? { + _a: a.c, + _b: a.m, + _c: a.y, + _d: a.k, + space: 'cmyk' + } : { + _a: 0, + _b: 0, + _c: 0, + space: 'rgb' + }; + return params; + } + + function cieSpace(space) { + if (space === 'lab' || space === 'xyz' || space === 'lch') { + return true; + } else { + return false; + } + } + + function hueToRgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + } + var Color = /*#__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); - this.init.apply(this, arguments); + // 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]; + + 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 + + + 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); } _createClass(Color, [{ - key: "init", - value: function init(color, g, b) { - var match; // initialize defaults - - this.r = 0; - this.g = 0; - this.b = 0; - if (!color) return; // parse color - - if (typeof color === 'string') { - if (isRgb.test(color)) { - // get rgb values - match = rgb.exec(color.replace(whitespace, '')); // parse numeric values - - this.r = parseInt(match[1]); - this.g = parseInt(match[2]); - this.b = parseInt(match[3]); - } else if (isHex.test(color)) { - // get hex values - match = hex.exec(fullHex(color)); // parse numeric values - - this.r = parseInt(match[1], 16); - this.g = parseInt(match[2], 16); - this.b = parseInt(match[3], 16); - } - } else if (Array.isArray(color)) { - this.r = color[0]; - this.g = color[1]; - this.b = color[2]; - } else if (_typeof(color) === 'object') { - this.r = color.r; - this.g = color.g; - this.b = color.b; - } else if (arguments.length === 3) { - this.r = color; - this.g = g; - this.b = b; + key: "opacity", + value: function opacity() { + var _opacity = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; + + this.opacity = _opacity; + } + /* + */ + + }, { + key: "brightness", + value: function brightness() { + var _this$rgb = this.rgb(), + r = _this$rgb._a, + g = _this$rgb._b, + b = _this$rgb._c; + + var value = r / 255 * 0.30 + g / 255 * 0.59 + b / 255 * 0.11; + return value; + } + /* + Conversion Methods + */ + + }, { + key: "rgb", + value: function rgb$$1() { + if (this.space === 'rgb') { + return this; + } else if (cieSpace(this.space)) { + // Convert to the xyz color space + var x = this.x, + y = this.y, + z = this.z; + + if (this.space === 'lab' || this.space === 'lch') { + // Get the values in the lab space + var l = this.l, + a = this.a, + _b4 = this.b; + + if (this.space === 'lch') { + var c = this.c, + h = this.h; + var dToR = Math.PI / 180; + a = c * Math.cos(dToR * h); + _b4 = c * Math.sin(dToR * h); + } // Undo the nonlinear function + + + var yL = (l + 16) / 116; + var xL = a / 500 + y; + var zL = y - _b4 / 200; // Get the xyz values + + var ct = 16 / 116; + var mx = 0.008856; + var nm = 7.787; + x = 0.95047 * (Math.pow(xL, 3) > mx ? Math.pow(xL, 3) : (xL - ct) / nm); + y = 1.00000 * (Math.pow(yL, 3) > mx ? Math.pow(yL, 3) : (yL - ct) / nm); + z = 1.08883 * (Math.pow(zL, 3) > mx ? Math.pow(zL, 3) : (zL - ct) / nm); + } // Convert xyz to unbounded rgb values + + + var rU = x * 3.2406 + y * -1.5372 + z * -0.4986; + var gU = x * -0.9689 + y * 1.8758 + z * 0.0415; + var bU = x * 0.0557 + y * -0.2040 + z * 1.0570; // Convert the values to true rgb values + + var pow = Math.pow; + var bd = 0.0031308; + var r = rU > bd ? 1.055 * pow(rU, 1 / 2.4) - 0.055 : 12.92 * rU; + 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); + return color; + } else if (this.space === 'hsl') { + // stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion + // 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 + + if (s === 0) { + var _color2 = new Color(_l, _l, _l); + + return _color2; + } // TODO I have no idea what this does :D If you figure it out, tell me! + + + 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 _g = hueToRgb(p, q, _h); + + var _b5 = hueToRgb(p, q, _h - 1 / 3); // Make a new color + + + var _color = new Color(_r, _g, _b5); + + return _color; + } 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 _r2 = 1 - Math.min(1, _c4 * (1 - k) + k); + + var _g2 = 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 _color3 = new Color(_r2, _g2, _b6); + + return _color3; + } else { + return this; } + } + }, { + key: "lab", + value: function lab() { + // Get the xyz color + var _this$xyz = this.xyz(), + x = _this$xyz.x, + y = _this$xyz.y, + z = _this$xyz.z; // Get the lab components - return this; - } // Default to hex conversion + var l = 116 * y - 16; + var a = 500 * (x - y); + var b = 200 * (y - z); // Construct and return a new color + + var color = new Color(l, a, b, 'lab'); + return color; + } + }, { + key: "xyz", + value: function xyz() { + // Normalise the red, green and blue values + var _this$rgb2 = this.rgb(), + r255 = _this$rgb2._a, + g255 = _this$rgb2._b, + b255 = _this$rgb2._c; + + var _map3 = [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 + + + var rL = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92; + var gL = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92; + var bL = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92; // Convert to the xyz color space without bounding the values + + var xU = (rL * 0.4124 + gL * 0.3576 + bL * 0.1805) / 0.95047; + var yU = (rL * 0.2126 + gL * 0.7152 + bL * 0.0722) / 1.00000; + var zU = (rL * 0.0193 + gL * 0.1192 + bL * 0.9505) / 1.08883; // Get the proper xyz values by applying the bounding + + var x = xU > 0.008856 ? Math.pow(xU, 1 / 3) : 7.787 * xU + 16 / 116; + var y = yU > 0.008856 ? Math.pow(yU, 1 / 3) : 7.787 * yU + 16 / 116; + var z = zU > 0.008856 ? Math.pow(zU, 1 / 3) : 7.787 * zU + 16 / 116; // Make and return the color + + var color = new Color(x, y, z, 'xyz'); + return color; + } + }, { + key: "lch", + value: function lch() { + // Get the lab color directly + var _this$lab = this.lab(), + l = _this$lab.l, + a = _this$lab.a, + b = _this$lab.b; // Get the chromaticity and the hue using polar coordinates + + + var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); + var h = 180 * Math.atan2(b, a) / Math.PI; + + if (h < 0) { + h *= -1; + h = 360 - h; + } // Make a new color and return it + + + var color = new Color(l, c, h, 'lch'); + return color; + } + }, { + key: "hsl", + value: function hsl() { + // Get the rgb values + var _this$rgb3 = this.rgb(), + _a = _this$rgb3._a, + _b = _this$rgb3._b, + _c = _this$rgb3._c; + + var _map5 = [_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 + + + var max = Math.max(r, g, b); + var min = Math.min(r, g, b); + var l = (max + min) / 2; // If the r, g, v values are identical then we are grey + + var isGrey = max === min; // Calculate the hue and saturation + + var delta = max - min; + 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'); + return color; + } + }, { + key: "cmyk", + value: function cmyk() { + // Get the rgb values for the current color + var _this$rgb4 = this.rgb(), + _a = _this$rgb4._a, + _b = _this$rgb4._b, + _c = _this$rgb4._c; + + var _map7 = [_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 + + + 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 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 + */ + + }, { + key: "hex", + value: function hex$$1() { + var _this$rgb5 = this.rgb(), + _a = _this$rgb5._a, + _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]; + + return "#".concat(r).concat(g).concat(b); + } }, { key: "toString", value: function toString() { - return this.toHex(); + return this.hex(); + } + }, { + key: "toRgb", + value: function toRgb() { + var _this$rgb6 = this.rgb(), + r = _this$rgb6.r, + g = _this$rgb6.g, + b = _this$rgb6.b; + + var max = Math.max, + min = Math.min, + round = Math.round; + + var format = function format(v) { + 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 string = "rgb(".concat(rV, ",").concat(gV, ",").concat(bV, ")"); + return string; } }, { key: "toArray", value: function toArray() { - return [this.r, this.g, this.b]; - } // Build hex value + var _a = this._a, + _b = this._b, + _c = this._c, + _d = this._d, + space = this.space; + return [_a, _b, _c, _d, space]; + } + }], [{ + key: "fromArray", + value: function fromArray(array) { + var newColor = _construct(Color, _toConsumableArray(array)); - }, { - key: "toHex", - value: function toHex() { - return '#' + compToHex(Math.round(this.r)) + compToHex(Math.round(this.g)) + compToHex(Math.round(this.b)); - } // Build rgb value + return newColor; + } + /* + Generating random colors + */ }, { - key: "toRgb", - value: function toRgb() { - return 'rgb(' + [this.r, this.g, this.b].join() + ')'; - } // Calculate true brightness + key: "random", + value: function random() { + 'sine'; + 'pastel'; + 'vibrant'; + 'dark'; + 'rgb'; + 'lab'; + 'grey'; + } + /* + Constructing colors + */ }, { - key: "brightness", - value: function brightness() { - return this.r / 255 * 0.30 + this.g / 255 * 0.59 + this.b / 255 * 0.11; - } // Testers - // Test if given value is a color string + key: "temperature", + value: function temperature(kelvin) {} // Test if given value is a color string - }], [{ + }, { key: "test", value: function test(color) { color += ''; diff --git a/package-lock.json b/package-lock.json index 3ba3ab7..c5f923e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5268,7 +5268,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, diff --git a/src/animation/Animator.js b/src/animation/Animator.js index 9b460f5..e9d5797 100644 --- a/src/animation/Animator.js +++ b/src/animation/Animator.js @@ -8,78 +8,106 @@ 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) { 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 + } } diff --git a/src/animation/Controller.js b/src/animation/Controller.js index cee7115..9efe87e 100644 --- a/src/animation/Controller.js +++ b/src/animation/Controller.js @@ -7,85 +7,149 @@ 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) { return pos }, - '<>': function (pos) { return -Math.cos(pos * Math.PI) / 2 + 0.5 }, - '>': function (pos) { return Math.sin(pos * Math.PI / 2) }, - '<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 }, - bezier: function (x1, y1, x2, y2) { + '-': function ( pos ) { + + return pos + + }, + '<>': function ( pos ) { + + return -Math.cos( pos * Math.PI ) / 2 + 0.5 + + }, + '>': function ( pos ) { + + return Math.sin( pos * Math.PI / 2 ) + + }, + '<': function ( pos ) { + + return -Math.cos( pos * Math.PI / 2 ) + 1 + + }, + bezier: function ( 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 + let step = Math.floor( t * steps ) + const jumping = ( t * step ) % 1 === 0 + + if ( stepPosition === 'start' || stepPosition === 'both' ) { - 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 } + + done () { + + return false + + } + } /*** @@ -94,17 +158,25 @@ 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 ) + } + } /*** @@ -113,51 +185,65 @@ 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 @@ -165,65 +251,75 @@ 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 newPosition = current + - velocity * dt + - acceleration * dt * dt / 2 + var acceleration = -this.d * velocity - this.k * ( current - target ) + var newPosition = current + + velocity * dt + + acceleration * dt * dt / 2 // Store the velocity c.velocity = velocity + acceleration * dt // Figure out if we have converged, and if so, pass the value - c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002 + 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 + c.done = Math.abs( p ) < 0.001 + + return c.done ? target : current + ( this.P * p + this.I * i + this.D * d ) - 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' ) +} ) diff --git a/src/animation/Queue.js b/src/animation/Queue.js index 14b92b4..b4e2722 100644 --- a/src/animation/Queue.js +++ b/src/animation/Queue.js @@ -1,59 +1,77 @@ 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 + } + } diff --git a/src/animation/Runner.js b/src/animation/Runner.js index 47929fd..d2c7bdf 100644 --- a/src/animation/Runner.js +++ b/src/animation/Runner.js @@ -15,7 +15,9 @@ import SVGNumber from '../types/SVGNumber.js' import Timeline from './Timeline.js' export default class Runner extends EventTarget { - constructor (options) { + + constructor ( options ) { + super() // Store a unique id on the runner, so that we can identify it later @@ -28,7 +30,7 @@ export default class Runner extends EventTarget { // Ensure that we get a controller options = typeof options === 'function' - ? new Controller(options) + ? new Controller( options ) : options // Declare all of the variables @@ -61,6 +63,7 @@ export default class Runner extends EventTarget { this._swing = false this._wait = 0 this._times = 1 + } /* @@ -70,58 +73,75 @@ export default class Runner extends EventTarget { help us make new runners from the current runner */ - element (element) { - if (element == null) return this._element + element ( element ) { + + if ( element == null ) return this._element this._element = element element._prepareRunner() return this + } - timeline (timeline) { + timeline ( timeline ) { + // check explicitly for undefined so we can set the timeline to null - if (typeof timeline === 'undefined') return this._timeline + if ( typeof timeline === 'undefined' ) return this._timeline this._timeline = timeline return this + } - animate (duration, delay, when) { - var o = Runner.sanitise(duration, delay, when) - var runner = new Runner(o.duration) - if (this._timeline) runner.timeline(this._timeline) - if (this._element) runner.element(this._element) - return runner.loop(o).schedule(delay, when) + animate ( duration, delay, when ) { + + var o = Runner.sanitise( duration, delay, when ) + var runner = new Runner( o.duration ) + if ( this._timeline ) runner.timeline( this._timeline ) + if ( this._element ) runner.element( this._element ) + return runner.loop( o ).schedule( delay, when ) + } - schedule (timeline, delay, when) { + schedule ( timeline, delay, when ) { + // The user doesn't need to pass a timeline if we already have one - if (!(timeline instanceof Timeline)) { + if ( !( timeline instanceof Timeline ) ) { + when = delay delay = timeline timeline = this.timeline() + } // If there is no timeline, yell at the user... - if (!timeline) { - throw Error('Runner cannot be scheduled without timeline') + if ( !timeline ) { + + throw Error( 'Runner cannot be scheduled without timeline' ) + } // Schedule the runner on the timeline provided - timeline.schedule(this, delay, when) + timeline.schedule( this, delay, when ) return this + } unschedule () { + var timeline = this.timeline() - timeline && timeline.unschedule(this) + timeline && timeline.unschedule( this ) return this + } - loop (times, swing, wait) { + loop ( times, swing, wait ) { + // Deal with the user passing in an object - if (typeof times === 'object') { + if ( typeof times === 'object' ) { + swing = times.swing wait = times.wait times = times.times + } // Sanitise the values and store them @@ -129,10 +149,13 @@ export default class Runner extends EventTarget { this._swing = swing || false this._wait = wait || 0 return this + } - delay (delay) { - return this.animate(0, delay) + delay ( delay ) { + + return this.animate( 0, delay ) + } /* @@ -141,26 +164,32 @@ export default class Runner extends EventTarget { These methods allow us to attach basic functions to the runner directly */ - queue (initFn, runFn, retargetFn, isTransform) { - this._queue.push({ + queue ( initFn, runFn, retargetFn, isTransform ) { + + this._queue.push( { initialiser: initFn || noop, runner: runFn || noop, retarget: retargetFn, isTransform: isTransform, initialised: false, finished: false - }) + } ) var timeline = this.timeline() timeline && this.timeline()._continue() return this + } - during (fn) { - return this.queue(null, fn) + during ( fn ) { + + return this.queue( null, fn ) + } - after (fn) { - return this.on('finish', fn) + after ( fn ) { + + return this.on( 'finish', fn ) + } /* @@ -169,34 +198,45 @@ export default class Runner extends EventTarget { Control how the animation plays */ - time (time) { - if (time == null) { + time ( time ) { + + if ( time == null ) { + return this._time + } let dt = time - this._time - this.step(dt) + this.step( dt ) return this + } duration () { - return this._times * (this._wait + this._duration) - this._wait + + return this._times * ( this._wait + this._duration ) - this._wait + } - loops (p) { + loops ( p ) { + var loopDuration = this._duration + this._wait - if (p == null) { - var loopsDone = Math.floor(this._time / loopDuration) - var relativeTime = (this._time - loopsDone * loopDuration) + if ( p == null ) { + + var loopsDone = Math.floor( this._time / loopDuration ) + var relativeTime = ( this._time - loopsDone * loopDuration ) var position = relativeTime / this._duration - return Math.min(loopsDone + position, this._times) + return Math.min( loopsDone + position, this._times ) + } - var whole = Math.floor(p) + var whole = Math.floor( p ) var partial = p % 1 var time = loopDuration * whole + this._duration * partial - return this.time(time) + return this.time( time ) + } - position (p) { + position ( p ) { + // Get all of the variables we need var x = this._time var d = this._duration @@ -206,7 +246,8 @@ export default class Runner extends EventTarget { var r = this._reverse var position - if (p == null) { + if ( p == null ) { + /* This function converts a time to a position in the range [0, 1] The full explanation can be found in this desmos demonstration @@ -215,40 +256,49 @@ export default class Runner extends EventTarget { */ // Figure out the value without thinking about the start or end time - const f = function (x) { - var swinging = s * Math.floor(x % (2 * (w + d)) / (w + d)) - var backwards = (swinging && !r) || (!swinging && r) - var uncliped = Math.pow(-1, backwards) * (x % (w + d)) / d + backwards - var clipped = Math.max(Math.min(uncliped, 1), 0) + const f = function ( x ) { + + var swinging = s * Math.floor( x % ( 2 * ( w + d ) ) / ( w + d ) ) + var backwards = ( swinging && !r ) || ( !swinging && r ) + var uncliped = Math.pow( -1, backwards ) * ( x % ( w + d ) ) / d + backwards + var clipped = Math.max( Math.min( uncliped, 1 ), 0 ) return clipped + } // Figure out the value by incorporating the start time - var endTime = t * (w + d) - w - position = x <= 0 ? Math.round(f(1e-5)) - : x < endTime ? f(x) - : Math.round(f(endTime - 1e-5)) + var endTime = t * ( w + d ) - w + position = x <= 0 ? Math.round( f( 1e-5 ) ) + : x < endTime ? f( x ) + : Math.round( f( endTime - 1e-5 ) ) return position + } // Work out the loops done and add the position to the loops done - var loopsDone = Math.floor(this.loops()) - var swingForward = s && (loopsDone % 2 === 0) - var forwards = (swingForward && !r) || (r && swingForward) - position = loopsDone + (forwards ? p : 1 - p) - return this.loops(position) + var loopsDone = Math.floor( this.loops() ) + var swingForward = s && ( loopsDone % 2 === 0 ) + var forwards = ( swingForward && !r ) || ( r && swingForward ) + position = loopsDone + ( forwards ? p : 1 - p ) + return this.loops( position ) + } - progress (p) { - if (p == null) { - return Math.min(1, this._time / this.duration()) + progress ( p ) { + + if ( p == null ) { + + return Math.min( 1, this._time / this.duration() ) + } - return this.time(p * this.duration()) + return this.time( p * this.duration() ) + } - step (dt) { + step ( dt ) { + // If we are inactive, this stepper just gets skipped - if (!this.enabled) return this + if ( !this.enabled ) return this // Update the time and get the new position dt = dt == null ? 16 : dt @@ -264,8 +314,10 @@ export default class Runner extends EventTarget { var justStarted = this._lastTime < 0 && this._time > 0 var justFinished = this._lastTime < this._time && this.time > duration this._lastTime = this._time - if (justStarted) { - this.fire('start', this) + if ( justStarted ) { + + this.fire( 'start', this ) + } // Work out if the runner is finished set the done flag here so animations @@ -275,41 +327,54 @@ export default class Runner extends EventTarget { this.done = !declarative && !justFinished && this._time >= duration // Call initialise and the run function - if (running || declarative) { - this._initialise(running) + if ( running || declarative ) { + + this._initialise( running ) // clear the transforms on this runner so they dont get added again and again this.transforms = new Matrix() - var converged = this._run(declarative ? dt : position) - this.fire('step', this) + var converged = this._run( declarative ? dt : position ) + this.fire( 'step', this ) + } // correct the done flag here // declaritive animations itself know when they converged - this.done = this.done || (converged && declarative) - if (this.done) { - this.fire('finish', this) + this.done = this.done || ( converged && declarative ) + if ( this.done ) { + + this.fire( 'finish', this ) + } return this + } finish () { - return this.step(Infinity) + + return this.step( Infinity ) + } - reverse (reverse) { + reverse ( reverse ) { + this._reverse = reverse == null ? !this._reverse : reverse return this + } - ease (fn) { - this._stepper = new Ease(fn) + ease ( fn ) { + + this._stepper = new Ease( fn ) return this + } - active (enabled) { - if (enabled == null) return this.enabled + active ( enabled ) { + + if ( enabled == null ) return this.enabled this.enabled = enabled return this + } /* @@ -319,102 +384,135 @@ export default class Runner extends EventTarget { */ // Save a morpher to the morpher list so that we can retarget it later - _rememberMorpher (method, morpher) { + _rememberMorpher ( method, morpher ) { + this._history[method] = { morpher: morpher, caller: this._queue[this._queue.length - 1] } + } // Try to set the target for a morpher if the morpher exists, otherwise // do nothing and return false - _tryRetarget (method, target) { - if (this._history[method]) { + _tryRetarget ( method, target ) { + + if ( this._history[method] ) { + // if the last method wasnt even initialised, throw it away - if (!this._history[method].caller.initialised) { - let index = this._queue.indexOf(this._history[method].caller) - this._queue.splice(index, 1) + if ( !this._history[method].caller.initialised ) { + + let index = this._queue.indexOf( this._history[method].caller ) + this._queue.splice( index, 1 ) return false + } // for the case of transformations, we use the special retarget function // which has access to the outer scope - if (this._history[method].caller.retarget) { - this._history[method].caller.retarget(target) - // for everything else a simple morpher change is sufficient + if ( this._history[method].caller.retarget ) { + + this._history[method].caller.retarget( target ) + // for everything else a simple morpher change is sufficient + } else { - this._history[method].morpher.to(target) + + this._history[method].morpher.to( target ) + } this._history[method].caller.finished = false var timeline = this.timeline() timeline && timeline._continue() return true + } return false + } // Run each initialise function in the runner if required - _initialise (running) { + _initialise ( running ) { + // If we aren't running, we shouldn't initialise when not declarative - if (!running && !this._isDeclarative) return + if ( !running && !this._isDeclarative ) return // Loop through all of the initialisers - for (var i = 0, len = this._queue.length; i < len; ++i) { + for ( var i = 0, len = this._queue.length; i < len; ++i ) { + // Get the current initialiser var current = this._queue[i] // Determine whether we need to initialise - var needsIt = this._isDeclarative || (!current.initialised && running) + var needsIt = this._isDeclarative || ( !current.initialised && running ) running = !current.finished // Call the initialiser if we need to - if (needsIt && running) { - current.initialiser.call(this) + if ( needsIt && running ) { + + current.initialiser.call( this ) current.initialised = true + } + } + } // Run each run function for the position or dt given - _run (positionOrDt) { + _run ( positionOrDt ) { + // Run all of the _queue directly var allfinished = true - for (var i = 0, len = this._queue.length; i < len; ++i) { + for ( var i = 0, len = this._queue.length; i < len; ++i ) { + // Get the current function to run var current = this._queue[i] // Run the function if its not finished, we keep track of the finished // flag for the sake of declarative _queue - var converged = current.runner.call(this, positionOrDt) - current.finished = current.finished || (converged === true) + var converged = current.runner.call( this, positionOrDt ) + current.finished = current.finished || ( converged === true ) allfinished = allfinished && current.finished + } // We report when all of the constructors are finished return allfinished + } - addTransform (transform, index) { - this.transforms.lmultiplyO(transform) + addTransform ( transform, index ) { + + this.transforms.lmultiplyO( transform ) return this + } clearTransform () { + this.transforms = new Matrix() return this + } // TODO: Keep track of all transformations so that deletion is faster clearTransformsFromQueue () { - if (!this.done) { - this._queue = this._queue.filter((item) => { + + if ( !this.done ) { + + this._queue = this._queue.filter( ( item ) => { + return !item.isTransform - }) + + } ) + } + } - static sanitise (duration, delay, when) { + static sanitise ( duration, delay, when ) { + // Initialise the default parameters var times = 1 var swing = false @@ -424,13 +522,15 @@ export default class Runner extends EventTarget { when = when || 'last' // If we have an object, unpack the values - if (typeof duration === 'object' && !(duration instanceof Stepper)) { + if ( typeof duration === 'object' && !( duration instanceof Stepper ) ) { + delay = duration.delay || delay when = duration.when || when swing = duration.swing || swing times = duration.times || times wait = duration.wait || wait duration = duration.duration || timeline.duration + } return { @@ -441,215 +541,285 @@ export default class Runner extends EventTarget { wait: wait, when: when } + } + } Runner.id = 0 class FakeRunner { - constructor (transforms = new Matrix(), id = -1, done = true) { + + constructor ( transforms = new Matrix(), id = -1, done = true ) { + this.transforms = transforms this.id = id this.done = done + } clearTransformsFromQueue () { } + } -extend([Runner, FakeRunner], { - mergeWith (runner) { +extend( [ Runner, FakeRunner ], { + mergeWith ( runner ) { + return new FakeRunner( - runner.transforms.lmultiply(this.transforms), + runner.transforms.lmultiply( this.transforms ), runner.id ) + } -}) +} ) // FakeRunner.emptyRunner = new FakeRunner() -const lmultiply = (last, curr) => last.lmultiplyO(curr) -const getRunnerTransform = (runner) => runner.transforms +const lmultiply = ( last, curr ) => last.lmultiplyO( curr ) +const getRunnerTransform = ( runner ) => runner.transforms function mergeTransforms () { + // Find the matrix to apply to the element and apply it let runners = this._transformationRunners.runners let netTransform = runners - .map(getRunnerTransform) - .reduce(lmultiply, new Matrix()) + .map( getRunnerTransform ) + .reduce( lmultiply, new Matrix() ) - this.transform(netTransform) + this.transform( netTransform ) this._transformationRunners.merge() - if (this._transformationRunners.length() === 1) { + if ( this._transformationRunners.length() === 1 ) { + this._frameId = null + } + } class RunnerArray { + constructor () { + this.runners = [] this.ids = [] + } - add (runner) { - if (this.runners.includes(runner)) return + add ( runner ) { + + if ( this.runners.includes( runner ) ) return let id = runner.id + 1 - let leftSibling = this.ids.reduce((last, curr) => { - if (curr > last && curr < id) return curr + let leftSibling = this.ids.reduce( ( last, curr ) => { + + if ( curr > last && curr < id ) return curr return last - }, 0) - let index = this.ids.indexOf(leftSibling) + 1 + }, 0 ) - this.ids.splice(index, 0, id) - this.runners.splice(index, 0, runner) + let index = this.ids.indexOf( leftSibling ) + 1 + + this.ids.splice( index, 0, id ) + this.runners.splice( index, 0, runner ) return this + } - getByID (id) { - return this.runners[this.ids.indexOf(id + 1)] + getByID ( id ) { + + return this.runners[this.ids.indexOf( id + 1 )] + } - remove (id) { - let index = this.ids.indexOf(id + 1) - this.ids.splice(index, 1) - this.runners.splice(index, 1) + remove ( id ) { + + let index = this.ids.indexOf( id + 1 ) + this.ids.splice( index, 1 ) + this.runners.splice( index, 1 ) return this + } merge () { + let lastRunner = null - this.runners.forEach((runner, i) => { - if (lastRunner && runner.done && lastRunner.done) { - this.remove(runner.id) - this.edit(lastRunner.id, runner.mergeWith(lastRunner)) + this.runners.forEach( ( runner, i ) => { + + if ( lastRunner && runner.done && lastRunner.done ) { + + this.remove( runner.id ) + this.edit( lastRunner.id, runner.mergeWith( lastRunner ) ) + } lastRunner = runner - }) + + } ) return this + } - edit (id, newRunner) { - let index = this.ids.indexOf(id + 1) - this.ids.splice(index, 1, id) - this.runners.splice(index, 1, newRunner) + edit ( id, newRunner ) { + + let index = this.ids.indexOf( id + 1 ) + this.ids.splice( index, 1, id ) + this.runners.splice( index, 1, newRunner ) return this + } length () { + return this.ids.length + } - clearBefore (id) { - let deleteCnt = this.ids.indexOf(id + 1) || 1 - this.ids.splice(0, deleteCnt, 0) - this.runners.splice(0, deleteCnt, new FakeRunner()) - .forEach((r) => r.clearTransformsFromQueue()) + clearBefore ( id ) { + + let deleteCnt = this.ids.indexOf( id + 1 ) || 1 + this.ids.splice( 0, deleteCnt, 0 ) + this.runners.splice( 0, deleteCnt, new FakeRunner() ) + .forEach( ( r ) => r.clearTransformsFromQueue() ) return this + } + } let frameId = 0 -registerMethods({ +registerMethods( { Element: { - animate (duration, delay, when) { - var o = Runner.sanitise(duration, delay, when) + animate ( duration, delay, when ) { + + var o = Runner.sanitise( duration, delay, when ) var timeline = this.timeline() - return new Runner(o.duration) - .loop(o) - .element(this) - .timeline(timeline) - .schedule(delay, when) + return new Runner( o.duration ) + .loop( o ) + .element( this ) + .timeline( timeline ) + .schedule( delay, when ) + }, - delay (by, when) { - return this.animate(0, by, when) + delay ( by, when ) { + + return this.animate( 0, by, when ) + }, // this function searches for all runners on the element and deletes the ones // which run before the current one. This is because absolute transformations // overwfrite anything anyway so there is no need to waste time computing // other runners - _clearTransformRunnersBefore (currentRunner) { - this._transformationRunners.clearBefore(currentRunner.id) + _clearTransformRunnersBefore ( currentRunner ) { + + this._transformationRunners.clearBefore( currentRunner.id ) + }, - _currentTransform (current) { + _currentTransform ( current ) { + return this._transformationRunners.runners // we need the equal sign here to make sure, that also transformations // on the same runner which execute before the current transformation are // taken into account - .filter((runner) => runner.id <= current.id) - .map(getRunnerTransform) - .reduce(lmultiply, new Matrix()) + .filter( ( runner ) => runner.id <= current.id ) + .map( getRunnerTransform ) + .reduce( lmultiply, new Matrix() ) + }, - addRunner (runner) { - this._transformationRunners.add(runner) + addRunner ( runner ) { + + this._transformationRunners.add( runner ) Animator.transform_frame( - mergeTransforms.bind(this), this._frameId + mergeTransforms.bind( this ), this._frameId ) + }, _prepareRunner () { - if (this._frameId == null) { + + if ( this._frameId == null ) { + this._transformationRunners = new RunnerArray() - .add(new FakeRunner(new Matrix(this))) + .add( new FakeRunner( new Matrix( this ) ) ) this._frameId = frameId++ + } + } } -}) +} ) + +extend( Runner, { + attr ( a, v ) { + + return this.styleAttr( 'attr', a, v ) -extend(Runner, { - attr (a, v) { - return this.styleAttr('attr', a, v) }, // Add animatable styles - css (s, v) { - return this.styleAttr('css', s, v) + css ( s, v ) { + + return this.styleAttr( 'css', s, v ) + }, - styleAttr (type, name, val) { + styleAttr ( type, name, val ) { + // apply attributes individually - if (typeof name === 'object') { - for (var key in val) { - this.styleAttr(type, key, val[key]) + if ( typeof name === 'object' ) { + + for ( var key in val ) { + + this.styleAttr( type, key, val[key] ) + } + } - var morpher = new Morphable(this._stepper).to(val) + var morpher = new Morphable( this._stepper ).to( val ) + + this.queue( function () { + + morpher = morpher.from( this.element()[type]( name ) ) + + }, function ( pos ) { - this.queue(function () { - morpher = morpher.from(this.element()[type](name)) - }, function (pos) { - this.element()[type](name, morpher.at(pos)) + this.element()[type]( name, morpher.at( pos ) ) return morpher.done() - }) + + } ) return this + }, - zoom (level, point) { - var morpher = new Morphable(this._stepper).to(new SVGNumber(level)) + zoom ( level, point ) { + + var morpher = new Morphable( this._stepper ).to( new SVGNumber( level ) ) + + this.queue( function () { - this.queue(function () { - morpher = morpher.from(this.zoom()) - }, function (pos) { - this.element().zoom(morpher.at(pos), point) + morpher = morpher.from( this.zoom() ) + + }, function ( pos ) { + + this.element().zoom( morpher.at( pos ), point ) return morpher.done() - }) + + } ) return this + }, /** @@ -669,22 +839,25 @@ extend(Runner, { // - Note F(1) = T // 4. Now you get the delta matrix as a result: D = F * inv(M) - transform (transforms, relative, affine) { + transform ( transforms, relative, affine ) { + // If we have a declarative function, we should retarget it if possible relative = transforms.relative || relative - if (this._isDeclarative && !relative && this._tryRetarget('transform', transforms)) { + if ( this._isDeclarative && !relative && this._tryRetarget( 'transform', transforms ) ) { + return this + } // Parse the parameters - var isMatrix = Matrix.isMatrixLike(transforms) + var isMatrix = Matrix.isMatrixLike( transforms ) affine = transforms.affine != null ? transforms.affine - : (affine != null ? affine : !isMatrix) + : ( affine != null ? affine : !isMatrix ) // Create a morepher and set its type - const morpher = new Morphable(this._stepper) - .type(affine ? TransformBag : Matrix) + const morpher = new Morphable( this._stepper ) + .type( affine ? TransformBag : Matrix ) let origin let element @@ -693,249 +866,328 @@ extend(Runner, { let startTransform function setup () { + // make sure element and origin is defined element = element || this.element() - origin = origin || getOrigin(transforms, element) + origin = origin || getOrigin( transforms, element ) - startTransform = new Matrix(relative ? undefined : element) + startTransform = new Matrix( relative ? undefined : element ) // add the runner to the element so it can merge transformations - element.addRunner(this) + element.addRunner( this ) // Deactivate all transforms that have run so far if we are absolute - if (!relative) { - element._clearTransformRunnersBefore(this) + if ( !relative ) { + + element._clearTransformRunnersBefore( this ) + } + } - function run (pos) { + function run ( pos ) { + // clear all other transforms before this in case something is saved // on this runner. We are absolute. We dont need these! - if (!relative) this.clearTransform() + if ( !relative ) this.clearTransform() - let { x, y } = new Point(origin).transform(element._currentTransform(this)) + let { x, y } = new Point( origin ).transform( element._currentTransform( this ) ) - let target = new Matrix({ ...transforms, origin: [x, y] }) + let target = new Matrix( { ...transforms, origin: [ x, y ] } ) let start = this._isDeclarative && current ? current : startTransform - if (affine) { - target = target.decompose(x, y) - start = start.decompose(x, y) + if ( affine ) { + + target = target.decompose( x, y ) + start = start.decompose( x, y ) // Get the current and target angle as it was set const rTarget = target.rotate const rCurrent = start.rotate // Figure out the shortest path to rotate directly - const possibilities = [rTarget - 360, rTarget, rTarget + 360] - const distances = possibilities.map(a => Math.abs(a - rCurrent)) - const shortest = Math.min(...distances) - const index = distances.indexOf(shortest) + const possibilities = [ rTarget - 360, rTarget, rTarget + 360 ] + const distances = possibilities.map( a => Math.abs( a - rCurrent ) ) + const shortest = Math.min( ...distances ) + const index = distances.indexOf( shortest ) target.rotate = possibilities[index] + } - if (relative) { + if ( relative ) { + // we have to be careful here not to overwrite the rotation // with the rotate method of Matrix - if (!isMatrix) { + if ( !isMatrix ) { + target.rotate = transforms.rotate || 0 + } - if (this._isDeclarative && currentAngle) { + if ( this._isDeclarative && currentAngle ) { + start.rotate = currentAngle + } + } - morpher.from(start) - morpher.to(target) + morpher.from( start ) + morpher.to( target ) - let affineParameters = morpher.at(pos) + let affineParameters = morpher.at( pos ) currentAngle = affineParameters.rotate - current = new Matrix(affineParameters) + current = new Matrix( affineParameters ) - this.addTransform(current) + this.addTransform( current ) return morpher.done() + } - function retarget (newTransforms) { + function retarget ( newTransforms ) { + // only get a new origin if it changed since the last call if ( - (newTransforms.origin || 'center').toString() !== - (transforms.origin || 'center').toString() + ( newTransforms.origin || 'center' ).toString() + !== ( transforms.origin || 'center' ).toString() ) { - origin = getOrigin(transforms, element) + + origin = getOrigin( transforms, element ) + } // overwrite the old transformations with the new ones transforms = { ...newTransforms, origin } + } - this.queue(setup, run, retarget, true) - this._isDeclarative && this._rememberMorpher('transform', morpher) + this.queue( setup, run, retarget, true ) + this._isDeclarative && this._rememberMorpher( 'transform', morpher ) return this + }, // Animatable x-axis - x (x, relative) { - return this._queueNumber('x', x) + x ( x, relative ) { + + return this._queueNumber( 'x', x ) + }, // Animatable y-axis - y (y) { - return this._queueNumber('y', y) + y ( y ) { + + return this._queueNumber( 'y', y ) + }, - dx (x) { - return this._queueNumberDelta('x', x) + dx ( x ) { + + return this._queueNumberDelta( 'x', x ) + }, - dy (y) { - return this._queueNumberDelta('y', y) + dy ( y ) { + + return this._queueNumberDelta( 'y', y ) + }, - _queueNumberDelta (method, to) { - to = new SVGNumber(to) + _queueNumberDelta ( method, to ) { + + to = new SVGNumber( to ) // Try to change the target if we have this method already registerd - if (this._tryRetarget(method, to)) return this + if ( this._tryRetarget( method, to ) ) return this // Make a morpher and queue the animation - var morpher = new Morphable(this._stepper).to(to) + var morpher = new Morphable( this._stepper ).to( to ) var from = null - this.queue(function () { + this.queue( function () { + from = this.element()[method]() - morpher.from(from) - morpher.to(from + to) - }, function (pos) { - this.element()[method](morpher.at(pos)) + morpher.from( from ) + morpher.to( from + to ) + + }, function ( pos ) { + + this.element()[method]( morpher.at( pos ) ) return morpher.done() - }, function (newTo) { - morpher.to(from + new SVGNumber(newTo)) - }) + + }, function ( newTo ) { + + morpher.to( from + new SVGNumber( newTo ) ) + + } ) // Register the morpher so that if it is changed again, we can retarget it - this._rememberMorpher(method, morpher) + this._rememberMorpher( method, morpher ) return this + }, - _queueObject (method, to) { + _queueObject ( method, to ) { + // Try to change the target if we have this method already registerd - if (this._tryRetarget(method, to)) return this + if ( this._tryRetarget( method, to ) ) return this // Make a morpher and queue the animation - var morpher = new Morphable(this._stepper).to(to) - this.queue(function () { - morpher.from(this.element()[method]()) - }, function (pos) { - this.element()[method](morpher.at(pos)) + var morpher = new Morphable( this._stepper ).to( to ) + this.queue( function () { + + morpher.from( this.element()[method]() ) + + }, function ( pos ) { + + this.element()[method]( morpher.at( pos ) ) return morpher.done() - }) + + } ) // Register the morpher so that if it is changed again, we can retarget it - this._rememberMorpher(method, morpher) + this._rememberMorpher( method, morpher ) return this + }, - _queueNumber (method, value) { - return this._queueObject(method, new SVGNumber(value)) + _queueNumber ( method, value ) { + + return this._queueObject( method, new SVGNumber( value ) ) + }, // Animatable center x-axis - cx (x) { - return this._queueNumber('cx', x) + cx ( x ) { + + return this._queueNumber( 'cx', x ) + }, // Animatable center y-axis - cy (y) { - return this._queueNumber('cy', y) + cy ( y ) { + + return this._queueNumber( 'cy', y ) + }, // Add animatable move - move (x, y) { - return this.x(x).y(y) + move ( x, y ) { + + return this.x( x ).y( y ) + }, // Add animatable center - center (x, y) { - return this.cx(x).cy(y) + center ( x, y ) { + + return this.cx( x ).cy( y ) + }, // Add animatable size - size (width, height) { + size ( width, height ) { + // animate bbox based size for all other elements var box - if (!width || !height) { + if ( !width || !height ) { + box = this._element.bbox() + } - if (!width) { + if ( !width ) { + width = box.width / box.height * height + } - if (!height) { + if ( !height ) { + height = box.height / box.width * width + } return this - .width(width) - .height(height) + .width( width ) + .height( height ) + }, // Add animatable width - width (width) { - return this._queueNumber('width', width) + width ( width ) { + + return this._queueNumber( 'width', width ) + }, // Add animatable height - height (height) { - return this._queueNumber('height', height) + height ( height ) { + + return this._queueNumber( 'height', height ) + }, // Add animatable plot - plot (a, b, c, d) { + plot ( a, b, c, d ) { + // Lines can be plotted with 4 arguments - if (arguments.length === 4) { - return this.plot([a, b, c, d]) + if ( arguments.length === 4 ) { + + return this.plot( [ a, b, c, d ] ) + } - var morpher = this._element.MorphArray().to(a) + var morpher = this._element.MorphArray().to( a ) + + this.queue( function () { + + morpher.from( this._element.array() ) - this.queue(function () { - morpher.from(this._element.array()) - }, function (pos) { - this._element.plot(morpher.at(pos)) - }) + }, function ( pos ) { + + this._element.plot( morpher.at( pos ) ) + + } ) return this + }, // Add leading method - leading (value) { - return this._queueNumber('leading', value) + leading ( value ) { + + return this._queueNumber( 'leading', value ) + }, // Add animatable viewbox - viewbox (x, y, width, height) { - return this._queueObject('viewbox', new Box(x, y, width, height)) + viewbox ( x, y, width, height ) { + + return this._queueObject( 'viewbox', new Box( x, y, width, height ) ) + }, - update (o) { - if (typeof o !== 'object') { - return this.update({ + update ( o ) { + + if ( typeof o !== 'object' ) { + + return this.update( { offset: arguments[0], color: arguments[1], opacity: arguments[2] - }) + } ) + } - if (o.opacity != null) this.attr('stop-opacity', o.opacity) - if (o.color != null) this.attr('stop-color', o.color) - if (o.offset != null) this.attr('offset', o.offset) + if ( o.opacity != null ) this.attr( 'stop-opacity', o.opacity ) + if ( o.color != null ) this.attr( 'stop-color', o.color ) + if ( o.offset != null ) this.attr( 'offset', o.offset ) return this + } -}) +} ) -extend(Runner, { rx, ry, from, to }) +extend( Runner, { rx, ry, from, to } ) diff --git a/src/animation/Timeline.js b/src/animation/Timeline.js index 6abcb80..2fa281c 100644 --- a/src/animation/Timeline.js +++ b/src/animation/Timeline.js @@ -3,21 +3,27 @@ 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 @@ -36,6 +42,7 @@ export default class Timeline extends EventTarget { this._time = 0 this._lastSourceTime = 0 this._lastStepTime = 0 + } /** @@ -43,19 +50,28 @@ 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, @@ -65,28 +81,40 @@ 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 @@ -99,91 +127,115 @@ 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) + if ( yes == null ) return this.speed( -currentSpeed ) + + var positive = Math.abs( currentSpeed ) + return this.speed( yes ? positive : -positive ) - 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 @@ -193,7 +245,8 @@ 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 @@ -204,64 +257,89 @@ 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 + } } -}) +} ) diff --git a/src/elements/A.js b/src/elements/A.js index 722deed..ee81975 100644 --- a/src/elements/A.js +++ b/src/elements/A.js @@ -4,40 +4,58 @@ 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) } else { - link.to(url) + if ( typeof url === 'function' ) { + + url.call( link, link ) + + } else { + + link.to( url ) + } - return this.parent().put(link).put(this) + return this.parent().put( link ).put( this ) + } } -}) +} ) -register(A) +register( A ) diff --git a/src/elements/Bare.js b/src/elements/Bare.js index a057634..190aa1f 100644 --- a/src/elements/Bare.js +++ b/src/elements/Bare.js @@ -4,28 +4,38 @@ 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 ) ) + + } ) +} ) diff --git a/src/elements/Circle.js b/src/elements/Circle.js index 3135ada..5aa969a 100644 --- a/src/elements/Circle.js +++ b/src/elements/Circle.js @@ -10,40 +10,54 @@ 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 ) diff --git a/src/elements/ClipPath.js b/src/elements/ClipPath.js index e545baa..199ee5b 100644 --- a/src/elements/ClipPath.js +++ b/src/elements/ClipPath.js @@ -4,54 +4,72 @@ 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 ) diff --git a/src/elements/Container.js b/src/elements/Container.js index b47972e..82ee0ae 100644 --- a/src/elements/Container.js +++ b/src/elements/Container.js @@ -2,29 +2,39 @@ 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 ) diff --git a/src/elements/Defs.js b/src/elements/Defs.js index 2826611..bcbea01 100644 --- a/src/elements/Defs.js +++ b/src/elements/Defs.js @@ -2,12 +2,24 @@ 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 + } - flatten () { return this } - ungroup () { return this } } -register(Defs) +register( Defs ) diff --git a/src/elements/Dom.js b/src/elements/Dom.js index 2fcedce..f3ea467 100644 --- a/src/elements/Dom.js +++ b/src/elements/Dom.js @@ -15,217 +15,294 @@ 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) + 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] ) - 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]) + for ( i = 0, il = children.length; i < il; i++ ) { + + block.apply( children[i], [ i, children ] ) + + if ( deep ) { + + children[i].each( block, deep ) - 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 @@ -234,38 +311,49 @@ 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) + // If modifier returns new node, use it + + } 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 @@ -274,33 +362,41 @@ 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 ) diff --git a/src/elements/Element.js b/src/elements/Element.js index 91aa3e0..169c872 100644 --- a/src/elements/Element.js +++ b/src/elements/Element.js @@ -15,11 +15,13 @@ 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 = {} @@ -27,135 +29,177 @@ 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 + 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.node !== until.node && - parent.node !== globals.document + ( 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 + reference ( attr ) { + + attr = this.attr( attr ) + if ( !attr ) return null + + const m = attr.match( reference ) + return m ? makeInstance( m[1] ) : 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') + this.node.removeAttribute( 'svgjs:data' ) + + if ( Object.keys( this.dom ).length ) { + + this.node.setAttribute( 'svgjs:data', JSON.stringify( this.dom ) ) // see #428 - 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 ) diff --git a/src/elements/Ellipse.js b/src/elements/Ellipse.js index 0350f1f..e1e1fe0 100644 --- a/src/elements/Ellipse.js +++ b/src/elements/Ellipse.js @@ -11,26 +11,34 @@ 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 ) diff --git a/src/elements/G.js b/src/elements/G.js index 6a93a3f..a72f1fb 100644 --- a/src/elements/G.js +++ b/src/elements/G.js @@ -3,18 +3,24 @@ 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 ) diff --git a/src/elements/Gradient.js b/src/elements/Gradient.js index 23de97d..7116fc8 100644 --- a/src/elements/Gradient.js +++ b/src/elements/Gradient.js @@ -12,71 +12,95 @@ 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 ) diff --git a/src/elements/HtmlNode.js b/src/elements/HtmlNode.js index 009b122..d2299ed 100644 --- a/src/elements/HtmlNode.js +++ b/src/elements/HtmlNode.js @@ -3,4 +3,4 @@ import Dom from './Dom.js' export default class HtmlNode extends Dom {} -register(HtmlNode) +register( HtmlNode ) diff --git a/src/elements/Image.js b/src/elements/Image.js index 8f27470..4945271 100644 --- a/src/elements/Image.js +++ b/src/elements/Image.js @@ -9,69 +9,99 @@ 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) - on(img, 'load error', function () { + }, this ) + + on( img, 'load error', function () { + // dont forget to unbind memory leaking events - off(img) - }) + off( img ) + + } ) + + return this.attr( 'href', ( img.src = url ), xlink ) - 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 ) diff --git a/src/elements/Line.js b/src/elements/Line.js index 99e7497..123f2bb 100644 --- a/src/elements/Line.js +++ b/src/elements/Line.js @@ -11,58 +11,78 @@ 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()) - , args[0] != null ? args : [0, 0, 0, 0] + this.put( new Line() ) + , args[0] != null ? args : [ 0, 0, 0, 0 ] ) - }) + + } ) } -}) +} ) -register(Line) +register( Line ) diff --git a/src/elements/Marker.js b/src/elements/Marker.js index d40d13b..1054987 100644 --- a/src/elements/Marker.js +++ b/src/elements/Marker.js @@ -3,79 +3,103 @@ 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) { - var attr = ['marker'] + 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) + : this.defs().marker( width, height, block ) + + return this.attr( attr, marker ) - return this.attr(attr, marker) } } -}) +} ) -register(Marker) +register( Marker ) diff --git a/src/elements/Mask.js b/src/elements/Mask.js index 8dfffd6..523b9de 100644 --- a/src/elements/Mask.js +++ b/src/elements/Mask.js @@ -4,54 +4,72 @@ 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 ) diff --git a/src/elements/Path.js b/src/elements/Path.js index dc27320..1fe5661 100644 --- a/src/elements/Path.js +++ b/src/elements/Path.js @@ -6,76 +6,102 @@ 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 ) diff --git a/src/elements/Pattern.js b/src/elements/Pattern.js index 6dd4e01..6f7897b 100644 --- a/src/elements/Pattern.js +++ b/src/elements/Pattern.js @@ -5,67 +5,89 @@ 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 ) diff --git a/src/elements/Polygon.js b/src/elements/Polygon.js index afa5f31..2288b75 100644 --- a/src/elements/Polygon.js +++ b/src/elements/Polygon.js @@ -11,22 +11,28 @@ 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 ) diff --git a/src/elements/Polyline.js b/src/elements/Polyline.js index 5897295..3749c93 100644 --- a/src/elements/Polyline.js +++ b/src/elements/Polyline.js @@ -11,22 +11,28 @@ 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 ) diff --git a/src/elements/Rect.js b/src/elements/Rect.js index 6e161c9..465b52b 100644 --- a/src/elements/Rect.js +++ b/src/elements/Rect.js @@ -9,21 +9,27 @@ 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 ) diff --git a/src/elements/Shape.js b/src/elements/Shape.js index cdddc60..0d5a5b0 100644 --- a/src/elements/Shape.js +++ b/src/elements/Shape.js @@ -3,4 +3,4 @@ import Element from './Element.js' export default class Shape extends Element {} -register(Shape) +register( Shape ) diff --git a/src/elements/Stop.js b/src/elements/Stop.js index 9a5acaa..8838923 100644 --- a/src/elements/Stop.js +++ b/src/elements/Stop.js @@ -3,27 +3,35 @@ 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 ) diff --git a/src/elements/Style.js b/src/elements/Style.js index 50ec50e..643f356 100644 --- a/src/elements/Style.js +++ b/src/elements/Style.js @@ -3,51 +3,69 @@ 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 ) diff --git a/src/elements/Svg.js b/src/elements/Svg.js index 6172454..1326900 100644 --- a/src/elements/Svg.js +++ b/src/elements/Svg.js @@ -11,68 +11,90 @@ 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.nodeName === '#document' + + return !this.node.parentNode + || !( 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() - return adopt(this.node.getElementsByTagName('defs')[0]) || - this.put(new Defs()) + if ( !this.isRoot() ) return this.root().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 ) diff --git a/src/elements/Symbol.js b/src/elements/Symbol.js index f44125c..577d1f1 100644 --- a/src/elements/Symbol.js +++ b/src/elements/Symbol.js @@ -3,18 +3,24 @@ 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 ) diff --git a/src/elements/Text.js b/src/elements/Text.js index db9c2ee..a981d73 100644 --- a/src/elements/Text.js +++ b/src/elements/Text.js @@ -13,175 +13,233 @@ 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) + this.clear().build( true ) + + if ( typeof text === 'function' ) { - 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) + this.each( function () { + + var fontSize = globals.window.getComputedStyle( this.node ) + .getPropertyValue( 'font-size' ) + var dy = leading * new SVGNumber( fontSize ) + + if ( this.dom.newLined ) { - if (this.dom.newLined) { - this.attr('x', self.attr('x')) + this.attr( 'x', self.attr( 'x' ) ) + + if ( this.text() === '\n' ) { - 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 ) diff --git a/src/elements/TextPath.js b/src/elements/TextPath.js index 91c48ae..af89ef7 100644 --- a/src/elements/TextPath.js +++ b/src/elements/TextPath.js @@ -7,80 +7,106 @@ 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 ) diff --git a/src/elements/Tspan.js b/src/elements/Tspan.js index abd032f..fcf8cf5 100644 --- a/src/elements/Tspan.js +++ b/src/elements/Tspan.js @@ -9,61 +9,77 @@ 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) + this.node.appendChild( tspan.node ) + + return tspan.text( text ) - return tspan.text(text) - }) + } ) } -}) +} ) -register(Tspan) +register( Tspan ) diff --git a/src/elements/Use.js b/src/elements/Use.js index 7921461..9237e08 100644 --- a/src/elements/Use.js +++ b/src/elements/Use.js @@ -4,24 +4,32 @@ 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 ) diff --git a/src/main.js b/src/main.js index 919fb25..951cc69 100644 --- a/src/main.js +++ b/src/main.js @@ -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() diff --git a/src/modules/core/attr.js b/src/modules/core/attr.js index f90dcb9..7cb9e2a 100644 --- a/src/modules/core/attr.js +++ b/src/modules/core/attr.js @@ -5,77 +5,113 @@ 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) - : 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 + } diff --git a/src/modules/core/circled.js b/src/modules/core/circled.js index 597d252..ad901b9 100644 --- a/src/modules/core/circled.js +++ b/src/modules/core/circled.js @@ -1,53 +1,69 @@ 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 ) ) + } diff --git a/src/modules/core/event.js b/src/modules/core/event.js index a52a744..23459fb 100644 --- a/src/modules/core/event.js +++ b/src/modules/core/event.js @@ -4,38 +4,48 @@ 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] || {} @@ -45,76 +55,126 @@ 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 ) { - events.forEach(function (event) { - var ev = event && event.split('.')[0] - var ns = event && event.split('.')[1] + 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 + } diff --git a/src/modules/core/gradiented.js b/src/modules/core/gradiented.js index 6c744e4..dd9c46f 100644 --- a/src/modules/core/gradiented.js +++ b/src/modules/core/gradiented.js @@ -1,13 +1,17 @@ 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 ) } ) + } diff --git a/src/modules/core/parser.js b/src/modules/core/parser.js index 1ff2380..12c9728 100644 --- a/src/modules/core/parser.js +++ b/src/modules/core/parser.js @@ -2,26 +2,32 @@ 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 + } diff --git a/src/modules/core/pointed.js b/src/modules/core/pointed.js index 95e6819..813b0e3 100644 --- a/src/modules/core/pointed.js +++ b/src/modules/core/pointed.js @@ -3,23 +3,31 @@ 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 ) + } diff --git a/src/modules/core/poly.js b/src/modules/core/poly.js index ad12020..56703a5 100644 --- a/src/modules/core/poly.js +++ b/src/modules/core/poly.js @@ -3,29 +3,39 @@ 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 ) ) + } diff --git a/src/modules/core/selector.js b/src/modules/core/selector.js index 24841c5..a60df02 100644 --- a/src/modules/core/selector.js +++ b/src/modules/core/selector.js @@ -3,13 +3,19 @@ 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 ) + } diff --git a/src/modules/core/textable.js b/src/modules/core/textable.js index 55df7c6..b0a0993 100644 --- a/src/modules/core/textable.js +++ b/src/modules/core/textable.js @@ -1,19 +1,25 @@ 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() + } diff --git a/src/modules/optional/arrange.js b/src/modules/optional/arrange.js index 6ce2eea..51e8605 100644 --- a/src/modules/optional/arrange.js +++ b/src/modules/optional/arrange.js @@ -3,109 +3,141 @@ 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 -}) +} ) diff --git a/src/modules/optional/class.js b/src/modules/optional/class.js index b08c82b..93ccd0e 100644 --- a/src/modules/optional/class.js +++ b/src/modules/optional/class.js @@ -3,42 +3,58 @@ 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 -}) +} ) diff --git a/src/modules/optional/css.js b/src/modules/optional/css.js index babee7a..d5378f4 100644 --- a/src/modules/optional/css.js +++ b/src/modules/optional/css.js @@ -3,68 +3,98 @@ 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) { return !!el.length }) - .forEach(function (el) { - let t = el.split(/\s*:\s*/) + this.node.style.cssText.split( /\s*;\s*/ ) + .filter( function ( el ) { + + return !!el.length + + } ) + .forEach( function ( el ) { + + let t = el.split( /\s*:\s*/ ) ret[t[0]] = t[1] - }) + + } ) return ret + } - if (arguments.length < 2) { + 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 -}) +} ) diff --git a/src/modules/optional/data.js b/src/modules/optional/data.js index 341d129..498e65a 100644 --- a/src/modules/optional/data.js +++ b/src/modules/optional/data.js @@ -1,26 +1,40 @@ 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) + : r === true || typeof v === 'string' || typeof v === 'number' ? v + : JSON.stringify( v ) ) + } return this + } -registerMethods('Dom', { data }) +registerMethods( 'Dom', { data } ) diff --git a/src/modules/optional/memory.js b/src/modules/optional/memory.js index 6478367..7c599f0 100644 --- a/src/modules/optional/memory.js +++ b/src/modules/optional/memory.js @@ -1,40 +1,60 @@ 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 } ) diff --git a/src/modules/optional/sugar.js b/src/modules/optional/sugar.js index 6001631..4b6e6f3 100644 --- a/src/modules/optional/sugar.js +++ b/src/modules/optional/sugar.js @@ -8,169 +8,227 @@ import SVGNumber from '../../types/SVGNumber.js' // Define list of available attributes for stroke and fill var sugar = { - stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'], - fill: ['color', 'opacity', 'rule'], - prefix: function (t, a) { + stroke: [ 'color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset' ], + fill: [ 'color', 'opacity', 'rule' ], + 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' - : 'both' - 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) + : isFinite( direction ) ? 'both' + : 'both' + 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 ) + }, // 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', @@ -186,19 +244,27 @@ 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 ) diff --git a/src/modules/optional/transform.js b/src/modules/optional/transform.js index b8f4c74..717fbf3 100644 --- a/src/modules/optional/transform.js +++ b/src/modules/optional/transform.js @@ -5,68 +5,92 @@ 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('(') - return [kv[0], - kv[1].split(delimiter) - .map(function (str) { return parseFloat(str) }) + var kv = str.trim().split( '(' ) + return [ kv[0], + 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,11 +3,13 @@ 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 diff --git a/src/types/ArrayPolyfill.js b/src/types/ArrayPolyfill.js index 4d2309f..0ee29a5 100644 --- a/src/types/ArrayPolyfill.js +++ b/src/types/ArrayPolyfill.js @@ -1,8 +1,10 @@ /* 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 {', @@ -12,25 +14,35 @@ 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 + } + } -})() + +} )() diff --git a/src/types/Box.js b/src/types/Box.js index e55f114..2fcb923 100644 --- a/src/types/Box.js +++ b/src/types/Box.js @@ -4,33 +4,45 @@ 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) { - var base = [0, 0, 0, 0] - 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) - : base + init ( source ) { + + var base = [ 0, 0, 0, 0 ] + 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 ) + : base this.x = source[0] || 0 this.y = source[1] || 0 @@ -44,105 +56,139 @@ 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 + 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 ) - 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] + + 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) - if (isNulledBox(box) && !domContains(this.node)) { - throw new Error('Element not in the dom') + box = cb( this.node ) + + 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 ) ) + } } -}) +} ) diff --git a/src/types/EventTarget.js b/src/types/EventTarget.js index 5a005fd..3d755bf 100644 --- a/src/types/EventTarget.js +++ b/src/types/EventTarget.js @@ -2,57 +2,79 @@ 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 () {} + } diff --git a/src/types/List.js b/src/types/List.js index 8bd3985..a2d2226 100644 --- a/src/types/List.js +++ b/src/types/List.js @@ -1,38 +1,62 @@ 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 ) + } diff --git a/src/types/Matrix.js b/src/types/Matrix.js index a1eb317..a9a311e 100644 --- a/src/types/Matrix.js +++ b/src/types/Matrix.js @@ -3,27 +3,33 @@ 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)) - : base + : typeof source === 'string' ? Matrix.fromArray( source.split( delimiter ).map( parseFloat ) ) + : Array.isArray( source ) ? Matrix.fromArray( source ) + : ( typeof source === 'object' && Matrix.isMatrixLike( source ) ) ? source + : ( typeof source === 'object' ) ? new Matrix().transform( source ) + : arguments.length === 6 ? Matrix.fromArray( [].slice.call( arguments ) ) + : base // Merge the source matrix with the base matrix this.a = source.a != null ? source.a : base.a @@ -34,56 +40,68 @@ 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 @@ -97,18 +115,20 @@ 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 @@ -123,20 +143,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 { @@ -158,38 +178,48 @@ 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) + : new Matrix( matrix ) + + return Matrix.matrixMultiply( l, r, this ) - 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) + : new Matrix( matrix ) + + return Matrix.matrixMultiply( l, r, this ) - 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 @@ -200,7 +230,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 @@ -209,8 +239,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 @@ -221,34 +251,46 @@ 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 @@ -261,19 +303,23 @@ 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 @@ -285,25 +331,33 @@ 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 @@ -311,27 +365,33 @@ 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 @@ -343,55 +403,75 @@ 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] + + return [ this.a, this.b, this.c, this.d, this.e, this.f ] + } valueOf () { + return { a: this.a, b: this.b, @@ -400,56 +480,62 @@ 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 || - o.c != null || - o.d != null || - o.e != null || - o.f != null + o.a != null + || o.b != null + || o.c != null + || o.d != null + || 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 - : 0 + : isFinite( o.skew ) ? o.skew + : isFinite( o.skewX ) ? o.skewX + : 0 var skewY = o.skew && o.skew.length ? o.skew[1] - : isFinite(o.skew) ? o.skew - : isFinite(o.skewY) ? o.skewY - : 0 + : isFinite( o.skew ) ? o.skew + : isFinite( o.skewY ) ? o.skewY + : 0 var scaleX = o.scale && o.scale.length ? o.scale[0] * flipX - : isFinite(o.scale) ? o.scale * flipX - : isFinite(o.scaleX) ? o.scaleX * flipX - : flipX + : isFinite( o.scale ) ? o.scale * flipX + : isFinite( o.scaleX ) ? o.scaleX * flipX + : flipX var scaleY = o.scale && o.scale.length ? o.scale[1] * flipY - : isFinite(o.scale) ? o.scale * flipY - : isFinite(o.scaleY) ? o.scaleY * flipY - : flipY + : 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 @@ -457,10 +543,12 @@ 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 @@ -478,23 +566,31 @@ 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() ) + } diff --git a/src/types/Morphable.js b/src/types/Morphable.js index 703cc00..87fa800 100644 --- a/src/types/Morphable.js +++ b/src/types/Morphable.js @@ -11,135 +11,200 @@ 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) + this._context = this._context + || 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] + + 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], @@ -150,13 +215,16 @@ 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 [ @@ -169,7 +237,9 @@ export class TransformBag { v.originX, v.originY ] + } + } TransformBag.defaults = { @@ -184,40 +254,56 @@ 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 = [ @@ -226,21 +312,29 @@ 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 + } - }) + } ) + } diff --git a/src/types/PathArray.js b/src/types/PathArray.js index 989cd8f..739218d 100644 --- a/src/types/PathArray.js +++ b/src/types/PathArray.js @@ -12,131 +12,182 @@ 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] + 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]] + return [ 'L', c[0], c[1] ] + }, - H: function (c, p) { + H: function ( c, p ) { + p.x = c[0] - return ['H', c[0]] + return [ 'H', c[0] ] + }, - V: function (c, p) { + V: function ( c, p ) { + p.y = c[0] - return ['V', 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]] + 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]] + 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]] + 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]] + 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'] + 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]] + return [ 'A', c[0], c[1], c[2], c[3], c[4], c[5], c[6] ] + } } -let mlhvqtcsaz = 'mlhvqtcsaz'.split('') +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' ) { -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() @@ -144,110 +195,154 @@ 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) + morph ( pathArray ) { + + pathArray = new PathArray( pathArray ) + + if ( this.equalCommands( 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 @@ -257,47 +352,61 @@ 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++) { - 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 ( i = 0, il = sourceArray.length; i < il; i++ ) { + + array[i] = [ sourceArray[i][0] ] + for ( j = 1, jl = sourceArray[i].length; j < jl; j++ ) { + + array[i][j] = sourceArray[i][j] + ( destinationArray[i][j] - sourceArray[i][j] ) * pos + } // For the two flags of the elliptical arc command, the SVG spec say: // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true // Elliptical arc command as an array followed by corresponding indexes: // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] // 0 1 2 3 4 5 6 7 - if (array[i][0] === 'A') { - array[i][4] = +(array[i][4] !== 0) - array[i][5] = +(array[i][5] !== 0) + 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' ...] @@ -308,30 +417,41 @@ 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') { + // If last letter was a move command and we got no new, it defaults to [L]ine + + } else if ( s === 'M' ) { + s = 'L' - } else if (s === 'm') { + + } 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() + } -}) +} ) diff --git a/src/types/Point.js b/src/types/Point.js index 27d81ea..16ae44d 100644 --- a/src/types/Point.js +++ b/src/types/Point.js @@ -1,45 +1,59 @@ 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 } + : { x: x, y: y } // merge source this.x = source.x == null ? base.x : source.x 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] + + 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() ) + } diff --git a/src/types/PointArray.js b/src/types/PointArray.js index b246b2f..581b7dc 100644 --- a/src/types/PointArray.js +++ b/src/types/PointArray.js @@ -3,76 +3,97 @@ 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 @@ -80,41 +101,54 @@ extend(PointArray, { y -= box.y // move every point - if (!isNaN(x) && !isNaN(y)) { - for (var i = this.length - 1; i >= 0; i--) { - this[i] = [this[i][0] + x, this[i][1] + y] + 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 } + } -}) +} ) diff --git a/src/types/SVGArray.js b/src/types/SVGArray.js index 7f27ec4..7d59af1 100644 --- a/src/types/SVGArray.js +++ b/src/types/SVGArray.js @@ -2,49 +2,65 @@ 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 + if ( array instanceof Array ) return array + + return array.trim().split( delimiter ).map( parseFloat ) - 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 ) + } -}) +} ) diff --git a/src/types/SVGNumber.js b/src/types/SVGNumber.js index ea21cbd..a35ed66 100644 --- a/src/types/SVGNumber.js +++ b/src/types/SVGNumber.js @@ -2,88 +2,126 @@ 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) + 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 ) { - if (unit) { // make value numeric - this.value = parseFloat(unit[1]) + this.value = parseFloat( unit[1] ) // normalize - if (unit[5] === '%') { this.value /= 100 } else if (unit[5] === 's') { + if ( unit[5] === '%' ) { + + this.value /= 100 + + } 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.value ) + this.unit + } toJSON () { + return this.toString() + } toArray () { - return [this.value, this.unit] + + 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 ) + } + } diff --git a/src/utils/adopter.js b/src/utils/adopter.js index 80bfd8a..6e526e3 100644 --- a/src/utils/adopter.js +++ b/src/utils/adopter.js @@ -5,136 +5,190 @@ 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 +export function makeInstance ( element ) { + + if ( element instanceof Base ) return element + + if ( typeof element === 'object' ) { + + return adopt( 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.instance instanceof Base ) return node.instance + + if ( !( node instanceof globals.window.SVGElement ) ) { + + return new elements.HtmlNode( node ) - 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] + modules = Array.isArray( modules ) ? modules : [ modules ] + + for ( i = modules.length - 1; i >= 0; i-- ) { + + for ( key in methods ) { - 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 ) + } + } + } diff --git a/src/utils/methods.js b/src/utils/methods.js index 70f9329..4973d13 100644 --- a/src/utils/methods.js +++ b/src/utils/methods.js @@ -2,41 +2,61 @@ 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 } : {} + } diff --git a/src/utils/utils.js b/src/utils/utils.js index 64c0eed..01cd49f 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -1,103 +1,143 @@ // 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 - : x + width / 2 - let by = string.includes('top') ? y - : string.includes('bottom') ? y + height - : y + height / 2 + 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 + : 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 ] + } diff --git a/src/utils/window.js b/src/utils/window.js index 9e51339..55f0bb6 100644 --- a/src/utils/window.js +++ b/src/utils/window.js @@ -3,7 +3,9 @@ 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 + } |