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.

419 lines (418 loc) 20.3 kB
/** * Sankey chart legend */ 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 { BaseLegend, LegendOptions } from '../../common/legend/legend'; import { measureText } from '@syncfusion/ej2-svg-base'; import { Browser, isNullOrUndefined } from '@syncfusion/ej2-base'; import { getElement, getUnicodeText, indexFinder, removeElement, showTooltip, textTrim, textWrap } from '../../common/utils/helper'; import { regSub, regSup } from '../../common/model/constants'; /** * The `Legend` module is used to render the legend for the sankey chart. */ var SankeyLegend = /** @class */ (function (_super) { __extends(SankeyLegend, _super); function SankeyLegend(chart) { var _this = _super.call(this, chart) || this; _this.library = _this; _this.wireEvents(); return _this; } /** * Binding events for legend module. * * @returns {void} * @private */ SankeyLegend.prototype.wireEvents = function () { if (this.chart.isDestroyed) { return; } this.chart.on(Browser.touchMoveEvent, this.handleMouseMove, this); this.chart.on('click', this.handleClick, this); this.chart.on(Browser.touchEndEvent, this.handleMouseEnd, this); }; /** * UnBinding events for legend module. * * @returns {void} * @private */ SankeyLegend.prototype.unWireEvents = function () { if (this.chart.isDestroyed) { return; } this.chart.off(Browser.touchMoveEvent, this.handleMouseMove); this.chart.off('click', this.handleClick); this.chart.off(Browser.touchEndEvent, this.handleMouseEnd); }; /** * Handles mouse handleLegendMove events on the legend when legend interaction is enabled and the chart is not in touch mode. * * @param {MouseEvent} event - The mouse handleLegendMove event triggered by the user. * @returns {void} */ SankeyLegend.prototype.handleMouseMove = function (event) { var chart = this.chart; if (!chart || !chart.legendSettings.visible || chart.isTouch) { return; } this.handleLegendMove(event); }; /** * Handles mouse end events on the legend when legend interaction is enabled and the chart is in touch mode. * * @param {MouseEvent} event - The mouse end event triggered by the user. * @returns {void} * @private */ SankeyLegend.prototype.handleMouseEnd = function (event) { var chart = this.chart; if (!chart || !chart.legendSettings.visible || !chart.isTouch) { return; } this.handleLegendMove(event); }; /** * Builds legend items from the Sankey node collection and applies RTL/reverse ordering rules. * * @param {Sankey} chart - The Sankey chart instance used to derive legend items. * @returns {void} * @private */ SankeyLegend.prototype.getLegendOptions = function (chart) { this.legendCollections = []; this.isRtlEnable = chart.enableRtl; if (this.legendCollections.length === 0 && chart.nodeLayoutMap) { if (typeof chart.nodeLayoutMap === 'object') { var nodeLayoutMap = chart.nodeLayoutMap; for (var _i = 0, _a = Object.keys(nodeLayoutMap); _i < _a.length; _i++) { var nodeKey = _a[_i]; var nodeLayout = nodeLayoutMap[nodeKey]; if (!nodeLayout) { continue; } var legendText = nodeLayout.label || nodeLayout.id; var fillColor = nodeLayout.color; var legendOption = new LegendOptions(legendText, fillColor, 'Rectangle', true, null, ''); this.legendCollections.push(legendOption); } } } this.isReverse = !this.isRtlEnable && chart.legendSettings.reverse; if (this.isReverse) { this.legendCollections.reverse(); } }; /** * Calculates legend layout metrics and updates bounds including pagination based on available space. * * @param {Size} availableSize - The available rendering size for the legend. * @param {Rect} legendBounds - The legend bounds to be updated based on computed layout. * @param {LegendSettingsModel} legend - The legend settings model used for layout calculations. * @returns {void} * @private */ SankeyLegend.prototype.getLegendBounds = function (availableSize, legendBounds, legend) { this.calculateLegendTitle(legend, legendBounds); this.isTitle = !!legend.title; this.chartRowCount = 1; this.rowHeights = []; this.columnHeights = []; this.pageHeights = []; var padding = legend.padding; var extraHeight = 0; var extraWidth = 0; var titleSpace = this.isTitle ? (this.legendTitleSize.height + this.fivePixel) : 0; titleSpace = (this.isTitle && this.isVertical) ? (this.legendTitleSize.height + this.fivePixel) : titleSpace; if (!this.isVertical) { extraHeight = !legend.height ? ((availableSize.height / 100) * 5) : 0; } else { extraWidth = !legend.width ? ((availableSize.width / 100) * 5) : 0; } legendBounds.height += extraHeight; legendBounds.width += extraWidth; var shapeWidth = legend.shapeWidth; var shapePadding = legend.shapePadding; var maximumRowWidth = 0; var currentRowWidth = 0; var currentItemWidth = 0; var currentColumnHeight = 0; var currentColumnIndex = 0; var rowCount = 0; var titleArrowSpace = 0; var legendRenderArgs; var hasRenderableItems = false; this.maxItemHeight = Math.max(measureText('MeasureText', legend.textStyle, this.chart.themeStyle.legendLabelFont).height, legend.shapeHeight); for (var itemIndex = 0; itemIndex < this.legendCollections.length; itemIndex++) { var legendOption = this.legendCollections[itemIndex]; if (regSub.test(legendOption.text)) { legendOption.text = getUnicodeText(legendOption.text, regSub); } if (regSup.test(legendOption.text)) { legendOption.text = getUnicodeText(legendOption.text, regSup); } legendOption.originalText = legendOption.text; legendRenderArgs = { fill: legendOption.fill, text: legendOption.text }; this.chart.trigger('legendItemRendering', legendRenderArgs); legendOption.render = true; legendOption.text = legendRenderArgs.text; legendOption.fill = legendRenderArgs.fill; legendOption.textSize = measureText(legendOption.text, legend.textStyle, this.chart.themeStyle.legendLabelFont); shapeWidth = legendOption.text ? legend.shapeWidth : 0; shapePadding = legendOption.text ? legend.shapePadding : 0; if (legendOption.render && legendOption.text) { hasRenderableItems = true; currentItemWidth = shapeWidth + shapePadding + legendOption.textSize.width + (!this.isVertical ? (itemIndex === 0 ? padding : this.itemPadding) : padding); currentRowWidth += currentItemWidth; this.getLegendHeight(legendOption, legend, legendBounds, currentRowWidth, this.maxItemHeight, padding); if (legendBounds.width < (padding + currentRowWidth + titleArrowSpace) || this.isVertical) { maximumRowWidth = Math.max(maximumRowWidth, (currentRowWidth + padding + titleArrowSpace - (this.isVertical ? 0 : currentItemWidth))); if (rowCount === 0 && (currentItemWidth !== currentRowWidth)) { rowCount = 1; } currentRowWidth = this.isVertical ? 0 : currentItemWidth; rowCount++; currentColumnIndex = 0; currentColumnHeight = 0; } var rowIndex = rowCount > 0 ? (rowCount - 1) : 0; this.rowHeights[rowIndex] = Math.max((this.rowHeights[rowIndex] || 0), Math.max(legendOption.textSize.height, legend.shapeHeight)); this.columnHeights[currentColumnIndex] = (this.columnHeights[currentColumnIndex] || 0) + Math.max(legendOption.textSize.height, legend.shapeHeight) + ((this.isVertical || (rowCount > 0 && this.legend.itemPadding)) ? (itemIndex === 0 ? padding : this.itemPadding) : padding); currentColumnIndex++; } } var tallestColumnHeight = this.columnHeights.length > 0 ? Math.max.apply(Math, this.columnHeights) : 0; currentColumnHeight = tallestColumnHeight + padding + titleSpace; currentColumnHeight = Math.max(currentColumnHeight, (this.maxItemHeight + padding) + padding + titleSpace); this.isPaging = legendBounds.height < currentColumnHeight; this.totalPages = rowCount; if (hasRenderableItems) { this.setBounds(Math.max((currentRowWidth + padding), maximumRowWidth), currentColumnHeight, legend, legendBounds); } else { this.setBounds(0, 0, legend, legendBounds); } }; /** * Updates legend item text collection and height when wrapping is enabled and the item exceeds the available row width. * * @param {LegendOptions} legendOption - The legend item to update with wrapped text and computed height. * @param {LegendSettingsModel} legend - The legend settings used to determine wrapping and padding behavior. * @param {Rect} legendBounds - The bounding rectangle that limits legend layout width. * @param {number} rowWidth - The current accumulated row width including previous legend items. * @param {number} legendHeight - The base height used to compute wrapped text block height. * @param {number} padding - The padding value used in legend layout calculations. * @returns {void} * @private */ SankeyLegend.prototype.getLegendHeight = function (legendOption, legend, legendBounds, rowWidth, legendHeight, padding) { var textWidth = legendOption.textSize.width; var effectiveTextPadding = legend.shapePadding + (padding * 2) + legend.shapeWidth; switch (legend.textStyle.textOverflow) { case 'Wrap': if (textWidth + rowWidth > legendBounds.width) { legendOption.textCollection = textWrap(legendOption.text, (legendBounds.width - effectiveTextPadding), legend.textStyle, this.chart.enableRtl, null, null, this.chart.themeStyle.legendLabelFont); } else { legendOption.textCollection.push(legendOption.text); } legendOption.textSize.height = (legendHeight * legendOption.textCollection.length); break; } }; /** * Calculates and assigns the legend item render location and applies trim overflow based on available width. * * @param {LegendOptions} legendOption - The legend item whose render point is being computed. * @param {ChartLocation} start - The starting location for the current row/column layout. * @param {number} textPadding - The padding to apply between shape and text for positioning calculations. * @param {LegendOptions} previousLegend - The previous legend item used as a reference for placement. * @param {Rect} rect - The layout bounds rectangle used to determine line breaks/column breaks. * @param {number} count - The current legend item index in the collection. * @param {number} firstLegend - The index of the first legend item in the current page/section. * @returns {void} * @private */ SankeyLegend.prototype.getRenderPoint = function (legendOption, start, textPadding, previousLegend, rect, count, firstLegend) { var padding = this.legend.padding; var previousItemTextWidth = textPadding + previousLegend.textSize.width; var previousBound = previousLegend.location.x + ((!this.isRtlEnable) ? previousItemTextWidth : -previousItemTextWidth); if (this.isWithinLegendBounds(previousBound, (legendOption.textSize.width) + textPadding - this.itemPadding, rect) || this.isVertical) { legendOption.location.x = start.x; if (count !== firstLegend) { this.chartRowCount++; } var rowIndex = (this.chartRowCount - 2) >= this.rowHeights.length ? this.rowHeights.length - 1 : (this.chartRowCount - 2); legendOption.location.y = (count === firstLegend) ? previousLegend.location.y : previousLegend.location.y + (this.isVertical ? Math.max(previousLegend.textSize.height, this.legend.shapeHeight) : this.rowHeights[rowIndex]) + ((this.isVertical || (this.chartRowCount > 1 && this.legend.itemPadding)) ? this.itemPadding : padding); } else { legendOption.location.x = (count === firstLegend) ? previousLegend.location.x : previousBound; legendOption.location.y = previousLegend.location.y; } var availableWidth = (!this.isRtlEnable) ? (this.legendBounds.x + this.legendBounds.width) - (legendOption.location.x + textPadding - this.itemPadding - this.legend.shapeWidth / 2) : (legendOption.location.x - textPadding + this.itemPadding + (this.legend.shapeWidth / 2)) - this.legendBounds.x; if (this.legend.textStyle.textOverflow === 'Trim') { legendOption.text = textTrim(+availableWidth.toFixed(4), legendOption.text, this.legend.textStyle, this.chart.enableRtl, this.chart.themeStyle.legendLabelFont); } }; /** * Checks whether the next legend item will exceed the provided bounds based on RTL direction. * * @param {number} previousBound - The computed boundary position from the previous legend item. * @param {number} textWidth - The width to be tested against the bounds. * @param {Rect} rect - The bounding rectangle used for overflow detection. * @returns {boolean} true if within bouunds, else false. * * @private */ SankeyLegend.prototype.isWithinLegendBounds = function (previousBound, textWidth, rect) { if (!this.isRtlEnable) { return (previousBound + textWidth) > (rect.x + rect.width + (this.legend.shapeWidth / 2)); } else { return (previousBound - textWidth) < (rect.x - (this.legend.shapeWidth / 2)); } }; /** * Processes legend hover/trim-tooltip behavior based on the current pointer target within the legend. * * @param {Event} event - The interaction event used to resolve the current legend target element. * @returns {void} * * @private */ SankeyLegend.prototype.handleLegendMove = function (event) { var chart = this.chart; var x = chart.mouseX; var y = chart.mouseY; var targetElement = event.target; // Trigger legendItemHover event if (targetElement && targetElement.id.indexOf('_legend_') > -1) { var legendIndex = indexFinder(targetElement.id).point; if (isNullOrUndefined(legendIndex) && targetElement.id.indexOf('_g_') > -1) { var targetIdParts = targetElement.id.split(this.legendID + '_g_'); if (targetIdParts.length === 2) { legendIndex = parseInt(targetIdParts[1], 10); } } if (!isNaN(legendIndex) && this.legendCollections[legendIndex]) { var legendItem = this.legendCollections[legendIndex]; var nodeKey = null; var nodeKeys = Object.keys(chart.nodeLayoutMap); for (var keyIndex = 0; keyIndex < nodeKeys.length; keyIndex++) { var key = nodeKeys[keyIndex]; if ((chart.nodeLayoutMap[key].label || chart.nodeLayoutMap[key].id) === legendItem.originalText) { nodeKey = key; break; } } if (nodeKey && chart.nodeLayoutMap[nodeKey]) { var nodeLayout = chart.nodeLayoutMap[nodeKey]; if (nodeLayout) { var hoveredUserNode = null; var userNodes = chart.nodes || []; for (var nodeIndex = 0; nodeIndex < userNodes.length; nodeIndex++) { var nodeDefinition = userNodes[nodeIndex]; if (nodeDefinition && nodeDefinition.id === nodeLayout.id) { hoveredUserNode = nodeDefinition; break; } } var hoverArgs = { node: hoveredUserNode }; chart.trigger('legendItemHover', hoverArgs); } } } } if (targetElement && targetElement.textContent && targetElement.textContent.indexOf('...') > -1) { var targetIdParts = targetElement.id.split(this.legendID + '_text_'); if (targetIdParts.length === 2) { var legendTextIndex = parseInt(targetIdParts[1], 10); if (!isNaN(legendTextIndex)) { removeElement(this.chart.element.id + '_EJ2_Legend_Tooltip'); showTooltip(event.target.textContent, x, y, chart.element.offsetWidth, this.chart.element.id + '_EJ2_Legend_Tooltip', getElement(this.chart.element.id + '_Secondary_Element')); } } } else { removeElement(this.chart.element.id + '_EJ2_Legend_Tooltip'); } }; /** * Handles legend click interactions for paging controls when legend is visible. * * @param {Event | PointerEvent} event - The click or pointer event raised from the legend element. * @returns {void} * * @private */ SankeyLegend.prototype.handleClick = function (event) { var chart = this.chart; if (!chart || !chart.legendSettings.visible) { return; } var targetElement = event.target; var firstChildElement = targetElement.firstElementChild; var targetId = targetElement.id.indexOf('_chart_legend_g_') > -1 ? (firstChildElement ? firstChildElement.id : targetElement.id) : targetElement.id; if (targetId.indexOf(this.legendID + '_pageup') > -1) { this.changePage(event, true); } else if (targetId.indexOf(this.legendID + '_pagedown') > -1) { this.changePage(event, false); } }; /** * Gets the module name for the Sankey legend component. * * @returns {string} - the module name */ SankeyLegend.prototype.getModuleName = function () { return 'SankeyLegend'; }; /** * To destroy the Legend. * * @returns {void} * @private */ SankeyLegend.prototype.destroy = function () { this.unWireEvents(); }; return SankeyLegend; }(BaseLegend)); export { SankeyLegend };