@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
501 lines (492 loc) • 28.3 kB
text/typescript
import { Maps, ITooltipRenderEventArgs, tooltipRender, MapsTooltipOption, ITooltipRenderCompleteEventArgs, FontModel, PolygonTooltipSettingsModel, PolygonSettingModel, GeoPosition, BorderModel } from '../index';
import { Tooltip, TooltipTheme } from '@syncfusion/ej2-svg-base';
import { createElement, Browser, isNullOrUndefined, extend, remove } from '@syncfusion/ej2-base';
import { TooltipSettingsModel, LayerSettings, MarkerSettingsModel, BubbleSettingsModel } from '../index';
import { MapLocation, getMousePosition, Internalize, checkPropertyPath, getValueFromObject,
formatValue, convertStringToValue, removeElement} from '../utils/helper';
/**
* Map Tooltip
*/
export class MapsTooltip {
private maps: Maps;
/**
* @private
*/
public svgTooltip: Tooltip;
private isTouch: boolean;
private tooltipId: string;
private tooltipTimer: number;
/**
* @private
*/
public tooltipTargetID: string;
constructor(maps: Maps) {
this.maps = maps;
this.tooltipId = this.maps.element.id + '_mapsTooltip';
this.addEventListener();
}
/**
* @param {PointerEvent} e - Specifies the event.
* @returns {void}
* @private
*/
public renderTooltip(e: PointerEvent): void {
let pageX: number; let pageY: number;
let target: Element; let touchArg: TouchEvent;
let tooltipArgs: ITooltipRenderEventArgs;
if (e.type.indexOf('touch') !== - 1) {
this.isTouch = true;
touchArg = <TouchEvent & PointerEvent>e;
pageX = touchArg.changedTouches[0].pageX;
pageY = touchArg.changedTouches[0].pageY;
target = <Element>touchArg.target;
} else {
this.isTouch = e.pointerType === 'touch';
pageX = e.pageX;
pageY = e.pageY;
target = <Element>e.target;
}
if (target.id.indexOf(this.maps.element.id) === -1) {
const ancestor: Element = target.closest('.' + this.maps.element.id + '_marker_template_element');
if (!isNullOrUndefined(ancestor) && ancestor.id.indexOf('_MarkerIndex_') > -1) {
target = ancestor;
}
}
let option: TooltipSettingsModel;
let polygonTooltipOption : PolygonTooltipSettingsModel;
let currentData: string = '';
const targetId: string = target.id;
let tooltipEle: HTMLElement;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let templateData: any = [];
let keyString: string;
let index: number = targetId.indexOf('_LayerIndex_') > -1 && parseFloat(targetId.split('_LayerIndex_')[1].split('_')[0]);
const layer: LayerSettings = <LayerSettings>this.maps.layersCollection[index as number];
const tooltipContent: string[] = []; let markerFill: string;
const location: MapLocation = getMousePosition(pageX, pageY, this.maps.svgObject);
this.tooltipTargetID = targetId;
let polygonTextStyle: FontModel;
let polygonFill : string;
let polygon : PolygonSettingModel;
let latitude: number = null;
let longitude: number = null;
const latLongValue: GeoPosition = this.maps.getClickLocation(targetId, e.pageX, e.pageY, (target as HTMLElement), e['layerX'], e['layerY'], 'tooltip');
if (!isNullOrUndefined(latLongValue)) {
latitude = latLongValue.latitude;
longitude = latLongValue.longitude;
}
const isPolygon: boolean = targetId.indexOf('_PolygonIndex_') > -1;
const istooltipRender: boolean = (targetId.indexOf('_shapeIndex_') > -1)
|| (targetId.indexOf('_MarkerIndex_') > -1) || (targetId.indexOf('_BubbleIndex_') > -1)
|| (targetId.indexOf('_PolygonIndex_') > -1);
if (istooltipRender && this.maps.markerDragArgument === null) {
if (targetId.indexOf('_PolygonIndex_') > -1) {
const polygonIndex: number = parseInt(targetId.split('_PolygonIndex_')[1].split('_')[0], 10);
polygonTooltipOption = layer.polygonSettings.tooltipSettings;
polygon = layer.polygonSettings.polygons[polygonIndex as number];
polygonTextStyle = polygonTooltipOption.textStyle;
polygonFill = polygonTooltipOption.fill;
tooltipContent.push(polygon.tooltipText);
} else if (targetId.indexOf('_shapeIndex_') > -1) {
option = layer.tooltipSettings;
const shape: number = parseInt(targetId.split('_shapeIndex_')[1].split('_')[0], 10);
if (isNullOrUndefined(layer.layerData) || isNullOrUndefined(layer.layerData[shape as number])) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const value: any = layer.layerData[shape as number]['property']; let isShape: boolean = false;
const properties: string[] = (Object.prototype.toString.call(layer.shapePropertyPath) === '[object Array]' ?
layer.shapePropertyPath : [layer.shapePropertyPath]) as string[];
if (!isNullOrUndefined(properties)) {
for (let k: number = 0; k < properties.length; k++) {
if (!isNullOrUndefined(layer.dataSource) && !isNullOrUndefined(layer.shapeDataPath)) {
for (let i: number = 0; i < layer['dataSource']['length']; i++) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any[] = layer.dataSource[i as number];
const dataPath: string = (layer.shapeDataPath.indexOf('.') > -1) ?
(getValueFromObject(data, layer.shapeDataPath)) : data[layer.shapeDataPath];
const dataPathValue: string = !isNullOrUndefined(dataPath) && isNaN(data[layer.shapeDataPath])
? dataPath.toLowerCase() : dataPath;
const propertyValue: string = !isNullOrUndefined(value[properties[k as number]])
&& isNaN(value[properties[k as number]]) ? value[properties[k as number]].toLowerCase() :
value[properties[k as number]];
if (dataPathValue === propertyValue) {
isShape = true; index = i;
k = properties.length;
break;
}
}
}
}
index = isShape ? index : null;
if (layer['dataSource'] && layer['dataSource']['length'] > 0) {
if (!isNullOrUndefined(layer.dataSource[index as number])) {
templateData = JSON.parse(JSON.stringify(layer.dataSource[index as number]));
for (keyString in value) {
// eslint-disable-next-line no-prototype-builtins
if (!templateData.hasOwnProperty(keyString)) {
templateData[keyString as string] = value[keyString as string];
}
}
}
} else {
templateData = value;
}
}
if (option.visible && ((!isNullOrUndefined(index) && !isNaN(index)) || (!isNullOrUndefined(value)))) {
if (layer.tooltipSettings.format) {
currentData = this.formatter(layer.tooltipSettings.format, templateData);
} else {
const shapePath: string = checkPropertyPath(layer.shapeDataPath, layer.shapePropertyPath, value);
currentData = (!isNullOrUndefined(layer.dataSource) && !isNullOrUndefined(index)) ?
formatValue(((option.valuePath.indexOf('.') > -1) ?
(getValueFromObject(layer.dataSource[index as number], option.valuePath)) :
layer.dataSource[index as number][option.valuePath]),
this.maps) : value[shapePath as string];
if (isNullOrUndefined(currentData) && !isNullOrUndefined(option.valuePath)) {
currentData = (option.valuePath.indexOf('.') > -1) ?
(getValueFromObject(value, option.valuePath)) : value[option.valuePath];
}
}
}
//location.y = this.template(option, location);
} else if (targetId.indexOf('_MarkerIndex_') > -1) {
const markerIdex: number = parseInt(targetId.split('_MarkerIndex_')[1].split('_')[0], 10);
const dataIndex: number = parseInt(targetId.split('_MarkerIndex_')[1].split('_')[2], 10);
const marker: MarkerSettingsModel = layer.markerSettings[markerIdex as number];
option = marker.tooltipSettings;
templateData = marker.dataSource[dataIndex as number];
if (option.visible && !isNaN(markerIdex)) {
if (marker.tooltipSettings.format) {
currentData = this.formatter(marker.tooltipSettings.format, marker.dataSource[dataIndex as number]);
} else {
if (typeof marker.template !== 'function' && marker.template && !marker.tooltipSettings.valuePath) {
currentData = marker.template.split('>')[1].split('<')[0];
} else {
if (!isNullOrUndefined(marker.tooltipSettings.valuePath)) {
currentData =
formatValue(((marker.tooltipSettings.valuePath. indexOf('.') > -1) ?
(getValueFromObject(marker.dataSource[dataIndex as number], marker.tooltipSettings.valuePath)) :
marker.dataSource[dataIndex as number][marker.tooltipSettings.valuePath]),
this.maps
) as string;
}
}
}
}
//location.y = this.template(option, location);
} else if (targetId.indexOf('_BubbleIndex_') > -1) {
const bubbleIndex: number = parseInt(targetId.split('_BubbleIndex_')[1].split('_')[0], 10);
const dataIndex: number = parseInt(targetId.split('_BubbleIndex_')[1].split('_')[2], 10);
const bubble: BubbleSettingsModel = layer.bubbleSettings[bubbleIndex as number];
option = bubble.tooltipSettings;
templateData = bubble.dataSource[dataIndex as number];
if (option.visible && !isNaN(dataIndex)) {
if (bubble.tooltipSettings.format) {
currentData = this.formatter(bubble.tooltipSettings.format, bubble.dataSource[dataIndex as number]);
} else {
if (!isNullOrUndefined(bubble.tooltipSettings.valuePath)) {
currentData =
formatValue(((bubble.tooltipSettings.valuePath.indexOf('.') > -1) ?
(getValueFromObject(bubble.dataSource[dataIndex as number], bubble.tooltipSettings.valuePath)) :
bubble.dataSource[dataIndex as number][bubble.tooltipSettings.valuePath]),
this.maps
) as string;
}
}
}
//location.y = this.template(option, location);
}
if (isPolygon ? polygonTooltipOption.visible : option.visible) {
if (document.getElementById(this.tooltipId)) {
tooltipEle = document.getElementById(this.tooltipId);
} else {
tooltipEle = createElement('div', {
id: this.maps.element.id + '_mapsTooltip',
className: 'EJ2-maps-Tooltip'
});
if (isNullOrUndefined(isPolygon ? polygon.tooltipTemplate : option.template) || (isPolygon ? polygon.tooltipTemplate === '' : option.template === '') || this.maps.tooltipDisplayMode === 'MouseMove') {
tooltipEle.style.cssText = 'position: absolute;pointer-events:none;';
} else {
tooltipEle.style.position = 'absolute';
}
document.getElementById(this.maps.element.id + '_Secondary_Element').appendChild(tooltipEle);
}
// eslint-disable-next-line no-constant-condition
if (typeof (isPolygon ? polygon.tooltipTemplate !== 'function' : option.template !== 'function') && (isPolygon ? polygon.tooltipTemplate !== null : option.template !== null) && Object.keys(typeof (isPolygon ? polygon.tooltipTemplate === 'object' : option.template === 'object') ? (isPolygon ? polygon.tooltipTemplate : option.template) : {}).length === 1) {
if (isPolygon) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
polygon.tooltipTemplate = polygon.tooltipTemplate[Object.keys(polygon.tooltipTemplate as any)[0]];
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
option.template = option.template[Object.keys(option.template as any)[0]];
}
}
templateData = this.setTooltipContent(option, templateData);
const tooltipTextStyle: FontModel = {
// eslint-disable-next-line max-len
color: isPolygon ? polygonTextStyle.color : option.textStyle.color, fontFamily: isPolygon ? polygonTextStyle.fontFamily : option.textStyle.fontFamily, fontStyle: isPolygon ? polygonTextStyle.fontStyle : option.textStyle.fontStyle,
// eslint-disable-next-line max-len
fontWeight: isPolygon ? polygonTextStyle.fontWeight : option.textStyle.fontWeight, opacity: isPolygon ? polygonTextStyle.opacity : option.textStyle.opacity, size: isPolygon ? polygonTextStyle.size : option.textStyle.size
};
const tooltipOption : MapsTooltipOption = {
location: location, text: tooltipContent, data: templateData,
textStyle: tooltipTextStyle,
template: isPolygon ? polygon.tooltipTemplate : option.template
};
tooltipArgs = {
cancel: false, name: tooltipRender,
options: tooltipOption,
fill: isPolygon ? polygonFill : option.fill,
maps: this.maps, latitude: latitude, longitude: longitude,
element: target, eventArgs: e, content: isPolygon ? (!isNullOrUndefined(polygon.tooltipText) ? polygon.tooltipText : '') :
!isNullOrUndefined(currentData) ? currentData.toString() : ''
};
if (tooltipArgs.content !== '' || tooltipArgs.options['template'] !== '') {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
this.maps.trigger(tooltipRender, tooltipArgs, (args: ITooltipRenderEventArgs) => {
if (!tooltipArgs.cancel && !isNullOrUndefined(currentData) &&
(targetId.indexOf('_cluster_') === -1 && targetId.indexOf('_dataLabel_') === -1)) {
this.maps['isProtectedOnChange'] = true;
tooltipArgs.options['textStyle']['size'] = tooltipArgs.options['textStyle']['size']
|| this.maps.themeStyle.fontSize;
tooltipArgs.options['textStyle']['color'] = tooltipArgs.options['textStyle']['color']
|| this.maps.themeStyle.tooltipFontColor;
tooltipArgs.options['textStyle']['fontFamily'] = tooltipArgs.options['textStyle']['fontFamily']
|| this.maps.themeStyle.fontFamily;
tooltipArgs.options['textStyle']['fontWeight'] = tooltipArgs.options['textStyle']['fontWeight']
|| this.maps.themeStyle.fontWeight;
tooltipArgs.options['textStyle']['opacity'] = tooltipArgs.options['textStyle']['opacity']
|| this.maps.themeStyle.tooltipTextOpacity;
const borderObject: BorderModel = isPolygon ? {
color: polygonTooltipOption.border.color ||
this.maps.themeStyle.tooltipBorderColor, width: polygonTooltipOption.border.width,
opacity: polygonTooltipOption.border.opacity
} : {
color: option.border.color ||
this.maps.themeStyle.tooltipBorderColor, width: option.border.width, opacity: option.border.opacity
};
if (tooltipArgs.cancel) {
this.svgTooltip = new Tooltip({
theme: this.maps.theme as TooltipTheme,
enable: true,
header: '',
data: option['data'],
template: option['template'],
content: tooltipArgs.content.toString() !== currentData.toString() ? [tooltipArgs.content.toString()] :
[currentData.toString()],
shapes: [],
location: option['location'],
palette: [markerFill],
areaBounds: this.maps.mapAreaRect,
textStyle: option['textStyle'],
availableSize: this.maps.availableSize,
fill: option.fill || this.maps.themeStyle.tooltipFillColor,
enableShadow: true,
border: borderObject
});
} else {
this.svgTooltip = new Tooltip({
theme: this.maps.theme as TooltipTheme,
enable: true,
header: '',
data: tooltipArgs.options['data'],
template: tooltipArgs.options['template'],
content: tooltipArgs.content.toString() !== currentData.toString() ? [tooltipArgs.content.toString()] :
[currentData.toString()],
shapes: [],
location: tooltipArgs.options['location'],
palette: [markerFill],
areaBounds: this.maps.mapAreaRect,
textStyle: tooltipArgs.options['textStyle'],
availableSize: this.maps.availableSize,
fill: tooltipArgs.fill || this.maps.themeStyle.tooltipFillColor,
enableShadow: true,
border: borderObject
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((this.maps as any).isVue || (this.maps as any).isVue3) {
this.svgTooltip.controlInstance = this.maps;
}
this.svgTooltip.opacity = this.maps.themeStyle.tooltipFillOpacity || this.svgTooltip.opacity;
this.svgTooltip.appendTo(tooltipEle);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.maps as any).renderReactTemplates();
if (this.maps.isDevice) {
let timer: number = targetId.indexOf('_MarkerIndex_') > -1 || targetId.indexOf('_BubbleIndex_') > -1
|| targetId.indexOf('_shapeIndex_') > -1 ? option.duration : polygonTooltipOption.duration;
timer = (!isNullOrUndefined(timer) && timer > 0) ? timer : (timer < 0) ? 2000 : null;
if (timer !== null) {
clearTimeout(this.tooltipTimer);
this.tooltipTimer = setTimeout(this.removeTooltip.bind(this), timer);
}
}
} else {
this.clearTooltip(<HTMLElement>e.target);
}
});
} else {
this.clearTooltip(<HTMLElement>e.target);
}
if (this.svgTooltip) {
this.maps.trigger('tooltipRenderComplete', {
cancel: false, name: 'tooltipRenderComplete', maps: this.maps, options: tooltipOption,
element: this.svgTooltip.element
} as ITooltipRenderCompleteEventArgs);
}
if (this.svgTooltip) {
this.maps.trigger('tooltipRenderComplete', {
cancel: false, name: 'tooltipRenderComplete', maps: this.maps, options: tooltipOption, element: this.svgTooltip.element
} as ITooltipRenderCompleteEventArgs);
} else {
this.clearTooltip(<HTMLElement>e.target);
}
} else {
this.clearTooltip(<HTMLElement>e.target);
}
} else {
const tooltipElement: Element = (e.target as HTMLElement).closest('#' + this.maps.element.id + '_mapsTooltipparent_template');
if (isNullOrUndefined(tooltipElement)) {
this.clearTooltip(<HTMLElement>e.target);
}
}
}
/**
* To get content for the current toolitp.
*
* @param {TooltipSettingsModel} options - Specifies the options for rendering tooltip
* @param {any} templateData - Specifies the template data
* @returns {any} - Returns the local data
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private setTooltipContent(options: TooltipSettingsModel, templateData: any): any {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let localData: any = extend({}, templateData, null, true);
if (this.maps.format && !isNaN(Number(localData[options.valuePath]))) {
localData[options.valuePath] = Internalize(this.maps, Number(localData[options.valuePath]));
} else {
localData = Object.keys(localData).length ? localData : undefined;
}
return localData;
}
/*private template(tooltip: TooltipSettingsModel, location: MapLocation): number {
location.y = (tooltip.template) ? location.y + 10 : location.y;
return location.y;
}*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private formatter(format: string, data: any = {}): string {
const keys: string[] = Object.keys(data);
for (const key of keys) {
format = (typeof data[key as string] === 'object') ? convertStringToValue('', format, data, this.maps) :
format.split('${' + key + '}').join(formatValue(data[key as string], this.maps));
}
return format;
}
/**
* Handles the mouse up.
*
* @param {PointerEvent} e - Specifies the event
* @returns {void}
* @private
*/
public mouseUpHandler(e: PointerEvent): void {
if (!isNullOrUndefined(this.maps)) {
this.renderTooltip(e);
if (this.maps.tooltipDisplayMode === 'MouseMove') {
clearTimeout(this.tooltipTimer);
this.tooltipTimer = setTimeout(this.removeTooltip.bind(this), 2000);
}
}
}
/**
* Removes the tooltip.
*
* @returns {boolean} - Returns the boolean whether tooltip is removed or not.
* @private
*/
public removeTooltip(): boolean {
let isTooltipRemoved: boolean = false;
if (document.getElementsByClassName('EJ2-maps-Tooltip').length > 0) {
remove(document.getElementsByClassName('EJ2-maps-Tooltip')[0]);
isTooltipRemoved = true;
}
return isTooltipRemoved;
}
private clearTooltip(element: HTMLElement): void {
// eslint-disable-next-line @typescript-eslint/tslint/config
const tooltipElement = element.closest('#' + this.maps.element.id + '_mapsTooltipparent_template');
if (isNullOrUndefined(tooltipElement)) {
const isTooltipRemoved: boolean = this.removeTooltip();
if (isTooltipRemoved) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.maps as any).clearTemplate();
}
}
}
/**
* To bind events for tooltip module.
*
* @returns {void}
* @private
*/
public addEventListener(): void {
if (this.maps.isDestroyed) {
return;
}
if (this.maps.tooltipDisplayMode === 'DoubleClick') {
this.maps.on('dblclick', this.renderTooltip, this);
} else if (this.maps.tooltipDisplayMode === 'Click') {
this.maps.on(Browser.touchEndEvent, this.mouseUpHandler, this);
} else {
this.maps.on(Browser.touchMoveEvent, this.renderTooltip, this);
}
this.maps.on(Browser.touchCancelEvent, this.removeTooltip, this);
this.maps.element.addEventListener('contextmenu', this.removeTooltip);
}
/**
* Removes the event listeners.
*
* @returns {void}
* @private
*/
public removeEventListener(): void {
if (this.maps.isDestroyed) {
return;
}
if (this.maps.tooltipDisplayMode === 'DoubleClick') {
this.maps.off('dblclick', this.renderTooltip);
} else if (this.maps.tooltipDisplayMode === 'Click') {
this.maps.off(Browser.touchEndEvent, this.mouseUpHandler);
} else {
this.maps.off(Browser.touchMoveEvent, this.renderTooltip);
}
this.maps.off(Browser.touchCancelEvent, this.removeTooltip);
this.maps.element.removeEventListener('contextmenu', this.removeTooltip);
}
/**
* Get module name.
*
* @returns {string} Returns the module name
*/
protected getModuleName(): string {
return 'MapsTooltip';
}
/**
* To destroy the tooltip.
*
* @returns {void}
* @private
*/
public destroy(): void {
if (!isNullOrUndefined(this.svgTooltip)) {
this.svgTooltip.destroy();
this.svgTooltip.controlInstance = null;
removeElement(this.maps.element.id + '_mapsTooltip');
}
this.svgTooltip = null;
if (!(this.maps as any).refreshing) {
this.maps = null;
}
}
}