UNPKG

@syncfusion/ej2-circulargauge

Version:
912 lines (902 loc) 45 kB
import { CircularGauge } from '../circular-gauge'; import { getElement, stringToNumber, measureText, textElement, appendPath, calculateShapes, PathOption, RectOption, Size, GaugeLocation, Rect, TextOption } from '../utils/helper-common'; import { textTrim } from '../utils/helper-legend'; import { isNullOrUndefined } from '@syncfusion/ej2-base'; import { Border } from '../model/base'; import { LegendPosition, Alignment, GaugeShape } from '../utils/enum'; import { Axis } from '../axes/axis'; import { ILegendRenderEventArgs } from '../model/interface'; import { BorderModel, FontModel, LegendSettingsModel } from '../model/base-model'; import { LegendSettings } from '../model/base'; import { ILegendRegions } from '../model/interface'; import { RangeModel } from '../axes/axis-model'; import { Range } from '../axes/axis'; /* * Sets and gets the module to add the legend in the circular gauge. */ export class Legend { public legendCollection: LegendOptions[]; // eslint-disable-next-line @typescript-eslint/no-explicit-any public legendRenderingCollections: any[]; protected legendRegions: ILegendRegions[] = []; public titleRect: Rect; private totalRowCount: number; private maxColumnWidth: number; protected maxItemHeight: number; protected isPaging: boolean; protected isVertical: boolean; private rowCount: number = 0; // legend row counts per page private pageButtonSize: number = 8; protected pageXCollections: number[] = []; // pages of x locations protected maxColumns: number = 0; public maxWidth: number = 0; private clipRect: Element; private legendTranslateGroup: Element; protected currentPage: number = 1; private gauge: CircularGauge; private totalPages: number; private legend: LegendSettings; private legendID: string; protected pagingRegions: Rect[] = []; private clipPathHeight: number; private toggledIndexes: Index[]; /** * Sets and gets the legend bounds in circular gauge. * * @private */ public legendBounds: Rect; /** * @private */ public position: LegendPosition = 'Auto'; constructor(gauge: CircularGauge) { this.gauge = gauge; this.toggledIndexes = []; this.legend = this.gauge.legendSettings as LegendSettings; this.legendID = this.gauge.element.id + '_gauge_legend'; this.addEventListener(); } /** * Binding events for legend module. * * @returns {void} */ private addEventListener(): void { if (this.gauge.isDestroyed) { return; } // this.gauge.on(Browser.touchMoveEvent, this.mouseMove, this); this.gauge.on('click', this.click, this); // this.gauge.on(Browser.touchEndEvent, this.mouseEnd, this); } /** * UnBinding events for legend module. * * @returns {void} */ private removeEventListener(): void { if (this.gauge.isDestroyed) { return; } // this.gauge.off(Browser.touchMoveEvent, this.mouseMove); this.gauge.off('click', this.click); // this.gauge.off(Browser.touchEndEvent, this.mouseEnd); } /** * Get the legend options. * * @param {Axis[]} axes - Specifies the axes. * @returns {void} * @private */ public getLegendOptions(axes: Axis[]): void { this.legendCollection = []; let range: RangeModel; let text: string = ''; for (let i: number = 0; i < axes.length; i++) { for (let j: number = 0; j < axes[i as number].ranges.length; j++) { range = axes[i as number].ranges[j as number] as RangeModel; if (!isNullOrUndefined(range.start) && !isNullOrUndefined(range.end) && (range.start !== range.end)) { text = range.legendText ? range.legendText : range.start + ' - ' + range.end; this.legendCollection.push(new LegendOptions( text, text, range.color, this.legend.shape, this.legend.visible, this.legend.border as Border, this.legend.shapeBorder as Border, this.legend.shapeWidth, this.legend.shapeHeight, j, i )); } } } } public calculateLegendBounds(rect: Rect, availableSize: Size): void { const legend: LegendSettingsModel = this.legend; this.position = (legend.position !== 'Auto') ? legend.position : (availableSize.width > availableSize.height ? 'Right' : 'Bottom'); this.legendBounds = (this.position === 'Custom') ? new Rect(legend.location.x, legend.location.y, 0, 0) : new Rect(rect.x, rect.y, 0, 0); this.isVertical = (this.position === 'Left' || this.position === 'Right'); if (this.isVertical) { this.legendBounds.height = stringToNumber( legend.height, availableSize.height - (rect.y - this.gauge.margin.top)) || rect.height; this.legendBounds.width = stringToNumber(legend.width || '20%', availableSize.width); } else { this.legendBounds.width = stringToNumber(legend.width, availableSize.width) || rect.width; this.legendBounds.height = stringToNumber(legend.height || '20%', availableSize.height); } this.getLegendBounds(availableSize, this.legendBounds, legend); this.getLocation(this.position, legend.alignment, this.legendBounds, rect, availableSize); } /** * To find legend alignment for chart and accumulation chart * * @param {number} start - Specifies the start. * @param {number} size - Specifies the size. * @param {number} legendSize - Specifies the legendSize. * @param {Alignment} alignment - Specifies the alignment. * @returns {number} - Returns the start value. */ private alignLegend(start: number, size: number, legendSize: number, alignment: Alignment): number { switch (alignment) { case 'Far': start = (size - legendSize) - start; break; case 'Center': start = ((size - legendSize) / 2); break; } return start; } /** * To find legend location based on position, alignment for chart and accumulation chart * * @param {LegendPosition} position - Specifies the position. * @param {Alignment} alignment - Specifies the alignment. * @param {Rect} legendBounds - Specifies the legendBounds. * @param {Rect} rect - Specifies the rect. * @param {Size} availableSize - Specifies the availableSize. * @returns {void} */ private getLocation(position: LegendPosition, alignment: Alignment, legendBounds: Rect, rect: Rect, availableSize: Size): void { const padding: number = this.legend.border.width; const legendHeight: number = legendBounds.height + padding + this.legend.margin.top + this.legend.margin.bottom; const legendWidth: number = legendBounds.width + padding + this.legend.margin.left + this.legend.margin.right; const marginBottom: number = this.gauge.margin.bottom; if (position === 'Bottom') { legendBounds.x = this.alignLegend(legendBounds.x, availableSize.width, legendBounds.width, alignment); legendBounds.y = rect.y + (rect.height - legendHeight) + padding + this.legend.margin.top; this.subtractThickness(rect, 0, 0, 0, legendHeight); } else if (position === 'Top') { legendBounds.x = this.alignLegend(legendBounds.x, availableSize.width, legendBounds.width, alignment); legendBounds.y = rect.y + padding + this.legend.margin.top; this.subtractThickness(rect, 0, 0, legendHeight, 0); } else if (position === 'Right') { legendBounds.x = rect.x + (rect.width - legendBounds.width) + this.legend.margin.right; legendBounds.y = rect.y + this.alignLegend( 0, availableSize.height - (rect.y + marginBottom), legendBounds.height, alignment ); this.subtractThickness(rect, 0, legendWidth, 0, 0); } else if (position === 'Custom') { this.subtractThickness(rect, 0, 0, 0, 0); } else { legendBounds.x = legendBounds.x + this.legend.margin.left; legendBounds.y = rect.y + this.alignLegend( 0, availableSize.height - (rect.y + marginBottom), legendBounds.height, alignment ); this.subtractThickness(rect, legendWidth, 0, 0, 0); } } /** * Renders the legend. * * @param {LegendSettingsModel} legend - Specifies the legend. * @param {Rect} legendBounds - Specifies the legendBounds. * @returns {void} * @private */ public renderLegend(legend: LegendSettingsModel, legendBounds: Rect ): void { const firstLegend: number = this.findFirstLegendPosition(this.legendCollection); const padding: number = legend.padding; this.legendRegions = []; this.maxItemHeight = Math.max(this.legendCollection[0].textSize.height, legend.shapeHeight); const legendGroup: Element = this.gauge.renderer.createGroup({ id: this.legendID + '_g' }); const legendTranslateGroup: Element = this.createLegendElements(legendBounds, legendGroup, legend, this.legendID); if (firstLegend !== this.legendCollection.length) { this.totalPages = 0; let legendAxisGroup: Element; // legendItem group for each series group element // starting shape center x,y position && to resolve lint error used new line for declaration const start: GaugeLocation = new GaugeLocation( // eslint-disable-next-line max-len (!this.gauge.enableRtl) ? legendBounds.x + padding + (legend.shapeWidth / 2) : (!this.isVertical) ? legendBounds.width + legendBounds.x - (padding) - (legend.shapeWidth) : legendBounds.x + this.maxWidth - padding - legend.shapeWidth / 2, legendBounds.y + padding + this.maxItemHeight / 2 ); const textOptions: TextOption = new TextOption('', start.x, start.y, 'start'); const textPadding: number = (2 * legend.shapePadding) + (2 * padding) + legend.shapeWidth; let count: number = 0; this.pageXCollections = []; this.legendCollection[firstLegend as number].location = start; let previousLegend: LegendOptions = this.legendCollection[firstLegend as number]; for (const legendOption of this.legendCollection) { if (legendOption.render && legendOption.text !== '') { legendAxisGroup = this.gauge.renderer.createGroup({ id: this.legendID + '_g_' + count }); this.getRenderPoint(legendOption, start, textPadding, previousLegend, legendBounds, count, firstLegend); this.renderSymbol(legendOption, legendAxisGroup, legendOption.axisIndex, legendOption.rangeIndex); this.renderText(legendOption, legendAxisGroup, textOptions, legendOption.axisIndex, legendOption.rangeIndex); if (legendAxisGroup) { (legendAxisGroup as HTMLElement).style.cursor = (!legend.toggleVisibility) ? 'auto' : 'pointer'; } if (legendTranslateGroup) { legendTranslateGroup.appendChild(legendAxisGroup); } previousLegend = legendOption; } count++; } if (this.isPaging) { this.renderPagingElements(legendBounds, textOptions, legendGroup); } else { this.totalPages = 1; } } this.appendChildElement(this.gauge.svgObject, legendGroup); this.setStyles(this.toggledIndexes); } /** * To render legend paging elements for chart and accumulation chart * * @param {Rect} bounds - Specifies the bounds. * @param {TextOption} textOption - Specifies the textOption. * @param {Element} legendGroup - Specifies the legendGroup. * @returns {void} */ private renderPagingElements(bounds: Rect, textOption: TextOption, legendGroup: Element): void { const paginggroup: Element = this.gauge.renderer.createGroup({ id: this.legendID + '_navigation' }); this.pagingRegions = []; legendGroup.appendChild(paginggroup); const grayColor: string = this.gauge.themeStyle.labelColor; const legend: LegendSettingsModel = this.gauge.legendSettings; // to solve parameter lint error, legend declaration is here const padding: number = 8; // const padding for paging elements if (!this.isVertical) { this.totalPages = Math.ceil(this.totalPages / Math.max(1, this.rowCount - 1)); } else { this.totalPages = Math.ceil(this.totalPages / this.maxColumns); } const symbolOption: PathOption = new PathOption(this.legendID + '_pageup', 'transparent', 5, grayColor, 1, '', ''); const iconSize: number = this.pageButtonSize; if (paginggroup) { (paginggroup as HTMLElement).style.cursor = 'pointer'; } const textStyle: FontModel = { size: legend.textStyle.size || this.gauge.themeStyle.fontSize, color: legend.textStyle.color || this.gauge.themeStyle.labelColor, fontFamily: legend.textStyle.fontFamily || this.gauge.themeStyle.labelFontFamily, fontWeight: legend.textStyle.fontWeight || this.gauge.themeStyle.fontWeight, fontStyle: legend.textStyle.fontStyle, opacity: legend.textStyle.opacity }; // Page left arrow drawing calculation started here this.clipPathHeight = (this.rowCount - 1) * (this.maxItemHeight + legend.padding); this.clipRect.setAttribute('height', this.clipPathHeight.toString()); let x: number = bounds.x + iconSize / 2; const y: number = bounds.y + this.clipPathHeight + ((bounds.height - this.clipPathHeight) / 2); const size: Size = measureText(this.totalPages + '/' + this.totalPages, textStyle); appendPath( calculateShapes( { x: x, y: y }, 'LeftArrow', new Size(iconSize, iconSize), '', symbolOption ), paginggroup, this.gauge, 'Path' ); this.pagingRegions.push(new Rect(!this.gauge.enableRtl ? // eslint-disable-next-line max-len x + bounds.width - (2 * (iconSize + padding) + padding + size.width) - iconSize * 0.5 : x, y - iconSize * 0.5, iconSize, iconSize) ); // Page numbering rendering calculation started here textOption.x = x + (iconSize / 2) + padding; textOption.y = y + (size.height / 4); textOption.id = this.legendID + '_pagenumber'; textOption.text = !this.gauge.enableRtl ? '1/' + this.totalPages : this.totalPages + '/1'; const pageTextElement: Element = textElement(textOption, textStyle, grayColor, paginggroup); x = (textOption.x + padding + (iconSize / 2) + size.width); symbolOption.id = this.legendID + '_pagedown'; appendPath( calculateShapes( { x: x, y: y }, 'RightArrow', new Size(iconSize, iconSize), '', symbolOption ), paginggroup, this.gauge, 'Path' ); this.pagingRegions.push(new Rect(!this.gauge.enableRtl ? // eslint-disable-next-line max-len x + (bounds.width - (2 * (iconSize + padding) + padding + size.width) - iconSize * 0.5) : x , y - iconSize * 0.5, iconSize, iconSize )); //placing the navigation buttons and page numbering in legend right corner // eslint-disable-next-line max-len const translateX: number = (this.gauge.enableRtl) ? legend.border.width + (iconSize / 2) : bounds.width - (2 * (iconSize + padding) + padding + size.width) ; paginggroup.setAttribute('transform', 'translate(' + translateX + ', ' + 0 + ')'); this.translatePage(pageTextElement, this.currentPage - 1, this.currentPage); } /** * To translate legend pages for chart and accumulation chart * * @param {Element} pagingText - Specifies the pagingText. * @param {number} page - Specifies the page. * @param {number} pageNumber - Specifies the pageNumber. * @returns {number} - Returns the size. */ protected translatePage(pagingText: Element, page: number, pageNumber: number): number { let size: number = (this.clipPathHeight) * page; let translate: string = 'translate(0,-' + size + ')'; if (this.isVertical) { const pageX : number = this.pageXCollections[page * this.maxColumns]; size = (!this.gauge.enableRtl) ? pageX - this.legendBounds.x : (this.legendBounds.x + this.maxWidth) - pageX; size = size < 0 ? 0 : size; // to avoid small pixel variation translate = ((!this.gauge.enableRtl) ? 'translate(-' : 'translate(') + size + ',0)'; } this.legendTranslateGroup.setAttribute('transform', translate); pagingText.textContent = !this.gauge.enableRtl ? (pageNumber) + '/' + this.totalPages : this.totalPages + '/' + pageNumber; this.currentPage = pageNumber; return size; } /** * To render legend text for chart and accumulation chart * * @param {LegendOptions} legendOption - Specifies the legendOption. * @param {Element} group - Specifies the group. * @param {TextOption} textOptions - Specifies the textOptions. * @param {number} axisIndex - Specifies the axisIndex. * @param {number} rangeIndex - Specifies the rangeIndex. * @returns {void} */ protected renderText( legendOption: LegendOptions, group: Element, textOptions: TextOption, axisIndex: number, rangeIndex: number ): void { const legend: LegendSettingsModel = this.gauge.legendSettings; const hiddenColor: string = '#D3D3D3'; textOptions.id = this.legendID + '_Axis_' + axisIndex + '_text_' + rangeIndex; const fontcolor: string = legendOption.visible ? legend.textStyle.color || this.gauge.themeStyle.labelColor : hiddenColor; const textStyle: FontModel = { size: legend.textStyle.size || this.gauge.themeStyle.fontSize, color: fontcolor, fontFamily: legend.textStyle.fontFamily || this.gauge.themeStyle.labelFontFamily, fontWeight: legend.textStyle.fontWeight || this.gauge.themeStyle.fontWeight, fontStyle: legend.textStyle.fontStyle, opacity: legend.textStyle.opacity }; textOptions.text = legendOption.text; textOptions.x = this.gauge.enableRtl ? (legendOption.location.x - (measureText(legendOption.text, textStyle).width + legend.shapeWidth / 2 + legend.shapePadding)) : (legendOption.location.x + (legend.shapeWidth / 2) + legend.shapePadding); textOptions.y = legendOption.location.y + this.maxItemHeight / 4; const legendTextElement: Element = textElement(textOptions, textStyle, fontcolor, group, ''); legendTextElement.setAttribute('aria-label', textOptions.text); legendTextElement.setAttribute('role', 'region'); } /** * To render legend symbols for chart and accumulation chart * * @param {LegendOptions} legendOption - Specifies the legendOption. * @param {Element} group - Specifies the group. * @param {number} axisIndex - Specifies the axisIndex. * @param {number} rangeIndex - Specifies the rangeIndex. * @returns {void} */ protected renderSymbol(legendOption: LegendOptions, group: Element, axisIndex: number, rangeIndex: number): void { legendOption.fill = legendOption.fill ? legendOption.fill : (this.gauge.axes[axisIndex as number].ranges[rangeIndex as number] as Range).rangeColor; appendPath( calculateShapes( legendOption.location, legendOption.shape, new Size(legendOption.shapeWidth, legendOption.shapeHeight), '', new PathOption( this.legendID + '_Axis_' + axisIndex + '_Shape_' + rangeIndex, legendOption.fill, legendOption.shapeBorder.width, legendOption.shapeBorder.color, null, legendOption.shapeBorder.dashArray, '', '' ) ), group, this.gauge, legendOption.shape === 'Circle' ? 'Ellipse' : 'Path'); } /** * To find legend rendering locations from legend options. * * @param {LegendOptions} legendOption - Specifies the legendOption. * @param {GaugeLocation} start - Specifies the start. * @param {number} textPadding - Specifies the textPadding. * @param {LegendOptions} prevLegend - Specifies the prevLegend. * @param {Rect} rect - Specifies the rect. * @param {number} count - Specifies the count. * @param {number} firstLegend - Specifies the firstLegend. * @returns {void} * @private */ public getRenderPoint( legendOption: LegendOptions, start: GaugeLocation, textPadding: number, prevLegend: LegendOptions, rect: Rect, count: number, firstLegend: number ): void { const padding: number = this.legend.padding; const textStyle: FontModel = { size: this.legend.textStyle.size || this.gauge.themeStyle.fontSize, color: this.legend.textStyle.color || this.gauge.themeStyle.labelColor, fontFamily: this.legend.textStyle.fontFamily || this.gauge.themeStyle.labelFontFamily, fontWeight: this.legend.textStyle.fontWeight || this.gauge.themeStyle.fontWeight, fontStyle: this.legend.textStyle.fontStyle, opacity: this.legend.textStyle.opacity }; if (this.isVertical) { if (count === firstLegend || (prevLegend.location.y + (this.maxItemHeight * 1.5) + (padding * 2) > rect.y + rect.height)) { legendOption.location.x = prevLegend.location.x + ((count === firstLegend) ? 0 : (!this.gauge.enableRtl) ? this.maxColumnWidth : -this.maxColumnWidth - (4 * this.legend.shapePadding) / 3); legendOption.location.y = start.y; const textStartLoc: number = (this.legend.shapeWidth / 2) + padding; this.pageXCollections.push(legendOption.location.x + ((!this.gauge.enableRtl) ? -textStartLoc : textStartLoc)); this.totalPages++; } else { legendOption.location.x = prevLegend.location.x; legendOption.location.y = prevLegend.location.y + this.maxItemHeight + padding; } } else { // eslint-disable-next-line max-len const previousBound: number = (prevLegend.location.x + ((!this.gauge.enableRtl) ? prevLegend.textSize.width + textPadding : -prevLegend.textSize.width - textPadding)); // eslint-disable-next-line max-len if (this.isWithinBounds(previousBound, (legendOption.textSize.width + textPadding) - padding , rect , this.legend.shapeWidth / 2)) { legendOption.location.y = (count === firstLegend) ? prevLegend.location.y : prevLegend.location.y + this.maxItemHeight + padding; legendOption.location.x = start.x; } else { legendOption.location.y = prevLegend.location.y; legendOption.location.x = (count === firstLegend) ? prevLegend.location.x : previousBound; } this.totalPages = this.totalRowCount; } const availablewidth: number = this.getAvailWidth(legendOption.location.x, this.legendBounds.width); legendOption.text = textTrim(+availablewidth.toFixed(4), legendOption.text, textStyle); } private isWithinBounds(previousBound: number, textWidth: number, legendBounds: Rect, shapeWidth: number): boolean { if (!this.gauge.enableRtl) { return (previousBound + textWidth) > (legendBounds.x + legendBounds.width + shapeWidth); } else { return (previousBound - textWidth) < (legendBounds.x - shapeWidth); } } /** * To show or hide the legend on clicking the legend. * * @param {Event} event - Specifies the event argument. * @returns {void} * * @private */ public click(event: Event): void { const targetId: string = (<HTMLElement>event.target).id; const legendItemsId: string[] = ['_text_', '_Shape_']; let index: Index; let toggledIndex: number = -1; if (targetId.indexOf(this.legendID) > -1) { for (const id of legendItemsId) { if (targetId.indexOf(id) > -1) { const axisIndex: number = parseInt(targetId.split(this.legendID + '_Axis_')[1].split(id)[0], 10); const rangeIndex: number = parseInt(targetId.split(this.legendID + '_Axis_')[1].split(id)[1], 10); if (this.gauge.legendSettings.toggleVisibility && !isNaN(rangeIndex)) { const legendOption: LegendOptions = this.legendByIndex(axisIndex, rangeIndex, this.legendCollection); index = new Index(axisIndex, rangeIndex, !legendOption.render); if (this.toggledIndexes.length === 0) { this.toggledIndexes.push(index); } else { for (let i: number = 0; i < this.toggledIndexes.length; i++) { if (this.toggledIndexes[i as number].axisIndex === index.axisIndex && this.toggledIndexes[i as number].rangeIndex === index.rangeIndex) { toggledIndex = i; break; } else { toggledIndex = -1; } } if (toggledIndex === -1) { this.toggledIndexes.push(index); } else { this.toggledIndexes[toggledIndex as number].isToggled = !this.toggledIndexes[toggledIndex as number].isToggled; } } this.setStyles(this.toggledIndexes); } } } } if (targetId.indexOf(this.legendID + '_pageup') > -1) { this.changePage(event, !this.gauge.enableRtl ? true : false); } else if (targetId.indexOf(this.legendID + '_pagedown') > -1) { this.changePage(event, !this.gauge.enableRtl ? false : true); } } /** * Set toggled legend styles. * * @param {Index[]} toggledIndexes - Specifies the toggledIndexes. * @returns {void} */ private setStyles(toggledIndexes: Index[]): void { for (let i: number = 0; i < toggledIndexes.length; i++) { let count: number = 0; for (let j: number = 0; j < toggledIndexes[i as number].rangeIndex; j++) { const rangeStart: number = this.gauge.axes[toggledIndexes[i as number].axisIndex].ranges[j as number].start; const rangeEnd: number = this.gauge.axes[toggledIndexes[i as number].axisIndex].ranges[j as number].end; if (rangeStart === rangeEnd) { count++; } } const rangeID: string = this.gauge.element.id + '_Axis_' + toggledIndexes[i as number].axisIndex + '_Range_' + toggledIndexes[i as number].rangeIndex; const shapeID: string = this.legendID + '_Axis_' + toggledIndexes[i as number].axisIndex + '_Shape_' + toggledIndexes[i as number].rangeIndex; const textID: string = this.legendID + '_Axis_' + toggledIndexes[i as number].axisIndex + '_text_' + toggledIndexes[i as number].rangeIndex; const rangeElement: HTMLElement = this.gauge.svgObject.querySelector('#' + rangeID); const shapeElement: HTMLElement = this.gauge.svgObject.querySelector('#' + shapeID); const textElement: HTMLElement = this.gauge.svgObject.querySelector('#' + textID); if (toggledIndexes[i as number].isToggled) { if (!isNullOrUndefined(rangeElement)) { rangeElement.style.visibility = 'visible'; shapeElement.setAttribute('fill', this.legendCollection[toggledIndexes[i as number].rangeIndex - count].fill); textElement.setAttribute('fill', this.legend.textStyle.color || this.gauge.themeStyle.labelColor); } } else { const hiddenColor: string = '#D3D3D3'; if (!isNullOrUndefined(rangeElement)) { rangeElement.style.visibility = 'hidden'; shapeElement.setAttribute('fill', hiddenColor); textElement.setAttribute('fill', hiddenColor); } } } } /** * To get legend by index * * @param {number} axisIndex - Specifies the axisIndex. * @param {number} rangeIndex - Specifies the rangeIndex. * @param {LegendOptions[]} legendCollections - Specifies the legendCollections. * @returns {LegendOptions} - Specifies the LegendOptions. */ private legendByIndex(axisIndex: number, rangeIndex: number, legendCollections: LegendOptions[]): LegendOptions { for (const legend of legendCollections) { if (legend.axisIndex === axisIndex && legend.rangeIndex === rangeIndex) { return legend; } } return null; } /** * To change legend pages for chart and accumulation chart * * @param {Event} event - Specifies the event. * @param {boolean} pageUp - Specifies the pageUp. * @returns {void} */ protected changePage(event: Event, pageUp: boolean): void { const pageText: Element = document.getElementById(this.legendID + '_pagenumber'); const page: number = parseInt(pageText.textContent.split('/')[!this.gauge.enableRtl ? 0 : 1], 10); if (pageUp && page > 1) { this.translatePage(pageText, (page - 2), (page - 1)); } else if (!pageUp && page < this.totalPages) { this.translatePage(pageText, page, (page + 1)); } } /** * To find available width from legend x position. * * @param {number} tx - Specifies the tx value. * @param {number} width - Specifies the width. * @returns {number} - Returns the number. */ private getAvailWidth(tx: number, width: number): number { if (this.isVertical) { width = this.maxWidth; } return width - ((this.legend.padding * 2) + this.legend.shapeWidth + this.legend.shapePadding); } /** * To create legend rendering elements for chart and accumulation chart * * @param {Rect} legendBounds - Specifies the legendBounds. * @param {Element} legendGroup - Specifies the legendGroup. * @param {LegendSettingsModel} legend - Specifies the legend. * @param {string} id - Specifies the id. * @returns {Element} - Returns the element. */ private createLegendElements( legendBounds: Rect, legendGroup: Element, legend: LegendSettingsModel, id: string ): Element { const padding: number = legend.padding; const borderStyle: BorderModel = { color: legend.border.color || this.gauge.themeStyle.legendBorderColor || '', width: legend.border.width || this.gauge.themeStyle.legendBorderWidth || 1, dashArray: legend.border.dashArray }; const options: RectOption = new RectOption(id + '_element', legend.background, borderStyle, legend.opacity, legendBounds); options.width = this.isVertical ? this.maxWidth : legendBounds.width; // eslint-disable-next-line @typescript-eslint/no-unused-expressions legendGroup ? legendGroup.appendChild(this.gauge.renderer.drawRectangle(options)) : this.gauge.renderer.drawRectangle(options); const legendItemsGroup: Element = this.gauge.renderer.createGroup({ id: id + '_collections' }); legendGroup.appendChild(legendItemsGroup); this.legendTranslateGroup = this.gauge.renderer.createGroup({ id: id + '_translate_g' }); legendItemsGroup.appendChild(this.legendTranslateGroup); const clippath: Element = this.gauge.renderer.createClipPath({ id: id + '_clipPath' }); options.id += '_clipPath_rect'; options.width = this.isVertical ? options.width - padding : options.width; this.clipRect = this.gauge.renderer.drawRectangle(options); clippath.appendChild(this.clipRect); this.appendChildElement(this.gauge.svgObject, clippath); (legendItemsGroup as HTMLElement).style.cssText = 'clip-path:url(#' + clippath.id + ')'; return this.legendTranslateGroup; } /** * Method to append child element * * @param {Element} parent - Specifies the element. * @param {Element} childElement - Specifies the child element. * @returns {void} */ private appendChildElement( parent: Element | HTMLElement, childElement: Element | HTMLElement ): void { const existChild: HTMLElement = parent.querySelector('#' + childElement.id); const element: HTMLElement = <HTMLElement>(existChild || getElement(childElement.id)); const child: HTMLElement = <HTMLElement>childElement; if (existChild) { parent.replaceChild(child, element); } else { parent.appendChild(child); } } /** * To find first valid legend text index for chart and accumulation chart * * @param {LegendOptions[]} legendCollection - Specifies the legend collection. * @returns {number} - Returns the count. */ private findFirstLegendPosition(legendCollection: LegendOptions[]): number { let count: number = 0; for (const legend of legendCollection) { if (legend.render && legend.text !== '') { break; } count++; } return count; } /** * To find legend bounds for accumulation chart. * * @param {Size} availableSize - Specifies the availableSize. * @param {Rect} legendBounds - Specifies the legendBounds. * @param {LegendSettingsModel} legend - Specifies the legend. * @returns {void} * @private */ public getLegendBounds(availableSize: Size, legendBounds: Rect, legend: LegendSettingsModel): void { let extraWidth: number = 0; let extraHeight: number = 0; const padding: number = legend.padding; if (!this.isVertical) { extraHeight = !legend.height ? ((availableSize.height / 100) * 5) : 0; } else { extraWidth = !legend.width ? ((availableSize.width / 100) * 5) : 0; } legendBounds.width += extraWidth; legendBounds.height += extraHeight; const textStyle: FontModel = { size: legend.textStyle.size || this.gauge.themeStyle.fontSize, color: this.legend.textStyle.color || this.gauge.themeStyle.labelColor, fontFamily: legend.textStyle.fontFamily || this.gauge.themeStyle.labelFontFamily, fontWeight: legend.textStyle.fontWeight || this.gauge.themeStyle.fontWeight, fontStyle: legend.textStyle.fontStyle, opacity: legend.textStyle.opacity }; let maximumWidth: number = 0; let rowWidth: number = 0; let rowCount: number = 0; const columnWidth: number[] = []; let columnHeight: number = 0; let legendWidth: number = 0; this.maxItemHeight = Math.max(measureText('MeasureText', textStyle).height, legend.shapeHeight); let legendEventArgs: ILegendRenderEventArgs; let render: boolean = false; for (const legendOption of this.legendCollection) { legendEventArgs = { fill: legendOption.fill, text: legendOption.text, shape: legendOption.shape, name: 'legendRender', cancel: false }; this.gauge.trigger('legendRender', legendEventArgs); legendOption.render = !legendEventArgs.cancel; legendOption.text = legendEventArgs.text; legendOption.fill = legendEventArgs.fill; legendOption.shape = legendEventArgs.shape; legendOption.textSize = measureText(legendOption.text, textStyle); if (legendOption.render && legendOption.text !== '') { render = true; legendWidth = legend.shapeWidth + (2 * legend.shapePadding) + legendOption.textSize.width + (2 * padding); if (this.isVertical) { ++rowCount; columnHeight = (rowCount * (this.maxItemHeight + padding)) + padding; if ((rowCount * (this.maxItemHeight + padding)) + padding > legendBounds.height) { columnHeight = Math.max(columnHeight, (rowCount * (this.maxItemHeight + padding)) + padding); rowWidth = rowWidth + maximumWidth; columnWidth.push(maximumWidth); this.totalPages = Math.max(rowCount, this.totalPages || 1); maximumWidth = 0; rowCount = 1; } maximumWidth = Math.max(legendWidth, maximumWidth); } else { rowWidth = rowWidth + legendWidth; if (legendBounds.width < (padding + rowWidth)) { maximumWidth = Math.max(maximumWidth, (rowWidth + padding - legendWidth)); if (rowCount === 0 && (legendWidth !== rowWidth)) { rowCount = 1; } rowWidth = legendWidth; rowCount++; columnHeight = (rowCount * (this.maxItemHeight + padding)) + padding; } } } } if (this.isVertical) { rowWidth = rowWidth + maximumWidth; this.isPaging = legendBounds.width < (rowWidth + padding); columnHeight = Math.max(columnHeight, ((this.totalPages || 1) * (this.maxItemHeight + padding)) + padding); this.isPaging = this.isPaging && (this.totalPages > 1); if (columnWidth[columnWidth.length - 1] !== maximumWidth) { columnWidth.push(maximumWidth); } } else { this.isPaging = legendBounds.height < columnHeight; this.totalPages = this.totalRowCount = rowCount; columnHeight = Math.max(columnHeight, (this.maxItemHeight + padding) + padding); } this.maxColumns = 0; // initialization for max columns const width: number = this.isVertical ? this.getMaxColumn(columnWidth, legendBounds.width, padding, rowWidth + padding) : Math.max(rowWidth + padding, maximumWidth); if (render) { // if any legends not skipped in event check this.setBounds(width, columnHeight, legend, legendBounds); } else { this.setBounds(0, 0, legend, legendBounds); } } /** * @param {Rect} rect - Specifies the rect. * @param {number} left - Specifies the left. * @param {number} right - Specifies the right. * @param {number} top - Specifies the top. * @param {number} bottom - Specifies the bottom. * @returns {Rect} - Returns the rect. * @private */ private subtractThickness(rect: Rect, left: number, right: number, top: number, bottom: number): Rect { rect.x += left; rect.y += top; rect.width -= left + right; rect.height -= top + bottom; return rect; } /** * To set bounds for chart and accumulation chart * * @param {number} computedWidth - Specifies compute width. * @param {number} computedHeight - Specifies compute height. * @param {LegendSettingsModel} legend - Specifies the legend. * @param {Rect} legendBounds - Specifies the legend bounds. * @returns {void} */ protected setBounds(computedWidth: number, computedHeight: number, legend: LegendSettingsModel, legendBounds: Rect): void { computedWidth = computedWidth < legendBounds.width ? computedWidth : legendBounds.width; computedHeight = computedHeight < legendBounds.height ? computedHeight : legendBounds.height; legendBounds.width = !legend.width ? computedWidth : legendBounds.width; legendBounds.height = !legend.height ? computedHeight : legendBounds.height; this.rowCount = Math.max(1, Math.ceil((legendBounds.height - legend.padding) / (this.maxItemHeight + legend.padding))); if (this.rowCount === 1 && (legend.position === 'Bottom' || legend.position === 'Top') && (!isNullOrUndefined(legend.width) && legend.width.indexOf('%') > -1)) { legendBounds.width = computedWidth; } } /** * To find maximum column size for legend * * @param {number[]} columns - Specifies the columns * @param {number} width - Specifies the width * @param {number} padding - Specifies the padding * @param {number} rowWidth - Specifies the row width * @returns {number} - Returns the number */ private getMaxColumn(columns: number[], width: number, padding: number, rowWidth: number): number { let maxPageColumn: number = padding; this.maxColumnWidth = Math.max.apply(null, columns); for (let i: number = 0; i < columns.length; i++) { maxPageColumn += this.maxColumnWidth; this.maxColumns++; if (maxPageColumn + padding > width) { maxPageColumn -= this.maxColumnWidth; this.maxColumns--; break; } } this.isPaging = (maxPageColumn < rowWidth) && (this.totalPages > 1); if (maxPageColumn === padding) { maxPageColumn = width; } this.maxColumns = Math.max(1, this.maxColumns); this.maxWidth = maxPageColumn; return maxPageColumn; } /** * Get module name. * * @returns {string} - Returns the module name. */ protected getModuleName(): string { return 'Legend'; } /** * To destroy the legend. * * @returns {void} * @private */ public destroy(): void { this.legendCollection = []; this.legendRenderingCollections = []; this.legendRegions = []; this.titleRect = null; this.pageXCollections = []; this.clipRect = null; this.legendTranslateGroup = null; this.legend = null; this.pagingRegions = []; this.toggledIndexes = []; this.legendBounds = null; this.removeEventListener(); this.gauge = null; } } /** * @private */ export class Index { public axisIndex: number; public rangeIndex: number; public isToggled: boolean; constructor(axisIndex: number, rangeIndex?: number, isToggled?: boolean) { this.axisIndex = axisIndex; this.rangeIndex = rangeIndex; this.isToggled = isToggled; } } /** * Class for legend options * * @private */ export class LegendOptions { public render: boolean; public text: string; public originalText: string; public fill: string; public shape: GaugeShape; public visible: boolean; public textSize: Size; public location: GaugeLocation = { x: 0, y: 0 }; public border: Border; public shapeBorder: Border; public shapeWidth: number; public shapeHeight: number; public rangeIndex?: number; public axisIndex?: number; constructor( text: string, originalText: string, fill: string, shape: GaugeShape, visible: boolean, border: Border, shapeBorder: Border, shapeWidth: number, shapeHeight: number, rangeIndex?: number, axisIndex?: number ) { this.text = text; this.originalText = originalText; this.fill = fill; this.shape = shape; this.visible = visible; this.border = border; this.shapeBorder = shapeBorder; this.shapeWidth = shapeWidth; this.shapeHeight = shapeHeight; this.rangeIndex = rangeIndex; this.axisIndex = axisIndex; } }