UNPKG

@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
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: '&nbsp;' + text + '&nbsp;', styles: style }); getElement(chartId).appendChild(tooltip); } else { tooltip.setAttribute('innerHTML', '&nbsp;' + text + '&nbsp;'); 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