@flowfuse/node-red-dashboard 1.30.1-9f365cd-202512291502.0 → 1.30.1-a60a579-202512301349.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/nodes/store/data.js +17 -0
- package/nodes/widgets/ui_chart.js +45 -14
- package/package.json +1 -1
package/nodes/store/data.js
CHANGED
|
@@ -102,6 +102,22 @@ const setters = {
|
|
|
102
102
|
}
|
|
103
103
|
data[node.id].push(config.RED.util.cloneMessage(msg))
|
|
104
104
|
}
|
|
105
|
+
},
|
|
106
|
+
/**
|
|
107
|
+
* Fast filtering of existing array data (skips cloning and save checks for fast data cleanup)
|
|
108
|
+
* @param {*} base - the base node
|
|
109
|
+
* @param {*} node - the owner node
|
|
110
|
+
* @param {(msg) => Boolean} filterFunction
|
|
111
|
+
*/
|
|
112
|
+
filter (base, node, filterFunction) {
|
|
113
|
+
const currentData = data[node.id]
|
|
114
|
+
if (filterFunction && Array.isArray(currentData) && currentData.length) {
|
|
115
|
+
const filteredMessages = currentData.filter(filterFunction)
|
|
116
|
+
if (filteredMessages.length !== currentData.length) {
|
|
117
|
+
// no need for save operation to process messages - just apply them
|
|
118
|
+
data[node.id] = filteredMessages
|
|
119
|
+
}
|
|
120
|
+
}
|
|
105
121
|
}
|
|
106
122
|
}
|
|
107
123
|
|
|
@@ -111,5 +127,6 @@ module.exports = {
|
|
|
111
127
|
setConfig: setters.setConfig,
|
|
112
128
|
save: setters.save,
|
|
113
129
|
append: setters.append,
|
|
130
|
+
filter: setters.filter,
|
|
114
131
|
clear: setters.clear
|
|
115
132
|
}
|
|
@@ -61,17 +61,34 @@ module.exports = function (RED) {
|
|
|
61
61
|
if (removeOlder > 0) {
|
|
62
62
|
const removeOlderUnit = parseFloat(config.removeOlderUnit)
|
|
63
63
|
const ago = (removeOlder * removeOlderUnit) * 1000 // milliseconds ago
|
|
64
|
-
const
|
|
65
|
-
const
|
|
64
|
+
const cutOff = (new Date()).getTime() - ago
|
|
65
|
+
const filterFn = (msg) => {
|
|
66
66
|
let timestamp = msg._datapoint.x
|
|
67
67
|
// is x already a millisecond timestamp?
|
|
68
68
|
if (typeof (msg._datapoint.x) === 'string') {
|
|
69
69
|
timestamp = (new Date(msg._datapoint.x)).getTime()
|
|
70
70
|
}
|
|
71
|
-
return timestamp >
|
|
72
|
-
}
|
|
73
|
-
datastore.
|
|
71
|
+
return timestamp > cutOff
|
|
72
|
+
}
|
|
73
|
+
datastore.filter(base, node, filterFn)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* For categorical xaxis and types other than histogram then only keep the latest data point for
|
|
79
|
+
* each category in each series
|
|
80
|
+
*/
|
|
81
|
+
function clearOldCategoricalPoints () {
|
|
82
|
+
const points = datastore.get(node.id)
|
|
83
|
+
const latestSet = {}
|
|
84
|
+
for (const item of points) {
|
|
85
|
+
const { category, x } = item._datapoint
|
|
86
|
+
const key = JSON.stringify([category, x]) // a unique key for each category/series combination
|
|
87
|
+
latestSet[key] = item
|
|
74
88
|
}
|
|
89
|
+
|
|
90
|
+
const filtered = Object.values(latestSet)
|
|
91
|
+
datastore.save(base, node, filtered)
|
|
75
92
|
}
|
|
76
93
|
|
|
77
94
|
// ensure sane defaults
|
|
@@ -249,26 +266,40 @@ module.exports = function (RED) {
|
|
|
249
266
|
if (maxPoints && config.removeOlderPoints) {
|
|
250
267
|
// account for multiple lines?
|
|
251
268
|
// client-side does this for _each_ line
|
|
252
|
-
// remove older points
|
|
269
|
+
// remove older points using datastore.filter instead of saving the whole array
|
|
253
270
|
const lineCounts = {}
|
|
254
|
-
const _msg = datastore.get(node.id)
|
|
255
|
-
|
|
271
|
+
const _msg = datastore.get(node.id) || []
|
|
272
|
+
|
|
273
|
+
// determine which message objects to keep (latest maxPoints per label)
|
|
274
|
+
const keepIndexes = []
|
|
275
|
+
let doFiltering = false
|
|
256
276
|
for (let i = _msg.length - 1; i >= 0; i--) {
|
|
257
|
-
const
|
|
258
|
-
const label =
|
|
277
|
+
const m = _msg[i]
|
|
278
|
+
const label = m.topic
|
|
259
279
|
lineCounts[label] = lineCounts[label] || 0
|
|
260
|
-
if (lineCounts[label]
|
|
261
|
-
|
|
262
|
-
} else {
|
|
280
|
+
if (lineCounts[label] < maxPoints) {
|
|
281
|
+
keepIndexes[i] = true
|
|
263
282
|
lineCounts[label]++
|
|
283
|
+
} else {
|
|
284
|
+
doFiltering = true
|
|
264
285
|
}
|
|
265
286
|
}
|
|
266
|
-
|
|
287
|
+
|
|
288
|
+
// filter the datastore to only keep the selected messages
|
|
289
|
+
if (doFiltering) {
|
|
290
|
+
datastore.filter(base, node, (m, i) => {
|
|
291
|
+
return keepIndexes[i]
|
|
292
|
+
})
|
|
293
|
+
}
|
|
267
294
|
}
|
|
268
295
|
|
|
269
296
|
if (config.xAxisType === 'time' && config.removeOlder && config.removeOlderUnit) {
|
|
270
297
|
// remove any points older than the specified time
|
|
271
298
|
clearOldPoints()
|
|
299
|
+
} else if (config.xAxisType === 'category' && config.chartType !== 'histogram') {
|
|
300
|
+
// for categorical xaxis and types other than histogram then only keep the latest data point for
|
|
301
|
+
// each category in each series
|
|
302
|
+
clearOldCategoricalPoints()
|
|
272
303
|
}
|
|
273
304
|
}
|
|
274
305
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowfuse/node-red-dashboard",
|
|
3
|
-
"version": "1.30.1-
|
|
3
|
+
"version": "1.30.1-a60a579-202512301349.0",
|
|
4
4
|
"description": "FlowFuse Dashboard - A collection of Node-RED nodes that provide functionality to build your own UI applications (inc. forms, buttons, charts).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red"
|