From: Evert Timberg Date: Sun, 1 Dec 2019 18:28:34 +0000 (-0500) Subject: Convert src/helpers to use ES6 exports (#6797) X-Git-Tag: v3.0.0-alpha~210 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5a99dff5f7eeaa3c59ae9c473ccbe6738f289593;p=thirdparty%2FChart.js.git Convert src/helpers to use ES6 exports (#6797) * Canvas helpers exported * Easing helpers use exports * Math helper uses export * Linting * RTL options use export * Allow object spread operator * Rest of helpers use `export` --- diff --git a/.eslintrc.yml b/.eslintrc.yml index fe38944da..ace7ae7a4 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -5,7 +5,7 @@ env: node: true parserOptions: - ecmaVersion: 6 + ecmaVersion: 2018 sourceType: 'module' plugins: ['html'] diff --git a/src/helpers/helpers.canvas.js b/src/helpers/helpers.canvas.js index 2d216a30c..8c3b92e09 100644 --- a/src/helpers/helpers.canvas.js +++ b/src/helpers/helpers.canvas.js @@ -10,193 +10,191 @@ const TWO_THIRDS_PI = PI * 2 / 3; /** * @namespace Chart.helpers.canvas */ -module.exports = { - /** - * Returns the aligned pixel value to avoid anti-aliasing blur - * @param {Chart} chart - The chart instance. - * @param {number} pixel - A pixel value. - * @param {number} width - The width of the element. - * @returns {number} The aligned pixel value. - * @private - */ - _alignPixel: function(chart, pixel, width) { - const devicePixelRatio = chart.currentDevicePixelRatio; - const halfWidth = width / 2; - return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth; - }, - - /** - * Clears the entire canvas associated to the given `chart`. - * @param {Chart} chart - The chart for which to clear the canvas. - */ - clear: function(chart) { - chart.ctx.clearRect(0, 0, chart.width, chart.height); - }, +/** + * Returns the aligned pixel value to avoid anti-aliasing blur + * @param {Chart} chart - The chart instance. + * @param {number} pixel - A pixel value. + * @param {number} width - The width of the element. + * @returns {number} The aligned pixel value. + * @private + */ +export function _alignPixel(chart, pixel, width) { + const devicePixelRatio = chart.currentDevicePixelRatio; + const halfWidth = width / 2; + return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth; +} - drawPoint: function(ctx, style, radius, x, y, rotation) { - var type, xOffset, yOffset, size, cornerRadius; - var rad = (rotation || 0) * RAD_PER_DEG; +/** + * Clears the entire canvas associated to the given `chart`. + * @param {Chart} chart - The chart for which to clear the canvas. + */ +export function clear(chart) { + chart.ctx.clearRect(0, 0, chart.width, chart.height); +} - if (style && typeof style === 'object') { - type = style.toString(); - if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { - ctx.save(); - ctx.translate(x, y); - ctx.rotate(rad); - ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height); - ctx.restore(); - return; - } - } +export function drawPoint(ctx, style, radius, x, y, rotation) { + var type, xOffset, yOffset, size, cornerRadius; + var rad = (rotation || 0) * RAD_PER_DEG; - if (isNaN(radius) || radius <= 0) { + if (style && typeof style === 'object') { + type = style.toString(); + if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { + ctx.save(); + ctx.translate(x, y); + ctx.rotate(rad); + ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height); + ctx.restore(); return; } + } - ctx.beginPath(); + if (isNaN(radius) || radius <= 0) { + return; + } - switch (style) { - // Default includes circle - default: - ctx.arc(x, y, radius, 0, DOUBLE_PI); - ctx.closePath(); - break; - case 'triangle': - ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); - rad += TWO_THIRDS_PI; - ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); - rad += TWO_THIRDS_PI; - ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); - ctx.closePath(); - break; - case 'rectRounded': - // NOTE: the rounded rect implementation changed to use `arc` instead of - // `quadraticCurveTo` since it generates better results when rect is - // almost a circle. 0.516 (instead of 0.5) produces results with visually - // closer proportion to the previous impl and it is inscribed in the - // circle with `radius`. For more details, see the following PRs: - // https://github.com/chartjs/Chart.js/issues/5597 - // https://github.com/chartjs/Chart.js/issues/5858 - cornerRadius = radius * 0.516; - size = radius - cornerRadius; - xOffset = Math.cos(rad + QUARTER_PI) * size; - yOffset = Math.sin(rad + QUARTER_PI) * size; - ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI); - ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad); - ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI); - ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI); - ctx.closePath(); - break; - case 'rect': - if (!rotation) { - size = Math.SQRT1_2 * radius; - ctx.rect(x - size, y - size, 2 * size, 2 * size); - break; - } - rad += QUARTER_PI; - /* falls through */ - case 'rectRot': - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + yOffset, y - xOffset); - ctx.lineTo(x + xOffset, y + yOffset); - ctx.lineTo(x - yOffset, y + xOffset); - ctx.closePath(); - break; - case 'crossRot': - rad += QUARTER_PI; - /* falls through */ - case 'cross': - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + xOffset, y + yOffset); - ctx.moveTo(x + yOffset, y - xOffset); - ctx.lineTo(x - yOffset, y + xOffset); - break; - case 'star': - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + xOffset, y + yOffset); - ctx.moveTo(x + yOffset, y - xOffset); - ctx.lineTo(x - yOffset, y + xOffset); - rad += QUARTER_PI; - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + xOffset, y + yOffset); - ctx.moveTo(x + yOffset, y - xOffset); - ctx.lineTo(x - yOffset, y + xOffset); - break; - case 'line': - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + xOffset, y + yOffset); - break; - case 'dash': - ctx.moveTo(x, y); - ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); + ctx.beginPath(); + + switch (style) { + // Default includes circle + default: + ctx.arc(x, y, radius, 0, DOUBLE_PI); + ctx.closePath(); + break; + case 'triangle': + ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + ctx.closePath(); + break; + case 'rectRounded': + // NOTE: the rounded rect implementation changed to use `arc` instead of + // `quadraticCurveTo` since it generates better results when rect is + // almost a circle. 0.516 (instead of 0.5) produces results with visually + // closer proportion to the previous impl and it is inscribed in the + // circle with `radius`. For more details, see the following PRs: + // https://github.com/chartjs/Chart.js/issues/5597 + // https://github.com/chartjs/Chart.js/issues/5858 + cornerRadius = radius * 0.516; + size = radius - cornerRadius; + xOffset = Math.cos(rad + QUARTER_PI) * size; + yOffset = Math.sin(rad + QUARTER_PI) * size; + ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI); + ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad); + ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI); + ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI); + ctx.closePath(); + break; + case 'rect': + if (!rotation) { + size = Math.SQRT1_2 * radius; + ctx.rect(x - size, y - size, 2 * size, 2 * size); break; } + rad += QUARTER_PI; + /* falls through */ + case 'rectRot': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + yOffset, y - xOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.lineTo(x - yOffset, y + xOffset); + ctx.closePath(); + break; + case 'crossRot': + rad += QUARTER_PI; + /* falls through */ + case 'cross': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + break; + case 'star': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + rad += QUARTER_PI; + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + break; + case 'line': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + break; + case 'dash': + ctx.moveTo(x, y); + ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); + break; + } - ctx.fill(); - ctx.stroke(); - }, + ctx.fill(); + ctx.stroke(); +} - /** - * Returns true if the point is inside the rectangle - * @param {object} point - The point to test - * @param {object} area - The rectangle - * @returns {boolean} - * @private - */ - _isPointInArea: function(point, area) { - var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. +/** + * Returns true if the point is inside the rectangle + * @param {object} point - The point to test + * @param {object} area - The rectangle + * @returns {boolean} + * @private + */ +export function _isPointInArea(point, area) { + var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. - return point.x > area.left - epsilon && point.x < area.right + epsilon && - point.y > area.top - epsilon && point.y < area.bottom + epsilon; - }, + return point.x > area.left - epsilon && point.x < area.right + epsilon && + point.y > area.top - epsilon && point.y < area.bottom + epsilon; +} - clipArea: function(ctx, area) { - ctx.save(); - ctx.beginPath(); - ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); - ctx.clip(); - }, +export function clipArea(ctx, area) { + ctx.save(); + ctx.beginPath(); + ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); + ctx.clip(); +} - unclipArea: function(ctx) { - ctx.restore(); - }, +export function unclipArea(ctx) { + ctx.restore(); +} - /** - * @private - */ - _steppedLineTo: function(ctx, previous, target, flip, mode) { - if (mode === 'middle') { - const midpoint = (previous.x + target.x) / 2.0; - ctx.lineTo(midpoint, flip ? target.y : previous.y); - ctx.lineTo(midpoint, flip ? previous.y : target.y); - } else if ((mode === 'after' && !flip) || (mode !== 'after' && flip)) { - ctx.lineTo(previous.x, target.y); - } else { - ctx.lineTo(target.x, previous.y); - } - ctx.lineTo(target.x, target.y); - }, - - /** - * @private - */ - _bezierCurveTo: function(ctx, previous, target, flip) { - ctx.bezierCurveTo( - flip ? previous.controlPointPreviousX : previous.controlPointNextX, - flip ? previous.controlPointPreviousY : previous.controlPointNextY, - flip ? target.controlPointNextX : target.controlPointPreviousX, - flip ? target.controlPointNextY : target.controlPointPreviousY, - target.x, - target.y); +/** + * @private + */ +export function _steppedLineTo(ctx, previous, target, flip, mode) { + if (mode === 'middle') { + const midpoint = (previous.x + target.x) / 2.0; + ctx.lineTo(midpoint, flip ? target.y : previous.y); + ctx.lineTo(midpoint, flip ? previous.y : target.y); + } else if ((mode === 'after' && !flip) || (mode !== 'after' && flip)) { + ctx.lineTo(previous.x, target.y); + } else { + ctx.lineTo(target.x, previous.y); } -}; + ctx.lineTo(target.x, target.y); +} + +/** + * @private + */ +export function _bezierCurveTo(ctx, previous, target, flip) { + ctx.bezierCurveTo( + flip ? previous.controlPointPreviousX : previous.controlPointNextX, + flip ? previous.controlPointPreviousY : previous.controlPointNextY, + flip ? target.controlPointNextX : target.controlPointPreviousX, + flip ? target.controlPointNextY : target.controlPointPreviousY, + target.x, + target.y); +} diff --git a/src/helpers/helpers.core.js b/src/helpers/helpers.core.js index 9276a18b8..100d4edf4 100644 --- a/src/helpers/helpers.core.js +++ b/src/helpers/helpers.core.js @@ -3,342 +3,343 @@ /** * @namespace Chart.helpers */ -var helpers = { - /** - * An empty function that can be used, for example, for optional callback. - */ - noop: function() {}, - - /** - * Returns a unique id, sequentially generated from a global variable. - * @returns {number} - * @function - */ - uid: (function() { - var id = 0; - return function() { - return id++; - }; - }()), - - /** - * Returns true if `value` is neither null nor undefined, else returns false. - * @param {*} value - The value to test. - * @returns {boolean} - * @since 2.7.0 - */ - isNullOrUndef: function(value) { - return value === null || typeof value === 'undefined'; - }, - - /** - * Returns true if `value` is an array (including typed arrays), else returns false. - * @param {*} value - The value to test. - * @returns {boolean} - * @function - */ - isArray: function(value) { - if (Array.isArray && Array.isArray(value)) { - return true; - } - var type = Object.prototype.toString.call(value); - if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') { - return true; - } - return false; - }, - - /** - * Returns true if `value` is an object (excluding null), else returns false. - * @param {*} value - The value to test. - * @returns {boolean} - * @since 2.7.0 - */ - isObject: function(value) { - return value !== null && Object.prototype.toString.call(value) === '[object Object]'; - }, - - /** - * Returns true if `value` is a finite number, else returns false - * @param {*} value - The value to test. - * @returns {boolean} - */ - isFinite: function(value) { - return (typeof value === 'number' || value instanceof Number) && isFinite(value); - }, - - /** - * Returns `value` if defined, else returns `defaultValue`. - * @param {*} value - The value to return if defined. - * @param {*} defaultValue - The value to return if `value` is undefined. - * @returns {*} - */ - valueOrDefault: function(value, defaultValue) { - return typeof value === 'undefined' ? defaultValue : value; - }, - - /** - * Returns value at the given `index` in array if defined, else returns `defaultValue`. - * @param {Array} value - The array to lookup for value at `index`. - * @param {number} index - The index in `value` to lookup for value. - * @param {*} defaultValue - The value to return if `value[index]` is undefined. - * @returns {*} - */ - valueAtIndexOrDefault: function(value, index, defaultValue) { - return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); - }, - - /** - * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the - * value returned by `fn`. If `fn` is not a function, this method returns undefined. - * @param {function} fn - The function to call. - * @param {Array|undefined|null} args - The arguments with which `fn` should be called. - * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. - * @returns {*} - */ - callback: function(fn, args, thisArg) { - if (fn && typeof fn.call === 'function') { - return fn.apply(thisArg, args); - } - }, - - /** - * Note(SB) for performance sake, this method should only be used when loopable type - * is unknown or in none intensive code (not called often and small loopable). Else - * it's preferable to use a regular for() loop and save extra function calls. - * @param {object|Array} loopable - The object or array to be iterated. - * @param {function} fn - The function to call for each item. - * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. - * @param {boolean} [reverse] - If true, iterates backward on the loopable. - */ - each: function(loopable, fn, thisArg, reverse) { - var i, len, keys; - if (helpers.isArray(loopable)) { - len = loopable.length; - if (reverse) { - for (i = len - 1; i >= 0; i--) { - fn.call(thisArg, loopable[i], i); - } - } else { - for (i = 0; i < len; i++) { - fn.call(thisArg, loopable[i], i); - } +/** + * An empty function that can be used, for example, for optional callback. + */ +export function noop() {} + +/** + * Returns a unique id, sequentially generated from a global variable. + * @returns {number} + * @function + */ +export const uid = (function() { + var id = 0; + return function() { + return id++; + }; +}()); + +/** + * Returns true if `value` is neither null nor undefined, else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @since 2.7.0 + */ +export function isNullOrUndef(value) { + return value === null || typeof value === 'undefined'; +} + +/** + * Returns true if `value` is an array (including typed arrays), else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @function + */ +export function isArray(value) { + if (Array.isArray && Array.isArray(value)) { + return true; + } + var type = Object.prototype.toString.call(value); + if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') { + return true; + } + return false; +} + +/** + * Returns true if `value` is an object (excluding null), else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @since 2.7.0 + */ +export function isObject(value) { + return value !== null && Object.prototype.toString.call(value) === '[object Object]'; +} + +/** + * Returns true if `value` is a finite number, else returns false + * @param {*} value - The value to test. + * @returns {boolean} + */ +const isNumberFinite = (value) => { + return (typeof value === 'number' || value instanceof Number) && isFinite(value); +}; +export { + isNumberFinite as isFinite, +}; + +/** + * Returns `value` if defined, else returns `defaultValue`. + * @param {*} value - The value to return if defined. + * @param {*} defaultValue - The value to return if `value` is undefined. + * @returns {*} + */ +export function valueOrDefault(value, defaultValue) { + return typeof value === 'undefined' ? defaultValue : value; +} + +/** + * Returns value at the given `index` in array if defined, else returns `defaultValue`. + * @param {Array} value - The array to lookup for value at `index`. + * @param {number} index - The index in `value` to lookup for value. + * @param {*} defaultValue - The value to return if `value[index]` is undefined. + * @returns {*} + */ +export function valueAtIndexOrDefault(value, index, defaultValue) { + return valueOrDefault(isArray(value) ? value[index] : value, defaultValue); +} + +/** + * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the + * value returned by `fn`. If `fn` is not a function, this method returns undefined. + * @param {function} fn - The function to call. + * @param {Array|undefined|null} args - The arguments with which `fn` should be called. + * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. + * @returns {*} + */ +export function callback(fn, args, thisArg) { + if (fn && typeof fn.call === 'function') { + return fn.apply(thisArg, args); + } +} + +/** + * Note(SB) for performance sake, this method should only be used when loopable type + * is unknown or in none intensive code (not called often and small loopable). Else + * it's preferable to use a regular for() loop and save extra function calls. + * @param {object|Array} loopable - The object or array to be iterated. + * @param {function} fn - The function to call for each item. + * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. + * @param {boolean} [reverse] - If true, iterates backward on the loopable. + */ +export function each(loopable, fn, thisArg, reverse) { + var i, len, keys; + if (isArray(loopable)) { + len = loopable.length; + if (reverse) { + for (i = len - 1; i >= 0; i--) { + fn.call(thisArg, loopable[i], i); } - } else if (helpers.isObject(loopable)) { - keys = Object.keys(loopable); - len = keys.length; + } else { for (i = 0; i < len; i++) { - fn.call(thisArg, loopable[keys[i]], keys[i]); + fn.call(thisArg, loopable[i], i); } } - }, - - /** - * Returns true if the `a0` and `a1` arrays have the same content, else returns false. - * @see https://stackoverflow.com/a/14853974 - * @param {Array} a0 - The array to compare - * @param {Array} a1 - The array to compare - * @returns {boolean} - */ - arrayEquals: function(a0, a1) { - var i, ilen, v0, v1; - - if (!a0 || !a1 || a0.length !== a1.length) { - return false; + } else if (isObject(loopable)) { + keys = Object.keys(loopable); + len = keys.length; + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[keys[i]], keys[i]); } + } +} - for (i = 0, ilen = a0.length; i < ilen; ++i) { - v0 = a0[i]; - v1 = a1[i]; +/** + * Returns true if the `a0` and `a1` arrays have the same content, else returns false. + * @see https://stackoverflow.com/a/14853974 + * @param {Array} a0 - The array to compare + * @param {Array} a1 - The array to compare + * @returns {boolean} + */ +export function arrayEquals(a0, a1) { + var i, ilen, v0, v1; - if (v0 instanceof Array && v1 instanceof Array) { - if (!helpers.arrayEquals(v0, v1)) { - return false; - } - } else if (v0 !== v1) { - // NOTE: two different object instances will never be equal: {x:20} != {x:20} + if (!a0 || !a1 || a0.length !== a1.length) { + return false; + } + + for (i = 0, ilen = a0.length; i < ilen; ++i) { + v0 = a0[i]; + v1 = a1[i]; + + if (v0 instanceof Array && v1 instanceof Array) { + if (!arrayEquals(v0, v1)) { return false; } - } - - return true; - }, - - /** - * Returns true if the `a0` and `a1` arrays have the same content, else returns false. - * @param {Array} a0 - The array to compare - * @param {Array} a1 - The array to compare - * @returns {boolean} - */ - _elementsEqual: function(a0, a1) { - let i, ilen, v0, v1; - - if (!a0 || !a1 || a0.length !== a1.length) { + } else if (v0 !== v1) { + // NOTE: two different object instances will never be equal: {x:20} != {x:20} return false; } + } - for (i = 0, ilen = a0.length; i < ilen; ++i) { - v0 = a0[i]; - v1 = a1[i]; + return true; +} - if (v0.datasetIndex !== v1.datasetIndex || v0.index !== v1.index) { - return false; - } - } +/** + * Returns true if the `a0` and `a1` arrays have the same content, else returns false. + * @param {Array} a0 - The array to compare + * @param {Array} a1 - The array to compare + * @returns {boolean} + */ +export function _elementsEqual(a0, a1) { + let i, ilen, v0, v1; - return true; - }, - - /** - * Returns a deep copy of `source` without keeping references on objects and arrays. - * @param {*} source - The value to clone. - * @returns {*} - */ - clone: function(source) { - if (helpers.isArray(source)) { - return source.map(helpers.clone); + if (!a0 || !a1 || a0.length !== a1.length) { + return false; + } + + for (i = 0, ilen = a0.length; i < ilen; ++i) { + v0 = a0[i]; + v1 = a1[i]; + + if (v0.datasetIndex !== v1.datasetIndex || v0.index !== v1.index) { + return false; } + } - if (helpers.isObject(source)) { - var target = {}; - var keys = Object.keys(source); - var klen = keys.length; - var k = 0; + return true; +} - for (; k < klen; ++k) { - target[keys[k]] = helpers.clone(source[keys[k]]); - } +/** + * Returns a deep copy of `source` without keeping references on objects and arrays. + * @param {*} source - The value to clone. + * @returns {*} + */ +export function clone(source) { + if (isArray(source)) { + return source.map(clone); + } + + if (isObject(source)) { + var target = {}; + var keys = Object.keys(source); + var klen = keys.length; + var k = 0; - return target; + for (; k < klen; ++k) { + target[keys[k]] = clone(source[keys[k]]); } - return source; - }, + return target; + } - /** - * The default merger when Chart.helpers.merge is called without merger option. - * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback. - * @private - */ - _merger: function(key, target, source, options) { - var tval = target[key]; - var sval = source[key]; + return source; +} - if (helpers.isObject(tval) && helpers.isObject(sval)) { - helpers.merge(tval, sval, options); - } else { - target[key] = helpers.clone(sval); - } - }, - - /** - * Merges source[key] in target[key] only if target[key] is undefined. - * @private - */ - _mergerIf: function(key, target, source) { - var tval = target[key]; - var sval = source[key]; - - if (helpers.isObject(tval) && helpers.isObject(sval)) { - helpers.mergeIf(tval, sval); - } else if (!Object.prototype.hasOwnProperty.call(target, key)) { - target[key] = helpers.clone(sval); - } - }, - - /** - * Recursively deep copies `source` properties into `target` with the given `options`. - * IMPORTANT: `target` is not cloned and will be updated with `source` properties. - * @param {object} target - The target object in which all sources are merged into. - * @param {object|object[]} source - Object(s) to merge into `target`. - * @param {object} [options] - Merging options: - * @param {function} [options.merger] - The merge method (key, target, source, options) - * @returns {object} The `target` object. - */ - merge: function(target, source, options) { - var sources = helpers.isArray(source) ? source : [source]; - var ilen = sources.length; - var merge, i, keys, klen, k; - - if (!helpers.isObject(target)) { - return target; - } +/** + * The default merger when Chart.helpers.merge is called without merger option. + * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback. + * @private + */ +export function _merger(key, target, source, options) { + var tval = target[key]; + var sval = source[key]; + + if (isObject(tval) && isObject(sval)) { + // eslint-disable-next-line no-use-before-define + merge(tval, sval, options); + } else { + target[key] = clone(sval); + } +} - options = options || {}; - merge = options.merger || helpers._merger; +/** + * Recursively deep copies `source` properties into `target` with the given `options`. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {object} target - The target object in which all sources are merged into. + * @param {object|object[]} source - Object(s) to merge into `target`. + * @param {object} [options] - Merging options: + * @param {function} [options.merger] - The merge method (key, target, source, options) + * @returns {object} The `target` object. + */ +export function merge(target, source, options) { + var sources = isArray(source) ? source : [source]; + var ilen = sources.length; + var merger, i, keys, klen, k; - for (i = 0; i < ilen; ++i) { - source = sources[i]; - if (!helpers.isObject(source)) { - continue; - } + if (!isObject(target)) { + return target; + } - keys = Object.keys(source); - for (k = 0, klen = keys.length; k < klen; ++k) { - merge(keys[k], target, source, options); - } + options = options || {}; + merger = options.merger || _merger; + + for (i = 0; i < ilen; ++i) { + source = sources[i]; + if (!isObject(source)) { + continue; } - return target; - }, - - /** - * Recursively deep copies `source` properties into `target` *only* if not defined in target. - * IMPORTANT: `target` is not cloned and will be updated with `source` properties. - * @param {object} target - The target object in which all sources are merged into. - * @param {object|object[]} source - Object(s) to merge into `target`. - * @returns {object} The `target` object. - */ - mergeIf: function(target, source) { - return helpers.merge(target, source, {merger: helpers._mergerIf}); - }, - - /** - * Applies the contents of two or more objects together into the first object. - * @param {object} target - The target object in which all objects are merged into. - * @param {object} arg1 - Object containing additional properties to merge in target. - * @param {object} argN - Additional objects containing properties to merge in target. - * @returns {object} The `target` object. - */ - extend: Object.assign || function(target) { - return helpers.merge(target, [].slice.call(arguments, 1), { - merger: function(key, dst, src) { - dst[key] = src[key]; - } - }); - }, - - /** - * Basic javascript inheritance based on the model created in Backbone.js - */ - inherits: function(extensions) { - var me = this; - var ChartElement = (extensions && Object.prototype.hasOwnProperty.call(extensions, 'constructor')) ? extensions.constructor : function() { - return me.apply(this, arguments); - }; - - var Surrogate = function() { - this.constructor = ChartElement; - }; - - Surrogate.prototype = me.prototype; - ChartElement.prototype = new Surrogate(); - ChartElement.extend = helpers.inherits; - - if (extensions) { - helpers.extend(ChartElement.prototype, extensions); + keys = Object.keys(source); + for (k = 0, klen = keys.length; k < klen; ++k) { + merger(keys[k], target, source, options); } + } - ChartElement.__super__ = me.prototype; - return ChartElement; - }, + return target; +} - _deprecated: function(scope, value, previous, current) { - if (value !== undefined) { - console.warn(scope + ': "' + previous + - '" is deprecated. Please use "' + current + '" instead'); - } +/** + * Recursively deep copies `source` properties into `target` *only* if not defined in target. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {object} target - The target object in which all sources are merged into. + * @param {object|object[]} source - Object(s) to merge into `target`. + * @returns {object} The `target` object. + */ +export function mergeIf(target, source) { + // eslint-disable-next-line no-use-before-define + return merge(target, source, {merger: _mergerIf}); +} + +/** + * Merges source[key] in target[key] only if target[key] is undefined. + * @private + */ +export function _mergerIf(key, target, source) { + var tval = target[key]; + var sval = source[key]; + + if (isObject(tval) && isObject(sval)) { + mergeIf(tval, sval); + } else if (!Object.prototype.hasOwnProperty.call(target, key)) { + target[key] = clone(sval); } +} + +/** + * Applies the contents of two or more objects together into the first object. + * @param {object} target - The target object in which all objects are merged into. + * @param {object} arg1 - Object containing additional properties to merge in target. + * @param {object} argN - Additional objects containing properties to merge in target. + * @returns {object} The `target` object. + */ +export const extend = Object.assign || function(target) { + return merge(target, [].slice.call(arguments, 1), { + merger: function(key, dst, src) { + dst[key] = src[key]; + } + }); }; -module.exports = helpers; +/** + * Basic javascript inheritance based on the model created in Backbone.js + */ +export function inherits(extensions) { + var me = this; + var ChartElement = (extensions && Object.prototype.hasOwnProperty.call(extensions, 'constructor')) ? extensions.constructor : function() { + return me.apply(this, arguments); + }; + + var Surrogate = function() { + this.constructor = ChartElement; + }; + + Surrogate.prototype = me.prototype; + ChartElement.prototype = new Surrogate(); + ChartElement.extend = inherits; + + if (extensions) { + extend(ChartElement.prototype, extensions); + } + + ChartElement.__super__ = me.prototype; + return ChartElement; +} + +export function _deprecated(scope, value, previous, current) { + if (value !== undefined) { + console.warn(scope + ': "' + previous + + '" is deprecated. Please use "' + current + '" instead'); + } +} diff --git a/src/helpers/helpers.easing.js b/src/helpers/helpers.easing.js index 5c909953e..586de37f0 100644 --- a/src/helpers/helpers.easing.js +++ b/src/helpers/helpers.easing.js @@ -5,7 +5,7 @@ * @namespace Chart.helpers.easing.effects * @see http://www.robertpenner.com/easing/ */ -var effects = { +export const effects = { linear: function(t) { return t; }, @@ -231,7 +231,3 @@ var effects = { return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; } }; - -module.exports = { - effects: effects -}; diff --git a/src/helpers/helpers.math.js b/src/helpers/helpers.math.js index 4cf45f9b8..f08ff2c1e 100644 --- a/src/helpers/helpers.math.js +++ b/src/helpers/helpers.math.js @@ -4,41 +4,37 @@ * @alias Chart.helpers.math * @namespace */ -var exports = { - /** - * Returns an array of factors sorted from 1 to sqrt(value) - * @private - */ - _factorize: function(value) { - var result = []; - var sqrt = Math.sqrt(value); - var i; +/** + * Returns an array of factors sorted from 1 to sqrt(value) + * @private + */ +export function _factorize(value) { + var result = []; + var sqrt = Math.sqrt(value); + var i; - for (i = 1; i < sqrt; i++) { - if (value % i === 0) { - result.push(i); - result.push(value / i); - } - } - if (sqrt === (sqrt | 0)) { // if value is a square number - result.push(sqrt); + for (i = 1; i < sqrt; i++) { + if (value % i === 0) { + result.push(i); + result.push(value / i); } + } + if (sqrt === (sqrt | 0)) { // if value is a square number + result.push(sqrt); + } - result.sort(function(a, b) { - return a - b; - }).pop(); - return result; - }, + result.sort(function(a, b) { + return a - b; + }).pop(); + return result; +} - log10: Math.log10 || function(x) { - var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. - // Check for whole powers of 10, - // which due to floating point rounding error should be corrected. - var powerOf10 = Math.round(exponent); - var isPowerOf10 = x === Math.pow(10, powerOf10); +export const log10 = Math.log10 || function(x) { + var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. + // Check for whole powers of 10, + // which due to floating point rounding error should be corrected. + var powerOf10 = Math.round(exponent); + var isPowerOf10 = x === Math.pow(10, powerOf10); - return isPowerOf10 ? powerOf10 : exponent; - } + return isPowerOf10 ? powerOf10 : exponent; }; - -module.exports = exports; diff --git a/src/helpers/helpers.options.js b/src/helpers/helpers.options.js index 0bca84dba..e2dbf5870 100644 --- a/src/helpers/helpers.options.js +++ b/src/helpers/helpers.options.js @@ -1,9 +1,7 @@ 'use strict'; -var defaults = require('../core/core.defaults'); -var helpers = require('./helpers.core'); - -var valueOrDefault = helpers.valueOrDefault; +import defaults from '../core/core.defaults'; +import {isNullOrUndef, isArray, isObject, valueOrDefault} from './helpers.core'; /** * Converts the given font object into a CSS font string. @@ -12,7 +10,7 @@ var valueOrDefault = helpers.valueOrDefault; * @private */ function toFontString(font) { - if (!font || helpers.isNullOrUndef(font.size) || helpers.isNullOrUndef(font.family)) { + if (!font || isNullOrUndef(font.size) || isNullOrUndef(font.family)) { return null; } @@ -26,122 +24,120 @@ function toFontString(font) { * @alias Chart.helpers.options * @namespace */ -module.exports = { - /** - * Converts the given line height `value` in pixels for a specific font `size`. - * @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). - * @param {number} size - The font size (in pixels) used to resolve relative `value`. - * @returns {number} The effective line height in pixels (size * 1.2 if value is invalid). - * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height - * @since 2.7.0 - */ - toLineHeight: function(value, size) { - var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); - if (!matches || matches[1] === 'normal') { - return size * 1.2; - } +/** + * Converts the given line height `value` in pixels for a specific font `size`. + * @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). + * @param {number} size - The font size (in pixels) used to resolve relative `value`. + * @returns {number} The effective line height in pixels (size * 1.2 if value is invalid). + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height + * @since 2.7.0 + */ +export function toLineHeight(value, size) { + var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); + if (!matches || matches[1] === 'normal') { + return size * 1.2; + } - value = +matches[2]; + value = +matches[2]; - switch (matches[3]) { - case 'px': - return value; - case '%': - value /= 100; - break; - default: - break; - } + switch (matches[3]) { + case 'px': + return value; + case '%': + value /= 100; + break; + default: + break; + } - return size * value; - }, + return size * value; +} - /** - * Converts the given value into a padding object with pre-computed width/height. - * @param {number|object} value - If a number, set the value to all TRBL component, - * else, if and object, use defined properties and sets undefined ones to 0. - * @returns {object} The padding values (top, right, bottom, left, width, height) - * @since 2.7.0 - */ - toPadding: function(value) { - var t, r, b, l; +/** + * Converts the given value into a padding object with pre-computed width/height. + * @param {number|object} value - If a number, set the value to all TRBL component, + * else, if and object, use defined properties and sets undefined ones to 0. + * @returns {object} The padding values (top, right, bottom, left, width, height) + * @since 2.7.0 + */ +export function toPadding(value) { + var t, r, b, l; - if (helpers.isObject(value)) { - t = +value.top || 0; - r = +value.right || 0; - b = +value.bottom || 0; - l = +value.left || 0; - } else { - t = r = b = l = +value || 0; - } + if (isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } - return { - top: t, - right: r, - bottom: b, - left: l, - height: t + b, - width: l + r - }; - }, + return { + top: t, + right: r, + bottom: b, + left: l, + height: t + b, + width: l + r + }; +} - /** - * Parses font options and returns the font object. - * @param {object} options - A object that contains font options to be parsed. - * @return {object} The font object. - * @todo Support font.* options and renamed to toFont(). - * @private - */ - _parseFont: function(options) { - var globalDefaults = defaults.global; - var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); - var font = { - family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily), - lineHeight: helpers.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size), - size: size, - style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle), - weight: null, - string: '' - }; +/** + * Parses font options and returns the font object. + * @param {object} options - A object that contains font options to be parsed. + * @return {object} The font object. + * @todo Support font.* options and renamed to toFont(). + * @private + */ +export function _parseFont(options) { + var globalDefaults = defaults.global; + var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); + var font = { + family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily), + lineHeight: toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size), + size: size, + style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle), + weight: null, + string: '' + }; - font.string = toFontString(font); - return font; - }, + font.string = toFontString(font); + return font; +} - /** - * Evaluates the given `inputs` sequentially and returns the first defined value. - * @param {Array} inputs - An array of values, falling back to the last value. - * @param {object} [context] - If defined and the current value is a function, the value - * is called with `context` as first argument and the result becomes the new input. - * @param {number} [index] - If defined and the current value is an array, the value - * at `index` become the new input. - * @param {object} [info] - object to return information about resolution in - * @param {boolean} [info.cacheable] - Will be set to `false` if option is not cacheable. - * @since 2.7.0 - */ - resolve: function(inputs, context, index, info) { - var cacheable = true; - var i, ilen, value; +/** + * Evaluates the given `inputs` sequentially and returns the first defined value. + * @param {Array} inputs - An array of values, falling back to the last value. + * @param {object} [context] - If defined and the current value is a function, the value + * is called with `context` as first argument and the result becomes the new input. + * @param {number} [index] - If defined and the current value is an array, the value + * at `index` become the new input. + * @param {object} [info] - object to return information about resolution in + * @param {boolean} [info.cacheable] - Will be set to `false` if option is not cacheable. + * @since 2.7.0 + */ +export function resolve(inputs, context, index, info) { + var cacheable = true; + var i, ilen, value; - for (i = 0, ilen = inputs.length; i < ilen; ++i) { - value = inputs[i]; - if (value === undefined) { - continue; - } - if (context !== undefined && typeof value === 'function') { - value = value(context); - cacheable = false; - } - if (index !== undefined && helpers.isArray(value)) { - value = value[index]; - cacheable = false; - } - if (value !== undefined) { - if (info && !cacheable) { - info.cacheable = false; - } - return value; + for (i = 0, ilen = inputs.length; i < ilen; ++i) { + value = inputs[i]; + if (value === undefined) { + continue; + } + if (context !== undefined && typeof value === 'function') { + value = value(context); + cacheable = false; + } + if (index !== undefined && isArray(value)) { + value = value[index]; + cacheable = false; + } + if (value !== undefined) { + if (info && !cacheable) { + info.cacheable = false; } + return value; } } -}; +} diff --git a/src/helpers/helpers.rtl.js b/src/helpers/helpers.rtl.js index 392b0534b..ebd5e7c0b 100644 --- a/src/helpers/helpers.rtl.js +++ b/src/helpers/helpers.rtl.js @@ -42,11 +42,11 @@ var getLtrAdapter = function() { }; }; -var getAdapter = function(rtl, rectX, width) { +const getAdapter = function(rtl, rectX, width) { return rtl ? getRtlAdapter(rectX, width) : getLtrAdapter(); }; -var overrideTextDirection = function(ctx, direction) { +const overrideTextDirection = function(ctx, direction) { var style, original; if (direction === 'ltr' || direction === 'rtl') { style = ctx.canvas.style; @@ -60,7 +60,7 @@ var overrideTextDirection = function(ctx, direction) { } }; -var restoreTextDirection = function(ctx) { +const restoreTextDirection = function(ctx) { var original = ctx.prevTextDirection; if (original !== undefined) { delete ctx.prevTextDirection; @@ -68,8 +68,8 @@ var restoreTextDirection = function(ctx) { } }; -module.exports = { - getRtlAdapter: getAdapter, - overrideTextDirection: overrideTextDirection, - restoreTextDirection: restoreTextDirection, +export { + getAdapter as getRtlAdapter, + overrideTextDirection, + restoreTextDirection }; diff --git a/src/helpers/index.js b/src/helpers/index.js index 167c550b6..fa7efa3b0 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -1,8 +1,17 @@ 'use strict'; -module.exports = require('./helpers.core'); -module.exports.easing = require('./helpers.easing'); -module.exports.canvas = require('./helpers.canvas'); -module.exports.options = require('./helpers.options'); -module.exports.math = require('./helpers.math'); -module.exports.rtl = require('./helpers.rtl'); +import * as coreHelpers from './helpers.core'; +import * as canvas from './helpers.canvas'; +import * as easing from './helpers.easing'; +import * as options from './helpers.options'; +import * as math from './helpers.math'; +import * as rtl from './helpers.rtl'; + +export default { + ...coreHelpers, + canvas, + easing, + options, + math, + rtl, +}; diff --git a/test/specs/global.namespace.tests.js b/test/specs/global.namespace.tests.js index 49942fc9a..89f8cad5b 100644 --- a/test/specs/global.namespace.tests.js +++ b/test/specs/global.namespace.tests.js @@ -37,10 +37,5 @@ describe('Chart namespace', function() { it('should be an object', function() { expect(Chart.helpers instanceof Object).toBeTruthy(); }); - it('should contains "helpers" namespaces', function() { - expect(Chart.helpers.easing instanceof Object).toBeTruthy(); - expect(Chart.helpers.canvas instanceof Object).toBeTruthy(); - expect(Chart.helpers.options instanceof Object).toBeTruthy(); - }); }); });