Move all helpers to src/helpers (#6841)

* Move all helpers into src/helpers

* Move curve helpers to their own file

* DOM helpers moved to their own file

* Update migration docs

* Remove migration docs on new functions
This commit is contained in:
Evert Timberg 2019-12-17 08:04:40 -05:00 committed by GitHub
parent 374b7491a3
commit c8bdca62e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1200 additions and 1155 deletions

View File

@ -105,6 +105,22 @@ Chart.js 3.0 introduces a number of breaking changes. Chart.js 2.0 was released
* `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`

View File

@ -422,7 +422,7 @@ module.exports = DatasetController.extend({
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;

View File

@ -188,11 +188,11 @@ module.exports = DatasetController.extend({
}
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,

View File

@ -192,7 +192,7 @@ module.exports = DatasetController.extend({
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,

View File

@ -225,7 +225,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
// Before init plugin notification
plugins.notify(me, 'beforeInit');
helpers.retinaScale(me, me.options.devicePixelRatio);
helpers.dom.retinaScale(me, me.options.devicePixelRatio);
me.bindEvents();
@ -263,8 +263,8 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
// 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;
@ -275,7 +275,7 @@ helpers.extend(Chart.prototype, /** @lends Chart */ {
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

View File

@ -145,7 +145,7 @@ function applyStack(stack, value, dsIndex, allOther) {
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;
}
}

View File

@ -1,7 +1,8 @@
'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);
@ -110,10 +111,9 @@ class Element {
}
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;

View File

@ -1,571 +0,0 @@
'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();
};
};

View File

@ -1,6 +1,7 @@
'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
@ -16,7 +17,7 @@ function getRelativePosition(e, chart) {
};
}
return helpers.getRelativePosition(e, chart);
return helpers.dom.getRelativePosition(e, chart);
}
/**
@ -55,7 +56,7 @@ function evaluateItemsAtIndex(chart, axis, position, handler) {
return false;
}
const index = iScale.getIndexForPixel(position[axis]);
if (!helpers.isNumber(index)) {
if (!isNumber(index)) {
return false;
}
indices.push(index);
@ -163,7 +164,7 @@ function getNearestItems(chart, position, axis, intersect) {
* Contains interaction related functions
* @namespace Chart.Interaction
*/
module.exports = {
export default {
// Helper function for different modes
modes: {
/**

View File

@ -636,7 +636,7 @@ class Scale extends Element {
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)
));
@ -699,7 +699,7 @@ class Scale extends Element {
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);
@ -931,7 +931,7 @@ class Scale extends Element {
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));
@ -1103,7 +1103,7 @@ class Scale extends Element {
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;

View File

@ -2,7 +2,7 @@
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', {
@ -101,7 +101,7 @@ class Arc extends Element {
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;

View File

@ -0,0 +1,130 @@
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;
}
}
}

175
src/helpers/helpers.dom.js Normal file
View File

@ -0,0 +1,175 @@
/**
* 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';
}
}

View File

@ -1,5 +1,7 @@
'use strict';
import {isFinite as isFiniteNumber} from './helpers.core';
/**
* @alias Chart.helpers.math
* @namespace
@ -38,3 +40,103 @@ export const log10 = Math.log10 || function(x) {
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));
}

View File

@ -1,17 +1,200 @@
'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();
}
};

View File

@ -4,10 +4,6 @@
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');

View File

@ -41,7 +41,7 @@ var EVENT_TYPES = {
* @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;
}
@ -146,7 +146,7 @@ function createEvent(type, chart, x, y, nativeEvent) {
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);
}

View File

@ -1,6 +1,7 @@
'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;
@ -43,7 +44,7 @@ function generateTicks(generationOptions, dataRange) {
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);
@ -56,17 +57,17 @@ function generateTicks(generationOptions, dataRange) {
// 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);
@ -103,8 +104,8 @@ class LinearScaleBase extends Scale {
// 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
@ -215,7 +216,7 @@ class LinearScaleBase extends Scale {
// 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();

View File

@ -2,6 +2,7 @@
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';
@ -132,7 +133,7 @@ class LogarithmicScale extends Scale {
// 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;

View File

@ -2,6 +2,7 @@
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';
@ -156,7 +157,7 @@ function fitWithPointLabels(scale) {
// 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);
@ -239,7 +240,7 @@ function drawPointLabels(scale) {
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);
@ -287,7 +288,7 @@ function drawRadiusLine(scale, gridLineOpts, radius, index) {
}
function numberOrZero(param) {
return helpers.isNumber(param) ? param : 0;
return isNumber(param) ? param : 0;
}
class RadialLinearScale extends LinearScaleBase {

View File

@ -3,6 +3,7 @@
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;
@ -731,7 +732,7 @@ class TimeScale extends Scale {
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);

View File

@ -20,26 +20,6 @@ describe('Core helper tests', function() {
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));
@ -48,270 +28,6 @@ describe('Core helper tests', function() {
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";
@ -363,257 +79,6 @@ describe('Core helper tests', function() {
}]);
});
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');
@ -622,13 +87,6 @@ describe('Core helper tests', function() {
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() {

View File

@ -0,0 +1,211 @@
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
}
}]);
});
});

View File

@ -0,0 +1,259 @@
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');
});
});

View File

@ -4,6 +4,7 @@
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]);
@ -25,4 +26,84 @@ describe('Chart.helpers.math', function() {
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);
});
});