UNPKG

@amcharts/amcharts4

Version:
701 lines 21.4 kB
/** * A collection of Math-related functions * * @todo Comment trigonometric functions? */ import * as $type from "./Type"; /** * ============================================================================ * CONSTANTS * ============================================================================ * @hidden */ export var PI = Math.PI; export var HALFPI = PI / 2; export var RADIANS = PI / 180; export var DEGREES = 180 / PI; /** * Converts any value and fits it into a specific value range. * * @param value Source value * @param min Minimum allowable value * @param max Maximum allowable value * @return Number */ export function toNumberRange(value, min, max) { if ($type.hasValue(value)) { value = $type.toNumber(value); return fitToRange(value, min, max); } return value; } /** * Rounds the numeric value to whole number or specific precision of set. * * @param value Value * @param precision Precision (number of decimal points) * @param floor In case value ends with 0.5 and precision is 0, we might need to floor the value instead of ceiling it. * @return Rounded value */ export function round(value, precision, floor) { if (!$type.isNumber(precision) || precision <= 0) { var rounded = Math.round(value); if (floor) { if (rounded - value == 0.5) { rounded--; } } return rounded; } else { var d = Math.pow(10, precision); return Math.round(value * d) / d; } } /** * Ceils the numeric value to whole number or specific precision of set. * * @param value Value * @param precision Precision (number of decimal points) * @return Rounded value */ export function ceil(value, precision) { if (!$type.isNumber(precision) || precision <= 0) { return Math.ceil(value); } else { var d = Math.pow(10, precision); return Math.ceil(value * d) / d; } } /** * Stretches `t` so that it will always be between `from` and `to`. * * @param t Number from 0 to 1 * @param from Lowest possible value * @param to Highest possible value * @return Adjusted value */ export function stretch(t, from, to) { return (t * (to - from)) + from; } /** * Adjust numeric value so it fits to specific value range. * * @param value Value * @param minValue Lowest possible value * @param maxValue Highest possible value * @return Adjusted value */ export function fitToRange(value, minValue, maxValue) { if ($type.isNumber(minValue)) { if ($type.isNumber(maxValue) && maxValue < minValue) { var temp = maxValue; maxValue = minValue; minValue = temp; } if (value < minValue) { value = minValue; } } if ($type.isNumber(maxValue)) { if (value > maxValue) { value = maxValue; } } return value; } /** * Returns sine of a number. * * @param value Value * @return Sine */ export function sin(value) { return round(Math.sin(RADIANS * value), 10); } /** * Returns tan of a number. * * @param value Value * @return Sine */ export function tan(value) { return round(Math.tan(RADIANS * value), 10); } /** * Returns cosine of a number. * * @param value Value * @return Cosine */ export function cos(value) { return round(Math.cos(RADIANS * value), 10); } export function max(left, right) { if ($type.isNumber(left)) { if ($type.isNumber(right)) { if (right > left) { return right; } else { return left; } } else { return left; } } else if ($type.isNumber(right)) { return right; } else { return null; } } export function min(left, right) { if ($type.isNumber(left)) { if ($type.isNumber(right)) { if (right < left) { return right; } else { return left; } } else { return left; } } else if ($type.isNumber(right)) { return right; } else { return null; } } /** * Returns the closest value from the array of values to the reference value. * * @param values Array of values * @param value Reference value * @return Closes value from the array */ export function closest(values, referenceValue) { return values.reduce(function (prev, curr) { return (Math.abs(curr - referenceValue) < Math.abs(prev - referenceValue) ? curr : prev); }); } /** * Checks whether two ranges of values intersect. * * @param range1 Range 1 * @param range2 Range 2 * @return Any intersecting numbers? */ export function intersect(range1, range2) { var start1 = $type.getValue(range1.start); var start2 = $type.getValue(range2.start); var end1 = $type.getValue(range1.end); var end2 = $type.getValue(range2.end); return Math.max(start1, start2) <= Math.min(end1, end2); } /** * Inverts the range of values. * * @param range Range */ export function invertRange(range) { var start = $type.getValue(range.start); var end = $type.getValue(range.end); return { start: 1 - end, end: 1 - start }; } /** * Returns an intersection range between two ranges of values. * * @param range1 Range 1 * @param range2 Range 2 * @return Intersecting value range */ export function intersection(range1, range2) { var start1 = $type.getValue(range1.start); var start2 = $type.getValue(range2.start); var end1 = $type.getValue(range1.end); var end2 = $type.getValue(range2.end); var startMax = Math.max(start1, start2); var endMin = Math.min(end1, end2); if (endMin < startMax) { return undefined; } else { return { start: startMax, end: endMin }; } } /** * Returns pixel "distance" between two points. * * If second point is not specified, distance from {x:0, y:0} point is * calculated. * * @param point1 Point 1 * @param point2 Point 2 * @return Distance in relative pixels */ export function getDistance(point1, point2) { if (!point1) { return 0; } if (!point2) { point2 = { x: 0, y: 0 }; } return Math.sqrt(Math.pow(Math.abs(point1.x - point2.x), 2) + Math.pow(Math.abs(point1.y - point2.y), 2)); } /** * Returns pixel "horizontal distance" between two points. * * If second point is not specified, distance from {x:0, y:0} point is * calculated. * * @param point1 Point 1 * @param point2 Point 2 * @return Distance in relative pixels */ export function getHorizontalDistance(point1, point2) { if (!point1) { return 0; } if (!point2) { point2 = { x: 0, y: 0 }; } return Math.abs(point1.x - point2.x); } /** * Returns pixel "vertical distance" between two points. * * If second point is not specified, distance from {x:0, y:0} point is * calculated. * * @param point1 Point 1 * @param point2 Point 2 * @return Distance in relative pixels */ export function getVerticalDistance(point1, point2) { if (!point1) { return 0; } if (!point2) { point2 = { x: 0, y: 0 }; } return Math.abs(point1.y - point2.y); } /** * Returns approximate pixel "distance" between two points of cubic curve * * If second point is not specified, distance from {x:0, y:0} point is * calculated. * * @param point1 Point 1 * @param point2 Point 2 * @param controlPointA Control Point 1 * @param controlPointB Control Point 2 * @param stepCount number of steps (the more, the more accurate result) * @return Distance in relative pixels */ export function getCubicCurveDistance(point1, point2, controlPointA, controlPointB, stepCount) { if (!point1) { return 0; } if (!point2) { point2 = { x: 0, y: 0 }; } var distance = 0; var prevPoint = point1; if (stepCount > 0) { for (var s = 0; s <= stepCount; s++) { var point = getPointOnCubicCurve(point1, point2, controlPointA, controlPointB, s / stepCount); distance += getDistance(prevPoint, point); prevPoint = point; } } return distance; } /** * Returns scale based on original and end position of the two points. * * @param point1 Current position of point 1 * @param startPoint1 Start position of point 1 * @param point2 Current position of point 1 * @param startPoint2 Start position of point 2 * @return Scale Calculated scale */ export function getScale(point1, startPoint1, point2, startPoint2) { var initialDistance = getDistance(startPoint1, startPoint2); var currentDistance = getDistance(point1, point2); return Math.abs(currentDistance / initialDistance); } /** * Returns an exact mid point between two points. * * @param point1 Position of point 1 * @param point2 Position of point 2 * @return Mid point Position of mid-point */ export function getMidPoint(point1, point2, position) { if (!$type.isNumber(position)) { position = 0.5; } return { "x": (point1.x + (point2.x - point1.x) * position), "y": (point1.y + (point2.y - point1.y) * position) }; } /** * Returns difference in angles between starting and ending position of two * vectors. * * @param point1 Current position of point 1 * @param startPoint1 Start position of point 1 * @param point2 Current position of point 1 * @param startPoint2 Start position of point 2 * @return Angle difference in degrees */ export function getRotation(point1, startPoint1, point2, startPoint2) { // Get start and end angles var startAngle = getAngle(startPoint1, startPoint2); var angle = getAngle(point1, point2); // Calculate angle var diff = startAngle - angle; if (diff < 0) { diff += 360; } return diff; } /** * Calculates angle of the vector based on two or one point. * * @param point1 Point 1 * @param point2 Point 2 * @return Angle in degrees */ export function getAngle(point1, point2) { if (!point2) { point2 = { x: point1.x * 2, y: point1.y * 2 }; } var diffX = point2.x - point1.x; var diffY = point2.y - point1.y; var angle = Math.atan2(diffY, diffX) * DEGREES; if (angle < 0) { angle += 360; } return normalizeAngle(angle); } /** * Returns the shift in coordinates of the center when item is rotated, moved * and scaled at the same time. * * @param center Current center * @param point1 Frst reference point * @param startPoint1 Original position of the first reference point * @param point2 Second reference point * @param startPoint2 Original position of the first reference point * @return Shift in center point coordinates */ export function getCenterShift(center, point1, startPoint1, point2, startPoint2) { // Get angle var angle = getRotation(point1, startPoint1, point2, startPoint2) - 90; if (angle < 0) { angle += 360; } // Get distance between new position var distance = getDistance(point1, point2); // Calculate new X var x = Math.cos(angle) / distance + point1.x; var y = Math.cos(angle) / distance + point1.y; var shift = { "x": x - center.x, "y": y - center.y }; return shift; } /** * Converts an array of points into a bounding box rectangle. * * Array can contain any number of points. * * @param points Points * @return Bounding box rectangle */ export function getBBox(points) { if (points) { var length_1 = points.length; if (length_1 !== 0) { var left = void 0; var right = void 0; var top_1; var bottom = void 0; for (var i = 0; i < length_1; i++) { var point = points[i]; if (!$type.isNumber(right) || (point.x > right)) { right = point.x; } if (!$type.isNumber(left) || (point.x < left)) { left = point.x; } if (!$type.isNumber(top_1) || (point.y < top_1)) { top_1 = point.y; } if (!$type.isNumber(bottom) || (point.y > bottom)) { bottom = point.y; } } return { x: left, y: top_1, width: right - left, height: bottom - top_1 }; } } return { x: 0, y: 0, width: 0, height: 0 }; } /** * Returns a [[IRectangle]] object representing a common rectangle that fits * all passed in rectangles in it. * * @param rectangles An array of rectangles * @return Common rectangle */ export function getCommonRectangle(rectangles) { var length = rectangles.length; if (length !== 0) { var minX = void 0; var minY = void 0; var maxX = void 0; var maxY = void 0; for (var i = 0; i < length; i++) { var rectangle = rectangles[i]; minX = min(rectangle.x, minX); minY = min(rectangle.y, minY); maxX = max(rectangle.x + rectangle.width, maxX); maxY = max(rectangle.y + rectangle.height, maxY); } return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }; } } /** * [getPointOnQuadraticCurve description] * * @ignore Exclude from docs * @todo Description * @param pointA [description] * @param pointB [description] * @param controlPoint [description] * @param position [description] * @return [description] */ export function getPointOnQuadraticCurve(pointA, pointB, controlPoint, position) { var x = (1 - position) * (1 - position) * pointA.x + 2 * (1 - position) * position * controlPoint.x + position * position * pointB.x; var y = (1 - position) * (1 - position) * pointA.y + 2 * (1 - position) * position * controlPoint.y + position * position * pointB.y; return { x: x, y: y }; } /** * [getPointOnCubicCurve description] * * @ignore Exclude from docs * @todo Description * @param pointA [description] * @param pointB [description] * @param controlPointA [description] * @param controlPointB [description] * @param position [description] * @return [description] */ export function getPointOnCubicCurve(pointA, pointB, controlPointA, controlPointB, position) { var point = { x: 0, y: 0 }; var mt1 = 1 - position; var mt2 = mt1 * mt1; var mt3 = mt2 * mt1; point.x = pointA.x * mt3 + controlPointA.x * 3 * mt2 * position + controlPointB.x * 3 * mt1 * position * position + pointB.x * position * position * position; point.y = pointA.y * mt3 + controlPointA.y * 3 * mt2 * position + controlPointB.y * 3 * mt1 * position * position + pointB.y * position * position * position; return point; } /** * [getCubicControlPointA description] * * @ignore Exclude from docs * @todo Description * @param p0 [description] * @param p1 [description] * @param p2 [description] * @param p3 [description] * @param tensionX [description] * @param tensionY [description] * @return [description] */ export function getCubicControlPointA(p0, p1, p2, p3, tensionX, tensionY) { tensionX = adjustTension(tensionX); tensionY = adjustTension(tensionY); return { x: ((-p0.x + p1.x / tensionX + p2.x) * tensionX), y: ((-p0.y + p1.y / tensionY + p2.y) * tensionY) }; } /** * [getCubicControlPointB description] * * @ignore Exclude from docs * @todo Description * @param p0 [description] * @param p1 [description] * @param p2 [description] * @param p3 [description] * @param tensionX [description] * @param tensionY [description] * @return [description] */ export function getCubicControlPointB(p0, p1, p2, p3, tensionX, tensionY) { tensionX = adjustTension(tensionX); tensionY = adjustTension(tensionY); return { x: ((p1.x + p2.x / tensionX - p3.x) * tensionX), y: ((p1.y + p2.y / tensionY - p3.y) * tensionY) }; } /** * [adjustTension description] * * @ignore Exclude from docs * @todo Description * @param tension [description] * @return [description] */ export function adjustTension(tension) { return 1 - tension + 0.00001; } /** * [normalizeAngle description] * * @ignore Exclude from docs * @todo Description * @param value [description] * @return [description] */ export function normalizeAngle(value) { if (value == 360) { return 360; } return value % 360; } /** * [normalizeAngleToRange description] * * @ignore Exclude from docs * @todo Description * @todo review this with various angles, can be tested on radar chart with custom start/end angles * @param value [description] * @param startAngle [description] * @param endAngle [description] * @return [description] */ export function fitAngleToRange(value, startAngle, endAngle) { if (startAngle > endAngle) { var temp = startAngle; startAngle = endAngle; endAngle = temp; } value = normalizeAngle(value); var count = (startAngle - normalizeAngle(startAngle)) / 360; if (value < startAngle) { value += 360 * (count + 1); } var maxEnd = startAngle + (endAngle - startAngle) / 2 + 180; var maxStart = startAngle + (endAngle - startAngle) / 2 - 180; if (value > endAngle) { if (value - 360 > startAngle) { value -= 360; } else { if (value < maxEnd) { value = endAngle; } else { value = startAngle; } } } if (value < startAngle) { if (value > maxStart) { value = startAngle; } else { value = endAngle; } } return value; } /** * Returns [[IRectangle]] of an arc in relative values, assuming that the * center is at the circle center. * * Used to find out max radius of an arc. * * @ignore Exclude from docs * @param startAngle Start angle * @param endAngle End angle * @param radius Relative radius * @return Rectangle */ export function getArcRect(startAngle, endAngle, radius) { var minX = Number.MAX_VALUE; var minY = Number.MAX_VALUE; var maxX = -Number.MAX_VALUE; var maxY = -Number.MAX_VALUE; var bpoints = []; if (!$type.isNumber(radius)) { radius = 1; } bpoints.push(getArcPoint(radius, startAngle)); bpoints.push(getArcPoint(radius, endAngle)); var fromAngle = Math.min(Math.floor(startAngle / 90) * 90, Math.floor(endAngle / 90) * 90); var toAngle = Math.max(Math.ceil(startAngle / 90) * 90, Math.ceil(endAngle / 90) * 90); for (var angle = fromAngle; angle <= toAngle; angle += 90) { if (angle >= startAngle && angle <= endAngle) { bpoints.push(getArcPoint(radius, angle)); } } for (var i = 0; i < bpoints.length; i++) { var pt = bpoints[i]; if (pt.x < minX) { minX = pt.x; } if (pt.y < minY) { minY = pt.y; } if (pt.x > maxX) { maxX = pt.x; } if (pt.y > maxY) { maxY = pt.y; } } return ({ x: minX, y: minY, width: maxX - minX, height: maxY - minY }); } /** * Returns point on arc * * @param center point * @param radius * @param arc * @return {boolean} */ export function getArcPoint(radius, arc) { return ({ x: radius * cos(arc), y: radius * sin(arc) }); } /** * Returns true if a point is within rectangle * * @param point * @param rectangle * @return {boolean} */ export function isInRectangle(point, rectangle) { if (point.x >= rectangle.x && point.x <= rectangle.x + rectangle.width && point.y >= rectangle.y && point.y <= rectangle.y + rectangle.height) { return true; } return false; } export function getLineIntersection(pointA1, pointA2, pointB1, pointB2) { var x = ((pointA1.x * pointA2.y - pointA2.x * pointA1.y) * (pointB1.x - pointB2.x) - (pointA1.x - pointA2.x) * (pointB1.x * pointB2.y - pointB1.y * pointB2.x)) / ((pointA1.x - pointA2.x) * (pointB1.y - pointB2.y) - (pointA1.y - pointA2.y) * (pointB1.x - pointB2.x)); var y = ((pointA1.x * pointA2.y - pointA2.x * pointA1.y) * (pointB1.y - pointB2.y) - (pointA1.y - pointA2.y) * (pointB1.x * pointB2.y - pointB1.y * pointB2.x)) / ((pointA1.x - pointA2.x) * (pointB1.y - pointB2.y) - (pointA1.y - pointA2.y) * (pointB1.x - pointB2.x)); return { x: x, y: y }; } //# sourceMappingURL=Math.js.map