node-red-node-ui-duallineargauge
Version:
A Node-RED ui node to display a linear dual gauge on the dashboard.
280 lines (254 loc) • 13.2 kB
JavaScript
/**
* Copyright 2018 Seth350
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
/**
* REQUIRED
* A ui-node must always begin with the following function.
* module.exports = function(RED) {your code here}
*/
module.exports = function(RED) {
var settings = RED.settings; // not sure if this is needed?
/**
* REQUIRED
* A ui-node must always contain the following function.
* function HTML(config) {}
* This function will build the HTML necessary to display the lineargauge on the dashboard.
* It will also pass in the node's config so that the parameters may be referenced from the flow editor.
*/
function HTML(config) {
var html = String.raw`
<style>
#Nome{
color: #405977;
text-align: center;
}
.Valor{
color: #0BA2A7;
text-align: center;
}
.linearGauge {
position:absolute;
width:90px;
height:100px;
}
#valueContainer {
position: bottom;
width:90px;
height:30px;
justify-content: center;
text-align:center ;
font-size:20px;
}
.scaleContainer {
height:100px;
width:90px;
}
</style>
<div id="lg_{{$id}}>
<div class="linearGauge">
<svg class="scaleContainer">
<title id="lgtooltip_{{$id}}"></title>
<rect class="1{{$id}}" x="25" y="0" width="20" height="100" fill="` + config.color1 + `"></rect>
<rect class="2{{$id}}" x="45" y="0" width="20" height="100" fill="` + config.color2 + `"></rect>
<rect id="scaleArea2_{{$id}}" x="24" y="0" width="21" height="100" fill="`+ config.backcolor +`"></rect>
<rect id="scaleArea1_{{$id}}" x="44.9" y="0" width="21" height="100" fill="`+ config.backcolor +`"></rect>
</div>
<div id="valueContainer">
<text class="Valor" dx="8" dy="3" >{{msg.payload}}`+config.unit+`</text><br>
<text class="Valor1 dx="8" dy="3" >{{msg.topic}}`+config.unit+`</text><br>
<text id="Nome" dx="8" dy="3">`+config.text1+`</text>
</div>
</div>
`;
return html;
};
/**
* REQUIRED
* A ui-node must always contain the following function.
* This function will verify that the configuration is valid
* by making sure the node is part of a group. If it is not,
* it will throw a "no-group" error.
* You must enter your node name that you are registering here.
*/
function checkConfig(node, conf) {
if (!conf || !conf.hasOwnProperty("group")) {
node.error(RED._("ui_lineargaugedual.error.no-group"));
return false;
}
return true;
}
var ui = undefined; // instantiate a ui variable to link to the dashboard
/**
* REQUIRED
* A ui-node must always contain the following function.
* function YourNodeNameHere(config){}
* This function will set the needed variables with the parameters from the flow editor.
* It also will contain any Javascript needed for your node to function.
*
*/
function LinearGaugeNode(config) {
try {
var node = this;
if(ui === undefined) {
ui = RED.require("node-red-dashboard")(RED);
}
RED.nodes.createNode(this, config);
// placing a "debugger;" in the code will cause the code to pause its execution in the web browser
// this allows the user to inspect the variable values and see how the code is executing
//debugger;
var done = null;
if (checkConfig(node, config)) {
var html = HTML(config); // REQUIRED* get the HTML for this node usi*ng the function from above
done = ui.addWidget({ // *REQUIRED* add our widget to the ui dashboard using the following configuration
node: node, // *REQUIRED*
order: config.order, // *REQUIRED* placeholder for position in page
group: config.group, // *REQUIRED*
width: config.width, // *REQUIRED*
height: config.height, // *REQUIRED*
format: html, // *REQUIRED*
templateScope: "local", // *REQUIRED*
emitOnlyNewValues: false, // *REQUIRED*
forwardInputMessages: false, // *REQUIRED*
storeFrontEndInputAsState: false, // *REQUIRED*
convertBack: function (value) {
return value;
},
beforeEmit: function(msg, value) {
return { msg: msg };
},
beforeSend: function (msg, orig) {
if (orig) { return orig.msg; }
},
/**
* The initController is where most of the magic happens.
* This is the section where you will write the Javascript needed for your node to function.
* The 'msg' object will be available here.
*/
initController: function($scope, events) {
//debugger;
$scope.flag = true; // not sure if this is needed?
$scope.$watch('msg', function(msg) {
if (!msg) {
// Ignore undefined msg
return;
}
var payload = msg.payload;
var topic = msg.topic;
//var highLimit = msg.highlimit || 100;
//var lowLimit= msg.lowlimit || 0;
//var setpoint = msg.setpoint || 0;
//var gaugeStart = 100; // this is the gauge starting position, should be left at zero
//var gaugeEnd = 0; // this is the length of the gauge, if the gauge is to be longer, this value should be the sum of the heights of the scale areas
/**
* The pointer is positioned within the scale container with the following equation.
* Pointer Position = (msg.payload x Rate) + Offset
* Rate = (Length Of Gauge - 0) / ((msg.lowlimit - delta) - (msg.highlimt + delta)
* Offset = 0 - (msg.highlimit + delta) x Rate)
* This equation will scale the msg.payload value into the gauge's length linearly.
*/
//var highDiff = highLimit - setpoint //find difference between setpoint and the high limit
//var lowDiff = setpoint - lowLimit //find difference between setpoint and the low limit
/**
* In order to span the the high and low areas of the gauge, we need to first calculate
* how much area to allow above and below the high and low limits.
* The high limit starts where the middle area meets the top area.
* The low limit starts where the middle area meets the bottom area.
* __
* | | <- Gauge Start
* | |
* |__| <- High limit begins at this horizontal line
* | | <-|
* | | | Allowable
* | | | Range
* | | <-|
* |__| <- Low limit begins at this horizontal line
* | |
* | |
* |__| <- Gauge End
*
* Without some extra room at either end, the pointer would peg out at
* either end if the payload >= highLimit or if payload <= lowLimit.
* Adding some cushion on either end of the gauge allows the user to visually
* see how far above or below the setpoint range the payload is.
*/
//var delta = ( ( highDiff + lowDiff ) / 2 ) //calculate the mean to allow the same span above/below setpoint area
//var lowSpan = lowLimit - delta //calculated low area span
//var highSpan = highLimit + delta //calulated high area span
//var rate = ( gaugeEnd - gaugeStart ) / ( lowSpan - highSpan )
//var offset = gaugeStart - ( highSpan * rate )
var value = ( 100 -payload )
var value1 = (100-topic)
//final scaled value should be between the value of gaugeStart and gaugeEnd
/**
* In order to reference an element within the HTML of the node, we must make a call
* to the $scope to get the $id of the element we want to interact with.
* We do this by calling
* $scope.$eval('$id')
* This will return the unique identifier as a number to which is associated with
* this particular node.
* In order to interact with an element, we must also declare an object to reference
* the element.
* This is done by accessing the DOM via Javascript
* document.getElementById("elementId_"+$scope.$eval('$id))
* Note that in order for this to work, you must have also entered the element id
* in the HTML. For example.
* <div id="elementId_{{$id}}">
* This is an Angular expression that will inject a unique ID number where ever you place {{$id}}.
* During creation of the ui node on the dashboard, only one ID number will be used during the
* time of creation.
*/
var ptr = document.getElementById("scaleArea2_"+$scope.$eval('$id'))
var ptr1 = document.getElementById("scaleArea1_"+$scope.$eval('$id'))
$(ptr).animate( //animate the pointer
{'ptrVal':value}, //get the final scaled value
{
step: function(ptrVal){
$(this).attr('height', ptrVal); //update the transform attribute with the new final scaled value
},
duration: 1 //sets the duration of the sliding animation of the pointer
}
);
$(ptr1).animate( //animate the pointer
{'ptrVal1':value1}, //get the final scaled value
{
step: function(ptrVal1){
$(this).attr('height', ptrVal1); //update the transform attribute with the new final scaled value
},
duration: 1 //sets the duration of the sliding animation of the pointer
}
);
});
}
});
}
}
catch (e) {
console.log(e); // catch any errors that may occur and display them in the web browsers console
}
/**
* REQUIRED
* I'm not sure what this does, but it is needed.
*/
node.on("close", function() {
if (done) { done(); }
});
}
/**
* REQUIRED
* Registers the node with a name, and a configuration.
* Type MUST start with ui_
*/
RED.nodes.registerType("ui_lineargaugedual", LinearGaugeNode);
};