thingsboard-aplcache

Details

diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json
index 0fc0085..ddb90db 100644
--- a/application/src/main/data/json/system/widget_bundles/cards.json
+++ b/application/src/main/data/json/system/widget_bundles/cards.json
@@ -112,9 +112,9 @@
         "templateHtml": "<tb-timeseries-table-widget \n    table-id=\"tableId\"\n    ctx=\"ctx\">\n</tb-timeseries-table-widget>",
         "templateCss": "",
         "controllerScript": "self.onInit = function() {\n    var scope = self.ctx.$scope;\n    var id = self.ctx.$scope.$injector.get('utils').guid();\n    scope.tableId = \"table-\"+id;\n    scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n    self.ctx.$scope.$broadcast('timeseries-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.onDestroy = function() {\n}",
-        "settingsSchema": "{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"TimeseriesTableSettings\",\n        \"properties\": {\n            \"showTimestamp\": {\n                \"title\": \"Display timestamp column\",\n                \"type\": \"boolean\",\n                \"default\": true\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"showTimestamp\"\n    ]\n}",
+        "settingsSchema": "{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"TimeseriesTableSettings\",\n        \"properties\": {\n            \"showTimestamp\": {\n                \"title\": \"Display timestamp column\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"displayPagination\": {\n                \"title\": \"Display pagination\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },            \n            \"defaultPageSize\": {\n                \"title\": \"Default page size\",\n                \"type\": \"number\",\n                \"default\": 10\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"showTimestamp\",\n        \"displayPagination\",\n        \"defaultPageSize\"\n    ]\n}",
         "dataKeySettingsSchema": "{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"DataKeySettings\",\n        \"properties\": {\n            \"useCellStyleFunction\": {\n                \"title\": \"Use cell style function\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },\n            \"cellStyleFunction\": {\n                \"title\": \"Cell style function: f(value)\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"useCellContentFunction\": {\n                \"title\": \"Use cell content function\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },\n            \"cellContentFunction\": {\n                \"title\": \"Cell content function: f(value, rowData, filter)\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"useCellStyleFunction\",\n        {\n            \"key\": \"cellStyleFunction\",\n            \"type\": \"javascript\"\n        },\n        \"useCellContentFunction\",\n        {\n            \"key\": \"cellContentFunction\",\n            \"type\": \"javascript\"\n        }\n    ]\n}",
-        "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature  °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n    var percent = (value + 60)/120 * 100;\\n    var color = tinycolor.mix('blue', 'red', amount = percent);\\n    color.setAlpha(.5);\\n    return {\\n      paddingLeft: '20px',\\n      color: '#ffffff',\\n      background: color.toRgbString(),\\n      fontSize: '18px'\\n    };\\n} else {\\n    return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n    var percent = value;\\n    var backgroundColor = tinycolor('blue');\\n    backgroundColor.setAlpha(value/100);\\n    var color = 'blue';\\n    if (value > 50) {\\n        color = 'white';\\n    }\\n    \\n    return {\\n      paddingLeft: '20px',\\n      color: color,\\n      background: backgroundColor.toRgbString(),\\n      fontSize: '18px'\\n    };\\n} else {\\n    return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":false,\"showLegend\":false}"
+        "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature  °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n    var percent = (value + 60)/120 * 100;\\n    var color = tinycolor.mix('blue', 'red', amount = percent);\\n    color.setAlpha(.5);\\n    return {\\n      paddingLeft: '20px',\\n      color: '#ffffff',\\n      background: color.toRgbString(),\\n      fontSize: '18px'\\n    };\\n} else {\\n    return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n    var percent = value;\\n    var backgroundColor = tinycolor('blue');\\n    backgroundColor.setAlpha(value/100);\\n    var color = 'blue';\\n    if (value > 50) {\\n        color = 'white';\\n    }\\n    \\n    return {\\n      paddingLeft: '20px',\\n      color: color,\\n      background: backgroundColor.toRgbString(),\\n      fontSize: '18px'\\n    };\\n} else {\\n    return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}"
       }
     }
   ]
diff --git a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java
index 35d1773..9f7c08f 100644
--- a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java
+++ b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java
@@ -47,8 +47,8 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
     @Bean
     public ServletServerContainerFactoryBean createWebSocketContainer() {
         ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
-        container.setMaxTextMessageBufferSize(8192);
-        container.setMaxBinaryMessageBufferSize(8192);
+        container.setMaxTextMessageBufferSize(32768);
+        container.setMaxBinaryMessageBufferSize(32768);
         return container;
     }
 
diff --git a/ui/src/app/api/telemetry-websocket.service.js b/ui/src/app/api/telemetry-websocket.service.js
index f84d66d..b0e8ea9 100644
--- a/ui/src/app/api/telemetry-websocket.service.js
+++ b/ui/src/app/api/telemetry-websocket.service.js
@@ -23,6 +23,8 @@ export default angular.module('thingsboard.api.telemetryWebsocket', [thingsboard
 const RECONNECT_INTERVAL = 2000;
 const WS_IDLE_TIMEOUT = 90000;
 
+const MAX_PUBLISH_COMMANDS = 10;
+
 /*@ngInject*/
 function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, types, userService) {
 
@@ -75,19 +77,40 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
     return service;
 
     function publishCommands () {
-        if (isOpened && (cmdsWrapper.tsSubCmds.length > 0 ||
-            cmdsWrapper.historyCmds.length > 0 ||
-            cmdsWrapper.attrSubCmds.length > 0)) {
-            dataStream.send(angular.copy(cmdsWrapper)).then(function () {
+        while(isOpened && hasCommands()) {
+            dataStream.send(preparePublishCommands()).then(function () {
                 checkToClose();
             });
-            cmdsWrapper.tsSubCmds = [];
-            cmdsWrapper.historyCmds = [];
-            cmdsWrapper.attrSubCmds = [];
         }
         tryOpenSocket();
     }
 
+    function hasCommands() {
+        return cmdsWrapper.tsSubCmds.length > 0 ||
+            cmdsWrapper.historyCmds.length > 0 ||
+            cmdsWrapper.attrSubCmds.length > 0;
+    }
+
+    function preparePublishCommands() {
+        var preparedWrapper = {};
+        var leftCount = MAX_PUBLISH_COMMANDS;
+        preparedWrapper.tsSubCmds = popCmds(cmdsWrapper.tsSubCmds, leftCount);
+        leftCount -= preparedWrapper.tsSubCmds.length;
+        preparedWrapper.historyCmds = popCmds(cmdsWrapper.historyCmds, leftCount);
+        leftCount -= preparedWrapper.historyCmds.length;
+        preparedWrapper.attrSubCmds = popCmds(cmdsWrapper.attrSubCmds, leftCount);
+        return preparedWrapper;
+    }
+
+    function popCmds(cmds, leftCount) {
+        var toPublish = Math.min(cmds.length, leftCount);
+        if (toPublish > 0) {
+            return cmds.splice(0, toPublish);
+        } else {
+            return [];
+        }
+    }
+
     function onError (/*message*/) {
         isOpening = false;
     }
diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js
index 9d99781..085a28b 100644
--- a/ui/src/app/common/utils.service.js
+++ b/ui/src/app/common/utils.service.js
@@ -134,6 +134,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
         defaultAlarmDataKeys.push(dataKey);
     }
 
+    var imageAspectMap = {};
+
     var service = {
         getDefaultDatasource: getDefaultDatasource,
         generateObjectFromJsonSchema: generateObjectFromJsonSchema,
@@ -159,7 +161,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
         insertVariable: insertVariable,
         customTranslation: customTranslation,
         objToBase64: objToBase64,
-        base64toObj: base64toObj
+        base64toObj: base64toObj,
+        loadImageAspect: loadImageAspect
     }
 
     return service;
@@ -543,4 +546,34 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
         return obj;
     }
 
+    function loadImageAspect(imageUrl) {
+        var deferred = $q.defer();
+        if (imageUrl && imageUrl.length) {
+            var urlHashCode = hashCode(imageUrl);
+            var aspect = imageAspectMap[urlHashCode];
+            if (angular.isUndefined(aspect)) {
+                var testImage = document.createElement('img'); // eslint-disable-line
+                testImage.style.visibility = 'hidden';
+                testImage.onload = function() {
+                    aspect = testImage.width / testImage.height;
+                    document.body.removeChild(testImage); //eslint-disable-line
+                    imageAspectMap[urlHashCode] = aspect;
+                    deferred.resolve(aspect);
+                };
+                testImage.onerror = function() {
+                    aspect = 0;
+                    imageAspectMap[urlHashCode] = aspect;
+                    deferred.resolve(aspect);
+                };
+                document.body.appendChild(testImage); //eslint-disable-line
+                testImage.src = imageUrl;
+            } else {
+                deferred.resolve(aspect);
+            }
+        } else {
+            deferred.resolve(0);
+        }
+        return deferred.promise;
+    }
+
 }
diff --git a/ui/src/app/entity/attribute/attribute-table.scss b/ui/src/app/entity/attribute/attribute-table.scss
index cdf9ffd..3f40cdf 100644
--- a/ui/src/app/entity/attribute/attribute-table.scss
+++ b/ui/src/app/entity/attribute/attribute-table.scss
@@ -47,7 +47,7 @@ md-toolbar.md-table-toolbar.alternate {
 .widgets-carousel {
   position: relative;
   margin: 0px;
-
+  height: calc(100% - 100px);
   min-height: 150px !important;
 
   tb-dashboard {
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index ab672de..7769b3d 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -1213,6 +1213,7 @@ export default angular.module('thingsboard.locale', [])
                     "remove-widget-title": "Are you sure you want to remove the widget '{{widgetTitle}}'?",
                     "remove-widget-text": "After the confirmation the widget and all related data will become unrecoverable.",
                     "timeseries": "Time series",
+                    "search-data": "Search data",
                     "latest-values": "Latest values",
                     "rpc": "Control widget",
                     "alarm": "Alarm widget",
diff --git a/ui/src/app/widget/lib/alarms-table-widget.tpl.html b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
index 860128d..34c7c1b 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.tpl.html
+++ b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
@@ -77,7 +77,7 @@
                     </td>
                     <td md-cell ng-if="vm.displayDetails" class="tb-action-cell">
                         <md-button class="md-icon-button" aria-label="{{ 'alarm.details' | translate }}"
-                                   ng-click="vm.openAlarmDetails($event, alarm)">
+                                   ng-click="vm.openAlarmDetails($event, alarm)" ng-disabled="$root.loading">
                             <md-icon aria-label="{{ 'alarm.details' | translate }}" class="material-icons">more_horiz</md-icon>
                             <md-tooltip md-direction="top">
                                 {{ 'alarm.details' | translate }}
@@ -90,7 +90,7 @@
                                    width: vm.actionCellDescriptors.length*36+'px'}">
                         <md-button class="md-icon-button" ng-repeat="actionDescriptor in vm.actionCellDescriptors"
                                    aria-label="{{ actionDescriptor.displayName }}"
-                                   ng-click="vm.onActionButtonClick($event, alarm, actionDescriptor)">
+                                   ng-click="vm.onActionButtonClick($event, alarm, actionDescriptor)" ng-disabled="$root.loading">
                             <md-icon aria-label="{{ actionDescriptor.displayName }}" class="material-icons">{{actionDescriptor.icon}}</md-icon>
                             <md-tooltip md-direction="top">
                                 {{ actionDescriptor.displayName }}
diff --git a/ui/src/app/widget/lib/CanvasDigitalGauge.js b/ui/src/app/widget/lib/CanvasDigitalGauge.js
index bd3ff98..200430c 100644
--- a/ui/src/app/widget/lib/CanvasDigitalGauge.js
+++ b/ui/src/app/widget/lib/CanvasDigitalGauge.js
@@ -455,7 +455,7 @@ function barDimensions(context, options, x, y, w, h) {
         if (options.hideMinMax && options.label === '') {
             bd.labelY = bd.barBottom;
             bd.barLeft = bd.origBaseX + options.fontMinMaxSize/3 * bd.fontSizeFactor;
-            bd.barRight = bd.bd.origBaseX + w + /*bd.width*/ - options.fontMinMaxSize/3 * bd.fontSizeFactor;
+            bd.barRight = bd.origBaseX + w + /*bd.width*/ - options.fontMinMaxSize/3 * bd.fontSizeFactor;
         } else {
             context.font = canvasGauges.drawings.font(options, 'MinMax', bd.fontSizeFactor);
             var minTextWidth  = context.measureText(options.minValue+'').width;
diff --git a/ui/src/app/widget/lib/entities-table-widget.tpl.html b/ui/src/app/widget/lib/entities-table-widget.tpl.html
index 2587da1..7ef8c54 100644
--- a/ui/src/app/widget/lib/entities-table-widget.tpl.html
+++ b/ui/src/app/widget/lib/entities-table-widget.tpl.html
@@ -63,7 +63,7 @@
                                    width: vm.actionCellDescriptors.length*36+'px'}">
                         <md-button class="md-icon-button" ng-repeat="actionDescriptor in vm.actionCellDescriptors"
                                    aria-label="{{ actionDescriptor.displayName }}"
-                                   ng-click="vm.onActionButtonClick($event, entity, actionDescriptor)">
+                                   ng-click="vm.onActionButtonClick($event, entity, actionDescriptor)" ng-disabled="$root.loading">
                             <md-icon aria-label="{{ actionDescriptor.displayName }}" class="material-icons">{{actionDescriptor.icon}}</md-icon>
                             <md-tooltip md-direction="top">
                                 {{ actionDescriptor.displayName }}
diff --git a/ui/src/app/widget/lib/google-map.js b/ui/src/app/widget/lib/google-map.js
index e2b4dc6..af11ac3 100644
--- a/ui/src/app/widget/lib/google-map.js
+++ b/ui/src/app/widget/lib/google-map.js
@@ -13,16 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 var gmGlobals = {
     loadingGmId: null,
     gmApiKeys: {}
 }
 
 export default class TbGoogleMap {
-    constructor($containerElement, initCallback, defaultZoomLevel, dontFitMapBounds, minZoomLevel, gmApiKey, gmDefaultMapType) {
+    constructor($containerElement, utils, initCallback, defaultZoomLevel, dontFitMapBounds, minZoomLevel, gmApiKey, gmDefaultMapType) {
 
         var tbMap = this;
+        this.utils = utils;
         this.defaultZoomLevel = defaultZoomLevel;
         this.dontFitMapBounds = dontFitMapBounds;
         this.minZoomLevel = minZoomLevel;
@@ -151,80 +151,97 @@ export default class TbGoogleMap {
 
     /* eslint-disable no-undef */
     updateMarkerColor(marker, color) {
-        var pinColor = color.substr(1);
-        var pinImage = new google.maps.MarkerImage("https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|" + pinColor,
-            new google.maps.Size(21, 34),
-            new google.maps.Point(0,0),
-            new google.maps.Point(10, 34));
-        marker.setIcon(pinImage);
+        this.createDefaultMarkerIcon(marker, color, (iconInfo) => {
+            marker.setIcon(iconInfo.icon);
+        });
     }
     /* eslint-enable no-undef */
 
     /* eslint-disable no-undef */
-    updateMarkerImage(marker, settings, image, maxSize) {
-        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;
-            } else {
-                width = maxSize * aspect;
-                height = maxSize;
-            }
-            var pinImage = {
-                url: image,
-                scaledSize : new google.maps.Size(width, height)
-            }
-            marker.setIcon(pinImage);
+    updateMarkerIcon(marker, settings) {
+        this.createMarkerIcon(marker, settings, (iconInfo) => {
+            marker.setIcon(iconInfo.icon);
             if (settings.showLabel) {
-                marker.set('labelAnchor', new google.maps.Point(100, height + 20));
+                marker.set('labelAnchor', new google.maps.Point(100, iconInfo.size[1] + 20));
             }
+        });
+    }
+    /* eslint-disable no-undef */
+
+    /* eslint-disable no-undef */
+    createMarkerIcon(marker, settings, onMarkerIconReady) {
+        var currentImage = settings.currentImage;
+        var gMap = this;
+        if (currentImage && currentImage.url) {
+            this.utils.loadImageAspect(currentImage.url).then(
+                (aspect) => {
+                    if (aspect) {
+                        var width;
+                        var height;
+                        if (aspect > 1) {
+                            width = currentImage.size;
+                            height = currentImage.size / aspect;
+                        } else {
+                            width = currentImage.size * aspect;
+                            height = currentImage.size;
+                        }
+                        var icon = {
+                            url: currentImage.url,
+                            scaledSize : new google.maps.Size(width, height)
+                        };
+                        var iconInfo = {
+                            size: [width, height],
+                            icon: icon
+                        };
+                        onMarkerIconReady(iconInfo);
+                    } else {
+                        gMap.createDefaultMarkerIcon(marker, settings.color, onMarkerIconReady);
+                    }
+                }
+            );
+        } else {
+            this.createDefaultMarkerIcon(marker, settings.color, onMarkerIconReady);
         }
-        document.body.appendChild(testImage); //eslint-disable-line
-        testImage.src = image;
     }
     /* eslint-enable no-undef */
 
     /* eslint-disable no-undef */
-    createMarker(location, settings, onClickListener, markerArgs) {
-        var height = 34;
-        var pinColor = settings.color.substr(1);
-        var pinImage = new google.maps.MarkerImage("https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|" + pinColor,
-            new google.maps.Size(21, 34),
-            new google.maps.Point(0,0),
-            new google.maps.Point(10, 34));
-        var pinShadow = new google.maps.MarkerImage("https://chart.apis.google.com/chart?chst=d_map_pin_shadow",
+    createDefaultMarkerIcon(marker, color, onMarkerIconReady) {
+        var pinColor = color.substr(1);
+        var icon = new google.maps.MarkerImage("https://chart.apis.google.com/chart?chst=d_map_pin_letter_withshadow&chld=%E2%80%A2|" + pinColor,
             new google.maps.Size(40, 37),
-            new google.maps.Point(0, 0),
-            new google.maps.Point(12, 35));
+            new google.maps.Point(0,0),
+            new google.maps.Point(10, 37));
+        var iconInfo = {
+            size: [40, 37],
+            icon: icon
+        };
+        onMarkerIconReady(iconInfo);
+    }
+    /* eslint-enable no-undef */
+
+    /* eslint-disable no-undef */
+    createMarker(location, settings, onClickListener, markerArgs) {
         var marker;
         if (settings.showLabel) {
             marker = new MarkerWithLabel({
                 position: location,
-                map: this.map,
-                icon: pinImage,
-                shadow: pinShadow,
                 labelContent: '<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>',
-                labelClass: "tb-labels",
-                labelAnchor: new google.maps.Point(100, height + 20)
+                labelClass: "tb-labels"
             });
         } else {
             marker = new google.maps.Marker({
                 position: location,
-                map: this.map,
-                icon: pinImage,
-                shadow: pinShadow
             });
         }
-
-        if (settings.useMarkerImage) {
-            this.updateMarkerImage(marker, settings, settings.markerImage, settings.markerImageSize || 34);
-        }
+        var gMap = this;
+        this.createMarkerIcon(marker, settings, (iconInfo) => {
+            marker.setIcon(iconInfo.icon);
+            if (settings.showLabel) {
+                marker.set('labelAnchor', new google.maps.Point(100, iconInfo.size[1] + 20));
+            }
+            marker.setMap(gMap.map);
+        });
 
         if (settings.displayTooltip) {
             this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, settings.autocloseTooltip, markerArgs);
diff --git a/ui/src/app/widget/lib/image-map.js b/ui/src/app/widget/lib/image-map.js
index e9d9976..c130539 100644
--- a/ui/src/app/widget/lib/image-map.js
+++ b/ui/src/app/widget/lib/image-map.js
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 import 'leaflet/dist/leaflet.css';
 import * as L from 'leaflet';
 
@@ -21,9 +20,10 @@ const maxZoom = 4;
 
 export default class TbImageMap {
 
-    constructor(ctx, $containerElement, initCallback, imageUrl, posFunction, imageEntityAlias, imageUrlAttribute) {
+    constructor(ctx, $containerElement, utils, initCallback, imageUrl, posFunction, imageEntityAlias, imageUrlAttribute) {
 
         this.ctx = ctx;
+        this.utils = utils;
         this.tooltips = [];
 
         this.$containerElement = $containerElement;
@@ -117,18 +117,15 @@ export default class TbImageMap {
         }
         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(updateImage);
-            if (initCallback) {
-                setTimeout(initCallback, 0); //eslint-disable-line
+        this.utils.loadImageAspect(imageUrl).then(
+            (aspect) => {
+                imageMap.aspect = aspect;
+                imageMap.onresize(updateImage);
+                if (initCallback) {
+                    setTimeout(initCallback, 0); //eslint-disable-line
+                }
             }
-        }
-        document.body.appendChild(testImage); //eslint-disable-line
-        testImage.src = imageUrl;
+        );
     }
 
     onresize(updateImage) {
@@ -229,83 +226,97 @@ export default class TbImageMap {
     }
 
     updateMarkerColor(marker, 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]
+        this.createDefaultMarkerIcon(marker, color, (iconInfo) => {
+            marker.setIcon(iconInfo.icon);
         });
-        marker.setIcon(icon);
-    }
-
-    updateMarkerImage(marker, settings, image, maxSize) {
-        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;
-            } else {
-                width = maxSize * aspect;
-                height = maxSize;
-            }
-            var icon = L.icon({
-                iconUrl: image,
-                iconSize: [width, height],
-                iconAnchor: [marker.offsetX * width, marker.offsetY * height],
-                popupAnchor: [0, -height]
-            });
-            marker.setIcon(icon);
+    }
+
+    updateMarkerIcon(marker, settings) {
+        this.createMarkerIcon(marker, settings, (iconInfo) => {
+            marker.setIcon(iconInfo.icon);
             if (settings.showLabel) {
                 marker.unbindTooltip();
-                marker.tooltipOffset = [0, -height * marker.offsetY + 10];
+                marker.tooltipOffset = [0, -iconInfo.size[1] * 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 });
             }
+        });
+    }
+
+    createMarkerIcon(marker, settings, onMarkerIconReady) {
+        var currentImage = settings.currentImage;
+        var opMap = this;
+        if (currentImage && currentImage.url) {
+            this.utils.loadImageAspect(currentImage.url).then(
+                (aspect) => {
+                    if (aspect) {
+                        var width;
+                        var height;
+                        if (aspect > 1) {
+                            width = currentImage.size;
+                            height = currentImage.size / aspect;
+                        } else {
+                            width = currentImage.size * aspect;
+                            height = currentImage.size;
+                        }
+                        var icon = L.icon({
+                            iconUrl: currentImage.url,
+                            iconSize: [width, height],
+                            iconAnchor: [marker.offsetX * width, marker.offsetY * height],
+                            popupAnchor: [0, -height]
+                        });
+                        var iconInfo = {
+                            size: [width, height],
+                            icon: icon
+                        };
+                        onMarkerIconReady(iconInfo);
+                    } else {
+                        opMap.createDefaultMarkerIcon(marker, settings.color, onMarkerIconReady);
+                    }
+                }
+            );
+        } else {
+            this.createDefaultMarkerIcon(marker, settings.color, onMarkerIconReady);
         }
-        document.body.appendChild(testImage); //eslint-disable-line
-        testImage.src = image;
     }
 
-    createMarker(position, settings, onClickListener, markerArgs) {
-        var height = 34;
-        var pinColor = settings.color.substr(1);
+    createDefaultMarkerIcon(marker, color, onMarkerIconReady) {
+        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: [21 * settings.markerOffsetX, 34 * settings.markerOffsetY],
+            iconAnchor: [21 * marker.offsetX, 34 * marker.offsetY],
             popupAnchor: [0, -34],
             shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow',
             shadowSize: [40, 37],
             shadowAnchor: [12, 35]
         });
+        var iconInfo = {
+            size: [21, 34],
+            icon: icon
+        };
+        onMarkerIconReady(iconInfo);
+    }
 
+    createMarker(position, settings, onClickListener, markerArgs) {
         var pos = this.posFunction(position.x, position.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);
+        var marker = L.marker(location, {});//.addTo(this.map);
         marker.position = position;
         marker.offsetX = settings.markerOffsetX;
         marker.offsetY = settings.markerOffsetY;
-
-        if (settings.showLabel) {
-            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 });
-        }
-
-        if (settings.useMarkerImage) {
-            this.updateMarkerImage(marker, settings, settings.markerImage, settings.markerImageSize || 34);
-        }
+        var opMap = this;
+        this.createMarkerIcon(marker, settings, (iconInfo) => {
+            marker.setIcon(iconInfo.icon);
+            if (settings.showLabel) {
+                marker.tooltipOffset = [0, -iconInfo.size[1] * 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.addTo(opMap.map);
+        });
 
         if (settings.displayTooltip) {
             this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, settings.autocloseTooltip, markerArgs);
diff --git a/ui/src/app/widget/lib/map-widget.js b/ui/src/app/widget/lib/map-widget.js
index f742514..ef86381 100644
--- a/ui/src/app/widget/lib/map-widget.js
+++ b/ui/src/app/widget/lib/map-widget.js
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 import tinycolor from 'tinycolor2';
 
 import TbGoogleMap from './google-map';
@@ -75,7 +74,7 @@ export default class TbMapWidget {
         if (!$element) {
             $element = ctx.$container;
         }
-
+        this.utils = ctx.$scope.$injector.get('utils');
         this.drawRoutes = drawRoutes;
         this.markers = [];
         if (this.drawRoutes) {
@@ -110,9 +109,9 @@ export default class TbMapWidget {
         };
 
         if (mapProvider === 'google-map') {
-            this.map = new TbGoogleMap($element, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel, settings.gmApiKey, settings.gmDefaultMapType);
+            this.map = new TbGoogleMap($element, this.utils, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel, settings.gmApiKey, settings.gmDefaultMapType);
         } else if (mapProvider === 'openstreet-map') {
-            this.map = new TbOpenStreetMap($element, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel);
+            this.map = new TbOpenStreetMap($element, this.utils, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel);
         }
 
     }
@@ -274,9 +273,13 @@ export default class TbMapWidget {
                 if (!this.locationsSettings[i].useMarkerImageFunction &&
                     angular.isDefined(configuredLocationsSettings[i].markerImage) &&
                     configuredLocationsSettings[i].markerImage.length > 0) {
-                    this.locationsSettings[i].markerImage = configuredLocationsSettings[i].markerImage;
                     this.locationsSettings[i].useMarkerImage = true;
-                    this.locationsSettings[i].markerImageSize = configuredLocationsSettings[i].markerImageSize || 34;
+                    var url = this.ctx.settings.markerImage;
+                    var size = this.ctx.settings.markerImageSize || 34;
+                    this.locationSettings.currentImage = {
+                        url: url,
+                        size: size
+                    };
                 }
 
                 if (this.drawRoutes) {
@@ -380,17 +383,41 @@ export default class TbMapWidget {
             }
         }
 
-        function updateLocationMarkerImage(location, dataMap) {
+        function updateLocationMarkerIcon(location, dataMap) {
             var image = calculateLocationMarkerImage(location, dataMap);
-            if (image != null && (!location.settings.calculatedImage || !angular.equals(location.settings.calculatedImage, image))) {
-                tbMap.map.updateMarkerImage(location.marker, location.settings, image.url, image.size);
-                location.settings.calculatedImage = image;
+            if (image && (!location.settings.currentImage || !angular.equals(location.settings.currentImage, image))) {
+                location.settings.currentImage = image;
+                tbMap.map.updateMarkerIcon(location.marker, location.settings);
             }
         }
 
         function updateLocationStyle(location, dataMap) {
             updateLocationColor(location, dataMap);
-            updateLocationMarkerImage(location, dataMap);
+            updateLocationMarkerIcon(location, dataMap);
+        }
+
+        function createOrUpdateLocationMarker(location, markerLocation, dataMap) {
+            var changed = false;
+            if (!location.marker) {
+                var image = calculateLocationMarkerImage(location, dataMap);
+                if (image && (!location.settings.currentImage || !angular.equals(location.settings.currentImage, image))) {
+                    location.settings.currentImage = image;
+                }
+                location.marker = tbMap.map.createMarker(markerLocation, location.settings,
+                    function() {
+                        tbMap.callbacks.onLocationClick(location);
+                    }
+                );
+                tbMap.markers.push(location.marker);
+                changed = true;
+            } else {
+                var prevPosition = tbMap.map.getMarkerPosition(location.marker);
+                if (!prevPosition.equals(markerLocation)) {
+                    tbMap.map.setMarkerPosition(location.marker, markerLocation);
+                    changed = true;
+                }
+            }
+            return changed;
         }
 
         function updateLocation(location, data, dataMap) {
@@ -413,15 +440,7 @@ export default class TbMapWidget {
                         }
                         if (latLngs.length > 0) {
                             var markerLocation = latLngs[latLngs.length-1];
-                            if (!location.marker) {
-                                location.marker = tbMap.map.createMarker(markerLocation, location.settings,
-                                    function() {
-                                        tbMap.callbacks.onLocationClick(location);
-                                    }
-                                );
-                            } else {
-                                tbMap.map.setMarkerPosition(location.marker, markerLocation);
-                            }
+                            createOrUpdateLocationMarker(location, markerLocation, dataMap);
                         }
                         if (!location.polyline) {
                             location.polyline = tbMap.map.createPolyline(latLngs, location.settings);
@@ -439,18 +458,8 @@ export default class TbMapWidget {
                         lat = latData[latData.length-1][1];
                         lng = lngData[lngData.length-1][1];
                         latLng = tbMap.map.createLatLng(lat, lng);
-                        if (!location.marker) {
-                            location.marker = tbMap.map.createMarker(latLng, location.settings, function() {
-                                tbMap.callbacks.onLocationClick(location);
-                            });
-                            tbMap.markers.push(location.marker);
+                        if (createOrUpdateLocationMarker(location, latLng, dataMap)) {
                             locationChanged = true;
-                        } else {
-                            var prevPosition = tbMap.map.getMarkerPosition(location.marker);
-                            if (!prevPosition.equals(latLng)) {
-                                tbMap.map.setMarkerPosition(location.marker, latLng);
-                                locationChanged = true;
-                            }
                         }
                     }
                     updateLocationStyle(location, dataMap);
diff --git a/ui/src/app/widget/lib/map-widget2.js b/ui/src/app/widget/lib/map-widget2.js
index 44abb2b..6b589aa 100644
--- a/ui/src/app/widget/lib/map-widget2.js
+++ b/ui/src/app/widget/lib/map-widget2.js
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 import tinycolor from 'tinycolor2';
 
 import TbGoogleMap from './google-map';
@@ -75,11 +74,11 @@ export default class TbMapWidgetV2 {
         });
 
         if (mapProvider === 'google-map') {
-            this.map = new TbGoogleMap($element, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel, settings.gmApiKey, settings.gmDefaultMapType);
+            this.map = new TbGoogleMap($element, this.utils, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel, settings.gmApiKey, settings.gmDefaultMapType);
         } else if (mapProvider === 'openstreet-map') {
-            this.map = new TbOpenStreetMap($element, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel, settings.mapProvider);
+            this.map = new TbOpenStreetMap($element, this.utils,  initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel, settings.mapProvider);
         } else if (mapProvider === 'image-map') {
-            this.map = new TbImageMap(this.ctx, $element, initCallback,
+            this.map = new TbImageMap(this.ctx, $element, this.utils, initCallback,
                 settings.mapImageUrl,
                 settings.posFunction,
                 settings.imageEntityAlias,
@@ -159,9 +158,13 @@ export default class TbMapWidgetV2 {
         if (!this.locationSettings.useMarkerImageFunction &&
             angular.isDefined(this.ctx.settings.markerImage) &&
             this.ctx.settings.markerImage.length > 0) {
-            this.locationSettings.markerImage = this.ctx.settings.markerImage;
             this.locationSettings.useMarkerImage = true;
-            this.locationSettings.markerImageSize = this.ctx.settings.markerImageSize || 34;
+            var url = this.ctx.settings.markerImage;
+            var size = this.ctx.settings.markerImageSize || 34;
+            this.locationSettings.currentImage = {
+                url: url,
+                size: size
+            };
         }
 
         if (this.drawRoutes) {
@@ -235,10 +238,10 @@ export default class TbMapWidgetV2 {
             }
         }
 
-        function updateLocationMarkerImage(location, image) {
-            if (image && (!location.settings.calculatedImage || !angular.equals(location.settings.calculatedImage, image))) {
-                tbMap.map.updateMarkerImage(location.marker, location.settings, image.url, image.size);
-                location.settings.calculatedImage = image;
+        function updateLocationMarkerIcon(location, image) {
+            if (image && (!location.settings.currentImage || !angular.equals(location.settings.currentImage, image))) {
+                location.settings.currentImage = image;
+                tbMap.map.updateMarkerIcon(location.marker, location.settings);
             }
         }
 
@@ -247,7 +250,31 @@ export default class TbMapWidgetV2 {
             var color = calculateLocationColor(location, dataMap);
             var image = calculateLocationMarkerImage(location, dataMap);
             updateLocationColor(location, color, image);
-            updateLocationMarkerImage(location, image);
+            updateLocationMarkerIcon(location, image);
+        }
+
+        function createOrUpdateLocationMarker(location, markerLocation, dataMap) {
+            var changed = false;
+            if (!location.marker) {
+                var image = calculateLocationMarkerImage(location, dataMap);
+                if (image && (!location.settings.currentImage || !angular.equals(location.settings.currentImage, image))) {
+                    location.settings.currentImage = image;
+                }
+                location.marker = tbMap.map.createMarker(markerLocation, location.settings,
+                    function (event) {
+                        tbMap.callbacks.onLocationClick(location);
+                        locationRowClick(event, location);
+                    }, [location.dsIndex]);
+                tbMap.markers.push(location.marker);
+                changed = true;
+            } else {
+                var prevPosition = tbMap.map.getMarkerPosition(location.marker);
+                if (!prevPosition.equals(markerLocation)) {
+                    tbMap.map.setMarkerPosition(location.marker, markerLocation);
+                    changed = true;
+                }
+            }
+            return changed;
         }
 
         function locationRowClick($event, location) {
@@ -284,16 +311,7 @@ export default class TbMapWidgetV2 {
                         }
                         if (latLngs.length > 0) {
                             var markerLocation = latLngs[latLngs.length - 1];
-                            if (!location.marker) {
-                                location.marker = tbMap.map.createMarker(markerLocation, location.settings,
-                                    function (event) {
-                                        tbMap.callbacks.onLocationClick(location);
-                                        locationRowClick(event, location);
-                                    }, [location.dsIndex]
-                                );
-                            } else {
-                                tbMap.map.setMarkerPosition(location.marker, markerLocation);
-                            }
+                            createOrUpdateLocationMarker(location, markerLocation, dataMap);
                         }
                         if (!location.polyline) {
                             location.polyline = tbMap.map.createPolyline(latLngs, location.settings);
@@ -312,20 +330,8 @@ export default class TbMapWidgetV2 {
                         lng = lngData[lngData.length - 1][1];
                         if (angular.isDefined(lat) && lat != null && angular.isDefined(lng) && lng != null) {
                             latLng = tbMap.map.createLatLng(lat, lng);
-                            if (!location.marker) {
-                                location.marker = tbMap.map.createMarker(latLng, location.settings,
-                                    function (event) {
-                                        tbMap.callbacks.onLocationClick(location);
-                                        locationRowClick(event, location);
-                                    }, [location.dsIndex]);
-                                tbMap.markers.push(location.marker);
+                            if (createOrUpdateLocationMarker(location, latLng, dataMap)) {
                                 locationChanged = true;
-                            } else {
-                                var prevPosition = tbMap.map.getMarkerPosition(location.marker);
-                                if (!prevPosition.equals(latLng)) {
-                                    tbMap.map.setMarkerPosition(location.marker, latLng);
-                                    locationChanged = true;
-                                }
                             }
                         }
                     }
diff --git a/ui/src/app/widget/lib/openstreet-map.js b/ui/src/app/widget/lib/openstreet-map.js
index 1d1d3f7..8aafe69 100644
--- a/ui/src/app/widget/lib/openstreet-map.js
+++ b/ui/src/app/widget/lib/openstreet-map.js
@@ -13,15 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 import 'leaflet/dist/leaflet.css';
 import * as L from 'leaflet';
 import 'leaflet-providers';
 
 export default class TbOpenStreetMap {
 
-    constructor($containerElement, initCallback, defaultZoomLevel, dontFitMapBounds, minZoomLevel, mapProvider) {
+    constructor($containerElement, utils, initCallback, defaultZoomLevel, dontFitMapBounds, minZoomLevel, mapProvider) {
 
+        this.utils = utils;
         this.defaultZoomLevel = defaultZoomLevel;
         this.dontFitMapBounds = dontFitMapBounds;
         this.minZoomLevel = minZoomLevel;
@@ -54,55 +54,62 @@ export default class TbOpenStreetMap {
     }
 
     updateMarkerColor(marker, 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]
+        this.createDefaultMarkerIcon(marker, color, (iconInfo) => {
+            marker.setIcon(iconInfo.icon);
         });
-        marker.setIcon(icon);
-    }
-
-    updateMarkerImage(marker, settings, image, maxSize) {
-        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;
-            } else {
-                width = maxSize * aspect;
-                height = maxSize;
-            }
-            var icon = L.icon({
-                iconUrl: image,
-                iconSize: [width, height],
-                iconAnchor: [width/2, height],
-                popupAnchor: [0, -height]
-            });
-            marker.setIcon(icon);
+    }
+
+    updateMarkerIcon(marker, settings) {
+        this.createMarkerIcon(marker, settings, (iconInfo) => {
+            marker.setIcon(iconInfo.icon);
             if (settings.showLabel) {
                 marker.unbindTooltip();
-                marker.tooltipOffset = [0, -height + 10];
+                marker.tooltipOffset = [0, -iconInfo.size[1] + 10];
                 marker.bindTooltip('<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>',
                     { className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset });
             }
+        });
+    }
+
+    createMarkerIcon(marker, settings, onMarkerIconReady) {
+        var currentImage = settings.currentImage;
+        var opMap = this;
+        if (currentImage && currentImage.url) {
+            this.utils.loadImageAspect(currentImage.url).then(
+                (aspect) => {
+                    if (aspect) {
+                        var width;
+                        var height;
+                        if (aspect > 1) {
+                            width = currentImage.size;
+                            height = currentImage.size / aspect;
+                        } else {
+                            width = currentImage.size * aspect;
+                            height = currentImage.size;
+                        }
+                        var icon = L.icon({
+                            iconUrl: currentImage.url,
+                            iconSize: [width, height],
+                            iconAnchor: [width/2, height],
+                            popupAnchor: [0, -height]
+                        });
+                        var iconInfo = {
+                            size: [width, height],
+                            icon: icon
+                        };
+                        onMarkerIconReady(iconInfo);
+                    } else {
+                        opMap.createDefaultMarkerIcon(marker, settings.color, onMarkerIconReady);
+                    }
+                }
+            );
+        } else {
+            this.createDefaultMarkerIcon(marker, settings.color, onMarkerIconReady);
         }
-        document.body.appendChild(testImage); //eslint-disable-line
-        testImage.src = image;
     }
 
-    createMarker(location, settings, onClickListener, markerArgs) {
-        var height = 34;
-        var pinColor = settings.color.substr(1);
+    createDefaultMarkerIcon(marker, color, onMarkerIconReady) {
+        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],
@@ -112,18 +119,25 @@ export default class TbOpenStreetMap {
             shadowSize: [40, 37],
             shadowAnchor: [12, 35]
         });
+        var iconInfo = {
+            size: [21, 34],
+            icon: icon
+        };
+        onMarkerIconReady(iconInfo);
+    }
 
-        var marker = L.marker(location, {icon: icon}).addTo(this.map);
-
-        if (settings.showLabel) {
-            marker.tooltipOffset = [0, -height + 10];
-            marker.bindTooltip('<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>',
-                { className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset });
-        }
-
-        if (settings.useMarkerImage) {
-            this.updateMarkerImage(marker, settings, settings.markerImage, settings.markerImageSize || 34);
-        }
+    createMarker(location, settings, onClickListener, markerArgs) {
+        var marker = L.marker(location, {});
+        var opMap = this;
+        this.createMarkerIcon(marker, settings, (iconInfo) => {
+            marker.setIcon(iconInfo.icon);
+            if (settings.showLabel) {
+                marker.tooltipOffset = [0, -iconInfo.size[1] + 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.addTo(opMap.map);
+        });
 
         if (settings.displayTooltip) {
             this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, settings.autocloseTooltip, markerArgs);
diff --git a/ui/src/app/widget/lib/timeseries-table-widget.js b/ui/src/app/widget/lib/timeseries-table-widget.js
index 0c23e02..28ac811 100644
--- a/ui/src/app/widget/lib/timeseries-table-widget.js
+++ b/ui/src/app/widget/lib/timeseries-table-widget.js
@@ -47,9 +47,37 @@ function TimeseriesTableWidget() {
 /*@ngInject*/
 function TimeseriesTableWidgetController($element, $scope, $filter) {
     var vm = this;
+    let dateFormatFilter = 'yyyy-MM-dd HH:mm:ss';
 
     vm.sources = [];
     vm.sourceIndex = 0;
+    vm.defaultPageSize = 10;
+    vm.defaultSortOrder = '-0';
+    vm.query = {
+        "search": null
+    };
+
+    vm.enterFilterMode = enterFilterMode;
+    vm.exitFilterMode = exitFilterMode;
+
+    function enterFilterMode () {
+        vm.query.search = '';
+        vm.ctx.hideTitlePanel = true;
+    }
+
+    function exitFilterMode () {
+        vm.query.search = null;
+        vm.ctx.hideTitlePanel = false;
+    }
+
+    vm.searchAction = {
+        name: 'action.search',
+        show: true,
+        onAction: function() {
+            vm.enterFilterMode();
+        },
+        icon: 'search'
+    };
 
     $scope.$watch('vm.ctx', function() {
        if (vm.ctx) {
@@ -62,6 +90,7 @@ function TimeseriesTableWidgetController($element, $scope, $filter) {
     });
 
     function initialize() {
+        vm.ctx.widgetActions = [ vm.searchAction ];
         vm.showTimestamp = vm.settings.showTimestamp !== false;
         var origColor = vm.widgetConfig.color || 'rgba(0, 0, 0, 0.87)';
         var defaultColor = tinycolor(origColor);
@@ -108,6 +137,8 @@ function TimeseriesTableWidgetController($element, $scope, $filter) {
         cssParser.createStyleElement(namespace, cssString);
         $element.addClass(namespace);
 
+        vm.displayPagination = angular.isDefined(vm.settings.displayPagination) ? vm.settings.displayPagination : true;
+
         function hashCode(str) {
             var hash = 0;
             var i, char;
@@ -163,7 +194,7 @@ function TimeseriesTableWidgetController($element, $scope, $filter) {
 
     vm.cellContent = function(source, index, row, value) {
         if (index === 0) {
-            return $filter('date')(value, 'yyyy-MM-dd HH:mm:ss');
+            return $filter('date')(value, dateFormatFilter);
         } else {
             var strContent = '';
             if (angular.isDefined(value)) {
@@ -211,7 +242,7 @@ function TimeseriesTableWidgetController($element, $scope, $filter) {
                 source.data = [];
                 source.rawData = [];
                 source.query = {
-                    limit: 5,
+                    limit: vm.settings.defaultPageSize || 10,
                     page: 1,
                     order: '-0'
                 }
@@ -287,7 +318,30 @@ function TimeseriesTableWidgetController($element, $scope, $filter) {
     }
 
     function reorder(source) {
+        let searchRegExp = new RegExp(vm.query.search);
+
         source.data = $filter('orderBy')(source.data, source.query.order);
+        if (vm.query.search !== null) {
+            source.data = source.data.filter(function(item){
+                for (let i = 0; i < item.length; i++) {
+                    if (vm.showTimestamp) {
+                        if (i === 0) {
+                            if (searchRegExp.test($filter('date')(item[i], dateFormatFilter))) {
+                                return true;
+                            }
+                        } else {
+                            if (searchRegExp.test(item[i])) {
+                                return true;
+                            }
+                        }
+                    } else {
+                        if (searchRegExp.test(item[i])) {
+                            return true;
+                        }
+                    }
+                }
+            });
+        }
     }
 
     function convertData(data) {
diff --git a/ui/src/app/widget/lib/timeseries-table-widget.scss b/ui/src/app/widget/lib/timeseries-table-widget.scss
index 99a0653..da3ee81 100644
--- a/ui/src/app/widget/lib/timeseries-table-widget.scss
+++ b/ui/src/app/widget/lib/timeseries-table-widget.scss
@@ -26,4 +26,8 @@ tb-timeseries-table-widget {
     .md-table-pagination>* {
         height: 46px;
     }
+
+    .tb-data-table md-toolbar {
+        z-index: 10;
+    }
 }
diff --git a/ui/src/app/widget/lib/timeseries-table-widget.tpl.html b/ui/src/app/widget/lib/timeseries-table-widget.tpl.html
index 2b6d72c..2e7286c 100644
--- a/ui/src/app/widget/lib/timeseries-table-widget.tpl.html
+++ b/ui/src/app/widget/lib/timeseries-table-widget.tpl.html
@@ -15,29 +15,71 @@
     limitations under the License.
 
 -->
+<div class="tb-absolute-fill tb-entities-table tb-data-table timeseriesWidget" layout="column">
+    <div flex class="tb-absolute-fill" layout="column">
+        <md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search !== null">
+            <div class="md-toolbar-tools">
+                <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
+                    <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
+                    <md-tooltip md-direction="{{vm.ctx.dashboard.isWidgetExpanded ? 'bottom' : 'top'}}">
+                        {{'entity.search' | translate}}
+                    </md-tooltip>
+                </md-button>
+                <md-input-container flex>
+                    <label>&nbsp;</label>
+                    <input ng-model="vm.query.search" placeholder="{{'widget.search-data' | translate}}" md-autofocus/>
+                </md-input-container>
+                <md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
+                    <md-icon aria-label="Close" class="material-icons">close</md-icon>
+                    <md-tooltip md-direction="{{vm.ctx.dashboard.isWidgetExpanded ? 'bottom' : 'top'}}">
+                        {{ 'action.close' | translate }}
+                    </md-tooltip>
+                </md-button>
+            </div>
+        </md-toolbar>
 
-<md-tabs md-selected="vm.sourceIndex" ng-class="{'tb-headless': vm.sources.length === 1}"
-         id="tabs" md-border-bottom flex class="tb-absolute-fill">
-    <md-tab ng-repeat="source in vm.sources" label="{{ source.datasource.name }}">
-        <md-table-container>
-            <table md-table>
-                <thead md-head md-order="source.query.order" md-on-reorder="vm.onReorder(source)">
-                <tr md-row>
-                    <th ng-show="vm.showTimestamp" md-column md-order-by="0"><span>Timestamp</span></th>
-                    <th md-column md-order-by="{{ h.index }}" ng-repeat="h in source.ts.header"><span>{{ h.dataKey.label }}</span></th>
-                </tr>
-                </thead>
-                <tbody md-body>
-                <tr md-row ng-repeat="row in source.ts.data">
-                    <td ng-show="$index > 0 || ($index === 0 && vm.showTimestamp)" md-cell ng-repeat="d in row track by $index" ng-style="vm.cellStyle(source, $index, d)" ng-bind-html="vm.cellContent(source, $index, row, d)">
-                    </td>
-                </tr>
-                </tbody>
-            </table>
-        </md-table-container>
-        <md-table-pagination md-limit="source.query.limit" md-limit-options="[5, 10, 15]"
-                             md-page="source.query.page" md-total="{{source.ts.count}}"
-                             md-on-paginate="vm.onPaginate(source)" md-page-select>
-        </md-table-pagination>
-    </md-tab>
-</md-tabs>
\ No newline at end of file
+        <md-tabs flex md-selected="vm.sourceIndex" ng-class="{'tb-headless': vm.sources.length === 1}"
+                 id="tabs" md-border-bottom flex>
+            <md-tab ng-repeat="source in vm.sources" label="{{ source.datasource.name }}">
+                <md-table-container>
+                    <table md-table>
+                        <thead md-head md-order="source.query.order" md-on-reorder="vm.onReorder(source)">
+                            <tr md-row>
+                                <th ng-show="vm.showTimestamp"
+                                    md-column md-order-by="0"
+                                >
+                                    <span>Timestamp</span>
+                                </th>
+                                <th md-column
+                                    md-order-by="{{ h.index }}"
+                                    ng-repeat="h in source.ts.header"
+                                >
+                                    <span>{{ h.dataKey.label }}</span>
+                                </th>
+                            </tr>
+                        </thead>
+
+                        <tbody md-body>
+                            <tr md-row ng-repeat="row in source.ts.data track by $index">
+                                <td ng-show="$index > 0 || ($index === 0 && vm.showTimestamp)"
+                                    md-cell
+                                    ng-repeat="d in row track by $index"
+                                    ng-style="vm.cellStyle(source, $index, d)"
+                                    ng-bind-html="vm.cellContent(source, $index, row, d)"
+                                ></td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </md-table-container>
+                <md-table-pagination ng-if="vm.displayPagination"
+                                     md-limit="source.query.limit"
+                                     md-limit-options="vm.limitOptions"
+                                     md-page="source.query.page"
+                                     md-total="{{source.data.length}}"
+                                     md-on-paginate="vm.onPaginate(source)"
+                                     md-page-select>
+                </md-table-pagination>
+            </md-tab>
+        </md-tabs>
+    </div>
+</div>
\ No newline at end of file