Skip to content

Commit 6b30c4f

Browse files
Merge branch 'master' into minmax_msg
2 parents dca9e51 + 391c755 commit 6b30c4f

File tree

4 files changed

+121
-13
lines changed

4 files changed

+121
-13
lines changed

Diff for: README.md

+29-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ npm install node-red-contrib-ui-heatmap
1313

1414
To solve this, you will need to remove your old heatmap nodes in the flow and replace them by new heatmap nodes. Or if you are an experienced user, you can replace the node type *'heat-map'* by *'ui_heat_map'* in your flow JSON file.
1515

16+
## Support my Node-RED developments
17+
18+
Please buy my wife a coffee to keep her happy, while I am busy developing Node-RED stuff for you ...
19+
20+
<a href="https://www.buymeacoffee.com/bartbutenaers" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy my wife a coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
21+
1622
## Node Usage
1723
A heatmap (or temperature map) is a graphical representation of data, where the input values (contained in a **matrix**) are represented as colors. Low numeric input numbers will be represented in the heatmap as *blue*, while high numeric numbers will be represented as *red*. All other numbers in between will be represented by intermediate colors.
1824

@@ -48,6 +54,7 @@ The matrix of input ***numbers*** needs to be specified in ```msg.payload``` as
4854
```
4955
[{"id":"24236b6b.9ffc64","type":"function","z":"5a89baed.89e9c4","name":"Mix of values and key-value pairs","func":"// Generate some random data\n// See https://www.patrick-wied.at/static/heatmapjs/example-minimal-config.html\nmsg.payload = [];\n\nfor (var i = 0; i < 49; i++) {\n var value = Math.floor(Math.random()*100);\n if (i % 2 === 0) {\n var pair = {};\n pair['key' + i] = value;\n msg.payload.push(pair);\n }\n else {\n msg.payload.push(value);\n }\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":520,"y":1700,"wires":[["fe91d79b.3b9be8"]]},{"id":"4372ce10.a3309","type":"inject","z":"5a89baed.89e9c4","name":"Show heatmap","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":260,"y":1700,"wires":[["24236b6b.9ffc64"]]},{"id":"fe91d79b.3b9be8","type":"ui_heat_map","z":"5a89baed.89e9c4","group":"143de3c.3c1901c","order":0,"width":"6","height":"5","name":"","rows":"7","columns":"7","minMax":false,"minimumValue":0,"maximumValue":0,"backgroundType":"image","backgroundColor":"#ff80c0","radius":40,"opacity":"0","blur":0.85,"showValues":false,"gridType":"keys","valuesDecimals":0,"showLegend":false,"legendType":"vals","legendDecimals":0,"legendCount":2,"x":760,"y":1700,"wires":[]},{"id":"143de3c.3c1901c","type":"ui_group","z":"","name":"Heatmap","tab":"4e49ccae.5e3364","disp":true,"width":"6","collapse":false},{"id":"4e49ccae.5e3364","type":"ui_tab","z":"","name":"Heatmap","icon":"dashboard","disabled":false,"hidden":false}]
5056
```
57+
***CAUTION:*** Only integer numbers are supported! If e.g. input numbers between 0.0 and 1.0 need to be displayed, then it is required to convert those decimal numbers to integer numbers before they are injected into this node...
5158

5259
### Troubleshooting
5360
If no heatmap is being drawn, you might check the following:
@@ -58,7 +65,7 @@ If no heatmap is being drawn, you might check the following:
5865
## Node configuration
5966

6067
### Grid size (rows & columns)
61-
Define the number of rows and columns, i.e. the size of the matrix that will contain all the individual numeric values (which will be represented visually as colors in the heatmap).
68+
Define the number of rows and columns, i.e. the size of the matrix that will contain all the individual numeric values (which will be represented visually as colors in the heatmap). The size can dynamically overridden by adding `msg.rows` and `msg.columns` in the input message.
6269

6370
***CAUTION: the product of rows * columns should equal to the length of the input array!***
6471
In other words, you need to specify a numeric value for 'every' cell in the result matrix.
@@ -127,6 +134,27 @@ Specify which data needs to be displayed behind the heatmap:
127134

128135
**CAUTION**: The opacity property should be 0 (or a value close to 0), otherwise the background image won't be clearly visible!
129136

137+
### Snapshot
138+
Specify whether a (base64 encoded) jpeg snapshot image (of the heatmap) should be send in the `msg.payload` of an output message:
139+
+ *Never*: the image will never be created or send.
140+
+ *Always*: the image will be created and send for every input message.
141+
+ *At request*: the image will only be created when an input message with `msg.snapshot=true` arrives.
142+
+ When the `msg.payload` also contains an array, then first the heatmap will be regenerated (based on the array values). And afterwards the snapshot of this new heatmap will be send.
143+
+ When the `msg.payload` does not contains an array, then the snapshot (of the previous heatmap) will be send.
144+
145+
The following example flow demonstrates the possible scenario's:
146+
147+
![snapshot flow](https://user-images.githubusercontent.com/14224149/109879776-52ad3400-7c76-11eb-807b-51803918ab1e.png)
148+
```
149+
[{"id":"1c6c035e.67de0d","type":"function","z":"2b6f5d19.202242","name":"Generate random matrix","func":"// Generate some random data\n// See https://www.patrick-wied.at/static/heatmapjs/example-minimal-config.html\nvar len = 200;\n\nmsg.payload = [];\n\nwhile (len--) {\n var value = Math.floor(Math.random()*100);\n msg.payload.push(value);\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":550,"y":720,"wires":[["8d9577d5.393358"]]},{"id":"7b9a2303.663d1c","type":"inject","z":"2b6f5d19.202242","name":"Show heatmap","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":720,"wires":[["1c6c035e.67de0d"]]},{"id":"8d9577d5.393358","type":"ui_heat_map","z":"2b6f5d19.202242","group":"2e442781.0c5608","order":0,"width":"6","height":"5","name":"","rows":"20","columns":"10","minMax":false,"minimumValue":0,"maximumValue":0,"backgroundType":"color","backgroundColor":"#ffffff","image":"request","radius":40,"opacity":0.6,"blur":0.85,"showValues":false,"gridType":"none","valuesDecimals":0,"showLegend":false,"legendType":"none","legendDecimals":0,"legendCount":2,"x":800,"y":720,"wires":[["a111d65c.d7be98"]]},{"id":"a111d65c.d7be98","type":"image","z":"2b6f5d19.202242","name":"","width":160,"data":"payload","dataType":"msg","thumbnail":false,"active":true,"pass":false,"outputs":0,"x":1000,"y":720,"wires":[]},{"id":"7911161f.a440a8","type":"inject","z":"2b6f5d19.202242","name":"Show heatmap + get snapshot","props":[{"p":"payload"},{"p":"topic","vt":"str"},{"p":"snapshot","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":230,"y":780,"wires":[["1c6c035e.67de0d"]]},{"id":"3a705f1a.5772c","type":"inject","z":"2b6f5d19.202242","name":"Get snapshot","props":[{"p":"snapshot","v":"true","vt":"bool"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","x":580,"y":780,"wires":[["8d9577d5.393358"]]},{"id":"2e442781.0c5608","type":"ui_group","name":"Heatmap","tab":"4779176.99cd2e8","order":1,"disp":true,"width":"6","collapse":true},{"id":"4779176.99cd2e8","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]
150+
```
151+
Note that the node-red-contrib-image-output node should be installed to run this flow.
152+
The following demo shows that a jpeg will be send to the output, for every input message that arrives:
153+
154+
![heatmap_image](https://user-images.githubusercontent.com/14224149/109710987-a3515e00-7b9e-11eb-9467-a925f48c31d0.gif)
155+
156+
Note the above flow is in fact not really useful. When the server side nodes inject a snapshot message, we will get N duplicate snapshots when N dashboards are showing the heatmap at this moment. And when no dashboard is open, no snapshot will be send. So the snapshot option is only useful when triggered by a user action on the dashboard (e.g. a user clicks a 'create snapshot' button)!
157+
130158
### Radius
131159
Each point in the input matrix will have a radius, to avoid a blocky map for low-resolution matrices.
132160

Diff for: heat_map.html

+24-3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
maximumValue: {value: 0},
4141
backgroundType: {value: "color"},
4242
backgroundColor: {value: '#ffffff'},
43+
image: {value: "never"},
4344
radius: {value: 40},
4445
opacity: {value: 0.6},
4546
blur: {value: 0.85},
@@ -52,7 +53,7 @@
5253
legendCount: {value: 2}
5354
},
5455
inputs:1,
55-
outputs:0,
56+
outputs:1,
5657
icon: "heatmap.png",
5758
align: 'right',
5859
paletteLabel:"heat map",
@@ -64,6 +65,11 @@
6465
if (!this.backgroundType) {
6566
this.backgroundType = "color";
6667
}
68+
69+
// For existing nodes the image should be 'never'
70+
if (!this.image) {
71+
$("#node-input-image").val("never");
72+
}
6773

6874
// When loading a heatmap version 2.0.0 or older, there was a checkbox 'showValues' that showed numeric values (on top of the heatmap).
6975
// Let's convert that checkbox now to a dropdown value.
@@ -214,7 +220,15 @@
214220
<label for="node-input-backgroundColor"><i class="fa fa-paint-brush"></i> Color</span></label>
215221
<input type="color" id="node-input-backgroundColor"/>
216222
</div>
217-
</div>
223+
</div>
224+
<div class="form-row">
225+
<label for="node-input-image"><i class="fa fa-picture-o"></i> Snapshot</label>
226+
<select id="node-input-image">
227+
<option value="never">Never</option>
228+
<option value="always">Always</option>
229+
<option value="request">At request</option>
230+
</select>
231+
</div>
218232
<div class="form-row">
219233
<label for="node-input-radius"><i class="fa fa-bullseye"></i> Radius</span></label>
220234
<input type="number" id="node-input-radius" min="0"/>
@@ -237,7 +251,7 @@
237251
<p>A Node Red node to show a heat map.</p>
238252
<p>A heatmap is a graphical representation of data, where the individual values contained in a matrix are represented as colors. See my <a target="_blank" href="https://github.com/bartbutenaers/node-red-contrib-ui-heatmap/">readme page</a> for more information</p>
239253
<p><strong>Grid size (rows & columns):</strong><br/>
240-
Define the number of rows and columns, for the matrix that will contain all the individual numeric values (which will be represented visually as colors in the heatmap).</p>
254+
Define the number of rows and columns, for the matrix that will contain all the individual numeric values (which will be represented visually as colors in the heatmap). These settings can be overridden by <code>msg.rows</code> and <code>msg.columns</code></p>
241255
<p><strong>Specify minimum and maximum value:</strong><br/>
242256
Low numeric input numbers will be represented in the heatmap as <font color="blue">blue</font>, while high numeric numbers will be represented as <font color="red">red</font>. All other numbers in between will be represented by intermediate colors. There are three ways to specify the minimum and maximum numbers:
243257
<ul>
@@ -265,6 +279,13 @@
265279
<li><i>Fixed color:</i> the specified color will be drawn in the background.</li>
266280
<li><i>Image:</i> an image will be drawn (stretched) as background. That background image can be specified via <code>msg.image</code>. Make sure the opacity is set to 0.</li>
267281
</ul></p>
282+
<p><strong>Snapshot:</strong><br/>
283+
Specify whether a (base64 encoded) jpeg snapshot image (of the heatmap) should be send in an output message:
284+
<ul>
285+
<li><i>Never:</i> the image will never be created or send.</li>
286+
<li><i>Always:</i> the image will be created and send for every input message.</li>
287+
<li><i>At request:</i> the image will only be created when an input message with <code>msg.snapshot=true</code> arrives.</li>
288+
</ul></p>
268289
<p><strong>Radius:</strong><br/>
269290
Each point in the matrix will have a radius, to avoid a blocky map for low-resolution matrices.</p>
270291
<p><strong>Opacity:</strong><br/>

Diff for: heat_map.js

+67-8
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,39 @@ module.exports = function(RED) {
7676
// See https://discourse.nodered.org/t/custom-ui-node-not-visible-in-dashboard-sidebar/9666
7777
// We will workaround it by sending a 'null' payload to the dashboard.
7878

79+
var configRows = parseInt(config.rows);
80+
var configColumns = parseInt(config.columns);
81+
82+
// The row count might be specified in the input message
83+
if (msg.rows != undefined) {
84+
if (Number.isInteger(msg.rows)) {
85+
configRows = msg.rows;
86+
}
87+
else {
88+
console.log("The input msg.rows should be an integer number");
89+
}
90+
}
91+
92+
// The column count might be specified in the input message
93+
if (msg.columns != undefined) {
94+
if (Number.isInteger(msg.columns)) {
95+
configColumns = msg.columns;
96+
}
97+
else {
98+
console.log("The input msg.columns should be an integer number");
99+
}
100+
}
101+
79102
// When there is a payload, it should be an array.
80103
// It could be that there is no payload, e.g. when only a background image is being set
81104
if (msg.payload) {
82105
if (!Array.isArray(msg.payload)) {
83106
node.error("The msg.payload should contain an array");
84107
msg.payload = null;
85108
}
86-
else if (msg.payload.length != parseInt(config.rows) * parseInt(config.columns)) {
109+
else if (msg.payload.length != parseInt(configRows) * parseInt(configColumns)) {
87110
node.error("The length (" + msg.payload.length + ") of the array in msg.payload should be equal to rows (" +
88-
config.rows + ") x columns (" + config.columns + ")");
111+
configRows + ") x columns (" + configColumns + ")");
89112
msg.payload = null;
90113
}
91114
else {
@@ -160,9 +183,6 @@ module.exports = function(RED) {
160183
if (!msg) {
161184
return;
162185
}
163-
164-
debugger;
165-
166186

167187
var parentDiv = document.getElementById('heatMapContainer_' + $scope.config.id);
168188

@@ -197,15 +217,38 @@ module.exports = function(RED) {
197217
// Make sure the background image will fit inside the div, to avoid it will be repeated
198218
parentDiv.style.backgroundSize = "100% 100%";
199219
}
220+
221+
var configRows = $scope.config.rows;
222+
var configColumns = $scope.config.columns;
223+
224+
// The row count might be specified in the input message
225+
if (msg.rows != undefined) {
226+
if (Number.isInteger(msg.rows)) {
227+
configRows = msg.rows;
228+
}
229+
else {
230+
console.log("The input msg.rows should be an integer number");
231+
}
232+
}
233+
234+
// The column count might be specified in the input message
235+
if (msg.columns != undefined) {
236+
if (Number.isInteger(msg.columns)) {
237+
configColumns = msg.columns;
238+
}
239+
else {
240+
console.log("The input msg.columns should be an integer number");
241+
}
242+
}
200243

201-
if (msg.payload && Array.isArray(msg.payload) && msg.payload.length === $scope.config.rows * $scope.config.columns) {
244+
if (msg.payload && Array.isArray(msg.payload) && msg.payload.length === configRows * configColumns) {
202245
var maxValue = 0;
203246
var minValue = Number.MAX_VALUE;
204247
var points = [];
205248
var index = 0;
206249

207-
var columns = parseInt($scope.config.columns);
208-
var rows = parseInt($scope.config.rows);
250+
var columns = parseInt(configColumns);
251+
var rows = parseInt(configRows);
209252

210253
// Calculate the width and height ratios, from the data matrix to the available canvas size.
211254
// These ratio's are in fact the distance between the points ...
@@ -406,6 +449,22 @@ module.exports = function(RED) {
406449
}
407450
}
408451
}
452+
else {
453+
console.log("The msg.payload is not an array of length " + configRows + " * " + configColumns);
454+
}
455+
456+
if ($scope.config.image === "always" || ($scope.config.image === "request" && msg.snapshot === true)) {
457+
// Get a reference to the heatmap.js canvas element
458+
var heatmapCanvas = parentDiv.getElementsByClassName("heatmap-canvas")[0];
459+
460+
// Get the canvas as a jpeg image inside a data url (data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ...9oADAMBAAIRAxEAPwD/AD/6AP/Z")
461+
var dataUrl = heatmapCanvas.toDataURL('image/jpeg', 1.0); // 1.0 = full quality
462+
463+
// Get the base64 image from the data url
464+
var base64Image = dataUrl.split(';base64,')[1];
465+
466+
$scope.send({payload: base64Image});
467+
}
409468
});
410469
}
411470
});

Diff for: package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name" : "node-red-contrib-ui-heatmap",
3-
"version" : "2.1.4",
3+
"version" : "2.2.1",
44
"description" : "A Node Red node to show a heat map",
55
"dependencies": {
66
},

0 commit comments

Comments
 (0)