Вебные аналого-цифровые индикаторы.
По итогам Есть ли что-то типа Grafana только на порядки легче и проще?
Написал ещё тогда и забыл. Ну пусть новогодний подарочек будет.
Продолжаем делать правильный вебдваноль. Как обычно, всё очень маленькое (2.5kb) и шустрое, на plain JS без каких-либо «фреймворков» и прочего многотонного жирного говна. Легко влезает в эмбедщину всякую и т.п.
Итак…
gauge.js
'use strict'
function Gauges( class_name )
{
var _ = this;
_.urls = [];
var gauge = document.getElementsByClassName( class_name );
for( var j = gauge.length; j--; )
{
var e = gauge.item(j);
var g = JSON.parse( e.getAttribute( "data-conf" ) );
if( !g ) return;
g.id = e.id;
g.title = e.title;
e.removeAttribute( 'title' );
var c = g.size / 2;
var r = 0.8 * c;
var l = 2 * Math.PI * r;
for( var i = _.urls.length; i--; ) { if( _.urls[i].url == g.url ) break; }
if( i < 0 ) _.urls.push( { url: g.url, gauges: [ g ] } );
else _.urls[i].gauges.push( g );
e.innerHTML =
'<svg width="' + g.size + '" height="' + g.size + '">'
+ '<circle class="gauge-bg" id="' + g.id + '-bg" cx="' + c + '" cy="' + c + '" r="' + r + '" stroke-dasharray="' + ( 2 * l / 3 ) + " " + ( l / 3 ) + '" stroke-dashoffset="' + ( -5 * l / 12 ) + '"/>'
+ '<circle class="gauge-pnt" id="' + g.id + '-pnt" cx="' + c + '" cy="' + c + '" r="' + r + '" stroke="' + g.color + '" stroke-width="' + ( 0.1 * g.size ) + '" stroke-dasharray="0 1"/>'
+ '<text class="gauge-val" id="' + g.id + '-val" fill="' + g.color + '" x="' + c + '" y="' + ( 1.1 * c ) + '" font-size="' + ( 0.45 * r ) + '" text-anchor="middle"></text>'
+ '<text class="gauge-title" x="' + c + '" y="' + ( c + r ) + '">' + g.title + '</text>'
+ '</svg>';
}
_.urls.forEach( function( u )
{
_.update( u );
setInterval( function(){ _.update( u ) }, u.gauges[0].int );
} );
}
Gauges.prototype.set = function( id, min, max, val )
{
var e = document.getElementById( id + '-pnt' );
if( !e ) return;
var r = e.getAttribute( 'r' );
var l = 2 * Math.PI * r;
var v = val < min ? min : val > max ? max : val;
var dash = ( 2 / 3 ) * l * ( v - min ) / ( max - min );
e.setAttribute( 'stroke-dasharray', [ dash, l - dash ]);
e.setAttribute( 'stroke-dashoffset', (-5 / 12) * l );
document.getElementById( id + "-val" ).textContent = val;
}
Gauges.prototype.update = function( u )
{
var _ = this;
var req = new XMLHttpRequest();
req.onreadystatechange = function()
{
if( this.readyState != 4 ) return;
var obj = JSON.parse( this.responseText );
if( !obj ) return;
u.gauges.forEach( function( g )
{
var v = g.path.split('.').reduce( function( o, k ) { return o && o[k] }, obj );
_.set( g.id, g.min, g.max, v.toFixed( g.prec ) );
} );
};
req.open( "GET", u.url );
req.send();
}
gauge.css
@font-face {
font-family: "7SEG";
src: url("DSEG7Classic-BoldItalic.ttf") format("opentype");
}
.gauge-pnt {
fill: transparent;
}
.gauge-bg {
stroke: #808080;
stroke-width: 1px;
fill: transparent;
}
.gauge-val {
font-family: "7SEG", serif;
}
.gauge-title {
text-anchor: middle;
fill: #C0C0C0;
}
Шрифт DSEG7Classic-BoldItalic.ttf ради прикола, можно из дебиановского пакета fonts-dseg взять. Или в формате woff2, он размером поменьше будет.
Как использовать:
<HTML>
<HEAD>
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
<META HTTP-EQUIV="Cache-Control" content="no-cache">
<META HTTP-EQUIV="Content-Type" content="text/html; charset=utf-8">
<LINK rel="stylesheet" type="text/css" href="gauge.css">
<SCRIPT src="gauge.js" type="text/javascript"></SCRIPT>
<SCRIPT>
var ups_gauges;
window.addEventListener( 'load', function() { ups_gauges = new Gauges( 'ups' ); }, false );
</SCRIPT>
<STYLE>
a.ups { outline: none; }
body { background-color: #000000; color: #c0c0c0; }
</STYLE>
<TITLE>Gauge</TITLE>
</HEAD>
<BODY>
<a href="ups/voltage.html" class="ups" title="UPS Vin, V" id="Vin" data-conf='{"url":"data/ups.json","path":"Vin","int":20000,"min":0,"max":300,"prec":0,"color":"#40C040","size":150}'></a>
<a href="ups/load.html" class="ups" title="UPS Load, %" id="Load" data-conf='{"url":"data/ups.json","path":"Load","int":20000,"min":0,"max":100,"prec":0,"color":"#C040C0","size":150}'></a>
<a href="ups/temp.html" class="ups" title="UPS T, °C" id="Temp" data-conf='{"url":"data/ups.json","path":"Temp","int":20000,"min":0,"max":100,"prec":1,"color":"#C0C0C0","size":150}'></a>
</BODY>
data/ups.json
{ "Vin": 193.2, "Load": 40, "Temp": 38.0 }
В аттрибут data-conf элемента который превращаем в индикатор пишем JSON где
url - URL источника данных для индикатора в формате JSON
path - "путь" в JSON до нужной величины, например "sensors.0.temp"
int - интервал обновления в миллисекундах
min - минимальное значение аналоговой шкалы индикатора
max - максимальное значение аналоговой шкалы индикатора
prec - число цифр после запятой для цифрового индикатора
color - цвет индикатора
size - размер индикатора
У элементов превращаемых в индикаторы должен быть установлен аттрибут id.
В аттрибуте title указывается подпись под индикатором.
Элементам индикаторов одной группы присваеваем один класс и его имя передаём в конструктор new Gauges('class_name')
когда нужно создать и запустить индикаторы.
Если у нескольких индикаторов одного и того же класса URL источника данных одинаковый, то запрос к этому URL при периодическом обновлении будет только один, но при этом будут обновлены все индикаторы этого класса с этим URL.
Выглядит так: http://stanson.ch/gauges.png
Лицнзия WTFPL