@syncfusion/ej2-treemap
Version:
Essential JS 2 TreeMap Components
759 lines (735 loc) • 42.7 kB
text/typescript
import { TreeMap } from '../treemap';
import {
Rect, itemsToOrder, TextOption, Size, measureText, textTrim, hide, wordWrap, textWrap,
getTemplateFunction, convertElement, findLabelLocation, PathOption, textFormatter, ColorValue, colorNameToHex, convertHexToColor,
colorMap, measureElement, convertToContainer, convertToRect, getShortestEdge, getArea, orderByArea, isParentItem, maintainSelection
} from '../utils/helper';
import { isNullOrUndefined, createElement, extend, SanitizeHtmlHelper } from '@syncfusion/ej2-base';
import { SvgRenderer } from '@syncfusion/ej2-svg-base';
import { Location, findChildren, renderTextElement } from '../utils/helper';
import { LevelSettings, LeafItemSettings } from '../model/base';
import { LabelPosition, LabelAlignment } from '../utils/enum';
import { BorderModel, FontModel, ColorMappingModel, LevelSettingsModel, LeafItemSettingsModel } from '../model/base-model';
import { IItemRenderingEventArgs } from '../model/interface';
import { itemRendering } from '../model/constants';
/**
* To calculate and render the shape layer
*/
export class LayoutPanel {
private treemap: TreeMap;
private currentRect: Rect;
public layoutGroup: Element;
private renderer: SvgRenderer;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public renderItems: any[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private parentData: any[];
constructor(treemap: TreeMap) {
this.treemap = treemap;
}
public processLayoutPanel(): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let data: any[] | any; let totalRect: Rect;
if (this.treemap.treemapLevelData.levelsData && this.treemap.treemapLevelData.levelsData.length > 0) {
data = (!isNullOrUndefined(this.treemap.initialDrillDown.groupIndex) &&
!isNullOrUndefined(this.treemap.initialDrillDown.groupName)) &&
(isNullOrUndefined(this.treemap.drilledItems) ? isNullOrUndefined(this.treemap.drilledItems)
: this.treemap.drilledItems.length === 0) ?
this.getDrilldownData(this.treemap.treemapLevelData.levelsData[0], [])[0] : this.treemap.treemapLevelData.levelsData[0];
totalRect = extend({}, this.treemap.areaRect, totalRect, false) as Rect;
if (!isNullOrUndefined(this.treemap.treeMapLegendModule) && !isNullOrUndefined(this.treemap.totalRect)) {
if (this.treemap.legendSettings.position !== 'Float') {
totalRect = this.treemap.totalRect;
}
}
if (!isNullOrUndefined(this.treemap.currentLevel) &&
(isNullOrUndefined(this.treemap.drilledItems) ? !isNullOrUndefined(this.treemap.drilledItems)
: this.treemap.drilledItems.length !== 0)) {
const count: number = this.treemap.drilledItems.length - 1;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const x: any = this.treemap.drilledItems[count as number]['data'];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const y: any = {};
y[this.treemap.drilledItems[count as number]['data']['groupName']] = [x];
if (!isNullOrUndefined(this.treemap.initialDrillDown.groupIndex) && !this.treemap.enableBreadcrumb) {
this.treemap.currentLevel = this.treemap.drilledItems[count as number]['data']['groupIndex'];
}
this.calculateLayoutItems(y || this.treemap.treemapLevelData.levelsData[0], totalRect);
this.renderLayoutItems();
} else {
if (!isNullOrUndefined(this.treemap.initialDrillDown.groupIndex) &&
(isNullOrUndefined(this.treemap.drilledItems) ? isNullOrUndefined(this.treemap.drilledItems)
: this.treemap.drilledItems.length === 0)) {
this.treemap.currentLevel = this.treemap.initialDrillDown.groupIndex;
}
this.calculateLayoutItems(data || this.treemap.treemapLevelData.levelsData[0], totalRect);
this.renderLayoutItems();
}
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private getDrilldownData(data: any, drillData: any[]): any {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const treemap: TreeMap = this.treemap; const newData: any = {};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const child: any[] = findChildren(data)['values'];
if (child && child.length > 0 && drillData.length === 0) {
for (let i: number = 0; i < child.length; i++) {
if (child[i as number]['groupIndex'] === treemap.initialDrillDown.groupIndex &&
child[i as number]['name'] === treemap.initialDrillDown.groupName) {
child[i as number]['isDrilled'] = true;
newData[child[i as number]['groupName']] = [child[i as number]];
drillData.push(newData);
}
}
for (let j: number = 0; j < child.length; j++) {
this.getDrilldownData(child[j as number], drillData);
}
}
return drillData;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public calculateLayoutItems(data: any, rect: Rect): void {
this.renderItems = [];
this.parentData = [];
if (!isNullOrUndefined(this.treemap.weightValuePath)) {
if (this.treemap.layoutType.indexOf('SliceAndDice') > -1) {
this.computeSliceAndDiceDimensional(data, rect);
} else {
rect.height = rect.height + rect.y;
rect.width = rect.width + rect.x;
this.computeSquarifyDimensional(data, rect);
}
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private computeSliceAndDiceDimensional(data: any, coords: Rect): any {
const leafItem: LeafItemSettings = this.treemap.leafItemSettings as LeafItemSettings;
let rect: Rect; const groups: LevelSettings[] = this.treemap.levels as LevelSettings[];
let groupIndex: number; let isLeafItem: boolean = false;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const children: any[] = findChildren(data)['values'];
let gap: number; let headerHeight: number;
if (children && children.length > 0) {
this.sliceAndDiceProcess(children, coords);
if (this.treemap.levels.length > 0) {
for (let i: number = 0; i < children.length; i++) {
groupIndex = children[i as number]['groupIndex'];
isLeafItem = (groups.length === 0 || groupIndex === groups.length);
gap = isLeafItem ? leafItem.gap : groups[groupIndex as number].groupGap;
headerHeight = groups.length === 0 ? 0 : groups[groupIndex as number] ? groups[groupIndex as number].showHeader ?
groups[groupIndex as number].headerHeight : 0 : groups[groupIndex - 1].showHeader ?
groups[groupIndex - 1].headerHeight : 0;
rect = children[i as number]['rect'];
rect = new Rect(
rect.x + (gap / 2), rect.y + (headerHeight + (gap / 2)), rect.width - gap,
Math.abs(rect.height - (gap + headerHeight)));
this.computeSliceAndDiceDimensional(children[i as number], rect);
}
}
}
return data;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private sliceAndDiceProcess(processData: any[], rect: Rect): void {
const parentArea: number = rect.height * rect.width;
const levels: LevelSettingsModel[] = this.treemap.levels;
let childValue: number; let alottedValue: number = 0; let totalWeight: number = 0;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
processData.forEach((data: any) => { totalWeight += data['weight']; });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
processData.forEach((child: any) => {
child['weightArea'] = parentArea * (child['weight'] as number) / totalWeight;
});
const isHorizontal: boolean = (this.treemap.layoutType === 'SliceAndDiceAuto') ? (rect.width > rect.height) :
(this.treemap.layoutType === 'SliceAndDiceHorizontal');
processData.sort(itemsToOrder);
for (let i: number = 0; i < processData.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const item: any = processData[i as number];
item['isLeafItem'] = (levels.length === 0) || ((this.treemap.isHierarchicalData ||
isNullOrUndefined(this.treemap.leafItemSettings.labelPath)) ?
item['groupIndex'] === levels.length - 1 : item['groupIndex'] === this.treemap.levels.length);
if (isHorizontal) {
childValue = ((parentArea / totalWeight) * processData[i as number]['weight']) / rect.height;
if (alottedValue <= rect.width) {
processData[i as number]['rect'] = new Rect(alottedValue + rect.x, rect.y, childValue, rect.height);
}
} else {
childValue = ((parentArea / totalWeight) * processData[i as number]['weight']) / rect.width;
if (alottedValue <= rect.height) {
processData[i as number]['rect'] = new Rect(rect.x, alottedValue + rect.y, rect.width, childValue);
}
}
alottedValue += childValue;
this.renderItems.push(processData[i as number]);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private computeSquarifyDimensional(data: any, coords: Rect): void {
const leaf: LeafItemSettings = this.treemap.leafItemSettings as LeafItemSettings;
let rect: Rect; const levels: LevelSettings[] = this.treemap.levels as LevelSettings[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let item: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const child: any[] = findChildren(data)['values']; let index: number;
let padding: number; let headerHeight: number;
if (child && child.length > 0) {
if (this.parentData.length === 0) {
this.parentData = [];
this.parentData.push(child);
}
this.calculateChildrenLayout(data, child, coords);
if (this.treemap.levels.length > 0) {
for (let i: number = 0; i < child.length; i++) {
item = child[i as number];
index = item['groupIndex'];
rect = item['rect'];
padding = (item['isLeafItem'] ? leaf.padding : levels[index as number].groupPadding) / 2;
headerHeight = this.treemap.isHierarchicalData ? index === 0 && item['isLeafItem'] ? 0 : levels[index as number] ?
levels[index as number].showHeader ? levels[index as number].headerHeight : 0 : 0 :
(levels.length === 0) ? 0 : levels[index as number] ?
levels[index as number].showHeader ? levels[index as number].headerHeight : 0 : 0;
rect = new Rect(
rect.x + padding, rect.y + (headerHeight + padding),
rect.width - padding, rect.height - padding
);
if (!item['isLeafItem'] && item['weight'] > 0) {
this.computeSquarifyDimensional(child[i as number], rect);
}
}
}
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private calculateChildrenLayout(parent: any, children: any[], coords: Rect): void {
this.computeTotalArea(children, getArea(coords));
children.sort(orderByArea);
this.performRowsLayout(children, [], coords, []);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private performRowsLayout(data: any[], currentRow: any[], rect: Rect, stack: any[]): any[] {
const dataLength: number = data.length;
if (dataLength === 0) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newCoordinates: any[] = this.getCoordinates(currentRow, rect);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newStack: any[] = stack.concat(newCoordinates);
return newStack;
}
const width: number = getShortestEdge(rect);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const nextDatum: any = data[0];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const restData: any[] = data.slice(1, dataLength);
if (this.aspectRatio(currentRow, nextDatum, width)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newRow: any[] = currentRow.concat(nextDatum);
return this.performRowsLayout(restData, newRow, rect, stack);
} else {
const currentRowLength: number = currentRow.length;
let valueSum: number = 0;
for (let i: number = 0; i < currentRowLength; i += 1) {
valueSum += currentRow[i as number]['itemArea'];
}
const newContainer: Rect = this.cutArea(rect, valueSum);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newCoordinates: any[] = this.getCoordinates(currentRow, rect);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newStack: any[] = stack.concat(newCoordinates);
return this.performRowsLayout(data, [], newContainer, newStack);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private aspectRatio(currentRow: any[], nextDatum: any, length: number): boolean {
if (currentRow.length === 0) {
return true;
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newRow: any[] = currentRow.concat(nextDatum);
const currentMaxAspectRatio: number = this.findMaxAspectRatio(currentRow, length);
const newMaxAspectRatio: number = this.findMaxAspectRatio(newRow, length);
return (currentMaxAspectRatio >= newMaxAspectRatio);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private findMaxAspectRatio(row: any[], length: number): number {
const rowLength: number = row.length;
let minArea: number = Infinity;
let maxArea: number = -Infinity;
let sumArea: number = 0;
for (let i: number = 0; i < rowLength; i += 1) {
const area: number = row[i as number]['itemArea'];
if (area < minArea) {
minArea = area;
}
if (area > maxArea) {
maxArea = area;
}
sumArea += area;
}
const result: number = Math.max((Math.pow(length, 2)) * maxArea / (Math.pow(sumArea, 2)), (Math.pow(sumArea, 2)) /
((Math.pow(length, 2)) * minArea));
return result;
}
private cutArea(rect: Rect, area: number): Rect {
const newContainer: Rect = convertToContainer(rect);
const width: number = newContainer.width;
const height: number = newContainer.height;
const xOffset: number = newContainer.x;
const yOffset: number = newContainer.y;
if (width >= height) {
const areaWidth: number = area / height;
const newWidth: number = width - areaWidth;
const container: Rect = {
x: xOffset + areaWidth,
y: yOffset,
width: newWidth,
height: height
};
return convertToRect(container);
} else {
const areaHeight: number = area / width;
const newHeight: number = height - areaHeight;
const container: Rect = {
x: xOffset,
y: yOffset + areaHeight,
width: width,
height: newHeight
};
return convertToRect(container);
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private getCoordinates(row: any[], rect: Rect): any[] {
const container: Rect = convertToContainer(rect);
const width: number = container.width; const height: number = container.height;
const xOffset: number = container.x; const yOffset: number = container.y;
const rowLength: number = row.length; const levels: LevelSettingsModel[] = this.treemap.levels;
const leaf: LeafItemSettingsModel = this.treemap.leafItemSettings; let index: number;
let valueSum: number = 0;
for (let i: number = 0; i < rowLength; i += 1) {
valueSum += row[i as number]['itemArea'];
}
const areaWidth: number = valueSum / height; const areaHeight: number = valueSum / width;
let subXOffset: number = xOffset; let subYOffset: number = yOffset; let padding: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const coordinates: any[] = []; let isParent: boolean; let parentRect: Rect;
for (let i: number = 0; i < rowLength; i += 1) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const item: any = row[i as number];
index = item['groupIndex'];
item['isLeafItem'] = (levels.length === 0) || (this.treemap.isHierarchicalData ? index === levels.length :
isNullOrUndefined(leaf.labelPath) ? false : index === levels.length);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
isParent = isParentItem(this.parentData[0] as any[], item);
parentRect = isParent ? this.treemap.areaRect : item['parent'].rect;
padding = item['isLeafItem'] ? leaf.padding : levels[index as number].groupPadding;
if (width >= height) {
const y1: number = subYOffset + item['itemArea'] / areaWidth;
item['rect'] = {
x: subXOffset,
y: subYOffset,
width: subXOffset + areaWidth,
height: y1
};
subYOffset = y1;
} else {
const x1: number = subXOffset + item['itemArea'] / areaHeight;
item['rect'] = {
x: subXOffset,
y: subYOffset,
width: x1,
height: subYOffset + areaHeight
};
subXOffset = x1;
}
if (item['weight'] > 0 && (isParent || (Math.round(rect.y + (padding / 2)) <=
Math.round(parentRect.y + (parentRect.height - parentRect.y)) && Math.round(rect.x + (padding / 2)) <=
Math.round(parentRect.x + (parentRect.width - parentRect.x))))) {
this.renderItems.push(item);
coordinates.push(item);
}
}
return coordinates;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private computeTotalArea(data: any[], area: number): any[] {
const dataLength: number = data.length;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result: any[] = [];
for (let i: number = 0; i < dataLength; i += 1) {
const dataLength: number = data.length;
let dataSum: number = 0;
for (let i: number = 0; i < dataLength; i += 1) {
dataSum += data[i as number]['weight'];
}
const multiplier: number = area / dataSum;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let datum: any;
for (let j: number = 0; j < dataLength; j++) {
datum = data[j as number];
datum['itemArea'] = datum['weight'] * multiplier;
result.push(datum);
}
}
return result;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public onDemandProcess(childItems: any): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let parentItem: any = {}; let totalRect: Rect;
parentItem = childItems[0]['parent'];
this.treemap.currentLevel = parentItem['isDrilled'] ? parentItem['groupIndex'] : null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let parentItemGroupname: any = {};
if (isNullOrUndefined(parentItem['groupName'])) {
parentItemGroupname = parentItem;
} else {
parentItemGroupname[parentItem['groupName']] = [parentItem];
}
totalRect = extend({}, this.treemap.areaRect, totalRect, false) as Rect;
if (!isNullOrUndefined(this.treemap.treeMapLegendModule) && !isNullOrUndefined(this.treemap.totalRect)) {
totalRect = this.treemap.totalRect;
}
const count: number = this.treemap.levels.length;
for (let i: number = 0; i < count; i++) {
const levelCount: number = childItems[0]['groupIndex'];
if (count === levelCount) {
this.treemap.levels[count as number] = this.treemap.levels[i as number];
} else {
this.treemap.levels.splice(count - 1, 1);
}
}
this.calculateLayoutItems(parentItemGroupname, totalRect);
this.renderLayoutItems();
}
// eslint-disable-next-line valid-jsdoc
/** @private */
public renderLayoutItems(): void {
let position: string;
const treeMap: TreeMap = this.treemap;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let txtVisible: boolean; let getItemColor: any; let eventArgs: IItemRenderingEventArgs;
this.renderer = treeMap.renderer;
let pathOptions: PathOption;
const elementID: string = treeMap.element.id; let index: number; let templatePosition: LabelPosition;
const mode: string = treeMap.layoutType; let rect: Rect; let format: string;
const interSectAction: LabelAlignment = this.treemap.leafItemSettings.interSectAction;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let fill: string; let item: any; let renderText: string;
let opacity: number; let rectPath: string = '';
const secondaryEle: HTMLElement = document.getElementById(treeMap.element.id + '_Secondary_Element');
let groupId: string; let templateEle: HTMLElement; let gap: number;
let textStyle: FontModel; const levels: LevelSettings[] = treeMap.levels as LevelSettings[];
this.layoutGroup = this.renderer.createGroup({ id: elementID + '_TreeMap_' + mode + '_Layout' });
let itemGroup: Element; let template: string | Function; let border: BorderModel;
const templateGroup: HTMLElement = createElement('div', {
id: treeMap.element.id + '_Label_Template_Group',
className: 'template'
});
templateGroup.style.cssText = 'overflow: hidden; position: absolute;pointer-events: none;' +
'top:' + treeMap.areaRect.y + 'px;' +
'left:' + treeMap.areaRect.x + 'px;' +
'height:' + treeMap.areaRect.height + 'px;' +
'width:' + treeMap.areaRect.width + 'px;';
let isLeafItem: boolean = false; const leaf: LeafItemSettings = treeMap.leafItemSettings as LeafItemSettings;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let childItems: any[]; let connectorText: string;
for (let i: number = 0; i < this.renderItems.length; i++) {
item = this.renderItems[i as number];
index = item['groupIndex'];
if (this.treemap.drillDownView && isNullOrUndefined(this.treemap.currentLevel)
&& index > 0 || this.treemap.drillDownView
&& index > (this.treemap.currentLevel + 1)) {
continue;
}
rect = item['rect'];
isLeafItem = item['isLeafItem'];
groupId = elementID + '_Level_Index_' + index + '_Item_Index_' + i;
itemGroup = this.renderer.createGroup({ id: groupId + '_Group' });
gap = (isLeafItem ? leaf.gap : levels[index as number].groupGap) / 2;
const treemapItemRect: Rect = this.treemap.totalRect ? (treeMap.legendSettings.visible ? this.treemap.totalRect
: convertToContainer(this.treemap.totalRect)) : this.treemap.areaRect;
if (treeMap.layoutType === 'Squarified') {
rect.width = Math.abs(rect.x - rect.width) - gap;
rect.height = Math.abs(rect.y - rect.height) - gap;
}
if (treeMap.renderDirection === 'TopRightBottomLeft') {
rect.x = (treemapItemRect.x + treemapItemRect.width) - rect.width - Math.abs(treemapItemRect.x - rect.x);
} else if (treeMap.renderDirection === 'BottomLeftTopRight') {
rect.y = (treemapItemRect.y + treemapItemRect.height) - rect.height - Math.abs(treemapItemRect.y - rect.y);
} else if (treeMap.renderDirection === 'BottomRightTopLeft') {
rect.x = (treemapItemRect.x + treemapItemRect.width) - rect.width - Math.abs(treemapItemRect.x - rect.x);
rect.y = (treemapItemRect.y + treemapItemRect.height) - rect.height - Math.abs(treemapItemRect.y - rect.y);
}
getItemColor = this.getItemColor(isLeafItem, item);
fill = getItemColor['fill'];
opacity = getItemColor['opacity'];
format = isLeafItem ? leaf.labelFormat : (levels[index as number]).headerFormat;
let levelName: string;
txtVisible = isLeafItem ? leaf.showLabels : (levels[index as number]).showHeader;
if (index === this.treemap.currentLevel) {
if (this.treemap.enableBreadcrumb) {
const re: RegExp = /#/gi;
connectorText = '#' + this.treemap.breadcrumbConnector + '#';
levelName = item['levelOrderName'].replace(re, connectorText);
levelName = index !== 0 ? '#' + levelName : levelName;
} else {
levelName = item['name'];
}
} else {
if (this.treemap.enableBreadcrumb) {
item['isDrilled'] = false;
}
levelName = item['name'];
}
renderText = textFormatter(format, item['data'], this.treemap) || levelName || 'undefined';
childItems = findChildren(item)['values'];
renderText = !isLeafItem && childItems && childItems.length > 0 && this.treemap.enableDrillDown ?
!item['isDrilled'] ? treeMap.enableRtl ? renderText + ' [+]' : '[+] ' + renderText :
treeMap.enableRtl ? renderText + ' [-]' : '[-] ' + renderText : renderText;
if (treeMap.enableHtmlSanitizer) {
renderText = SanitizeHtmlHelper.sanitize(renderText);
}
let fontFamily: string = (isLeafItem ? leaf.labelStyle.fontFamily : levels[index as number].headerStyle.fontFamily);
fontFamily = fontFamily || this.treemap.themeStyle.labelFontFamily;
let size: string = (isLeafItem ? leaf.labelStyle.size : levels[index as number].headerStyle.size);
size = size || this.treemap.themeStyle.labelFontSize;
let fontWeight: string = (isLeafItem ? leaf.labelStyle.fontWeight : levels[index as number].headerStyle.fontWeight);
fontWeight = fontWeight || this.treemap.themeStyle.fontWeight;
const color: string = (isLeafItem ? leaf.labelStyle.color : levels[index as number].headerStyle.color);
const fontStyle: string = (isLeafItem ? leaf.labelStyle.fontStyle : levels[index as number].headerStyle.fontStyle);
const textStyleOpacity: number = (isLeafItem ? leaf.labelStyle.opacity : levels[index as number].headerStyle.opacity);
textStyle = {
fontFamily: fontFamily, size: size, fontWeight: fontWeight, color: color, fontStyle: fontStyle, opacity: textStyleOpacity
};
border = isLeafItem ? leaf.border : levels[index as number].border;
position = !isLeafItem ? (levels[index as number].headerAlignment) === 'Near' ? 'TopLeft' : (levels[index as number].headerAlignment) === 'Center' ?
'TopCenter' : 'TopRight' : leaf.labelPosition;
templatePosition = isLeafItem ? leaf.templatePosition : levels[index as number].templatePosition;
template = isLeafItem ? leaf.labelTemplate : levels[index as number].headerTemplate;
item['options'] = { border: border, opacity: opacity, fill: fill };
eventArgs = {
cancel: false, name: itemRendering, treemap: this.treemap, text: renderText,
currentItem: item, RenderItems: this.renderItems, options: item['options'], textColor: textStyle.color
};
this.treemap.trigger(itemRendering, eventArgs, (observedArgs: IItemRenderingEventArgs) => {
if (!observedArgs.cancel) {
rectPath = ' M ' + rect.x + ' ' + rect.y + ' L ' + (rect.x + rect.width) + ' ' + rect.y +
' L ' + (rect.x + rect.width) + ' ' + (rect.y + rect.height) + ' L ' + rect.x + ' ' + (rect.y + rect.height) + 'z';
pathOptions = new PathOption(groupId + '_RectPath', fill, border.width, border.color, opacity, null, rectPath);
const path: Element = this.renderer.drawPath(pathOptions);
itemGroup.appendChild(path);
if (txtVisible) {
if (eventArgs.text !== renderText) {
eventArgs.text = textFormatter(eventArgs.text, item['data'], this.treemap) || levelName;
}
textStyle.color = eventArgs.textColor ? eventArgs.textColor : textStyle.color;
this.renderItemText(
eventArgs.text.toString(), itemGroup, textStyle, rect, interSectAction, groupId, fill,
position as LabelPosition, connectorText
);
}
if (template) {
templateEle = this.renderTemplate(secondaryEle, groupId, rect, templatePosition, template, item, isLeafItem);
if (!isNullOrUndefined(templateEle)) {
templateGroup.appendChild(templateEle);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.treemap as any).renderReactTemplates();
}
}
itemGroup.setAttribute('aria-label', item['name']);
if ((this.treemap.enableDrillDown && !isLeafItem) || (this.treemap.selectionSettings.enable ||
this.treemap.highlightSettings.enable)) {
itemGroup.setAttribute('role', 'button');
itemGroup.setAttribute('tabindex', this.treemap.tabIndex.toString());
(itemGroup as HTMLElement).style.outline = 'none';
(itemGroup as HTMLElement).style.cursor = this.treemap.highlightSettings.enable && !this.treemap.selectionSettings.enable && (this.treemap.enableDrillDown && item['groupIndex'] === (this.treemap.levels.length - 1)) ? 'default' :
this.treemap.highlightSettings.enable && !this.treemap.selectionSettings.enable && !this.treemap.enableDrillDown ? 'default' : 'pointer';
} else {
itemGroup.setAttribute('role', 'region');
}
maintainSelection(this.treemap, itemGroup, 'treeMapSelection');
this.layoutGroup.appendChild(itemGroup);
}
});
}
if (templateGroup.childNodes.length > 0) {
secondaryEle.appendChild(templateGroup);
}
this.treemap.svgObject.appendChild(this.layoutGroup);
maintainSelection(this.treemap, this.layoutGroup, 'treeMapSelection');
}
private renderItemText(
text: string, parentElement: Element, textStyle: FontModel, rect: Rect, interSectAction: LabelAlignment,
groupId: string, fill: string, position: LabelPosition, connectorText: string
): void {
const padding: number = 5; let textSize: Size;
let textCollection: string[] = []; let customText: string | string[];
const tspanText: string[] = []; let height: number = 0; let textName: string;
textCollection = ((text.indexOf('<br>')) !== -1) ? text.split('<br>') : null;
customText = this.labelInterSectAction(rect, text, textStyle, interSectAction);
textSize = measureText(textCollection && textCollection[0] || customText[0], textStyle);
if (this.treemap.enableRtl) {
const labelSize: Size = measureText(text, textStyle);
const drillSymbolCount: number = text.search('[+]') || text.search('[-]');
if (rect.width < labelSize.width && drillSymbolCount > 0) {
const label: string = text.substring(drillSymbolCount - 1, text.length);
const drillSymbol: string = '[+]';
const drillSymbolSize: Size = measureText(drillSymbol, textStyle);
customText['0'] = textTrim(rect.width - drillSymbolSize.width - padding, customText[0], textStyle) + label;
}
}
const textLocation: Location = findLabelLocation(rect, position, textSize, 'Text', this.treemap);
if (!isNullOrUndefined(textCollection)) {
const collection: string[] = [];
let texts: string = null;
const maxNumber: number[] = [];
for (let i: number = 0; i < textCollection.length; i++) {
texts = textTrim((rect.width - 5), textCollection[i as number], textStyle);
textSize = measureText(texts, textStyle);
height += textSize.height;
maxNumber.push(textSize.width);
collection.push(texts);
}
customText = collection;
textSize.width = Math.max.apply(null, maxNumber);
textSize.height = height;
}
if (interSectAction === 'WrapByWord' || interSectAction === 'Wrap' || interSectAction === 'Trim') {
for (let j: number = 0; j < customText.length; j++) {
textSize = measureText(customText[j as number], textStyle);
height += textSize.height;
if ((rect.height - padding) > height) {
tspanText.push(customText[j as number]);
}
}
if (interSectAction === 'Wrap' && customText.length !== tspanText.length && tspanText.length) {
const collectionLength: number = tspanText.length - 1;
let stringText: string = tspanText[collectionLength as number];
stringText = stringText.substring(0, (stringText.length - 1)) + '...';
tspanText.splice(collectionLength);
if (stringText !== '...') {
tspanText.push(stringText);
}
}
} else {
textName = customText as string;
tspanText.push(textName);
}
const textOptions: TextOption = new TextOption(
groupId + '_Text', textLocation.x, textLocation.y, 'start', tspanText, '', '', connectorText
);
renderTextElement(textOptions, textStyle, textStyle.color || this.getSaturatedColor(fill), parentElement);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private getItemColor(isLeafItem: boolean, item: any): any {
const treemap: TreeMap = this.treemap;
let itemFill: string = isLeafItem ? treemap.leafItemSettings.fill : treemap.levels[item['groupIndex']].fill;
let itemOpacity: number = isLeafItem ? treemap.leafItemSettings.opacity : treemap.levels[item['groupIndex']].opacity;
if (!isNullOrUndefined(treemap.treemapLevelData.defaultLevelsData)) {
if (treemap.treemapLevelData.defaultLevelsData.length > 0) {
treemap.treemapLevelData.levelsData = treemap.treemapLevelData.defaultLevelsData;
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const parentData: any[] = findChildren(treemap.treemapLevelData.levelsData[0])['values'];
const colorMapping: ColorMappingModel[] = isLeafItem ? treemap.leafItemSettings.colorMapping :
treemap.levels[item['groupIndex']].colorMapping;
if (colorMapping.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const option: any = colorMap(
colorMapping, item['data'][treemap.equalColorValuePath],
item['data'][treemap.rangeColorValuePath]);
if (!isNullOrUndefined(option)) {
itemFill = !isNullOrUndefined(option['fill']) ? option['fill'] : treemap.leafItemSettings.fill;
itemOpacity = option['opacity'];
}
} else {
for (let i: number = 0; i < parentData.length; i++) {
if ((parentData[i as number]['levelOrderName'] as string) === (item['levelOrderName'] as string).split('#')[0]) {
itemFill = !isNullOrUndefined(itemFill) ? itemFill : !isNullOrUndefined(treemap.colorValuePath) ?
parentData[i as number]['data'][treemap.colorValuePath] : !isNullOrUndefined(item['options']) ?
item['options'].fill : (!isNullOrUndefined(treemap.palette) && treemap.palette.length > 0) ?
treemap.palette[i % treemap.palette.length] : '#808080';
}
}
}
return { fill: itemFill, opacity: itemOpacity };
}
/**
* To find saturated color for datalabel
*
* @param {string} color - Specifies the color
* @returns {string} - Returns the color
*/
private getSaturatedColor(color: string): string {
let saturatedColor: string = color;
saturatedColor = (saturatedColor === 'transparent') ? window.getComputedStyle(document.body, null).backgroundColor : saturatedColor;
const rgbValue: ColorValue = convertHexToColor(colorNameToHex(saturatedColor));
const contrast: number = Math.round((rgbValue.r * 299 + rgbValue.g * 587 + rgbValue.b * 114) / 1000);
return contrast >= 128 ? 'black' : 'white';
}
private renderTemplate(
// eslint-disable-next-line @typescript-eslint/no-explicit-any, max-len
secondaryEle: HTMLElement, groupId: string, rect: Rect, position: LabelPosition, template: string | Function, item: any, isLeafItem: boolean
): HTMLElement {
const templateId: string = isLeafItem ? groupId + '_LabelTemplate' : groupId + '_HeaderTemplate';
const baseTemplateId: string = isLeafItem ? '_LabelTemplate' : '_HeaderTemplate';
if (isNullOrUndefined(template['prototype']) && typeof template === 'string') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const keys: any[] = Object.keys(item['data']);
for (let i: number = 0; i < keys.length; i++) {
const regExp: RegExpConstructor = RegExp;
template = template.replace(new regExp('{{:' + <string>keys[i as number] + '}}', 'g'), item['data'][keys[i as number].toString()]);
}
}
if (this.treemap.enableHtmlSanitizer && typeof template === 'string') {
template = SanitizeHtmlHelper.sanitize(template);
}
let labelElement: HTMLElement;
if (!isNullOrUndefined(document.getElementById(this.treemap.element.id + '_Secondary_Element'))) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const templateFn: any = getTemplateFunction(template);
const templateElement: HTMLCollection = templateFn(item['data'], this.treemap, template, this.treemap.element.id + baseTemplateId, false);
labelElement = convertElement(templateElement, templateId, item['data']);
const templateSize: Size = measureElement(labelElement, secondaryEle);
const templateLocation: Location = findLabelLocation(rect, position, templateSize, 'Template', this.treemap);
labelElement.style.left = templateLocation.x + 'px';
labelElement.style.top = templateLocation.y + 'px';
}
return labelElement;
}
private labelInterSectAction(rect: Rect, text: string, textStyle: FontModel, alignment: LabelAlignment): string | string[] {
let textValue: string | string[]; const maxWidth: number = rect.width - 10;
switch (alignment) {
case 'Hide':
textValue = [hide(maxWidth, rect.height, text, textStyle)];
break;
case 'Trim':
textValue = [textTrim((maxWidth + 3), text, textStyle)];
break;
case 'WrapByWord':
textValue = wordWrap(maxWidth, text, textStyle);
break;
case 'Wrap':
textValue = textWrap(maxWidth, text, textStyle);
break;
}
return textValue;
}
/**
*
* @returns {void}
* @private
*/
public destroy(): void {
this.treemap = null;
this.currentRect = null;
this.layoutGroup = null;
this.renderer = null;
this.renderItems = [];
this.parentData = [];
}
}