@syncfusion/ej2-maps
Version:
The Maps component is used to visualize the geographical data and represent the statistical data of a particular geographical area on earth with user interactivity, and provides various customizing options
458 lines (454 loc) • 29.7 kB
text/typescript
import { Maps } from '../../index';
import {
findMidPointOfPolygon, Rect, filter, getTemplateFunction, getZoomTranslate,
getTranslate, RectOption, convertElementFromLabel,
Point, TextOption, renderTextElement, MapLocation, textTrim, Size, Internalize, measureTextElement
} from '../utils/helper';
import { isNullOrUndefined, AnimationOptions, Animation, animationMode } from '@syncfusion/ej2-base';
import { FontModel, DataLabelSettingsModel, ILabelRenderingEventArgs, LayerSettings } from '../index';
import { dataLabelRendering } from '../model/constants';
/**
* DataLabel Module used to render the maps datalabel
*/
export class DataLabel {
private maps: Maps;
/**
* Datalabel collections
*
* @private
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public dataLabelCollections: any[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private value: any = { rightWidth: 0, leftWidth: 0, heightTop: 0, heightBottom: 0 };
constructor(maps: Maps) {
this.maps = maps;
this.dataLabelCollections = [];
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private getDataLabel(dataSource: any[], labelPath: string, shapeName: string, shapeDataPath: string): any {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let text: any; let shapeNameValue : string;
for (let i: number = 0; i < (isNullOrUndefined(dataSource) ? 0 : dataSource.length); i++) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any = dataSource[i as number];
const dataShapePathValue: string = !isNullOrUndefined(data[shapeDataPath as string]) && isNaN(data[shapeDataPath as string]) &&
typeof data[shapeDataPath as string] === 'string' ? data[shapeDataPath as string].toLowerCase() : data[shapeDataPath as string];
shapeName = !isNullOrUndefined(shapeName) && typeof shapeName === 'string' ? shapeName.toString() : shapeName;
shapeNameValue = !isNullOrUndefined(shapeName) && typeof shapeName === 'string' ? shapeName.toLowerCase() : shapeName;
if ((dataShapePathValue) === shapeNameValue) {
text = data;
break;
}
}
return text;
}
/**
* To render label for maps.
*
* @param {LayerSettings} layer - Specifies the layer settings
* @param {number} layerIndex - Specifies the layer index.
* @param {object} shape - Specifies the shape.
* @param {any[]} layerData - Specifies the layer data.
* @param {Element} group Specifies the element.
* @param {HTMLElement} labelTemplateElement - Specifies the template element.
* @param {number} index - Specifies the index number.
* @param {any[]} intersect - Specifies the intersect.
* @returns {void}
* @private
*/
public renderLabel(
layer: LayerSettings, layerIndex: number, shape: object,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
layerData: any[], group: Element, labelTemplateElement: HTMLElement, index: number, intersect: any[]
): void {
const dataLabel: DataLabelSettingsModel = layer.dataLabelSettings;
const style: FontModel = layer.dataLabelSettings.textStyle;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let templateFn: any;
let options: TextOption;
const dataLabelSettings: DataLabelSettingsModel = layer.dataLabelSettings;
const labelpath: string = layer.dataLabelSettings.labelPath;
let shapePoint: [MapLocation[]] = [[]];
let midIndex: number = 0;
let pointsLength: number = 0;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const shapeData: any = shape;
let element: Element;
let rect: Element;
let text: string = '';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let datasrcObj: any;
let currentLength: number = 0; let oldIndex : number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let location: any;
let sublayerIndexLabel : boolean = false;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const shapeProperties: any = shape['properties'];
const labelId: string = this.maps.element.id + '_LayerIndex_' + layerIndex + '_shapeIndex_' + index + '_LabelIndex_' + index;
const textLocation: Point = new Point(0, 0);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const shapes: any = layerData[index as number]; let locationX: any; let locationY: any;
style.fontFamily = this.maps.theme.toLowerCase() !== 'material' ? this.maps.themeStyle.labelFontFamily : style.fontFamily;
style.fontWeight = style.fontWeight || this.maps.themeStyle.fontWeight;
style.size = style.size || this.maps.themeStyle.fontSize;
shape = !isNullOrUndefined(shapes) ? shapes['property'] : null;
const properties: string[] = (Object.prototype.toString.call(layer.shapePropertyPath) === '[object Array]' ?
layer.shapePropertyPath : [layer.shapePropertyPath]) as string[];
let propertyPath: string; const isPoint : boolean = false;
const animate: boolean = (layer.animationDuration !== 0 || animationMode === 'Enable') || isNullOrUndefined(this.maps.zoomModule);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const translate: any = (this.maps.isTileMap) ? new Object() : ((this.maps.zoomSettings.zoomFactor > 1 &&
!isNullOrUndefined(this.maps.zoomModule)) ? getZoomTranslate(this.maps, layer, animate) :
getTranslate(this.maps, layer, animate));
const scale: number = (this.maps.isTileMap) ? this.maps.scale : translate['scale'];
const transPoint: Point = (this.maps.isTileMap) ? this.maps.translatePoint : translate['location'] as Point;
const zoomTransPoint : Point = this.maps.zoomTranslatePoint; let shapeWidth: number;
const scaleZoomValue : number = !isNullOrUndefined(this.maps.scale) ? Math.floor(this.maps.scale) : 1;
const zoomLabelsPosition : boolean = this.maps.zoomSettings.enable ? !isNullOrUndefined(this.maps.zoomShapeCollection) &&
this.maps.zoomShapeCollection.length > 0 && !this.maps.isAddLayer : this.maps.zoomSettings.enable; this.maps.translateType = 'labels';
for (let j: number = 0; j < properties.length; j++) {
if (shapeProperties[properties[j as number]]) {
propertyPath = properties[j as number];
datasrcObj = this.getDataLabel(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
layer.dataSource as any[], layer.shapeDataPath, shapeData['properties'][propertyPath as string], layer.shapeDataPath);
if (datasrcObj) {
break;
}
}
}
datasrcObj = this.getDataLabel(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
layer.dataSource as any[], layer.shapeDataPath, shapeData['properties'][propertyPath as string], layer.shapeDataPath);
if (!isNullOrUndefined(shapes) && !isNullOrUndefined(shapes['property'])) {
shapePoint = [[]];
if (!layerData[index as number]['_isMultiPolygon'] && layerData[index as number]['type'] !== 'Point' && layerData[index as number]['type'] !== 'MultiPoint') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
shapePoint.push(this.getPoint(layerData[index as number] as any[], []));
currentLength = shapePoint[shapePoint.length - 1].length;
if (pointsLength < currentLength) {
pointsLength = currentLength;
midIndex = shapePoint.length - 1;
}
} else if (layerData[index as number]['type'] !== 'Point' && layerData[index as number]['type'] !== 'MultiPoint') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const layer: any[] = <any[]>layerData[index as number];
for (let j: number = 0; j < layer.length; j++) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
shapePoint.push(this.getPoint(layer[j as number] as any[], []));
currentLength = shapePoint[shapePoint.length - 1].length;
if (pointsLength < currentLength) {
pointsLength = currentLength;
midIndex = shapePoint.length - 1;
}
}
}
}
text = (!isNullOrUndefined(datasrcObj)) ? !isNullOrUndefined(datasrcObj[labelpath as string]) ?
datasrcObj[labelpath as string].toString() : shapeData['properties'][labelpath as string] || datasrcObj[layer.shapeDataPath] : shapeData['properties'][labelpath as string];
if ((Object.prototype.toString.call(layer.shapePropertyPath) === '[object Array]') &&
(isNullOrUndefined(text) && (!isNullOrUndefined(layer.dataSource) && layer.dataSource['length'] === 0))) {
for (let l: number = 0; l < layer.shapePropertyPath.length; l++) {
if (shapeData['properties'][layer.shapePropertyPath[l as number]]) {
text = shapeData['properties'][layer.shapePropertyPath[l as number]];
break;
}
}
}
if (isNullOrUndefined(text) && (layer.dataLabelSettings.template !== '' && layer.dataSource['length'] === 0)) {
text = shapeData['properties'][layer.shapePropertyPath as string];
}
if (isNullOrUndefined(text) && (!isNullOrUndefined(layer.dataSource) && layer.dataSource['length'] > 0)) {
text = '';
}
const dataLabelText : string = text;
const projectionType : string = this.maps.projectionType;
if (isPoint) {
location = {
x: shapePoint[midIndex as number][index as number]['x'], y: shapePoint[midIndex as number][index as number]['y'],
rightMin: 0, rightMax: 0, leftMin: 0, leftMax: 0,
points: shapePoint[midIndex as number][index as number], topMax: 0, topMin: 0,
bottomMax: 0, bottomMin: 0, height: 0
};
} else {
location = findMidPointOfPolygon(shapePoint[midIndex as number], projectionType, layer.geometryType);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const firstLevelMapLocation : any = location;
if (!isNullOrUndefined(text) && !isNullOrUndefined(location)) {
if (zoomLabelsPosition && scaleZoomValue > 1 && !this.maps.zoomNotApplied && dataLabel.template === '') {
if (layerIndex > 0){
for (let k : number = 0; k < this.maps.zoomLabelPositions.length; k++){
if (this.maps.zoomLabelPositions[k as number]['dataLabelText'] === text) {
oldIndex = index;
index = k;
sublayerIndexLabel = true;
break;
}
}
}
locationX = location['x'];
locationY = location['y'];
location['x'] = ((location['x'] + zoomTransPoint['x']) * scale);
location['y'] = ((location['y'] + zoomTransPoint['y']) * scale);
}
location['y'] = (this.maps.projectionType === 'Mercator') || layer.geometryType === 'Normal' ? location['y'] : (-location['y']);
if (!isNullOrUndefined(this.maps.format) && !isNaN(Number(text)) && !isNaN(parseFloat(text))) {
if (this.maps.useGroupingSeparator) {
text = Internalize(this.maps, parseFloat(text));
if (!isNullOrUndefined(datasrcObj)) {
datasrcObj[labelpath as string] = text;
}
}
}
const eventargs: ILabelRenderingEventArgs = {
name: dataLabelRendering, maps: this.maps, cancel: false, border: { color: dataLabel.border.color,
width: dataLabel.border.width, opacity: dataLabel.border.opacity }, datalabel: dataLabel,
fill: dataLabel.fill, template: dataLabel.template, text: text, offsetX: 0, offsetY: 0
};
this.maps.trigger('dataLabelRendering', eventargs, (labelArgs: ILabelRenderingEventArgs) => {
if (eventargs.cancel) {
return;
}
let position: MapLocation[] = [];
let width: number = zoomLabelsPosition && scaleZoomValue > 1 && !this.maps.zoomNotApplied
&& this.maps.zoomShapeCollection.length > index ? (this.maps.dataLabelShape[index as number]) * scale :
(location['rightMax']['x'] - location['leftMax']['x']) * scale;
if (!isNullOrUndefined(this.maps.dataLabelShape) && !this.maps.isReset){
shapeWidth = firstLevelMapLocation['rightMax']['x'] - firstLevelMapLocation['leftMax']['x'];
this.maps.dataLabelShape.push(shapeWidth);
}
if (eventargs.text !== text && !eventargs.cancel){
text = eventargs.text;
}
const textSize: Size = measureTextElement(text, style);
let trimmedLable: string = text;
let elementSize: Size = textSize;
const startY: number = location['y'] - textSize['height'] / 2;
const endY: number = location['y'] + textSize['height'] / 2;
const start: number = ((location['y'] + transPoint['y']) * scale) - textSize['height'] / 2;
const end: number = ((location['y'] + transPoint['y']) * scale) + textSize['height'] / 2;
position = filter(shapePoint[midIndex as number], startY, endY);
if (!isPoint && position.length > 5 && (shapeData['geometry']['type'] !== 'MultiPolygon') &&
(shapeData['type'] !== 'MultiPolygon')) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const location1: any = findMidPointOfPolygon(position, projectionType, layer.geometryType);
if (zoomLabelsPosition && scaleZoomValue > 1 && !this.maps.zoomNotApplied && eventargs.template === '') {
location1['x'] = ((this.maps.zoomLabelPositions[index as number]['location']['x'] + zoomTransPoint['x']) * scale);
location1['y'] = ((this.maps.zoomLabelPositions[index as number]['location']['y'] + zoomTransPoint['y']) * scale);
}
locationX = location1['x'];
location['x'] = location1['x'];
width = zoomLabelsPosition && scaleZoomValue > 1 && !this.maps.zoomNotApplied
&& this.maps.zoomShapeCollection.length > index ? (this.maps.dataLabelShape[index as number]) * scale :
((location1['rightMax']['x'] - location1['leftMax']['x']) * scale) > 0 ?
((location1['rightMax']['x'] - location1['leftMax']['x']) * scale) : width;
}
const xpositionEnds: number = ((location['x'] + transPoint['x']) * scale) + textSize['width'] / 2;
const xpositionStart: number = ((location['x'] + transPoint['x']) * scale) - textSize['width'] / 2;
this.value[index as number] = { rightWidth: xpositionEnds, leftWidth: xpositionStart, heightTop: start, heightBottom: end };
let labelElement: HTMLElement;
if (eventargs.template !== '') {
templateFn = getTemplateFunction(eventargs.template, this.maps);
const templateElement: Element = templateFn ? templateFn(!isNullOrUndefined(datasrcObj) ?
datasrcObj : shapeData['properties'], this.maps, eventargs.template, this.maps.element.id + '_LabelTemplate', false) : document.createElement('div');
templateElement.innerHTML = !templateFn ? eventargs.template as string : '';
labelElement = <HTMLElement>convertElementFromLabel(
templateElement, labelId, !isNullOrUndefined(datasrcObj) ? datasrcObj : shapeData['properties']);
if (this.maps.isTileMap) {
labelElement.style.left = (((location['x'] + transPoint['x']) * scale) - (textSize['width'] / 2)) + 'px';
labelElement.style.top = (((location['y'] + transPoint['y']) * scale) - textSize['height']) + 'px';
} else {
labelElement.style.left = ((Math.abs(this.maps.baseMapRectBounds['min']['x'] - location['x'])) * scale) + labelArgs.offsetX + 'px';
labelElement.style.top = ((Math.abs(this.maps.baseMapRectBounds['min']['y'] - location['y'])) * scale) + labelArgs.offsetY + 'px';
}
labelTemplateElement.appendChild(labelElement);
} else {
const smartLabelMode: string = !isNullOrUndefined(dataLabelSettings.smartLabelMode) ? dataLabelSettings.smartLabelMode.toString() : 'None';
if (smartLabelMode === 'Trim') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const textType: any = typeof text === 'number' ? (text as any).toString() : text;
trimmedLable = textTrim(width, textType, style, null, true);
elementSize = measureTextElement(trimmedLable, style);
options = new TextOption(labelId, textLocation.x, textLocation.y, 'middle', trimmedLable, '', '');
}
if (smartLabelMode === 'None') {
options = new TextOption(labelId, (textLocation.x), textLocation.y, 'middle', text, '', '');
}
if (smartLabelMode === 'Hide') {
text = (width >= textSize['width']) ? text : '';
options = new TextOption(labelId, (textLocation.x), (textLocation.y), 'middle', text, '', '');
}
if (!isNullOrUndefined(options)) {
text = options['text'] as string;
}
const intersectionAction: string = !isNullOrUndefined(dataLabelSettings.intersectionAction) ? dataLabelSettings.intersectionAction.toString() : 'None';
if (intersectionAction === 'Hide') {
for (let i: number = 0; i < intersect.length; i++) {
if (!isNullOrUndefined(intersect[i as number])) {
if (!(this.value[index as number]['leftWidth'] > intersect[i as number]['rightWidth']
|| this.value[index as number]['rightWidth'] < intersect[i as number]['leftWidth']
|| this.value[index as number]['heightTop'] > intersect[i as number]['heightBottom']
|| this.value[index as number]['heightBottom'] < intersect[i as number]['heightTop'])) {
text = '';
break;
}
}
}
intersect.push(this.value[index as number]);
options = new TextOption(labelId, textLocation.x, textLocation.y, 'middle', text, '', '');
}
let difference: number;
if (intersectionAction === 'Trim') {
for (let j: number = 0; j < intersect.length; j++) {
if (!isNullOrUndefined(intersect[j as number])) {
if (intersect[j as number]['rightWidth'] < this.value[index as number]['leftWidth']
|| intersect[j as number]['leftWidth'] > this.value[index as number]['rightWidth']
|| intersect[j as number]['heightBottom'] < this.value[index as number]['heightTop']
|| intersect[j as number]['heightTop'] > this.value[index as number]['heightBottom']) {
trimmedLable = text;
difference = 0;
} else {
if (this.value[index as number]['leftWidth'] > intersect[j as number]['leftWidth']) {
width = intersect[j as number]['rightWidth'] - this.value[index as number]['leftWidth'];
difference = width - (this.value[index as number]['rightWidth'] - this.value[index as number]['leftWidth']);
trimmedLable = textTrim(difference, text, style, null, true);
break;
}
if (this.value[index as number]['leftWidth'] < intersect[j as number]['leftWidth']) {
width = this.value[index as number]['rightWidth'] - intersect[j as number]['leftWidth'];
difference = Math.abs(width - (this.value[index as number]['rightWidth'] - this.value[index as number]['leftWidth']));
trimmedLable = textTrim(difference, text, style, null, true);
break;
}
}
}
}
elementSize = measureTextElement(trimmedLable, style);
intersect.push(this.value[index as number]);
options = new TextOption(labelId, textLocation.x, (textLocation.y), 'middle', trimmedLable, '', '');
}
if (intersectionAction === 'None') {
options = new TextOption(labelId, (textLocation.x), (textLocation.y), 'middle', text, '', '');
}
if (trimmedLable.length > 1) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const border: any = eventargs.border;
if (border['width'] > 1) {
const fill: string = eventargs.fill;
const opacity: number = dataLabelSettings.opacity;
const rx: number = dataLabelSettings.rx;
const ry: number = dataLabelSettings.ry;
let x : number ; let y : number; const padding : number = 5;
if (zoomLabelsPosition && scaleZoomValue > 1 && !this.maps.zoomNotApplied) {
x = ((location['x'] ) ) - textSize['width'] / 2;
y = ((location['y'] ) ) - textSize['height'] / 2 - padding;
} else {
x = ((location['x'] + transPoint['x']) * scale) - textSize['width'] / 2;
y = ((location['y'] + transPoint['y'] ) * scale) - textSize['height'] / 2;
}
border.opacity = isNullOrUndefined(border.opacity) ? opacity : border.opacity;
const rectOptions: RectOption = new RectOption(
this.maps.element.id + '_LayerIndex_' + layerIndex + '_shapeIndex_' + index + '_rectIndex_' + index,
fill, border, opacity, new Rect((x + labelArgs.offsetX), (y + labelArgs.offsetY), textSize['width'], textSize['height']), rx, ry
);
rect = this.maps.renderer.drawRectangle(rectOptions) as SVGRectElement;
rect.setAttribute('visibility', layer.dataLabelSettings.animationDuration > 0 || animationMode === 'Enable' ? 'hidden' : 'visibile');
group.appendChild(rect);
}
}
element = renderTextElement(options, style, style.color || this.maps.themeStyle.dataLabelFontColor, group);
element.setAttribute('aria-label', text);
element.setAttribute('role', 'region');
element.setAttribute('visibility', layer.dataLabelSettings.animationDuration > 0 || animationMode === 'Enable' ? 'hidden' : 'visibile');
if (zoomLabelsPosition && scaleZoomValue > 1 && !this.maps.zoomNotApplied){
element.setAttribute('transform', 'translate( ' + ((location['x'] + labelArgs.offsetX) ) + ' '
+ (((location['y'] + labelArgs.offsetY) ) ) + ' )');
location['x'] = locationX;
location['y'] = locationY;
} else {
element.setAttribute('transform', 'translate( ' + (((location['x'] + transPoint.x) * scale) + labelArgs.offsetX) + ' '
+ ((((location['y'] + transPoint.y) * scale) + (elementSize.height / 2)) + labelArgs.offsetY) + ' )');
}
group.appendChild(element);
}
this.dataLabelCollections.push({
location: { x: location['x'] + labelArgs.offsetX, y: location['y'] + labelArgs.offsetY },
element: isNullOrUndefined(labelElement) ? element : labelElement,
layerIndex: layerIndex,
shapeIndex: sublayerIndexLabel ? oldIndex : index,
labelIndex: sublayerIndexLabel ? oldIndex : index,
dataLabelText: dataLabelText
});
if (labelTemplateElement.childElementCount > 0 && !this.maps.element.contains(labelTemplateElement)) {
document.getElementById(this.maps.element.id + '_Secondary_Element').appendChild(labelTemplateElement);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.maps as any).renderReactTemplates();
}
if (layer.dataLabelSettings.animationDuration > 0 || animationMode === 'Enable') {
if (!isNullOrUndefined(element)) {
this.datalabelAnimate(element as HTMLElement, dataLabelSettings.animationDuration, style.opacity, false);
if (!isNullOrUndefined(rect)) {
this.datalabelAnimate(rect as HTMLElement, dataLabelSettings.animationDuration,
dataLabelSettings.opacity, true);
}
}
}
});
}
}
private datalabelAnimate(element: HTMLElement, duration: number, opacity: number, isRect: boolean): void {
let height: number = 0;
new Animation({}).animate(<HTMLElement>element, {
duration: (duration === 0 && animationMode === 'Enable') ? 1000 : duration,
delay: 0,
progress: (args: AnimationOptions) => {
if (args.timeStamp > args.delay) {
height = ((args.timeStamp - args.delay) / args.duration);
element.setAttribute('style', 'user-select: none; visibility: visible;');
element.setAttribute(isRect ? 'fill-opacity' : 'opacity', (opacity * height).toString());
}
},
end: () => {
element.style.visibility = 'visible';
element.setAttribute(isRect ? 'fill-opacity' : 'opacity', opacity.toString());
}
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private getPoint(shapes: any[], points: MapLocation[]): MapLocation[] {
if (shapes['type'] === 'MultiLineString') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Array.prototype.forEach.call(shapes, (current: any) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Array.prototype.forEach.call(current, (shape: any) => {
points.push(new Point(shape['point']['x'], shape['point']['y']));
});
});
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Array.prototype.forEach.call(shapes, (current: any) => {
points.push(new Point(current['point']['x'], current['point']['y']));
});
}
return points;
}
/**
* Get module name.
*
* @returns {string} - Returns the module name.
*/
protected getModuleName(): string {
return 'DataLabel';
}
/**
* @returns {void}
* @private
*/
public destroy(): void {
this.dataLabelCollections = [];
this.value = null;
this.maps = null;
}
}