* `helpers.getValueAtIndexOrDefault` was renamed to `helpers.valueAtIndexOrDefault`
* `helpers.easingEffects` was renamed to `helpers.easing.effects`
* `helpers.log10` was renamed to `helpers.math.log10`
+* `helpers.almostEquals` was renamed to `helpers.math.almostEquals`
+* `helpers.almostWhole` was renamed to `helpers.math.almostWhole`
+* `helpers._decimalPlaces` was renamed to `helpers.math._decimalPlaces`
+* `helpers.distanceBetweenPoints` was renamed to `helpers.math.distanceBetweenPoints`
+* `helpers.isNumber` was renamed to `helpers.math.isNumber`
+* `helpers.sign` was renamed to `helpers.math.sign`
+* `helpers.toDegrees` was renamed to `helpers.math.toDegrees`
+* `helpers.toRadians` was renamed to `helpers.math.toRadians`
+* `helpers.getAngleFromPoint` was renamed to `helpers.math.getAngleFromPoint`
+* `helpers.splineCurveMonotone` was renamed to `helpers.curve.splineCurveMonotone`
+* `helpers.splineCurve` was renamed to `helpers.curve.splineCurve`
+* `helpers.retinaScale` was renamed to `helpers.dom.retinaScale`
+* `helpers.getMaximumWidth` was renamed to `helpers.dom.getMaximumWidth`
+* `helpers.getMaximumHeight` was renamed to `helpers.dom.getMaximumHeight`
+* `helpers.getRelativePosition` was renamed to `helpers.dom.getRelativePosition`
+* `helpers.getStyle` was renamed to `helpers.dom.getStyle`
* `Chart.Animation.animationObject` was renamed to `Chart.Animation`
* `Chart.Animation.chartInstance` was renamed to `Chart.Animation.chart`
* `DatasetController.updateElement` was renamed to `DatasetController.updateElements`
value = custom.barStart;
length = custom.barEnd - custom.barStart;
// bars crossing origin are not stacked
- if (value !== 0 && helpers.sign(value) !== helpers.sign(custom.barEnd)) {
+ if (value !== 0 && helpers.math.sign(value) !== helpers.math.sign(custom.barEnd)) {
start = 0;
}
start += value;
}
if (lineModel.cubicInterpolationMode === 'monotone') {
- helpers.splineCurveMonotone(points);
+ helpers.curve.splineCurveMonotone(points);
} else {
for (i = 0, ilen = points.length; i < ilen; ++i) {
const model = points[i]._model;
- const controlPoints = helpers.splineCurve(
+ const controlPoints = helpers.curve.splineCurve(
points[Math.max(0, i - 1)]._model,
model,
points[Math.min(i + 1, ilen - 1)]._model,
for (i = 0, ilen = points.length; i < ilen; ++i) {
model = points[i]._model;
- controlPoints = helpers.splineCurve(
+ controlPoints = helpers.curve.splineCurve(
previousItem(points, i)._model,
model,
nextItem(points, i)._model,
// Before init plugin notification
plugins.notify(me, 'beforeInit');
- helpers.retinaScale(me, me.options.devicePixelRatio);
+ helpers.dom.retinaScale(me, me.options.devicePixelRatio);
me.bindEvents();
// the canvas display style uses the same integer values to avoid blurring effect.
// Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed
- var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas)));
- var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)));
+ var newWidth = Math.max(0, Math.floor(helpers.dom.getMaximumWidth(canvas)));
+ var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.dom.getMaximumHeight(canvas)));
if (me.width === newWidth && me.height === newHeight) {
return;
canvas.style.width = newWidth + 'px';
canvas.style.height = newHeight + 'px';
- helpers.retinaScale(me, options.devicePixelRatio);
+ helpers.dom.retinaScale(me, options.devicePixelRatio);
if (!silent) {
// Notify any plugins about the resize
break;
}
otherValue = stack.values[datasetIndex];
- if (!isNaN(otherValue) && (value === 0 || helpers.sign(value) === helpers.sign(otherValue))) {
+ if (!isNaN(otherValue) && (value === 0 || helpers.math.sign(value) === helpers.math.sign(otherValue))) {
value += otherValue;
}
}
'use strict';
-const color = require('chartjs-color');
-const helpers = require('../helpers/index');
+import color from 'chartjs-color';
+import helpers from '../helpers/index';
+import {isNumber} from '../helpers/helpers.math';
function interpolate(start, view, model, ease) {
var keys = Object.keys(model);
}
hasValue() {
- return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y);
+ return isNumber(this._model.x) && isNumber(this._model.y);
}
}
Element.extend = helpers.inherits;
-
-module.exports = Element;
+export default Element;
+++ /dev/null
-'use strict';
-
-var color = require('chartjs-color');
-var defaults = require('./core.defaults');
-var helpers = require('../helpers/index');
-
-module.exports = function() {
-
- // -- Basic js utility methods
-
- helpers.where = function(collection, filterCallback) {
- if (helpers.isArray(collection) && Array.prototype.filter) {
- return collection.filter(filterCallback);
- }
- var filtered = [];
-
- helpers.each(collection, function(item) {
- if (filterCallback(item)) {
- filtered.push(item);
- }
- });
-
- return filtered;
- };
- helpers.findIndex = Array.prototype.findIndex ?
- function(array, callback, scope) {
- return array.findIndex(callback, scope);
- } :
- function(array, callback, scope) {
- scope = scope === undefined ? array : scope;
- for (var i = 0, ilen = array.length; i < ilen; ++i) {
- if (callback.call(scope, array[i], i, array)) {
- return i;
- }
- }
- return -1;
- };
- helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
- // Default to start of the array
- if (helpers.isNullOrUndef(startIndex)) {
- startIndex = -1;
- }
- for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
- var currentItem = arrayToSearch[i];
- if (filterCallback(currentItem)) {
- return currentItem;
- }
- }
- };
- helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
- // Default to end of the array
- if (helpers.isNullOrUndef(startIndex)) {
- startIndex = arrayToSearch.length;
- }
- for (var i = startIndex - 1; i >= 0; i--) {
- var currentItem = arrayToSearch[i];
- if (filterCallback(currentItem)) {
- return currentItem;
- }
- }
- };
-
- // -- Math methods
- helpers.isNumber = function(n) {
- return !isNaN(parseFloat(n)) && isFinite(n);
- };
- helpers.almostEquals = function(x, y, epsilon) {
- return Math.abs(x - y) < epsilon;
- };
- helpers.almostWhole = function(x, epsilon) {
- var rounded = Math.round(x);
- return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x);
- };
- helpers._setMinAndMax = function(array, target) {
- var i, ilen, value;
-
- for (i = 0, ilen = array.length; i < ilen; i++) {
- value = array[i];
- if (!isNaN(value)) {
- target.min = Math.min(target.min, value);
- target.max = Math.max(target.max, value);
- }
- }
- };
- helpers._setMinAndMaxByKey = function(array, target, property) {
- var i, ilen, value;
-
- for (i = 0, ilen = array.length; i < ilen; i++) {
- value = array[i][property];
- if (!isNaN(value)) {
- target.min = Math.min(target.min, value);
- target.max = Math.max(target.max, value);
- }
- }
- };
- helpers.sign = Math.sign ?
- function(x) {
- return Math.sign(x);
- } :
- function(x) {
- x = +x; // convert to a number
- if (x === 0 || isNaN(x)) {
- return x;
- }
- return x > 0 ? 1 : -1;
- };
- helpers.toRadians = function(degrees) {
- return degrees * (Math.PI / 180);
- };
- helpers.toDegrees = function(radians) {
- return radians * (180 / Math.PI);
- };
-
- /**
- * Returns the number of decimal places
- * i.e. the number of digits after the decimal point, of the value of this Number.
- * @param {number} x - A number.
- * @returns {number} The number of decimal places.
- * @private
- */
- helpers._decimalPlaces = function(x) {
- if (!helpers.isFinite(x)) {
- return;
- }
- var e = 1;
- var p = 0;
- while (Math.round(x * e) / e !== x) {
- e *= 10;
- p++;
- }
- return p;
- };
-
- // Gets the angle from vertical upright to the point about a centre.
- helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
- var distanceFromXCenter = anglePoint.x - centrePoint.x;
- var distanceFromYCenter = anglePoint.y - centrePoint.y;
- var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
-
- var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
-
- if (angle < (-0.5 * Math.PI)) {
- angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
- }
-
- return {
- angle: angle,
- distance: radialDistanceFromCenter
- };
- };
- helpers.distanceBetweenPoints = function(pt1, pt2) {
- return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
- };
-
- helpers.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
- // Props to Rob Spencer at scaled innovation for his post on splining between points
- // http://scaledinnovation.com/analytics/splines/aboutSplines.html
-
- // This function must also respect "skipped" points
-
- var previous = firstPoint.skip ? middlePoint : firstPoint;
- var current = middlePoint;
- var next = afterPoint.skip ? middlePoint : afterPoint;
-
- var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
- var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
-
- var s01 = d01 / (d01 + d12);
- var s12 = d12 / (d01 + d12);
-
- // If all points are the same, s01 & s02 will be inf
- s01 = isNaN(s01) ? 0 : s01;
- s12 = isNaN(s12) ? 0 : s12;
-
- var fa = t * s01; // scaling factor for triangle Ta
- var fb = t * s12;
-
- return {
- previous: {
- x: current.x - fa * (next.x - previous.x),
- y: current.y - fa * (next.y - previous.y)
- },
- next: {
- x: current.x + fb * (next.x - previous.x),
- y: current.y + fb * (next.y - previous.y)
- }
- };
- };
- helpers.EPSILON = Number.EPSILON || 1e-14;
- helpers.splineCurveMonotone = function(points) {
- // This function calculates Bézier control points in a similar way than |splineCurve|,
- // but preserves monotonicity of the provided data and ensures no local extremums are added
- // between the dataset discrete points due to the interpolation.
- // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
-
- var pointsWithTangents = (points || []).map(function(point) {
- return {
- model: point._model,
- deltaK: 0,
- mK: 0
- };
- });
-
- // Calculate slopes (deltaK) and initialize tangents (mK)
- var pointsLen = pointsWithTangents.length;
- var i, pointBefore, pointCurrent, pointAfter;
- for (i = 0; i < pointsLen; ++i) {
- pointCurrent = pointsWithTangents[i];
- if (pointCurrent.model.skip) {
- continue;
- }
-
- pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
- pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
- if (pointAfter && !pointAfter.model.skip) {
- var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
-
- // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
- pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
- }
-
- if (!pointBefore || pointBefore.model.skip) {
- pointCurrent.mK = pointCurrent.deltaK;
- } else if (!pointAfter || pointAfter.model.skip) {
- pointCurrent.mK = pointBefore.deltaK;
- } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
- pointCurrent.mK = 0;
- } else {
- pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
- }
- }
-
- // Adjust tangents to ensure monotonic properties
- var alphaK, betaK, tauK, squaredMagnitude;
- for (i = 0; i < pointsLen - 1; ++i) {
- pointCurrent = pointsWithTangents[i];
- pointAfter = pointsWithTangents[i + 1];
- if (pointCurrent.model.skip || pointAfter.model.skip) {
- continue;
- }
-
- if (helpers.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
- pointCurrent.mK = pointAfter.mK = 0;
- continue;
- }
-
- alphaK = pointCurrent.mK / pointCurrent.deltaK;
- betaK = pointAfter.mK / pointCurrent.deltaK;
- squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
- if (squaredMagnitude <= 9) {
- continue;
- }
-
- tauK = 3 / Math.sqrt(squaredMagnitude);
- pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
- pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
- }
-
- // Compute control points
- var deltaX;
- for (i = 0; i < pointsLen; ++i) {
- pointCurrent = pointsWithTangents[i];
- if (pointCurrent.model.skip) {
- continue;
- }
-
- pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
- pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
- if (pointBefore && !pointBefore.model.skip) {
- deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
- pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
- pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
- }
- if (pointAfter && !pointAfter.model.skip) {
- deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
- pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
- pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
- }
- }
- };
- // Implementation of the nice number algorithm used in determining where axis labels will go
- helpers.niceNum = function(range, round) {
- var exponent = Math.floor(helpers.math.log10(range));
- var fraction = range / Math.pow(10, exponent);
- var niceFraction;
-
- if (round) {
- if (fraction < 1.5) {
- niceFraction = 1;
- } else if (fraction < 3) {
- niceFraction = 2;
- } else if (fraction < 7) {
- niceFraction = 5;
- } else {
- niceFraction = 10;
- }
- } else if (fraction <= 1.0) {
- niceFraction = 1;
- } else if (fraction <= 2) {
- niceFraction = 2;
- } else if (fraction <= 5) {
- niceFraction = 5;
- } else {
- niceFraction = 10;
- }
-
- return niceFraction * Math.pow(10, exponent);
- };
- // Request animation polyfill - https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
- helpers.requestAnimFrame = (function() {
- if (typeof window === 'undefined') {
- return function(callback) {
- callback();
- };
- }
- return window.requestAnimationFrame ||
- window.webkitRequestAnimationFrame ||
- window.mozRequestAnimationFrame ||
- window.oRequestAnimationFrame ||
- window.msRequestAnimationFrame ||
- function(callback) {
- return window.setTimeout(callback, 1000 / 60);
- };
- }());
- // -- DOM methods
- helpers.getRelativePosition = function(evt, chart) {
- var mouseX, mouseY;
- var e = evt.originalEvent || evt;
- var canvas = evt.target || evt.srcElement;
- var boundingRect = canvas.getBoundingClientRect();
-
- var touches = e.touches;
- if (touches && touches.length > 0) {
- mouseX = touches[0].clientX;
- mouseY = touches[0].clientY;
-
- } else {
- mouseX = e.clientX;
- mouseY = e.clientY;
- }
-
- // Scale mouse coordinates into canvas coordinates
- // by following the pattern laid out by 'jerryj' in the comments of
- // https://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
- var paddingLeft = parseFloat(helpers.getStyle(canvas, 'padding-left'));
- var paddingTop = parseFloat(helpers.getStyle(canvas, 'padding-top'));
- var paddingRight = parseFloat(helpers.getStyle(canvas, 'padding-right'));
- var paddingBottom = parseFloat(helpers.getStyle(canvas, 'padding-bottom'));
- var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
- var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
-
- // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
- // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
- mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
- mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
-
- return {
- x: mouseX,
- y: mouseY
- };
-
- };
-
- // Private helper function to convert max-width/max-height values that may be percentages into a number
- function parseMaxStyle(styleValue, node, parentProperty) {
- var valueInPixels;
- if (typeof styleValue === 'string') {
- valueInPixels = parseInt(styleValue, 10);
-
- if (styleValue.indexOf('%') !== -1) {
- // percentage * size in dimension
- valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
- }
- } else {
- valueInPixels = styleValue;
- }
-
- return valueInPixels;
- }
-
- /**
- * Returns if the given value contains an effective constraint.
- * @private
- */
- function isConstrainedValue(value) {
- return value !== undefined && value !== null && value !== 'none';
- }
-
- /**
- * Returns the max width or height of the given DOM node in a cross-browser compatible fashion
- * @param {HTMLElement} domNode - the node to check the constraint on
- * @param {string} maxStyle - the style that defines the maximum for the direction we are using ('max-width' / 'max-height')
- * @param {string} percentageProperty - property of parent to use when calculating width as a percentage
- * @see {@link https://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser}
- */
- function getConstraintDimension(domNode, maxStyle, percentageProperty) {
- var view = document.defaultView;
- var parentNode = helpers._getParentNode(domNode);
- var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
- var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
- var hasCNode = isConstrainedValue(constrainedNode);
- var hasCContainer = isConstrainedValue(constrainedContainer);
- var infinity = Number.POSITIVE_INFINITY;
-
- if (hasCNode || hasCContainer) {
- return Math.min(
- hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
- hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
- }
-
- return 'none';
- }
- // returns Number or undefined if no constraint
- helpers.getConstraintWidth = function(domNode) {
- return getConstraintDimension(domNode, 'max-width', 'clientWidth');
- };
- // returns Number or undefined if no constraint
- helpers.getConstraintHeight = function(domNode) {
- return getConstraintDimension(domNode, 'max-height', 'clientHeight');
- };
- /**
- * @private
- */
- helpers._calculatePadding = function(container, padding, parentDimension) {
- padding = helpers.getStyle(container, padding);
-
- return padding.indexOf('%') > -1 ? parentDimension * parseInt(padding, 10) / 100 : parseInt(padding, 10);
- };
- /**
- * @private
- */
- helpers._getParentNode = function(domNode) {
- var parent = domNode.parentNode;
- if (parent && parent.toString() === '[object ShadowRoot]') {
- parent = parent.host;
- }
- return parent;
- };
- helpers.getMaximumWidth = function(domNode) {
- var container = helpers._getParentNode(domNode);
- if (!container) {
- return domNode.clientWidth;
- }
-
- var clientWidth = container.clientWidth;
- var paddingLeft = helpers._calculatePadding(container, 'padding-left', clientWidth);
- var paddingRight = helpers._calculatePadding(container, 'padding-right', clientWidth);
-
- var w = clientWidth - paddingLeft - paddingRight;
- var cw = helpers.getConstraintWidth(domNode);
- return isNaN(cw) ? w : Math.min(w, cw);
- };
- helpers.getMaximumHeight = function(domNode) {
- var container = helpers._getParentNode(domNode);
- if (!container) {
- return domNode.clientHeight;
- }
-
- var clientHeight = container.clientHeight;
- var paddingTop = helpers._calculatePadding(container, 'padding-top', clientHeight);
- var paddingBottom = helpers._calculatePadding(container, 'padding-bottom', clientHeight);
-
- var h = clientHeight - paddingTop - paddingBottom;
- var ch = helpers.getConstraintHeight(domNode);
- return isNaN(ch) ? h : Math.min(h, ch);
- };
- helpers.getStyle = function(el, property) {
- return el.currentStyle ?
- el.currentStyle[property] :
- document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
- };
- helpers.retinaScale = function(chart, forceRatio) {
- var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1;
- if (pixelRatio === 1) {
- return;
- }
-
- var canvas = chart.canvas;
- var height = chart.height;
- var width = chart.width;
-
- canvas.height = height * pixelRatio;
- canvas.width = width * pixelRatio;
- chart.ctx.scale(pixelRatio, pixelRatio);
-
- // If no style has been set on the canvas, the render size is used as display size,
- // making the chart visually bigger, so let's enforce it to the "correct" values.
- // See https://github.com/chartjs/Chart.js/issues/3575
- if (!canvas.style.height && !canvas.style.width) {
- canvas.style.height = height + 'px';
- canvas.style.width = width + 'px';
- }
- };
- // -- Canvas methods
- helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
- return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
- };
- helpers.longestText = function(ctx, font, arrayOfThings, cache) {
- cache = cache || {};
- var data = cache.data = cache.data || {};
- var gc = cache.garbageCollect = cache.garbageCollect || [];
-
- if (cache.font !== font) {
- data = cache.data = {};
- gc = cache.garbageCollect = [];
- cache.font = font;
- }
-
- ctx.font = font;
- var longest = 0;
- var ilen = arrayOfThings.length;
- var i, j, jlen, thing, nestedThing;
- for (i = 0; i < ilen; i++) {
- thing = arrayOfThings[i];
-
- // Undefined strings and arrays should not be measured
- if (thing !== undefined && thing !== null && helpers.isArray(thing) !== true) {
- longest = helpers.measureText(ctx, data, gc, longest, thing);
- } else if (helpers.isArray(thing)) {
- // if it is an array lets measure each element
- // to do maybe simplify this function a bit so we can do this more recursively?
- for (j = 0, jlen = thing.length; j < jlen; j++) {
- nestedThing = thing[j];
- // Undefined strings and arrays should not be measured
- if (nestedThing !== undefined && nestedThing !== null && !helpers.isArray(nestedThing)) {
- longest = helpers.measureText(ctx, data, gc, longest, nestedThing);
- }
- }
- }
- }
-
- var gcLen = gc.length / 2;
- if (gcLen > arrayOfThings.length) {
- for (i = 0; i < gcLen; i++) {
- delete data[gc[i]];
- }
- gc.splice(0, gcLen);
- }
- return longest;
- };
- helpers.measureText = function(ctx, data, gc, longest, string) {
- var textWidth = data[string];
- if (!textWidth) {
- textWidth = data[string] = ctx.measureText(string).width;
- gc.push(string);
- }
- if (textWidth > longest) {
- longest = textWidth;
- }
- return longest;
- };
-
- helpers.color = !color ?
- function(value) {
- console.error('Color.js not found!');
- return value;
- } :
- function(value) {
- if (value instanceof CanvasGradient) {
- value = defaults.global.defaultColor;
- }
-
- return color(value);
- };
-
- helpers.getHoverColor = function(colorValue) {
- return (colorValue instanceof CanvasPattern || colorValue instanceof CanvasGradient) ?
- colorValue :
- helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString();
- };
-};
'use strict';
-var helpers = require('../helpers/index');
+import helpers from '../helpers/index';
+import {isNumber} from '../helpers/helpers.math';
/**
* Helper function to get relative position for an event
};
}
- return helpers.getRelativePosition(e, chart);
+ return helpers.dom.getRelativePosition(e, chart);
}
/**
return false;
}
const index = iScale.getIndexForPixel(position[axis]);
- if (!helpers.isNumber(index)) {
+ if (!isNumber(index)) {
return false;
}
indices.push(index);
* Contains interaction related functions
* @namespace Chart.Interaction
*/
-module.exports = {
+export default {
// Helper function for different modes
modes: {
/**
maxHeight = me.maxHeight - getTickMarkLength(options.gridLines)
- tickOpts.padding - getScaleLabelHeight(options.scaleLabel);
maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
- labelRotation = helpers.toDegrees(Math.min(
+ labelRotation = helpers.math.toDegrees(Math.min(
Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)),
Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal)
));
if (isHorizontal) {
// A horizontal axis is more constrained by the height.
var isRotated = me.labelRotation !== 0;
- var angleRadians = helpers.toRadians(me.labelRotation);
+ var angleRadians = helpers.math.toRadians(me.labelRotation);
var cosRotation = Math.cos(angleRadians);
var sinRotation = Math.sin(angleRadians);
var optionTicks = me.options.ticks;
// Calculate space needed by label in axis direction.
- var rot = helpers.toRadians(me.labelRotation);
+ var rot = helpers.math.toRadians(me.labelRotation);
var cos = Math.abs(Math.cos(rot));
var sin = Math.abs(Math.sin(rot));
const fonts = parseTickFontOptions(optionTicks);
const tickPadding = optionTicks.padding;
const tl = getTickMarkLength(options.gridLines);
- const rotation = -helpers.toRadians(me.labelRotation);
+ const rotation = -helpers.math.toRadians(me.labelRotation);
const items = [];
let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
import defaults from '../core/core.defaults';
import Element from '../core/core.element';
-import helpers from '../helpers';
+import {getAngleFromPoint} from '../helpers/helpers.math';
const TAU = Math.PI * 2;
defaults._set('global', {
var vm = this._view;
if (vm) {
- var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY});
+ var pointRelativePosition = getAngleFromPoint(vm, {x: chartX, y: chartY});
var angle = pointRelativePosition.angle;
var distance = pointRelativePosition.distance;
--- /dev/null
+import {almostEquals, sign} from './helpers.math';
+
+const EPSILON = Number.EPSILON || 1e-14;
+
+export function splineCurve(firstPoint, middlePoint, afterPoint, t) {
+ // Props to Rob Spencer at scaled innovation for his post on splining between points
+ // http://scaledinnovation.com/analytics/splines/aboutSplines.html
+
+ // This function must also respect "skipped" points
+
+ var previous = firstPoint.skip ? middlePoint : firstPoint;
+ var current = middlePoint;
+ var next = afterPoint.skip ? middlePoint : afterPoint;
+
+ var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
+ var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
+
+ var s01 = d01 / (d01 + d12);
+ var s12 = d12 / (d01 + d12);
+
+ // If all points are the same, s01 & s02 will be inf
+ s01 = isNaN(s01) ? 0 : s01;
+ s12 = isNaN(s12) ? 0 : s12;
+
+ var fa = t * s01; // scaling factor for triangle Ta
+ var fb = t * s12;
+
+ return {
+ previous: {
+ x: current.x - fa * (next.x - previous.x),
+ y: current.y - fa * (next.y - previous.y)
+ },
+ next: {
+ x: current.x + fb * (next.x - previous.x),
+ y: current.y + fb * (next.y - previous.y)
+ }
+ };
+}
+
+export function splineCurveMonotone(points) {
+ // This function calculates Bézier control points in a similar way than |splineCurve|,
+ // but preserves monotonicity of the provided data and ensures no local extremums are added
+ // between the dataset discrete points due to the interpolation.
+ // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
+
+ var pointsWithTangents = (points || []).map(function(point) {
+ return {
+ model: point._model,
+ deltaK: 0,
+ mK: 0
+ };
+ });
+
+ // Calculate slopes (deltaK) and initialize tangents (mK)
+ var pointsLen = pointsWithTangents.length;
+ var i, pointBefore, pointCurrent, pointAfter;
+ for (i = 0; i < pointsLen; ++i) {
+ pointCurrent = pointsWithTangents[i];
+ if (pointCurrent.model.skip) {
+ continue;
+ }
+
+ pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
+ pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
+ if (pointAfter && !pointAfter.model.skip) {
+ var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
+
+ // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
+ pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
+ }
+
+ if (!pointBefore || pointBefore.model.skip) {
+ pointCurrent.mK = pointCurrent.deltaK;
+ } else if (!pointAfter || pointAfter.model.skip) {
+ pointCurrent.mK = pointBefore.deltaK;
+ } else if (sign(pointBefore.deltaK) !== sign(pointCurrent.deltaK)) {
+ pointCurrent.mK = 0;
+ } else {
+ pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
+ }
+ }
+
+ // Adjust tangents to ensure monotonic properties
+ var alphaK, betaK, tauK, squaredMagnitude;
+ for (i = 0; i < pointsLen - 1; ++i) {
+ pointCurrent = pointsWithTangents[i];
+ pointAfter = pointsWithTangents[i + 1];
+ if (pointCurrent.model.skip || pointAfter.model.skip) {
+ continue;
+ }
+
+ if (almostEquals(pointCurrent.deltaK, 0, EPSILON)) {
+ pointCurrent.mK = pointAfter.mK = 0;
+ continue;
+ }
+
+ alphaK = pointCurrent.mK / pointCurrent.deltaK;
+ betaK = pointAfter.mK / pointCurrent.deltaK;
+ squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
+ if (squaredMagnitude <= 9) {
+ continue;
+ }
+
+ tauK = 3 / Math.sqrt(squaredMagnitude);
+ pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
+ pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
+ }
+
+ // Compute control points
+ var deltaX;
+ for (i = 0; i < pointsLen; ++i) {
+ pointCurrent = pointsWithTangents[i];
+ if (pointCurrent.model.skip) {
+ continue;
+ }
+
+ pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
+ pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
+ if (pointBefore && !pointBefore.model.skip) {
+ deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
+ pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
+ pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
+ }
+ if (pointAfter && !pointAfter.model.skip) {
+ deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
+ pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
+ pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
+ }
+ }
+}
--- /dev/null
+/**
+ * Returns if the given value contains an effective constraint.
+ * @private
+ */
+function isConstrainedValue(value) {
+ return value !== undefined && value !== null && value !== 'none';
+}
+
+/**
+ * @private
+ */
+function _getParentNode(domNode) {
+ var parent = domNode.parentNode;
+ if (parent && parent.toString() === '[object ShadowRoot]') {
+ parent = parent.host;
+ }
+ return parent;
+}
+
+// Private helper function to convert max-width/max-height values that may be percentages into a number
+function parseMaxStyle(styleValue, node, parentProperty) {
+ var valueInPixels;
+ if (typeof styleValue === 'string') {
+ valueInPixels = parseInt(styleValue, 10);
+
+ if (styleValue.indexOf('%') !== -1) {
+ // percentage * size in dimension
+ valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
+ }
+ } else {
+ valueInPixels = styleValue;
+ }
+
+ return valueInPixels;
+}
+
+/**
+ * Returns the max width or height of the given DOM node in a cross-browser compatible fashion
+ * @param {HTMLElement} domNode - the node to check the constraint on
+ * @param {string} maxStyle - the style that defines the maximum for the direction we are using ('max-width' / 'max-height')
+ * @param {string} percentageProperty - property of parent to use when calculating width as a percentage
+ * @see {@link https://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser}
+ */
+function getConstraintDimension(domNode, maxStyle, percentageProperty) {
+ var view = document.defaultView;
+ var parentNode = _getParentNode(domNode);
+ var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
+ var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
+ var hasCNode = isConstrainedValue(constrainedNode);
+ var hasCContainer = isConstrainedValue(constrainedContainer);
+ var infinity = Number.POSITIVE_INFINITY;
+
+ if (hasCNode || hasCContainer) {
+ return Math.min(
+ hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
+ hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
+ }
+
+ return 'none';
+}
+
+export function getStyle(el, property) {
+ return el.currentStyle ?
+ el.currentStyle[property] :
+ document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
+}
+
+// returns Number or undefined if no constraint
+function getConstraintWidth(domNode) {
+ return getConstraintDimension(domNode, 'max-width', 'clientWidth');
+}
+
+// returns Number or undefined if no constraint
+function getConstraintHeight(domNode) {
+ return getConstraintDimension(domNode, 'max-height', 'clientHeight');
+}
+
+/**
+ * @private
+ */
+function _calculatePadding(container, padding, parentDimension) {
+ padding = getStyle(container, padding);
+
+ return padding.indexOf('%') > -1 ? parentDimension * parseInt(padding, 10) / 100 : parseInt(padding, 10);
+}
+
+export function getRelativePosition(evt, chart) {
+ var mouseX, mouseY;
+ var e = evt.originalEvent || evt;
+ var canvasElement = evt.target || evt.srcElement;
+ var boundingRect = canvasElement.getBoundingClientRect();
+
+ var touches = e.touches;
+ if (touches && touches.length > 0) {
+ mouseX = touches[0].clientX;
+ mouseY = touches[0].clientY;
+
+ } else {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+ }
+
+ // Scale mouse coordinates into canvas coordinates
+ // by following the pattern laid out by 'jerryj' in the comments of
+ // https://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
+ var paddingLeft = parseFloat(getStyle(canvasElement, 'padding-left'));
+ var paddingTop = parseFloat(getStyle(canvasElement, 'padding-top'));
+ var paddingRight = parseFloat(getStyle(canvasElement, 'padding-right'));
+ var paddingBottom = parseFloat(getStyle(canvasElement, 'padding-bottom'));
+ var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
+ var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
+
+ // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
+ // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
+ mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvasElement.width / chart.currentDevicePixelRatio);
+ mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvasElement.height / chart.currentDevicePixelRatio);
+
+ return {
+ x: mouseX,
+ y: mouseY
+ };
+}
+
+export function getMaximumWidth(domNode) {
+ var container = _getParentNode(domNode);
+ if (!container) {
+ return domNode.clientWidth;
+ }
+
+ var clientWidth = container.clientWidth;
+ var paddingLeft = _calculatePadding(container, 'padding-left', clientWidth);
+ var paddingRight = _calculatePadding(container, 'padding-right', clientWidth);
+
+ var w = clientWidth - paddingLeft - paddingRight;
+ var cw = getConstraintWidth(domNode);
+ return isNaN(cw) ? w : Math.min(w, cw);
+}
+
+export function getMaximumHeight(domNode) {
+ var container = _getParentNode(domNode);
+ if (!container) {
+ return domNode.clientHeight;
+ }
+
+ var clientHeight = container.clientHeight;
+ var paddingTop = _calculatePadding(container, 'padding-top', clientHeight);
+ var paddingBottom = _calculatePadding(container, 'padding-bottom', clientHeight);
+
+ var h = clientHeight - paddingTop - paddingBottom;
+ var ch = getConstraintHeight(domNode);
+ return isNaN(ch) ? h : Math.min(h, ch);
+}
+
+export function retinaScale(chart, forceRatio) {
+ var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1;
+ if (pixelRatio === 1) {
+ return;
+ }
+
+ var canvasElement = chart.canvas;
+ var height = chart.height;
+ var width = chart.width;
+
+ canvasElement.height = height * pixelRatio;
+ canvasElement.width = width * pixelRatio;
+ chart.ctx.scale(pixelRatio, pixelRatio);
+
+ // If no style has been set on the canvas, the render size is used as display size,
+ // making the chart visually bigger, so let's enforce it to the "correct" values.
+ // See https://github.com/chartjs/Chart.js/issues/3575
+ if (!canvasElement.style.height && !canvasElement.style.width) {
+ canvasElement.style.height = height + 'px';
+ canvasElement.style.width = width + 'px';
+ }
+}
'use strict';
+import {isFinite as isFiniteNumber} from './helpers.core';
+
/**
* @alias Chart.helpers.math
* @namespace
return isPowerOf10 ? powerOf10 : exponent;
};
+
+
+export function isNumber(n) {
+ return !isNaN(parseFloat(n)) && isFinite(n);
+}
+
+export function almostEquals(x, y, epsilon) {
+ return Math.abs(x - y) < epsilon;
+}
+
+export function almostWhole(x, epsilon) {
+ var rounded = Math.round(x);
+ return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x);
+}
+
+export function _setMinAndMax(array, target) {
+ var i, ilen, value;
+
+ for (i = 0, ilen = array.length; i < ilen; i++) {
+ value = array[i];
+ if (!isNaN(value)) {
+ target.min = Math.min(target.min, value);
+ target.max = Math.max(target.max, value);
+ }
+ }
+}
+
+export function _setMinAndMaxByKey(array, target, property) {
+ var i, ilen, value;
+
+ for (i = 0, ilen = array.length; i < ilen; i++) {
+ value = array[i][property];
+ if (!isNaN(value)) {
+ target.min = Math.min(target.min, value);
+ target.max = Math.max(target.max, value);
+ }
+ }
+}
+
+export const sign = Math.sign ?
+ function(x) {
+ return Math.sign(x);
+ } :
+ function(x) {
+ x = +x; // convert to a number
+ if (x === 0 || isNaN(x)) {
+ return x;
+ }
+ return x > 0 ? 1 : -1;
+ };
+
+export function toRadians(degrees) {
+ return degrees * (Math.PI / 180);
+}
+
+export function toDegrees(radians) {
+ return radians * (180 / Math.PI);
+}
+
+/**
+ * Returns the number of decimal places
+ * i.e. the number of digits after the decimal point, of the value of this Number.
+ * @param {number} x - A number.
+ * @returns {number} The number of decimal places.
+ * @private
+ */
+export function _decimalPlaces(x) {
+ if (!isFiniteNumber(x)) {
+ return;
+ }
+ var e = 1;
+ var p = 0;
+ while (Math.round(x * e) / e !== x) {
+ e *= 10;
+ p++;
+ }
+ return p;
+}
+
+// Gets the angle from vertical upright to the point about a centre.
+export function getAngleFromPoint(centrePoint, anglePoint) {
+ var distanceFromXCenter = anglePoint.x - centrePoint.x;
+ var distanceFromYCenter = anglePoint.y - centrePoint.y;
+ var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
+
+ var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
+
+ if (angle < (-0.5 * Math.PI)) {
+ angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
+ }
+
+ return {
+ angle: angle,
+ distance: radialDistanceFromCenter
+ };
+}
+
+export function distanceBetweenPoints(pt1, pt2) {
+ return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
+}
'use strict';
+import color from 'chartjs-color';
+
import * as coreHelpers from './helpers.core';
import * as canvas from './helpers.canvas';
+import * as curve from './helpers.curve';
+import * as dom from './helpers.dom';
import * as easing from './helpers.easing';
import * as options from './helpers.options';
import * as math from './helpers.math';
import * as rtl from './helpers.rtl';
+const colorHelper = !color ?
+ function(value) {
+ console.error('Color.js not found!');
+ return value;
+ } :
+ function(value) {
+ if (value instanceof CanvasGradient || value instanceof CanvasPattern) {
+ // TODO: figure out what this should be. Previously returned
+ // the default color
+ return value;
+ }
+
+ return color(value);
+ };
+
+function measureText(ctx, data, gc, longest, string) {
+ var textWidth = data[string];
+ if (!textWidth) {
+ textWidth = data[string] = ctx.measureText(string).width;
+ gc.push(string);
+ }
+ if (textWidth > longest) {
+ longest = textWidth;
+ }
+ return longest;
+}
+
export default {
...coreHelpers,
canvas,
+ curve,
+ dom,
easing,
options,
math,
rtl,
+
+ where: function(collection, filterCallback) {
+ if (coreHelpers.isArray(collection) && Array.prototype.filter) {
+ return collection.filter(filterCallback);
+ }
+ var filtered = [];
+
+ coreHelpers.each(collection, function(item) {
+ if (filterCallback(item)) {
+ filtered.push(item);
+ }
+ });
+
+ return filtered;
+ },
+ findIndex: Array.prototype.findIndex ?
+ function(array, callback, scope) {
+ return array.findIndex(callback, scope);
+ } :
+ function(array, callback, scope) {
+ scope = scope === undefined ? array : scope;
+ for (var i = 0, ilen = array.length; i < ilen; ++i) {
+ if (callback.call(scope, array[i], i, array)) {
+ return i;
+ }
+ }
+ return -1;
+ },
+ findNextWhere: function(arrayToSearch, filterCallback, startIndex) {
+ // Default to start of the array
+ if (coreHelpers.isNullOrUndef(startIndex)) {
+ startIndex = -1;
+ }
+ for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
+ var currentItem = arrayToSearch[i];
+ if (filterCallback(currentItem)) {
+ return currentItem;
+ }
+ }
+ },
+ findPreviousWhere: function(arrayToSearch, filterCallback, startIndex) {
+ // Default to end of the array
+ if (coreHelpers.isNullOrUndef(startIndex)) {
+ startIndex = arrayToSearch.length;
+ }
+ for (var i = startIndex - 1; i >= 0; i--) {
+ var currentItem = arrayToSearch[i];
+ if (filterCallback(currentItem)) {
+ return currentItem;
+ }
+ }
+ },
+ // Implementation of the nice number algorithm used in determining where axis labels will go
+ niceNum: function(range, round) {
+ var exponent = Math.floor(math.log10(range));
+ var fraction = range / Math.pow(10, exponent);
+ var niceFraction;
+
+ if (round) {
+ if (fraction < 1.5) {
+ niceFraction = 1;
+ } else if (fraction < 3) {
+ niceFraction = 2;
+ } else if (fraction < 7) {
+ niceFraction = 5;
+ } else {
+ niceFraction = 10;
+ }
+ } else if (fraction <= 1.0) {
+ niceFraction = 1;
+ } else if (fraction <= 2) {
+ niceFraction = 2;
+ } else if (fraction <= 5) {
+ niceFraction = 5;
+ } else {
+ niceFraction = 10;
+ }
+
+ return niceFraction * Math.pow(10, exponent);
+ },
+ // Request animation polyfill - https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
+ requestAnimFrame: (function() {
+ if (typeof window === 'undefined') {
+ return function(callback) {
+ callback();
+ };
+ }
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function(callback) {
+ return window.setTimeout(callback, 1000 / 60);
+ };
+ }()),
+ // -- Canvas methods
+ fontString: function(pixelSize, fontStyle, fontFamily) {
+ return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
+ },
+ longestText: function(ctx, font, arrayOfThings, cache) {
+ cache = cache || {};
+ var data = cache.data = cache.data || {};
+ var gc = cache.garbageCollect = cache.garbageCollect || [];
+
+ if (cache.font !== font) {
+ data = cache.data = {};
+ gc = cache.garbageCollect = [];
+ cache.font = font;
+ }
+
+ ctx.font = font;
+ var longest = 0;
+ var ilen = arrayOfThings.length;
+ var i, j, jlen, thing, nestedThing;
+ for (i = 0; i < ilen; i++) {
+ thing = arrayOfThings[i];
+
+ // Undefined strings and arrays should not be measured
+ if (thing !== undefined && thing !== null && coreHelpers.isArray(thing) !== true) {
+ longest = measureText(ctx, data, gc, longest, thing);
+ } else if (coreHelpers.isArray(thing)) {
+ // if it is an array lets measure each element
+ // to do maybe simplify this function a bit so we can do this more recursively?
+ for (j = 0, jlen = thing.length; j < jlen; j++) {
+ nestedThing = thing[j];
+ // Undefined strings and arrays should not be measured
+ if (nestedThing !== undefined && nestedThing !== null && !coreHelpers.isArray(nestedThing)) {
+ longest = measureText(ctx, data, gc, longest, nestedThing);
+ }
+ }
+ }
+ }
+
+ var gcLen = gc.length / 2;
+ if (gcLen > arrayOfThings.length) {
+ for (i = 0; i < gcLen; i++) {
+ delete data[gc[i]];
+ }
+ gc.splice(0, gcLen);
+ }
+ return longest;
+ },
+ measureText,
+ color: colorHelper,
+ getHoverColor: function(colorValue) {
+ return (colorValue instanceof CanvasPattern || colorValue instanceof CanvasGradient) ?
+ colorValue :
+ colorHelper(colorValue).saturate(0.5).darken(0.1).rgbString();
+ }
};
var Chart = require('./core/core.controller');
Chart.helpers = require('./helpers/index');
-
-// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
-require('./core/core.helpers')(Chart);
-
Chart._adapters = require('./core/core.adapters');
Chart.Animation = require('./core/core.animation');
Chart.animationService = require('./core/core.animations');
* @returns {number} Size in pixels or undefined if unknown.
*/
function readUsedSize(element, property) {
- var value = helpers.getStyle(element, property);
+ var value = helpers.dom.getStyle(element, property);
var matches = value && value.match(/^(\d+)(\.\d+)?px$/);
return matches ? Number(matches[1]) : undefined;
}
function fromNativeEvent(event, chart) {
var type = EVENT_TYPES[event.type] || event.type;
- var pos = helpers.getRelativePosition(event, chart);
+ var pos = helpers.dom.getRelativePosition(event, chart);
return createEvent(type, chart, pos.x, pos.y, event);
}
'use strict';
import helpers from '../helpers/index';
+import {almostEquals, almostWhole, _decimalPlaces, _setMinAndMaxByKey, sign} from '../helpers/helpers.math';
import Scale from '../core/core.scale';
const isNullOrUndef = helpers.isNullOrUndef;
if (stepSize || isNullOrUndef(precision)) {
// If a precision is not specified, calculate factor based on spacing
- factor = Math.pow(10, helpers._decimalPlaces(spacing));
+ factor = Math.pow(10, _decimalPlaces(spacing));
} else {
// If the user specified a precision, round to that number of decimal places
factor = Math.pow(10, precision);
// If min, max and stepSize is set and they make an evenly spaced scale use it.
if (stepSize) {
// If very close to our whole number, use it.
- if (!isNullOrUndef(min) && helpers.almostWhole(min / spacing, spacing / 1000)) {
+ if (!isNullOrUndef(min) && almostWhole(min / spacing, spacing / 1000)) {
niceMin = min;
}
- if (!isNullOrUndef(max) && helpers.almostWhole(max / spacing, spacing / 1000)) {
+ if (!isNullOrUndef(max) && almostWhole(max / spacing, spacing / 1000)) {
niceMax = max;
}
}
numSpaces = (niceMax - niceMin) / spacing;
// If very close to our rounded value, use it.
- if (helpers.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
+ if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
numSpaces = Math.round(numSpaces);
} else {
numSpaces = Math.ceil(numSpaces);
// do nothing since that would make the chart weird. If the user really wants a weird chart
// axis, they can manually override it
if (opts.beginAtZero) {
- var minSign = helpers.sign(me.min);
- var maxSign = helpers.sign(me.max);
+ var minSign = sign(me.min);
+ var maxSign = sign(me.max);
if (minSign < 0 && maxSign < 0) {
// move the top up to 0
// At this point, we need to update our max and min given the tick values since we have expanded the
// range of the scale
- helpers._setMinAndMaxByKey(ticks, me, 'value');
+ _setMinAndMaxByKey(ticks, me, 'value');
if (opts.reverse) {
ticks.reverse();
import defaults from '../core/core.defaults';
import helpers from '../helpers/index';
+import {_setMinAndMaxByKey} from '../helpers/helpers.math';
import Scale from '../core/core.scale';
import LinearScaleBase from './scale.linearbase';
import Ticks from '../core/core.ticks';
// At this point, we need to update our max and min given the tick values since we have expanded the
// range of the scale
- helpers._setMinAndMaxByKey(ticks, me, 'value');
+ _setMinAndMaxByKey(ticks, me, 'value');
if (opts.reverse) {
reverse = !reverse;
import defaults from '../core/core.defaults';
import helpers from '../helpers/index';
+import {isNumber, toDegrees} from '../helpers/helpers.math';
import LinearScaleBase from './scale.linearbase';
import Ticks from '../core/core.ticks';
// Add quarter circle to make degree 0 mean top of circle
var angleRadians = scale.getIndexAngle(i);
- var angle = helpers.toDegrees(angleRadians) % 360;
+ var angle = toDegrees(angleRadians) % 360;
var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
ctx.fillStyle = pointLabelFontColor;
var angleRadians = scale.getIndexAngle(i);
- var angle = helpers.toDegrees(angleRadians);
+ var angle = toDegrees(angleRadians);
ctx.textAlign = getTextAlignForAngle(angle);
adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
fillText(ctx, scale.pointLabels[i], pointLabelPosition, plFont.lineHeight);
}
function numberOrZero(param) {
- return helpers.isNumber(param) ? param : 0;
+ return isNumber(param) ? param : 0;
}
class RadialLinearScale extends LinearScaleBase {
import adapters from '../core/core.adapters';
import defaults from '../core/core.defaults';
import helpers from '../helpers/index';
+import {toRadians} from '../helpers/helpers.math';
import Scale from '../core/core.scale';
const resolve = helpers.options.resolve;
const me = this;
const ticksOpts = me.options.ticks;
const tickLabelWidth = me.ctx.measureText(label).width;
- const angle = helpers.toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
+ const angle = toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
const cosRotation = Math.cos(angle);
const sinRotation = Math.sin(angle);
const tickFontSize = valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize);
expect(helpers.findPreviousWhere(data, callback, 0)).toBe(undefined);
});
- it('should get the correct sign', function() {
- expect(helpers.sign(0)).toBe(0);
- expect(helpers.sign(10)).toBe(1);
- expect(helpers.sign(-5)).toBe(-1);
- });
-
- it('should correctly determine if two numbers are essentially equal', function() {
- expect(helpers.almostEquals(0, Number.EPSILON, 2 * Number.EPSILON)).toBe(true);
- expect(helpers.almostEquals(1, 1.1, 0.0001)).toBe(false);
- expect(helpers.almostEquals(1e30, 1e30 + Number.EPSILON, 0)).toBe(false);
- expect(helpers.almostEquals(1e30, 1e30 + Number.EPSILON, 2 * Number.EPSILON)).toBe(true);
- });
-
- it('should correctly determine if a numbers are essentially whole', function() {
- expect(helpers.almostWhole(0.99999, 0.0001)).toBe(true);
- expect(helpers.almostWhole(0.9, 0.0001)).toBe(false);
- expect(helpers.almostWhole(1234567890123, 0.0001)).toBe(true);
- expect(helpers.almostWhole(1234567890123.001, 0.0001)).toBe(false);
- });
-
it('should generate integer ids', function() {
var uid = helpers.uid();
expect(uid).toEqual(jasmine.any(Number));
expect(helpers.uid()).toBe(uid + 3);
});
- it('should detect a number', function() {
- expect(helpers.isNumber(123)).toBe(true);
- expect(helpers.isNumber('123')).toBe(true);
- expect(helpers.isNumber(null)).toBe(false);
- expect(helpers.isNumber(NaN)).toBe(false);
- expect(helpers.isNumber(undefined)).toBe(false);
- expect(helpers.isNumber('cbc')).toBe(false);
- });
-
- it('should convert between radians and degrees', function() {
- expect(helpers.toRadians(180)).toBe(Math.PI);
- expect(helpers.toRadians(90)).toBe(0.5 * Math.PI);
- expect(helpers.toDegrees(Math.PI)).toBe(180);
- expect(helpers.toDegrees(Math.PI * 3 / 2)).toBe(270);
- });
-
- it('should get the correct number of decimal places', function() {
- expect(helpers._decimalPlaces(100)).toBe(0);
- expect(helpers._decimalPlaces(1)).toBe(0);
- expect(helpers._decimalPlaces(0)).toBe(0);
- expect(helpers._decimalPlaces(0.01)).toBe(2);
- expect(helpers._decimalPlaces(-0.01)).toBe(2);
- expect(helpers._decimalPlaces('1')).toBe(undefined);
- expect(helpers._decimalPlaces('')).toBe(undefined);
- expect(helpers._decimalPlaces(undefined)).toBe(undefined);
- expect(helpers._decimalPlaces(12345678.1234)).toBe(4);
- expect(helpers._decimalPlaces(1234567890.1234567)).toBe(7);
- });
-
- it('should get an angle from a point', function() {
- var center = {
- x: 0,
- y: 0
- };
-
- expect(helpers.getAngleFromPoint(center, {
- x: 0,
- y: 10
- })).toEqual({
- angle: Math.PI / 2,
- distance: 10,
- });
-
- expect(helpers.getAngleFromPoint(center, {
- x: Math.sqrt(2),
- y: Math.sqrt(2)
- })).toEqual({
- angle: Math.PI / 4,
- distance: 2
- });
-
- expect(helpers.getAngleFromPoint(center, {
- x: -1.0 * Math.sqrt(2),
- y: -1.0 * Math.sqrt(2)
- })).toEqual({
- angle: Math.PI * 1.25,
- distance: 2
- });
- });
-
- it('should spline curves', function() {
- expect(helpers.splineCurve({
- x: 0,
- y: 0
- }, {
- x: 1,
- y: 1
- }, {
- x: 2,
- y: 0
- }, 0)).toEqual({
- previous: {
- x: 1,
- y: 1,
- },
- next: {
- x: 1,
- y: 1,
- }
- });
-
- expect(helpers.splineCurve({
- x: 0,
- y: 0
- }, {
- x: 1,
- y: 1
- }, {
- x: 2,
- y: 0
- }, 1)).toEqual({
- previous: {
- x: 0,
- y: 1,
- },
- next: {
- x: 2,
- y: 1,
- }
- });
- });
-
- it('should spline curves with monotone cubic interpolation', function() {
- var dataPoints = [
- {_model: {x: 0, y: 0, skip: false}},
- {_model: {x: 3, y: 6, skip: false}},
- {_model: {x: 9, y: 6, skip: false}},
- {_model: {x: 12, y: 60, skip: false}},
- {_model: {x: 15, y: 60, skip: false}},
- {_model: {x: 18, y: 120, skip: false}},
- {_model: {x: null, y: null, skip: true}},
- {_model: {x: 21, y: 180, skip: false}},
- {_model: {x: 24, y: 120, skip: false}},
- {_model: {x: 27, y: 125, skip: false}},
- {_model: {x: 30, y: 105, skip: false}},
- {_model: {x: 33, y: 110, skip: false}},
- {_model: {x: 33, y: 110, skip: false}},
- {_model: {x: 36, y: 170, skip: false}}
- ];
- helpers.splineCurveMonotone(dataPoints);
- expect(dataPoints).toEqual([{
- _model: {
- x: 0,
- y: 0,
- skip: false,
- controlPointNextX: 1,
- controlPointNextY: 2
- }
- },
- {
- _model: {
- x: 3,
- y: 6,
- skip: false,
- controlPointPreviousX: 2,
- controlPointPreviousY: 6,
- controlPointNextX: 5,
- controlPointNextY: 6
- }
- },
- {
- _model: {
- x: 9,
- y: 6,
- skip: false,
- controlPointPreviousX: 7,
- controlPointPreviousY: 6,
- controlPointNextX: 10,
- controlPointNextY: 6
- }
- },
- {
- _model: {
- x: 12,
- y: 60,
- skip: false,
- controlPointPreviousX: 11,
- controlPointPreviousY: 60,
- controlPointNextX: 13,
- controlPointNextY: 60
- }
- },
- {
- _model: {
- x: 15,
- y: 60,
- skip: false,
- controlPointPreviousX: 14,
- controlPointPreviousY: 60,
- controlPointNextX: 16,
- controlPointNextY: 60
- }
- },
- {
- _model: {
- x: 18,
- y: 120,
- skip: false,
- controlPointPreviousX: 17,
- controlPointPreviousY: 100
- }
- },
- {
- _model: {
- x: null,
- y: null,
- skip: true
- }
- },
- {
- _model: {
- x: 21,
- y: 180,
- skip: false,
- controlPointNextX: 22,
- controlPointNextY: 160
- }
- },
- {
- _model: {
- x: 24,
- y: 120,
- skip: false,
- controlPointPreviousX: 23,
- controlPointPreviousY: 120,
- controlPointNextX: 25,
- controlPointNextY: 120
- }
- },
- {
- _model: {
- x: 27,
- y: 125,
- skip: false,
- controlPointPreviousX: 26,
- controlPointPreviousY: 125,
- controlPointNextX: 28,
- controlPointNextY: 125
- }
- },
- {
- _model: {
- x: 30,
- y: 105,
- skip: false,
- controlPointPreviousX: 29,
- controlPointPreviousY: 105,
- controlPointNextX: 31,
- controlPointNextY: 105
- }
- },
- {
- _model: {
- x: 33,
- y: 110,
- skip: false,
- controlPointPreviousX: 32,
- controlPointPreviousY: 110,
- controlPointNextX: 33,
- controlPointNextY: 110
- }
- },
- {
- _model: {
- x: 33,
- y: 110,
- skip: false,
- controlPointPreviousX: 33,
- controlPointPreviousY: 110,
- controlPointNextX: 34,
- controlPointNextY: 110
- }
- },
- {
- _model: {
- x: 36,
- y: 170,
- skip: false,
- controlPointPreviousX: 35,
- controlPointPreviousY: 150
- }
- }]);
- });
-
it('should return the width of the longest text in an Array and 2D Array', function() {
var context = window.createMockContext();
var font = "normal 12px 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif";
}]);
});
- it ('should get the maximum width and height for a node', function() {
- // Create div with fixed size as a test bed
- var div = document.createElement('div');
- div.style.width = '200px';
- div.style.height = '300px';
-
- document.body.appendChild(div);
-
- // Create the div we want to get the max size for
- var innerDiv = document.createElement('div');
- div.appendChild(innerDiv);
-
- expect(helpers.getMaximumWidth(innerDiv)).toBe(200);
- expect(helpers.getMaximumHeight(innerDiv)).toBe(300);
-
- document.body.removeChild(div);
- });
-
- it ('should get the maximum width and height for a node in a ShadowRoot', function() {
- // Create div with fixed size as a test bed
- var div = document.createElement('div');
- div.style.width = '200px';
- div.style.height = '300px';
-
- document.body.appendChild(div);
-
- if (!div.attachShadow) {
- // Shadow DOM is not natively supported
- return;
- }
-
- var shadow = div.attachShadow({mode: 'closed'});
-
- // Create the div we want to get the max size for
- var innerDiv = document.createElement('div');
- shadow.appendChild(innerDiv);
-
- expect(helpers.getMaximumWidth(innerDiv)).toBe(200);
- expect(helpers.getMaximumHeight(innerDiv)).toBe(300);
-
- document.body.removeChild(div);
- });
-
- it ('should get the maximum width of a node that has a max-width style', function() {
- // Create div with fixed size as a test bed
- var div = document.createElement('div');
- div.style.width = '200px';
- div.style.height = '300px';
-
- document.body.appendChild(div);
-
- // Create the div we want to get the max size for and set a max-width style
- var innerDiv = document.createElement('div');
- innerDiv.style.maxWidth = '150px';
- div.appendChild(innerDiv);
-
- expect(helpers.getMaximumWidth(innerDiv)).toBe(150);
-
- document.body.removeChild(div);
- });
-
- it ('should get the maximum height of a node that has a max-height style', function() {
- // Create div with fixed size as a test bed
- var div = document.createElement('div');
- div.style.width = '200px';
- div.style.height = '300px';
-
- document.body.appendChild(div);
-
- // Create the div we want to get the max size for and set a max-height style
- var innerDiv = document.createElement('div');
- innerDiv.style.maxHeight = '150px';
- div.appendChild(innerDiv);
-
- expect(helpers.getMaximumHeight(innerDiv)).toBe(150);
-
- document.body.removeChild(div);
- });
-
- it ('should get the maximum width of a node when the parent has a max-width style', function() {
- // Create div with fixed size as a test bed
- var div = document.createElement('div');
- div.style.width = '200px';
- div.style.height = '300px';
-
- document.body.appendChild(div);
-
- // Create an inner wrapper around our div we want to size and give that a max-width style
- var parentDiv = document.createElement('div');
- parentDiv.style.maxWidth = '150px';
- div.appendChild(parentDiv);
-
- // Create the div we want to get the max size for
- var innerDiv = document.createElement('div');
- parentDiv.appendChild(innerDiv);
-
- expect(helpers.getMaximumWidth(innerDiv)).toBe(150);
-
- document.body.removeChild(div);
- });
-
- it ('should get the maximum height of a node when the parent has a max-height style', function() {
- // Create div with fixed size as a test bed
- var div = document.createElement('div');
- div.style.width = '200px';
- div.style.height = '300px';
-
- document.body.appendChild(div);
-
- // Create an inner wrapper around our div we want to size and give that a max-height style
- var parentDiv = document.createElement('div');
- parentDiv.style.maxHeight = '150px';
- div.appendChild(parentDiv);
-
- // Create the div we want to get the max size for
- var innerDiv = document.createElement('div');
- innerDiv.style.height = '300px'; // make it large
- parentDiv.appendChild(innerDiv);
-
- expect(helpers.getMaximumHeight(innerDiv)).toBe(150);
-
- document.body.removeChild(div);
- });
-
- it ('should get the maximum width of a node that has a percentage max-width style', function() {
- // Create div with fixed size as a test bed
- var div = document.createElement('div');
- div.style.width = '200px';
- div.style.height = '300px';
-
- document.body.appendChild(div);
-
- // Create the div we want to get the max size for and set a max-width style
- var innerDiv = document.createElement('div');
- innerDiv.style.maxWidth = '50%';
- div.appendChild(innerDiv);
-
- expect(helpers.getMaximumWidth(innerDiv)).toBe(100);
-
- document.body.removeChild(div);
- });
-
- it ('should get the maximum height of a node that has a percentage max-height style', function() {
- // Create div with fixed size as a test bed
- var div = document.createElement('div');
- div.style.width = '200px';
- div.style.height = '300px';
-
- document.body.appendChild(div);
-
- // Create the div we want to get the max size for and set a max-height style
- var innerDiv = document.createElement('div');
- innerDiv.style.maxHeight = '50%';
- div.appendChild(innerDiv);
-
- expect(helpers.getMaximumHeight(innerDiv)).toBe(150);
-
- document.body.removeChild(div);
- });
-
- it ('should get the maximum width of a node when the parent has a percentage max-width style', function() {
- // Create div with fixed size as a test bed
- var div = document.createElement('div');
- div.style.width = '200px';
- div.style.height = '300px';
-
- document.body.appendChild(div);
-
- // Create an inner wrapper around our div we want to size and give that a max-width style
- var parentDiv = document.createElement('div');
- parentDiv.style.maxWidth = '50%';
- div.appendChild(parentDiv);
-
- // Create the div we want to get the max size for
- var innerDiv = document.createElement('div');
- parentDiv.appendChild(innerDiv);
-
- expect(helpers.getMaximumWidth(innerDiv)).toBe(100);
-
- document.body.removeChild(div);
- });
-
- it ('should get the maximum height of a node when the parent has a percentage max-height style', function() {
- // Create div with fixed size as a test bed
- var div = document.createElement('div');
- div.style.width = '200px';
- div.style.height = '300px';
-
- document.body.appendChild(div);
-
- // Create an inner wrapper around our div we want to size and give that a max-height style
- var parentDiv = document.createElement('div');
- parentDiv.style.maxHeight = '50%';
- div.appendChild(parentDiv);
-
- var innerDiv = document.createElement('div');
- innerDiv.style.height = '300px'; // make it large
- parentDiv.appendChild(innerDiv);
-
- expect(helpers.getMaximumHeight(innerDiv)).toBe(150);
-
- document.body.removeChild(div);
- });
-
- it ('should leave styled height and width on canvas if explicitly set', function() {
- var chart = window.acquireChart({}, {
- canvas: {
- height: 200,
- width: 200,
- style: 'height: 400px; width: 400px;'
- }
- });
-
- helpers.retinaScale(chart, true);
-
- var canvas = chart.canvas;
-
- expect(canvas.style.height).toBe('400px');
- expect(canvas.style.width).toBe('400px');
- });
-
- it ('Should get padding of parent as number (pixels) when defined as percent (returns incorrectly in IE11)', function() {
-
- // Create div with fixed size as a test bed
- var div = document.createElement('div');
- div.style.width = '300px';
- div.style.height = '300px';
- document.body.appendChild(div);
-
- // Inner DIV to have 5% padding of parent
- var innerDiv = document.createElement('div');
-
- div.appendChild(innerDiv);
-
- var canvas = document.createElement('canvas');
- innerDiv.appendChild(canvas);
-
- // No padding
- expect(helpers.getMaximumWidth(canvas)).toBe(300);
-
- // test with percentage
- innerDiv.style.padding = '5%';
- expect(helpers.getMaximumWidth(canvas)).toBe(270);
-
- // test with pixels
- innerDiv.style.padding = '10px';
- expect(helpers.getMaximumWidth(canvas)).toBe(280);
-
- document.body.removeChild(div);
- });
-
describe('Color helper', function() {
function isColorInstance(obj) {
return typeof obj === 'object' && Object.prototype.hasOwnProperty.call(obj, 'values') && Object.prototype.hasOwnProperty.call(obj.values, 'rgb');
it('should return a color when called with a color', function() {
expect(isColorInstance(helpers.color('rgb(1, 2, 3)'))).toBe(true);
});
-
- it('should return a color when called with a CanvasGradient instance', function() {
- var context = document.createElement('canvas').getContext('2d');
- var gradient = context.createLinearGradient(0, 1, 2, 3);
-
- expect(isColorInstance(helpers.color(gradient))).toBe(true);
- });
});
describe('Background hover color helper', function() {
--- /dev/null
+describe('Curve helper tests', function() {
+ let helpers;
+
+ beforeAll(function() {
+ helpers = window.Chart.helpers.curve;
+ });
+
+ it('should spline curves', function() {
+ expect(helpers.splineCurve({
+ x: 0,
+ y: 0
+ }, {
+ x: 1,
+ y: 1
+ }, {
+ x: 2,
+ y: 0
+ }, 0)).toEqual({
+ previous: {
+ x: 1,
+ y: 1,
+ },
+ next: {
+ x: 1,
+ y: 1,
+ }
+ });
+
+ expect(helpers.splineCurve({
+ x: 0,
+ y: 0
+ }, {
+ x: 1,
+ y: 1
+ }, {
+ x: 2,
+ y: 0
+ }, 1)).toEqual({
+ previous: {
+ x: 0,
+ y: 1,
+ },
+ next: {
+ x: 2,
+ y: 1,
+ }
+ });
+ });
+
+ it('should spline curves with monotone cubic interpolation', function() {
+ var dataPoints = [
+ {_model: {x: 0, y: 0, skip: false}},
+ {_model: {x: 3, y: 6, skip: false}},
+ {_model: {x: 9, y: 6, skip: false}},
+ {_model: {x: 12, y: 60, skip: false}},
+ {_model: {x: 15, y: 60, skip: false}},
+ {_model: {x: 18, y: 120, skip: false}},
+ {_model: {x: null, y: null, skip: true}},
+ {_model: {x: 21, y: 180, skip: false}},
+ {_model: {x: 24, y: 120, skip: false}},
+ {_model: {x: 27, y: 125, skip: false}},
+ {_model: {x: 30, y: 105, skip: false}},
+ {_model: {x: 33, y: 110, skip: false}},
+ {_model: {x: 33, y: 110, skip: false}},
+ {_model: {x: 36, y: 170, skip: false}}
+ ];
+ helpers.splineCurveMonotone(dataPoints);
+ expect(dataPoints).toEqual([{
+ _model: {
+ x: 0,
+ y: 0,
+ skip: false,
+ controlPointNextX: 1,
+ controlPointNextY: 2
+ }
+ },
+ {
+ _model: {
+ x: 3,
+ y: 6,
+ skip: false,
+ controlPointPreviousX: 2,
+ controlPointPreviousY: 6,
+ controlPointNextX: 5,
+ controlPointNextY: 6
+ }
+ },
+ {
+ _model: {
+ x: 9,
+ y: 6,
+ skip: false,
+ controlPointPreviousX: 7,
+ controlPointPreviousY: 6,
+ controlPointNextX: 10,
+ controlPointNextY: 6
+ }
+ },
+ {
+ _model: {
+ x: 12,
+ y: 60,
+ skip: false,
+ controlPointPreviousX: 11,
+ controlPointPreviousY: 60,
+ controlPointNextX: 13,
+ controlPointNextY: 60
+ }
+ },
+ {
+ _model: {
+ x: 15,
+ y: 60,
+ skip: false,
+ controlPointPreviousX: 14,
+ controlPointPreviousY: 60,
+ controlPointNextX: 16,
+ controlPointNextY: 60
+ }
+ },
+ {
+ _model: {
+ x: 18,
+ y: 120,
+ skip: false,
+ controlPointPreviousX: 17,
+ controlPointPreviousY: 100
+ }
+ },
+ {
+ _model: {
+ x: null,
+ y: null,
+ skip: true
+ }
+ },
+ {
+ _model: {
+ x: 21,
+ y: 180,
+ skip: false,
+ controlPointNextX: 22,
+ controlPointNextY: 160
+ }
+ },
+ {
+ _model: {
+ x: 24,
+ y: 120,
+ skip: false,
+ controlPointPreviousX: 23,
+ controlPointPreviousY: 120,
+ controlPointNextX: 25,
+ controlPointNextY: 120
+ }
+ },
+ {
+ _model: {
+ x: 27,
+ y: 125,
+ skip: false,
+ controlPointPreviousX: 26,
+ controlPointPreviousY: 125,
+ controlPointNextX: 28,
+ controlPointNextY: 125
+ }
+ },
+ {
+ _model: {
+ x: 30,
+ y: 105,
+ skip: false,
+ controlPointPreviousX: 29,
+ controlPointPreviousY: 105,
+ controlPointNextX: 31,
+ controlPointNextY: 105
+ }
+ },
+ {
+ _model: {
+ x: 33,
+ y: 110,
+ skip: false,
+ controlPointPreviousX: 32,
+ controlPointPreviousY: 110,
+ controlPointNextX: 33,
+ controlPointNextY: 110
+ }
+ },
+ {
+ _model: {
+ x: 33,
+ y: 110,
+ skip: false,
+ controlPointPreviousX: 33,
+ controlPointPreviousY: 110,
+ controlPointNextX: 34,
+ controlPointNextY: 110
+ }
+ },
+ {
+ _model: {
+ x: 36,
+ y: 170,
+ skip: false,
+ controlPointPreviousX: 35,
+ controlPointPreviousY: 150
+ }
+ }]);
+ });
+});
--- /dev/null
+describe('DOM helpers tests', function() {
+ let helpers;
+
+ beforeAll(function() {
+ helpers = window.Chart.helpers.dom;
+ });
+
+ it ('should get the maximum width and height for a node', function() {
+ // Create div with fixed size as a test bed
+ var div = document.createElement('div');
+ div.style.width = '200px';
+ div.style.height = '300px';
+
+ document.body.appendChild(div);
+
+ // Create the div we want to get the max size for
+ var innerDiv = document.createElement('div');
+ div.appendChild(innerDiv);
+
+ expect(helpers.getMaximumWidth(innerDiv)).toBe(200);
+ expect(helpers.getMaximumHeight(innerDiv)).toBe(300);
+
+ document.body.removeChild(div);
+ });
+
+ it ('should get the maximum width and height for a node in a ShadowRoot', function() {
+ // Create div with fixed size as a test bed
+ var div = document.createElement('div');
+ div.style.width = '200px';
+ div.style.height = '300px';
+
+ document.body.appendChild(div);
+
+ if (!div.attachShadow) {
+ // Shadow DOM is not natively supported
+ return;
+ }
+
+ var shadow = div.attachShadow({mode: 'closed'});
+
+ // Create the div we want to get the max size for
+ var innerDiv = document.createElement('div');
+ shadow.appendChild(innerDiv);
+
+ expect(helpers.getMaximumWidth(innerDiv)).toBe(200);
+ expect(helpers.getMaximumHeight(innerDiv)).toBe(300);
+
+ document.body.removeChild(div);
+ });
+
+ it ('should get the maximum width of a node that has a max-width style', function() {
+ // Create div with fixed size as a test bed
+ var div = document.createElement('div');
+ div.style.width = '200px';
+ div.style.height = '300px';
+
+ document.body.appendChild(div);
+
+ // Create the div we want to get the max size for and set a max-width style
+ var innerDiv = document.createElement('div');
+ innerDiv.style.maxWidth = '150px';
+ div.appendChild(innerDiv);
+
+ expect(helpers.getMaximumWidth(innerDiv)).toBe(150);
+
+ document.body.removeChild(div);
+ });
+
+ it ('should get the maximum height of a node that has a max-height style', function() {
+ // Create div with fixed size as a test bed
+ var div = document.createElement('div');
+ div.style.width = '200px';
+ div.style.height = '300px';
+
+ document.body.appendChild(div);
+
+ // Create the div we want to get the max size for and set a max-height style
+ var innerDiv = document.createElement('div');
+ innerDiv.style.maxHeight = '150px';
+ div.appendChild(innerDiv);
+
+ expect(helpers.getMaximumHeight(innerDiv)).toBe(150);
+
+ document.body.removeChild(div);
+ });
+
+ it ('should get the maximum width of a node when the parent has a max-width style', function() {
+ // Create div with fixed size as a test bed
+ var div = document.createElement('div');
+ div.style.width = '200px';
+ div.style.height = '300px';
+
+ document.body.appendChild(div);
+
+ // Create an inner wrapper around our div we want to size and give that a max-width style
+ var parentDiv = document.createElement('div');
+ parentDiv.style.maxWidth = '150px';
+ div.appendChild(parentDiv);
+
+ // Create the div we want to get the max size for
+ var innerDiv = document.createElement('div');
+ parentDiv.appendChild(innerDiv);
+
+ expect(helpers.getMaximumWidth(innerDiv)).toBe(150);
+
+ document.body.removeChild(div);
+ });
+
+ it ('should get the maximum height of a node when the parent has a max-height style', function() {
+ // Create div with fixed size as a test bed
+ var div = document.createElement('div');
+ div.style.width = '200px';
+ div.style.height = '300px';
+
+ document.body.appendChild(div);
+
+ // Create an inner wrapper around our div we want to size and give that a max-height style
+ var parentDiv = document.createElement('div');
+ parentDiv.style.maxHeight = '150px';
+ div.appendChild(parentDiv);
+
+ // Create the div we want to get the max size for
+ var innerDiv = document.createElement('div');
+ innerDiv.style.height = '300px'; // make it large
+ parentDiv.appendChild(innerDiv);
+
+ expect(helpers.getMaximumHeight(innerDiv)).toBe(150);
+
+ document.body.removeChild(div);
+ });
+
+ it ('should get the maximum width of a node that has a percentage max-width style', function() {
+ // Create div with fixed size as a test bed
+ var div = document.createElement('div');
+ div.style.width = '200px';
+ div.style.height = '300px';
+
+ document.body.appendChild(div);
+
+ // Create the div we want to get the max size for and set a max-width style
+ var innerDiv = document.createElement('div');
+ innerDiv.style.maxWidth = '50%';
+ div.appendChild(innerDiv);
+
+ expect(helpers.getMaximumWidth(innerDiv)).toBe(100);
+
+ document.body.removeChild(div);
+ });
+
+ it ('should get the maximum height of a node that has a percentage max-height style', function() {
+ // Create div with fixed size as a test bed
+ var div = document.createElement('div');
+ div.style.width = '200px';
+ div.style.height = '300px';
+
+ document.body.appendChild(div);
+
+ // Create the div we want to get the max size for and set a max-height style
+ var innerDiv = document.createElement('div');
+ innerDiv.style.maxHeight = '50%';
+ div.appendChild(innerDiv);
+
+ expect(helpers.getMaximumHeight(innerDiv)).toBe(150);
+
+ document.body.removeChild(div);
+ });
+
+ it ('should get the maximum width of a node when the parent has a percentage max-width style', function() {
+ // Create div with fixed size as a test bed
+ var div = document.createElement('div');
+ div.style.width = '200px';
+ div.style.height = '300px';
+
+ document.body.appendChild(div);
+
+ // Create an inner wrapper around our div we want to size and give that a max-width style
+ var parentDiv = document.createElement('div');
+ parentDiv.style.maxWidth = '50%';
+ div.appendChild(parentDiv);
+
+ // Create the div we want to get the max size for
+ var innerDiv = document.createElement('div');
+ parentDiv.appendChild(innerDiv);
+
+ expect(helpers.getMaximumWidth(innerDiv)).toBe(100);
+
+ document.body.removeChild(div);
+ });
+
+ it ('should get the maximum height of a node when the parent has a percentage max-height style', function() {
+ // Create div with fixed size as a test bed
+ var div = document.createElement('div');
+ div.style.width = '200px';
+ div.style.height = '300px';
+
+ document.body.appendChild(div);
+
+ // Create an inner wrapper around our div we want to size and give that a max-height style
+ var parentDiv = document.createElement('div');
+ parentDiv.style.maxHeight = '50%';
+ div.appendChild(parentDiv);
+
+ var innerDiv = document.createElement('div');
+ innerDiv.style.height = '300px'; // make it large
+ parentDiv.appendChild(innerDiv);
+
+ expect(helpers.getMaximumHeight(innerDiv)).toBe(150);
+
+ document.body.removeChild(div);
+ });
+
+ it ('Should get padding of parent as number (pixels) when defined as percent (returns incorrectly in IE11)', function() {
+
+ // Create div with fixed size as a test bed
+ var div = document.createElement('div');
+ div.style.width = '300px';
+ div.style.height = '300px';
+ document.body.appendChild(div);
+
+ // Inner DIV to have 5% padding of parent
+ var innerDiv = document.createElement('div');
+
+ div.appendChild(innerDiv);
+
+ var canvas = document.createElement('canvas');
+ innerDiv.appendChild(canvas);
+
+ // No padding
+ expect(helpers.getMaximumWidth(canvas)).toBe(300);
+
+ // test with percentage
+ innerDiv.style.padding = '5%';
+ expect(helpers.getMaximumWidth(canvas)).toBe(270);
+
+ // test with pixels
+ innerDiv.style.padding = '10px';
+ expect(helpers.getMaximumWidth(canvas)).toBe(280);
+
+ document.body.removeChild(div);
+ });
+
+ it ('should leave styled height and width on canvas if explicitly set', function() {
+ var chart = window.acquireChart({}, {
+ canvas: {
+ height: 200,
+ width: 200,
+ style: 'height: 400px; width: 400px;'
+ }
+ });
+
+ helpers.retinaScale(chart, true);
+
+ var canvas = chart.canvas;
+
+ expect(canvas.style.height).toBe('400px');
+ expect(canvas.style.width).toBe('400px');
+ });
+
+});
describe('Chart.helpers.math', function() {
var math = Chart.helpers.math;
var factorize = math._factorize;
+ var decimalPlaces = math._decimalPlaces;
it('should factorize', function() {
expect(factorize(1000)).toEqual([1, 2, 4, 5, 8, 10, 20, 25, 40, 50, 100, 125, 200, 250, 500]);
expect(math.log10(Math.pow(10, i))).toBe(i);
}
});
+
+ it('should get the correct number of decimal places', function() {
+ expect(decimalPlaces(100)).toBe(0);
+ expect(decimalPlaces(1)).toBe(0);
+ expect(decimalPlaces(0)).toBe(0);
+ expect(decimalPlaces(0.01)).toBe(2);
+ expect(decimalPlaces(-0.01)).toBe(2);
+ expect(decimalPlaces('1')).toBe(undefined);
+ expect(decimalPlaces('')).toBe(undefined);
+ expect(decimalPlaces(undefined)).toBe(undefined);
+ expect(decimalPlaces(12345678.1234)).toBe(4);
+ expect(decimalPlaces(1234567890.1234567)).toBe(7);
+ });
+
+ it('should get an angle from a point', function() {
+ var center = {
+ x: 0,
+ y: 0
+ };
+
+ expect(math.getAngleFromPoint(center, {
+ x: 0,
+ y: 10
+ })).toEqual({
+ angle: Math.PI / 2,
+ distance: 10,
+ });
+
+ expect(math.getAngleFromPoint(center, {
+ x: Math.sqrt(2),
+ y: Math.sqrt(2)
+ })).toEqual({
+ angle: Math.PI / 4,
+ distance: 2
+ });
+
+ expect(math.getAngleFromPoint(center, {
+ x: -1.0 * Math.sqrt(2),
+ y: -1.0 * Math.sqrt(2)
+ })).toEqual({
+ angle: Math.PI * 1.25,
+ distance: 2
+ });
+ });
+
+ it('should convert between radians and degrees', function() {
+ expect(math.toRadians(180)).toBe(Math.PI);
+ expect(math.toRadians(90)).toBe(0.5 * Math.PI);
+ expect(math.toDegrees(Math.PI)).toBe(180);
+ expect(math.toDegrees(Math.PI * 3 / 2)).toBe(270);
+ });
+
+ it('should correctly determine if two numbers are essentially equal', function() {
+ expect(math.almostEquals(0, Number.EPSILON, 2 * Number.EPSILON)).toBe(true);
+ expect(math.almostEquals(1, 1.1, 0.0001)).toBe(false);
+ expect(math.almostEquals(1e30, 1e30 + Number.EPSILON, 0)).toBe(false);
+ expect(math.almostEquals(1e30, 1e30 + Number.EPSILON, 2 * Number.EPSILON)).toBe(true);
+ });
+
+ it('should get the correct sign', function() {
+ expect(math.sign(0)).toBe(0);
+ expect(math.sign(10)).toBe(1);
+ expect(math.sign(-5)).toBe(-1);
+ });
+
+ it('should correctly determine if a numbers are essentially whole', function() {
+ expect(math.almostWhole(0.99999, 0.0001)).toBe(true);
+ expect(math.almostWhole(0.9, 0.0001)).toBe(false);
+ expect(math.almostWhole(1234567890123, 0.0001)).toBe(true);
+ expect(math.almostWhole(1234567890123.001, 0.0001)).toBe(false);
+ });
+
+ it('should detect a number', function() {
+ expect(math.isNumber(123)).toBe(true);
+ expect(math.isNumber('123')).toBe(true);
+ expect(math.isNumber(null)).toBe(false);
+ expect(math.isNumber(NaN)).toBe(false);
+ expect(math.isNumber(undefined)).toBe(false);
+ expect(math.isNumber('cbc')).toBe(false);
+ });
});