UNPKG

@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
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; } } }