@syncfusion/ej2-treemap
Version:
Essential JS 2 TreeMap Components
1,212 lines (1,180 loc) • 61 kB
text/typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { BorderModel, FontModel, ColorMappingModel, LeafItemSettingsModel } from '../model/base-model';
import { createElement, compile, merge, isNullOrUndefined, remove, SanitizeHtmlHelper } from '@syncfusion/ej2-base';
import { SvgRenderer } from '@syncfusion/ej2-svg-base';
import { Alignment, LabelPosition } from '../utils/enum';
import { TreeMap } from '../treemap';
import { IShapes } from '../model/interface';
import { ExportType } from '../utils/enum';
/**
* Specifies the size parameters.
*/
export class Size {
/**
* Defines the height in the size object.
*/
public height: number;
/**
* Defines the width in the size object.
*/
public width: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
}
/**
*
* @param {string} value - specifies the text.
* @param {number} containerSize - specifies the container size value.
* @returns {number} - Returns the number value which is converted from string.
*/
export function stringToNumber(value: string, containerSize: number): number {
if (value !== null && value !== undefined) {
return value.indexOf('%') !== -1 ? (containerSize / 100) * parseInt(value, 10) : parseInt(value, 10);
}
return null;
}
/**
* Internal use of type rect
*
* @private
*/
export class Rect {
public x: number;
public y: number;
public height: number;
public width: number;
constructor(x: number, y: number, width: number, height: number) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
}
/**
* Internal use of rectangle options
*
* @private
*/
export class RectOption {
public id: string;
public fill: string;
public x: number;
public y: number;
public height: number;
public width: number;
public opacity: number;
public stroke: string;
public ['stroke-width']: number;
public ['stroke-dasharray']: string;
constructor(
id: string, fill: string, border: BorderModel, opacity: number, rect: Rect, dashArray?: string
) {
this.y = rect.y;
this.x = rect.x;
this.height = rect.height;
this.width = rect.width;
this.id = id;
this.fill = fill;
this.opacity = opacity;
this.stroke = border.color;
this['stroke-width'] = border.width;
this['stroke-dasharray'] = dashArray;
}
}
export class PathOption {
public id: string;
public opacity: number;
public fill: string;
public stroke: string;
public ['stroke-width']: number;
public ['stroke-dasharray']: string;
public d: string;
constructor(
id: string, fill: string, width: number, color: string, opacity?: number,
dashArray?: string, d?: string
) {
this.id = id;
this.opacity = opacity;
this.fill = fill;
this.stroke = color;
this['stroke-width'] = width;
this['stroke-dasharray'] = dashArray;
this.d = d;
}
}
/**
* Function to measure the height and width of the text.
*
* @param {string} text - Specifies the text.
* @param {FontModel} font - Specifies the font.
* @returns {Size} - Returns the size.
* @private
*/
export function measureText(text: string, font: FontModel): Size {
let measureObject: HTMLElement = document.getElementById('treeMapMeasureText');
if (measureObject === null) {
measureObject = createElement('text', { id: 'treeMapMeasureText' });
document.body.appendChild(measureObject);
}
measureObject.innerHTML = SanitizeHtmlHelper.sanitize(text);
measureObject.style.position = 'absolute';
measureObject.style.fontSize = font.size;
measureObject.style.fontWeight = font.fontWeight;
measureObject.style.fontStyle = font.fontStyle;
measureObject.style.fontFamily = font.fontFamily;
measureObject.style.visibility = 'hidden';
measureObject.style.top = '-100';
measureObject.style.left = '0';
measureObject.style.whiteSpace = 'nowrap';
// For bootstrap line height issue
measureObject.style.lineHeight = 'normal';
return new Size(measureObject.clientWidth, measureObject.clientHeight);
}
/**
* Internal use of text options
*
* @private
*/
export class TextOption {
public anchor: string;
public id: string;
public transform: string = '';
public x: number;
public y: number;
public text: string | string[];
public baseLine: string = 'auto';
public connectorText: string;
constructor(
id?: string, x?: number, y?: number, anchor?: string, text?: string | string[], transform: string = '',
baseLine?: string, connectorText?: string
) {
this.id = id;
this.text = text;
this.transform = transform;
this.anchor = anchor;
this.x = x;
this.y = y;
this.baseLine = baseLine;
this.connectorText = connectorText;
}
}
/**
* Trim the title text
*
* @param {number} maxWidth - Specifies the maximum width
* @param {string} text - Specifies the text
* @param {FontModel} font - Specifies the font
* @returns {string} - Returns the string
* @private
*/
export function textTrim(maxWidth: number, text: string, font: FontModel): string {
let label: string = text;
let size: number = measureText(text, font).width;
if (size > maxWidth) {
const textLength: number = text.length;
for (let i: number = textLength - 1; i >= 0; --i) {
label = text.substring(0, i) + '...';
size = measureText(label, font).width;
if (size <= maxWidth || label.length < 4) {
if (label.length < 4) {
label = ' ';
}
return label;
}
}
}
return label;
}
/**
* Specifies the location parameters.
*/
export class Location {
/**
* Defines the horizontal position.
*/
public x: number;
/**
* Defines the vertical position.
*/
public y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
/**
* Method to calculate x position of title
*
* @param {Rect} location - Specifies the location of text.
* @param {Alignment} alignment - Specifies the alignment of the text.
* @param {Size} textSize - Specifies the size of the text.
* @param {type} type - Specifies whether the provided text is title or subtitle.
* @returns {Location} - Returns the location of text.
* @private
*/
export function findPosition(location: Rect, alignment: Alignment, textSize: Size, type: string): Location {
let x: number;
switch (alignment) {
case 'Near':
x = location.x;
break;
case 'Center':
x = (type === 'title') ? (location.width / 2 - textSize.width / 2) :
((location.x + (location.width / 2)) - textSize.width / 2);
break;
case 'Far':
x = (type === 'title') ? (location.width - location.y - textSize.width) :
((location.x + location.width) - textSize.width);
break;
}
const y: number = (type === 'title') ? location.y + (textSize.height / 2) : ((location.y + location.height / 2) + textSize.height / 2);
return new Location(x, y);
}
/**
*
* @param {SvgRenderer} renderer - Specifies the rendering element of the SVG.
* @param {any} renderOptions - Specifies the settings of the text.
* @param {string} text - Specifies the text.
* @returns {HTMLElement} - Returns the HTML element for the text.
*/
export function createTextStyle(
renderer: SvgRenderer, renderOptions: any, text: string
): HTMLElement {
const htmlObject: HTMLElement = <HTMLElement>renderer.createText(renderOptions, text);
htmlObject.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
htmlObject.style['user-select'] = 'none';
htmlObject.style['-moz-user-select'] = 'none';
htmlObject.style['-webkit-touch-callout'] = 'none';
htmlObject.style['-webkit-user-select'] = 'none';
htmlObject.style['-khtml-user-select'] = 'none';
htmlObject.style['-ms-user-select'] = 'none';
htmlObject.style['-o-user-select'] = 'none';
return htmlObject;
}
/**
* Internal rendering of text
*
* @param {TextOption} options - Specifies the text option
* @param {FontModel} font - Specifies the font model
* @param {string} color - Specifies the color
* @param {HTMLElement | Element} parent - Specifies the parent element of the text
* @param {boolean} isMinus - Specifies the boolean value
* @returns {Element} - Returns the element
* @private
*/
export function renderTextElement(
options: TextOption, font: FontModel, color: string, parent: HTMLElement | Element, isMinus: boolean = false
): Element {
const renderOptions: any = {
'font-size': font.size,
'font-style': font.fontStyle,
'font-family': font.fontFamily,
'font-weight': font.fontWeight,
'text-anchor': options.anchor,
'transform': options.transform,
'opacity': font.opacity,
'dominant-baseline': options.baseLine,
'id': options.id,
'x': options.x,
'y': options.y,
'fill': color
};
const text: string = typeof options.text === 'string' ? options.text : isMinus ? options.text[options.text.length - 1] : options.text[0];
let tspanElement: Element;
const renderer: SvgRenderer = new SvgRenderer('');
let height: number; let htmlObject: HTMLElement;
const breadCrumbText: boolean = !isNullOrUndefined(text) && !isNullOrUndefined(options.connectorText) ?
(text.includes(options.connectorText[1])) : false;
if (breadCrumbText) {
const drilledLabel: string = text;
const spacing: number = 5;
const drillLevelText: string[] = drilledLabel.split('#');
for (let z: number = 0; z < drillLevelText.length; z++) {
let drillText: string = (drillLevelText[z as number].search(options.connectorText) !== -1 &&
!isNullOrUndefined(options.connectorText)) ?
options.connectorText : drillLevelText[z as number];
renderOptions['id'] = options.id + '_' + z;
htmlObject = createTextStyle(renderer, renderOptions, drillText);
if (z % 2 === 0 && z !== 0) {
const re: RegExp = /\s+/g;
drillText = drillText.replace(re, ' ');
}
const size: Size = measureText(drillText, font);
renderOptions['x'] = z !== 0 ? renderOptions['x'] + size.width : renderOptions['x'] + size.width + spacing;
parent.appendChild(htmlObject);
}
} else {
htmlObject = createTextStyle(renderer, renderOptions, text);
parent.appendChild(htmlObject);
}
if (typeof options.text !== 'string' && options.text.length > 1) {
for (let i: number = 1, len: number = options.text.length; i < len; i++) {
height = (measureText(options.text[i as number], font).height);
tspanElement = renderer.createTSpan(
{
'x': options.x, 'id': options.id,
'y': (options.y) + (i * height)
},
options.text[i as number]);
htmlObject.appendChild(tspanElement);
}
parent.appendChild(htmlObject);
}
return htmlObject;
}
/**
*
* @param {string} targetId - Specifies the id of the element to which template is to be appended.
* @param {Element} targetElement - Specifies the element to which template is to be appended.
* @param {string} contentItemTemplate - Specifies the content to be appended as template.
* @returns {void}
*/
export function setItemTemplateContent(targetId: string, targetElement: Element, contentItemTemplate: string): void {
const itemSelect: string = targetId.split('_RectPath')[0];
let itemTemplate: Element;
if (targetId.indexOf('_LabelTemplate') > -1) {
itemTemplate = targetElement;
} else {
itemTemplate = document.querySelector('#' + itemSelect + '_LabelTemplate');
}
if (!isNullOrUndefined(itemTemplate)) {
itemTemplate.innerHTML = contentItemTemplate;
}
}
/**
*
* @param {string} id - Specifies the id of the element.
* @returns {Element} - Returns the element.
*/
export function getElement(id: string): Element {
return document.getElementById(id);
}
/**
*
* @param {any} a - Specifies the first order of TreeMap leaf elements.
* @param {any} b - Specifies the second order of TreeMap leaf elements.
* @returns {number} - Returns the order of the TreeMap leaf element.
*/
export function itemsToOrder(a: any, b: any): number {
return a['weight'] === b['weight'] ? 0 : a['weight'] < b['weight'] ? 1 : -1;
}
/**
*
* @param {string[]} source - Specifies the data from the data source.
* @param {string} pathName - Specifies the path name in the data source.
* @param {any} processData - Specifies the data source object.
* @param {TreeMap} treemap - Specifies the treemap instance.
* @returns {boolean} - Specifies whether data is available in the data source or not.
*/
export function isContainsData(source: string[], pathName: string, processData: any, treemap: TreeMap): boolean {
let isExist: boolean = false; let name: string = ''; let path: string;
const leaf: LeafItemSettingsModel = treemap.leafItemSettings; for (let i: number = 0; i < source.length; i++) {
path = treemap.levels[i as number] ? treemap.levels[i as number].groupPath : leaf.labelPath ? leaf.labelPath :
treemap.weightValuePath;
const data: string = processData[path as string] || 'undefined';
if (source[i as number] === data) {
name += data + (i === source.length - 1 ? '' : '#');
if (name === pathName) {
isExist = true;
break;
}
}
}
return isExist;
}
/**
*
* @param {any} data - Specifies the data to which the children elements to be found.
* @returns {any} - Returns the children elements of the TreeMap leaf element.
*/
export function findChildren(data: any): any {
let children: any;
if (data) {
const keys: string[] = Object.keys(data);
children = {};
for (let i: number = 0; i < keys.length; i++) {
if (data[keys[i as number]] instanceof Array) {
children['values'] = data[keys[i as number]];
children['key'] = keys[i as number];
break;
}
}
}
return children;
}
/**
*
* @param {any} data - Specifies the data to which highlight must be done.
* @param {items} items - Specifies the data source items.
* @param {string} mode - Specifies the mode of highlight.
* @param {TreeMap} treeMap - Specifies the treemap instance.
* @returns {string[]} - Returns the highlighted items.
*/
export function findHightLightItems(data: any, items: string[], mode: string, treeMap: TreeMap): string[] {
if (mode === 'Child') {
items.push(data['levelOrderName']);
const children: any[] = findChildren(data)['values'];
if (children && children.length > 0) {
for (let i: number = 0; i < children.length; i++) {
if (items.indexOf(children[i as number]['levelOrderName']) === -1) {
items.push(children[i as number]['levelOrderName']);
}
}
for (let j: number = 0; j < children.length; j++) {
findHightLightItems(children[j as number], items, mode, treeMap);
}
}
} else if (mode === 'Parent') {
if (typeof data['levelOrderName'] === 'string' && items.indexOf(data['levelOrderName']) === -1) {
items.push(data['levelOrderName']);
findHightLightItems(data['parent'], items, mode, treeMap);
}
} else if (mode === 'All') {
const parentName: string = (data['levelOrderName'] as string).split('#')[0];
let currentItem: any;
for (let i: number = 0; i < treeMap.layout.renderItems.length; i++) {
currentItem = treeMap.layout.renderItems[i as number];
if ((currentItem['levelOrderName']).indexOf(parentName) > -1 && items.indexOf(currentItem['levelOrderName']) === -1) {
items.push(currentItem['levelOrderName']);
}
}
} else {
items.push(data['levelOrderName']);
}
return items;
}
/**
* Function to compile the template function for maps.
*
* @param {string} template - Specifies the template
* @returns {Function} - Returns the template function
* @private
*/
export function getTemplateFunction(template: string | Function): any {
let templateFn: any = null;
try {
if (typeof template !== 'function' && document.querySelectorAll(template).length) {
templateFn = compile(document.querySelector(template).innerHTML.trim());
} else {
templateFn = compile(template);
}
} catch (e) {
templateFn = compile(template);
}
return templateFn;
}
/**
* @private
* @param {HTMLCollection} element - Specifies the element
* @param {string} labelId - Specifies the label id
* @param {Object} data - Specifies the data
* @returns {HTMLElement} - Returns the element
*/
export function convertElement(element: HTMLCollection, labelId: string, data: any): HTMLElement {
const childElement: HTMLElement = createElement('div', {
id: labelId
});
childElement.style.cssText = 'position: absolute;pointer-events: auto;';
let elementLength: number = element.length;
while (elementLength > 0) {
childElement.appendChild(element[0]);
elementLength--;
}
let templateHtml: string = childElement.innerHTML;
const keys: any[] = Object.keys(data);
for (let i: number = 0; i < keys.length; i++) {
const regExp: RegExpConstructor = RegExp;
templateHtml = templateHtml.replace(new regExp('{{:' + <string>keys[i as number] + '}}', 'g'), data[keys[i as number].toString()]);
}
childElement.innerHTML = templateHtml;
return childElement;
}
/**
*
* @param {Rect} rect - Specifies the area.
* @param {LabelPosition} position - Specifies the position
* @param {Size} labelSize - Specifies the label size.
* @param {string} type - Specifies the type.
* @param {TreeMap} treemap - Specifies the treemap instance.
* @returns {Location} - Returns the text location.
*/
export function findLabelLocation(rect: Rect, position: LabelPosition, labelSize: Size, type: string, treemap: TreeMap): Location {
const location: Location = new Location(0, 0);
const padding: number = 5;
const paddings: number = 2;
const x: number = (type === 'Template') ? treemap.areaRect.x : 0;
const y: number = (type === 'Template') ? treemap.areaRect.y : 0;
location.x = (Math.abs(x - ((position.indexOf('Left') > -1) ? rect.x + padding : !(position.indexOf('Right') > -1) ?
rect.x + ((rect.width / 2) - (labelSize.width / 2)) : (rect.x + rect.width) - labelSize.width))) - paddings;
if (treemap.enableDrillDown && (treemap.renderDirection === 'BottomLeftTopRight'
|| treemap.renderDirection === 'BottomRightTopLeft')) {
location.y = Math.abs((rect.y + rect.height) - labelSize.height + padding);
} else {
location.y = Math.abs(y - ((position.indexOf('Top') > -1) ? (type === 'Template' ? rect.y : rect.y + labelSize.height) :
!(position.indexOf('Bottom') > -1) ? type === 'Template' ? (rect.y + ((rect.height / 2) - (labelSize.height / 2))) :
(rect.y + (rect.height / 2) + labelSize.height / 4) : (rect.y + rect.height) - labelSize.height));
}
return location;
}
/**
*
* @param {HTMLElement} element - Specifies the element to be measured.
* @param {HTMLElement} parentElement - Specifies the parent element of the element to be measured.
* @returns {Size} - Returns the element size.
*/
export function measureElement(element: HTMLElement, parentElement: HTMLElement): Size {
const size: Size = new Size(0, 0);
parentElement.appendChild(element);
size.height = element.offsetHeight;
size.width = element.offsetWidth;
const measureElementId: HTMLElement = document.getElementById(element.id);
measureElementId.parentNode.removeChild(measureElementId);
return size;
}
/**
*
* @param {Rect} rect - Specifies the area.
* @returns {number} - Returns the area width.
*/
export function getArea(rect: Rect): number {
return (rect.width - rect.x) * (rect.height - rect.y);
}
/**
*
* @param {Rect} input - Specifies input for the calculation.
* @returns {number} - Returns the shortest edge.
*/
export function getShortestEdge(input: Rect): number {
const container: Rect = convertToContainer(input);
const width: number = container.width;
const height: number = container.height;
const result: number = Math.min(width, height);
return result;
}
/**
*
* @param {Rect} rect - Specifies the rectangle bounds of the container.
* @returns {Rect} - Returns the rectangle bounds.
*/
export function convertToContainer(rect: Rect): Rect {
const x: number = rect.x;
const y: number = rect.y;
const width: number = rect.width;
const height: number = rect.height;
return {
x: x,
y: y,
width: width - x,
height: height - y
};
}
/**
*
* @param {Rect} container - Specifies the rectangle bounds of the container.
* @returns {Rect} - Returns the rectangle bounds.
*/
export function convertToRect(container: Rect): Rect {
const xOffset: number = container.x;
const yOffset: number = container.y;
const width: number = container.width;
const height: number = container.height;
return {
x: xOffset,
y: yOffset,
width: xOffset + width,
height: yOffset + height
};
}
/**
*
* @param {number} pageX - Specifies the horizontal position of the mouse location.
* @param {number} pageY - Specifies the vertical position of the mouse location.
* @param {Element} element - Specifies the element to which the click is done.
* @returns {Location} - Returns the clicked location.
*/
export function getMousePosition(pageX: number, pageY: number, element: Element): Location {
const elementRect: ClientRect = element.getBoundingClientRect();
const pageXOffset: number = element.ownerDocument.defaultView.pageXOffset;
const pageYOffset: number = element.ownerDocument.defaultView.pageYOffset;
const clientTop: number = element.ownerDocument.documentElement.clientTop;
const clientLeft: number = element.ownerDocument.documentElement.clientLeft;
const positionX: number = elementRect.left + pageXOffset - clientLeft;
const positionY: number = elementRect.top + pageYOffset - clientTop;
return new Location((pageX - positionX), (pageY - positionY));
}
/**
*
* @param {ColorMappingModel[]} colorMapping - Specifies the color mapping instance.
* @param {string} equalValue - Specifies the equal value.
* @param {number | string} value - Specifies the range value.
* @returns {any} - Returns the color mapping object.
* @private
*/
export function colorMap(
colorMapping: ColorMappingModel[], equalValue: string,
value: number | string): any {
let fill: string; const paths: string[] = []; let opacity: string;
if (isNullOrUndefined(equalValue) && (isNullOrUndefined(value) && isNaN(<number>value))) {
return null;
}
for (let i: number = 0; i < colorMapping.length; i++) {
let isEqualColor: boolean = false; const dataValue: number = <number>value;
if (!isNullOrUndefined(colorMapping[i as number].from) && !isNullOrUndefined(colorMapping[i as number].to)
&& !isNullOrUndefined(colorMapping[i as number].value)) {
if ((value >= colorMapping[i as number].from && colorMapping[i as number].to >= value) &&
(colorMapping[i as number].value === equalValue)) {
isEqualColor = true;
if (Object.prototype.toString.call(colorMapping[i as number].color) === '[object Array]') {
fill = !isEqualColor ? colorCollections(colorMapping[i as number], dataValue) : colorMapping[i as number].color[0];
} else {
fill = <string>colorMapping[i as number].color;
}
}
} else if ((!isNullOrUndefined(colorMapping[i as number].from) && !isNullOrUndefined(colorMapping[i as number].to))
|| !isNullOrUndefined((colorMapping[i as number].value))) {
if ((value >= colorMapping[i as number].from && colorMapping[i as number].to >= value)
|| (colorMapping[i as number].value === equalValue)) {
if (colorMapping[i as number].value === equalValue) {
isEqualColor = true;
}
if (Object.prototype.toString.call(colorMapping[i as number].color) === '[object Array]') {
fill = !isEqualColor ? colorCollections(colorMapping[i as number], dataValue) : colorMapping[i as number].color[0];
} else {
fill = <string>colorMapping[i as number].color;
}
}
}
if (((value >= colorMapping[i as number].from && value <= colorMapping[i as number].to)
|| (colorMapping[i as number].value === equalValue))
&& !isNullOrUndefined(colorMapping[i as number].minOpacity) && !isNullOrUndefined(colorMapping[i as number].maxOpacity)
&& fill) {
opacity = deSaturationColor(colorMapping[i as number], value as number);
}
if ((fill === '' || isNullOrUndefined(fill))
&& isNullOrUndefined(colorMapping[i as number].from) && isNullOrUndefined(colorMapping[i as number].to)
&& isNullOrUndefined(colorMapping[i as number].minOpacity) && isNullOrUndefined(colorMapping[i as number].maxOpacity)
&& isNullOrUndefined(colorMapping[i as number].value)) {
fill = (Object.prototype.toString.call(colorMapping[i as number].color) === '[object Array]') ?
<string>colorMapping[i as number].color[0] : <string>colorMapping[i as number].color;
}
opacity = !isNullOrUndefined(opacity) ? opacity : '1';
paths.push(fill);
}
for (let j: number = paths.length - 1; j >= 0; j--) {
fill = paths[j as number];
j = (fill) ? -1 : j;
}
return { fill: fill, opacity: opacity };
}
/**
*
* @param {ColorMappingModel} colorMapping - Specifies the color mapping object.
* @param {number} rangeValue - Specifies the range value.
* @returns {string} - Returns the opacity for the color mapping.
* @private
*/
export function deSaturationColor(colorMapping: ColorMappingModel, rangeValue: number): string {
let opacity: number = 1;
if ((rangeValue >= colorMapping.from && rangeValue <= colorMapping.to)) {
const ratio: number = (rangeValue - colorMapping.from) / (colorMapping.to - colorMapping.from);
opacity = (ratio * (colorMapping.maxOpacity - colorMapping.minOpacity)) + colorMapping.minOpacity;
}
return opacity.toString();
}
/**
*
* @param {ColorMappingModel} colorMap - Specifies the color mapping object.
* @param {number} value - Specifies the range value.
* @returns {string} - Returns the fill color.
*/
export function colorCollections(colorMap: ColorMappingModel, value: number): string {
const gradientFill: string = getColorByValue(colorMap, value);
return gradientFill;
}
/**
*
* @param {number} r - Specifies the red color value.
* @param {number} g - Specifies the green color value.
* @param {number} b - Specifies the blue color value.
* @returns {string} - Returns the fill color.
*/
export function rgbToHex(r: number, g: number, b: number): string {
return '#' + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
/**
*
* @param {ColorMappingModel} colorMap - Specifies the color mapping.
* @param {number} value - Specifies the range value.
* @returns {string} - Returns the fill color.
*/
export function getColorByValue(colorMap: ColorMappingModel, value: number): string {
let color: string = '';
let rbg: ColorValue;
if (Number(value) === colorMap.from) {
color = colorMap.color[0];
} else if (Number(value) === colorMap.to) {
color = colorMap.color[colorMap.color.length - 1];
} else {
rbg = getGradientColor(Number(value), colorMap);
color = rgbToHex(rbg.r, rbg.g, rbg.b);
}
return color;
}
/**
*
* @param {number} value - Specifies the range value.
* @param {ColorMappingModel} colorMap - Specifies the color mapping.
* @returns {ColorValue} - Returns the color value object.
*/
export function getGradientColor(value: number, colorMap: ColorMappingModel): ColorValue {
const previousOffset: number = colorMap.from;
const nextOffset: number = colorMap.to;
let percent: number = 0;
const full: number = nextOffset - previousOffset; let midColor: string;
percent = (value - previousOffset) / full; let previousColor: string; let nextColor: string;
if (colorMap.color.length <= 2) {
previousColor = colorMap.color[0].charAt(0) === '#' ? colorMap.color[0] : colorNameToHex(colorMap.color[0]);
nextColor = colorMap.color[colorMap.color.length - 1].charAt(0) === '#' ?
colorMap.color[colorMap.color.length - 1] : colorNameToHex(colorMap.color[colorMap.color.length - 1]);
} else {
previousColor = colorMap.color[0].charAt(0) === '#' ? colorMap.color[0] : colorNameToHex(colorMap.color[0]);
nextColor = colorMap.color[colorMap.color.length - 1].charAt(0) === '#' ?
colorMap.color[colorMap.color.length - 1] : colorNameToHex(colorMap.color[colorMap.color.length - 1]);
const a: number = full / (colorMap.color.length - 1); let b: number; let c: number;
const length: number = colorMap.color.length - 1;
const splitColorValueOffset: any[] = []; let splitColor: any = {};
for (let j: number = 1; j < length; j++) {
c = j * a;
b = previousOffset + c;
splitColor = { b: b, color: colorMap.color[j as number] };
splitColorValueOffset.push(splitColor);
}
for (let i: number = 0; i < splitColorValueOffset.length; i++) {
if (previousOffset <= value && value <= splitColorValueOffset[i as number]['b'] && i === 0) {
midColor = splitColorValueOffset[i as number]['color'].charAt(0) === '#' ?
splitColorValueOffset[i as number]['color'] : colorNameToHex(splitColorValueOffset[i as number]['color']);
nextColor = midColor;
percent = value < splitColorValueOffset[i as number]['b'] ? 1 - Math.abs((value - splitColorValueOffset[i as number]['b']) / a)
: (value - splitColorValueOffset[i as number]['b']) / a;
} else if (splitColorValueOffset[i as number]['b'] <= value && value <= nextOffset && i === (splitColorValueOffset.length - 1)) {
midColor = splitColorValueOffset[i as number]['color'].charAt(0) === '#' ?
splitColorValueOffset[i as number]['color'] : colorNameToHex(splitColorValueOffset[i as number]['color']);
previousColor = midColor;
percent = value < splitColorValueOffset[i as number]['b'] ?
1 - Math.abs((value - splitColorValueOffset[i as number]['b']) / a) : (value - splitColorValueOffset[i as number]['b']) / a;
}
if (i !== splitColorValueOffset.length - 1 && i < splitColorValueOffset.length) {
if (splitColorValueOffset[i as number]['b'] <= value && value <= splitColorValueOffset[i + 1]['b']) {
midColor = splitColorValueOffset[i as number]['color'].charAt(0) === '#' ?
splitColorValueOffset[i as number]['color'] : colorNameToHex(splitColorValueOffset[i as number]['color']);
previousColor = midColor;
nextColor = splitColorValueOffset[i + 1]['color'].charAt(0) === '#' ?
splitColorValueOffset[i + 1]['color'] : colorNameToHex(splitColorValueOffset[i + 1]['color']);
percent = Math.abs((value - splitColorValueOffset[i + 1]['b'])) / a;
}
}
}
}
return getPercentageColor(percent, previousColor, nextColor);
}
/**
*
* @param {number} percent - Specifies the percentage of the color.
* @param {number} previous - Specifies the previous color.
* @param {number} next - Specifies the next color.
* @returns {ColorValue} - Returns the color value object.
*/
export function getPercentageColor(percent: number, previous: string, next: string): ColorValue {
const nextColor: string = next.split('#')[1];
const prevColor: string = previous.split('#')[1];
const r: number = getPercentage(percent, parseInt(prevColor.substr(0, 2), 16), parseInt(nextColor.substr(0, 2), 16));
const g: number = getPercentage(percent, parseInt(prevColor.substr(2, 2), 16), parseInt(nextColor.substr(2, 2), 16));
const b: number = getPercentage(percent, parseInt(prevColor.substr(4, 2), 16), parseInt(nextColor.substr(4, 2), 16));
return new ColorValue(r, g, b);
}
/**
*
* @param {number} percent - Specifies the percentage of the color.
* @param {number} previous - Specifies the previous color.
* @param {number} next - Specifies the next color.
* @returns {number} - Returns the color value.
*/
export function getPercentage(percent: number, previous: number, next: number): number {
const full: number = next - previous;
return Math.round((previous + (full * percent)));
}
/**
*
* @param {number} maximumWidth - Specifies the length of the text.
* @param {string} dataLabel - Specifies the label.
* @param {FontModel} font - Specifies the font of the label.
* @returns {string[]} - Returns the labels.
*/
export function wordWrap(maximumWidth: number, dataLabel: string, font: FontModel): string[] {
const textCollection: string[] = dataLabel.split(' ');
let label: string = '';
const labelCollection: string[] = [];
let text: string;
for (let i: number = 0, len: number = textCollection.length; i < len; i++) {
text = textCollection[i as number];
if (measureText(label.concat(text), font).width < maximumWidth) {
label = label.concat((label === '' ? '' : ' ') + text);
} else {
if (label !== '') {
labelCollection.push(textTrim(maximumWidth, label, font));
label = text;
} else {
labelCollection.push(textTrim(maximumWidth, text, font));
text = '';
}
}
if (label && i === len - 1) {
labelCollection.push(textTrim(maximumWidth, label, font));
}
}
return labelCollection;
}
/**
*
* @param {number} maxWidth - Specifies the length of the text.
* @param {string} label - Specifies the label.
* @param {FontModel} font - Specifies the font of the label.
* @returns {string[]} - Returns the labels.
*/
export function textWrap(maxWidth: number, label: string, font: FontModel): string[] {
const resultText: string[] = [];
let currentLength: number = 0;
let totalWidth: number = measureText(label, font).width;
const totalLength: number = label.length;
if (maxWidth >= totalWidth) {
resultText.push(label);
return resultText;
} else {
for (let i: number = label.length; i > currentLength; i--) {
const sliceString: string = label.slice(currentLength, i);
totalWidth = measureText(sliceString, font).width;
if (totalWidth <= maxWidth) {
resultText.push(sliceString);
currentLength += sliceString.length;
if (totalLength === currentLength) {
return resultText;
}
i = totalLength + 1;
}
}
}
return resultText;
}
/**
* hide function
*
* @param {number} maxWidth - Specifies the maximum width.
* @param {number} maxHeight - Specifies the maximum height.
* @param {string} text - Specifies the text.
* @param {FontModel} font - Specifies the font.
* @returns {string} - Returns the hidden text.
* @private
*/
export function hide(maxWidth: number, maxHeight: number, text: string, font: FontModel): string {
let hideText: string = text;
const textSize: Size = measureText(text, font);
hideText = (textSize.width > maxWidth || textSize.height > maxHeight) ? ' ' : text;
return hideText;
}
/**
*
* @param {number} a - Specifies the first value of the leaf.
* @param {number} b - Specifies the second value of the leaf.
* @returns {number} - Returns whether values are equal or not.
*/
export function orderByArea(a: number, b: number): number {
if (a['itemArea'] === b['itemArea']) {
return 0;
} else if (a['itemArea'] < b['itemArea']) {
return 1;
}
return -1;
}
/**
*
* @param {TreeMap} treemap - Specifies the treemap instance.
* @param {Element} element - Specifies the selected TreeMap leaf item.
* @param {string} className -Specifies the selected class name.
* @returns {void}
*/
export function maintainSelection(treemap: TreeMap, element: Element, className: string): void {
const elementId: string[] = treemap.levelSelection;
if (elementId) {
for (let index: number = 0; index < elementId.length; index++) {
if (element.getAttribute('id') === elementId[index as number] ||
element.children[0].id === elementId[index as number]) {
if (element.childElementCount > 0 && element.children[0].id.indexOf('_Group') === -1) {
element.children[0].setAttribute('class', className);
applyOptions(
element.childNodes[0] as SVGPathElement,
{
border: treemap.selectionSettings.border, fill: treemap.selectionSettings.fill,
opacity: treemap.selectionSettings.opacity
}
);
}
} else {
element.setAttribute('class', '');
}
}
}
}
/**
*
* @param {TreeMap} treemap - Specifies the treemap instance.
* @param {Element} legendGroup - Specifies the selected element.
* @returns {void}
*/
export function legendMaintain(treemap: TreeMap, legendGroup: Element): void {
const elementId: string[] = treemap.legendId;
if (elementId) {
for (let i: number = 0; i < elementId.length; i++) {
if (treemap.legendSettings.mode === 'Interactive') {
for (let j: number = 0; j < legendGroup.childElementCount; j++) {
if (legendGroup.childNodes[j as number]['id'] === elementId[i as number] ||
parseFloat(legendGroup.childNodes[j as number]['id'].split('Index_')[1]) === parseFloat(elementId[i as number].split('Index_')[1])) {
const treemapSVGRectElement: SVGRectElement = <SVGRectElement>legendGroup.childNodes[j as number];
treemapSVGRectElement.setAttribute('fill', treemap.selectionSettings.fill);
treemapSVGRectElement.setAttribute('opacity', treemap.selectionSettings.opacity);
if (treemapSVGRectElement.id.indexOf('Text') === -1) {
treemapSVGRectElement.setAttribute('stroke-width', (treemap.selectionSettings.border.width).toString());
treemapSVGRectElement.setAttribute('stroke', treemap.selectionSettings.border.color);
} else {
treemapSVGRectElement.setAttribute('stroke', null);
treemapSVGRectElement.setAttribute('stroke-width', null);
}
}
}
} else {
const legendItem: Element = document.getElementById(elementId[i as number]);
if (!isNullOrUndefined(legendItem)) {
legendItem.setAttribute('fill', treemap.selectionSettings.fill);
legendItem.setAttribute('opacity', treemap.selectionSettings.opacity);
if (legendItem.id.indexOf('Text') === -1) {
legendItem.setAttribute('stroke', treemap.selectionSettings.border.color);
legendItem.setAttribute('stroke-width', (treemap.selectionSettings.border.width).toString());
} else {
legendItem.setAttribute('stroke', null);
legendItem.setAttribute('stroke-width', null);
}
}
}
}
}
}
/**
*
* @param {HTMLCollection} elements - Specifies the selected TreeMap element.
* @param {string} type - Specifies the selection type.
* @param {TreeMap} treemap - Specifies the TreeMap instance.
* @returns {void}
*/
export function removeClassNames(elements: HTMLCollection, type: string, treemap: TreeMap): void {
let element: SVGPathElement;
let options: any = {};
for (let j: number = 0; j < elements.length; j++) {
element = isNullOrUndefined(elements[j as number].childNodes[0] as SVGPathElement) ? elements[j as number] as SVGPathElement :
elements[j as number].childNodes[0] as SVGPathElement;
options = treemap.layout.renderItems[parseFloat(element.id.split('_Item_Index_')[1])]['options'];
applyOptions(element, options);
elements[j as number].classList.remove(type);
j -= 1;
}
}
/**
*
* @param {SVGPathElement} element - Specifies the SVG path element.
* @param {any} options - Specifies the settings for the SVG path element.
* @returns {void}
*/
export function applyOptions(element: SVGPathElement, options: any): void {
element.setAttribute('opacity', options['opacity']);
if (!isNullOrUndefined(options['fill'])) {
element.setAttribute('fill', options['fill']);
} else {
element.setAttribute('fill', 'black');
}
element.setAttribute('stroke', options['border']['color']);
element.setAttribute('stroke-width', options['border']['width']);
}
/**
*
* @param {string} format - Specifies the format value.
* @param {any} data - Specifies the data source object.
* @param {TreeMap} treemap - Specifies the TreeMap instance.
* @returns {string} - Returns the formatted text.
*/
export function textFormatter(format: string, data: any, treemap: TreeMap): string {
if (isNullOrUndefined(format)) {
return null;
}
const keys: string[] = Object.keys(data);
for (const key of keys) {
format = format.split('${' + key + '}').join(formatValue(data[key as string], treemap).toString());
}
return format;
}
/**
*
* @param {number} value - Specifies the text to be formatted.
* @param {TreeMap} treemap - Specifies the TreeMap instance.
* @returns {string | number} - Returns the formatted text.
*/
export function formatValue(value: number, treemap: TreeMap): string | number {
let formatValue: string | number; let formatFunction: any;
if (treemap.format && !isNaN(Number(value))) {
formatFunction = treemap.intl.getNumberFormat(
{ format: treemap.format, useGrouping: treemap.useGroupingSeparator });
formatValue = formatFunction(Number(value));
} else {
formatValue = value;
}
return formatValue ? formatValue : '';
}
/**
* @private
*/
export class ColorValue {
public r: number;
public g: number;
public b: number;
constructor(r?: number, g?: number, b?: number) {
this.r = r;
this.g = g;
this.b = b;
}
}
/**
* @param {ColorValue} value - Specfies the color value
* @returns {string} - Returns the string
* @private
*/
export function convertToHexCode(value: ColorValue): string {
return '#' + componentToHex(value.r) + componentToHex(value.g) + componentToHex(value.b);
}
/**
* @param {number} value - Specifies the value
* @returns {string} - Returns the string
* @private */
export function componentToHex(value: number): string {
const hex: string = value.toString(16);
return hex.length === 1 ? '0' + hex : hex;
}
/**
* @param {string} hex - Specifies the hex value
* @returns {ColorValue} - Returns the color value
* @private
*/
export function convertHexToColor(hex: string): ColorValue {
const result: RegExpExecArray = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? new ColorValue(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)) :
new ColorValue(255, 255, 255);
}
/**
* @param {string} color - Specifies the color
* @returns {string} - Returns the string
* @private
*/
export function colorNameToHex(color: string): string {
color = color === 'transparent' ? 'white' : color;
const element: HTMLElement = document.getElementById('treeMapMeasureText');
element.style.color = color;
color = window.getComputedStyle(element).color;
const isRGBValue: string[] = color.replace(/[()RGBrgba ]/g, '').split(',');
return convertToHexCode(
new ColorValue(parseInt(isRGBValue[0], 10), parseInt(isRGBValue[1], 10), parseInt(isRGBValue[2], 10))
);
}
/**
* @param {Location} location - Specifies the location
* @param {string} shape - Specifies the shape
* @param {Size} size - Specifies the size
* @param {string} url - Specifies the url
* @param {PathOption} options - Specifies the options
* @returns {Element} - Returns the element
* @private
*/
export function drawSymbol(location: Location, shape: string, size: Size, url: string, options: PathOption): Element {
const svgRenderer: SvgRenderer = new SvgRenderer('');
const temp: IShapes = renderLegendShape(location, size, shape, options, url);
const htmlElement: Element = svgRenderer['draw' + temp.functionName](temp.renderOption);
return htmlElement;
}
/**
* @param {Location} location - Specifies the location
* @param {Size} size - Specifies the size
* @param {string} shape - Specifies the shape
* @param {PathOption} options - Specifies the path option
* @param {string} url - Specifies the string
* @returns {IShapes} - Returns the shapes
* @private
*/
export function renderLegendShape(location: Location, size: Size, shape: string, options: PathOption, url: string): IShapes {
let renderPath: string;
let functionName: string = 'Path';
const shapeWidth: number = size.width;
const shapeHeight: number = size.height;
const shapeX: number = location.x;
const shapeY: number = location.y;
const x: number = location.x + (-shapeWidth / 2);
const y: number = location.y + (-shapeHeight / 2);
switch (shape) {
case 'Circle':
case 'Bubble':
functionName = 'Ellipse';
merge(options, { 'rx': shapeWidth / 2, 'ry': shapeHeight / 2, 'cx': shapeX, 'cy': shapeY });
break;
case 'VerticalLine':
renderPath = 'M' + ' ' + shapeX + ' ' + (shapeY + (shapeHeight / 2)) + ' ' + 'L' + ' ' + shapeX + ' '
+ (shapeY + (-shapeHeight / 2));
merge(options, { 'd': renderPath });
break;
case 'Diamond':
renderPath = 'M' + ' ' + x + ' ' + shapeY + ' ' +
'L' + ' ' + shapeX + ' ' + (shapeY + (-shapeHeight / 2)) + ' ' +
'L' + ' ' + (shapeX + (shapeWidth / 2)) + ' ' + shapeY + ' ' +
'L' + ' ' + shapeX + ' ' + (shapeY + (shapeHeight / 2)) + ' ' +
'L' + ' ' + x + ' ' + shapeY + ' z';
merge(options, { 'd': renderPath });
break;
case 'Rectangle':
renderPath = 'M' + ' ' + x + ' ' + (shapeY + (-shapeHeight / 2)) + ' ' +
'L' + ' ' + (shapeX + (shapeWidth / 2)) + ' ' + (shapeY + (-shapeHeight / 2)) + ' ' +
'L' + ' ' + (shapeX + (shapeWidth / 2)) + ' ' + (shapeY + (shapeHeight / 2)) + ' ' +
'L' + ' ' + x + ' ' + (shapeY + (shapeHeight / 2)) + ' ' +
'L' + ' ' + x + ' ' + (shapeY + (-shapeHeight / 2)) + ' z';
merge(options, { 'd': renderPath });
break;
case 'Triangle':
renderPath = 'M' + ' ' + x + ' ' + (shapeY + (shapeHeight / 2)) + ' ' +
'L' + ' ' + shapeX + ' ' + (shapeY + (-shapeHeight / 2)) + ' ' +
'L' + ' ' + (shapeX + (shapeWidth / 2)) + ' ' + (shapeY + (shapeHeight / 2)) + ' ' +
'L' + ' ' + x + ' ' + (shapeY + (shapeHeight / 2)) + ' z';
merge(options, { 'd': renderPath });
break;
case 'InvertedTriangle':
renderPath = 'M' + ' ' + (shapeX + (shapeWidth / 2)) + ' ' + (shapeY - (shapeHeight / 2)) + ' ' +
'L' + ' ' + shapeX + ' ' + (shapeY + (shapeHeight / 2)) + ' ' +
'L' + ' ' + (shapeX - (shapeWidth / 2)) + ' ' + (shapeY - (shapeHeight / 2)) + ' ' +
'L' + ' ' + (shapeX + (shapeWidth / 2)) + ' ' + (shapeY - (shapeHeight / 2)) + ' z';
merge(options, { 'd': renderPath });
break;
case 'Pentagon':
// eslint-disable-next-line no-case-declarations
const eq: number = 72;
// eslint-disable-next-line no-case-declarations
let xValue: number;
// eslint-disable-next-line no-case-declarations
let yValue: number;
for (let i: number = 0; i <= 5; i++) {
xValue = (shapeWidth / 2) * Math.cos((Math.PI / 180) * (i * eq));
yValue = (shapeWidth / 2) * Math.sin((Math.PI / 180) * (i * eq));
if (i === 0) {
ren