thingsboard-aplcache

UI: Widgets. Improve image map.

9/26/2017 11:59:53 AM

Details

diff --git a/application/src/main/data/json/system/widget_bundles/maps.json b/application/src/main/data/json/system/widget_bundles/maps.json
index 46596d0..8c2d0e5 100644
--- a/application/src/main/data/json/system/widget_bundles/maps.json
+++ b/application/src/main/data/json/system/widget_bundles/maps.json
@@ -78,7 +78,7 @@
         "sizeY": 6.5,
         "resources": [],
         "templateHtml": "",
-        "templateCss": ".error {\n    color: red;\n}\n.tb-labels {\n  color: #222;\n  font: 12px/1.5 \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n  text-align: center;\n  width: 200px;\n  white-space: nowrap;\n}",
+        "templateCss": ".leaflet-zoom-box {\n\tz-index: 9;\n}\n\n.leaflet-pane         { z-index: 4; }\n\n.leaflet-tile-pane    { z-index: 2; }\n.leaflet-overlay-pane { z-index: 4; }\n.leaflet-shadow-pane  { z-index: 5; }\n.leaflet-marker-pane  { z-index: 6; }\n.leaflet-tooltip-pane   { z-index: 7; }\n.leaflet-popup-pane   { z-index: 8; }\n\n.leaflet-map-pane canvas { z-index: 1; }\n.leaflet-map-pane svg    { z-index: 2; }\n\n.leaflet-control {\n\tz-index: 9;\n}\n.leaflet-top,\n.leaflet-bottom {\n\tz-index: 11;\n}\n\n.tb-marker-label {\n    border: none;\n    background: none;\n    box-shadow: none;\n}\n\n.tb-marker-label:before {\n    border: none;\n    background: none;\n}\n",
         "controllerScript": "self.onInit = function() {\n    self.ctx.map = new TbMapWidgetV2('image-map', false, self.ctx);\n}\n\nself.onDataUpdated = function() {\n    self.ctx.map.update();\n}\n\nself.onResize = function() {\n    self.ctx.map.resize();\n}\n\nself.getSettingsSchema = function() {\n    return TbMapWidgetV2.settingsSchema('image-map');\n}\n\nself.getDataKeySettingsSchema = function() {\n    return TbMapWidgetV2.dataKeySettingsSchema('image-map');\n}\n\nself.actionSources = function() {\n    return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n}\n",
         "settingsSchema": "{}",
         "dataKeySettingsSchema": "{}\n",
diff --git a/ui/src/app/widget/lib/google-map.js b/ui/src/app/widget/lib/google-map.js
index 4b0ea2f..4dc2dc0 100644
--- a/ui/src/app/widget/lib/google-map.js
+++ b/ui/src/app/widget/lib/google-map.js
@@ -224,7 +224,7 @@ export default class TbGoogleMap {
         }
 
         if (settings.displayTooltip) {
-            this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, markerArgs);
+            this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, settings.autocloseTooltip, markerArgs);
         }
 
         if (onClickListener) {
@@ -241,11 +241,17 @@ export default class TbGoogleMap {
     /* eslint-enable no-undef */
 
     /* eslint-disable no-undef */
-    createTooltip(marker, pattern, replaceInfo, markerArgs) {
+    createTooltip(marker, pattern, replaceInfo, autoClose, markerArgs) {
         var popup = new google.maps.InfoWindow({
             content: ''
         });
+        var map = this;
         marker.addListener('click', function() {
+            if (autoClose) {
+                map.tooltips.forEach((tooltip) => {
+                    tooltip.popup.close();
+                });
+            }
             popup.open(this.map, marker);
         });
         this.tooltips.push( {
diff --git a/ui/src/app/widget/lib/image-map.js b/ui/src/app/widget/lib/image-map.js
index e3b5162..29e40c3 100644
--- a/ui/src/app/widget/lib/image-map.js
+++ b/ui/src/app/widget/lib/image-map.js
@@ -14,15 +14,10 @@
  * limitations under the License.
  */
 
-import 'tooltipster/dist/css/tooltipster.bundle.min.css';
-import 'tooltipster/dist/js/tooltipster.bundle.min.js';
-import 'tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-shadow.min.css';
+import 'leaflet/dist/leaflet.css';
+import L from 'leaflet/dist/leaflet';
 
-import './image-map.scss';
-
-const pinShape = '<path id="pin" d="m 12.033721,23.509909 c 0.165665,-3.220958 1.940547,-8.45243 4.512974,-11.745035 1.401507,-1.7940561 2.046337,-3.5425327 2.046337,-4.6032909 0,-3.6844827 -2.951858,-6.67149197 -6.592948,-6.67149197 l -1.68e-4,0 c -3.6412584,0 -6.5929483,2.98700927 -6.5929483,6.67149197 0,1.0607582 0.6448307,2.8092348 2.0463367,4.6032909 2.5724276,3.292605 4.3471416,8.524077 4.5129736,11.745035 l 0.06745,0 z" style="fill:#f2756a;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-opacity:1"/>';
-const circleShape = '<circle id="circle" fill-rule="evenodd" cy="6.9234" cx="12" clip-rule="evenodd" r="1.5"/>';
-const pinSvg = `<svg class="image-map-pin-image" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">${pinShape}${circleShape}</svg>`;
+const maxZoom = 4;
 
 export default class TbImageMap {
 
@@ -31,10 +26,9 @@ export default class TbImageMap {
         this.ctx = ctx;
         this.tooltips = [];
 
-        $containerElement.append('<div id="image-map-container"><div id="image-map"></div></div>');
+        this.$containerElement = $containerElement;
+        this.$containerElement.css('background', '#fff');
 
-        this.imageMapContainer = angular.element('#image-map-container', $containerElement);
-        this.imageMap = angular.element('#image-map', $containerElement);
         this.aspect = 0;
         this.width = 0;
         this.height = 0;
@@ -108,7 +102,7 @@ export default class TbImageMap {
             if (keyData && keyData.data && keyData.data[0]) {
                 var attrValue = keyData.data[0][1];
                 if (attrValue && attrValue.length) {
-                    this.loadImage(attrValue, this.aspect > 0 ? null : this.initCallback);
+                    this.loadImage(attrValue, this.aspect > 0 ? null : this.initCallback, true);
                 }
             }
         }
@@ -117,72 +111,145 @@ export default class TbImageMap {
         }
     }
 
-    loadImage(imageUrl, initCallback) {
+    loadImage(imageUrl, initCallback, updateImage) {
         if (!imageUrl) {
             imageUrl = 'data:image/svg+xml;base64,PHN2ZyBpZD0ic3ZnMiIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAwIiB3aWR0aD0iMTAwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgdmlld0JveD0iMCAwIDEwMCAxMDAiPgogPGcgaWQ9ImxheWVyMSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtOTUyLjM2KSI+CiAgPHJlY3QgaWQ9InJlY3Q0Njg0IiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBoZWlnaHQ9Ijk5LjAxIiB3aWR0aD0iOTkuMDEiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiB5PSI5NTIuODYiIHg9Ii40OTUwNSIgc3Ryb2tlLXdpZHRoPSIuOTkwMTAiIGZpbGw9IiNlZWUiLz4KICA8dGV4dCBpZD0idGV4dDQ2ODYiIHN0eWxlPSJ3b3JkLXNwYWNpbmc6MHB4O2xldHRlci1zcGFjaW5nOjBweDt0ZXh0LWFuY2hvcjptaWRkbGU7dGV4dC1hbGlnbjpjZW50ZXIiIGZvbnQtd2VpZ2h0PSJib2xkIiB4bWw6c3BhY2U9InByZXNlcnZlIiBmb250LXNpemU9IjEwcHgiIGxpbmUtaGVpZ2h0PSIxMjUlIiB5PSI5NzAuNzI4MDkiIHg9IjQ5LjM5NjQ3NyIgZm9udC1mYW1pbHk9IlJvYm90byIgZmlsbD0iIzY2NjY2NiI+PHRzcGFuIGlkPSJ0c3BhbjQ2OTAiIHg9IjUwLjY0NjQ3NyIgeT0iOTcwLjcyODA5Ij5JbWFnZSBiYWNrZ3JvdW5kIDwvdHNwYW4+PHRzcGFuIGlkPSJ0c3BhbjQ2OTIiIHg9IjQ5LjM5NjQ3NyIgeT0iOTgzLjIyODA5Ij5pcyBub3QgY29uZmlndXJlZDwvdHNwYW4+PC90ZXh0PgogIDxyZWN0IGlkPSJyZWN0NDY5NCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgaGVpZ2h0PSIxOS4zNiIgd2lkdGg9IjY5LjM2IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgeT0iOTkyLjY4IiB4PSIxNS4zMiIgc3Ryb2tlLXdpZHRoPSIuNjM5ODYiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+Cg==';
         }
-        this.imageMap.css({backgroundImage: 'url('+imageUrl+')'});
+        this.imageUrl = imageUrl;
         var imageMap = this;
         var testImage = document.createElement('img'); // eslint-disable-line
         testImage.style.visibility = 'hidden';
         testImage.onload = function() {
             imageMap.aspect = testImage.width / testImage.height;
             document.body.removeChild(testImage); //eslint-disable-line
-            imageMap.onresize();
+            imageMap.onresize(updateImage);
             if (initCallback) {
                 setTimeout(initCallback, 0); //eslint-disable-line
-            } else {
-                imageMap.onresize();
             }
         }
         document.body.appendChild(testImage); //eslint-disable-line
         testImage.src = imageUrl;
     }
 
-    onresize() {
+    onresize(updateImage) {
         if (this.aspect > 0) {
-            var width = this.imageMapContainer.width();
+            var width = this.$containerElement.width();
             if (width > 0) {
                 var height = width / this.aspect;
-                var imageMapHeight = this.imageMapContainer.height();
+                var imageMapHeight = this.$containerElement.height();
                 if (imageMapHeight > 0 && height > imageMapHeight) {
                     height = imageMapHeight;
                     width = height * this.aspect;
                 }
+                width *= maxZoom;
+                var prevWidth = this.width;
+                var prevHeight = this.height;
                 if (this.width !== width) {
                     this.width = width;
                     this.height = width / this.aspect;
-                    this.imageMap.css({width: this.width, height: this.height});
-                    this.markers.forEach((marker) => {
-                        this.updateMarkerDimensions(marker);
-                    });
+                    if (!this.map) {
+                        this.initMap(updateImage);
+                    } else {
+                        var lastCenterPos = this.latLngToPoint(this.map.getCenter());
+                        lastCenterPos.x /= prevWidth;
+                        lastCenterPos.y /= prevHeight;
+                        this.updateBounds(updateImage, lastCenterPos);
+                        this.map.invalidateSize(true);
+                        this.updateMarkers();
+                    }
                 }
             }
         }
     }
 
+    initMap(updateImage) {
+        if (!this.map && this.aspect > 0) {
+            var center = this.pointToLatLng(this.width/2, this.height/2);
+            this.map = L.map(this.$containerElement[0], {
+                minZoom: 1,
+                maxZoom: maxZoom,
+                center: center,
+                zoom: 1,
+                crs: L.CRS.Simple,
+                attributionControl: false
+            });
+            this.updateBounds(updateImage);
+            this.updateMarkers();
+        }
+    }
+
+    pointToLatLng(x, y) {
+        return L.CRS.Simple.pointToLatLng({x:x, y:y}, maxZoom-1);
+    }
+
+    latLngToPoint(latLng) {
+        return L.CRS.Simple.latLngToPoint(latLng, maxZoom-1);
+    }
+
     inited() {
-        return this.aspect > 0 ? true : false;
+        return angular.isDefined(this.map);
     }
 
-    updateMarkerLabel(marker, settings) {
-        if (settings.showLabel) {
-            marker.labelElement.css({color: settings.labelColor});
-            marker.labelElement.html(`<b>${settings.labelText}</b>`);
+    updateBounds(updateImage, lastCenterPos) {
+        var w = this.width;
+        var h = this.height;
+        var southWest = this.pointToLatLng(0, h);
+        var northEast = this.pointToLatLng(w, 0);
+        var bounds = new L.LatLngBounds(southWest, northEast);
+
+        if (updateImage && this.imageOverlay) {
+            this.imageOverlay.remove();
+            this.imageOverlay = null;
         }
+
+        if (this.imageOverlay) {
+            this.imageOverlay.setBounds(bounds);
+        } else {
+            this.imageOverlay = L.imageOverlay(this.imageUrl, bounds).addTo(this.map);
+        }
+        var padding = 200 * maxZoom;
+        southWest = this.pointToLatLng(-padding, h + padding);
+        northEast = this.pointToLatLng(w+padding, -padding);
+        var maxBounds = new L.LatLngBounds(southWest, northEast);
+        this.map.setMaxBounds(maxBounds);
+        if (lastCenterPos) {
+            lastCenterPos.x *= w;
+            lastCenterPos.y *= h;
+            var center = this.pointToLatLng(lastCenterPos.x, lastCenterPos.y);
+            this.ctx.$scope.$injector.get('$mdUtil').nextTick(() => {
+                this.map.panTo(center, {animate: false});
+            });
+        }
+    }
+
+    updateMarkerLabel(marker, settings) {
+        marker.unbindTooltip();
+        marker.bindTooltip('<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>',
+            { className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset });
     }
 
     updateMarkerColor(marker, color) {
-        marker.pinSvgElement.css({fill: color});
+        var pinColor = color.substr(1);
+        var icon = L.icon({
+            iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + pinColor,
+            iconSize: [21, 34],
+            iconAnchor: [10, 34],
+            popupAnchor: [0, -34],
+            shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow',
+            shadowSize: [40, 37],
+            shadowAnchor: [12, 35]
+        });
+        marker.setIcon(icon);
     }
 
     updateMarkerImage(marker, settings, image, maxSize) {
-        var testImage = new Image(); // eslint-disable-line no-undef
-        var imageMap = this;
+        var testImage = document.createElement('img'); // eslint-disable-line
+        testImage.style.visibility = 'hidden';
         testImage.onload = function() {
             var width;
             var height;
             var aspect = testImage.width / testImage.height;
+            document.body.removeChild(testImage);  //eslint-disable-line
             if (aspect > 1) {
                 width = maxSize;
                 height = maxSize / aspect;
@@ -190,74 +257,79 @@ export default class TbImageMap {
                 width = maxSize * aspect;
                 height = maxSize;
             }
-            var size = Math.max(width, height);
-            marker.size = size;
-            if (marker.imgElement) {
-                marker.imgElement.remove();
+            var icon = L.icon({
+                iconUrl: image,
+                iconSize: [width, height],
+                iconAnchor: [marker.offsetX * width, marker.offsetY * height],
+                popupAnchor: [0, -height]
+            });
+            marker.setIcon(icon);
+            if (settings.showLabel) {
+                marker.unbindTooltip();
+                marker.tooltipOffset = [0, -height * marker.offsetY + 10];
+                marker.bindTooltip('<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>',
+                    { className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset });
             }
-            marker.imgElement = angular.element(`<img src="${image}" aria-label="pin" class="image-map-pin-image"/>`);
-            var left = (size - width)/2;
-            var top = (size - height)/2;
-            marker.imgElement.css({width: width, height: height, left: left, top: top});
-            marker.pinElement.append(marker.imgElement);
-            imageMap.updateMarkerDimensions(marker);
         }
+        document.body.appendChild(testImage); //eslint-disable-line
         testImage.src = image;
     }
 
-    updateMarkerDimensions(marker) {
-        var pinElement = marker.pinElement;
-        pinElement.css({width: marker.size, height: marker.size});
-        var left = marker.x * this.width - marker.size * marker.offsetX;
-        var top = marker.y * this.height - marker.size * marker.offsetY;
-        pinElement.css({left: left, top: top});
-    }
-
     createMarker(position, settings, onClickListener, markerArgs) {
-        var marker = {
-              size: 34,
-              position: position
-        };
+        var height = 34;
+        var pinColor = settings.color.substr(1);
+        var icon = L.icon({
+            iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + pinColor,
+            iconSize: [21, 34],
+            iconAnchor: [21 * settings.markerOffsetX, 34 * settings.markerOffsetY],
+            popupAnchor: [0, -34],
+            shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow',
+            shadowSize: [40, 37],
+            shadowAnchor: [12, 35]
+        });
+
         var pos = this.posFunction(position.x, position.y);
-        marker.x = pos.x;
-        marker.y = pos.y;
+        var x = pos.x * this.width;
+        var y = pos.y * this.height;
+        var location = this.pointToLatLng(x, y);
+        var marker = L.marker(location, {icon: icon}).addTo(this.map);
+        marker.position = position;
         marker.offsetX = settings.markerOffsetX;
         marker.offsetY = settings.markerOffsetY;
-        marker.pinElement = angular.element('<div class="image-map-pin"></div>');
 
         if (settings.showLabel) {
-            marker.labelElement = angular.element(`<div class="image-map-pin-title"><b>${settings.labelText}</b></div>`);
-            marker.labelElement.css({color: settings.labelColor});
-            marker.pinElement.append(marker.labelElement);
+            marker.tooltipOffset = [0, -height * marker.offsetY + 10];
+            marker.bindTooltip('<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>',
+                { className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset });
         }
 
-        marker.imgElement = angular.element(pinSvg);
-        marker.pinSvgElement = marker.imgElement.find('#pin');
-        marker.pinElement.append(marker.imgElement);
-
-        marker.pinSvgElement.css({fill: settings.color});
-
-        this.updateMarkerDimensions(marker);
-
-        this.imageMap.append(marker.pinElement);
-
         if (settings.useMarkerImage) {
             this.updateMarkerImage(marker, settings, settings.markerImage, settings.markerImageSize || 34);
         }
 
         if (settings.displayTooltip) {
-            this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, markerArgs);
+            this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, settings.autocloseTooltip, markerArgs);
         }
 
         if (onClickListener) {
-            marker.pinElement.on('click', onClickListener);
+            marker.on('click', onClickListener);
         }
-
         this.markers.push(marker);
         return marker;
     }
 
+    updateMarkers() {
+        this.markers.forEach((marker) => {
+            this.updateMarkerLocation(marker);
+        });
+    }
+
+    updateMarkerLocation(marker) {
+        this.setMarkerPosition(marker, marker.position);
+    }
+
     removeMarker(marker) {
+        this.map.removeLayer(marker);
         var index = this.markers.indexOf(marker);
         if (index > -1) {
             marker.pinElement.remove();
@@ -265,9 +337,10 @@ export default class TbImageMap {
         }
     }
 
-    createTooltip(marker, pattern, replaceInfo, markerArgs) {
-        var popup = new Popup(this.ctx, marker.pinElement);
+    createTooltip(marker, pattern, replaceInfo, autoClose, markerArgs) {
+        var popup = L.popup();
         popup.setContent('');
+        marker.bindPopup(popup, {autoClose: autoClose, closeOnClick: false});
         this.tooltips.push( {
             markerArgs: markerArgs,
             popup: popup,
@@ -302,9 +375,10 @@ export default class TbImageMap {
     setMarkerPosition(marker, position) {
         marker.position = position;
         var pos = this.posFunction(position.x, position.y);
-        marker.x = pos.x;
-        marker.y = pos.y;
-        this.updateMarkerDimensions(marker);
+        var x = pos.x * this.width;
+        var y = pos.y * this.height;
+        var location = this.pointToLatLng(x, y);
+        marker.setLatLng(location);
     }
 
     getPolylineLatLngs(/*polyline*/) {
@@ -340,38 +414,3 @@ class Position {
         return loc && loc.x == this.x && loc.y == this.y;
     }
 }
-
-class Popup {
-    constructor(ctx, anchor) {
-        anchor.tooltipster(
-            {
-                theme: 'tooltipster-shadow',
-                delay: 100,
-                trigger: 'custom',
-                triggerOpen: {
-                    click: true,
-                    tap: true
-                },
-                trackOrigin: true
-            }
-        );
-        this.tooltip = anchor.tooltipster('instance');
-        var contentElement = angular.element('<div class="image-map-pin-tooltip">' +
-                '<a class="image-map-pin-tooltip-close-button" id="close" style="outline: none;">×</a>' +
-                '<div id="tooltip-content">' +
-                '</div>' +
-            '</div>');
-        var $compile = ctx.$scope.$injector.get('$compile');
-        $compile(contentElement)(ctx.$scope);
-        var popup = this;
-        contentElement.find('#close').on('click', function() {
-            popup.tooltip.close();
-        });
-        this.content = contentElement.find('#tooltip-content');
-        this.tooltip.content(contentElement);
-    }
-
-    setContent(content) {
-        this.content.html(content);
-    }
-}
diff --git a/ui/src/app/widget/lib/map-widget2.js b/ui/src/app/widget/lib/map-widget2.js
index abd3e92..96fb698 100644
--- a/ui/src/app/widget/lib/map-widget2.js
+++ b/ui/src/app/widget/lib/map-widget2.js
@@ -131,6 +131,7 @@ export default class TbMapWidgetV2 {
 
         this.locationSettings.showLabel = this.ctx.settings.showLabel !== false;
         this.locationSettings.displayTooltip = this.ctx.settings.showTooltip !== false;
+        this.locationSettings.autocloseTooltip = this.ctx.settings.autocloseTooltip !== false;
         this.locationSettings.labelColor = this.ctx.widgetConfig.color || '#000000',
         this.locationSettings.label = this.ctx.settings.label || "${entityName}";
         this.locationSettings.color = this.ctx.settings.color ? tinycolor(this.ctx.settings.color).toHexString() : "#FE7569";
@@ -586,6 +587,11 @@ const commonMapSettingsSchema =
                     "type":"boolean",
                     "default":true
                 },
+                "autocloseTooltip": {
+                    "title": "Auto-close tooltips",
+                    "type":"boolean",
+                    "default":true
+                },
                 "tooltipPattern":{
                     "title":"Tooltip (for ex. 'Text ${keyName} units.' or <link-act name='my-action'>Link text</link-act>')",
                     "type":"string",
@@ -641,6 +647,7 @@ const commonMapSettingsSchema =
             "showLabel",
             "label",
             "showTooltip",
+            "autocloseTooltip",
             {
                 "key": "tooltipPattern",
                 "type": "textarea"
@@ -748,6 +755,11 @@ const imageMapSettingsSchema =
                 "type":"boolean",
                 "default":true
             },
+            "autocloseTooltip": {
+                "title": "Auto-close tooltips",
+                "type":"boolean",
+                "default":true
+            },
             "tooltipPattern":{
                 "title":"Tooltip (for ex. 'Text ${keyName} units.' or <link-act name='my-action'>Link text</link-act>')",
                 "type":"string",
@@ -822,6 +834,7 @@ const imageMapSettingsSchema =
         "showLabel",
         "label",
         "showTooltip",
+        "autocloseTooltip",
         {
             "key": "tooltipPattern",
             "type": "textarea"
diff --git a/ui/src/app/widget/lib/openstreet-map.js b/ui/src/app/widget/lib/openstreet-map.js
index 32cea20..137fabf 100644
--- a/ui/src/app/widget/lib/openstreet-map.js
+++ b/ui/src/app/widget/lib/openstreet-map.js
@@ -118,7 +118,7 @@ export default class TbOpenStreetMap {
         }
 
         if (settings.displayTooltip) {
-            this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, markerArgs);
+            this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, settings.autocloseTooltip, markerArgs);
         }
 
         if (onClickListener) {
@@ -132,10 +132,10 @@ export default class TbOpenStreetMap {
         this.map.removeLayer(marker);
     }
 
-    createTooltip(marker, pattern, replaceInfo, markerArgs) {
+    createTooltip(marker, pattern, replaceInfo, autoClose, markerArgs) {
         var popup = L.popup();
         popup.setContent('');
-        marker.bindPopup(popup, {autoClose: false, closeOnClick: false});
+        marker.bindPopup(popup, {autoClose: autoClose, closeOnClick: false});
         this.tooltips.push( {
             markerArgs: markerArgs,
             popup: popup,