Well this is far better than I expected - or deserve even!
I took the liberty of adding the following to the CSS template to remove the redundant scroll bar on the RHS of the display. My application has a black background for maximum contrast in daylight on a tablet, hence the need to hide it.
.nr-dashboard-template {
overflow: hidden; /* Hide both vertical and horizontal scrollbars */
}
.nr-dashboard-template .md-card {
overflow: hidden; /* Ensure that the md-card element also hides overflow */
}
Not sure if the second entry is particularly essential, otherwise, totally brill!
It hadn't dawned upon me that a template node can be used in such a manner. I obviously need to immerse myself in these techniques to learn more, because I feel awkward when so much is handed on a plate a lot of people seem to want an instant solution as opposed to some pointers to start their research. Is there a source of sample templates or code which one can play around with to create custom gauges?
I would like to explore the option of feeding a second input to create an arc either side of the red lubber-line. A much simpler option would be to to set the secondary value to display from a second input, perhaps? As you can guess I am relatively new to Node-Red.
My system receives the Course Over Ground from a GPS NMEA sentence and when feeding it together with the Compass data through a simple differential comparator, I can create a Drift value and at present I am using an Artless-Gauge node in linear mode which is centred to display degrees +/- and spans left in red for drift to the left and green to the right.
If anyone is interested, here is a simplified flow,
[{"id":"698d3f71240ceac6","type":"function","z":"f8cabeaf5583af25","name":"Differential calculator","func":"\n// Extract HEADING and COURSE values from input\nvar heading = msg.payload.heading;\nvar course = msg.payload.track;\n\n// Convert to -180 to 180 range\nif (heading > 180) {\n heading -= 360;\n}\nif (course > 180) {\n course -= 360;\n}\n\n// Compute difference\nvar diff = course - heading;\n\n// Handle wraparound case\nif (diff > 180) {\n diff -= 360;\n}\nelse if (diff < -180) {\n diff += 360;\n}\n\n// Output difference invert\nmsg.payload = diff *-1;\nmsg.payload = Number(msg.payload.toFixed(0));\nreturn msg;\n\n\n\n\n\n\n\n\n\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":920,"y":160,"wires":[["f1338bfc4ea2dac9"]]},{"id":"2419889beb674d9c","type":"mqtt in","z":"f8cabeaf5583af25","name":"","topic":"/nav/compass1","qos":"0","datatype":"auto-detect","broker":"a9d314e3.32bb38","nl":false,"rap":true,"rh":0,"inputs":0,"x":200,"y":260,"wires":[["f3043f6d21ebfaf9","9afd8086c41578dd"]]},{"id":"632488c623efb4bb","type":"join","z":"f8cabeaf5583af25","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":710,"y":160,"wires":[["698d3f71240ceac6"]]},{"id":"f3043f6d21ebfaf9","type":"function","z":"f8cabeaf5583af25","name":"set topic","func":"msg.topic = \"heading\";\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":460,"y":200,"wires":[["632488c623efb4bb"]]},{"id":"93b3ddc801a1b7ee","type":"function","z":"f8cabeaf5583af25","name":"set topic & convert","func":"//sets topic and converts GPS track ° string to number\n\nmsg.topic = \"track\";\n\nmsg.payload = Number(msg.payload);\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":160,"wires":[["632488c623efb4bb"]]},{"id":"9afd8086c41578dd","type":"function","z":"f8cabeaf5583af25","name":"359° / 0° Transition","func":"\nif (msg.payload == null){\n msg.payload = \"OFFLINE\";\n}\n\n//Correcton for North between and 360 / 0 \nif \n (msg.payload <0) {\n msg.payload = msg.payload + 359;\n }\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":260,"wires":[["2c81cb5325f47588"]]},{"id":"f1338bfc4ea2dac9","type":"ui_artlessgauge","z":"f8cabeaf5583af25","group":"6bee4bad7f6e6f30","order":7,"width":4,"height":2,"name":"DRIFT","icon":"","label":"DRIFT","unit":"","layout":"linear","decimals":"0","differential":true,"minmax":false,"colorTrack":"#555555","style":"2, 2","colorFromTheme":true,"property":"payload","secondary":"secondary","inline":false,"animate":true,"sectors":[{"val":-25,"col":"#ff0000","t":"min","dot":0},{"val":-1,"col":"#ffffff","t":"sec","dot":0},{"val":2,"col":"#00ff40","t":"sec","dot":0},{"val":25,"col":"#00ff40","t":"max","dot":0}],"lineWidth":"21","bgcolorFromTheme":true,"diffCenter":"0","x":1110,"y":160,"wires":[]},{"id":"d93d907177019905","type":"link in","z":"f8cabeaf5583af25","name":"GPS COMPASS IN","links":["71af3fc27771da1d"],"x":265,"y":160,"wires":[["93b3ddc801a1b7ee"]]},{"id":"2c81cb5325f47588","type":"ui_template","z":"f8cabeaf5583af25","group":"6bee4bad7f6e6f30","name":"compass","order":2,"width":6,"height":6,"format":"<div class=\"windrose\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" x=\"0\" y=\"0\" width=\"100%\" height=\"100%\" viewBox=\"0 0 500 500\">\n <g class=\"graphics\" id=\"{{'wr_'+$id}}\">\n <g>\n <circle cx=\"250\" cy=\"250\" r=\"205\" class=\"ring\" />\n <circle cx=\"250\" cy=\"250\" r=\"205\" class=\"tick major\" pathLength=\"720\" />\n <circle cx=\"250\" cy=\"250\" r=\"200\" class=\"tick mid\" pathLength=\"720\" />\n <circle cx=\"250\" cy=\"250\" r=\"195\" class=\"tick minor\" pathLength=\"720\" />\n </g>\n <g>\n <text transform=\"matrix(1 0 0 1 385 268)\" class=\"letters\">E</text>\n <text transform=\"matrix(1 0 0 1 233 420)\" class=\"letters\">S</text>\n <text transform=\"matrix(1 0 0 1 233 112)\" class=\"letters\">N</text>\n <text transform=\"matrix(1 0 0 1 82 268)\" class=\"letters\">W</text>\n </g>\n </g>\n\n </svg>\n <div class=\"txt\">\n <div id=\"{{'wrp_'+$id}}\"></div>\n <div class=\"small\"></div>\n <div class=\"small\">DRIFT</div>\n <div id=\"{{'wrs_'+$id}}\"></div>\n </div>\n <div class=\"needle\"></div>\n</div>\n\n<script>\n (function(scope) {\n scope.$watch('msg', function(msg) {\n if (msg) {\n $(\"#wr_\"+scope.$id).css('transform','rotate('+(360 - msg.payload)+'deg)')\n $(\"#wrp_\"+scope.$id).text(msg.payload+\"°\")\n $(\"#wrs_\"+scope.$id).text(360-msg.payload+\"°\")\n // $(\"#wrs_\"+scope.$id).text(360-msg.payload+\"°\")\n }\n });\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":720,"y":260,"wires":[[]]},{"id":"16475922080466d5","type":"ui_template","z":"f8cabeaf5583af25","group":"114a2e1f69fe362d","name":"CSS","order":1,"width":0,"height":0,"format":"<style>\n div:has(>.windrose) {\n align-items: center;\n }\n\n .windrose {\n position: relative;\n width: 100%;\n aspect-ratio: 1;\n container-type: inline-size;\n }\n .windrose .graphics{\n transform-box: fill-box;\n transform-origin:center;\n }\n\n .tick {\n fill: none;\n stroke: currentColor;\n }\n\n .major {\n stroke-dasharray: 1 89;\n stroke-dashoffset: 0.5;\n stroke-width: 40px;\n }\n\n .mid {\n stroke-dasharray: 0 45 1 44 0 45 1 44;\n stroke-dashoffset: 0.5;\n stroke-width: 30px;\n }\n\n .minor {\n stroke-dasharray: 0 5 1 4 1 4 1 4 1 4 1 4 1 4 1 4 1 4;\n stroke-dashoffset: 0.5;\n stroke-width: 20px;\n }\n\n .letters {\n font-size: 48px;\n fill: currentColor;\n }\n\n .ring {\n fill: none;\n stroke: currentColor;\n stroke-width: 40;\n opacity: 0.1;\n }\n\n .needle {\n position: absolute;\n inset: 0;\n left: calc(50% - 1px);\n width: 2px;\n height: 100%;\n transform-origin: center;\n transition: rotate .5s;\n }\n\n .needle:before {\n content: \"|\";\n color: red;\n position: absolute;\n top: 1%;\n text-align: center;\n font-size: clamp(0.5rem, 10cqi, 2rem);\n transform: translateX(calc(-50% + 1px));\n }\n\n\n .txt {\n position: absolute;\n inset: 0;\n display: grid;\n place-content: center;\n width: 100%;\n text-align: center;\n font-size: clamp(0.5rem, 12cqi, 3rem);\n font-weight: 700;\n line-height: 1.2em;\n }\n\n .txt .small {\n font-size: clamp(0.2rem, 6cqi, 1.5rem);\n line-height: 1em;\n font-weight: 500;\n }\n\n\n.nr-dashboard-template {\noverflow: hidden; /* Hide both vertical and horizontal scrollbars */\n}\n\n\n\n\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"global","className":"","x":710,"y":320,"wires":[[]]},{"id":"a9d314e3.32bb38","type":"mqtt-broker","name":"FLUIDS","broker":"192.168.4.1","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"6bee4bad7f6e6f30","type":"ui_group","name":"OPS","tab":"556fcff9.b3236","order":1,"disp":false,"width":"18","collapse":false,"className":""},{"id":"114a2e1f69fe362d","type":"ui_group","name":"Default","tab":"47600b49e8d3c165","order":1,"disp":true,"width":"6","collapse":false,"className":""},{"id":"556fcff9.b3236","type":"ui_tab","name":"OPS","icon":"dashboard","order":4,"disabled":false,"hidden":false},{"id":"47600b49e8d3c165","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]