Commit ba6c2c1a1b046ce45e532cf2c312c3332761b58f
1 parent
70ddf204
UI: Implement state data aggregation. Introduce State chart.
Showing
12 changed files
with
358 additions
and
123 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) { | ... | ... |
... | ... | @@ -17,7 +17,7 @@ |
17 | 17 | export default class DataAggregator { |
18 | 18 | |
19 | 19 | constructor(onDataCb, tsKeyNames, startTs, limit, aggregationType, timeWindow, interval, |
20 | - steppedChart, types, $timeout, $filter) { | |
20 | + stateData, types, $timeout, $filter) { | |
21 | 21 | this.onDataCb = onDataCb; |
22 | 22 | this.tsKeyNames = tsKeyNames; |
23 | 23 | this.dataBuffer = {}; |
... | ... | @@ -35,8 +35,10 @@ export default class DataAggregator { |
35 | 35 | this.limit = limit; |
36 | 36 | this.timeWindow = timeWindow; |
37 | 37 | this.interval = interval; |
38 | - this.steppedChart = steppedChart; | |
39 | - this.firstStepDataReceived = !this.steppedChart; | |
38 | + this.stateData = stateData; | |
39 | + if (this.stateData) { | |
40 | + this.lastPrevKvPairData = {}; | |
41 | + } | |
40 | 42 | this.aggregationTimeout = Math.max(this.interval, 1000); |
41 | 43 | switch (aggregationType) { |
42 | 44 | case types.aggregation.min.value: |
... | ... | @@ -81,10 +83,6 @@ export default class DataAggregator { |
81 | 83 | }, this.aggregationTimeout, false); |
82 | 84 | } |
83 | 85 | |
84 | - onFirstStepData(data) { | |
85 | - this.firstStepData = data; | |
86 | - } | |
87 | - | |
88 | 86 | onData(data, update, history, apply) { |
89 | 87 | if (!this.dataReceived || this.resetPending) { |
90 | 88 | var updateIntervalScheduledTime = true; |
... | ... | @@ -158,6 +156,10 @@ export default class DataAggregator { |
158 | 156 | var keyData = this.dataBuffer[key]; |
159 | 157 | for (var aggTimestamp in aggKeyData) { |
160 | 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 | + } | |
161 | 163 | delete aggKeyData[aggTimestamp]; |
162 | 164 | } else if (aggTimestamp <= this.endTs) { |
163 | 165 | var aggData = aggKeyData[aggTimestamp]; |
... | ... | @@ -166,6 +168,9 @@ export default class DataAggregator { |
166 | 168 | } |
167 | 169 | } |
168 | 170 | keyData = this.$filter('orderBy')(keyData, '+this[0]'); |
171 | + if (this.stateData) { | |
172 | + this.updateStateBounds(keyData, angular.copy(this.lastPrevKvPairData[key])); | |
173 | + } | |
169 | 174 | if (keyData.length > this.limit) { |
170 | 175 | keyData = keyData.slice(keyData.length - this.limit); |
171 | 176 | } |
... | ... | @@ -174,6 +179,34 @@ export default class DataAggregator { |
174 | 179 | return this.dataBuffer; |
175 | 180 | } |
176 | 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 | + | |
177 | 210 | destroy() { |
178 | 211 | if (this.intervalTimeoutHandle) { |
179 | 212 | this.$timeout.cancel(this.intervalTimeoutHandle); | ... | ... |
... | ... | @@ -105,7 +105,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
105 | 105 | var datasourceType = datasourceSubscription.datasourceType; |
106 | 106 | var datasourceData = {}; |
107 | 107 | var dataKeys = {}; |
108 | - var subscribers = {}; | |
108 | + var subscribers = []; | |
109 | 109 | var history = datasourceSubscription.subscriptionTimewindow && |
110 | 110 | datasourceSubscription.subscriptionTimewindow.fixedWindow; |
111 | 111 | var realtime = datasourceSubscription.subscriptionTimewindow && |
... | ... | @@ -249,7 +249,6 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
249 | 249 | if (tsKeys.length > 0) { |
250 | 250 | |
251 | 251 | var subscriber; |
252 | - var subscriptionCommand; | |
253 | 252 | |
254 | 253 | if (history) { |
255 | 254 | |
... | ... | @@ -265,45 +264,103 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
265 | 264 | }; |
266 | 265 | |
267 | 266 | subscriber = { |
268 | - historyCommand: historyCommand, | |
267 | + historyCommands: [ historyCommand ], | |
269 | 268 | type: types.dataKeyType.timeseries, |
270 | - onData: function (data) { | |
271 | - if (data.data) { | |
272 | - 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) { | |
273 | 302 | var keyData = data.data[key]; |
274 | 303 | data.data[key] = $filter('orderBy')(keyData, '+this[0]'); |
275 | 304 | } |
276 | 305 | onData(data.data, types.dataKeyType.timeseries, true); |
277 | 306 | } |
278 | - }, | |
279 | - onReconnected: function() {} | |
307 | + } | |
280 | 308 | }; |
281 | - | |
309 | + subscriber.onReconnected = function() {}; | |
282 | 310 | telemetryWebsocketService.subscribe(subscriber); |
283 | - subscribers[subscriber.historyCommand.cmdId] = subscriber; | |
284 | - | |
285 | - if (subsTw.aggregation.steppedChart) { | |
286 | - createFirstStepSubscription(subsTw, tsKeys); | |
287 | - } | |
311 | + subscribers.push(subscriber); | |
288 | 312 | |
289 | 313 | } else { |
290 | 314 | |
291 | - subscriptionCommand = { | |
315 | + var subscriptionCommand = { | |
292 | 316 | entityType: datasourceSubscription.entityType, |
293 | 317 | entityId: datasourceSubscription.entityId, |
294 | 318 | keys: tsKeys |
295 | 319 | }; |
296 | 320 | |
297 | 321 | subscriber = { |
298 | - subscriptionCommand: subscriptionCommand, | |
322 | + subscriptionCommands: [subscriptionCommand], | |
299 | 323 | type: types.dataKeyType.timeseries |
300 | 324 | }; |
301 | 325 | |
302 | 326 | if (datasourceSubscription.type === types.widgetType.timeseries.value) { |
327 | + subscriber.subsTw = subsTw; | |
303 | 328 | updateRealtimeSubscriptionCommand(subscriptionCommand, subsTw); |
329 | + | |
330 | + if (subsTw.aggregation.stateData) { | |
331 | + subscriber.firstStateSubscriptionCommand = createFirstStateHistoryCommand(subsTw.startTs, tsKeys); | |
332 | + subscriber.historyCommands = [subscriber.firstStateSubscriptionCommand]; | |
333 | + } | |
304 | 334 | dataAggregator = createRealtimeDataAggregator(subsTw, tsKeyNames, types.dataKeyType.timeseries); |
305 | - subscriber.onData = function(data) { | |
306 | - 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 | + } | |
307 | 364 | } |
308 | 365 | subscriber.onReconnected = function() { |
309 | 366 | var newSubsTw = null; |
... | ... | @@ -315,14 +372,16 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
315 | 372 | listener.setRealtimeSubscription(newSubsTw); |
316 | 373 | } |
317 | 374 | } |
318 | - 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 | + } | |
319 | 383 | dataAggregator.reset(newSubsTw.startTs, newSubsTw.aggregation.timeWindow, newSubsTw.aggregation.interval); |
320 | 384 | } |
321 | - | |
322 | - if (subsTw.aggregation.steppedChart) { | |
323 | - createFirstStepSubscription(subsTw, tsKeys); | |
324 | - } | |
325 | - | |
326 | 385 | } else { |
327 | 386 | subscriber.onReconnected = function() {} |
328 | 387 | subscriber.onData = function(data) { |
... | ... | @@ -333,21 +392,21 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
333 | 392 | } |
334 | 393 | |
335 | 394 | telemetryWebsocketService.subscribe(subscriber); |
336 | - subscribers[subscriber.subscriptionCommand.cmdId] = subscriber; | |
395 | + subscribers.push(subscriber); | |
337 | 396 | |
338 | 397 | } |
339 | 398 | } |
340 | 399 | |
341 | 400 | if (attrKeys.length > 0) { |
342 | 401 | |
343 | - subscriptionCommand = { | |
402 | + var attrsSubscriptionCommand = { | |
344 | 403 | entityType: datasourceSubscription.entityType, |
345 | 404 | entityId: datasourceSubscription.entityId, |
346 | 405 | keys: attrKeys |
347 | 406 | }; |
348 | 407 | |
349 | 408 | subscriber = { |
350 | - subscriptionCommand: subscriptionCommand, | |
409 | + subscriptionCommands: [attrsSubscriptionCommand], | |
351 | 410 | type: types.dataKeyType.attribute, |
352 | 411 | onData: function (data) { |
353 | 412 | if (data.data) { |
... | ... | @@ -358,7 +417,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
358 | 417 | }; |
359 | 418 | |
360 | 419 | telemetryWebsocketService.subscribe(subscriber); |
361 | - subscribers[subscriber.cmdId] = subscriber; | |
420 | + subscribers.push(subscriber); | |
362 | 421 | |
363 | 422 | } |
364 | 423 | |
... | ... | @@ -388,35 +447,47 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
388 | 447 | } |
389 | 448 | } |
390 | 449 | |
391 | - function createFirstStepSubscription(subsTw, tsKeys) { | |
392 | - var startStepCommand = { | |
450 | + function createFirstStateHistoryCommand(startTs, tsKeys) { | |
451 | + return { | |
393 | 452 | entityType: datasourceSubscription.entityType, |
394 | 453 | entityId: datasourceSubscription.entityId, |
395 | 454 | keys: tsKeys, |
396 | - startTs: subsTw.fixedWindow.startTimeMs - YEAR, | |
397 | - endTs: subsTw.fixedWindow.startTimeMs, | |
398 | - interval: subsTw.aggregation.interval, | |
455 | + startTs: startTs - YEAR, | |
456 | + endTs: startTs, | |
457 | + interval: 1000, | |
399 | 458 | limit: 1, |
400 | - agg: subsTw.aggregation.type | |
401 | - }; | |
402 | - var subscriber = { | |
403 | - historyCommand: startStepCommand, | |
404 | - type: types.dataKeyType.timeseries, | |
405 | - onData: function (data) { | |
406 | - if (data.data) { | |
407 | - for (var key in data.data) { | |
408 | - var keyData = data.data[key]; | |
409 | - data.data[key] = $filter('orderBy')(keyData, '+this[0]'); | |
410 | - } | |
411 | - //onData(data.data, types.dataKeyType.timeseries, true); | |
412 | - //TODO: onStartStepData | |
413 | - } | |
414 | - }, | |
415 | - onReconnected: function() {} | |
459 | + agg: types.aggregation.none.value | |
416 | 460 | }; |
461 | + } | |
462 | + | |
463 | + function updateFirstStateHistoryCommand(stateHistoryCommand, startTs) { | |
464 | + stateHistoryCommand.startTs = startTs - YEAR; | |
465 | + stateHistoryCommand.endTs = startTs; | |
466 | + } | |
417 | 467 | |
418 | - telemetryWebsocketService.subscribe(subscriber); | |
419 | - subscribers[subscriber.historyCommand.cmdId] = subscriber; | |
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); | |
420 | 491 | } |
421 | 492 | |
422 | 493 | function createRealtimeDataAggregator(subsTw, tsKeyNames, dataKeyType) { |
... | ... | @@ -430,7 +501,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
430 | 501 | subsTw.aggregation.type, |
431 | 502 | subsTw.aggregation.timeWindow, |
432 | 503 | subsTw.aggregation.interval, |
433 | - subsTw.aggregation.steppedChart, | |
504 | + subsTw.aggregation.stateData, | |
434 | 505 | types, |
435 | 506 | $timeout, |
436 | 507 | $filter |
... | ... | @@ -451,14 +522,14 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic |
451 | 522 | timer = null; |
452 | 523 | } |
453 | 524 | if (datasourceType === types.datasourceType.entity) { |
454 | - for (var cmdId in subscribers) { | |
455 | - var subscriber = subscribers[cmdId]; | |
525 | + for (var i=0;i<subscribers.length;i++) { | |
526 | + var subscriber = subscribers[i]; | |
456 | 527 | telemetryWebsocketService.unsubscribe(subscriber); |
457 | 528 | if (subscriber.onDestroy) { |
458 | 529 | subscriber.onDestroy(); |
459 | 530 | } |
460 | 531 | } |
461 | - subscribers = {}; | |
532 | + subscribers.length = 0; | |
462 | 533 | } |
463 | 534 | if (dataAggregator) { |
464 | 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 | - this.steppedChart = options.steppedChart; | |
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, this.steppedChart); | |
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, steppedChart) { | |
264 | + function createSubscriptionTimewindow(timewindow, stDiff, stateData) { | |
265 | 265 | |
266 | 266 | var subscriptionTimewindow = { |
267 | 267 | fixedWindow: null, |
... | ... | @@ -273,12 +273,12 @@ function TimeService($translate, types) { |
273 | 273 | } |
274 | 274 | }; |
275 | 275 | var aggTimewindow = 0; |
276 | - if (steppedChart) { | |
276 | + if (stateData) { | |
277 | 277 | subscriptionTimewindow.aggregation = { |
278 | 278 | interval: SECOND, |
279 | 279 | limit: MAX_LIMIT, |
280 | 280 | type: types.aggregation.none.value, |
281 | - steppedChart: true | |
281 | + stateData: true | |
282 | 282 | }; |
283 | 283 | } else { |
284 | 284 | subscriptionTimewindow.aggregation = { |
... | ... | @@ -288,7 +288,7 @@ function TimeService($translate, types) { |
288 | 288 | }; |
289 | 289 | } |
290 | 290 | |
291 | - if (angular.isDefined(timewindow.aggregation) && !steppedChart) { | |
291 | + if (angular.isDefined(timewindow.aggregation) && !stateData) { | |
292 | 292 | subscriptionTimewindow.aggregation = { |
293 | 293 | type: timewindow.aggregation.type || types.aggregation.avg.value, |
294 | 294 | limit: timewindow.aggregation.limit || AVG_LIMIT | ... | ... |
... | ... | @@ -561,7 +561,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr |
561 | 561 | maxDatasources: -1, //unlimited |
562 | 562 | maxDataKeys: -1, //unlimited |
563 | 563 | dataKeysOptional: false, |
564 | - steppedChart: false | |
564 | + stateData: false | |
565 | 565 | }; |
566 | 566 | ' }\n\n' + |
567 | 567 | |
... | ... | @@ -632,8 +632,8 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr |
632 | 632 | if (angular.isUndefined(result.typeParameters.dataKeysOptional)) { |
633 | 633 | result.typeParameters.dataKeysOptional = false; |
634 | 634 | } |
635 | - if (angular.isUndefined(result.typeParameters.steppedChart)) { | |
636 | - result.typeParameters.steppedChart = false; | |
635 | + if (angular.isUndefined(result.typeParameters.stateData)) { | |
636 | + result.typeParameters.stateData = false; | |
637 | 637 | } |
638 | 638 | if (angular.isFunction(widgetTypeInstance.actionSources)) { |
639 | 639 | result.actionSources = widgetTypeInstance.actionSources(); | ... | ... |
... | ... | @@ -340,7 +340,7 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele |
340 | 340 | if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { |
341 | 341 | options = { |
342 | 342 | type: widget.type, |
343 | - steppedChart: vm.typeParameters.steppedChart | |
343 | + stateData: vm.typeParameters.stateData | |
344 | 344 | } |
345 | 345 | if (widget.type == types.widgetType.alarm.value) { |
346 | 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' || this.chartType === 'stepped') { | |
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; |
... | ... | @@ -271,7 +286,7 @@ export default class TbFlot { |
271 | 286 | } |
272 | 287 | } |
273 | 288 | |
274 | - if (this.chartType === 'stepped') { | |
289 | + if (this.chartType === 'state') { | |
275 | 290 | options.series.lines = { |
276 | 291 | steps: true, |
277 | 292 | show: true |
... | ... | @@ -331,11 +346,28 @@ export default class TbFlot { |
331 | 346 | var colors = []; |
332 | 347 | this.yaxes = []; |
333 | 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 | + | |
334 | 359 | for (var i = 0; i < this.subscription.data.length; i++) { |
335 | 360 | var series = this.subscription.data[i]; |
336 | 361 | colors.push(series.dataKey.color); |
337 | 362 | var keySettings = series.dataKey.settings; |
338 | - | |
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 | + } | |
339 | 371 | series.lines = { |
340 | 372 | fill: keySettings.fillLines === true, |
341 | 373 | show: this.chartType === 'line' ? keySettings.showLines !== false : keySettings.showLines === true |
... | ... | @@ -389,7 +421,7 @@ export default class TbFlot { |
389 | 421 | |
390 | 422 | this.options.colors = colors; |
391 | 423 | this.options.yaxes = angular.copy(this.yaxes); |
392 | - if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'stepped') { | |
424 | + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { | |
393 | 425 | if (this.chartType === 'bar') { |
394 | 426 | this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6; |
395 | 427 | } |
... | ... | @@ -432,6 +464,14 @@ export default class TbFlot { |
432 | 464 | yaxis.position = position; |
433 | 465 | |
434 | 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 | + } | |
435 | 475 | return yaxis; |
436 | 476 | } |
437 | 477 | |
... | ... | @@ -442,7 +482,7 @@ export default class TbFlot { |
442 | 482 | } |
443 | 483 | if (this.subscription) { |
444 | 484 | if (!this.isMouseInteraction && this.ctx.plot) { |
445 | - if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'stepped') { | |
485 | + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { | |
446 | 486 | |
447 | 487 | var axisVisibilityChanged = false; |
448 | 488 | if (this.yaxis) { |
... | ... | @@ -654,6 +694,11 @@ export default class TbFlot { |
654 | 694 | "type": "boolean", |
655 | 695 | "default": false |
656 | 696 | }, |
697 | + "tooltipValueFormatter": { | |
698 | + "title": "Tooltip value format function, f(value)", | |
699 | + "type": "string", | |
700 | + "default": "" | |
701 | + }, | |
657 | 702 | "grid": { |
658 | 703 | "title": "Grid settings", |
659 | 704 | "type": "object", |
... | ... | @@ -739,6 +784,11 @@ export default class TbFlot { |
739 | 784 | "title": "Ticks color", |
740 | 785 | "type": "string", |
741 | 786 | "default": null |
787 | + }, | |
788 | + "ticksFormatter": { | |
789 | + "title": "Ticks formatter function, f(value)", | |
790 | + "type": "string", | |
791 | + "default": "" | |
742 | 792 | } |
743 | 793 | } |
744 | 794 | } |
... | ... | @@ -757,6 +807,10 @@ export default class TbFlot { |
757 | 807 | "tooltipIndividual", |
758 | 808 | "tooltipCumulative", |
759 | 809 | { |
810 | + "key": "tooltipValueFormatter", | |
811 | + "type": "javascript" | |
812 | + }, | |
813 | + { | |
760 | 814 | "key": "grid", |
761 | 815 | "items": [ |
762 | 816 | { |
... | ... | @@ -797,6 +851,10 @@ export default class TbFlot { |
797 | 851 | { |
798 | 852 | "key": "yaxis.color", |
799 | 853 | "type": "color" |
854 | + }, | |
855 | + { | |
856 | + "key": "yaxis.ticksFormatter", | |
857 | + "type": "javascript" | |
800 | 858 | } |
801 | 859 | ] |
802 | 860 | } |
... | ... | @@ -830,6 +888,11 @@ export default class TbFlot { |
830 | 888 | "type": "boolean", |
831 | 889 | "default": false |
832 | 890 | }, |
891 | + "tooltipValueFormatter": { | |
892 | + "title": "Tooltip value format function, f(value)", | |
893 | + "type": "string", | |
894 | + "default": "" | |
895 | + }, | |
833 | 896 | "showSeparateAxis": { |
834 | 897 | "title": "Show separate axis", |
835 | 898 | "type": "boolean", |
... | ... | @@ -849,6 +912,11 @@ export default class TbFlot { |
849 | 912 | "title": "Axis position", |
850 | 913 | "type": "string", |
851 | 914 | "default": "left" |
915 | + }, | |
916 | + "axisTicksFormatter": { | |
917 | + "title": "Ticks formatter function, f(value)", | |
918 | + "type": "string", | |
919 | + "default": "" | |
852 | 920 | } |
853 | 921 | }, |
854 | 922 | "required": ["showLines", "fillLines", "showPoints"] |
... | ... | @@ -857,6 +925,10 @@ export default class TbFlot { |
857 | 925 | "showLines", |
858 | 926 | "fillLines", |
859 | 927 | "showPoints", |
928 | + { | |
929 | + "key": "tooltipValueFormatter", | |
930 | + "type": "javascript" | |
931 | + }, | |
860 | 932 | "showSeparateAxis", |
861 | 933 | "axisTitle", |
862 | 934 | "axisTickDecimals", |
... | ... | @@ -874,8 +946,11 @@ export default class TbFlot { |
874 | 946 | "label": "Right" |
875 | 947 | } |
876 | 948 | ] |
949 | + }, | |
950 | + { | |
951 | + "key": "axisTicksFormatter", | |
952 | + "type": "javascript" | |
877 | 953 | } |
878 | - | |
879 | 954 | ] |
880 | 955 | } |
881 | 956 | } |
... | ... | @@ -1129,6 +1204,7 @@ export default class TbFlot { |
1129 | 1204 | label: series.dataKey.label, |
1130 | 1205 | units: series.dataKey.units, |
1131 | 1206 | decimals: series.dataKey.decimals, |
1207 | + tooltipValueFormatFunction: series.dataKey.tooltipValueFormatFunction, | |
1132 | 1208 | time: pointTime, |
1133 | 1209 | distance: hoverDistance, |
1134 | 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 | } | ... | ... |