@syncfusion/ej2-circulargauge
Version:
Essential JS 2 CircularGauge Components
912 lines (902 loc) • 45 kB
text/typescript
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;
}
}