{
  "widgetsBundle": {
    "alias": "cards",
    "title": "Cards",
    "image": null
  },
  "widgetTypes": [
    {
      "alias": "attributes_card",
      "name": "Attributes card",
      "descriptor": {
        "type": "latest",
        "sizeX": 7.5,
        "sizeY": 3,
        "resources": [],
        "templateHtml": "",
        "templateCss": "#container {\n    overflow: auto;\n}\n\n.tbDatasource-container {\n    margin: 5px;\n    padding: 8px;\n}\n\n.tbDatasource-title {\n    font-size: 1.200rem;\n    font-weight: 500;\n    padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n    width: 100%;\n    box-shadow: 0 0 10px #ccc;\n    border-collapse: collapse;\n    white-space: nowrap;\n    font-size: 1.000rem;\n    color: #757575;\n}\n\n.tbDatasource-table td {\n    position: relative;\n    border-top: 1px solid rgba(0, 0, 0, 0.12);\n    border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n    padding: 0px 18px;\n    box-sizing: border-box;\n}",
        "controllerScript": "self.onInit = function() {\n    \n    self.ctx.datasourceTitleCells = [];\n    self.ctx.valueCells = [];\n    self.ctx.labelCells = [];\n    \n    for (var i=0; i < self.ctx.datasources.length; i++) {\n        var tbDatasource = self.ctx.datasources[i];\n\n        var datasourceId = 'tbDatasource' + i;\n        self.ctx.$container.append(\n            \"<div id='\" + datasourceId +\n            \"' class='tbDatasource-container'></div>\"\n        );\n\n        var datasourceContainer = $('#' + datasourceId,\n            self.ctx.$container);\n\n        datasourceContainer.append(\n            \"<div class='tbDatasource-title'>\" +\n            tbDatasource.name + \"</div>\"\n        );\n        \n        var datasourceTitleCell = $('.tbDatasource-title', datasourceContainer);\n        self.ctx.datasourceTitleCells.push(datasourceTitleCell);\n        \n        var tableId = 'table' + i;\n        datasourceContainer.append(\n            \"<table id='\" + tableId +\n            \"' class='tbDatasource-table'><col width='30%'><col width='70%'></table>\"\n        );\n        var table = $('#' + tableId, self.ctx.$container);\n\n        for (var a = 0; a < tbDatasource.dataKeys.length; a++) {\n            var dataKey = tbDatasource.dataKeys[a];\n            var labelCellId = 'labelCell' + a;\n            var cellId = 'cell' + a;\n            table.append(\"<tr><td id='\" + labelCellId + \"'>\" + dataKey.label +\n                \"</td><td id='\" + cellId +\n                \"'></td></tr>\");\n            var labelCell = $('#' + labelCellId, table);\n            self.ctx.labelCells.push(labelCell);\n            var valueCell = $('#' + cellId, table);\n            self.ctx.valueCells.push(valueCell);\n        }\n    }    \n    \n    self.onResize();\n}\n\nself.onDataUpdated = function() {\n    for (var i = 0; i < self.ctx.valueCells.length; i++) {\n        var cellData = self.ctx.data[i];\n        if (cellData && cellData.data && cellData.data.length > 0) {\n            var tvPair = cellData.data[cellData.data.length -\n                1];\n            var value = tvPair[1];\n            self.ctx.valueCells[i].html(value);\n        }\n    }    \n}\n\nself.onResize = function() {\n    var datasoirceTitleFontSize = self.ctx.height/8;\n    if (self.ctx.width/self.ctx.height <= 1.5) {\n        datasoirceTitleFontSize = self.ctx.width/12;\n    }\n    datasoirceTitleFontSize = Math.min(datasoirceTitleFontSize, 20);\n    for (var i = 0; i < self.ctx.datasourceTitleCells.length; i++) {\n        self.ctx.datasourceTitleCells[i].css('font-size', datasoirceTitleFontSize+'px');\n    }\n    var valueFontSize = self.ctx.height/9;\n    var labelFontSize = self.ctx.height/9;\n    if (self.ctx.width/self.ctx.height <= 1.5) {\n        valueFontSize = self.ctx.width/15;\n        labelFontSize = self.ctx.width/15;\n    }\n    valueFontSize = Math.min(valueFontSize, 18);\n    labelFontSize = Math.min(labelFontSize, 18);\n\n    for (i = 0; i < self.ctx.valueCells; i++) {\n        self.ctx.valueCells[i].css('font-size', valueFontSize+'px');\n        self.ctx.valueCells[i].css('height', valueFontSize*2.5+'px');\n        self.ctx.valueCells[i].css('padding', '0px ' + valueFontSize + 'px');\n        self.ctx.labelCells[i].css('font-size', labelFontSize+'px');\n        self.ctx.labelCells[i].css('height', labelFontSize*2.5+'px');\n        self.ctx.labelCells[i].css('padding', '0px ' + labelFontSize + 'px');\n    }    \n}\n\nself.onDestroy = function() {\n}\n",
        "settingsSchema": "{}",
        "dataKeySettingsSchema": "{}\n",
        "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Attributes card\"}"
      }
    },
    {
      "alias": "entities_table",
      "name": "Entities table",
      "descriptor": {
        "type": "latest",
        "sizeX": 7.5,
        "sizeY": 4.5,
        "resources": [],
        "templateHtml": "<tb-entities-table-widget \n    table-id=\"tableId\"\n    ctx=\"ctx\">\n</tb-entities-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('entities-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.typeParameters = function() {\n    return {\n        maxDatasources: 1,\n        dataKeysOptional: true\n    };\n}\n\nself.actionSources = function() {\n    return {\n        'actionCellButton': {\n            name: 'widget-action.action-cell-button',\n            multiple: true\n        },\n        'rowClick': {\n            name: 'widget-action.row-click',\n            multiple: false\n        }\n    };\n}\n\nself.onDestroy = function() {\n}\n",
        "settingsSchema": "{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"EntitiesTableSettings\",\n        \"properties\": {\n            \"entitiesTitle\": {\n                \"title\": \"Entities table title\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"enableSearch\": {\n                \"title\": \"Enable entities search\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"displayEntityName\": {\n                \"title\": \"Display entity name column\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"entityNameColumnTitle\": {\n                \"title\": \"Entity name column title\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"displayEntityType\": {\n                \"title\": \"Display entity type 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            \"defaultSortOrder\": {\n                \"title\": \"Default sort order\",\n                \"type\": \"string\",\n                \"default\": \"entityName\"\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"entitiesTitle\",\n        \"enableSearch\",\n        \"displayEntityName\",\n        \"entityNameColumnTitle\",\n        \"displayEntityType\",\n        \"displayPagination\",\n        \"defaultPageSize\",\n        \"defaultSortOrder\"\n    ]\n}",
        "dataKeySettingsSchema": "{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"DataKeySettings\",\n        \"properties\": {\n            \"columnWidth\": {\n                \"title\": \"Column width (px or %)\",\n                \"type\": \"string\",\n                \"default\": \"0px\"\n            },\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, entity, filter)\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"columnWidth\",\n        \"useCellStyleFunction\",\n        {\n            \"key\": \"cellStyleFunction\",\n            \"type\": \"javascript\"\n        },\n        \"useCellContentFunction\",\n        {\n            \"key\": \"cellContentFunction\",\n            \"type\": \"javascript\"\n        }\n    ]\n}",
        "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true},\"title\":\"Entities table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"18px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}]}"
      }
    },
    {
      "alias": "html_card",
      "name": "HTML Card",
      "descriptor": {
        "type": "static",
        "sizeX": 7.5,
        "sizeY": 3,
        "resources": [],
        "templateHtml": "",
        "templateCss": "",
        "controllerScript": "self.onInit = function() {\n\n    var cssParser = new cssjs();\n    cssParser.testMode = false;\n    var namespace = 'html-card-' + hashCode(self.ctx.settings.cardCss);\n    cssParser.cssPreviewNamespace = namespace;\n    cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n    self.ctx.$container.addClass(namespace);\n    cardHtml = self.ctx.settings.cardHtml;\n    self.ctx.$container.html(cardHtml);\n    \n    function hashCode(str) {\n        var hash = 0;\n        var i, char;\n        if (str.length === 0) return hash;\n        for (i = 0; i < str.length; i++) {\n            char = str.charCodeAt(i);\n            hash = ((hash << 5) - hash) + char;\n            hash = hash & hash;\n        }\n        return hash;\n    }\n}\n\nself.onDestroy = function() {\n}\n",
        "settingsSchema": "{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"required\": [\"cardHtml\"],\n        \"properties\": {\n            \"cardCss\": {\n                \"title\": \"CSS\",\n                \"type\": \"string\",\n                \"default\": \".card {\\n font-weight: bold; \\n}\"\n            },\n            \"cardHtml\": {\n                \"title\": \"HTML\",\n                \"type\": \"string\",\n                \"default\": \"<div class='card'>HTML code here</div>\"\n            }\n        }\n    },\n    \"form\": [\n        {\n            \"key\": \"cardCss\",\n            \"type\": \"css\"\n        },           \n        {\n            \"key\": \"cardHtml\",\n            \"type\": \"html\"\n        }    \n    ]\n}",
        "dataKeySettingsSchema": "{}\n",
        "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"cardHtml\":\"<div class='card'>HTML code here</div>\",\"cardCss\":\".card {\\n    font-weight: bold;\\n    font-size: 32px;\\n    color: #999;\\n    width: 100%;\\n    height: 100%;\\n    display: flex;\\n    align-items: center;\\n    justify-content: center;\\n}\"},\"title\":\"HTML Card\",\"dropShadow\":true}"
      }
    },
    {
      "alias": "html_value_card",
      "name": "HTML Value Card",
      "descriptor": {
        "type": "latest",
        "sizeX": 7.5,
        "sizeY": 3,
        "resources": [],
        "templateHtml": "",
        "templateCss": "",
        "controllerScript": "self.onInit = function() {\n    self.ctx.varsRegex = /\\$\\{([^\\}]*)\\}/g;\n    self.ctx.htmlSet = false;\n    \n    var cssParser = new cssjs();\n    cssParser.testMode = false;\n    var namespace = 'html-value-card-' + hashCode(self.ctx.settings.cardCss);\n    cssParser.cssPreviewNamespace = namespace;\n    cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n    self.ctx.$container.addClass(namespace);\n    self.ctx.html = self.ctx.settings.cardHtml;\n    self.ctx.replaceInfo = processHtmlPattern(self.ctx.html, self.ctx.data);\n    \n    updateHtml();\n    \n    function hashCode(str) {\n        var hash = 0;\n        var i, char;\n        if (str.length === 0) return hash;\n        for (i = 0; i < str.length; i++) {\n            char = str.charCodeAt(i);\n            hash = ((hash << 5) - hash) + char;\n            hash = hash & hash;\n        }\n        return hash;\n    }\n    \n    function processHtmlPattern(pattern, data) {\n        var match = self.ctx.varsRegex.exec(pattern);\n        var replaceInfo = {};\n        replaceInfo.variables = [];\n        while (match !== null) {\n            var variableInfo = {};\n            variableInfo.dataKeyIndex = -1;\n            var variable = match[0];\n            var label = match[1];\n            var valDec = 2;\n            var splitVals = label.split(':');\n            if (splitVals.length > 1) {\n                label = splitVals[0];\n                valDec = parseFloat(splitVals[1]);\n            }\n            variableInfo.variable = variable;\n            variableInfo.valDec = valDec;\n            if (label == 'entityName') {\n                variableInfo.isEntityName = true;\n            } else if (label.startsWith('#')) {\n                var keyIndexStr = label.substring(1);\n                var n = Math.floor(Number(keyIndexStr));\n                if (String(n) === keyIndexStr && n >= 0) {\n                    variableInfo.dataKeyIndex = n;\n                }\n            }\n            if (!variableInfo.isEntityName && variableInfo.dataKeyIndex === -1) {\n                for (var i = 0; i < data.length; i++) {\n                     var datasourceData = data[i];\n                     var dataKey = datasourceData.dataKey;\n                     if (dataKey.label === label) {\n                         variableInfo.dataKeyIndex = i;\n                         break;\n                     }\n                }\n            }\n            replaceInfo.variables.push(variableInfo);\n            match = self.ctx.varsRegex.exec(pattern);\n        }\n        return replaceInfo;\n    }    \n}\n\nself.onDataUpdated = function() {\n    updateHtml();\n}\n\nself.onDestroy = function() {\n}\n\nfunction isNumber(n) {\n    return !isNaN(parseFloat(n)) && isFinite(n);\n}\n\nfunction padValue(val, dec, int) {\n    var i = 0;\n    var s, strVal, n;\n\n    val = parseFloat(val);\n    n = (val < 0);\n    val = Math.abs(val);\n\n    if (dec > 0) {\n        strVal = val.toFixed(dec).toString().split('.');\n        s = int - strVal[0].length;\n\n        for (; i < s; ++i) {\n            strVal[0] = '0' + strVal[0];\n        }\n\n        strVal = (n ? '-' : '') + strVal[0] + '.' + strVal[1];\n    }\n\n    else {\n        strVal = Math.round(val).toString();\n        s = int - strVal.length;\n\n        for (; i < s; ++i) {\n            strVal = '0' + strVal;\n        }\n\n        strVal = (n ? '-' : '') + strVal;\n    }\n\n    return strVal;\n}\n\nfunction updateHtml() {\n    var text = self.ctx.html;\n    var updated = false;\n    for (var v in self.ctx.replaceInfo.variables) {\n        var variableInfo = self.ctx.replaceInfo.variables[v];\n        var txtVal = '';\n        if (variableInfo.dataKeyIndex > -1) {\n            var varData = self.ctx.data[variableInfo.dataKeyIndex].data;\n            if (varData.length > 0) {\n                var val = varData[varData.length-1][1];\n                if (isNumber(val)) {\n                    txtVal = padValue(val, variableInfo.valDec, 0);\n                } else {\n                    txtVal = val;\n                }\n            }\n        } else if (variableInfo.isEntityName) {\n            if (self.ctx.defaultSubscription.datasources.length) {\n                txtVal = self.ctx.defaultSubscription.datasources[0].entityName;\n            } else {\n                txtVal = 'Unknown';\n            }\n        }\n        if (typeof variableInfo.lastVal === undefined ||\n            variableInfo.lastVal !== txtVal) {\n            updated = true;\n            variableInfo.lastVal = txtVal;\n        }\n        text = text.split(variableInfo.variable).join(txtVal);\n    }\n    if (updated || !self.ctx.htmlSet) {\n        self.ctx.$container.html(text);\n        if (!self.ctx.htmlSet) {\n            self.ctx.htmlSet = true;\n        }\n    }\n}\n\n",
        "settingsSchema": "{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"required\": [\"cardHtml\"],\n        \"properties\": {\n            \"cardCss\": {\n                \"title\": \"CSS\",\n                \"type\": \"string\",\n                \"default\": \".card {\\n font-weight: bold; \\n}\"\n            },\n            \"cardHtml\": {\n                \"title\": \"HTML\",\n                \"type\": \"string\",\n                \"default\": \"<div class='card'>HTML code here</div>\"\n            }\n        }\n    },\n    \"form\": [\n        {\n            \"key\": \"cardCss\",\n            \"type\": \"css\"\n        },           \n        {\n            \"key\": \"cardHtml\",\n            \"type\": \"html\"\n        }    \n    ]\n}",
        "dataKeySettingsSchema": "{}\n",
        "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"My value\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.random() * 5.45;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"cardCss\":\".card {\\n   width: 100%;\\n   height: 100%;\\n   border: 2px solid #ccc;\\n   box-sizing: border-box;\\n}\\n\\n.card .content {\\n   padding: 20px;\\n   display: flex;\\n   flex-direction: row;\\n   align-items: center;\\n   justify-content: space-around;\\n   height: 100%;\\n   box-sizing: border-box;\\n}\\n\\n.card .content .column {\\n   display: flex;\\n   flex-direction: column;    \\n   justify-content: space-around;\\n   height: 100%;\\n}\\n\\n.card h1 {\\n    text-transform: uppercase;\\n    color: #999;\\n    font-size: 20px;\\n    font-weight: bold;\\n    margin: 0;\\n    padding-bottom: 10px;\\n    line-height: 32px;\\n}\\n\\n.card .value {\\n    font-size: 38px;\\n    font-weight: 200;\\n}\\n\\n.card .description {\\n    font-size: 20px;\\n    color: #999;\\n}\\n\",\"cardHtml\":\"<div class='card'>\\n    <div class='content'>\\n        <div class='column'>\\n            <h1>Value title</h1>\\n            <div class='value'>\\n                ${My value:2} units.\\n            </div>    \\n            <div class='description'>\\n                Value description text\\n            </div>\\n        </div>\\n        <img height=\\\"80px\\\" src=\\\"https://thingsboard.io/images/logo_small.png\\\" />\\n    </div>\\n</div>\"},\"title\":\"HTML Value Card\",\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
      }
    },
    {
      "alias": "label_widget",
      "name": "Label widget",
      "descriptor": {
        "type": "latest",
        "sizeX": 4.5,
        "sizeY": 5,
        "resources": [],
        "templateHtml": "",
        "templateCss": "#container {\n    overflow: auto;\n}\n\n.tbDatasource-container {\n    margin: 5px;\n    padding: 8px;\n}\n\n.tbDatasource-title {\n    font-size: 1.200rem;\n    font-weight: 500;\n    padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n    width: 100%;\n    box-shadow: 0 0 10px #ccc;\n    border-collapse: collapse;\n    white-space: nowrap;\n    font-size: 1.000rem;\n    color: #757575;\n}\n\n.tbDatasource-table td {\n    position: relative;\n    border-top: 1px solid rgba(0, 0, 0, 0.12);\n    border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n    padding: 0px 18px;\n    box-sizing: border-box;\n}",
        "controllerScript": "self.onInit = function() {\n    self.ctx.varsRegex = /\\$\\{([^\\}]*)\\}/g;\n    \n    var imageUrl = self.ctx.settings.backgroundImageUrl ? self.ctx.settings.backgroundImageUrl :\n    'data:image/svg+xml;base64,PHN2ZyBpZD0ic3ZnMiIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAwIiB3aWR0aD0iMTAwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgdmlld0JveD0iMCAwIDEwMCAxMDAiPgogPGcgaWQ9ImxheWVyMSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtOTUyLjM2KSI+CiAgPHJlY3QgaWQ9InJlY3Q0Njg0IiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBoZWlnaHQ9Ijk5LjAxIiB3aWR0aD0iOTkuMDEiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiB5PSI5NTIuODYiIHg9Ii40OTUwNSIgc3Ryb2tlLXdpZHRoPSIuOTkwMTAiIGZpbGw9IiNlZWUiLz4KICA8dGV4dCBpZD0idGV4dDQ2ODYiIHN0eWxlPSJ3b3JkLXNwYWNpbmc6MHB4O2xldHRlci1zcGFjaW5nOjBweDt0ZXh0LWFuY2hvcjptaWRkbGU7dGV4dC1hbGlnbjpjZW50ZXIiIGZvbnQtd2VpZ2h0PSJib2xkIiB4bWw6c3BhY2U9InByZXNlcnZlIiBmb250LXNpemU9IjEwcHgiIGxpbmUtaGVpZ2h0PSIxMjUlIiB5PSI5NzAuNzI4MDkiIHg9IjQ5LjM5NjQ3NyIgZm9udC1mYW1pbHk9IlJvYm90byIgZmlsbD0iIzY2NjY2NiI+PHRzcGFuIGlkPSJ0c3BhbjQ2OTAiIHg9IjUwLjY0NjQ3NyIgeT0iOTcwLjcyODA5Ij5JbWFnZSBiYWNrZ3JvdW5kIDwvdHNwYW4+PHRzcGFuIGlkPSJ0c3BhbjQ2OTIiIHg9IjQ5LjM5NjQ3NyIgeT0iOTgzLjIyODA5Ij5pcyBub3QgY29uZmlndXJlZDwvdHNwYW4+PC90ZXh0PgogIDxyZWN0IGlkPSJyZWN0NDY5NCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgaGVpZ2h0PSIxOS4zNiIgd2lkdGg9IjY5LjM2IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgeT0iOTkyLjY4IiB4PSIxNS4zMiIgc3Ryb2tlLXdpZHRoPSIuNjM5ODYiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+Cg==';\n\n    self.ctx.$container.css('background', 'url(\"'+imageUrl+'\") no-repeat');\n    self.ctx.$container.css('backgroundSize', 'contain');\n    self.ctx.$container.css('backgroundPosition', '50% 50%');\n    \n    function processLabelPattern(pattern, data) {\n        var match = self.ctx.varsRegex.exec(pattern);\n        var replaceInfo = {};\n        replaceInfo.variables = [];\n        while (match !== null) {\n            var variableInfo = {};\n            variableInfo.dataKeyIndex = -1;\n            var variable = match[0];\n            var label = match[1];\n            var valDec = 2;\n            var splitVals = label.split(':');\n            if (splitVals.length > 1) {\n                label = splitVals[0];\n                valDec = parseFloat(splitVals[1]);\n            }\n            variableInfo.variable = variable;\n            variableInfo.valDec = valDec;\n            \n            if (label.startsWith('#')) {\n                var keyIndexStr = label.substring(1);\n                var n = Math.floor(Number(keyIndexStr));\n                if (String(n) === keyIndexStr && n >= 0) {\n                    variableInfo.dataKeyIndex = n;\n                }\n            }\n            if (variableInfo.dataKeyIndex === -1) {\n                for (var i = 0; i < data.length; i++) {\n                     var datasourceData = data[i];\n                     var dataKey = datasourceData.dataKey;\n                     if (dataKey.label === label) {\n                         variableInfo.dataKeyIndex = i;\n                         break;\n                     }\n                }\n            }\n            replaceInfo.variables.push(variableInfo);\n            match = self.ctx.varsRegex.exec(pattern);\n        }\n        return replaceInfo;\n    }\n\n    var configuredLabels = self.ctx.settings.labels;\n    if (!configuredLabels) {\n        configuredLabels = [];\n    }\n    \n    self.ctx.labels = [];\n\n    for (var l = 0; l < configuredLabels.length; l++) {\n        var labelConfig = configuredLabels[l];\n        var localConfig = {};\n        localConfig.font = {};\n        \n        localConfig.pattern = labelConfig.pattern ? labelConfig.pattern : '${#0}';\n        localConfig.x = labelConfig.x ? labelConfig.x : 0;\n        localConfig.y = labelConfig.y ? labelConfig.y : 0;\n        localConfig.backgroundColor = labelConfig.backgroundColor ? labelConfig.backgroundColor : 'rgba(0,0,0,0)';\n        \n        var settingsFont = labelConfig.font;\n        if (!settingsFont) {\n            settingsFont = {};\n        }\n        \n        localConfig.font.family = settingsFont.family || 'Roboto';\n        localConfig.font.size = settingsFont.size ? settingsFont.size : 6;\n        localConfig.font.style = settingsFont.style ? settingsFont.style : 'normal';\n        localConfig.font.weight = settingsFont.weight ? settingsFont.weight : '500';\n        localConfig.font.color = settingsFont.color ? settingsFont.color : '#fff';\n        \n        localConfig.replaceInfo = processLabelPattern(localConfig.pattern, self.ctx.data);\n        \n        var label = {};\n        var labelElement = $('<div/>');\n        labelElement.css('position', 'absolute');\n        labelElement.css('display', 'none');\n        labelElement.css('top', '0');\n        labelElement.css('left', '0');\n        labelElement.css('backgroundColor', localConfig.backgroundColor);\n        labelElement.css('color', localConfig.font.color);\n        labelElement.css('fontFamily', localConfig.font.family);\n        labelElement.css('fontStyle', localConfig.font.style);\n        labelElement.css('fontWeight', localConfig.font.weight);\n        \n        labelElement.html(localConfig.pattern);\n        self.ctx.$container.append(labelElement);\n        label.element = labelElement;\n        label.config = localConfig;\n        label.htmlSet = false;\n        label.visible = false;\n        self.ctx.labels.push(label);\n    }\n\n    var bgImg = $('<img />');\n    bgImg.hide();\n    bgImg.bind('load', function()\n    {\n        self.ctx.bImageHeight = $(this).height();\n        self.ctx.bImageWidth = $(this).width();\n        self.onResize();\n    });\n    self.ctx.$container.append(bgImg);\n    bgImg.attr('src', imageUrl);\n    \n    self.onDataUpdated();\n}\n\nself.onDataUpdated = function() {\n    updateLabels();\n}\n\nself.onResize = function() {\n    if (self.ctx.bImageHeight && self.ctx.bImageWidth) {\n        var backgroundRect = {};\n        var imageRatio = self.ctx.bImageWidth / self.ctx.bImageHeight;\n        var componentRatio = self.ctx.width / self.ctx.height;\n        if (componentRatio >= imageRatio) {\n            backgroundRect.top = 0;\n            backgroundRect.bottom = 1.0;\n            backgroundRect.xRatio = imageRatio / componentRatio;\n            backgroundRect.yRatio = 1;\n            var offset = (1 - backgroundRect.xRatio) / 2;\n            backgroundRect.left = offset;\n            backgroundRect.right = 1 - offset;\n        } else {\n            backgroundRect.left = 0;\n            backgroundRect.right = 1.0;\n            backgroundRect.xRatio = 1;\n            backgroundRect.yRatio = componentRatio / imageRatio;\n            var offset = (1 - backgroundRect.yRatio) / 2;\n            backgroundRect.top = offset;\n            backgroundRect.bottom = 1 - offset;\n        }\n        for (var l = 0; l < self.ctx.labels.length; l++) {\n            var label = self.ctx.labels[l];\n            var labelLeft = backgroundRect.left*100 + (label.config.x*backgroundRect.xRatio);\n            var labelTop = backgroundRect.top*100 + (label.config.y*backgroundRect.yRatio);\n            var fontSize = self.ctx.height * backgroundRect.yRatio * label.config.font.size / 100;\n            label.element.css('top', labelTop + '%');\n            label.element.css('left', labelLeft + '%');\n            label.element.css('fontSize', fontSize + 'px');\n            if (!label.visible) {\n                label.element.css('display', 'block');\n                label.visible = true;\n            }\n        }\n    }    \n}\n\n\nfunction isNumber(n) {\n    return !isNaN(parseFloat(n)) && isFinite(n);\n}\n\nfunction padValue(val, dec, int) {\n    var i = 0;\n    var s, strVal, n;\n\n    val = parseFloat(val);\n    n = (val < 0);\n    val = Math.abs(val);\n\n    if (dec > 0) {\n        strVal = val.toFixed(dec).toString().split('.');\n        s = int - strVal[0].length;\n\n        for (; i < s; ++i) {\n            strVal[0] = '0' + strVal[0];\n        }\n\n        strVal = (n ? '-' : '') + strVal[0] + '.' + strVal[1];\n    }\n\n    else {\n        strVal = Math.round(val).toString();\n        s = int - strVal.length;\n\n        for (; i < s; ++i) {\n            strVal = '0' + strVal;\n        }\n\n        strVal = (n ? '-' : '') + strVal;\n    }\n\n    return strVal;\n}\n\nfunction updateLabels() {\n    for (var l = 0; l < self.ctx.labels.length; l++) {\n        var label = self.ctx.labels[l];\n        var text = label.config.pattern;\n        var replaceInfo = label.config.replaceInfo;\n        var updated = false;\n        for (var v = 0; v < replaceInfo.variables.length; v++) {\n            var variableInfo = replaceInfo.variables[v];\n            var txtVal = '';\n            if (variableInfo.dataKeyIndex > -1) {\n                var varData = self.ctx.data[variableInfo.dataKeyIndex].data;\n                if (varData.length > 0) {\n                    var val = varData[varData.length-1][1];\n                    if (isNumber(val)) {\n                        txtVal = padValue(val, variableInfo.valDec, 0);\n                        updated = true;\n                    } else {\n                        txtVal = val;\n                        updated = true;\n                    }\n                }\n            }\n            text = text.split(variableInfo.variable).join(txtVal);\n        }\n        if (updated || !label.htmlSet) {\n            label.element.html(text);\n            if (!label.htmlSet) {\n                label.htmlSet = true;\n            }\n        }\n    }\n}\n\nself.onDestroy = function() {\n}\n",
        "settingsSchema": "{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"required\": [\"backgroundImageUrl\"],\n        \"properties\": {\n            \"backgroundImageUrl\": {\n                \"title\": \"Background image\",\n                \"type\": \"string\",\n                \"default\": \"data:image/svg+xml;base64,PHN2ZyBpZD0ic3ZnMiIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAwIiB3aWR0aD0iMTAwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgdmlld0JveD0iMCAwIDEwMCAxMDAiPgogPGcgaWQ9ImxheWVyMSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtOTUyLjM2KSI+CiAgPHJlY3QgaWQ9InJlY3Q0Njg0IiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBoZWlnaHQ9Ijk5LjAxIiB3aWR0aD0iOTkuMDEiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiB5PSI5NTIuODYiIHg9Ii40OTUwNSIgc3Ryb2tlLXdpZHRoPSIuOTkwMTAiIGZpbGw9IiNlZWUiLz4KICA8dGV4dCBpZD0idGV4dDQ2ODYiIHN0eWxlPSJ3b3JkLXNwYWNpbmc6MHB4O2xldHRlci1zcGFjaW5nOjBweDt0ZXh0LWFuY2hvcjptaWRkbGU7dGV4dC1hbGlnbjpjZW50ZXIiIGZvbnQtd2VpZ2h0PSJib2xkIiB4bWw6c3BhY2U9InByZXNlcnZlIiBmb250LXNpemU9IjEwcHgiIGxpbmUtaGVpZ2h0PSIxMjUlIiB5PSI5NzAuNzI4MDkiIHg9IjQ5LjM5NjQ3NyIgZm9udC1mYW1pbHk9IlJvYm90byIgZmlsbD0iIzY2NjY2NiI+PHRzcGFuIGlkPSJ0c3BhbjQ2OTAiIHg9IjUwLjY0NjQ3NyIgeT0iOTcwLjcyODA5Ij5JbWFnZSBiYWNrZ3JvdW5kIDwvdHNwYW4+PHRzcGFuIGlkPSJ0c3BhbjQ2OTIiIHg9IjQ5LjM5NjQ3NyIgeT0iOTgzLjIyODA5Ij5pcyBub3QgY29uZmlndXJlZDwvdHNwYW4+PC90ZXh0PgogIDxyZWN0IGlkPSJyZWN0NDY5NCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgaGVpZ2h0PSIxOS4zNiIgd2lkdGg9IjY5LjM2IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgeT0iOTkyLjY4IiB4PSIxNS4zMiIgc3Ryb2tlLXdpZHRoPSIuNjM5ODYiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+Cg==\"\n            },\n            \"labels\": {\n                \"title\": \"Labels\",\n                \"type\": \"array\",\n                \"items\": {\n                   \"title\": \"Label\",\n                   \"type\": \"object\",\n                   \"required\": [\"pattern\"],\n                   \"properties\": {\n                       \"pattern\": {\n                           \"title\": \"Pattern ( for ex. 'Text ${keyName} units.' or '${#<key index>} units'  )\",\n                           \"type\": \"string\",\n                           \"default\": \"${#0}\"\n                       },\n                       \"x\": {\n                           \"title\": \"X (Percentage relative to background)\",\n                           \"type\": \"number\",\n                           \"default\": 50\n                       },\n                       \"y\": {\n                           \"title\": \"Y (Percentage relative to background)\",\n                           \"type\": \"number\",\n                           \"default\": 50\n                       },\n                       \"backgroundColor\": {\n                           \"title\": \"Backround color\",\n                           \"type\": \"string\",\n                           \"default\": \"rgba(0,0,0,0)\"\n                       },\n                       \"font\": {\n                           \"type\": \"object\",\n                           \"properties\": {\n                               \"family\": {\n                                    \"title\": \"Font family\",\n                                    \"type\": \"string\",\n                                    \"default\": \"Roboto\"\n                                },\n                                \"size\": {\n                                    \"title\": \"Relative font size (percents)\",\n                                    \"type\": \"number\",\n                                    \"default\": 6\n                                },\n                                \"style\": {\n                                    \"title\": \"Style\",\n                                    \"type\": \"string\",\n                                    \"default\": \"normal\"\n                                },\n                                \"weight\": {\n                                    \"title\": \"Weight\",\n                                    \"type\": \"string\",\n                                    \"default\": \"500\"\n                                },\n                                \"color\": {\n                                    \"title\": \"color\",\n                                    \"type\": \"string\",\n                                    \"default\": \"#fff\"\n                                }\n                           }\n                       }\n                   }\n                }\n            }\n        }\n    },\n    \"form\": [\n        {\n            \"key\": \"backgroundImageUrl\",\n            \"type\": \"image\"\n        },\n        {\n            \"key\": \"labels\",\n            \"items\": [\n                \"labels[].pattern\",\n                \"labels[].x\",\n                \"labels[].y\",\n                {\n                    \"key\": \"labels[].backgroundColor\",\n                    \"type\": \"color\"\n                },\n                \"labels[].font.family\",\n                \"labels[].font.size\",\n                {\n                   \"key\": \"labels[].font.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n\n                },\n                {\n                   \"key\": \"labels[].font.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"labels[].font.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        }\n    ]\n}",
        "dataKeySettingsSchema": "{}\n",
        "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"var\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"backgroundImageUrl\":\"data:image/svg+xml;base64,PHN2ZyBpZD0ic3ZnMiIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAwIiB3aWR0aD0iMTAwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgdmlld0JveD0iMCAwIDEwMCAxMDAiPgogPGcgaWQ9ImxheWVyMSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtOTUyLjM2KSI+CiAgPHJlY3QgaWQ9InJlY3Q0Njg0IiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBoZWlnaHQ9Ijk5LjAxIiB3aWR0aD0iOTkuMDEiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiB5PSI5NTIuODYiIHg9Ii40OTUwNSIgc3Ryb2tlLXdpZHRoPSIuOTkwMTAiIGZpbGw9IiNlZWUiLz4KICA8dGV4dCBpZD0idGV4dDQ2ODYiIHN0eWxlPSJ3b3JkLXNwYWNpbmc6MHB4O2xldHRlci1zcGFjaW5nOjBweDt0ZXh0LWFuY2hvcjptaWRkbGU7dGV4dC1hbGlnbjpjZW50ZXIiIGZvbnQtd2VpZ2h0PSJib2xkIiB4bWw6c3BhY2U9InByZXNlcnZlIiBmb250LXNpemU9IjEwcHgiIGxpbmUtaGVpZ2h0PSIxMjUlIiB5PSI5NzAuNzI4MDkiIHg9IjQ5LjM5NjQ3NyIgZm9udC1mYW1pbHk9IlJvYm90byIgZmlsbD0iIzY2NjY2NiI+PHRzcGFuIGlkPSJ0c3BhbjQ2OTAiIHg9IjUwLjY0NjQ3NyIgeT0iOTcwLjcyODA5Ij5JbWFnZSBiYWNrZ3JvdW5kIDwvdHNwYW4+PHRzcGFuIGlkPSJ0c3BhbjQ2OTIiIHg9IjQ5LjM5NjQ3NyIgeT0iOTgzLjIyODA5Ij5pcyBub3QgY29uZmlndXJlZDwvdHNwYW4+PC90ZXh0PgogIDxyZWN0IGlkPSJyZWN0NDY5NCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgaGVpZ2h0PSIxOS4zNiIgd2lkdGg9IjY5LjM2IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgeT0iOTkyLjY4IiB4PSIxNS4zMiIgc3Ryb2tlLXdpZHRoPSIuNjM5ODYiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+Cg==\",\"labels\":[{\"pattern\":\"Value: ${#0:2} units.\",\"x\":20,\"y\":47,\"font\":{\"color\":\"#515151\",\"family\":\"Roboto\",\"size\":6,\"style\":\"normal\",\"weight\":\"500\"}}]},\"title\":\"Label widget\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
      }
    },
    {
      "alias": "simple_card",
      "name": "Simple card",
      "descriptor": {
        "type": "latest",
        "sizeX": 5,
        "sizeY": 3,
        "resources": [],
        "templateHtml": "",
        "templateCss": "#container {\n    overflow: auto;\n}\n\n.tbDatasource-container {\n    width: 100%;\n    height: 100%;\n    overflow: hidden;\n}\n\n.tbDatasource-table {\n    width: 100%;\n    height: 100%;\n    border-collapse: collapse;\n    white-space: nowrap;\n    font-weight: 100;\n    text-align: right;\n}\n\n.tbDatasource-table td {\n    padding: 12px;\n    position: relative;\n    box-sizing: border-box;\n}\n\n.tbDatasource-data-key {\n    opacity: 0.7;\n    font-weight: 400;\n    font-size: 3.500rem;\n}\n\n.tbDatasource-value {\n    font-size: 5.000rem;\n}",
        "controllerScript": "self.onInit = function() {\n\n    self.ctx.labelPosition = self.ctx.settings.labelPosition || 'left';\n    \n    if (self.ctx.datasources.length > 0) {\n        var tbDatasource = self.ctx.datasources[0];\n        var datasourceId = 'tbDatasource' + 0;\n        self.ctx.$container.append(\n            \"<div id='\" + datasourceId +\n            \"' class='tbDatasource-container'></div>\"\n        );\n        \n        self.ctx.datasourceContainer = $('#' + datasourceId,\n            self.ctx.$container);\n        \n        var tableId = 'table' + 0;\n        self.ctx.datasourceContainer.append(\n            \"<table id='\" + tableId +\n            \"' class='tbDatasource-table'><col width='30%'><col width='70%'></table>\"\n        );\n        var table = $('#' + tableId, self.ctx.$container);\n        if (self.ctx.labelPosition === 'top') {\n            table.css('text-align', 'left');\n        }\n        \n        if (tbDatasource.dataKeys.length > 0) {\n            var dataKey = tbDatasource.dataKeys[0];\n            var labelCellId = 'labelCell' + 0;\n            var cellId = 'cell' + 0;\n            if (self.ctx.labelPosition === 'left') {\n                table.append(\n                    \"<tr><td class='tbDatasource-data-key' id='\" + labelCellId +\"'>\" +\n                    dataKey.label +\n                    \"</td><td class='tbDatasource-value' id='\" +\n                    cellId +\n                    \"'></td></tr>\");\n            } else {\n                table.append(\n                    \"<tr style='vertical-align: bottom;'><td class='tbDatasource-data-key' id='\" + labelCellId +\"'>\" +\n                    dataKey.label +\n                    \"</td></tr><tr><td class='tbDatasource-value' id='\" +\n                    cellId +\n                    \"'></td></tr>\");\n            }\n            self.ctx.labelCell = $('#' + labelCellId, table);\n            self.ctx.valueCell = $('#' + cellId, table);\n            self.ctx.valueCell.html(0 + ' ' + self.ctx.units);\n        }\n    }\n    \n    $.fn.textWidth = function(){\n        var html_org = $(this).html();\n        var html_calc = '<span>' + html_org + '</span>';\n        $(this).html(html_calc);\n        var width = $(this).find('span:first').width();\n        $(this).html(html_org);\n        return width;\n    };    \n    \n    self.onResize();\n};\n\nself.onDataUpdated = function() {\n    \n    function isNumber(n) {\n        return !isNaN(parseFloat(n)) && isFinite(n);\n    }\n\n    if (self.ctx.valueCell && self.ctx.data.length > 0) {\n        var cellData = self.ctx.data[0];\n        if (cellData.data.length > 0) {\n            var tvPair = cellData.data[cellData.data.length -\n                1];\n            var value = tvPair[1];\n            var txtValue;\n            if (isNumber(value)) {\n                txtValue = self.ctx.utils.formatValue(value, self.ctx.decimals, self.ctx.units);\n            } else {\n                txtValue = value;\n            }\n            self.ctx.valueCell.html(txtValue);\n            var targetWidth;\n            var minDelta;\n            if (self.ctx.labelPosition === 'left') {\n                targetWidth = self.ctx.datasourceContainer.width() - self.ctx.labelCell.width();\n                minDelta = self.ctx.width/16 + self.ctx.padding;\n            } else {\n                targetWidth = self.ctx.datasourceContainer.width();\n                minDelta = self.ctx.padding;\n            }\n            var delta = targetWidth - self.ctx.valueCell.textWidth();\n            var fontSize = self.ctx.valueFontSize;\n            if (targetWidth > minDelta) {\n                while (delta < minDelta && fontSize > 6) {\n                    fontSize--;\n                    self.ctx.valueCell.css('font-size', fontSize+'px');\n                    delta = targetWidth - self.ctx.valueCell.textWidth();\n                }\n            }\n        }\n    }    \n    \n};\n\nself.onResize = function() {\n    var labelFontSize;\n    if (self.ctx.labelPosition === 'top') {\n        self.ctx.padding = self.ctx.height/20;\n        labelFontSize = self.ctx.height/4;\n        self.ctx.valueFontSize = self.ctx.height/2;\n    } else {\n        self.ctx.padding = self.ctx.width/50;\n        labelFontSize = self.ctx.height/2.5;\n        self.ctx.valueFontSize = self.ctx.height/2;\n        if (self.ctx.width/self.ctx.height <= 2.7) {\n            labelFontSize = self.ctx.width/7;\n            self.ctx.valueFontSize = self.ctx.width/6;\n        }\n    }\n    self.ctx.padding = Math.min(12, self.ctx.padding);\n    \n    if (self.ctx.labelCell) {\n        self.ctx.labelCell.css('font-size', labelFontSize+'px');\n        self.ctx.labelCell.css('padding', self.ctx.padding+'px');\n    }\n    if (self.ctx.valueCell) {\n        self.ctx.valueCell.css('font-size', self.ctx.valueFontSize+'px');\n        self.ctx.valueCell.css('padding', self.ctx.padding+'px');\n    }    \n};\n\nself.typeParameters = function() {\n    return {\n        maxDatasources: 1,\n        maxDataKeys: 1\n    };\n};\n\n\nself.onDestroy = function() {\n};\n",
        "settingsSchema": "{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"labelPosition\": {\n                \"title\": \"Label position\",\n                \"type\": \"string\",\n                \"default\": \"left\"\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        {\n           \"key\": \"labelPosition\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"left\",\n                   \"label\": \"Left\"\n               },\n               {\n                   \"value\": \"top\",\n                   \"label\": \"Top\"\n               }\n            ]\n        }\n    ]\n}",
        "dataKeySettingsSchema": "{}\n",
        "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ff5722\",\"color\":\"rgba(255, 255, 255, 0.87)\",\"padding\":\"16px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Simple card\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}"
      }
    },
    {
      "alias": "timeseries_table",
      "name": "Timeseries table",
      "descriptor": {
        "type": "timeseries",
        "sizeX": 8,
        "sizeY": 6.5,
        "resources": [],
        "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}",
        "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}"
      }
    }
  ]
}