Commit ec2bbbc25b2833420c9c26f2386bcad6bf79f7c8

Authored by Igor Kulikov
1 parent c5d47bbe

UI: Widgets. Improve image map.

@@ -78,7 +78,7 @@ @@ -78,7 +78,7 @@
78 "sizeY": 6.5, 78 "sizeY": 6.5,
79 "resources": [], 79 "resources": [],
80 "templateHtml": "", 80 "templateHtml": "",
81 - "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}", 81 + "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",
82 "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", 82 "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",
83 "settingsSchema": "{}", 83 "settingsSchema": "{}",
84 "dataKeySettingsSchema": "{}\n", 84 "dataKeySettingsSchema": "{}\n",
@@ -224,7 +224,7 @@ export default class TbGoogleMap { @@ -224,7 +224,7 @@ export default class TbGoogleMap {
224 } 224 }
225 225
226 if (settings.displayTooltip) { 226 if (settings.displayTooltip) {
227 - this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, markerArgs); 227 + this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, settings.autocloseTooltip, markerArgs);
228 } 228 }
229 229
230 if (onClickListener) { 230 if (onClickListener) {
@@ -241,11 +241,17 @@ export default class TbGoogleMap { @@ -241,11 +241,17 @@ export default class TbGoogleMap {
241 /* eslint-enable no-undef */ 241 /* eslint-enable no-undef */
242 242
243 /* eslint-disable no-undef */ 243 /* eslint-disable no-undef */
244 - createTooltip(marker, pattern, replaceInfo, markerArgs) { 244 + createTooltip(marker, pattern, replaceInfo, autoClose, markerArgs) {
245 var popup = new google.maps.InfoWindow({ 245 var popup = new google.maps.InfoWindow({
246 content: '' 246 content: ''
247 }); 247 });
  248 + var map = this;
248 marker.addListener('click', function() { 249 marker.addListener('click', function() {
  250 + if (autoClose) {
  251 + map.tooltips.forEach((tooltip) => {
  252 + tooltip.popup.close();
  253 + });
  254 + }
249 popup.open(this.map, marker); 255 popup.open(this.map, marker);
250 }); 256 });
251 this.tooltips.push( { 257 this.tooltips.push( {
@@ -14,15 +14,10 @@ @@ -14,15 +14,10 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 16
17 -import 'tooltipster/dist/css/tooltipster.bundle.min.css';  
18 -import 'tooltipster/dist/js/tooltipster.bundle.min.js';  
19 -import 'tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-shadow.min.css'; 17 +import 'leaflet/dist/leaflet.css';
  18 +import L from 'leaflet/dist/leaflet';
20 19
21 -import './image-map.scss';  
22 -  
23 -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"/>';  
24 -const circleShape = '<circle id="circle" fill-rule="evenodd" cy="6.9234" cx="12" clip-rule="evenodd" r="1.5"/>';  
25 -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>`; 20 +const maxZoom = 4;
26 21
27 export default class TbImageMap { 22 export default class TbImageMap {
28 23
@@ -31,10 +26,9 @@ export default class TbImageMap { @@ -31,10 +26,9 @@ export default class TbImageMap {
31 this.ctx = ctx; 26 this.ctx = ctx;
32 this.tooltips = []; 27 this.tooltips = [];
33 28
34 - $containerElement.append('<div id="image-map-container"><div id="image-map"></div></div>'); 29 + this.$containerElement = $containerElement;
  30 + this.$containerElement.css('background', '#fff');
35 31
36 - this.imageMapContainer = angular.element('#image-map-container', $containerElement);  
37 - this.imageMap = angular.element('#image-map', $containerElement);  
38 this.aspect = 0; 32 this.aspect = 0;
39 this.width = 0; 33 this.width = 0;
40 this.height = 0; 34 this.height = 0;
@@ -108,7 +102,7 @@ export default class TbImageMap { @@ -108,7 +102,7 @@ export default class TbImageMap {
108 if (keyData && keyData.data && keyData.data[0]) { 102 if (keyData && keyData.data && keyData.data[0]) {
109 var attrValue = keyData.data[0][1]; 103 var attrValue = keyData.data[0][1];
110 if (attrValue && attrValue.length) { 104 if (attrValue && attrValue.length) {
111 - this.loadImage(attrValue, this.aspect > 0 ? null : this.initCallback); 105 + this.loadImage(attrValue, this.aspect > 0 ? null : this.initCallback, true);
112 } 106 }
113 } 107 }
114 } 108 }
@@ -117,72 +111,145 @@ export default class TbImageMap { @@ -117,72 +111,145 @@ export default class TbImageMap {
117 } 111 }
118 } 112 }
119 113
120 - loadImage(imageUrl, initCallback) { 114 + loadImage(imageUrl, initCallback, updateImage) {
121 if (!imageUrl) { 115 if (!imageUrl) {
122 imageUrl = 'data:image/svg+xml;base64,PHN2ZyBpZD0ic3ZnMiIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAwIiB3aWR0aD0iMTAwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgdmlld0JveD0iMCAwIDEwMCAxMDAiPgogPGcgaWQ9ImxheWVyMSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtOTUyLjM2KSI+CiAgPHJlY3QgaWQ9InJlY3Q0Njg0IiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBoZWlnaHQ9Ijk5LjAxIiB3aWR0aD0iOTkuMDEiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiB5PSI5NTIuODYiIHg9Ii40OTUwNSIgc3Ryb2tlLXdpZHRoPSIuOTkwMTAiIGZpbGw9IiNlZWUiLz4KICA8dGV4dCBpZD0idGV4dDQ2ODYiIHN0eWxlPSJ3b3JkLXNwYWNpbmc6MHB4O2xldHRlci1zcGFjaW5nOjBweDt0ZXh0LWFuY2hvcjptaWRkbGU7dGV4dC1hbGlnbjpjZW50ZXIiIGZvbnQtd2VpZ2h0PSJib2xkIiB4bWw6c3BhY2U9InByZXNlcnZlIiBmb250LXNpemU9IjEwcHgiIGxpbmUtaGVpZ2h0PSIxMjUlIiB5PSI5NzAuNzI4MDkiIHg9IjQ5LjM5NjQ3NyIgZm9udC1mYW1pbHk9IlJvYm90byIgZmlsbD0iIzY2NjY2NiI+PHRzcGFuIGlkPSJ0c3BhbjQ2OTAiIHg9IjUwLjY0NjQ3NyIgeT0iOTcwLjcyODA5Ij5JbWFnZSBiYWNrZ3JvdW5kIDwvdHNwYW4+PHRzcGFuIGlkPSJ0c3BhbjQ2OTIiIHg9IjQ5LjM5NjQ3NyIgeT0iOTgzLjIyODA5Ij5pcyBub3QgY29uZmlndXJlZDwvdHNwYW4+PC90ZXh0PgogIDxyZWN0IGlkPSJyZWN0NDY5NCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgaGVpZ2h0PSIxOS4zNiIgd2lkdGg9IjY5LjM2IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgeT0iOTkyLjY4IiB4PSIxNS4zMiIgc3Ryb2tlLXdpZHRoPSIuNjM5ODYiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+Cg=='; 116 imageUrl = 'data:image/svg+xml;base64,PHN2ZyBpZD0ic3ZnMiIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAwIiB3aWR0aD0iMTAwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgdmlld0JveD0iMCAwIDEwMCAxMDAiPgogPGcgaWQ9ImxheWVyMSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtOTUyLjM2KSI+CiAgPHJlY3QgaWQ9InJlY3Q0Njg0IiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBoZWlnaHQ9Ijk5LjAxIiB3aWR0aD0iOTkuMDEiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiB5PSI5NTIuODYiIHg9Ii40OTUwNSIgc3Ryb2tlLXdpZHRoPSIuOTkwMTAiIGZpbGw9IiNlZWUiLz4KICA8dGV4dCBpZD0idGV4dDQ2ODYiIHN0eWxlPSJ3b3JkLXNwYWNpbmc6MHB4O2xldHRlci1zcGFjaW5nOjBweDt0ZXh0LWFuY2hvcjptaWRkbGU7dGV4dC1hbGlnbjpjZW50ZXIiIGZvbnQtd2VpZ2h0PSJib2xkIiB4bWw6c3BhY2U9InByZXNlcnZlIiBmb250LXNpemU9IjEwcHgiIGxpbmUtaGVpZ2h0PSIxMjUlIiB5PSI5NzAuNzI4MDkiIHg9IjQ5LjM5NjQ3NyIgZm9udC1mYW1pbHk9IlJvYm90byIgZmlsbD0iIzY2NjY2NiI+PHRzcGFuIGlkPSJ0c3BhbjQ2OTAiIHg9IjUwLjY0NjQ3NyIgeT0iOTcwLjcyODA5Ij5JbWFnZSBiYWNrZ3JvdW5kIDwvdHNwYW4+PHRzcGFuIGlkPSJ0c3BhbjQ2OTIiIHg9IjQ5LjM5NjQ3NyIgeT0iOTgzLjIyODA5Ij5pcyBub3QgY29uZmlndXJlZDwvdHNwYW4+PC90ZXh0PgogIDxyZWN0IGlkPSJyZWN0NDY5NCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgaGVpZ2h0PSIxOS4zNiIgd2lkdGg9IjY5LjM2IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgeT0iOTkyLjY4IiB4PSIxNS4zMiIgc3Ryb2tlLXdpZHRoPSIuNjM5ODYiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+Cg==';
123 } 117 }
124 - this.imageMap.css({backgroundImage: 'url('+imageUrl+')'}); 118 + this.imageUrl = imageUrl;
125 var imageMap = this; 119 var imageMap = this;
126 var testImage = document.createElement('img'); // eslint-disable-line 120 var testImage = document.createElement('img'); // eslint-disable-line
127 testImage.style.visibility = 'hidden'; 121 testImage.style.visibility = 'hidden';
128 testImage.onload = function() { 122 testImage.onload = function() {
129 imageMap.aspect = testImage.width / testImage.height; 123 imageMap.aspect = testImage.width / testImage.height;
130 document.body.removeChild(testImage); //eslint-disable-line 124 document.body.removeChild(testImage); //eslint-disable-line
131 - imageMap.onresize(); 125 + imageMap.onresize(updateImage);
132 if (initCallback) { 126 if (initCallback) {
133 setTimeout(initCallback, 0); //eslint-disable-line 127 setTimeout(initCallback, 0); //eslint-disable-line
134 - } else {  
135 - imageMap.onresize();  
136 } 128 }
137 } 129 }
138 document.body.appendChild(testImage); //eslint-disable-line 130 document.body.appendChild(testImage); //eslint-disable-line
139 testImage.src = imageUrl; 131 testImage.src = imageUrl;
140 } 132 }
141 133
142 - onresize() { 134 + onresize(updateImage) {
143 if (this.aspect > 0) { 135 if (this.aspect > 0) {
144 - var width = this.imageMapContainer.width(); 136 + var width = this.$containerElement.width();
145 if (width > 0) { 137 if (width > 0) {
146 var height = width / this.aspect; 138 var height = width / this.aspect;
147 - var imageMapHeight = this.imageMapContainer.height(); 139 + var imageMapHeight = this.$containerElement.height();
148 if (imageMapHeight > 0 && height > imageMapHeight) { 140 if (imageMapHeight > 0 && height > imageMapHeight) {
149 height = imageMapHeight; 141 height = imageMapHeight;
150 width = height * this.aspect; 142 width = height * this.aspect;
151 } 143 }
  144 + width *= maxZoom;
  145 + var prevWidth = this.width;
  146 + var prevHeight = this.height;
152 if (this.width !== width) { 147 if (this.width !== width) {
153 this.width = width; 148 this.width = width;
154 this.height = width / this.aspect; 149 this.height = width / this.aspect;
155 - this.imageMap.css({width: this.width, height: this.height});  
156 - this.markers.forEach((marker) => {  
157 - this.updateMarkerDimensions(marker);  
158 - }); 150 + if (!this.map) {
  151 + this.initMap(updateImage);
  152 + } else {
  153 + var lastCenterPos = this.latLngToPoint(this.map.getCenter());
  154 + lastCenterPos.x /= prevWidth;
  155 + lastCenterPos.y /= prevHeight;
  156 + this.updateBounds(updateImage, lastCenterPos);
  157 + this.map.invalidateSize(true);
  158 + this.updateMarkers();
  159 + }
159 } 160 }
160 } 161 }
161 } 162 }
162 } 163 }
163 164
  165 + initMap(updateImage) {
  166 + if (!this.map && this.aspect > 0) {
  167 + var center = this.pointToLatLng(this.width/2, this.height/2);
  168 + this.map = L.map(this.$containerElement[0], {
  169 + minZoom: 1,
  170 + maxZoom: maxZoom,
  171 + center: center,
  172 + zoom: 1,
  173 + crs: L.CRS.Simple,
  174 + attributionControl: false
  175 + });
  176 + this.updateBounds(updateImage);
  177 + this.updateMarkers();
  178 + }
  179 + }
  180 +
  181 + pointToLatLng(x, y) {
  182 + return L.CRS.Simple.pointToLatLng({x:x, y:y}, maxZoom-1);
  183 + }
  184 +
  185 + latLngToPoint(latLng) {
  186 + return L.CRS.Simple.latLngToPoint(latLng, maxZoom-1);
  187 + }
  188 +
164 inited() { 189 inited() {
165 - return this.aspect > 0 ? true : false; 190 + return angular.isDefined(this.map);
166 } 191 }
167 192
168 - updateMarkerLabel(marker, settings) {  
169 - if (settings.showLabel) {  
170 - marker.labelElement.css({color: settings.labelColor});  
171 - marker.labelElement.html(`<b>${settings.labelText}</b>`); 193 + updateBounds(updateImage, lastCenterPos) {
  194 + var w = this.width;
  195 + var h = this.height;
  196 + var southWest = this.pointToLatLng(0, h);
  197 + var northEast = this.pointToLatLng(w, 0);
  198 + var bounds = new L.LatLngBounds(southWest, northEast);
  199 +
  200 + if (updateImage && this.imageOverlay) {
  201 + this.imageOverlay.remove();
  202 + this.imageOverlay = null;
172 } 203 }
  204 +
  205 + if (this.imageOverlay) {
  206 + this.imageOverlay.setBounds(bounds);
  207 + } else {
  208 + this.imageOverlay = L.imageOverlay(this.imageUrl, bounds).addTo(this.map);
  209 + }
  210 + var padding = 200 * maxZoom;
  211 + southWest = this.pointToLatLng(-padding, h + padding);
  212 + northEast = this.pointToLatLng(w+padding, -padding);
  213 + var maxBounds = new L.LatLngBounds(southWest, northEast);
  214 + this.map.setMaxBounds(maxBounds);
  215 + if (lastCenterPos) {
  216 + lastCenterPos.x *= w;
  217 + lastCenterPos.y *= h;
  218 + var center = this.pointToLatLng(lastCenterPos.x, lastCenterPos.y);
  219 + this.ctx.$scope.$injector.get('$mdUtil').nextTick(() => {
  220 + this.map.panTo(center, {animate: false});
  221 + });
  222 + }
  223 + }
  224 +
  225 + updateMarkerLabel(marker, settings) {
  226 + marker.unbindTooltip();
  227 + marker.bindTooltip('<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>',
  228 + { className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset });
173 } 229 }
174 230
175 updateMarkerColor(marker, color) { 231 updateMarkerColor(marker, color) {
176 - marker.pinSvgElement.css({fill: color}); 232 + var pinColor = color.substr(1);
  233 + var icon = L.icon({
  234 + iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + pinColor,
  235 + iconSize: [21, 34],
  236 + iconAnchor: [10, 34],
  237 + popupAnchor: [0, -34],
  238 + shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow',
  239 + shadowSize: [40, 37],
  240 + shadowAnchor: [12, 35]
  241 + });
  242 + marker.setIcon(icon);
177 } 243 }
178 244
179 updateMarkerImage(marker, settings, image, maxSize) { 245 updateMarkerImage(marker, settings, image, maxSize) {
180 - var testImage = new Image(); // eslint-disable-line no-undef  
181 - var imageMap = this; 246 + var testImage = document.createElement('img'); // eslint-disable-line
  247 + testImage.style.visibility = 'hidden';
182 testImage.onload = function() { 248 testImage.onload = function() {
183 var width; 249 var width;
184 var height; 250 var height;
185 var aspect = testImage.width / testImage.height; 251 var aspect = testImage.width / testImage.height;
  252 + document.body.removeChild(testImage); //eslint-disable-line
186 if (aspect > 1) { 253 if (aspect > 1) {
187 width = maxSize; 254 width = maxSize;
188 height = maxSize / aspect; 255 height = maxSize / aspect;
@@ -190,74 +257,79 @@ export default class TbImageMap { @@ -190,74 +257,79 @@ export default class TbImageMap {
190 width = maxSize * aspect; 257 width = maxSize * aspect;
191 height = maxSize; 258 height = maxSize;
192 } 259 }
193 - var size = Math.max(width, height);  
194 - marker.size = size;  
195 - if (marker.imgElement) {  
196 - marker.imgElement.remove(); 260 + var icon = L.icon({
  261 + iconUrl: image,
  262 + iconSize: [width, height],
  263 + iconAnchor: [marker.offsetX * width, marker.offsetY * height],
  264 + popupAnchor: [0, -height]
  265 + });
  266 + marker.setIcon(icon);
  267 + if (settings.showLabel) {
  268 + marker.unbindTooltip();
  269 + marker.tooltipOffset = [0, -height * marker.offsetY + 10];
  270 + marker.bindTooltip('<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>',
  271 + { className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset });
197 } 272 }
198 - marker.imgElement = angular.element(`<img src="${image}" aria-label="pin" class="image-map-pin-image"/>`);  
199 - var left = (size - width)/2;  
200 - var top = (size - height)/2;  
201 - marker.imgElement.css({width: width, height: height, left: left, top: top});  
202 - marker.pinElement.append(marker.imgElement);  
203 - imageMap.updateMarkerDimensions(marker);  
204 } 273 }
  274 + document.body.appendChild(testImage); //eslint-disable-line
205 testImage.src = image; 275 testImage.src = image;
206 } 276 }
207 277
208 - updateMarkerDimensions(marker) {  
209 - var pinElement = marker.pinElement;  
210 - pinElement.css({width: marker.size, height: marker.size});  
211 - var left = marker.x * this.width - marker.size * marker.offsetX;  
212 - var top = marker.y * this.height - marker.size * marker.offsetY;  
213 - pinElement.css({left: left, top: top});  
214 - }  
215 -  
216 createMarker(position, settings, onClickListener, markerArgs) { 278 createMarker(position, settings, onClickListener, markerArgs) {
217 - var marker = {  
218 - size: 34,  
219 - position: position  
220 - }; 279 + var height = 34;
  280 + var pinColor = settings.color.substr(1);
  281 + var icon = L.icon({
  282 + iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + pinColor,
  283 + iconSize: [21, 34],
  284 + iconAnchor: [21 * settings.markerOffsetX, 34 * settings.markerOffsetY],
  285 + popupAnchor: [0, -34],
  286 + shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow',
  287 + shadowSize: [40, 37],
  288 + shadowAnchor: [12, 35]
  289 + });
  290 +
221 var pos = this.posFunction(position.x, position.y); 291 var pos = this.posFunction(position.x, position.y);
222 - marker.x = pos.x;  
223 - marker.y = pos.y; 292 + var x = pos.x * this.width;
  293 + var y = pos.y * this.height;
  294 + var location = this.pointToLatLng(x, y);
  295 + var marker = L.marker(location, {icon: icon}).addTo(this.map);
  296 + marker.position = position;
224 marker.offsetX = settings.markerOffsetX; 297 marker.offsetX = settings.markerOffsetX;
225 marker.offsetY = settings.markerOffsetY; 298 marker.offsetY = settings.markerOffsetY;
226 - marker.pinElement = angular.element('<div class="image-map-pin"></div>');  
227 299
228 if (settings.showLabel) { 300 if (settings.showLabel) {
229 - marker.labelElement = angular.element(`<div class="image-map-pin-title"><b>${settings.labelText}</b></div>`);  
230 - marker.labelElement.css({color: settings.labelColor});  
231 - marker.pinElement.append(marker.labelElement); 301 + marker.tooltipOffset = [0, -height * marker.offsetY + 10];
  302 + marker.bindTooltip('<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>',
  303 + { className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset });
232 } 304 }
233 305
234 - marker.imgElement = angular.element(pinSvg);  
235 - marker.pinSvgElement = marker.imgElement.find('#pin');  
236 - marker.pinElement.append(marker.imgElement);  
237 -  
238 - marker.pinSvgElement.css({fill: settings.color});  
239 -  
240 - this.updateMarkerDimensions(marker);  
241 -  
242 - this.imageMap.append(marker.pinElement);  
243 -  
244 if (settings.useMarkerImage) { 306 if (settings.useMarkerImage) {
245 this.updateMarkerImage(marker, settings, settings.markerImage, settings.markerImageSize || 34); 307 this.updateMarkerImage(marker, settings, settings.markerImage, settings.markerImageSize || 34);
246 } 308 }
247 309
248 if (settings.displayTooltip) { 310 if (settings.displayTooltip) {
249 - this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, markerArgs); 311 + this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, settings.autocloseTooltip, markerArgs);
250 } 312 }
251 313
252 if (onClickListener) { 314 if (onClickListener) {
253 - marker.pinElement.on('click', onClickListener); 315 + marker.on('click', onClickListener);
254 } 316 }
255 -  
256 this.markers.push(marker); 317 this.markers.push(marker);
257 return marker; 318 return marker;
258 } 319 }
259 320
  321 + updateMarkers() {
  322 + this.markers.forEach((marker) => {
  323 + this.updateMarkerLocation(marker);
  324 + });
  325 + }
  326 +
  327 + updateMarkerLocation(marker) {
  328 + this.setMarkerPosition(marker, marker.position);
  329 + }
  330 +
260 removeMarker(marker) { 331 removeMarker(marker) {
  332 + this.map.removeLayer(marker);
261 var index = this.markers.indexOf(marker); 333 var index = this.markers.indexOf(marker);
262 if (index > -1) { 334 if (index > -1) {
263 marker.pinElement.remove(); 335 marker.pinElement.remove();
@@ -265,9 +337,10 @@ export default class TbImageMap { @@ -265,9 +337,10 @@ export default class TbImageMap {
265 } 337 }
266 } 338 }
267 339
268 - createTooltip(marker, pattern, replaceInfo, markerArgs) {  
269 - var popup = new Popup(this.ctx, marker.pinElement); 340 + createTooltip(marker, pattern, replaceInfo, autoClose, markerArgs) {
  341 + var popup = L.popup();
270 popup.setContent(''); 342 popup.setContent('');
  343 + marker.bindPopup(popup, {autoClose: autoClose, closeOnClick: false});
271 this.tooltips.push( { 344 this.tooltips.push( {
272 markerArgs: markerArgs, 345 markerArgs: markerArgs,
273 popup: popup, 346 popup: popup,
@@ -302,9 +375,10 @@ export default class TbImageMap { @@ -302,9 +375,10 @@ export default class TbImageMap {
302 setMarkerPosition(marker, position) { 375 setMarkerPosition(marker, position) {
303 marker.position = position; 376 marker.position = position;
304 var pos = this.posFunction(position.x, position.y); 377 var pos = this.posFunction(position.x, position.y);
305 - marker.x = pos.x;  
306 - marker.y = pos.y;  
307 - this.updateMarkerDimensions(marker); 378 + var x = pos.x * this.width;
  379 + var y = pos.y * this.height;
  380 + var location = this.pointToLatLng(x, y);
  381 + marker.setLatLng(location);
308 } 382 }
309 383
310 getPolylineLatLngs(/*polyline*/) { 384 getPolylineLatLngs(/*polyline*/) {
@@ -340,38 +414,3 @@ class Position { @@ -340,38 +414,3 @@ class Position {
340 return loc && loc.x == this.x && loc.y == this.y; 414 return loc && loc.x == this.x && loc.y == this.y;
341 } 415 }
342 } 416 }
343 -  
344 -class Popup {  
345 - constructor(ctx, anchor) {  
346 - anchor.tooltipster(  
347 - {  
348 - theme: 'tooltipster-shadow',  
349 - delay: 100,  
350 - trigger: 'custom',  
351 - triggerOpen: {  
352 - click: true,  
353 - tap: true  
354 - },  
355 - trackOrigin: true  
356 - }  
357 - );  
358 - this.tooltip = anchor.tooltipster('instance');  
359 - var contentElement = angular.element('<div class="image-map-pin-tooltip">' +  
360 - '<a class="image-map-pin-tooltip-close-button" id="close" style="outline: none;">×</a>' +  
361 - '<div id="tooltip-content">' +  
362 - '</div>' +  
363 - '</div>');  
364 - var $compile = ctx.$scope.$injector.get('$compile');  
365 - $compile(contentElement)(ctx.$scope);  
366 - var popup = this;  
367 - contentElement.find('#close').on('click', function() {  
368 - popup.tooltip.close();  
369 - });  
370 - this.content = contentElement.find('#tooltip-content');  
371 - this.tooltip.content(contentElement);  
372 - }  
373 -  
374 - setContent(content) {  
375 - this.content.html(content);  
376 - }  
377 -}  
1 -/**  
2 - * Copyright © 2016-2017 The Thingsboard Authors  
3 - *  
4 - * Licensed under the Apache License, Version 2.0 (the "License");  
5 - * you may not use this file except in compliance with the License.  
6 - * You may obtain a copy of the License at  
7 - *  
8 - * http://www.apache.org/licenses/LICENSE-2.0  
9 - *  
10 - * Unless required by applicable law or agreed to in writing, software  
11 - * distributed under the License is distributed on an "AS IS" BASIS,  
12 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
13 - * See the License for the specific language governing permissions and  
14 - * limitations under the License.  
15 - */  
16 -  
17 -.image-map-pin-tooltip {  
18 - pointer-events: all;  
19 - padding: 5px;  
20 - .image-map-pin-tooltip-close-button {  
21 - cursor: pointer;  
22 - position: absolute;  
23 - top: 0;  
24 - right: 0;  
25 - padding: 6px 6px 0 0;  
26 - border: none;  
27 - text-align: center;  
28 - width: 20px;  
29 - height: 16px;  
30 - font: 18px/16px Tahoma, Verdana, sans-serif;  
31 - color: #b0b0b0;  
32 - text-decoration: none;  
33 - font-weight: bold;  
34 - background: transparent;  
35 - &:hover {  
36 - color: #919191;  
37 - }  
38 - }  
39 - #tooltip-content {  
40 - line-height: normal;  
41 - font-size: 13px;  
42 - font-weight: 300;  
43 - color: #333;  
44 - }  
45 -}  
46 -  
47 -#image-map-container {  
48 - width: 100%;  
49 - height: 100%;  
50 - #image-map {  
51 - color: rgba(0, 0, 0, 0.870588);  
52 - position: relative;  
53 - margin: auto;  
54 - background: transparent no-repeat scroll 0 0;  
55 - background-size: 100% 100%;  
56 -  
57 - &.is-pointer {  
58 - cursor: pointer !important;  
59 - }  
60 -  
61 - .movable {  
62 - cursor: move;  
63 - }  
64 -  
65 - .image-map-pin {  
66 - outline: none;  
67 - position: absolute;  
68 - background: none;  
69 - .image-map-pin-title {  
70 - position: relative;  
71 - white-space: nowrap;  
72 - text-align: center;  
73 - line-height: 1.5;  
74 - font-size: 12px;  
75 - font-weight: 400;  
76 - top: -20px;  
77 - &:before {  
78 - content: "";  
79 - margin-left: -100%;  
80 - }  
81 - &:after {  
82 - content: "";  
83 - margin-right: -100%;  
84 - }  
85 - }  
86 - .image-map-pin-image {  
87 - position: absolute;  
88 - pointer-events: none;  
89 - top: 0;  
90 - bottom: 0;  
91 - right: 0;  
92 - left: 0;  
93 - width: 100%;  
94 - height: 100%;  
95 - }  
96 - }  
97 - }  
98 -}  
@@ -131,6 +131,7 @@ export default class TbMapWidgetV2 { @@ -131,6 +131,7 @@ export default class TbMapWidgetV2 {
131 131
132 this.locationSettings.showLabel = this.ctx.settings.showLabel !== false; 132 this.locationSettings.showLabel = this.ctx.settings.showLabel !== false;
133 this.locationSettings.displayTooltip = this.ctx.settings.showTooltip !== false; 133 this.locationSettings.displayTooltip = this.ctx.settings.showTooltip !== false;
  134 + this.locationSettings.autocloseTooltip = this.ctx.settings.autocloseTooltip !== false;
134 this.locationSettings.labelColor = this.ctx.widgetConfig.color || '#000000', 135 this.locationSettings.labelColor = this.ctx.widgetConfig.color || '#000000',
135 this.locationSettings.label = this.ctx.settings.label || "${entityName}"; 136 this.locationSettings.label = this.ctx.settings.label || "${entityName}";
136 this.locationSettings.color = this.ctx.settings.color ? tinycolor(this.ctx.settings.color).toHexString() : "#FE7569"; 137 this.locationSettings.color = this.ctx.settings.color ? tinycolor(this.ctx.settings.color).toHexString() : "#FE7569";
@@ -586,6 +587,11 @@ const commonMapSettingsSchema = @@ -586,6 +587,11 @@ const commonMapSettingsSchema =
586 "type":"boolean", 587 "type":"boolean",
587 "default":true 588 "default":true
588 }, 589 },
  590 + "autocloseTooltip": {
  591 + "title": "Auto-close tooltips",
  592 + "type":"boolean",
  593 + "default":true
  594 + },
589 "tooltipPattern":{ 595 "tooltipPattern":{
590 "title":"Tooltip (for ex. 'Text ${keyName} units.' or <link-act name='my-action'>Link text</link-act>')", 596 "title":"Tooltip (for ex. 'Text ${keyName} units.' or <link-act name='my-action'>Link text</link-act>')",
591 "type":"string", 597 "type":"string",
@@ -641,6 +647,7 @@ const commonMapSettingsSchema = @@ -641,6 +647,7 @@ const commonMapSettingsSchema =
641 "showLabel", 647 "showLabel",
642 "label", 648 "label",
643 "showTooltip", 649 "showTooltip",
  650 + "autocloseTooltip",
644 { 651 {
645 "key": "tooltipPattern", 652 "key": "tooltipPattern",
646 "type": "textarea" 653 "type": "textarea"
@@ -748,6 +755,11 @@ const imageMapSettingsSchema = @@ -748,6 +755,11 @@ const imageMapSettingsSchema =
748 "type":"boolean", 755 "type":"boolean",
749 "default":true 756 "default":true
750 }, 757 },
  758 + "autocloseTooltip": {
  759 + "title": "Auto-close tooltips",
  760 + "type":"boolean",
  761 + "default":true
  762 + },
751 "tooltipPattern":{ 763 "tooltipPattern":{
752 "title":"Tooltip (for ex. 'Text ${keyName} units.' or <link-act name='my-action'>Link text</link-act>')", 764 "title":"Tooltip (for ex. 'Text ${keyName} units.' or <link-act name='my-action'>Link text</link-act>')",
753 "type":"string", 765 "type":"string",
@@ -822,6 +834,7 @@ const imageMapSettingsSchema = @@ -822,6 +834,7 @@ const imageMapSettingsSchema =
822 "showLabel", 834 "showLabel",
823 "label", 835 "label",
824 "showTooltip", 836 "showTooltip",
  837 + "autocloseTooltip",
825 { 838 {
826 "key": "tooltipPattern", 839 "key": "tooltipPattern",
827 "type": "textarea" 840 "type": "textarea"
@@ -118,7 +118,7 @@ export default class TbOpenStreetMap { @@ -118,7 +118,7 @@ export default class TbOpenStreetMap {
118 } 118 }
119 119
120 if (settings.displayTooltip) { 120 if (settings.displayTooltip) {
121 - this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, markerArgs); 121 + this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, settings.autocloseTooltip, markerArgs);
122 } 122 }
123 123
124 if (onClickListener) { 124 if (onClickListener) {
@@ -132,10 +132,10 @@ export default class TbOpenStreetMap { @@ -132,10 +132,10 @@ export default class TbOpenStreetMap {
132 this.map.removeLayer(marker); 132 this.map.removeLayer(marker);
133 } 133 }
134 134
135 - createTooltip(marker, pattern, replaceInfo, markerArgs) { 135 + createTooltip(marker, pattern, replaceInfo, autoClose, markerArgs) {
136 var popup = L.popup(); 136 var popup = L.popup();
137 popup.setContent(''); 137 popup.setContent('');
138 - marker.bindPopup(popup, {autoClose: false, closeOnClick: false}); 138 + marker.bindPopup(popup, {autoClose: autoClose, closeOnClick: false});
139 this.tooltips.push( { 139 this.tooltips.push( {
140 markerArgs: markerArgs, 140 markerArgs: markerArgs,
141 popup: popup, 141 popup: popup,