@syncfusion/ej2-charts
Version:
Feature-rich chart control with built-in support for over 25 chart types, technical indictors, trendline, zooming, tooltip, selection, crosshair and trackball.
1,099 lines • 148 kB
JavaScript
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import { Animation, compile as templateComplier, Browser } from '@syncfusion/ej2-base';
import { merge, extend, isNullOrUndefined } from '@syncfusion/ej2-base';
import { createElement, remove } from '@syncfusion/ej2-base';
import { Index } from '../../common/model/base';
import { VisibleLabels } from '../../chart/axis/axis';
import { axisLabelRender, regSub } from '../model/constants';
import { measureText, Rect, TextOption, Size, PathOption, SvgRenderer, CanvasRenderer } from '@syncfusion/ej2-svg-base';
/**
* Function to sort the dataSource, by default it sort the data in ascending order.
*
* @param {Object} data chart data
* @param {string} fields date fields
* @param {boolean} isDescending boolean values of descending
* @returns {Object[]} It returns chart data which be sorted.
*/
export function sort(data, fields, isDescending) {
var sortData = extend([], data, null);
for (var i = 0; i < sortData.length; i++) {
for (var j = 0; j < fields.length; j++) {
if (sortData[i][fields[j]] instanceof Date) {
sortData[i][fields[j]] = sortData[i][fields[j]].getTime();
}
}
}
sortData.sort(function (a, b) {
var first = 0;
var second = 0;
for (var i = 0; i < fields.length; i++) {
first += a[fields[i]];
second += b[fields[i]];
}
if ((!isDescending && first < second) || (isDescending && first > second)) {
return -1;
}
else if (first === second) {
return 0;
}
return 1;
});
return sortData;
}
/**
* Checks if a label contains a line break.
*
* @param {string} label - The label to check.
* @returns {boolean} - True if the label contains a line break, otherwise false.
*/
export function isBreakLabel(label) {
return label.indexOf('<br>') !== -1;
}
/**
* Retrieves the visible data points from a series.
*
* @param {Series | Chart3DSeries} series - The series to retrieve the visible data points.
* @returns {Points[]} - An array containing the visible data points.
*/
export function getVisiblePoints(series) {
var points = extend([], series.points, null, true);
var tempPoints = [];
var tempPoint;
var pointIndex = 0;
for (var i = 0; i < points.length; i++) {
tempPoint = points[i];
if (isNullOrUndefined(tempPoint.x)) {
continue;
}
else {
tempPoint.index = pointIndex++;
tempPoints.push(tempPoint);
}
}
return tempPoints;
}
/**
* Calculates the offset for positioning a scrollbar on a chart axis.
*
* @param {ScrollBar} scrollbar - The scrollbar object to position.
* @param {boolean} isHorizontalAxis - Indicates whether the axis is horizontal.
* @returns {number} An object containing the calculated top and left offsets for the scrollbar.
*/
export function calculateScrollbarOffset(scrollbar, isHorizontalAxis) {
var scrollbarPadding = 5;
var chart = scrollbar.component;
var titleHeight = 0;
var subTitleHeight = 0;
var titlePadding = chart.titleStyle.position === 'Top' || (chart.titleStyle.position === 'Bottom' && !chart.legendSettings.visible) ? 15 : 5;
if (chart.title) {
titleHeight = (measureText(chart.title, chart.titleStyle, chart.themeStyle.chartTitleFont).height *
chart.titleCollection.length) + titlePadding;
if (chart.subTitle) {
subTitleHeight = (measureText(chart.subTitle, chart.subTitleStyle, chart.themeStyle.chartSubTitleFont).height *
chart.subTitleCollection.length);
}
}
var scrollbarOffsetValue;
if (isHorizontalAxis) {
if (scrollbar.axis.scrollbarSettings.position === 'Top') {
scrollbarOffsetValue = chart.margin.top + scrollbarPadding + ((scrollbar.height + scrollbarPadding) * chart.scrollBarModule.topScrollBarCount) + (chart.titleStyle.position === 'Top' ? titleHeight
+ chart.titleStyle.border.width : 0) + (chart.subTitleStyle.position === 'Top' ? chart.subTitleStyle.border.width + subTitleHeight : 0);
chart.scrollBarModule.topScrollBarCount++;
}
else if (scrollbar.axis.scrollbarSettings.position === 'Bottom') {
scrollbarOffsetValue = chart.availableSize.height - (((scrollbar.height + scrollbarPadding) * chart.scrollBarModule.bottomScrollBarCount) + scrollbar.height + chart.margin.bottom + scrollbarPadding + (chart.titleStyle.position === 'Bottom' ? titleHeight
+ chart.titleStyle.border.width : 0) + (chart.subTitleStyle.position === 'Bottom' ? chart.subTitleStyle.border.width + subTitleHeight : 0));
chart.scrollBarModule.bottomScrollBarCount++;
}
}
else {
if (scrollbar.axis.scrollbarSettings.position === 'Right') {
scrollbarOffsetValue = chart.availableSize.width - (((scrollbar.height + scrollbarPadding) * chart.scrollBarModule.rightScrollBarCount) + scrollbar.height + scrollbarPadding + chart.margin.right + (chart.titleStyle.position === 'Right' ? titleHeight
+ chart.titleStyle.border.width : 0) + (chart.subTitleStyle.position === 'Right' ? chart.subTitleStyle.border.width + subTitleHeight : 0));
chart.scrollBarModule.rightScrollBarCount++;
}
else if (scrollbar.axis.scrollbarSettings.position === 'Left') {
scrollbarOffsetValue = chart.margin.left + scrollbarPadding + ((scrollbar.height + scrollbarPadding) * chart.scrollBarModule.leftScrollBarCount) + (chart.titleStyle.position === 'Left' ? titleHeight
+ chart.titleStyle.border.width : 0) + (chart.subTitleStyle.position === 'Left' ? chart.subTitleStyle.border.width + subTitleHeight : 0);
chart.scrollBarModule.leftScrollBarCount++;
}
}
return scrollbarOffsetValue;
}
/**
* Rotates the size of text based on the provided angle.
*
* @param {FontModel} font - The font style of the text.
* @param {string} text - The text to be rotated.
* @param {number} angle - The angle of rotation.
* @param {Chart | Chart3D} chart - The chart instance.
* @param {FontModel} themeFontStyle - The font style based on the theme.
* @returns {Size} - The rotated size of the text.
*/
export function rotateTextSize(font, text, angle, chart, themeFontStyle) {
var transformValue = chart.element.style.transform;
if (transformValue) {
chart.element.style.transform = '';
}
var renderer = new SvgRenderer(chart.element.id);
var labelText;
var textCollection = [];
var height;
var dy;
var label;
var tspanElement;
var options = {
id: 'rotate_text',
x: chart.initialClipRect.x,
y: chart.initialClipRect.y,
'font-size': font.size || themeFontStyle.size,
'font-style': font.fontStyle || themeFontStyle.fontStyle,
'font-family': font.fontFamily,
'font-weight': font.fontWeight || themeFontStyle.fontWeight,
'transform': 'rotate(' + angle + ', 0, 0)',
'text-anchor': 'middle'
};
if (isBreakLabel(text)) {
textCollection = text.split('<br>');
labelText = textCollection[0];
}
else {
labelText = text;
}
var htmlObject = renderer.createText(options, labelText);
if (!chart.delayRedraw && !chart.redraw && !chart.stockChart) {
chart.element.appendChild(chart.svgObject);
}
// for line break label
if (typeof textCollection !== 'string' && textCollection.length > 1) {
for (var i = 1, len = textCollection.length; i < len; i++) {
height = (measureText(textCollection[i], font, chart.themeStyle.axisLabelFont).height);
dy = (options.y) + ((i * height));
label = textCollection[i];
tspanElement = renderer.createTSpan({
'x': options.x, 'id': options.id,
'y': dy
}, label);
htmlObject.appendChild(tspanElement);
}
}
var axisSvgObject = chart.svgRenderer.createSvg({
id: 'AxisLabelMax_svg', width: chart.availableSize.width,
height: chart.availableSize.height
});
document.body.appendChild(axisSvgObject);
axisSvgObject.appendChild(htmlObject);
var box = htmlObject.getBoundingClientRect();
if (transformValue) {
chart.element.style.transform = transformValue;
}
remove(axisSvgObject);
if (!chart.delayRedraw && !chart.redraw && !chart.stockChart && !chart.pointsAdded) {
remove(chart.svgObject);
}
if (chart.enableCanvas) {
var textWidth = measureText(text, font, chart.themeStyle.axisLabelFont).width;
var textHeight = measureText(text, font, chart.themeStyle.axisLabelFont).height;
var angleInRadians = (angle * Math.PI) / 180; // Convert the rotation angle to radians
var rotatedTextWidth = Math.abs(Math.cos(angleInRadians) * textWidth) + Math.abs(Math.sin(angleInRadians) * textHeight);
var rotatedTextHeight = Math.abs(Math.sin(angleInRadians) * textWidth) + Math.abs(Math.cos(angleInRadians) * textHeight);
return new Size(rotatedTextWidth, rotatedTextHeight);
}
return new Size((box.right - box.left), (box.bottom - box.top));
}
/**
* Removes the specified element.
*
* @param {string | Element} id - The id or reference of the element to be removed.
* @returns {void}
*/
export function removeElement(id) {
if (!id) {
return null;
}
var element = typeof id === 'string' ? getElement(id) : id;
if (element) {
remove(element);
}
}
/**
* Calculates the logarithm of a specified value with respect to a specified base.
*
* @param {number} value - The value for which to calculate the logarithm.
* @param {number} base - The base of the logarithm.
* @returns {number} - The logarithm of the value with respect to the specified base.
*/
export function logBase(value, base) {
return Math.log(value) / Math.log(base);
}
/**
* Displays a tooltip at the specified coordinates with the given text.
*
* @param {string} text - The text content of the tooltip.
* @param {number} x - The x-coordinate where the tooltip should be displayed.
* @param {number} y - The y-coordinate where the tooltip should be displayed.
* @param {number} areaWidth - The width of the area where the tooltip is displayed.
* @param {string} id - The id of the tooltip element.
* @param {Element} element - The element to which the tooltip is appended.
* @param {boolean} isTouch - Indicates whether the tooltip is displayed on a touch device.
* @param {boolean} isTitleOrLegendEnabled - Indicates whether the tooltip is enabled for title or legend.
* @param {Rect} bound - The bounding rectangle in which the tooltip should be confined.
* @returns {void}
* @private
*/
export function showTooltip(text, x, y, areaWidth, id, element, isTouch, isTitleOrLegendEnabled, bound) {
//let id1: string = 'EJ2_legend_tooltip';
var tooltip = document.getElementById(id);
var size = measureText(text, {
fontFamily: 'Segoe UI', size: '12px',
fontStyle: 'Normal', fontWeight: 'Regular'
});
var width = size.width + 5;
x = (x + width > areaWidth) ? x - (width + 15) : x;
if (bound && x < bound.x) {
x = bound.x;
}
y = isTitleOrLegendEnabled ? (y - size.height / 2) : y + 15;
if (bound && y + size.height > bound.y + bound.height) {
y -= (y + size.height) - (bound.y + bound.height);
}
if (!tooltip) {
tooltip = createElement('div', {
id: id,
styles: 'top:' + (y).toString() + 'px;left:' + (x + 15).toString() +
'px;background-color: rgb(255, 255, 255) !important; color:black !important; ' +
'position:absolute;border:1px solid rgb(112, 112, 112); padding-left : 3px; padding-right : 2px;' +
'padding-bottom : 2px; padding-top : 2px; font-size:12px; font-family: "Segoe UI"'
});
tooltip.innerText = text;
element.appendChild(tooltip);
var left = parseInt(tooltip.style.left.replace('px', ''), 10);
if (left < 0) {
tooltip.style.left = '0px';
}
}
else {
tooltip.innerText = text;
tooltip.style.top = (y).toString() + 'px';
tooltip.style.left = (x + 15).toString() + 'px';
}
if (isTouch) {
setTimeout(function () { removeElement(id); }, 1500);
}
}
/**
* Checks if a value is within the specified range.
*
* @param {number} value - The value to check.
* @param {VisibleRangeModel} range - The range to check against.
* @returns {boolean} - True if the value is inside the range, otherwise false.
*/
export function inside(value, range) {
return (value < range.max) && (value > range.min);
}
/**
* Checks if a value is within the specified range.
*
* @param {number} value - The value to check.
* @param {VisibleRangeModel} range - The range to check against.
* @returns {boolean} - True if the value is inside the range, otherwise false.
*/
export function withIn(value, range) {
return (value <= range.max) && (value >= range.min);
}
/**
* Adjusts the value based on the axis type.
*
* @param {number} value - The value to adjust.
* @param {Axis} axis - The axis used for adjustment.
* @returns {number} - The adjusted value.
*/
export function logWithIn(value, axis) {
return axis.valueType === 'Logarithmic' ? logBase(value, axis.logBase) : value;
}
/**
* Checks if a point is within the range of the previous and next points in a series.
*
* @param {Points} previousPoint - The previous point in the series.
* @param {Points} currentPoint - The current point to check.
* @param {Points} nextPoint - The next point in the series.
* @param {Series} series - The series to which the points belong.
* @returns {boolean} - A boolean indicating if the point is within the range.
* @private
*/
export function withInRange(previousPoint, currentPoint, nextPoint, series) {
if (series.chart.zoomModule && series.chart.zoomSettings.enableAnimation) {
return true;
}
var mX2 = logWithIn(currentPoint.xValue, series.xAxis);
var mX1 = previousPoint ? logWithIn(previousPoint.xValue, series.xAxis) : mX2;
var mX3 = nextPoint ? logWithIn(nextPoint.xValue, series.xAxis) : mX2;
var xStart = Math.floor(series.xAxis.visibleRange.min);
var xEnd = Math.ceil(series.xAxis.visibleRange.max);
return ((mX1 >= xStart && mX1 <= xEnd) || (mX2 >= xStart && mX2 <= xEnd) ||
(mX3 >= xStart && mX3 <= xEnd) || (xStart >= mX1 && xStart <= mX3));
}
/**
* Calculates the sum of an array of numbers.
*
* @param {number[]} values - An array of numbers.
* @returns {number} - The sum of the numbers in the array.
*/
export function sum(values) {
var sum = 0;
for (var _i = 0, values_1 = values; _i < values_1.length; _i++) {
var value = values_1[_i];
sum += value;
}
return sum;
}
/**
* Calculates the sum of elements in a subarray.
*
* @param {Object[]} values - The array containing elements.
* @param {number} first - The index of the first element in the subarray.
* @param {number} last - The index of the last element in the subarray.
* @param {number[]} index - The array of indices.
* @param {Series} series - The series object.
* @returns {number} - The sum of elements in the subarray.
* @private
*/
export function subArraySum(values, first, last, index, series) {
var sum = 0;
var sumIndex = 0;
var isFirst = true;
if (index !== null) {
for (var i = (first + 1); i < last; i++) {
if (index.indexOf(i) === -1 && (i === series.intermediateSumIndexes[sumIndex] ||
series.intermediateSumIndexes[series.intermediateSumIndexes.length - 1] < i)) {
sum += values[i][series.yName];
if (i === series.intermediateSumIndexes[sumIndex]) {
isFirst = false;
sumIndex += 1;
}
}
}
}
else {
for (var i = (first + 1); i < last; i++) {
if (!isNullOrUndefined(values[i][series.yName]) && !isNullOrUndefined(series.sumIndexes) &&
series.sumIndexes.indexOf(i) === -1) {
sum += values[i][series.yName];
}
}
}
return sum;
}
/**
* Subtracts thickness from the given rectangle.
*
* @param {Rect} rect - The rectangle from which to subtract thickness.
* @param {Thickness} thickness - The thickness to subtract.
* @returns {Rect} - The resulting rectangle after subtracting thickness.
*/
export function subtractThickness(rect, thickness) {
rect.x += thickness.left;
rect.y += thickness.top;
rect.width -= thickness.left + thickness.right;
rect.height -= thickness.top + thickness.bottom;
return rect;
}
/**
* Subtracts a rectangle representing thickness from the given rectangle.
*
* @param {Rect} rect - The rectangle from which to subtract the thickness rectangle.
* @param {Thickness} thickness - The rectangle representing the thickness to subtract.
* @returns {Rect} - The resulting rectangle after subtracting the thickness rectangle.
*/
export function subtractRect(rect, thickness) {
rect.x += thickness.x;
rect.y += thickness.y;
rect.width -= thickness.x + thickness.width;
rect.height -= thickness.y + thickness.height;
return rect;
}
/**
* Converts a degree value to a location on the chart based on the provided radius and center point.
*
* @param {number} degree - The degree value to convert.
* @param {number} radius - The radius from the center point.
* @param {ChartLocation} center - The center point of the chart.
* @returns {ChartLocation} - The location on the chart corresponding to the degree value.
*/
export function degreeToLocation(degree, radius, center) {
var radian = (degree * Math.PI) / 180;
return new ChartLocation(Math.cos(radian) * radius + center.x, Math.sin(radian) * radius + center.y);
}
/**
* Converts a degree value to radians.
*
* @param {number} degree - The degree value to convert.
* @returns {number} - The equivalent value in radians.
*/
export function degreeToRadian(degree) {
return degree * (Math.PI / 180);
}
/**
* Get the coordinates of a rotated rectangle.
*
* @param {ChartLocation[]} actualPoints - The coordinates of the original rectangle.
* @param {number} centerX - The x-coordinate of the center of rotation.
* @param {number} centerY - The y-coordinate of the center of rotation.
* @param {number} angle - The angle of rotation in degrees.
* @returns {ChartLocation[]} - The coordinates of the rotated rectangle.
*/
export function getRotatedRectangleCoordinates(actualPoints, centerX, centerY, angle) {
var coordinatesAfterRotation = [];
for (var i = 0; i < 4; i++) {
var point = actualPoints[i];
// translate point to origin
var tempX = point.x - centerX;
var tempY = point.y - centerY;
// now apply rotation
var rotatedX = tempX * Math.cos(degreeToRadian(angle)) - tempY * Math.sin(degreeToRadian(angle));
var rotatedY = tempX * Math.sin(degreeToRadian(angle)) + tempY * Math.cos(degreeToRadian(angle));
// translate back
point.x = rotatedX + centerX;
point.y = rotatedY + centerY;
coordinatesAfterRotation.push(new ChartLocation(point.x, point.y));
}
return coordinatesAfterRotation;
}
/**
* Helper function to determine whether there is an intersection between the two polygons described
* by the lists of vertices. Uses the Separating Axis Theorem.
*
* @param {ChartLocation[]} a an array of connected points [{x:, y:}, {x:, y:},...] that form a closed polygon.
* @param {ChartLocation[]} b an array of connected points [{x:, y:}, {x:, y:},...] that form a closed polygon.
* @returns {boolean} if there is any intersection between the 2 polygons, false otherwise.
*/
export function isRotatedRectIntersect(a, b) {
var polygons = [a, b];
var minA;
var maxA;
var projected;
var i;
var i1;
var j;
var minB;
var maxB;
for (i = 0; i < polygons.length; i++) {
// for each polygon, look at each edge of the polygon, and determine if it separates
// the two shapes
var polygon = polygons[i];
for (i1 = 0; i1 < polygon.length; i1++) {
// grab 2 vertices to create an edge
var i2 = (i1 + 1) % polygon.length;
var p1 = polygon[i1];
var p2 = polygon[i2];
// find the line perpendicular to this edge
var normal = new ChartLocation(p2.y - p1.y, p1.x - p2.x);
minA = maxA = undefined;
// for each vertex in the first shape, project it onto the line perpendicular to the edge
// and keep track of the min and max of these values
for (j = 0; j < a.length; j++) {
projected = normal.x * a[j].x + normal.y * a[j].y;
if (isNullOrUndefined(minA) || projected < minA) {
minA = projected;
}
if (isNullOrUndefined(maxA) || projected > maxA) {
maxA = projected;
}
}
// for each vertex in the second shape, project it onto the line perpendicular to the edge
// and keep track of the min and max of these values
minB = maxB = undefined;
for (j = 0; j < b.length; j++) {
projected = normal.x * b[j].x + normal.y * b[j].y;
if (isNullOrUndefined(minB) || projected < minB) {
minB = projected;
}
if (isNullOrUndefined(maxB) || projected > maxB) {
maxB = projected;
}
}
// if there is no overlap between the projects, the edge we are looking at separates the two
// polygons, and we know there is no overlap
if (maxA < minB || maxB < minA) {
return false;
}
}
}
return true;
}
/**
* Generates the legend for accumulation chart.
*
* @param {number} locX - The x-coordinate of the legend position.
* @param {number} locY - The y-coordinate of the legend position.
* @param {number} r - The radius of the chart.
* @param {number} height - The height of the legend.
* @param {number} width - The width of the legend.
* @returns {string} - The generated legend.
*/
function getAccumulationLegend(locX, locY, r, height, width) {
var cartesianlarge = degreeToLocation(270, r, new ChartLocation(locX, locY));
var cartesiansmall = degreeToLocation(270, r, new ChartLocation(locX + (width / 10), locY));
return 'M' + ' ' + locX + ' ' + locY + ' ' + 'L' + ' ' + (locX + r) + ' ' + (locY) + ' ' + 'A' + ' ' + (r) + ' ' + (r) +
' ' + 0 + ' ' + 1 + ' ' + 1 + ' ' + cartesianlarge.x + ' ' + cartesianlarge.y + ' ' + 'Z' + ' ' + 'M' + ' ' + (locX +
(width / 10)) + ' ' + (locY - (height / 10)) + ' ' + 'L' + (locX + (r)) + ' ' + (locY - height / 10) + ' ' + 'A' + ' '
+ (r) + ' ' + (r) + ' ' + 0 + ' ' + 0 + ' ' + 0 + ' ' + cartesiansmall.x + ' ' + cartesiansmall.y + ' ' + 'Z';
}
/**
* Calculates the angle between two points.
*
* @param {ChartLocation} center - The center point.
* @param {ChartLocation} point - The point to calculate the angle from the center.
* @returns {number} - The angle in degrees.
*/
export function getAngle(center, point) {
var angle = Math.atan2((point.y - center.y), (point.x - center.x));
angle = angle < 0 ? (6.283 + angle) : angle;
return angle * (180 / Math.PI);
}
/**
* Returns a sub-array of values starting from the specified index.
*
* @param {number[]} values - The array of numbers.
* @param {number} index - The index from which the sub-array starts.
* @returns {number[]} - The sub-array of values.
*/
export function subArray(values, index) {
var subArray = [];
for (var i = 0; i <= index - 1; i++) {
subArray.push(values[i]);
}
return subArray;
}
/**
* Converts a value to its corresponding coefficient based on the axis range.
*
* @param {number} value - The value to be converted.
* @param {Axis} axis - The axis object containing range information.
* @returns {number} - The coefficient value corresponding to the input value.
*/
export function valueToCoefficient(value, axis) {
var range = axis.visibleRange;
var result;
if (range.delta === 0) {
result = 0.5;
}
else {
result = (value - range.min) / range.delta;
}
var isInverse = axis.isChart ? axis.isAxisInverse : axis.isInversed;
return isInverse ? (1 - result) : result;
}
/**
* Transforms a point to its visible position based on the axes range and inversion.
*
* @param {number} x - The x-coordinate of the point.
* @param {number} y - The y-coordinate of the point.
* @param {Axis} xAxis - The x-axis object containing range information.
* @param {Axis} yAxis - The y-axis object containing range information.
* @param {boolean} [isInverted=false] - Specifies if the chart is inverted.
* @param {Series} [series] - The series object for additional information (optional).
* @returns {ChartLocation} - The transformed visible position of the point.
*/
export function TransformToVisible(x, y, xAxis, yAxis, isInverted, series) {
x = (xAxis.valueType === 'Logarithmic' ? logBase(x > 1 ? x : 1, xAxis.logBase) : x);
y = (yAxis.valueType === 'Logarithmic' ?
logBase(y > 1 ? y : 1, yAxis.logBase) : y);
x += xAxis.valueType === 'Category' && xAxis.labelPlacement === 'BetweenTicks' && series.type !== 'Radar' ? 0.5 : 0;
var radius = series.chart.radius * valueToCoefficient(y, yAxis);
var point = CoefficientToVector(valueToPolarCoefficient(x, xAxis), series.chart.primaryXAxis.startAngle);
return {
x: (series.clipRect.width / 2 + series.clipRect.x) + radius * point.x,
y: (series.clipRect.height / 2 + series.clipRect.y) + radius * point.y
};
}
/**
* Finds the index from the given id.
*
* @param {string} id - The id to search for.
* @param {boolean} [isPoint=false] - Specifies if the id represents a data point (optional).
* @returns {Index} - The index found from the id.
*/
export function indexFinder(id, isPoint) {
if (isPoint === void 0) { isPoint = false; }
var ids = ['NaN', 'NaN'];
if (id.indexOf('_polygon') > -1) {
ids = ['NaN', 'NaN'];
}
else if (id.indexOf('_Point_') > -1) {
ids = id.split('_Series_')[1].split('_Point_');
}
else if (id.indexOf('_shape_') > -1 && (!isPoint || (isPoint && id.indexOf('_legend_') === -1))) {
ids = id.split('_shape_');
ids[0] = '0';
}
else if (id.indexOf('_text_') > -1 && (!isPoint || (isPoint && id.indexOf('_legend_') === -1))) {
ids = id.split('_text_');
ids[0] = '0';
}
else if (id.indexOf('_datalabel_') > -1) {
ids = id.split('_datalabel_')[1].split('_g_');
ids[0] = ids[0].replace('Series_', '');
}
else if (id.indexOf('TextGroup') > -1) {
ids = id.split('TextGroup');
ids[0] = ids[1];
}
else if (id.indexOf('ShapeGroup') > -1) {
ids = id.split('ShapeGroup');
ids[0] = ids[1];
}
return new Index(parseInt(ids[0], 10), parseInt(ids[1], 10));
}
/**
* Converts a coefficient value to a vector representing a point on the circumference of a circle.
*
* @param {number} coefficient - The coefficient value to convert.
* @param {number} startAngle - The starting angle of the circle.
* @returns {ChartLocation} - The vector representing the point on the circle.
*/
export function CoefficientToVector(coefficient, startAngle) {
startAngle = startAngle < 0 ? startAngle + 360 : startAngle;
var angle = Math.PI * (1.5 - 2 * coefficient);
angle = angle + (startAngle * Math.PI) / 180;
return { x: Math.cos(angle), y: Math.sin(angle) };
}
/**
* Converts a value to a polar coefficient value based on the axis.
*
* @param {number} value - The value to convert.
* @param {Axis} axis - The axis object.
* @returns {number} - The polar coefficient value.
*/
export function valueToPolarCoefficient(value, axis) {
var range = axis.visibleRange;
var delta;
var length;
if (axis.valueType !== 'Category') {
delta = (range.max - (axis.valueType === 'DateTime' ? axis.dateTimeInterval : range.interval)) - range.min;
length = axis.visibleLabels.length - 1;
delta = delta === 0 ? 1 : delta;
}
else {
// To split an interval equally based on visible labels count
delta = axis.visibleLabels.length === 1 ? 1 :
(axis.visibleLabels[axis.visibleLabels.length - 1].value - axis.visibleLabels[0].value);
length = axis.visibleLabels.length;
}
return axis.isAxisInverse ? ((value - range.min) / delta) * (1 - 1 / (length)) :
1 - ((value - range.min) / delta) * (1 - 1 / (length));
}
/** @private */
var Mean = /** @class */ (function () {
function Mean(verticalStandardMean, verticalSquareRoot, horizontalStandardMean, horizontalSquareRoot, verticalMean, horizontalMean) {
this.verticalStandardMean = verticalStandardMean;
this.horizontalStandardMean = horizontalStandardMean;
this.verticalSquareRoot = verticalSquareRoot;
this.horizontalSquareRoot = horizontalSquareRoot;
this.verticalMean = verticalMean;
this.horizontalMean = horizontalMean;
}
return Mean;
}());
export { Mean };
/** @private */
var PolarArc = /** @class */ (function () {
function PolarArc(startAngle, endAngle, innerRadius, radius, currentXPosition) {
this.startAngle = startAngle;
this.endAngle = endAngle;
this.innerRadius = innerRadius;
this.radius = radius;
this.currentXPosition = currentXPosition;
}
return PolarArc;
}());
export { PolarArc };
/**
* Creates a tooltip element with the specified id, text, position, and font size.
*
* @param {string} id - The id of the tooltip element.
* @param {string} text - The text content of the tooltip.
* @param {number} top - The top position of the tooltip.
* @param {number} left - The left position of the tooltip.
* @param {string} fontSize - The font size of the tooltip text.
* @param {Chart} chartId - Chart element id.
* @returns {void}
*/
export function createTooltip(id, text, top, left, fontSize, chartId) {
var tooltip = getElement(id);
var style = 'top:' + top.toString() + 'px;' +
'left:' + left.toString() + 'px;' +
'color:black !important; ' +
'background:#FFFFFF !important; ' +
'position:absolute;border:1px solid #707070;font-size:' + fontSize + ';border-radius:2px; z-index:1';
if (!tooltip) {
tooltip = createElement('div', {
id: id, innerHTML: ' ' + text + ' ', styles: style
});
getElement(chartId).appendChild(tooltip);
}
else {
tooltip.setAttribute('innerHTML', ' ' + text + ' ');
tooltip.style.cssText = style;
}
}
/**
* Creates zooming labels for the specified axis and adds them to the parent element.
*
* @param {Chart} chart - The chart instance.
* @param {Axis} axis - The axis for which to create zooming labels.
* @param {Element} parent - The parent element to which the labels will be appended.
* @param {number} index - The index of the label.
* @param {boolean} isVertical - Indicates whether the axis is vertical.
* @param {Rect} rect - The bounding rectangle of the label.
* @returns {Element} - The created zooming label element.
*/
export function createZoomingLabels(chart, axis, parent, index, isVertical, rect) {
var margin = 5;
var opposedPosition = axis.isAxisOpposedPosition;
var anchor = chart.enableRtl ? 'end' : isVertical ? 'start' : 'auto';
var size;
var chartRect = chart.availableSize.width;
var pathElement;
var x;
var y;
var rx = 3;
var arrowLocation;
var direction;
var scrollBarHeight = axis.scrollbarSettings.enable || (axis.zoomingScrollBar && axis.zoomingScrollBar.svgObject)
? axis.scrollBarHeight : 0;
var isRtlEnabled = (chart.enableRtl && !isVertical && !axis.isInversed) ||
(axis.isInversed && !(chart.enableRtl && !isVertical));
for (var i = 0; i < 2; i++) {
size = measureText(i ? (isRtlEnabled ? axis.startLabel : axis.endLabel) : (isRtlEnabled ? axis.endLabel : axis.startLabel), axis.labelStyle, chart.themeStyle.axisLabelFont);
if (isVertical) {
arrowLocation = i ? new ChartLocation(rect.x - scrollBarHeight, rect.y + rx) :
new ChartLocation(axis.rect.x - scrollBarHeight, (rect.y + rect.height - rx));
x = (rect.x + (opposedPosition ? (rect.width + margin + scrollBarHeight) : -(size.width + margin + margin + scrollBarHeight)));
y = (rect.y + (i ? 0 : rect.height - size.height - margin));
x += (x < 0 || ((chartRect) < (x + size.width + margin))) ? (opposedPosition ? -(size.width / 2) : size.width / 2) : 0;
direction = findCrosshairDirection(rx, rx, new Rect(x, y, size.width + margin, size.height + margin), arrowLocation, margin, false, false, !opposedPosition, arrowLocation.x, arrowLocation.y + (i ? -rx : rx));
}
else {
arrowLocation = i ? new ChartLocation((rect.x + rect.width - rx), (rect.y + rect.height + scrollBarHeight)) :
new ChartLocation(rect.x + rx, (rect.y + rect.height + scrollBarHeight));
x = (rect.x + (i ? (rect.width - size.width - margin) : 0));
y = (opposedPosition ? (rect.y - size.height - 10 - scrollBarHeight) : (rect.y + rect.height + margin + scrollBarHeight));
direction = findCrosshairDirection(rx, rx, new Rect(x, y, size.width + margin, size.height + margin), arrowLocation, margin, opposedPosition, !opposedPosition, false, arrowLocation.x + (i ? rx : -rx), arrowLocation.y);
}
x = x + (margin / 2);
y = y + (3 * (size.height / 4)) + (margin / 2);
pathElement = chart.renderer.drawPath({
'id': chart.element.id + '_Zoom_' + index + '_AxisLabel_Shape_' + i,
'fill': chart.themeStyle.crosshairFill, 'width': 2, 'color': chart.themeStyle.crosshairFill,
'opacity': 1, 'stroke-dasharray': null, 'd': direction
}, null);
parent.appendChild(pathElement);
if (chart.theme === 'Fluent' || chart.theme === 'FluentDark') {
var shadowId = chart.element.id + '_shadow';
pathElement.setAttribute('filter', Browser.isIE ? '' : 'url(#' + shadowId + ')');
var shadow = '<filter id="' + shadowId + '" height="130%"><feGaussianBlur in="SourceAlpha" stdDeviation="3"/>';
shadow += '<feOffset dx="3" dy="3" result="offsetblur"/><feComponentTransfer><feFuncA type="linear" slope="0.5"/>';
shadow += '</feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge></filter>';
var defElement = chart.renderer.createDefs();
defElement.setAttribute('id', chart.element.id + 'SVG_tooltip_definition');
parent.appendChild(defElement);
defElement.innerHTML = shadow;
pathElement.setAttribute('stroke', '#cccccc');
pathElement.setAttribute('stroke-width', '0.5');
}
textElement(chart.renderer, new TextOption(chart.element.id + '_Zoom_' + index + '_AxisLabel_' + i, x, y, anchor, i ? (isRtlEnabled ? axis.startLabel : axis.endLabel) : (isRtlEnabled ? axis.endLabel : axis.startLabel)), { color: chart.themeStyle.crosshairLabelFont.color, fontFamily: 'Segoe UI', fontWeight: 'Regular', size: '11px' }, chart.themeStyle.crosshairLabelFont.color, parent, null, null, null, null, null, null, null, null, null, null, chart.themeStyle.crosshairLabelFont);
}
return parent;
}
/**
* Finds the direction of the crosshair based on the provided parameters.
*
* @param {number} rX - The x-coordinate of the crosshair line.
* @param {number} rY - The y-coordinate of the crosshair line.
* @param {Rect} rect - The bounding rectangle of the crosshair.
* @param {ChartLocation} arrowLocation - The location of the arrow in the crosshair.
* @param {number} arrowPadding - The padding for the arrow.
* @param {boolean} top - Indicates whether the crosshair is positioned at the top.
* @param {boolean} bottom - Indicates whether the crosshair is positioned at the bottom.
* @param {boolean} left - Indicates whether the crosshair is positioned at the left.
* @param {number} tipX - The x-coordinate of the crosshair tip.
* @param {number} tipY - The y-coordinate of the crosshair tip.
* @returns {string} - The direction of the crosshair ('Top', 'Bottom', 'Left', 'Right', 'Center').
*/
export function findCrosshairDirection(rX, rY, rect, arrowLocation, arrowPadding, top, bottom, left, tipX, tipY) {
var direction = '';
var startX = rect.x;
var startY = rect.y;
var width = rect.x + rect.width;
var height = rect.y + rect.height;
if (top) {
direction = direction.concat('M' + ' ' + (startX) + ' ' + (startY + rY) + ' Q ' + startX + ' '
+ startY + ' ' + (startX + rX) + ' ' + startY);
direction = direction.concat(' L' + ' ' + (width - rX) + ' ' + (startY) + ' Q ' + width + ' '
+ startY + ' ' + (width) + ' ' + (startY + rY));
direction = direction.concat(' L' + ' ' + (width) + ' ' + (height - rY) + ' Q ' + width + ' '
+ (height) + ' ' + (width - rX) + ' ' + (height));
if (arrowPadding !== 0) {
direction = direction.concat(' L' + ' ' + (arrowLocation.x + arrowPadding / 2) + ' ' + (height));
direction = direction.concat(' L' + ' ' + (tipX) + ' ' + (height + arrowPadding)
+ ' L' + ' ' + (arrowLocation.x - arrowPadding / 2) + ' ' + height);
}
if ((arrowLocation.x - arrowPadding / 2) > startX) {
direction = direction.concat(' L' + ' ' + (startX + rX) + ' ' + height + ' Q ' + startX + ' '
+ height + ' ' + (startX) + ' ' + (height - rY) + ' z');
}
else {
if (arrowPadding === 0) {
direction = direction.concat(' L' + ' ' + (startX + rX) + ' ' + height + ' Q ' + startX + ' '
+ height + ' ' + (startX) + ' ' + (height - rY) + ' z');
}
else {
direction = direction.concat(' L' + ' ' + (startX) + ' ' + (height + rY) + ' z');
}
}
}
else if (bottom) {
direction = direction.concat('M' + ' ' + (startX) + ' ' + (startY + rY) + ' Q ' + startX + ' '
+ (startY) + ' ' + (startX + rX) + ' ' + (startY) + ' L' + ' ' + (arrowLocation.x - arrowPadding / 2) + ' ' + (startY));
direction = direction.concat(' L' + ' ' + (tipX) + ' ' + (arrowLocation.y));
direction = direction.concat(' L' + ' ' + (arrowLocation.x + arrowPadding / 2) + ' ' + (startY));
direction = direction.concat(' L' + ' ' + (width - rX) + ' ' + (startY)
+ ' Q ' + (width) + ' ' + (startY) + ' ' + (width) + ' ' + (startY + rY));
direction = direction.concat(' L' + ' ' + (width) + ' ' + (height - rY) + ' Q ' + (width) + ' '
+ (height) + ' ' + (width - rX) + ' ' + (height));
direction = direction.concat(' L' + ' ' + (startX + rX) + ' ' + (height) + ' Q ' + (startX) + ' '
+ (height) + ' ' + (startX) + ' ' + (height - rY) + ' z');
}
else if (left) {
direction = direction.concat('M' + ' ' + (startX) + ' ' + (startY + rY) + ' Q ' + startX + ' '
+ (startY) + ' ' + (startX + rX) + ' ' + (startY));
direction = direction.concat(' L' + ' ' + (width - rX) + ' ' + (startY) + ' Q ' + (width) + ' '
+ (startY) + ' ' + (width) + ' ' + (startY + rY) + ' L' + ' ' + (width) + ' ' + (arrowLocation.y - arrowPadding / 2));
direction = direction.concat(' L' + ' ' + (width + arrowPadding) + ' ' + (tipY));
direction = direction.concat(' L' + ' ' + (width) + ' ' + (arrowLocation.y + arrowPadding / 2));
direction = direction.concat(' L' + ' ' + (width) + ' ' + (height - rY) + ' Q ' + width + ' ' + (height) + ' ' + (width - rX) + ' ' + (height));
direction = direction.concat(' L' + ' ' + (startX + rX) + ' ' + (height) + ' Q ' + startX + ' '
+ (height) + ' ' + (startX) + ' ' + (height - rY) + ' z');
}
else {
direction = direction.concat('M' + ' ' + (startX + rX) + ' ' + (startY) + ' Q ' + (startX) + ' '
+ (startY) + ' ' + (startX) + ' ' + (startY + rY) + ' L' + ' ' + (startX) + ' ' + (arrowLocation.y - arrowPadding / 2));
direction = direction.concat(' L' + ' ' + (startX - arrowPadding) + ' ' + (tipY));
direction = direction.concat(' L' + ' ' + (startX) + ' ' + (arrowLocation.y + arrowPadding / 2));
direction = direction.concat(' L' + ' ' + (startX) + ' ' + (height - rY) + ' Q ' + startX + ' '
+ (height) + ' ' + (startX + rX) + ' ' + (height));
direction = direction.concat(' L' + ' ' + (width - rX) + ' ' + (height) + ' Q ' + width + ' '
+ (height) + ' ' + (width) + ' ' + (height - rY));
direction = direction.concat(' L' + ' ' + (width) + ' ' + (startY + rY) + ' Q ' + width + ' '
+ (startY) + ' ' + (width - rX) + ' ' + (startY) + ' z');
}
return direction;
}
//Within bounds
/**
* Checks if the provided coordinates are within the bounds of the rectangle.
*
* @param {number} x - The x-coordinate to check.
* @param {number} y - The y-coordinate to check.
* @param {Rect} bounds - The bounding rectangle.
* @param {number} width - The width of the area to include in the bounds check.
* @param {number} height - The height of the area to include in the bounds check.
* @returns {boolean} - Returns true if the coordinates are within the bounds; otherwise, false.
*/
export function withInBounds(x, y, bounds, width, height) {
if (width === void 0) { width = 0; }
if (height === void 0) { height = 0; }
return (x >= bounds.x - width && x <= bounds.x + bounds.width + width && y >= bounds.y - height
&& y <= bounds.y + bounds.height + height);
}
/**
* Gets the x-coordinate value for a given point value on the axis.
*
* @param {number} value - The point value.
* @param {number} size - The size of the axis.
* @param {Axis} axis - The axis.
* @returns {number} - Returns the x-coordinate value.
*/
export function getValueXByPoint(value, size, axis) {
var actualValue = !axis.isAxisInverse ? value / size : (1 - (value / size));
return actualValue * (axis.visibleRange.delta) + axis.visibleRange.min;
}
/**
* Gets the y-coordinate value for a given point value on the axis.
*
* @param {number} value - The point value.
* @param {number} size - The size of the axis.
* @param {Axis} axis - The axis.
* @returns {number} - Returns the y-coordinate value.
*/
export function getValueYByPoint(value, size, axis) {
var actualValue = axis.isAxisInverse ? value / size : (1 - (value / size));
return actualValue * (axis.visibleRange.delta) + axis.visibleRange.min;
}
/**
* Finds the clip rectangle for a series.
*
* @param {Series} series - The series for which to find the clip rectangle.
* @param {boolean} isCanvas - Indicates whether the rendering is on a canvas.
* @returns {void}
*/
export function findClipRect(series, isCanvas) {
if (isCanvas === void 0) { isCanvas = false; }
var rect = series.clipRect;
if (isCanvas && (series.type === 'Polar' || series.type === 'Radar')) {
if (series.drawType === 'Scatter') {
rect.x = series.xAxis.rect.x;
rect.y = series.yAxis.rect.y;
rect.width = series.xAxis.rect.width;
rect.height = series.yAxis.rect.height;
}
else {
rect.x = series.xAxis.rect.x / 2;
rect.y = series.yAxis.rect.y / 2;
rect.width = series.xAxis.rect.width;
rect.height = series.yAxis.rect.height;
}
}
else {
if (series.chart.requireInvertedAxis) {
rect.x = series.yAxis.rect.x;
rect.y = series.xAxis.rect.y;
rect.width = series.yAxis.rect.width;
rect.height = series.xAxis.rect.height;
}
else {
rect.x = series.xAxis.rect.x;
rect.y = series.yAxis.rect.y;
rect.width = series.xAxis.rect.width;
rect.height = series.yAxis.rect.height;
}
}
}
/**
* Converts the first character of a string to lowercase.
*
* @param {string} str - The string to convert.
* @returns {string} The converted string.
*/
export function firstToLowerCase(str) {
return str.substr(0, 1).toLowerCase() + str.substr(1);
}
/**
* Gets the transformation of the chart area based on the provided axes and inverted axis state.
*
* @param {Axis} xAxis - The X-axis of the chart.
* @param {Axis} yAxis - The Y-axis of the chart.
* @param {boolean} invertedAxis - Indicates whether the chart axis is inverted.
* @returns {Rect} The transformed chart area.
*/
export function getTransform(xAxis, yAxis, invertedAxis) {
var x;
var y;
var width;
var height;
if (invertedAxis) {
x = yAxis.rect.x;
y = xAxis.rect.y;
width = yAxis.rect.width;
height = xAxis.rect.height;
}
else {
x = xAxis.rect.x;
y = yAxis.rect.y;
width = xAxis.rect.width;
height = yAxis.rect.height;
}
return new Rect(x, y, width, height);
}
/**
* Calculates the minimum points delta between data points on the provided axis.
*
* @param {Axis | Chart3DAxis} axis - The axis for which to calculate the minimum points delta.
* @param {Series[]} seriesCollection - The collection of series in the chart.
* @returns {number} The minimum points delta.
*/
export function getMinPointsDelta(axis, seriesCollection) {
var minDelta = Number.MAX_VALUE;
var xValues;
var minVal;
var seriesMin;
var allSeriesXvalueLen = true;
var stackingGroups = [];
for (var _i = 0, seriesCollection_1 = seriesCollection; _i < seriesCollection_1.length; _i++) {
var series = seriesCollection_1[_i];
if (series.visible) {
var xValues_1 = series.points.map(function (point) { return point.xValue; });
if (xValues_1.length !== 1) {
allSeriesXvalueLen = false;
break; // No need to continue if one series fails the condition
}
}
}
for (var index = 0; index < seriesCollection.length; index++) {
var series = seriesCollection[index];
xValues = [];
if (series.visible &&
(axis.name === series.xAxisName || (axis.name === 'primaryXAxis' && series.xAxisName === null)
|| (axis.name === series.chart.primaryXAxis.name && !series.xAxisName))) {
if (series.type.indexOf('Stacking') > -1 && stackingGroups.indexOf(series.stackingGroup) === -1) {
stackingGroups.push(series.stackingGroup);
}
xValues = series.points.map(function (point) {
return point.xValue;
});
xValues.sort(function (first, second) { return first - second; });
if (xValues.length === 1 && allSeriesXvalueLen) {
if (axis.valueType === 'Category') {
var minValue = series.xAxis.visibleRange.min;
var delta = xValues[0] - minValue;
if (delta !== 0) {
minDelta = Math.min(minDelta, delta);
}
}
else if (axis.valueType === 'DateTime') {
var timeOffset = seriesCollection.length === 1 ? 25920000 : 2592000000;
seriesMin = (series.xMin === series.xMax) ? (series.xMin - timeOffset) : series.xMin;
minVal = xValues[0] - (!isNullOrUndefined(seriesMin) ? seriesMin : axis.visibleRange.min);
if (minVal !== 0) {
minDelta = Math.min(minDelta, minVal);
}
}
else {
seriesMin = series.xMin;
minVal = xValues[0] - (!isNullOrUndefined(seriesMin) ?
seriesMin : axis.visibleRange.min);
if (minVal !== 0) {
minDelta = Math.min(minDelta, minVal);
}
}
}
else {
for (var index_1 = 0; index_1 < xValues.length; index_1++) {
var value = xValues[index_1];
if (index_1 > 0 && value) {
minVal = series.type.indexOf('Stacking') > -1 && axis.valueType === 'Category' ? stackingGroups.length : value - xValues[index_1 - 1];
if (minVal !== 0) {
minDelta = Math.min(minDelta, minVal);
}
}
}
}
}
}
if (minDelta === Number.MAX_VALUE) {
minDelta = 1;
}
return minDe