Gauges voor weergave van huidige temperaturen

Introductie

In het kader van het maken van een Raspberry Pi-temperatuursensor, waar ik een tijd geleden een blogpost over heb geschreven, heb ik mij ook wat meer verdiept in hoe je het beste een metertje zou kunnen weergeven die real-time informatie verschaft over de temperatuur. Het mooiste zou zijn om een analoge temperatuurmeter te kunnen simuleren. We zijn namelijk allemaal gewend om dit soort analoge meters snel af te kunnen lezen. De precisie van de digitale meters is voor ons vaak helemaal niet van belang en zijn soms zelfs lastiger te interpreteren. Vandaar dat ik geprobeerd heb een analoge ronde thermometer te realiseren.

Tekenframework

Omdat ronde elementen met enkel CSS op dit moment erg lastig te maken zijn, moest ik een tekenlibrary gaan gebruiken. De twee beste kandidaten waren:

  • ProcessingJS, om te kunnen tekenen binnen een HTML5-canvas.
  • RaphaëlJS, om een SVG-vectorafbeelding te genereren.

De kandidaat die voor het tekenen van een rond metertje het beste geschikt leek was RaphaelJS. Hierbij kunnen namelijk alle elementen van het metertje gedefinieerd worden als vectoren. Van een lijn worden dan niet alle pixels opgeslagen met hun kleurwaarde, maar worden enkel de coördinaten van de hoekpunten van de lijn opgeslagen. De tekeningen die hieruit volgen kunnen oneindig uitvergroot worden zonder vervelende bijwerkingen zoals korrelige randen.

ProcessingJS werkt met rasterelementen, Hierbij wordt wel gewerkt met pixelwaarden. Wanneer je een heel groot canvas hebt, waar enkel een lijn op staat, wordt toch voor alle pixels in het canvas de kleurwaarde opgeslagen. ProcessingJS is echter wel beter wanneer je gaat werken met foto's of wanneer de kans groot is dat het canvas helemaal vol zal worden getekend met veel verschillende overlappende elementen. Ook is het werken met een canvas beter geschikt op het moment dat veel gebruik gemaakt gaat worden met effecten als kleurverlopen en schaduwen.

Gebruik

Het aanmaken van een IADAGauge is erg simpel. Je maakt het object aan en geeft het een optieobject mee:

    var gauge1 = new IADAGauge({label: 'Binnen \n Temp (°C)', container: 'gaugeBinnen'});

Vervolgens kun je de meter updaten met de updatefunctie;

    gauge1.update(nieuwe_waarde);

Belangrijkste opties die je kunt meegeven tijdens het aanmaken van het object zijn:

  • container
    Dit is de ID van het element waarin je de meter wilt tonen.
  • margins (top, left, bottom, right)
    Hiermee geef je aan hoeveel ruimte er nog aan alle zijden buiten de meter moet worden vrijgelaten.
  • size (height, width)
    Hiermee kun je de grootte van de meter instellen, hoogte en breedte.
  • scale (min, max, step, bigstep)
    Hiermee kun je de schaal van de meter verdelen. De minimale waarde die de meter zal kunnen tonen en de maximale waarde. De step definieert de stapgrootte van de kleine streepjes. De bigstep definieert de grote schaalstrepen waarbij ook een waarde staat vermeld.
  • scaleColoring
    Dit is een array van objecten die een from, een to en een color definiëren. Hiermee kun je een bereik binnen de schaal een bepaalde kleur geven. Voorbeeld: {from: -10, to: 0, color: '#4183C4'}
  • label
    Dit bepaalt het label dat de meter toont.
  • labelPosition
    Positie van het label: moet deze in het bovenste halfrond getoond worden ("top"), of in het onderste halfrond van de meter ("bottom")
  • degreeUsed
    Gehele waarde die het aantal graden bepaalt dat de meter in beslag zal nemen. Bij 360 krijg je een schaalverdeling die helemaal rond loopt, bij 180 een schaalverdeling over de helft van de meter.
  • scaleIndicator (size, margin)
    Bepaalt de grootte van en de afstand tussen de schaalstreepjes.
  • needle (initialValue, pointRadius)
    Naald: initiële waarde die getoond wordt als de meter wordt opgebouwd, en de radius van de as waarom de naald draait.

Het standaard optieobject ziet er als volgt uit:

{
	container: 'gauge',
	margins: {top: 10, left: 10, bottom: 10, right: 10},
	size: {height: 200, width: 200},
	scale: {min: -10, max: 40, step: 1, bigStep: 5},
	scaleColoring: [
		{from: -10, to: 0, color: '#4183C4'},
		{from: 20, to: 30, color: '#FFDB14'},
		{from: 30, to: 40, color: '#DA3838'},
	],
	label: 'Temp (°C)',
	labelPosition: 'bottom', // top or bottom
	degreeUsed: 250,
	scaleIndicator: {size: 10, margin: 10},
	needle: {initialValue: 0, pointRadius: 5},
}

Raspberry PI temperature monitoring project

Voor de Raspberry Pi temperatuursnippet zou je dus de volgende functie kunnen toevoegen om met Ajax de huidige temperatuur op te halen (met JSON). Wanneer je deze functie vervolgens aanroept met een javascript-interval, wordt de meter automatisch bijgewerkt.

	function loadtempValue() {
		var xmlhttp;
		xmlhttp = new XMLHttpRequest();
		xmlhttp.onreadystatechange = function() {
			if (xmlhttp.readyState==4 && xmlhttp.status==200) {
				obj = JSON.parse(xmlhttp.responseText);
				gauge1.update(parseFloat(obj.temp1));
				gauge2.update(parseFloat(obj.temp2));
			}
		}
		xmlhttp.open("GET","URL_TO_YOUR_index.php?format=json",true);
		xmlhttp.send();
	}

Want deze index.php geeft de meest recente temperatuurwaarden terug als json indien de meegekregen $_GET parameter "format" gelijk is aan "json". In de index.php hadden we namelijk het volgende staan:

	if($_GET['format'] == 'json') {
		header('Content-Type: application/json');
		print json_encode($lastTemps);
		exit;
	}

Points

Om de code wat overzichtelijker te maken, heb ik gebruik gemaakt van een class die een punt in een 2D-matrix representeert (met een x-waarde en een y-waarde). Deze class is afgeleid van de uitwerking van moagrius (Point.js op Github). Dit heeft als voordeel dat je niet steeds apart een x- en een y-waarde als parameters mee hoeft te geven aan functies en dit maakt de kans op het per ongeluk verwisselen van de x- en y-waarden kleiner. Verder bevat de point class veelgebruikte puntmanipulatiefuncties zoals optellen, aftrekken, afstand tussen twee punten berekenen, vergelijken en polariseren. Een functie die ik heb toegevoegd aan deze class is de toPath-functie, zodat ik gemakkelijk in het juiste formaat de coördinaten van het punt aan de path()-functie van Rafaël kan doorgeven.

Beschikbaarheid

De source code is beschikbaar onder de MIT license via onderstaand ZIP-bestand of op Bitbucket (meeste recente versie): https://bitbucket.org/iadavof/gauges. Bijdragen door middel van pull requests wordt altijd op prijs gesteld.

Bijlagen: 

Reactie toevoegen