Commit d8d84b6868a38006cbbf9ce82e5f6ef45c8bdf60
Committed by
GitHub
Merge pull request #282 from thingsboard/feature/stepped-chart
State chart.
Showing
12 changed files
with
391 additions
and
79 deletions
... | ... | @@ -152,6 +152,22 @@ |
152 | 152 | "dataKeySettingsSchema": "{}", |
153 | 153 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":true,\"tooltipIndividual\":false},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}" |
154 | 154 | } |
155 | + }, | |
156 | + { | |
157 | + "alias": "state_chart", | |
158 | + "name": "State Chart", | |
159 | + "descriptor": { | |
160 | + "type": "timeseries", | |
161 | + "sizeX": 8, | |
162 | + "sizeY": 5, | |
163 | + "resources": [], | |
164 | + "templateHtml": "", | |
165 | + "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", | |
166 | + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema;\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", | |
167 | + "settingsSchema": "{}", | |
168 | + "dataKeySettingsSchema": "{}", | |
169 | + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}" | |
170 | + } | |
155 | 171 | } |
156 | 172 | ] |
157 | 173 | } |
\ No newline at end of file | ... | ... |
... | ... | @@ -96,9 +96,9 @@ |
96 | 96 | "templateHtml": "<tb-led-indicator ctx='ctx'></tb-led-indicator>", |
97 | 97 | "templateCss": "", |
98 | 98 | "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n", |
99 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"scheckStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"scheckStatusMethod\", \"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"scheckStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", | |
99 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"scheckStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"scheckStatusMethod\", \"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"scheckStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", | |
100 | 100 | "dataKeySettingsSchema": "{}\n", |
101 | - "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"scheckStatusMethod\":\"checkStatus\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\"},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" | |
101 | + "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"scheckStatusMethod\":\"checkStatus\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\",\"performCheckStatus\":true},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" | |
102 | 102 | } |
103 | 103 | } |
104 | 104 | ] | ... | ... |
... | ... | @@ -186,7 +186,7 @@ function AttributeService($http, $q, $filter, types, telemetryWebsocketService) |
186 | 186 | types.dataKeyType.timeseries : types.dataKeyType.attribute; |
187 | 187 | |
188 | 188 | var subscriber = { |
189 | - subscriptionCommand: subscriptionCommand, | |
189 | + subscriptionCommands: [subscriptionCommand], | |
190 | 190 | type: type, |
191 | 191 | onData: function (data) { |
192 | 192 | if (data.data) { | ... | ... |
... | ... | @@ -16,7 +16,8 @@ |
16 | 16 | |
17 | 17 | export default class DataAggregator { |
18 | 18 | |
19 | - constructor(onDataCb, tsKeyNames, startTs, limit, aggregationType, timeWindow, interval, types, $timeout, $filter) { | |
19 | + constructor(onDataCb, tsKeyNames, startTs, limit, aggregationType, timeWindow, interval, | |
20 | + stateData, types, $timeout, $filter) { | |
20 | 21 | this.onDataCb = onDataCb; |
21 | 22 | this.tsKeyNames = tsKeyNames; |
22 | 23 | this.dataBuffer = {}; |
... | ... | @@ -34,6 +35,10 @@ export default class DataAggregator { |
34 | 35 | this.limit = limit; |
35 | 36 | this.timeWindow = timeWindow; |
36 | 37 | this.interval = interval; |
38 | + this.stateData = stateData; | |
39 | + if (this.stateData) { | |
40 | + this.lastPrevKvPairData = {}; | |
41 | + } | |
37 | 42 | this.aggregationTimeout = Math.max(this.interval, 1000); |
38 | 43 | switch (aggregationType) { |
39 | 44 | case types.aggregation.min.value: |
... | ... | @@ -151,6 +156,10 @@ export default class DataAggregator { |
151 | 156 | var keyData = this.dataBuffer[key]; |
152 | 157 | for (var aggTimestamp in aggKeyData) { |
153 | 158 | if (aggTimestamp <= this.startTs) { |
159 | + if (this.stateData && | |
160 | + (!this.lastPrevKvPairData[key] || this.lastPrevKvPairData[key][0] < aggTimestamp)) { | |
161 | + this.lastPrevKvPairData[key] = [Number(aggTimestamp), aggKeyData[aggTimestamp].aggValue]; | |
162 | + } | |
154 | 163 | delete aggKeyData[aggTimestamp]; |
155 | 164 | } else if (aggTimestamp <= this.endTs) { |
156 | 165 | var aggData = aggKeyData[aggTimestamp]; |
... | ... | @@ -159,6 +168,9 @@ export default class DataAggregator { |
159 | 168 | } |
160 | 169 | } |
161 | 170 | keyData = this.$filter('orderBy')(keyData, '+this[0]'); |
171 | + if (this.stateData) { | |
172 | + this.updateStateBounds(keyData, angular.copy(this.lastPrevKvPairData[key])); | |
173 | + } | |
162 | 174 | if (keyData.length > this.limit) { |
163 | 175 | keyData = keyData.slice(keyData.length - this.limit); |
164 | 176 | } |
... | ... | @@ -167,6 +179,34 @@ export default class DataAggregator { |
167 | 179 | return this.dataBuffer; |
168 | 180 | } |
169 | 181 | |
182 | + updateStateBounds(keyData, lastPrevKvPair) { | |
183 | + if (lastPrevKvPair) { | |
184 | + lastPrevKvPair[0] = this.startTs; | |
185 | + } | |
186 | + var firstKvPair; | |
187 | + if (!keyData.length) { | |
188 | + if (lastPrevKvPair) { | |
189 | + firstKvPair = lastPrevKvPair; | |
190 | + keyData.push(firstKvPair); | |
191 | + } | |
192 | + } else { | |
193 | + firstKvPair = keyData[0]; | |
194 | + } | |
195 | + if (firstKvPair && firstKvPair[0] > this.startTs) { | |
196 | + if (lastPrevKvPair) { | |
197 | + keyData.unshift(lastPrevKvPair); | |
198 | + } | |
199 | + } | |
200 | + if (keyData.length) { | |
201 | + var lastKvPair = keyData[keyData.length-1]; | |
202 | + if (lastKvPair[0] < this.endTs) { | |
203 | + lastKvPair = angular.copy(lastKvPair); | |
204 | + lastKvPair[0] = this.endTs; | |
205 | + keyData.push(lastKvPair); | |
206 | + } | |
207 | + } | |
208 | + } | |
209 | + | |
170 | 210 | destroy() { |
171 | 211 | if (this.intervalTimeoutHandle) { |
172 | 212 | this.$timeout.cancel(this.intervalTimeoutHandle); | ... | ... |
... | ... | @@ -23,6 +23,8 @@ export default angular.module('thingsboard.api.datasource', [thingsboardApiDevic |
23 | 23 | .factory('datasourceService', DatasourceService) |
24 | 24 | .name; |
25 | 25 | |
26 | +const YEAR = 1000 * 60 * 60 * 24 * 365; | |
27 | + | |
26 | 28 | /*@ngInject*/ |
27 | 29 | function DatasourceService($timeout, $filter, $log, telemetryWebsocketService, types, utils) { |
28 | 30 | |
... | ... | @@ -103,7 +105,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
103 | 105 | var datasourceType = datasourceSubscription.datasourceType; |
104 | 106 | var datasourceData = {}; |
105 | 107 | var dataKeys = {}; |
106 | - var subscribers = {}; | |
108 | + var subscribers = []; | |
107 | 109 | var history = datasourceSubscription.subscriptionTimewindow && |
108 | 110 | datasourceSubscription.subscriptionTimewindow.fixedWindow; |
109 | 111 | var realtime = datasourceSubscription.subscriptionTimewindow && |
... | ... | @@ -247,7 +249,6 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
247 | 249 | if (tsKeys.length > 0) { |
248 | 250 | |
249 | 251 | var subscriber; |
250 | - var subscriptionCommand; | |
251 | 252 | |
252 | 253 | if (history) { |
253 | 254 | |
... | ... | @@ -263,41 +264,103 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
263 | 264 | }; |
264 | 265 | |
265 | 266 | subscriber = { |
266 | - historyCommand: historyCommand, | |
267 | + historyCommands: [ historyCommand ], | |
267 | 268 | type: types.dataKeyType.timeseries, |
268 | - onData: function (data) { | |
269 | - if (data.data) { | |
270 | - for (var key in data.data) { | |
269 | + subsTw: subsTw | |
270 | + }; | |
271 | + | |
272 | + if (subsTw.aggregation.stateData) { | |
273 | + subscriber.firstStateHistoryCommand = createFirstStateHistoryCommand(subsTw.fixedWindow.startTimeMs, tsKeys); | |
274 | + subscriber.historyCommands.push(subscriber.firstStateHistoryCommand); | |
275 | + } | |
276 | + | |
277 | + subscriber.onData = function (data, subscriptionId) { | |
278 | + if (this.subsTw.aggregation.stateData && | |
279 | + this.firstStateHistoryCommand && this.firstStateHistoryCommand.cmdId == subscriptionId) { | |
280 | + if (this.data) { | |
281 | + onStateHistoryData(data, this.data, this.subsTw.aggregation.limit, | |
282 | + subsTw.fixedWindow.startTimeMs, this.subsTw.fixedWindow.endTimeMs, | |
283 | + (data) => { | |
284 | + onData(data.data, types.dataKeyType.timeseries, true); | |
285 | + }); | |
286 | + } else { | |
287 | + this.firstStateData = data; | |
288 | + } | |
289 | + } else { | |
290 | + if (this.subsTw.aggregation.stateData) { | |
291 | + if (this.firstStateData) { | |
292 | + onStateHistoryData(this.firstStateData, data, this.subsTw.aggregation.limit, | |
293 | + this.subsTw.fixedWindow.startTimeMs, this.subsTw.fixedWindow.endTimeMs, | |
294 | + (data) => { | |
295 | + onData(data.data, types.dataKeyType.timeseries, true); | |
296 | + }); | |
297 | + } else { | |
298 | + this.data = data; | |
299 | + } | |
300 | + } else { | |
301 | + for (key in data.data) { | |
271 | 302 | var keyData = data.data[key]; |
272 | 303 | data.data[key] = $filter('orderBy')(keyData, '+this[0]'); |
273 | 304 | } |
274 | 305 | onData(data.data, types.dataKeyType.timeseries, true); |
275 | 306 | } |
276 | - }, | |
277 | - onReconnected: function() {} | |
307 | + } | |
278 | 308 | }; |
279 | - | |
309 | + subscriber.onReconnected = function() {}; | |
280 | 310 | telemetryWebsocketService.subscribe(subscriber); |
281 | - subscribers[subscriber.historyCommand.cmdId] = subscriber; | |
311 | + subscribers.push(subscriber); | |
282 | 312 | |
283 | 313 | } else { |
284 | 314 | |
285 | - subscriptionCommand = { | |
315 | + var subscriptionCommand = { | |
286 | 316 | entityType: datasourceSubscription.entityType, |
287 | 317 | entityId: datasourceSubscription.entityId, |
288 | 318 | keys: tsKeys |
289 | 319 | }; |
290 | 320 | |
291 | 321 | subscriber = { |
292 | - subscriptionCommand: subscriptionCommand, | |
322 | + subscriptionCommands: [subscriptionCommand], | |
293 | 323 | type: types.dataKeyType.timeseries |
294 | 324 | }; |
295 | 325 | |
296 | 326 | if (datasourceSubscription.type === types.widgetType.timeseries.value) { |
327 | + subscriber.subsTw = subsTw; | |
297 | 328 | updateRealtimeSubscriptionCommand(subscriptionCommand, subsTw); |
329 | + | |
330 | + if (subsTw.aggregation.stateData) { | |
331 | + subscriber.firstStateSubscriptionCommand = createFirstStateHistoryCommand(subsTw.startTs, tsKeys); | |
332 | + subscriber.historyCommands = [subscriber.firstStateSubscriptionCommand]; | |
333 | + } | |
298 | 334 | dataAggregator = createRealtimeDataAggregator(subsTw, tsKeyNames, types.dataKeyType.timeseries); |
299 | - subscriber.onData = function(data) { | |
300 | - dataAggregator.onData(data, false, false, true); | |
335 | + subscriber.onData = function(data, subscriptionId) { | |
336 | + if (this.subsTw.aggregation.stateData && | |
337 | + this.firstStateSubscriptionCommand && this.firstStateSubscriptionCommand.cmdId == subscriptionId) { | |
338 | + if (this.data) { | |
339 | + onStateHistoryData(data, this.data, this.subsTw.aggregation.limit, | |
340 | + this.subsTw.startTs, this.subsTw.startTs + this.subsTw.aggregation.timeWindow, | |
341 | + (data) => { | |
342 | + dataAggregator.onData(data, false, false, true); | |
343 | + }); | |
344 | + this.stateDataReceived = true; | |
345 | + } else { | |
346 | + this.firstStateData = data; | |
347 | + } | |
348 | + } else { | |
349 | + if (this.subsTw.aggregation.stateData && !this.stateDataReceived) { | |
350 | + if (this.firstStateData) { | |
351 | + onStateHistoryData(this.firstStateData, data, this.subsTw.aggregation.limit, | |
352 | + this.subsTw.startTs, this.subsTw.startTs + this.subsTw.aggregation.timeWindow, | |
353 | + (data) => { | |
354 | + dataAggregator.onData(data, false, false, true); | |
355 | + }); | |
356 | + this.stateDataReceived = true; | |
357 | + } else { | |
358 | + this.data = data; | |
359 | + } | |
360 | + } else { | |
361 | + dataAggregator.onData(data, false, false, true); | |
362 | + } | |
363 | + } | |
301 | 364 | } |
302 | 365 | subscriber.onReconnected = function() { |
303 | 366 | var newSubsTw = null; |
... | ... | @@ -309,7 +372,14 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
309 | 372 | listener.setRealtimeSubscription(newSubsTw); |
310 | 373 | } |
311 | 374 | } |
312 | - updateRealtimeSubscriptionCommand(this.subscriptionCommand, newSubsTw); | |
375 | + this.subsTw = newSubsTw; | |
376 | + this.firstStateData = null; | |
377 | + this.data = null; | |
378 | + this.stateDataReceived = false; | |
379 | + updateRealtimeSubscriptionCommand(this.subscriptionCommands[0], this.subsTw); | |
380 | + if (this.subsTw.aggregation.stateData) { | |
381 | + updateFirstStateHistoryCommand(this.firstStateSubscriptionCommand, this.subsTw.startTs); | |
382 | + } | |
313 | 383 | dataAggregator.reset(newSubsTw.startTs, newSubsTw.aggregation.timeWindow, newSubsTw.aggregation.interval); |
314 | 384 | } |
315 | 385 | } else { |
... | ... | @@ -322,21 +392,21 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
322 | 392 | } |
323 | 393 | |
324 | 394 | telemetryWebsocketService.subscribe(subscriber); |
325 | - subscribers[subscriber.subscriptionCommand.cmdId] = subscriber; | |
395 | + subscribers.push(subscriber); | |
326 | 396 | |
327 | 397 | } |
328 | 398 | } |
329 | 399 | |
330 | 400 | if (attrKeys.length > 0) { |
331 | 401 | |
332 | - subscriptionCommand = { | |
402 | + var attrsSubscriptionCommand = { | |
333 | 403 | entityType: datasourceSubscription.entityType, |
334 | 404 | entityId: datasourceSubscription.entityId, |
335 | 405 | keys: attrKeys |
336 | 406 | }; |
337 | 407 | |
338 | 408 | subscriber = { |
339 | - subscriptionCommand: subscriptionCommand, | |
409 | + subscriptionCommands: [attrsSubscriptionCommand], | |
340 | 410 | type: types.dataKeyType.attribute, |
341 | 411 | onData: function (data) { |
342 | 412 | if (data.data) { |
... | ... | @@ -347,7 +417,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
347 | 417 | }; |
348 | 418 | |
349 | 419 | telemetryWebsocketService.subscribe(subscriber); |
350 | - subscribers[subscriber.cmdId] = subscriber; | |
420 | + subscribers.push(subscriber); | |
351 | 421 | |
352 | 422 | } |
353 | 423 | |
... | ... | @@ -377,6 +447,49 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
377 | 447 | } |
378 | 448 | } |
379 | 449 | |
450 | + function createFirstStateHistoryCommand(startTs, tsKeys) { | |
451 | + return { | |
452 | + entityType: datasourceSubscription.entityType, | |
453 | + entityId: datasourceSubscription.entityId, | |
454 | + keys: tsKeys, | |
455 | + startTs: startTs - YEAR, | |
456 | + endTs: startTs, | |
457 | + interval: 1000, | |
458 | + limit: 1, | |
459 | + agg: types.aggregation.none.value | |
460 | + }; | |
461 | + } | |
462 | + | |
463 | + function updateFirstStateHistoryCommand(stateHistoryCommand, startTs) { | |
464 | + stateHistoryCommand.startTs = startTs - YEAR; | |
465 | + stateHistoryCommand.endTs = startTs; | |
466 | + } | |
467 | + | |
468 | + function onStateHistoryData(firstStateData, data, limit, startTs, endTs, onData) { | |
469 | + for (var key in data.data) { | |
470 | + var keyData = data.data[key]; | |
471 | + data.data[key] = $filter('orderBy')(keyData, '+this[0]'); | |
472 | + keyData = data.data[key]; | |
473 | + if (keyData.length < limit) { | |
474 | + var firstStateKeyData = firstStateData.data[key]; | |
475 | + if (firstStateKeyData.length) { | |
476 | + var firstStateDataTsKv = firstStateKeyData[0]; | |
477 | + firstStateDataTsKv[0] = startTs; | |
478 | + firstStateKeyData = [ | |
479 | + [ startTs, firstStateKeyData[0][1] ] | |
480 | + ]; | |
481 | + keyData.unshift(firstStateDataTsKv); | |
482 | + } | |
483 | + } | |
484 | + if (keyData.length) { | |
485 | + var lastTsKv = angular.copy(keyData[keyData.length-1]); | |
486 | + lastTsKv[0] = endTs; | |
487 | + keyData.push(lastTsKv); | |
488 | + } | |
489 | + } | |
490 | + onData(data); | |
491 | + } | |
492 | + | |
380 | 493 | function createRealtimeDataAggregator(subsTw, tsKeyNames, dataKeyType) { |
381 | 494 | return new DataAggregator( |
382 | 495 | function(data, apply) { |
... | ... | @@ -388,6 +501,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
388 | 501 | subsTw.aggregation.type, |
389 | 502 | subsTw.aggregation.timeWindow, |
390 | 503 | subsTw.aggregation.interval, |
504 | + subsTw.aggregation.stateData, | |
391 | 505 | types, |
392 | 506 | $timeout, |
393 | 507 | $filter |
... | ... | @@ -408,14 +522,14 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
408 | 522 | timer = null; |
409 | 523 | } |
410 | 524 | if (datasourceType === types.datasourceType.entity) { |
411 | - for (var cmdId in subscribers) { | |
412 | - var subscriber = subscribers[cmdId]; | |
525 | + for (var i=0;i<subscribers.length;i++) { | |
526 | + var subscriber = subscribers[i]; | |
413 | 527 | telemetryWebsocketService.unsubscribe(subscriber); |
414 | 528 | if (subscriber.onDestroy) { |
415 | 529 | subscriber.onDestroy(); |
416 | 530 | } |
417 | 531 | } |
418 | - subscribers = {}; | |
532 | + subscribers.length = 0; | |
419 | 533 | } |
420 | 534 | if (dataAggregator) { |
421 | 535 | dataAggregator.destroy(); | ... | ... |
... | ... | @@ -128,7 +128,7 @@ export default class Subscription { |
128 | 128 | stDiff: this.ctx.stDiff |
129 | 129 | } |
130 | 130 | this.useDashboardTimewindow = options.useDashboardTimewindow; |
131 | - | |
131 | + this.stateData = options.stateData; | |
132 | 132 | if (this.useDashboardTimewindow) { |
133 | 133 | this.timeWindowConfig = angular.copy(options.dashboardTimewindow); |
134 | 134 | } else { |
... | ... | @@ -612,7 +612,7 @@ export default class Subscription { |
612 | 612 | this.subscriptionTimewindow = |
613 | 613 | this.ctx.timeService.createSubscriptionTimewindow( |
614 | 614 | this.timeWindowConfig, |
615 | - this.timeWindow.stDiff); | |
615 | + this.timeWindow.stDiff, this.stateData); | |
616 | 616 | } |
617 | 617 | this.updateTimewindow(); |
618 | 618 | return this.subscriptionTimewindow; | ... | ... |
... | ... | @@ -34,6 +34,7 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty |
34 | 34 | lastCmdId = 0, |
35 | 35 | subscribers = {}, |
36 | 36 | subscribersCount = 0, |
37 | + commands = {}, | |
37 | 38 | cmdsWrapper = { |
38 | 39 | tsSubCmds: [], |
39 | 40 | historyCmds: [], |
... | ... | @@ -120,7 +121,10 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty |
120 | 121 | if (!isReconnect) { |
121 | 122 | reconnectSubscribers = []; |
122 | 123 | for (var id in subscribers) { |
123 | - reconnectSubscribers.push(subscribers[id]); | |
124 | + var subscriber = subscribers[id]; | |
125 | + if (reconnectSubscribers.indexOf(subscriber) === -1) { | |
126 | + reconnectSubscribers.push(subscriber); | |
127 | + } | |
124 | 128 | } |
125 | 129 | reset(false); |
126 | 130 | isReconnect = true; |
... | ... | @@ -138,7 +142,7 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty |
138 | 142 | if (data.subscriptionId) { |
139 | 143 | var subscriber = subscribers[data.subscriptionId]; |
140 | 144 | if (subscriber && data) { |
141 | - var keys = fetchKeys(subscriber); | |
145 | + var keys = fetchKeys(data.subscriptionId); | |
142 | 146 | if (!data.data) { |
143 | 147 | data.data = {}; |
144 | 148 | } |
... | ... | @@ -148,20 +152,15 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty |
148 | 152 | data.data[key] = []; |
149 | 153 | } |
150 | 154 | } |
151 | - subscriber.onData(data); | |
155 | + subscriber.onData(data, data.subscriptionId); | |
152 | 156 | } |
153 | 157 | } |
154 | 158 | } |
155 | 159 | checkToClose(); |
156 | 160 | } |
157 | 161 | |
158 | - function fetchKeys(subscriber) { | |
159 | - var command; | |
160 | - if (angular.isDefined(subscriber.subscriptionCommand)) { | |
161 | - command = subscriber.subscriptionCommand; | |
162 | - } else { | |
163 | - command = subscriber.historyCommand; | |
164 | - } | |
162 | + function fetchKeys(subscriptionId) { | |
163 | + var command = commands[subscriptionId]; | |
165 | 164 | if (command && command.keys && command.keys.length > 0) { |
166 | 165 | return command.keys.split(","); |
167 | 166 | } else { |
... | ... | @@ -176,41 +175,73 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty |
176 | 175 | |
177 | 176 | function subscribe (subscriber) { |
178 | 177 | isActive = true; |
179 | - var cmdId = nextCmdId(); | |
180 | - subscribers[cmdId] = subscriber; | |
181 | - subscribersCount++; | |
182 | - if (angular.isDefined(subscriber.subscriptionCommand)) { | |
183 | - subscriber.subscriptionCommand.cmdId = cmdId; | |
184 | - if (subscriber.type === types.dataKeyType.timeseries) { | |
185 | - cmdsWrapper.tsSubCmds.push(subscriber.subscriptionCommand); | |
186 | - } else if (subscriber.type === types.dataKeyType.attribute) { | |
187 | - cmdsWrapper.attrSubCmds.push(subscriber.subscriptionCommand); | |
178 | + var cmdId; | |
179 | + if (angular.isDefined(subscriber.subscriptionCommands)) { | |
180 | + for (var i=0;i<subscriber.subscriptionCommands.length;i++) { | |
181 | + var subscriptionCommand = subscriber.subscriptionCommands[i]; | |
182 | + cmdId = nextCmdId(); | |
183 | + subscribers[cmdId] = subscriber; | |
184 | + subscriptionCommand.cmdId = cmdId; | |
185 | + commands[cmdId] = subscriptionCommand; | |
186 | + if (subscriber.type === types.dataKeyType.timeseries) { | |
187 | + cmdsWrapper.tsSubCmds.push(subscriptionCommand); | |
188 | + } else if (subscriber.type === types.dataKeyType.attribute) { | |
189 | + cmdsWrapper.attrSubCmds.push(subscriptionCommand); | |
190 | + } | |
188 | 191 | } |
189 | - } else if (angular.isDefined(subscriber.historyCommand)) { | |
190 | - subscriber.historyCommand.cmdId = cmdId; | |
191 | - cmdsWrapper.historyCmds.push(subscriber.historyCommand); | |
192 | 192 | } |
193 | + if (angular.isDefined(subscriber.historyCommands)) { | |
194 | + for (i=0;i<subscriber.historyCommands.length;i++) { | |
195 | + var historyCommand = subscriber.historyCommands[i]; | |
196 | + cmdId = nextCmdId(); | |
197 | + subscribers[cmdId] = subscriber; | |
198 | + historyCommand.cmdId = cmdId; | |
199 | + commands[cmdId] = historyCommand; | |
200 | + cmdsWrapper.historyCmds.push(historyCommand); | |
201 | + } | |
202 | + } | |
203 | + subscribersCount++; | |
193 | 204 | publishCommands(); |
194 | 205 | } |
195 | 206 | |
196 | 207 | function unsubscribe (subscriber) { |
197 | 208 | if (isActive) { |
198 | 209 | var cmdId = null; |
199 | - if (subscriber.subscriptionCommand) { | |
200 | - subscriber.subscriptionCommand.unsubscribe = true; | |
201 | - if (subscriber.type === types.dataKeyType.timeseries) { | |
202 | - cmdsWrapper.tsSubCmds.push(subscriber.subscriptionCommand); | |
203 | - } else if (subscriber.type === types.dataKeyType.attribute) { | |
204 | - cmdsWrapper.attrSubCmds.push(subscriber.subscriptionCommand); | |
210 | + if (subscriber.subscriptionCommands) { | |
211 | + for (var i=0;i<subscriber.subscriptionCommands.length;i++) { | |
212 | + var subscriptionCommand = subscriber.subscriptionCommands[i]; | |
213 | + subscriptionCommand.unsubscribe = true; | |
214 | + if (subscriber.type === types.dataKeyType.timeseries) { | |
215 | + cmdsWrapper.tsSubCmds.push(subscriptionCommand); | |
216 | + } else if (subscriber.type === types.dataKeyType.attribute) { | |
217 | + cmdsWrapper.attrSubCmds.push(subscriptionCommand); | |
218 | + } | |
219 | + cmdId = subscriptionCommand.cmdId; | |
220 | + if (cmdId) { | |
221 | + if (subscribers[cmdId]) { | |
222 | + delete subscribers[cmdId]; | |
223 | + } | |
224 | + if (commands[cmdId]) { | |
225 | + delete commands[cmdId]; | |
226 | + } | |
227 | + } | |
205 | 228 | } |
206 | - cmdId = subscriber.subscriptionCommand.cmdId; | |
207 | - } else if (subscriber.historyCommand) { | |
208 | - cmdId = subscriber.historyCommand.cmdId; | |
209 | 229 | } |
210 | - if (cmdId && subscribers[cmdId]) { | |
211 | - delete subscribers[cmdId]; | |
212 | - subscribersCount--; | |
230 | + if (subscriber.historyCommands) { | |
231 | + for (i=0;i<subscriber.historyCommands.length;i++) { | |
232 | + var historyCommand = subscriber.historyCommands[i]; | |
233 | + cmdId = historyCommand.cmdId; | |
234 | + if (cmdId) { | |
235 | + if (subscribers[cmdId]) { | |
236 | + delete subscribers[cmdId]; | |
237 | + } | |
238 | + if (commands[cmdId]) { | |
239 | + delete commands[cmdId]; | |
240 | + } | |
241 | + } | |
242 | + } | |
213 | 243 | } |
244 | + subscribersCount--; | |
214 | 245 | publishCommands(); |
215 | 246 | } |
216 | 247 | } |
... | ... | @@ -268,6 +299,7 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty |
268 | 299 | lastCmdId = 0; |
269 | 300 | subscribers = {}; |
270 | 301 | subscribersCount = 0; |
302 | + commands = {}; | |
271 | 303 | cmdsWrapper.tsSubCmds = []; |
272 | 304 | cmdsWrapper.historyCmds = []; |
273 | 305 | cmdsWrapper.attrSubCmds = []; | ... | ... |
... | ... | @@ -261,7 +261,7 @@ function TimeService($translate, types) { |
261 | 261 | return historyTimewindow; |
262 | 262 | } |
263 | 263 | |
264 | - function createSubscriptionTimewindow(timewindow, stDiff) { | |
264 | + function createSubscriptionTimewindow(timewindow, stDiff, stateData) { | |
265 | 265 | |
266 | 266 | var subscriptionTimewindow = { |
267 | 267 | fixedWindow: null, |
... | ... | @@ -273,8 +273,22 @@ function TimeService($translate, types) { |
273 | 273 | } |
274 | 274 | }; |
275 | 275 | var aggTimewindow = 0; |
276 | + if (stateData) { | |
277 | + subscriptionTimewindow.aggregation = { | |
278 | + interval: SECOND, | |
279 | + limit: MAX_LIMIT, | |
280 | + type: types.aggregation.none.value, | |
281 | + stateData: true | |
282 | + }; | |
283 | + } else { | |
284 | + subscriptionTimewindow.aggregation = { | |
285 | + interval: SECOND, | |
286 | + limit: AVG_LIMIT, | |
287 | + type: types.aggregation.avg.value | |
288 | + }; | |
289 | + } | |
276 | 290 | |
277 | - if (angular.isDefined(timewindow.aggregation)) { | |
291 | + if (angular.isDefined(timewindow.aggregation) && !stateData) { | |
278 | 292 | subscriptionTimewindow.aggregation = { |
279 | 293 | type: timewindow.aggregation.type || types.aggregation.avg.value, |
280 | 294 | limit: timewindow.aggregation.limit || AVG_LIMIT | ... | ... |
... | ... | @@ -560,7 +560,8 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr |
560 | 560 | useCustomDatasources: false, |
561 | 561 | maxDatasources: -1, //unlimited |
562 | 562 | maxDataKeys: -1, //unlimited |
563 | - dataKeysOptional: false | |
563 | + dataKeysOptional: false, | |
564 | + stateData: false | |
564 | 565 | }; |
565 | 566 | ' }\n\n' + |
566 | 567 | |
... | ... | @@ -631,6 +632,9 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr |
631 | 632 | if (angular.isUndefined(result.typeParameters.dataKeysOptional)) { |
632 | 633 | result.typeParameters.dataKeysOptional = false; |
633 | 634 | } |
635 | + if (angular.isUndefined(result.typeParameters.stateData)) { | |
636 | + result.typeParameters.stateData = false; | |
637 | + } | |
634 | 638 | if (angular.isFunction(widgetTypeInstance.actionSources)) { |
635 | 639 | result.actionSources = widgetTypeInstance.actionSources(); |
636 | 640 | } else { | ... | ... |
... | ... | @@ -339,7 +339,8 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele |
339 | 339 | var deferred = $q.defer(); |
340 | 340 | if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { |
341 | 341 | options = { |
342 | - type: widget.type | |
342 | + type: widget.type, | |
343 | + stateData: vm.typeParameters.stateData | |
343 | 344 | } |
344 | 345 | if (widget.type == types.widgetType.alarm.value) { |
345 | 346 | options.alarmSource = angular.copy(widget.config.alarmSource); | ... | ... |
... | ... | @@ -55,7 +55,7 @@ export default class TbFlot { |
55 | 55 | |
56 | 56 | var tbFlot = this; |
57 | 57 | |
58 | - function seriesInfoDiv(label, color, value, units, trackDecimals, active, percent) { | |
58 | + function seriesInfoDiv(label, color, value, units, trackDecimals, active, percent, valueFormatFunction) { | |
59 | 59 | var divElement = $('<div></div>'); |
60 | 60 | divElement.css({ |
61 | 61 | display: "flex", |
... | ... | @@ -83,7 +83,12 @@ export default class TbFlot { |
83 | 83 | }); |
84 | 84 | } |
85 | 85 | divElement.append(labelSpan); |
86 | - var valueContent = tbFlot.ctx.utils.formatValue(value, trackDecimals, units); | |
86 | + var valueContent; | |
87 | + if (valueFormatFunction) { | |
88 | + valueContent = valueFormatFunction(value); | |
89 | + } else { | |
90 | + valueContent = tbFlot.ctx.utils.formatValue(value, trackDecimals, units); | |
91 | + } | |
87 | 92 | if (angular.isNumber(percent)) { |
88 | 93 | valueContent += ' (' + Math.round(percent) + ' %)'; |
89 | 94 | } |
... | ... | @@ -107,7 +112,7 @@ export default class TbFlot { |
107 | 112 | var units = item.series.dataKey.units && item.series.dataKey.units.length ? item.series.dataKey.units : tbFlot.ctx.trackUnits; |
108 | 113 | var decimals = angular.isDefined(item.series.dataKey.decimals) ? item.series.dataKey.decimals : tbFlot.ctx.trackDecimals; |
109 | 114 | var divElement = seriesInfoDiv(item.series.dataKey.label, item.series.dataKey.color, |
110 | - item.datapoint[1][0][1], units, decimals, true, item.series.percent); | |
115 | + item.datapoint[1][0][1], units, decimals, true, item.series.percent, item.series.dataKey.tooltipValueFormatFunction); | |
111 | 116 | return divElement.prop('outerHTML'); |
112 | 117 | }; |
113 | 118 | } else { |
... | ... | @@ -132,7 +137,7 @@ export default class TbFlot { |
132 | 137 | var units = seriesHoverInfo.units && seriesHoverInfo.units.length ? seriesHoverInfo.units : tbFlot.ctx.trackUnits; |
133 | 138 | var decimals = angular.isDefined(seriesHoverInfo.decimals) ? seriesHoverInfo.decimals : tbFlot.ctx.trackDecimals; |
134 | 139 | var divElement = seriesInfoDiv(seriesHoverInfo.label, seriesHoverInfo.color, |
135 | - seriesHoverInfo.value, units, decimals, seriesHoverInfo.index === seriesIndex); | |
140 | + seriesHoverInfo.value, units, decimals, seriesHoverInfo.index === seriesIndex, null, seriesHoverInfo.tooltipValueFormatFunction); | |
136 | 141 | content += divElement.prop('outerHTML'); |
137 | 142 | } |
138 | 143 | return content; |
... | ... | @@ -168,7 +173,7 @@ export default class TbFlot { |
168 | 173 | } |
169 | 174 | }; |
170 | 175 | |
171 | - if (this.chartType === 'line' || this.chartType === 'bar') { | |
176 | + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { | |
172 | 177 | options.xaxis = { |
173 | 178 | mode: 'time', |
174 | 179 | timezone: 'browser', |
... | ... | @@ -196,6 +201,9 @@ export default class TbFlot { |
196 | 201 | if (settings.yaxis && settings.yaxis.showLabels === false) { |
197 | 202 | return ''; |
198 | 203 | } |
204 | + if (this.ticksFormatterFunction) { | |
205 | + return this.ticksFormatterFunction(value); | |
206 | + } | |
199 | 207 | var factor = this.tickDecimals ? Math.pow(10, this.tickDecimals) : 1, |
200 | 208 | formatted = "" + Math.round(value * factor) / factor; |
201 | 209 | if (this.tickDecimals != null) { |
... | ... | @@ -218,6 +226,13 @@ export default class TbFlot { |
218 | 226 | this.yaxis.labelFont.color = this.yaxis.font.color; |
219 | 227 | this.yaxis.labelFont.size = this.yaxis.font.size+2; |
220 | 228 | this.yaxis.labelFont.weight = "bold"; |
229 | + if (settings.yaxis.ticksFormatter && settings.yaxis.ticksFormatter.length) { | |
230 | + try { | |
231 | + this.yaxis.ticksFormatterFunction = new Function('value', settings.yaxis.ticksFormatter); | |
232 | + } catch (e) { | |
233 | + this.yaxis.ticksFormatterFunction = null; | |
234 | + } | |
235 | + } | |
221 | 236 | } |
222 | 237 | |
223 | 238 | options.grid.borderWidth = 1; |
... | ... | @@ -270,6 +285,14 @@ export default class TbFlot { |
270 | 285 | fill: 0.9 |
271 | 286 | } |
272 | 287 | } |
288 | + | |
289 | + if (this.chartType === 'state') { | |
290 | + options.series.lines = { | |
291 | + steps: true, | |
292 | + show: true | |
293 | + } | |
294 | + } | |
295 | + | |
273 | 296 | } else if (this.chartType === 'pie') { |
274 | 297 | options.series = { |
275 | 298 | pie: { |
... | ... | @@ -323,11 +346,28 @@ export default class TbFlot { |
323 | 346 | var colors = []; |
324 | 347 | this.yaxes = []; |
325 | 348 | var yaxesMap = {}; |
349 | + | |
350 | + var tooltipValueFormatFunction = null; | |
351 | + if (this.ctx.settings.tooltipValueFormatter && this.ctx.settings.tooltipValueFormatter.length) { | |
352 | + try { | |
353 | + tooltipValueFormatFunction = new Function('value', this.ctx.settings.tooltipValueFormatter); | |
354 | + } catch (e) { | |
355 | + tooltipValueFormatFunction = null; | |
356 | + } | |
357 | + } | |
358 | + | |
326 | 359 | for (var i = 0; i < this.subscription.data.length; i++) { |
327 | 360 | var series = this.subscription.data[i]; |
328 | 361 | colors.push(series.dataKey.color); |
329 | 362 | var keySettings = series.dataKey.settings; |
330 | - | |
363 | + series.dataKey.tooltipValueFormatFunction = tooltipValueFormatFunction; | |
364 | + if (keySettings.tooltipValueFormatter && keySettings.tooltipValueFormatter.length) { | |
365 | + try { | |
366 | + series.dataKey.tooltipValueFormatFunction = new Function('value', keySettings.tooltipValueFormatter); | |
367 | + } catch (e) { | |
368 | + series.dataKey.tooltipValueFormatFunction = tooltipValueFormatFunction; | |
369 | + } | |
370 | + } | |
331 | 371 | series.lines = { |
332 | 372 | fill: keySettings.fillLines === true, |
333 | 373 | show: this.chartType === 'line' ? keySettings.showLines !== false : keySettings.showLines === true |
... | ... | @@ -381,7 +421,7 @@ export default class TbFlot { |
381 | 421 | |
382 | 422 | this.options.colors = colors; |
383 | 423 | this.options.yaxes = angular.copy(this.yaxes); |
384 | - if (this.chartType === 'line' || this.chartType === 'bar') { | |
424 | + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { | |
385 | 425 | if (this.chartType === 'bar') { |
386 | 426 | this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6; |
387 | 427 | } |
... | ... | @@ -424,6 +464,14 @@ export default class TbFlot { |
424 | 464 | yaxis.position = position; |
425 | 465 | |
426 | 466 | yaxis.keysInfo = []; |
467 | + | |
468 | + if (keySettings.axisTicksFormatter && keySettings.axisTicksFormatter.length) { | |
469 | + try { | |
470 | + yaxis.ticksFormatterFunction = new Function('value', keySettings.axisTicksFormatter); | |
471 | + } catch (e) { | |
472 | + yaxis.ticksFormatterFunction = this.yaxis.ticksFormatterFunction; | |
473 | + } | |
474 | + } | |
427 | 475 | return yaxis; |
428 | 476 | } |
429 | 477 | |
... | ... | @@ -434,7 +482,7 @@ export default class TbFlot { |
434 | 482 | } |
435 | 483 | if (this.subscription) { |
436 | 484 | if (!this.isMouseInteraction && this.ctx.plot) { |
437 | - if (this.chartType === 'line' || this.chartType === 'bar') { | |
485 | + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { | |
438 | 486 | |
439 | 487 | var axisVisibilityChanged = false; |
440 | 488 | if (this.yaxis) { |
... | ... | @@ -646,6 +694,11 @@ export default class TbFlot { |
646 | 694 | "type": "boolean", |
647 | 695 | "default": false |
648 | 696 | }, |
697 | + "tooltipValueFormatter": { | |
698 | + "title": "Tooltip value format function, f(value)", | |
699 | + "type": "string", | |
700 | + "default": "" | |
701 | + }, | |
649 | 702 | "grid": { |
650 | 703 | "title": "Grid settings", |
651 | 704 | "type": "object", |
... | ... | @@ -731,6 +784,11 @@ export default class TbFlot { |
731 | 784 | "title": "Ticks color", |
732 | 785 | "type": "string", |
733 | 786 | "default": null |
787 | + }, | |
788 | + "ticksFormatter": { | |
789 | + "title": "Ticks formatter function, f(value)", | |
790 | + "type": "string", | |
791 | + "default": "" | |
734 | 792 | } |
735 | 793 | } |
736 | 794 | } |
... | ... | @@ -749,6 +807,10 @@ export default class TbFlot { |
749 | 807 | "tooltipIndividual", |
750 | 808 | "tooltipCumulative", |
751 | 809 | { |
810 | + "key": "tooltipValueFormatter", | |
811 | + "type": "javascript" | |
812 | + }, | |
813 | + { | |
752 | 814 | "key": "grid", |
753 | 815 | "items": [ |
754 | 816 | { |
... | ... | @@ -789,6 +851,10 @@ export default class TbFlot { |
789 | 851 | { |
790 | 852 | "key": "yaxis.color", |
791 | 853 | "type": "color" |
854 | + }, | |
855 | + { | |
856 | + "key": "yaxis.ticksFormatter", | |
857 | + "type": "javascript" | |
792 | 858 | } |
793 | 859 | ] |
794 | 860 | } |
... | ... | @@ -822,6 +888,11 @@ export default class TbFlot { |
822 | 888 | "type": "boolean", |
823 | 889 | "default": false |
824 | 890 | }, |
891 | + "tooltipValueFormatter": { | |
892 | + "title": "Tooltip value format function, f(value)", | |
893 | + "type": "string", | |
894 | + "default": "" | |
895 | + }, | |
825 | 896 | "showSeparateAxis": { |
826 | 897 | "title": "Show separate axis", |
827 | 898 | "type": "boolean", |
... | ... | @@ -841,6 +912,11 @@ export default class TbFlot { |
841 | 912 | "title": "Axis position", |
842 | 913 | "type": "string", |
843 | 914 | "default": "left" |
915 | + }, | |
916 | + "axisTicksFormatter": { | |
917 | + "title": "Ticks formatter function, f(value)", | |
918 | + "type": "string", | |
919 | + "default": "" | |
844 | 920 | } |
845 | 921 | }, |
846 | 922 | "required": ["showLines", "fillLines", "showPoints"] |
... | ... | @@ -849,6 +925,10 @@ export default class TbFlot { |
849 | 925 | "showLines", |
850 | 926 | "fillLines", |
851 | 927 | "showPoints", |
928 | + { | |
929 | + "key": "tooltipValueFormatter", | |
930 | + "type": "javascript" | |
931 | + }, | |
852 | 932 | "showSeparateAxis", |
853 | 933 | "axisTitle", |
854 | 934 | "axisTickDecimals", |
... | ... | @@ -866,8 +946,11 @@ export default class TbFlot { |
866 | 946 | "label": "Right" |
867 | 947 | } |
868 | 948 | ] |
949 | + }, | |
950 | + { | |
951 | + "key": "axisTicksFormatter", | |
952 | + "type": "javascript" | |
869 | 953 | } |
870 | - | |
871 | 954 | ] |
872 | 955 | } |
873 | 956 | } |
... | ... | @@ -1121,6 +1204,7 @@ export default class TbFlot { |
1121 | 1204 | label: series.dataKey.label, |
1122 | 1205 | units: series.dataKey.units, |
1123 | 1206 | decimals: series.dataKey.decimals, |
1207 | + tooltipValueFormatFunction: series.dataKey.tooltipValueFormatFunction, | |
1124 | 1208 | time: pointTime, |
1125 | 1209 | distance: hoverDistance, |
1126 | 1210 | index: i | ... | ... |
... | ... | @@ -130,15 +130,22 @@ function LedIndicatorController($element, $scope, $timeout, utils, types) { |
130 | 130 | } |
131 | 131 | } |
132 | 132 | |
133 | - vm.checkStatusMethod = 'checkStatus'; | |
134 | - if (vm.ctx.settings.checkStatusMethod && vm.ctx.settings.checkStatusMethod.length) { | |
135 | - vm.checkStatusMethod = vm.ctx.settings.checkStatusMethod; | |
133 | + vm.performCheckStatus = vm.ctx.settings.performCheckStatus != false; | |
134 | + if (vm.performCheckStatus) { | |
135 | + vm.checkStatusMethod = 'checkStatus'; | |
136 | + if (vm.ctx.settings.checkStatusMethod && vm.ctx.settings.checkStatusMethod.length) { | |
137 | + vm.checkStatusMethod = vm.ctx.settings.checkStatusMethod; | |
138 | + } | |
136 | 139 | } |
137 | 140 | if (!rpcEnabled) { |
138 | 141 | onError('Target device is not set!'); |
139 | 142 | } else { |
140 | 143 | if (!vm.isSimulated) { |
141 | - rpcCheckStatus(); | |
144 | + if (vm.performCheckStatus) { | |
145 | + rpcCheckStatus(); | |
146 | + } else { | |
147 | + subscribeForValue(); | |
148 | + } | |
142 | 149 | } |
143 | 150 | } |
144 | 151 | } | ... | ... |