@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.
@@ -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 cutoff = (new Date()).getTime() - ago
65
- const _msg = datastore.get(node.id).filter((msg) => {
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 > cutoff
72
- })
73
- datastore.save(base, node, _msg)
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
- // trawl through in reverse order, and only keep the latest points (up to maxPoints) for each label
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 msg = _msg[i]
258
- const label = msg.topic
277
+ const m = _msg[i]
278
+ const label = m.topic
259
279
  lineCounts[label] = lineCounts[label] || 0
260
- if (lineCounts[label] >= maxPoints) {
261
- _msg.splice(i, 1)
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
- datastore.save(base, node, _msg)
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-9f365cd-202512291502.0",
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"