Generați și modificați grafica SVG a datelor de la senzorii conectați la IoT cu JavaScript
În această ultimă parte a seriei de articole despre desen grafică cu date de la senzorii conectați la Internetul lucrurilor, este timpul să vorbim despre cum să generați sau să modificați cu JavaScript desene în format SVG și unele dintre elemente HTML care servesc drept container sau care prezintă informaţii complementare graficelor.
Utilizatorii țintă ai acestui tutorial ar trebui să formeze un profil de programare electronică și computer. microcontrolere, este posibil să nu fie familiarizați HTML, CSS o SVG; Din acest motiv, în versiunile anterioare s-a făcut o scurtă introducere în limbaj sau tehnologia corespunzătoare. În această ultimă parte abordarea este puțin diferită, deoarece cititorii știu cu siguranță să programeze, este posibil ca folosind limbajul C ++ ce cum JavaScript, partajează sintaxa de bază cu C și poate fi folosit ca referință pentru a sări peste majoritatea conceptelor de programare de bază și astfel să ne concentrăm asupra diferențelor și a utilizării specifice care ne interesează pentru a crea grafice cu senzori în IoT.
Numele oferă un indiciu pentru prima diferență: JavaScript Este un limbaj de programare scenariu (cratima) și, ca atare, este interpretată, nu este nevoie să-l compilați; contextul în care scenariu (un browser web, de exemplu) va citi, traduce și executa comenzile. Pentru a fi precis, în cele mai multe cazuri există o compilare runtime (JIT), ci pentru procesul de scriere a codului JavaScript Nu ne afectează, pur și simplu scriem codul și poate funcționa.
Numele conține și prima confuzie: JavaScript nu are nici cea mai mică relație cu Java. Inițial, când a fost dezvoltat Netscape pentru browserul său, a fost numit mai întâi Mocha și apoi LiveScript mai puțin confuz. După implementarea sa cu succes în browsere și transcendendu-le, a fost standardizat ca ECMAScript ( ECMA-262, versiunea 6 la momentul redactării acestui articol) să devină neutru în ceea ce privește browserele care îl implementează. În prezent există și un standard ISO din versiunea 5, 2011 (ISO / IEC 16262: 2011 la momentul scrierii articolului)
Variabile, tipuri de date de bază și obiecte în JavaScript
Spre deosebire de ceea ce se întâmplă, de exemplu, în C ++, en JavaScript tipul de date nu este inclus la declararea unei variabile și, de asemenea, tipul asociat unei variabile nu este fix, este posibil să se atribuie o valoare de alt tip pe toată durata execuției programului.
1
2
3
4
5
6
7
|
var cosa;
cosa=“texto”;
console.log(typeof cosa); // Debería mostrar string en la consola
cosa=123;
console.log(typeof cosa); // Debería mostrar number en la consola
cosa={temperatura:22,corriente:1.5};
console.log(typeof cosa); // Debería mostrar object en la consola
|
În exemplul anterior, variabila „lucru” a fost declarată (fără a indica tipul de date), apoi se atribuie date de alt tip și se consultă cu typeof
tipul care JavaScript pe care le-a interpretat. Pentru a depana codul îl puteți scrie în consola de inspecție a browserului web (ceea ce nu va afecta prezentarea web-ului) cu console.log()
.
Pentru a forța conversia datelor într-un anumit tip, în special text în numere, puteți utiliza funcții precum parseInt()
o parseFloat()
care se convertesc în numere întregi sau, respectiv, în virgulă mobilă. Conversia inversă se poate face cu String()
, deși este puțin probabil să fie necesar, deoarece conversia automată este de obicei suficientă. Cu parseFloat()
De exemplu, puteți obține valoarea unei proprietăți de pagină web, cum ar fi lățimea sau înălțimea unui obiect, care include unități; În acest fel, expresia parseFloat("50px");
va returna 50, o valoare numerică, ca rezultat.
En JavaScript nu există nicio distincție între ghilimele duble și simple; Tipul de date în ambele cazuri este string
, iar fiecare dintre ele îl poate include pe celălalt fără a fi nevoie de coduri de evadare.
1
2
3
4
5
6
7
8
9
10
|
var texto;
console.log(typeof texto); // Debería mostrar string en la undefined
texto=“esto es un texto”;
console.log(typeof texto); // Debería mostrar string en la consola
texto=‘A’;
console.log(typeof texto); // Debería mostrar string en la consola
texto=“esto es un ‘texto'”;
console.log(typeof texto); // Debería mostrar string en la consola
texto=‘”A”‘;
console.log(typeof texto); // Debería mostrar string en la consola
|
În exemplul anterior se poate observa că o variabilă, atunci când a fost declarată (există) dar nu i s-a atribuit nicio valoare, conține un tip de date nedefinit (undefined
). Un obiect nealocat are valoarea null
; Adică obiectul există, dar fără valoare; o variabilă care face referire la aceasta nu ar avea a typeof
undefined
dar object
. Un obiect poate fi, de asemenea, gol, adică nu nul, dar să nu aibă nicio proprietate.
la defini un obiect în JavaScript sunt cuprinse între bretele ({
y }
) proprietățile sau metodele, separate prin semnul două puncte (:
) numele proprietății valoarea proprietății și prin virgulă (,
) diferitele proprietăți. Puteți găsi mai multe informații despre acest mod de a exprima un obiect în articolul despre Format JSON.
Deși puteți utiliza sintaxa care vă poate determina să gândiți altfel, en JavaScript Nu există clase, ci prototipuriAdică, pentru ca un obiect să moștenească proprietăți și metode, este creat un alt obiect (prototipul) pe care ceilalți (copii) îl folosesc ca referință. Sintaxa cea mai apropiată de stilul de JavaScript a folosi un prototip este Object.create
deși este și posibil (și uneori util) de utilizat new
ca în alte limbaje orientate pe obiecte.
1
2
3
4
|
var perro=new Mamifero(); // Esto funciona, pero no es exactamente el nuevo estilo JavaScript
console.log(perro instanceof Mamifero);
var gato=Object.create(Mamifero); // Crear un objeto usando un prototipo al estilo JavaScript
console.log(Mamifero.isPrototypeOf(gato));
|
la interogați dacă un obiect este o instanță a altuia, dacă îl folosești ca prototip, dacă îi moștenești proprietățile, pe scurt, poți folosi instanceof
(creat cu new
) Sau isPrototypeOf
(creat cu Object.create
) care va evalua adevărat când obiectul folosește prototipul și fals când nu.
Odată ce un obiect a fost creat folosind altul ca prototip, adică odată ce un obiect a fost instanțiat, acesta poate fi adăugați noi proprietăți sau înlocuiți proprietățile prototipului folosind sintaxa punct ca în gato.peso=2.5
.
La matrice în JavaScript Sunt diferite de cele pe care probabil le cunoști C. Pentru început, sunt declarate fără a fi nevoie să se indice lungimea, doar cu semnele de deschidere și închidere a parantezelor pătrate ([
y ]
), componentele pot fi eterogene (diferite tipuri de date în aceeași matrice) și pot fi adăugate elemente noi fără a fi limitate la o limită. Matricele de JavaScript sunt de fapt liste (colecţii) de elemente la care referit printr-un index numeric sau printr-un nume. O matrice poate conține simultan indecși numerici și nume de elemente, dar este obișnuit să folosiți obiecte (proprietăți) pentru a exploata al doilea tip.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
// Declarar matrices (arrays)
var preparada=[]; // La matriz ha sido declarada pero (todavía) no contiene valores
var cosas=[“silla”,“mesa”,“caja”]; // Matriz declarada con componentes formada por cadenas de texto
var valores=[200,“lleno”,0.5,true,“simple”,false,false,10]; // Matriz declarada con componentes heterogéneos
var ramas=[20,“abc”,[1,2,3],false,[10,20,[“uno”,“dos”]]]; // Matriz que contiene matrices
var demode=new Array(10,20,30,4,3,2,1); // La sintaxis con new no es la preferida de JavaScript aunque funciona…
var peligrosa=new Array(10); // …pero con el riesgo de confundir índices con elementos: la matriz peligrosa tiene 10 elementos, no un elemento de valor 10
// Acceder a los valores de la matriz
preparada.push(33.33); // Añade un nuevo valor al final de la matriz
console.log(“La matriz ‘preparada’ contiene “+preparada.length+” elementos”); // Ahora contine 1 elemento
console.log(cosas[0]); // Muestra en la consola el primer valor de la matriz (las matrices empiezan en el índice cero)
cosas[2]=“tarro”;
preparada[10]=50; // Los índices no tienen que ser consecutivos
console.log(“La matriz ‘preparada’ contiene “+preparada.length+” elementos”); // Ahora contine 11 elementos
console.log(“Elemento sexto: “+preparada[5]); // undefined
// Verificar si una variable es (apunta a) una matriz
console.log(Array.isArray(cosas)); // Nuevas versiones de JavaScript (ECMAScript versión 5 o superior)
console.log(cosas instanceof Array); // Implementaciones de JavaScript (ECMAScript) más viejas
// Para esto es mejor usar objetos
var frutas=[];
frutas[“peras”]=20;
frutas[“manzanas”]=30;
frutas[4]=10;
console.log(frutas.peras);
console.log(frutas[“manzanas”]);
console.log(frutas[4]);
console.log(frutas[3]); // undefined
|
După cum se poate vedea în exemplul anterior, pentru a ști dacă o variabilă corespunde unei instanțe a unui tablou (este un obiect matrice) puteți folosi instanceof
, așa cum a fost deja folosit cu obiecte generice sau, în versiunile mai recente ale JavaScript se poate recurge la Array.isArray()
Pentru a accesa elementele matricei puteți folosi indexul acestuia (matriz[7]
) sau după numele proprietății cu numele între paranteze drepte (matriz["nombre"]
) sau cu sintaxa punctului obișnuită pentru obiecte (matriz.nombre
). Deoarece numele este un șir de text, o expresie, inclusiv variabile, poate fi folosită pentru al compune. Pentru a trece printr-o matrice cu proprietăți, se poate folosi o buclă cu formatul for(propiedad in matriz)
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
var matriz=[];
matriz[“color”]=“verde”;
matriz[“grosor”]=10;
matriz[“estado”]=“nuevo”;
matriz[0]=25.0;
matriz[1]=“uno”;
for(propiedad in matriz)
{
console.log(propiedad+” valor “+matriz[propiedad]);
}
/* El resultado en la consola será:
0 valor 25
1 valor uno
color valor verde
grosor valor 10
estado valor nuevo
*/
|
Este interesant pentru obiectivul nostru de tratat obiectul Date
, cu care să reprezinte și să gestionezi data și ora în JavaScript. Obiectul poate fi instanțiat fără date, deci va lua data și ora curente, sau poate fi creat indicând o dată ca valoare, fie în milisecunde începând cu 1 ianuarie 1970 (cum ar fi Ora Unix sau ora POSIX dar exprimat în milisecunde în loc de secunde) sau specificând valori separate de an, lună, zi, oră...
Obiectul include o serie completă de metode de a interoga sau de a seta data și ora:
-
now()
Returnează data și ora curente exprimate în milisecunde începând cu 1 ianuarie 1970 -
getTime()
|setTime()
Obține sau modifică, respectiv, valoarea timpului în milisecunde începând cu 1 ianuarie 1970. UtilizareavalueOf()
, care este o metodă prezentă în majoritatea obiectelor, se obține și valoarea obiectului Date corespunzător, cum ar figetTime()
cu Ora Unix sau ora POSIX exprimat în ms. -
getMilliseconds()
|setMilliseconds()
Folosit pentru a interoga sau a seta partea fracțională de milisecundă a obiectuluiDate
pe care se executa. Dacă este consultată, valoarea obținută este între 0 și 999 dar se pot atribui valori mai mari care se vor acumula în data și ora totală astfel încât, ca și restul metodelor get, servește la creșterea valorii obiectuluiDate
(sau micșorați-l, dacă sunt utilizate valori negative). -
getSeconds()
|setSeconds()
Returnează sau modifică, respectiv, valoarea secundelor obiectuluiDate
. -
getMinutes()
|setMinutes()
Folosit pentru a consulta sau a seta minutele obiectuluiDate
. -
getHours()
|setHours()
Vă permite să consultați sau să modificați orele (de la 0 la 23) ale obiectuluiDate
. -
getDay()
Returnează ziua săptămânii pentru dată, exprimată ca valoare de la 0 la 6 (de duminică până sâmbătă). -
getDate()
|setDate()
Returnează sau modifică ziua lunii obiectuluiDate
asupra căruia se aplică. -
getMonth()
|setMonth()
Folosit pentru a consulta sau modifica numărul lunii al obiectuluiDate
. -
getFullYear()
|setFullYear()
Interogează sau setează valoarea anului pe obiectul care conține data și ora.
Metodele anterioare de Date
include o versiune UTC pentru a putea lucra direct cu timpul universal fără a fi nevoie să faci calcule intermediare. În acest sens, de exemplu, getHours()
are o versiune getUTCHours()
o getMilliseconds()
o alternativă getUTCMilliseconds()
să lucreze alternativ cu ora oficială (legală) sau universală. Cu getTimezoneOffset()
Puteți cunoaște diferența care există între ora universală și ora oficială locală.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
var dia_semana=[“domingo”,“lunes”,“martes”,“miércoles”,“jueves”,“viernes”,“sábado”];
var nombre_mes=[“enero”,“febrero”,“marzo”,“abril”,“mayo”,“junio”,“julio”,“agosto”,“septiembre”,“octubre”,“noviembre”,“diciembre”];
var digitos_hora;
var hoy=new Date();
var texto_hoy=“”;
texto_hoy+=“Hoy es “;
texto_hoy+=dia_semana[hoy.getDay()];
texto_hoy+=“, “;
texto_hoy+=hoy.getDate();
texto_hoy+=” de “;
texto_hoy+=nombre_mes[hoy.getMonth()];
texto_hoy+=” de “;
texto_hoy+=hoy.getFullYear();
texto_hoy+=” y son las “;
digitos_hora=hoy.getHours();
texto_hoy+=digitos_hora>9?digitos_hora:“0”+digitos_hora;
texto_hoy+=“:”;
digitos_hora=hoy.getMinutes();
texto_hoy+=digitos_hora>9?digitos_hora:“0”+digitos_hora;
texto_hoy+=“:”;
digitos_hora=hoy.getSeconds();
texto_hoy+=digitos_hora>9?digitos_hora:“0”+digitos_hora;
|
Funcții JavaScript
Dacă citești asta, cu siguranță știi cum să programezi. microcontrolere en C o o C ++ și cunoașteți conceptul de funcție. Deși ideea de bază este aceeași, în JavaScript Modul în care sunt definite și utilizate este puțin diferit. Pentru început, s-a spus deja: JavaScript Nu utilizează în mod explicit tipuri de date, așa că nu trebuie să le indicați atunci când definiți funcția. A urma, Nu este obligatoriu ca o funcție să aibă un nume, acestea pot fi anonime. Ele pot fi asociate cu o variabilă pentru a le invoca, dar poate să nu fie necesar deoarece, uneori, este util să le invoci imediat, pentru care parantezele și parametrii se adaugă după definirea funcției.
Pentru a defini o funcție, prefix function
, dacă este cazul, scrieți numele, argumentele (parametrii trecuți funcției) între paranteze și codul care va fi executat când funcția este invocată între paranteze.
1
2
3
4
5
|
function doble(numero)
{
var resultado=numero*2;
return resultado;
}
|
Cu siguranță, în exemplul anterior nu era deloc necesară variabila „rezultat”, dar este o scuză bună să ne amintim domeniul de aplicare variabil, care funcționează așa cum vă așteptați: variabila „rezultat” există doar în cadrul funcției „duble”. În JavaScript poate fi de asemenea folosit let
, în loc de var
, pentru a extinde o variabilă într-un context de bloc de cod (închis între acolade, {
y }
)
Când vorbim despre obiecte în secțiunea anterioară, ceva fundamental lipsea: proprietățile au fost definite, dar metodele nu au fost definite. Cum era de așteptat, metodele obiect sunt funcții, nu au nume și sunt folosite (invocate) din numele (proprietății) atribuit de definiția obiectului.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var
termostato=
{
temperatura_actual:0.0,
temperatura_frio:18.5,
temperatura_calor:22.0,
consumo:0,
ver_temperatura:
function()
{
console.log(“Temperatura actual: “+this.temperatura_actual+” °C”);
}
}
|
În exemplul anterior, există deja o metodă, „view_temperature”, care afișează valoarea proprietății „current_temperature” prin consolă. Nu este foarte util, dar oferă o idee mai completă despre cum este definiția unui obiect JavaScript.
Pentru a accesa metodele unui obiect (funcții) la proprietățile sale, utilizați this
, ca în exemplul anterior de pe linia 11, când se folosește proprietatea „current_temperature”.
Accesați Document Object Model (DOM) cu JavaScript
din JavaScript Aveți acces la conținutul paginii web pe care rulează, precum și la unele aspecte ale browserului care afișează pagina respectivă, deși nu și la resursele sistemului. Structura de date care acceptă proprietățile și metodele accesate de la JavaScript parte a obiectului fereastră, în special, conținutul obiectului (documentul HTML) corespunde obiectului document
. Deși uneori este folosit pentru claritate, nu este necesar să precedați metodele sau proprietățile cu fereastră pentru a face referire la ele, este suficient, de exemplu, să folosiți document
, nu este nevoie să scrieți numele obiectului rădăcină ca în window.document
, atâta timp cât se face referire la fereastra curentă.
Cea mai folosită formă de găsiți un obiect în document HTML Este prin metoda getElementById()
, căruia i se trece ca argument id-ul care a fost indicat la crearea codului HTML. Din ceea ce a fost explicat în secțiunile anterioare, este ușor de presupus că puteți accesa și componentele din interiorul obiectului document
folosind sintaxa punct (document.componente
) sau paranteze folosind ambele nume (document["componente"]
), cele mai utile, precum indexul numeric, greu de utilizat și nepractic la accesarea conținutului unei pagini web compuse manual.
cu JavaScript poți obține elementul care conține un alt element (element sau nod părinte) consultarea proprietății dvs parentNode
sau proprietatea dumneavoastră parentElement
, diferența este că elementul părinte (parentElement
) a elementului final al șirului HOTĂRÂREA Este nulă (null
) și nodul părinte (parentNode
) este documentul în sine (document
).
la modifică conținutul unui element HTML, de exemplu cel al unei etichete <div>
, Poate fi folosit innerHTML
iar pentru a-i schimba proprietățile puteți alege să îi atribuiți o altă clasă cu className
sau modifica proprietățile sale individual cu style
. A consulta stilul afișat de un element pe pagina web nu este neapărat util style
deoarece poate depinde de mai mulți factori sau pur și simplu nu a fost specificat în mod explicit. Pentru a verifica stilul unui element afișat în final pe pagina web, se folosește metoda getComputedStyle.
La un element de document HTML I se pot atribui mai multe clase pentru a-i determina aspectul și comportamentul gestionează lista de clase ale unui obiect din JavaScript se poate recurge la classList
care oferă metodele add
pentru a adăuga o nouă clasă la listă, remove
pentru a-l elimina, toggle
pentru a-l înlocui sau a consulta conținutul listei de clase a unui element cu item
şi contains
, care returnează clasa care ocupă o anumită poziție în listă și o valoare true
o false
indiferent dacă o anumită clasă este sau nu pe listă.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
var contenedor_temperatura=document.getElementById(“temperatura”); // Encontrar el div con id=”temperatura”
contenedor_temperatura.innerHTML=“”; // Eliminar el contenido provisionalmente para que no se vean los cambios
contenedor.className=“bloque_temperatura”; // Asignar una nueva clase
if(temperatura>20) // Si la temperatura es mayor que 20 °C…
{
contenedor.style.color=“#FF6666”; // …usar el color rojo en lugar del color normal de la clase
}
contenedor_temperatura.innerHTML=“La temperatura es “+temperatura+” °C”; // Cuando el aspecto esté preparado mostrar el valor
if(document[“titulo”].classList.contains(“estilo_titulo”)) // Si el objeto “titulo” tiene la clase “estilo_titulo”…
{
if(document[“titulo”].classList.contains(“general”)) // …y la clase “general”…
{
document[“titulo”].classList.remove(“general”); // …quitar la clase “general”
}
}
else // Si el objeto “titulo” no tiene la clase “estilo_titulo”…
{
document[“titulo”].classList.add(“estilo_titulo”); // …añadir la clase “estilo_titulo” (da igual que tenga o no la clase “general”)
}
|
În exemplul precedent este situat cu getElementById
obiectul pe care doriți să îl manipulați (un element <div>
pentru a lui id
), înainte de a schimba aspectul, conținutul este șters prin atribuirea cu innerHTML
un șir de text gol, i se atribuie o nouă clasă cu className
iar stilul ei este modificat cu style
in functie de valoarea continutului (temperatura), schimbarea culorii, daca este cazul, prin proprietate color
. Odată stabilit aspectul, valoarea este scrisă folosind din nou innerHTML
.
În a doua parte a exemplului de mai sus (liniile 9 la 19) este accesat un element de cod HTML folosind sintaxa document[]
si proprietatea id
elementului pentru a-și modifica lista de clase cu metoda classList.remove()
si cu metodaclassList.add()
, pe baza rezultatului mai multor interogări care sunt efectuate în execuții condiționate, pe care le compară folosind classList.contains()
.
Când va merge se referă la un element HTML mai mulți ori în tot codul JavaScript, este puțin mai eficient să-l atribui unei variabile sau folosește-i indexul în locul numelui deoarece, în caz contrar, metoda pe care ai folosi-o JavaScript pentru a-l obține de fiecare dată ar necesita căutarea numelui său, consumând puțin mai mult timp decât dacă ar fi accesată o variabilă.
la adăugați noi obiecte în document HTML, pot fi create mai întâi cu metoda createElement
de document
iar mai târziu le încorporează la restul elementelor în punctul arborelui care este necesar cu appendChild
. Pentru a crea un obiect XML, ca obiectele SVG pe care îl folosim pentru a desena graficul senzorilor IoT, îl puteți folosi createElementNS
(NS pentru spațiu de nume). După cum am explicat când vorbim despre format SVG, spațiul de nume care îi corespunde (pentru versiunea curentă) este http://www.w3.org/2000/svg
, care ar trebui transmisă la createElementNS
ca argument împreună cu tipul de element, svg
, în acest caz.
o alternativă la innerHTML
pentru a adăuga text ca conținut unui element de document HTML este metoda createTextNode()
a obiectului document
. Cu această alternativă poți creați text nou (care este accesat ulterior dacă este atribuit unei variabile) care este încorporat în arborele de obiecte cu metoda appendChild()
. ca alternativă la appendChild()
, care adaugă noul conținut la sfârșitul a ceea ce există deja în nodul la care este adăugat, puteți utiliza metoda insertBefore()
, care adaugă un obiect nou în fața unuia existent. Purta insertBefore()
în loc de appendChild()
oferă o metodă care servește, de exemplu, la sortați obiectele noi în fața celor existente când un element trebuie să fie în fața altuia (ca într-o listă) sau să fie acoperit sau să fie acoperit într-o structură grafică în care există elemente mai apropiate de prim-plan sau de fundal.
Reacționați la evenimente cu JavaScript
Când calea de utilizați o pagină web ca container pentru graficele senzorilor conectați IoT a fost folosit onload
În etichetă <body>
pentru a începe desenarea graficului. Această proprietate, asociată cu obiectele cod HTML, se referă la Evenimente JavaScript. După cum sa explicat deja, execută o funcție când pagina s-a încărcat. Deși a fost asociat cu codul HTML pentru a-l ține mai mult în minte, ar fi putut fi scris în cod JavaScript ca body.onload=dibujar;
siendo dibujar
numele funcției care ar trebui pornită la încărcarea paginii web.
În cele mai recente versiuni ale JavaScript evenimentele pot fi asociate cu funcții folosind addEventListener
cu formatul objeto.addEventListener(evento,función);
sau folosind sintaxa objeto.evento=función;
care funcționează și în implementări mai vechi. Pentru a deconecta funcția asociată evenimentului, aveți removeEventListener
care are același format ca addEventListener
.
JavaScript Este capabil să reacționeze la o multitudine de evenimente care pot avea loc pe o pagină web. De exemplu, poate detecta când se face clic pe un element HTML cu onmousedown
, sau când se dă clic cu onclick
, când o tastă este apăsată cu onkeydown
, prin acţionarea barei de defilare cu onscroll
. Pentru scopul nostru este suficient pentru noi detectează încărcarea paginii cu onload
și redimensionarea acestuia cu onresize
. Vom asocia aceste evenimente cu obiectele body
y window
del HOTĂRÂREA respectiv. Primul poate fi atribuit în cod HTML, așa cum se vede și al doilea în cod JavaScript în interiorul funcției numite de primul și cu formatul window.onresize=redimensionar;
siendo redimensionar
funcția care va fi apelată de fiecare dată când fereastra își schimbă dimensiunea.
Rulați după un interval de timp
JavaScript are două resurse pt execuție amânată: setTimeout
, care execută o funcție după un interval de timp și setInterval
care va executa o funcție la fiecare anumit interval de timp. Ambele metode necesită ca parametri (1) funcția invocată și (2) intervalul de timp exprimat în milisecunde. Pentru a opri funcționarea acestora, puteți atribui variabilelor rezultatul returnat de aceste funcții și le puteți transmite ca argument clearTimeout
sau clearInterval
când nu doriți să le invocați din nou (sau când nu doriți să fie executate pentru prima dată) setTimeout
o setInterval
respectiv.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
var cuenta_atras=setTimeout(descansar,1000*60*20); // Recordar que hay que descansar cuando pasen 20 minutos
var repeticion=setInterval(consultar_correo,1000*60*5); // Revisar el correo cada 5 minutos
function descansar()
{
alert(“Puedes descansar un rato”); // Esto solamente aparecerá una vez
}
function consultar_correo()
{
alert(“Revisa el correo electrónico”); // Esto aparecerá cada cinco minutos
}
function detener_cuenta_atras() // No utiliza argumentos sino la variable global
{
clearTimeout(cuenta_atras); // Detener la cuenta atrás para avisar a los 20 minutos
}
function no_avisar_lectura_correo() // No utiliza argumentos sino la variable global
{
clearInterval(repeticion); // Dejar de avisar cada 5 minutos
}
|
În exemplul precedent este introdusă metoda alert
care servește la afișarea unui semn de avertizare. Deși a fost folosit pe scară largă în trecut, în prezent este aproape interzis din cod JavaScript din cauza cât de agresiv (intruziv) este acoperirea paginii web cu o casetă de dialog.
Într-un program scris pentru a microcontroler dintr-o serie mică (cum ar fi cea de pe farfurie Arduino Uno) este obișnuit să se utilizeze variabile globale, ca în exemplul anterior în JavaScript, deoarece codul este scurt și nu este deosebit de confuz, deoarece de multe ori funcțiile sunt implementate ad-hoc și pentru că utilizarea variabilelor globale face posibilă prezicerea utilizării memoriei într-un mod foarte simplu și intuitiv, ceea ce este critic în sistemele cu puține resurse . In schimb, en JavaScript Este obișnuit să se reducă utilizarea variabilelor globale la minimum posibil. deoarece nu trebuie să grăbească utilizarea memoriei, deoarece rulează normal pe a Procesor cu resurse mult superioare celor ale a MCU, deoarece este probabil să coexiste cu o mulțime de coduri de la terți cu care trebuie să lucreze fără a interfera și din moment ce este un sistem deschis, contextul de execuție viitor nu poate fi prezis (programul unui microcontroler small determină complet funcționarea acestuia fără a adăuga mai mult cod odată ce este în funcțiune) și pentru că dimensiunile aplicațiilor ar putea îngreuna citirea dacă codul nu își încapsulează funcționarea, făcând metodele cât mai autonome.
Operații matematice cu obiectul JavaScript Math
Operațiile matematice ale calculului matematic mai complicat sunt grupate în obiect Math
. Acest obiect este folosit direct, nu este necesar să-l instanțiezi pentru a folosi metodele sau proprietățile (constantele) pe care le încorporează.
Math.abs(n)
Valoarea absolută a parametrului nMath.acos(n)
Arccosin al parametrului n (rezultat în radiani)Math.asin(n)
Arcsin al parametrului n (rezultat în radiani)Math.atan(n)
Arctangenta parametrului n (rezultat în radiani)Math.atan2(n,m)
Arctangent de n/m (rezultat în radiani)Math.ceil(n)
Rotunjiți parametrul la cel mai apropiat număr întregMath.cos(α)
Cosinusul parametrului α (α în radiani)Math.E
numărul e (≃2.718281828459045)Math.exp(n)
e ridicat la parametrul n: enMath.floor(n)
Rotunjiți parametrul n la cel mai apropiat număr întreg în josMath.log(n)
Logaritmul natural (baza e) al parametrului nMath.LN2
Logaritmul natural (baza e) de 2 (≃0.6931471805599453)Math.LN10
Logaritmul natural (baza e) de 10 (≃2.302585092994046)Math.LOG2E
Logaritmul de bază 2 al lui e (≃1.4426950408889634)Math.LOG10E
Logaritmul de bază 10 al lui e (≃0.4342944819032518)Math.max(a,b,c,…)
Cea mai mare valoare a listei de parametri trecuțiMath.min(a,b,c,…)
Cea mai mică valoare a listei de parametri trecuțiMath.PI
Numărul π (≃3.141592653589793)Math.pow(n,m)
Primul parametru n ridicat la al doilea parametru m: nmMath.random()
Număr (aproape) aleatoriu între 0.0 și 1.0Math.round(n)
Rotunjiți parametrul n la cel mai apropiat număr întregMath.sin(α)
Sinusul parametrului α (α în radiani)Math.sqrt(n)
Rădăcina pătrată a parametrului nMath.SQRT1_2
Rădăcină pătrată de 1/2 (≃0.7071067811865476)Math.SQRT2
Rădăcină pătrată a lui 2 (≃1.4142135623730951)Math.tan(α)
Tangenta parametrului α (α în radiani)
Încărcați datele de pe server cu AJAX
Metoda urmată pentru a desena informațiile stocate în IoT constă în încărcarea din când în când a datelor de pe server și redesenarea graficului cu care sunt reprezentate. Pentru a citi datele de pe server, se folosește tehnologia AJAX (JavaScript și XML asincron) printr-un obiect XMLHttpRequest
de JavaScript. Trasarea graficului de date se face prin reutilizarea unui obiect SVG care este deja în cod HTML și care conține o diagramă ale cărei coordonate sunt modificate pentru a le face să corespundă noilor date încărcate.
În exemplul acestei propuneri, pe lângă actualizarea desenului, se actualizează și un text de pe pagina web care arată data și valoarea ultimelor date măsurate pentru fiecare grafic.
Pe partea de server există o bază de date care conține informațiile că senzorii conectați la IoT au fost monitorizați. Această bază de date este citită de cererea de obiect XMLHttpRequest
răspunzând cu informații codificate în Format JSON, deși denumirea metodei utilizate sugerează o relație cu formatul XML.
În primul tutorial polaridad.es despre Stocarea datelor IoT Puteți vedea un exemplu de infrastructură pentru a gestiona, din partea serverului, informațiile furnizate de dispozitivele conectate la Internet of Things. În această serie de articole un server este folosit ca resursă Apache din care poți folosi limbajul de programare PHP pentru a accesa o bază de date MySQL o MariaDB. Pe serverele utilizate pentru a susține IoT este foarte comun să găsiți baze de date MongoDB (NoSQL) și limbajul de programare JavaScript pe Node.js ca infrastructură software.
Următoarea funcție este responsabilă pentru solicitarea celor mai recente date de la unul dintre senzorii de pe server. În apelul de funcție, obiectul este folosit ca argument JavaScript care susține datele care sunt desenate. Dacă același grafic reprezintă mai multe valori, de exemplu pentru căutarea vizuală a unei corelații, se poate face o solicitare către server pentru a returna mai multe simultan, o metodă mai optimă datorită modului în care funcționează serverul. Protocolul HTTP.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
function consultar_ultimo_valor_sensores(objeto_grafico)
{
var consulta=‘zona=”+objeto_grafico.sufijo_nombre;
var pagina=“ultimo_valor_sensor.php”;
var resultado;
var ajax;
if(window.XMLHttpRequest)
{
ajax=new XMLHttpRequest(); // ajax=Object.create(XMLHttpRequest);
}
else // Versiones antiguas de MS Internet Explorer
{
ajax=new ActiveXObject(“Microsoft.XMLHTTP”);
}
ajax.onreadystatechange=
function()
{
if(ajax.readyState==4&&ajax.status==200&&ajax.responseType==“json”)
{
resultado=JSON.parse(ajax.responseText);
if(resultado.fecha>objeto_grafico.fecha[objeto_grafico.fecha.length–1])
{
// Normalmente se gestionará la respuesta utilizando el objeto
redibujar_grafico(objeto_grafico,resultado);
// Si los datos son sencillos y tienen una estructura clara puede ser más práctico usarla directamente
// redibujar_grafico(objeto_grafico,[resultado.fecha,resultado.temperatura]);
}
}
}
ajax.open(“POST”,pagina);
ajax.setRequestHeader(“Method”,“POST “+pagina+” HTTP/1.1″);
ajax.setRequestHeader(“Content-type”,“application/x-www-form-urlencoded”);
ajax.setRequestHeader(“Content-length”,consulta.length);
ajax.setRequestHeader(“Connection”,“close”);
ajax.send(consulta);
}
|
În a treia linie a exemplului anterior se pregătește interogarea care va fi făcută către server, în care se va trece argumentul „zonă”, a cărui valoare va fi numele sau codul locului monitorizat deoarece informații despre zona poate coexista in aceeasi baza de date.senzori diferiti (de exemplu, termometre care masoara temperatura in incaperi diferite). Parametrul trecut la funcția anterioară, obiectul cu datele diagramei, este de așteptat să includă o proprietate cu numele camerei ("name_sufx").
Între rândurile 7 și 14 din codul anterior, obiect XMLHttpRequest
care este stocat în variabila „ajax”. Înainte de a alege cum să creați obiectul, căutați window
dacă XMLHttpRequest
nu a fost disponibil (ceva care s-a întâmplat în versiunile vechi ale exploratorului Microsoft și, deși este cu mult în urmă, servește ca exemplu de alternative pentru a crea obiectul folosind sintaxa (mai nativă)) Object.create
o new
, similar cu cel al altor limbaje orientate pe obiecte.
Pentru a putea gestiona imediat răspunsul, codul care îl gestionează este pregătit în rândurile de la 15 la 26 înainte de a face cererea către server.
Modul de efectuați interogarea HTTP la server constă din deschide o conexiune cu open
indicând tipul și pagina (opțional nume de utilizator și parolă), pregătiți anteturile a protocolului cu setRequestHeader
y trimite cererea cu send
. Antetul HTTP Content-length
va trebui să știți lungimea interogării (numărul de caractere) care se calculează folosind length
.
Când cererea AJAX este gata, funcția asociată evenimentului este executată onreadystatechange
. În loc să se atribuie o funcție, în exemplul anterior este definită din mers o funcție anonimă care va gestiona recepția datelor care sosesc de la server. În primul rând, la rândul 18 se verifică că starea cererii este „terminată”, ceea ce corespunde valorii 4
a proprietății readyState
, că starea este „OK” a Protocolul HTTP (cod 200
) care pot fi obținute de la proprietate status
și că datele care au sosit sunt Format JSON, consultand proprietatea responseType
.
Odată verificat că starea răspunsului este cea așteptată, în rândul 20 din exemplul anterior creează un obiect cu rezultatul, transformând textul JSON. Răspunsul prevede returnarea unei date, aceasta ne permite să vedem dacă rezultatul pe care îl trimite serverul a fost deja reprezentat anterior în grafic, care se verifică pe linia 21. Dacă datele sunt noi, pe linia 23 Funcția care este responsabil pentru redesenarea graficului cu noile informații se numește.
Ideea atunci când se propune această metodă de citire este ca datele să fie reîmprospătate foarte frecvent. Dacă informațiile prezentate corespund unui termen lung (cum ar fi temperaturile unei zile sau unei săptămâni), se poate implementa o solicitare inițială care colectează toate datele disponibile și apoi una, similară cu cea din exemplu, care o actualizează în corespondentul perioadei.
Generați date aleatorii pentru testare
Când toată infrastructura de server și client este gata, o funcție ca cea din secțiunea anterioară se va ocupa de citirea datelor și de desenarea graficului cu ea, dar În faza de testare, poate fi mai practic să folosiți numere aleatorii într-un interval controlat pentru a vedea dacă codul scris este corect. Următoarea funcție poate servi ca exemplu pentru a obține date în timpul construirii aplicației finale.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
function consultar_ultimo_valor_sensores
(
objeto_grafico,// Objeto dentro del elemento SVG que representa el trazado
valor_maximo, // Valor máximo
valor_minimo, // Valor mínimo
margen_valor // Cantidad extra que se representa sobre/bajo el valor máximo/mínimo
)
{
// La forma más genérica es usar objetos
/*
var nuevo_valor_inventado=
{
fecha:Math.round(Date.now()+Math.random()*1000),
temperatura:Math.random()*Math.abs(valor_maximo-valor_minimo)+valor_minimo
};
*/
// En este caso concreto, como son pocos datos y con una estructura concreta es más práctico usar un vector
var nuevo_valor_inventado=
[
Math.round(Date.now()+Math.random()*1000),
Math.random()*Math.abs(valor_maximo–valor_minimo)+valor_minimo
];
redibujar_grafico(objeto_grafico,nuevo_valor_inventado);
}
|
În loc să citească informațiile dintr-o bază de date, exemplul de mai sus le generează aleatoriu și le transmite funcției însărcinate cu desenarea graficului. Datele inventate sunt un vector format dintr-o dată exprimată ca valoare în milisecunde, momentul înregistrării informațiilor senzorului și datele monitorizate, care se află între o valoare maximă și o valoare minimă.
În acest exemplu, atunci când se generează o dată, aceasta poate fi întârziată cu până la o secundă (1000 milisecunde) în raport cu data la momentul invenției. La fel de Math.random()
generează un număr între 0.0 și 1.0, înmulțirea lui cu 1000 produce un număr între 0 și 1000 care este apoi convertit într-un număr întreg. În același mod, valoarea se obține prin înmulțirea numărului aleatoriu cu intervalul (maxim minus minim) și adăugarea minimului.
Desenați graficul senzorilor IoT cu un grafic SVG
Deoarece am văzut cum putem obține valorile pe care dorim să le reprezentăm (temperatura, în exemplu) și locația lor temporală, care pot fi exprimate împreună sub formă de coordonate, exemplul de mai jos arată o funcție pentru a trasa o cale care unește acele puncte și opțional o zonă colorată delimitată de acea linie în partea de sus. Rezultatul ar fi ca în imaginea următoare.
Axa orizontală (X) a graficului reprezintă timpul, iar axa verticală (Y) valorile pe care senzorii conectați la IoT le-au monitorizat. Intervalul orizontal este de câteva secunde deoarece în această propunere graficul este actualizat foarte frecvent (în fiecare secundă, de exemplu) pentru a oferi informații aproape în timp real despre starea senzorilor.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
function actualizar_grafico
(
grafico, // Objeto SVG con el que se dibuja la gráfico
coordenada, // Matriz con las coordenadas del trazado formada por pares [tiempo,valor] ordenados primero el más antiguo (tiempo menor) último el más nuevo
tiempo_total_representado, // Tiempo total representado por la gráfico (en milisegundos)
valor_maximo, // Valor máximo aceptable antes de emitir una alarma
valor_minimo, // Valor mínimo aceptable antes de emitir una alarma
margen_valor, // Cantidad extra que se representa sobre/bajo el valor máximo/mínimo
parametro_cerrado, // Valor booleano que indica si el trazado se cierra o no (por defecto false)
parametro_ancho_caja, // Ancho de la caja que contiene la gráfico (por defecto 100.0)
parametro_alto_caja // Alto de la caja que contiene la gráfico (por defecto 100.0)
)
{
var cerrado=parametro_cerrado||false; // Valor booleano que indica si el trazado se cierra o no (por defecto false)
var ancho_caja=parametro_ancho_caja||100.0; // Ancho de la caja que contiene la gráfico (por defecto 100.0)
var alto_caja=parametro_alto_caja||100.0; // Alto de la caja que contiene la gráfico (por defecto 100.0)
var coordenadas_trazado=“M “; // Cadena de texto que representa la propiedad “d” del trazado SVG
var desplazamiento=[]; // Desplazamientos X e Y para posicionar el tiempo y el valor representado dentro del rango del gráfico
var escala=[]; // Coeficientes X e Y para calcular el tamaño al representar el gráfico
var sin_recortar=true; // False si la gráfico se sale de la caja (si se sale, si recorta, se hace false para no seguir dibujando puntos)
var contador_valor=coordenada.length–1; // Variable para recorrer los valores (índice)
var posicion=[]; // Variable intermedia (para hacer más legible el código) con la que calcular la posición horizontal y vertical de un punto del trazado
escala[0]=ancho_caja/tiempo_total_representado; // Coeficiente que multiplica a los valores horizontales (tiempo) para calcular las coordenadas X
escala[1]=alto_caja/(Math.abs(valor_maximo–valor_minimo)+margen_valor*2); // Coeficiente que multiplica a los valores (vertical) para calcular las coordenadas Y
desplazamiento[0]=coordenada[coordenada.length–1][0]–tiempo_total_representado; // Valor desde el que se empieza a contar el tiempo: el valor mayor (último) menos el rango de tiempo representado
desplazamiento[1]=margen_valor–valor_minimo; // Valor menor mostrado (al menor se le añade un margen para visualizar el principio de los valores fuera del rango permitido)
if(cerrado) // Si se dibuja un path (trazado) cerrado…
{
coordenadas_trazado+=ancho_caja+“,”+alto_caja+” L “; // …se empieza por la parte inferior de la caja
}
while(contador_valor>=0&&sin_recortar) // Mientras queden valores por representar y no se haya llegado al borde izquierdo del gráfico…
{
posicion[0]=(coordenada[contador_valor][0]–desplazamiento[0])*escala[0]; // Calcular la X restando al tiempo el desplazamiento y convirtiéndolo a la escala del gráfico con el coeficiente horizontal
posicion[1]=alto_caja–(coordenada[contador_valor][1]+desplazamiento[1])*escala[1]; // Calcular la Y restando del alto de la caja (la Y crece hacia abajo en SVG)
coordenadas_trazado+=posicion[0]+“,”+posicion[1]; // Formar la coordenada con la X y la Y
if(posicion[0]>0) // Si no se ha rebasado el margen izquierdo…
{
coordenadas_trazado+=contador_valor>0?” L “:“”; // …y quedan valores que represntar, añadir una nueva línea (código L) para el próximo
contador_valor—; // Pasar al siguiente valor
}
else // Si se ha rebasado el margen izquierdo…
{
sin_recortar=false; // …abandonar el modo sin recorte (lo que terminará de calcular coordenadas)
}
}
if(cerrado) // Si se dibuja un trazado (path) cerrado…
{
coordenadas_trazado+=” L “+posicion[0]+“,”+alto_caja+” Z”; // …se termina por la parte inferior de la caja y se añade Z para cerrarlo en SVG
}
grafico.setAttribute(“d”,coordenadas_trazado); // Cambiar las coordenadas del trazado (propiedad “d”) por las que se han calculado
}
|
În codul anterior sunt două aspecte interesante, în primul rând calculul care permite adaptați gama de valori care sunt reprezentate iar în al doilea rând cel construcția proprietății d
care indică coordonatele punctelor de pe plan (path
).
Pentru a adapta intervalul de valori reprezentate, acestea sunt mutate de la un minim și scalate astfel încât mărimea vizibilă să corespundă mărimii graficului. În cazul timpului, decalajul se obține scăzând intervalul pe care doriți să-l afișați din cea mai lungă oră (data și ora cea mai apropiată de cea curentă) (20 de secunde în exemplu). Deplasarea valorilor de temperatură este cea a intervalului inferior (un grad) minus cea mai mică valoare, astfel încât datele prezentate mai jos sunt cel mai asemănătoare cu cea mai mică valoare permisă, dar lăsând o marjă care ne permite să apreciem ceea ce trece
Coeficientul care înmulțește valorile timpului pentru a obține coordonatele orizontale ale graficului se obține prin împărțirea lățimii totale a graficului (100 de unități în exemplu) la intervalul de timp reprezentat (20 de secunde în exemplu). Pentru a obține coeficientul cu valorile scalare de temperatură, trebuie reținut că intervalul reprezentat merge de la o marjă sub valoarea minimă la o marjă peste maximă, un grad în ambele cazuri. În acest fel, coeficientul de scară verticală rezultă din împărțirea înălțimii graficului (100 de unități în exemplu), la valoarea maximă, minus minimul plus marginea superioară și inferioară. Deoarece aceste valori s-ar putea dezvolta complet la temperaturi negative, folosim Math.abs()
pentru a folosi valoarea absolută a diferenței.
Proprietatea d
a obiectului path
Se construiește prin concatenarea coordonatelor punctelor dintr-un text. Fiecare pereche de coordonate este precedată de un cod SVG L
, care trasează o linie de la poziția curentă la o valoare absolută care este indicată de coordonate. Valorile X și Y sunt separate prin virgule și fiecare operație SVG este separată de celălalt printr-un spațiu.
Pentru a începe aspectul, utilizați codul M
(treceți la o coordonată absolută). În cazul diagramei închise și umplute se începe din dreapta jos, în cazul diagramei deschise care desenează profilul de date se începe cu ultima valoare reprezentată (cea mai recentă). Pentru a finaliza aspectul închis, se folosește codul Z
adăugând ca ultimul punct cel care are aceeași valoare a coordonatei X ca ultimul punct al dreptei și ca coordonată Y cea mai mică valoare reprezentată.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
function dibujar_grafico()
{
var tiempo_mostrado=20000; // Se representan 20 segundos (20000 milisegundos)
var valor_maximo=10; // 10 grados sobre cero
var valor_minimo=–5; // Cinco grados bajo cero
var fecha_hora=Date.now(); // Hora actual
var matriz_de_coordenadas_de_prueba=[]; // Preparar el vector de coordenadas
for(var contador=0;contador<20;contador++)
{
fecha_hora+=500+1500*Math.random(); // Añadir medio segundo a la hora anterior y entre 0 y segundo y medio aleatoriamente
matriz_de_coordenadas_de_prueba[contador]=[]; // Preparar el siguiente punto del vector de coordenadas
matriz_de_coordenadas_de_prueba[contador][0]=fecha_hora; // En la coordenada horizontal, situar la hora
matriz_de_coordenadas_de_prueba[contador][1]=Math.random()*Math.abs(valor_maximo–valor_minimo)+valor_minimo; // En la coordenada vertical situar el valor del sensor
}
actualizar_grafico
(
document.getElementById(“relleno_temperatura”), // Trazado para el relleno definido en el código HTML
matriz_de_coordenadas_de_prueba,
tiempo_mostrado,
valor_maximo, // Diez grados de valor máximo
valor_minimo, // Cinco grados bajo cero como valor mínimo
1, // Un grado por encima y por debajo de las temperatura mínimas y máximas respectivamente
true // Cerrar el trazado para representar el área rellena
);
actualizar_grafico
(
document.getElementById(“linea_temperatura”), // Trazado para el relleno definido en el código HTML
matriz_de_coordenadas_de_prueba,
tiempo_mostrado,
valor_maximo, // Diez grados de valor máximo
valor_minimo, // Cinco grados bajo cero como valor mínimo
1, // Un grado por encima y por debajo de las temperatura mínimas y máximas respectivamente
false // No cerrar el trazado para representar la linea que une los puntos que representan las temperaturas
);
}
|
În acest exemplu, funcția dibujar_grafico()
, care este apelul la încărcarea paginii, primește valorile inițiale de testat (nu ultima valoare în timp real) și pregătește intervalul în care vor fi redate datele: 20 de secunde (20000 ms) pe orizontală și 15°C în verticală de la -5°C la +10°C cu marginea superioară și inferioară de un grad. Efectuați două apeluri către actualizar_grafico()
, în prima trecere true
ca argument, care indică faptul că graficul ar trebui să fie închis pentru a reprezenta o zonă umplută, iar la al doilea apel trece false
a trasa linia. În fiecare caz, obiectul path
modificat este cel care are aspectul corespunzător, cu umplutură și fără chenar în primul caz și cu o anumită grosime de linie și fără umplutură în al doilea.
Funcția actualizar_grafico()
lucru la un obiect SVG care folosește următorul cod ca container HTML. Obiectul SVG conține două căi, una pentru a desena linia și alta pentru a desena zona umplută. La încărcarea paginii web, din element <body>
funcția anterioară este apelată automat, dibujar_grafico()
datorită evenimentului JavaScript onload
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<!DOCTYPE html>
<html lang=“es”>
<head>
<meta charset=“utf-8”>
<title>Temperatura</title>
<script type=“text/javascript” src=“https://polaridad.es/javascript-grafico-svg-sensor-internet-de-las-cosas-iot/grafico.js”></script>
</head>
<body onload=“dibujar_grafico();” style=“margin:0;”> <!– Cuerpo del documento HTML. Al cargar el contenido llama a la función JavaScript dibujar_grafico() –>
<div id=“temperatura”>
<div id=“bloque_temperatura” style=“width:820px;height:150px”>
<svg
id=“grafico_temperatura”
width=“100%”
height=“100%”
viewBox=“0 0 100 100”
preserveAspectRatio=“none”>
<path
id=“relleno_temperatura”
d=“”
style=“fill:#A8C3EA;stroke:none;”
vector-effect=“non-scaling-stroke”
/>
<path
id=“linea_temperatura”
d=“”
style=“fill:none;stroke:#205587;stroke-width:4;stroke-opacity:1;”
vector-effect=“non-scaling-stroke”
/>
</svg>
</div>
</div>
</body>
</html>
|
Pe linia 10 din cod HTML mai sus se stabilește în stil o lățime (de exemplu) de 820 px și o înălțime de 150 px (ceva ce, în versiunea finală, va fi indicat să faceți cu o clasă și un document CSS). Pare ciudat că liniile 13 și 14 definesc dimensiunea obiectului SVG cum ar fi 100% lățime și înălțime (care se potrivește cel mai bine cu dimensiunile ferestrei, 100×100). După cum am menționat deja, motivul pentru care faceți acest lucru este să lucrați întotdeauna cu dimensiuni cunoscute și să ajustați valorile reprezentate la acesta. Celelalte alternative ar fi să calculați spațiul graficului de fiecare dată și apoi să reajustați valorile sau să forțați dimensiunile fixe pentru grafic, la care documentul va trebui să le respecte.
După ce am optat pentru un grafic ale cărui dimensiuni se modifică conform codului HTML, este necesar să se includă proprietatea vector-effect
cu valoarea non-scaling-stroke
pentru a preveni deformarea grosimilor de linii atunci când graficul nu menține proporțiile alese 1:1 pe pagina web pe care este afișat, așa cum se întâmplă în propunerea anterioară.
Pentru a „decupa” graficul și a afișa doar zona pe care o alegeți, utilizați viewBox
. În acest caz, am ales să vedem partea din grafic care începe la 0,0 (colțul din stânga sus) și măsoară 100x100 în jos și în dreapta. Partea desenului situată în coordonate cu valori negative sau mai mari de 100 nu va fi afișată pe pagina web chiar dacă acestea există în obiect SVG
Adăugați elemente noi la desenul SVG
În exemplul anterior, funcția actualizar_grafico()
utilizați un aspect SVG la care se schimbă proprietatea d
, care este ceea ce exprimă lanțul de coordonate. Alternativa ar fi crearea întregului obiect de fiecare dată când este redesenat. Avantajul primei opțiuni este că aspectul grafic (cum ar fi grosimea sau culoarea) este definit în cod HTML, limitarea este că obiectele trebuie create anterior.
Pentru a crea obiecte SVG, utilizați createElementNS()
, care permite includerea spatiu de nume. În exemplul de mai jos este creat un nou obiect text (text
) și este asociat cu un element SVG care există deja în cod HTML a site-ului web. Odată ce noul element este creat, proprietățile sale sunt atribuite cu setAttribute()
si se adauga la SVG cu appendChild()
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
function
rotular
(
objeto_grafico,
texto,
inicio=[0,0],
altura=10.0,
tipo_letra=“SircuitoRegularMedium”,
color_texto=“#000000”,
color_fondo=“#FFFFFF”
)
{
nuevo_objeto_svg=document.createElementNS(“http://www.w3.org/2000/svg”,“text”);
nuevo_objeto_svg.setAttribute(“x”,inicio[0]);
nuevo_objeto_svg.setAttribute(“y”,inicio[1]);
nuevo_objeto_svg.setAttribute(“font-family”,tipo_letra);
nuevo_objeto_svg.setAttribute(“font-size”,altura);
nuevo_objeto_svg.setAttribute(“fill”,color_texto);
nuevo_objeto_svg.textContent=texto;
objeto_grafico.appendChild(nuevo_objeto_svg);
}
//rotular(document.getElementById(“cosa_svg”),”HOLA”,[10,10]);
|
Modificați proporția elementelor de desen
Dacă ați încercat să etichetați cu funcția din exemplul din secțiunea anterioară, veți fi văzut că textul apare deformat atunci când proporția obiectului de pe pagina web (width
y height
De cod HTML) nu este egală cu cea a zonei reprezentate (viewBox
). Pentru a adapta proporția este necesar să cunoașteți măsurătorile obiectului SVG pentru care se poate consulta stilul obiectului, sau containerul HTML, dacă obiectul SVG transfera această proprietate. Atribuirea dreptului de proprietate transform
la obiecte SVG care depind de proportie, deformatia poate fi corectata prin aplicarea unei operatii de scalare scale()
în care coeficientul din X este diferit de cel din Y.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
function
rotular
(
objeto_grafico,
texto,
inicio=[0,0],
altura=10.0,
proporcion=1.0,
tipo_letra=“SircuitoRegularMedium”,
color_texto=“#000000”,
color_fondo=“#FFFFFF”
)
{
var escala_horizontal=parseFloat(getComputedStyle(objeto_grafico).height)/parseFloat(getComputedStyle(objeto_grafico).width);
nuevo_objeto_svg=document.createElementNS(“http://www.w3.org/2000/svg”,‘text’);
nuevo_objeto_svg.setAttribute(“transform”,“scale(“+escala_horizontal+“,1.0)”); // scale permite cambiar la escala en X e Y
//nuevo_objeto_svg.setAttribute(“transform”,”scaleX(“+escala_horizontal+”)”); // Como se sabe que sólo cambia la escala en X, se puede usar scaleX
nuevo_objeto_svg.setAttribute(“x”,inicio[0]);
nuevo_objeto_svg.setAttribute(“y”,inicio[1]);
nuevo_objeto_svg.setAttribute(“font-family”,tipo_letra);
nuevo_objeto_svg.setAttribute(“font-size”,altura);
nuevo_objeto_svg.setAttribute(“fill”,color_texto);
nuevo_objeto_svg.textContent=texto;
objeto_grafico.appendChild(nuevo_objeto_svg);
}
//rotular(document.getElementById(“cosa_svg”),”HOLA”,[10,10]);
|
SVG permite gruparea mai multor obiecte formând un nou element compozit care suportă și proprietăți, ca niște obiecte simple. Pentru a aplica aceeași transformare unei serii de obiecte simultan în loc de fiecare obiect separat, le puteți grupa în funcție de această resursă și aplicați o singură proprietate transform
tuturor.
După cum am explicat când vorbim despre format SVG, elementele unui grup sunt incluse în etichete <g>
y </g>
. Pentru a adăuga de la JavaScript elementele unui grup SVG este folosit, așa cum se vede în exemplul anterior, appendChild()
odată ce noul obiect este definit.
Pentru a stabili o origine la aplicarea transformărilor, proprietatea poate fi folosită pe obiecte SVG transform-origin
, a cărui valoare sunt coordonatele X și Y ale punctului de la care începe transformarea. Dacă o valoare pentru originea transformării nu este indicată în mod expres (în browserul web), se utilizează centrul coordonatelor. Din păcate, la momentul scrierii, specificarea comportamentului transformărilor folosind o altă sursă decât cea implicită nu este omogenă între browsere și ar trebui folosită cu prudență.
Odată cu transformarea la scară cu scale
Există și altele, cum ar fi rotația cu rotation
iar mişcarea cu translate
, care oferă a alternativă la reprezentarea grafică: în loc să obțineți coordonate noi, le puteți reprezenta în spațiul propriu și puteți transforma graficul pentru a se potrivi cu formatul în care doriți să le reprezentați.
Adăugați referințe la diagramă
Acum că partea principală a graficului este rezolvată prin trasarea valorilor cu un profil și o zonă umplută, acesta poate fi completat cu referințe care ajută la citirea acestuia. De exemplu, să începem prin a trasa câteva referințe orizontale (linii) care marchează valorile maxime și minime acceptabile, precum și o valoare dorită. După cum sa explicat, puteți alege să adăugați obiectele la SVG direct de la JavaScript sau includeți-le manual în cod HTML și modificați-le mai târziu cu JavaScript.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
var CONTENEDOR_SVG; // Objeto SVG que contiene el gráfico que representa los valores monitorizados por los sensores en la IoT
var NS=“http://www.w3.org/2000/svg”; // Nombre del espacio de nombres (name space NS)
var ANCHO_CAJA=100.0; // Ancho del objeto SVG (aunque al dibujarlo con el código HTML tomará otras dimensiones
var ALTO_CAJA=100.0; // Alto del objeto SVG (aunque al dibujarlo con el código HTML tomará otras dimensiones
var VALOR_MAXIMO=10.0; // Mayor valor admisible en el gráfico SVG. Los valores mayores se saldrán del gráfico salvo por un margen que permite tener una idea de la tendencia
var VALOR_MINIMO=–5.0; // Menor valor admisible en el gráfico SVG. Los valores menores se saldrán del gráfico salvo por un margen que permite tener una idea de la tendencia
var VALOR_DESEADO=5.0; // Mejor valor del parámetro medido (temperatura). Sirve para tener una idea rápida de cómo de correcto es el estado del sistema
var MARGEN_VALOR=1.0; // Zona por encima del valor mayor y por debajo del valor menor que se representa para tener una idea aproximada de la tendencia cuando los datos monitorizados rebasen los valores máximo y/o mínimo
function inicializar_grafico()
{
CONTENEDOR_SVG=document.getElementById(“contenedor_svg”);
crear_referencia_horizontal_svg(VALOR_MAXIMO,“#FF0000”);
crear_referencia_horizontal_svg(VALOR_DESEADO,“#00FF00”);
crear_referencia_horizontal_svg(VALOR_MINIMO,“#0000FF”);
}
function crear_referencia_horizontal_svg
(
altura=0.0,
color=“#000000”,
grosor=0.5,
opacidad=1.0
)
{
var altura_corregida=ALTO_CAJA–(altura+MARGEN_VALOR–VALOR_MINIMO)*ALTO_CAJA/(Math.abs(VALOR_MAXIMO–VALOR_MINIMO)+MARGEN_VALOR*2);
var referencia_horizontal=document.createElementNS(NS,‘line’);
referencia_horizontal.setAttribute(“x1”,0.0);
referencia_horizontal.setAttribute(“x2”,ANCHO_CAJA);
referencia_horizontal.setAttribute(“y1”,altura_corregida);
referencia_horizontal.setAttribute(“y2”,altura_corregida);
referencia_horizontal.style.stroke=color;
referencia_horizontal.style.strokeWidth=grosor;
referencia_horizontal.style.strokeOpacity=opacidad;
CONTENEDOR_SVG.appendChild(referencia_horizontal);
}
//inicializar_grafico();
|
Pare logic să etichetăm aceste referințe orizontale cu text care clarifică valoarea pe care o reprezintă. Pentru a evidenția textul, puteți folosi dreptunghiuri care vor ieși în evidență din fundal și din grafic. Deoarece textele vor trebui scalate pentru a compensa deformarea, toate pot fi grupate într-un obiect căruia i se va aplica scara; Principalul avantaj de a proceda astfel este acela de a le putea modifica într-o singură operațiune dacă containerul grafic (fereastra browserului) este redimensionat și modifică acea proporție pe care o corectează scala.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
// Este código de ejemplo usa constantes para hacerlo más legible a desarrolladores de aplicaciones para microcontroladores de series pequeñas. La opción más recomendable, y más propia del estilo JavaScript, es crear un objeto cuyas propiedades serían las constantes y que incluiría los métodos (aquí funciones) que generan o modifican el gráfico o en este caso las referencias
var CONTENEDOR_SVG; // Objeto SVG que contiene el gráfico que representa los valores monitorizados por los sensores en la IoT
var NS=“http://www.w3.org/2000/svg”; // Nombre del espacio de nombres (name space NS)
var ANCHO_CAJA=100.0; // Ancho del objeto SVG (aunque al dibujarlo con el código HTML tomará otras dimensiones
var ALTO_CAJA=100.0; // Alto del objeto SVG (aunque al dibujarlo con el código HTML tomará otras dimensiones
var TIEMPO_REPRESENTADO=30000; // Milisegundos visibles en el gráfico empezando en el valor mayor (fecha y hora del último valor monitorizado)
var VALOR_MAXIMO=10.0; // Mayor valor admisible en el gráfico SVG. Los valores mayores se saldrán del gráfico salvo por un margen que permite tener una idea de la tendencia
var VALOR_MINIMO=–5.0; // Menor valor admisible en el gráfico SVG. Los valores menores se saldrán del gráfico salvo por un margen que permite tener una idea de la tendencia
var VALOR_OPTIMO=5.0; // Mejor valor del parámetro medido (temperatura). Sirve para tener una idea rápida de cómo de correcto es el estado del sistema
var MARGEN_VALOR=2.0; // Zona por encima del valor mayor y por debajo del valor menor que se representa para tener una idea aproximada de la tendencia cuando los datos monitorizados rebasen los valores máximo y/o mínimo
var desplazamiento_valor=desplazamiento(Date.now());
var escala_valor=escala();
var TIPOGRAFIA=“SircuitoRegularMedium”; // Tipografía con la que se rotula todo el gráfico (se usa una constante buscando la uniformidad, pero se puede rotular usando diferentes tipos de letra si es necesario)
var ALTURA_TEXTO=10.0; // Altura de los textos en valor absoluto (píxeles)
var COLOR_MINIMO=“#621D87”; // Color de la referencia que indica el valor mínimo
var COLOR_OPTIMO=“#1D8762”; // Color de la referencia que indica el valor máximo
var COLOR_MAXIMO=“#871E35”; // Color de la referencia que indica el valor óptimo
var COLOR_TIPOGRAFIA=“#A8C3EA”; // Color del tipo de letra con que se rotulan las referencias
var GROSOR_REFERENCIA=0.5; // Grosor de la línea que se dibuja como referencia en valor absoluto (píxeles)
var OPACIDAD_REFERENCIA=1.0; // Opacidad de la línea de referencia. Si se dibuja sobre el gráfico con cierta transparencia permite ver el dibujo bajo ella
var RELLENO_FONDO_REFERENCIA=4.0; // Margen entre el fondo de la referencia y el texto medido en valor absoluto (la línea empieza en el margen izquierdo y recorre todo el gráfico)
var MARGEN_FONDO_REFERENCIA=10.0; // Separación de la referencia y el borde izquierdo del gráfico medido en valor absoluto (la línea empieza en el margen izquierdo y recorre todo el gráfico)
var ANCHO_FONDO_REFERENCIA=34.0; // Medida horizontal del rectángulo que hace de fondo al texto de la referencia medido en valor absoluto
function proporcion_grafico()
{
return parseFloat(getComputedStyle(CONTENEDOR_SVG).height)/parseFloat(getComputedStyle(CONTENEDOR_SVG).width); // La escala debe calcularse cada vez ya que no se sabe si se ha redimensionado el objeto HTML que contiene al objeto SVG y que le da el tamaño
}
function medida_grafico()
{
var proporcion=[]; // Coeficiente que calcula la medida absoluta en píxeles en función del ancho/alto base del gráfico y de la representación en la página web // Coeficiente que calcula la medida absoluta en píxeles en función del ancho/alto base del gráfico y de la representación en la página web
proporcion[0]=ANCHO_CAJA/parseFloat(getComputedStyle(CONTENEDOR_SVG).width);
proporcion[1]=ALTO_CAJA/parseFloat(getComputedStyle(CONTENEDOR_SVG).height);
return proporcion;
}
function desplazamiento(valor_mayor)
{
var desplazamiento_valor=[];
desplazamiento_valor[0]=valor_mayor–TIEMPO_REPRESENTADO; // Valor desde el que se empieza a contar el tiempo: el valor mayor (último) menos el rango de tiempo representado
desplazamiento_valor[1]=MARGEN_VALOR–VALOR_MINIMO; // Valor menor mostrado (al menor se le añade un margen para visualizar el principio de los valores fuera del rango permitido)
return desplazamiento_valor;
}
function escala()
{
var escala_valor=[];
escala_valor[0]=ANCHO_CAJA/TIEMPO_REPRESENTADO; // Coeficiente que multiplica a los valores horizontales (tiempo) para calcular las coordenadas X
escala_valor[1]=ALTO_CAJA/(Math.abs(VALOR_MAXIMO–VALOR_MINIMO)+MARGEN_VALOR*2); // Coeficiente que multiplica a los valores (vertical) para calcular las coordenadas Y
return escala_valor;
}
function crear_referencia_horizontal_svg
(
posicion=0.0,
color_dibujo=“#000000”,
grosor_linea=GROSOR_REFERENCIA,
opacidad_linea=OPACIDAD_REFERENCIA,
color_texto=COLOR_TIPOGRAFIA,
altura_texto=ALTURA_TEXTO
)
{
var proporcion_horizontal=proporcion_grafico(); // La escala debe calcularse cada vez ya que no se sabe si se ha redimensionado el objeto HTML que contiene al objeto SVG y que le da el tamaño
var coeficiente_medida=medida_grafico();
var posicion_corregida=ALTO_CAJA–(posicion+desplazamiento_valor[1])*escala_valor[1];
var referencia_horizontal=document.createElementNS(NS,‘line’); // El orden en el que se crean los objetos determina qué tapa (lo último) y que es tapado (lo primero)
var fondo_referencia=document.createElementNS(NS,‘rect’); // El rectángulo (opaco) se creará después de la línea (que puede ser un poco transparente) para definir con claridad el fondo del texto
var texto_referencia=document.createElementNS(NS,‘text’); // El texto se creará en último lugar para que quede sobre los otros objetos
referencia_horizontal.setAttribute(“x1”,0.0);
referencia_horizontal.setAttribute(“x2”,ANCHO_CAJA);
referencia_horizontal.setAttribute(“y1”,posicion_corregida);
referencia_horizontal.setAttribute(“y2”,posicion_corregida);
referencia_horizontal.style.stroke=color_dibujo;
referencia_horizontal.style.strokeWidth=grosor_linea*coeficiente_medida[1];
referencia_horizontal.style.strokeOpacity=opacidad_linea;
CONTENEDOR_SVG.appendChild(referencia_horizontal); // Añadir la línea de referencia lo más abajo
fondo_referencia.setAttribute(“x”,MARGEN_FONDO_REFERENCIA*coeficiente_medida[0]);
fondo_referencia.setAttribute(“y”,posicion_corregida–(ALTURA_TEXTO+RELLENO_FONDO_REFERENCIA*2.0)*coeficiente_medida[1]/2.0);
fondo_referencia.setAttribute(“width”,ANCHO_FONDO_REFERENCIA*coeficiente_medida[0]);
fondo_referencia.setAttribute(“height”,(ALTURA_TEXTO+RELLENO_FONDO_REFERENCIA*2.0)*coeficiente_medida[1]);
fondo_referencia.style.fill=color_dibujo;
fondo_referencia.style.fillOpacity=1.0;
fondo_referencia.style.strokeWidth=0;
CONTENEDOR_SVG.appendChild(fondo_referencia);
texto_referencia.setAttribute(“x”,(MARGEN_FONDO_REFERENCIA+RELLENO_FONDO_REFERENCIA)*coeficiente_medida[0]/proporcion_horizontal);
texto_referencia.setAttribute(“y”,posicion_corregida+ALTURA_TEXTO/2.0*coeficiente_medida[1]);
texto_referencia.setAttribute(“font-family”,TIPOGRAFIA);
texto_referencia.setAttribute(“font-size”,ALTURA_TEXTO*coeficiente_medida[1]);
texto_referencia.setAttribute(“fill”,COLOR_TIPOGRAFIA);
texto_referencia.setAttribute(“transform”,“scale(“+proporcion_horizontal+“,1.0)”);
texto_referencia.textContent=(posicion>=0?“+”:“”)+posicion;
CONTENEDOR_SVG.appendChild(texto_referencia);
}
function inicializar_grafico()
{
CONTENEDOR_SVG=document.getElementById(“contenedor_svg”);
crear_referencia_horizontal_svg(VALOR_MAXIMO,COLOR_MAXIMO);
crear_referencia_horizontal_svg(VALOR_OPTIMO,COLOR_OPTIMO);
crear_referencia_horizontal_svg(VALOR_MINIMO,COLOR_MINIMO);
}
//inicializar_grafico();
|
Există mai multe aspecte interesante în exemplul de cod de mai sus. În primul rând, comentați că constantele (variabilele globale) au fost folosite pentru a face exemplul mai ușor de citit pentru utilizatorii care provin din programare. microcontrolere en C o o C ++. După cum se va vedea mai târziu, modul optim de programare JavaScript Ar fi folosirea de obiecte care ar conține aceste valori și metode care ar gestiona referințele din acest exemplu sau graficul, în general, într-un sistem de producție.
Pe de altă parte, avansând ceea ce ar fi codul mai generic, au fost dezvoltate funcții separate care calculează diferiții coeficienți care corectează proporția graficului pentru ajustarea textului proporcion_grafico()
, scara valorilor în funcție de intervalul acestora escala()
și un factor de corecție pentru măsurători care sunt cunoscute în valoare absolută, cum ar fi măsurătorile în referințe medida_grafico()
.
Citirea acestui cod ar trebui să ajute la clarificarea contextului în care funcționează o aplicație ca aceasta, care desenează grafică în timp real și trebuie să fie flexibilă pentru a fi prezentată în diverse contexte grafice (cel puțin diferite dimensiuni și proporții). În primul rând, obiectele trebuie generate SVG, fie „manual” în cod HTML, fie prin cod JavaScript și în orice caz, referiri la aceste obiecte trebuie obținute ulterior pentru a le manipula din JavaScript astfel încât să poată fi desenate noi grafice și să poată fi adaptată reprezentarea unui grafic deja desenat la o schimbare a mediului în care este prezentat.
O altă referință care poate ajuta la interpretarea cu ușurință a unui grafic sunt punctele care reprezintă valori specifice (nodurile liniei). În acest exemplu, în care reprezentăm o singură mărime, alegerea unui simbol nu este critică, dar dacă se suprapun mai multe valori diferite pentru a căuta corelația, este interesant de distins, pe lângă utilizarea altor resurse precum culoarea. , prin desenarea diferitelor simboluri. Grafica folosită pentru nodul de linie trebuie modificată ca dimensiune și proporție, așa cum se întâmplă, de exemplu, cu textele, astfel încât dimensiunile acestuia să fie absolute și astfel încât proporțiile acestuia să fie menținute chiar dacă cele ale casetei pe care o conține se modifică.graficul.
În exemplul anterior am văzut deja cum să calculăm diferiții coeficienți pentru a redimensiona și a corecta proporția desenului; În ceea ce privește modul de implementare a managementului simbolurilor nodurilor sau vârfurilor grafului, o posibilă soluție poate fi stocarea obiectelor SVG într-un vector și modificați poziția acestuia atunci când graficul este actualizat prin citirea unei noi valori sau când este redesenat prin redimensionarea containerului. În primul caz poziția sa ar trebui modificată, iar în al doilea proporția cu proprietatea transform
și valoarea de scale
. Următorul cod este o modificare a funcției actualizar_grafico()
pentru a include repoziționarea simbolurilor vârfurilor graficului.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
function actualizar_grafico_puntos
(
grafico, // Objeto SVG con el que se dibuja la gráfico
coordenada, // Matriz con las coordenadas del trazado formada por pares [tiempo,valor] ordenados primero el más antiguo (tiempo menor) último el más nuevo
puntos, // Matriz con los objetos SVG que se representan en los nodos de la línea (los valores reales)
tiempo_total_representado, // Tiempo total representado por la gráfico (en milisegundos)
valor_maximo, // Valor máximo aceptable antes de emitir una alarma
valor_minimo, // Valor mínimo aceptable antes de emitir una alarma
margen_valor, // Cantidad extra que se representa sobre/bajo el valor máximo/mínimo
parametro_cerrado, // Valor booleano que indica si el trazado se cierra o no (por defecto false)
parametro_ancho_caja, // Ancho de la caja que contiene la gráfico (por defecto 100.0)
parametro_alto_caja // Alto de la caja que contiene la gráfico (por defecto 100.0)
)
{
var cerrado=parametro_cerrado||false; // Valor booleano que indica si el trazado se cierra o no (por defecto false)
var ancho_caja=parametro_ancho_caja||100.0; // Ancho de la caja que contiene la gráfico (por defecto 100.0)
var alto_caja=parametro_alto_caja||100.0; // Alto de la caja que contiene la gráfico (por defecto 100.0)
var coordenadas_trazado=“M “; // Cadena de texto que representa la propiedad “d” del trazado SVG
var desplazamiento=[]; // Desplazamientos X e Y para posicionar el tiempo y el valor representado dentro del rango del gráfico
var escala=[]; // Coeficientes X e Y para calcular el tamaño al representar el gráfico
var sin_recortar=true; // False si la gráfico se sale de la caja (si se sale, si recorta, se hace false para no seguir dibujando puntos)
var contador_valor=coordenada.length–1; // Variable para recorrer los valores (índice)
var posicion=[]; // Variable intermedia (para hacer más legible el código) con la que calcular la posición horizontal y vertical de un punto del trazado
escala[0]=ancho_caja/tiempo_total_representado; // Coeficiente que multiplica a los valores horizontales (tiempo) para calcular las coordenadas X
escala[1]=alto_caja/(Math.abs(valor_maximo–valor_minimo)+margen_valor*2); // Coeficiente que multiplica a los valores (vertical) para calcular las coordenadas Y
desplazamiento[0]=coordenada[coordenada.length–1][0]–tiempo_total_representado; // Valor desde el que se empieza a contar el tiempo: el valor mayor (último) menos el rango de tiempo representado
desplazamiento[1]=margen_valor–valor_minimo; // Valor menor mostrado (al menor se le añade un margen para visualizar el principio de los valores fuera del rango permitido)
if(cerrado) // Si se dibuja un path (trazado) cerrado…
{
coordenadas_trazado+=ancho_caja+“,”+alto_caja+” L “; // …se empieza por la parte inferior de la caja
}
while(contador_valor>=0&&sin_recortar) // Mientras queden valores por representar y no se haya llegado al borde izquierdo del gráfico…
{
posicion[0]=(coordenada[contador_valor][0]–desplazamiento[0])*escala[0]; // Calcular la X restando al tiempo el desplazamiento y convirtiéndolo a la escala del gráfico con el coeficiente horizontal
posicion[1]=alto_caja–(coordenada[contador_valor][1]+desplazamiento[1])*escala[1]; // Calcular la Y restando del alto de la caja (la Y crece hacia abajo en SVG)
punto[contador_valor].setAttribute(“cx”,posicion[0]);
punto[contador_valor].setAttribute(“cy”,posicion[1]);
coordenadas_trazado+=posicion[0]+“,”+posicion[1]; // Formar la coordenada con la X y la Y
if(posicion[0]>0) // Si no se ha rebasado el margen izquierdo…
{
coordenadas_trazado+=contador_valor>0?” L “:“”; // …y quedan valores que represntar, añadir una nueva línea (código L) para el próximo
}
else // Si se ha rebasado el margen izquierdo…
{
sin_recortar=false; // …abandonar el modo sin recorte (lo que terminará de calcular coordenadas)
}
contador_valor—; // Pasar al siguiente valor
}
if(cerrado) // Si se dibuja un trazado (path) cerrado…
{
coordenadas_trazado+=” L “+posicion[0]+“,”+alto_caja+” Z”; // …se termina por la parte inferior de la caja y se añade Z para cerrarlo en SVG
}
grafico.setAttribute(“d”,coordenadas_trazado); // Cambiar las coordenadas del trazado (propiedad “d”) por las que se han calculado
for(;contador_valor>=0;contador_valor—)
{
punto[contador_valor].setAttribute(“cx”,–10000);
punto[contador_valor].setAttribute(“cy”,0);
}
}
|
Modificări aduse funcției actualizar_grafico()
pentru a obține noua funcție actualizar_grafico_puntos()
Sunt cele evidențiate în codul exemplului anterior. Mai întâi, în linia 5, luăm un vector de obiecte SVG ca parametru. Acest vector va conține simbolurile care trebuie repoziționate în noile noduri ale graficului.
În liniile 39 și 40 sunt atribuite noile coordonate ale centrului, cx
y cy
, la cele ale valorilor care sunt reprezentate. Dacă simbolul nu s-a bazat pe centru, probabil va fi necesar să adăugați un decalaj cx
jumatate din latime si in cy
de jumătate din înălțime pentru a le repoziționa exact pe nodul grafic.
În liniile 57 până la 61, punctele care corespund coordonatelor care nu sunt desenate deoarece sunt tăiate de marginea stângă sunt repoziționate în afara graficului. Coordonata de cy
la zero şi cea de cx
la orice număr negativ (mai mare decât punctul însuși), astfel încât să nu fie afișat atunci când este tăiat, ca partea din stânga a graficului, de fereastra SVG.
Gestionați diagrama dintr-un obiect cu JavaScript
Toate operațiunile care au fost explicate până acum pot fi integrate într-un obiect pentru a gestiona graficul cu un stil mai tipic noilor versiuni de JavaScript. Această alternativă de implementare are avantajul în plus de a simplifica încorporarea mai multor grafice, de valori diferite, pe aceeași pagină web.
Înainte de a discuta despre implementare, să trecem în revistă cele mai comune modalități de a crea obiecte cu JavaScript și unele dintre particularitățile funcțiilor care afectează propunerea de desenare grafică a senzorilor IoT.
S-a explicat deja că noul mod de a crea obiecte în JavaScript (disponibil începând cu versiunea 5 a ECMAScript) constă în folosirea Object.create
, care ar trebui să se obișnuiască să folosească în locul „clasicului” new
, care bineînțeles încă funcționează corect, deși scopul său este mai mult de a simula stilul limbajelor cu obiecte bazate pe clasă (JavaScript bazează crearea de obiecte pe prototipuri) decât o alternativă de lucru.
1
2
3
4
5
6
7
8
9
10
|
<!DOCTYPE html>
<html lang=“es”>
<head>
<meta charset=“utf-8”>
<title>Crear objetos con new o con Objetct.create</title>
<script type=“text/javascript” src=“https://polaridad.es/javascript-grafico-svg-sensor-internet-de-las-cosas-iot/crear_objetos.js”></script>
</head>
<body onload=“empezar();”>
</body>
</html>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
function Objeto_clasico(primero,segundo)
{
this.primero=primero||1;
this.segundo=segundo||2;
this.hacer_algo=function()
{
console.log(“El objeto clásico le saluda (primero=”+this.primero+” y segundo=”+this.segundo+“)”);
}
}
var Objeto_ES5=
{
primero:10,
segundo:20,
hacer_algo:
function()
{
console.log(“El objeto ES5 le saluda (primero=”+this.primero+” y segundo=”+this.segundo+“)”);
}
}
var objeto_clasico=new Objeto_clasico();
var objeto_ES5=Object.create(Objeto_ES5);
function empezar()
{
console.log(“Objeto clásico (“+objeto_clasico.primero+“,”+objeto_clasico.segundo+“)”);
console.log(“Objeto ES5 (“+objeto_ES5.primero+“,”+objeto_ES5.segundo+“)”);
objeto_clasico.hacer_algo();
objeto_ES5.hacer_algo();
}
|
Codul anterior vă permite să vă amintiți diferențele dintre crearea obiectelor cu Object.create
o con new
. De asemenea, servește pentru a sublinia că, în timp ce funcția cu care este creat obiectul new
poate fi oriunde în cod, obiectul trebuie să existe deja înainte de a putea fi instanțiat Object.create
(Obiectul ES5_Object nu este o funcție).
Pe liniile 3 și 4, pentru a seta o valoare implicită proprietăților din funcția cu care creează obiectul new
, fiecare proprietate este atribuită valorii argumentului corespunzător sau (||
), dacă nu au fost transmise argumente, adică dacă sunt nedefinite (undefined
), întrucât această împrejurare este evaluată ca false
, este atribuită valoarea implicită.
Contextul în care este executată o funcție JavaScript ridică două probleme care sunt importante de reținut și care pot fi, de asemenea, confuze atunci când utilizați acest limbaj de programare după ce ați lucrat cu alții, cum ar fi C o C ++, în cazul nostru. Contextul include variabilele definite în sfera funcției (și pe cele globale) care, de altfel, ridică un concept interesant, „închiderile” care stabilește un întreg stil de programare în JavaScript. Acestea fiind spuse, se putea aștepta la asta this
, care se referă la obiect atunci când este utilizat în cadrul codului care îl definește, contextul de execuție în care a fost definit este menținut dar cel pe care îl folosește este contextul din care este apelată funcția. Acest comportament este transparent în majoritatea cazurilor, dar există două circumstanțe în care poate fi confuz: o funcție definită în interiorul unei alte funcții și o metodă numită dintr-un eveniment obiect. window
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
var primero=“Primero global”;
var segundo=“Segundo global”;
var Contexto=
{
primero:“Primero en contexto”,
segundo:“Segundo en contexto”,
probar:
function()
{
console.log(“Primero en contexto: “+this.primero);
console.log(“Segundo en contexto: “+this.segundo);
function probar_dentro()
{
console.log(“Primero dentro: “+this.primero);
console.log(“Segundo dentro: “+this.segundo);
}
probar_dentro();
}
}
var probador=Object.create(Contexto);
probador.probar();
/*
Primero en contexto: Primero en contexto
Segundo en contexto: Segundo en contexto
Primero dentro: Primero global
Segundo dentro: Segundo global
*/
|
La executarea codului anterior, textul comentat de la sfârșit este afișat în consolă. Cele două linii marcate reflectă un comportament care poate fi confuz: contextul de execuție a funcției probar_dentro()
nu probar()
, așa cum era de așteptat, dar window
, care arată variabilele globale și nu proprietățile cu același nume. Dacă nu doriți acest comportament, pur și simplu creați o variabilă în funcția de cel mai înalt nivel și atribuiți-o this
, ca în codul următor.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
var primero=“Primero global”;
var segundo=“Segundo global”;
var Contexto=
{
primero:“Primero en contexto”,
segundo:“Segundo en contexto”,
probar:
function()
{
var esto=this;
console.log(“Primero en contexto: “+esto.primero);
console.log(“Segundo en contexto: “+esto.segundo);
function probar_dentro()
{
console.log(“Primero dentro: “+esto.primero);
console.log(“Segundo dentro: “+esto.segundo);
}
probar_dentro();
}
}
var probador=Object.create(Contexto);
probador.probar();
/*
Primero en contexto: Primero en contexto
Segundo en contexto: Segundo en contexto
Primero dentro: Primero en contexto
Segundo dentro: Segundo en contexto
*/
|
Pentru a controla contextul de execuție atunci când o metodă este apelată dintr-un eveniment window
, de exemplu prin redimensionarea ferestrei browserului, o altă particularitate a JavaScript: posibilitatea de a programa „fabrici de funcții”, adică funcții care generează alte funcții, returnându-le cu return
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
var Contexto=
{
prueba:“objeto”, // La propiedad prueba es de los objetos Contexto
llamar:
function()
{
esto=this;
return function()
{
console.log(“H + “+(Date.now()–hora)+” Contexto: “+esto.prueba);
};
}
}
var prueba=“global”; // Esta variable prueba es global (window.prueba)
var hora=Date.now();
var probador=Object.create(Contexto);
var cosa=probador.llamar();
var pesadez=setInterval(cosa,3000);
setTimeout(function(){clearInterval(pesadez)},30100); // Desactivar la llamada periódica
|
În exemplul de cod de mai sus, metoda llamar()
a obiectelor Contexto
Nu face treaba, dar returnează o funcție anonimă care se ocupă de ea. Pentru a verifica dacă totul funcționează conform așteptărilor, există o variabilă globală cu același nume cu proprietatea pe care o afișează funcția în consolă; Dacă contextul este corect, se va afișa valoarea proprietății și nu cea a variabilei globale.
JavaScript Încercați să corectați semnele punct și virgulă pe care le omitem la sfârșitul propozițiilor. Acest lucru permite un stil de scriere relaxat, dar este o sabie cu două tăișuri care trebuie tratată cu atenție. În cele mai multe cazuri, pentru a evita efectele nedorite pe care aceasta le produce în expresiile care ocupă mai multe rânduri, puteți folosi paranteze sau precedați modul în care JavaScript va interpreta codul; De aceea linia 8 din exemplu include function
in spatele a return
, dacă aș fi folosit altă linie sensul ar fi foarte diferit. După părerea mea, soluția cea mai lizibilă este utilizarea unei variabile intermediare (dispensabile) ca în versiunea următoare; Evident, odată ce comportamentul este înțeles, decizia îi corespunde programatorului.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
var Contexto=
{
prueba:“objeto”, // La propiedad prueba es de los objetos Contexto
llamar:
function()
{
esto=this;
var variable_auxiliar=
function()
{
console.log(“H + “+(Date.now()–hora)+” Contexto: “+esto.prueba);
};
return variable_auxiliar;
}
}
var prueba=“global”; // Esta variable prueba es global (window.prueba)
var hora=Date.now();
var probador=Object.create(Contexto);
var cosa=probador.llamar();
var pesadez=setInterval(cosa,3000);
setTimeout(function(){clearInterval(pesadez)},30100);
|
În același sens al evaluării unei expresii ca o funcție, adică returnând o funcție și nu valoarea pe care o returnează funcția; pe linia 21 din ultimul exemplu (era pe linia 19 din precedentul) se opreste cu clearInterval
funcția apelată cu setInterval
. Pentru ca acesta să acționeze timp de 30 de secunde, oprirea este amânată cu setTimeout
, care la rândul său are nevoie de o funcție ca prim argument; pentru a livra execuția ca parametru clearInterval
cu variabila care conține apelul periodic (și nu funcția clearInterval
) este pentru care este creată funcția anonimă din ultima linie.
Alegerea între scrierea codului integrând definiția funcției, mai compactă (ca în linia 21) sau folosirea unei variabile auxiliare, după părerea mea, mai lizibilă (ca în rândurile 19 și 20) variază puțin în performanță și depinde mai mult de stil și lizibilitate pt. întreținere.
Pentru a testa codul, înainte de a avea date pe server, puteți folosi un generator de valori aleatorii în intervalul dorit sau puteți pregăti tabele cu valori controlate care simulează funcționarea în condițiile dorite. Următorul exemplu folosește un generator de date simplu pe întreaga gamă, motiv pentru care par puțin exagerate.
Pentru a testa, puteți descărcați codul complet al exemplului format dintr-o pagină web scrisă în HTML, stilul CSS și codul JavaScript. Acesta din urmă este cel mai relevant, deoarece celelalte componente sunt doar suport minim, foarte simplificate și sunt mult mai dezvoltate în articolele din secțiunile corespunzătoare.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<!DOCTYPE html>
<html lang=“es”>
<head>
<meta charset=“utf-8”>
<title>Temperatura</title>
<script type=“text/javascript” src=“https://polaridad.es/javascript-grafico-svg-sensor-internet-de-las-cosas-iot/grafico.js”></script>
<link rel=“stylesheet” href=“estilo.css” type=“text/css” media=“all”>
</head>
<body onload=“iniciar_grafico(‘grafico’,’valor’,’fecha’);”>
<div id=“temperatura”>
<div id=“ultimo_valor”>
<div id=“fecha”></div>
<div id=“valor”></div>
</div>
<div id=“grafico”></div>
</div>
</body>
</html>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
body
{
margin:0px 0px 0px 0px;
}
#ultimo_valor
{
box–sizing:border–box;
width:100%;
height:40px;
padding:8px 0px 8px 14px;
font–family:monospaced;
font–size:18px;
color:#205587;
background–color:#86A7D0;
}
#fecha
{
float:left;
margin–right:15px;
}
#valor
{
float:left;
}
#grafico
{
width:100%;
height:200px;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
|
var Grafico=
{
contenedor_svg:void(0), // Objeto SVG con el que se dibuja el gráfico. Se inicializa con void(0) para asegurarse de que es === undefined al principio
contenedor_html:void(0), // Objeto HTML que contiene al objeto SVG con el gráfico (seguramente un div). Se inicializa con void(0) para asegurarse de que es === undefined al principio
contenedor_valor:void(0), // Objeto HTML en el que se muestra el último valor cargado del servidor
contenedor_fecha:void(0), // Objeto HTML para mostrar la fecha del último valor
pagina_servidor:“ultimo_valor_sensor.php”, // página en la que se consultan los datos
consulta:“fr1”, // Valor del parámetro con el que se consulta el servidor. La URL sería algo como http://servidoriot.com/ultimo_valor_sensor.php?zona=fr1
NS:“http://www.w3.org/2000/svg”, // Nombre del espacio de nombres (name space NS)
MINIMO:0, // Índice del menor valor admisible en el gráfico SVG. Los valores menores se saldrán del gráfico salvo por un margen que permite tener una idea de la tendencia
OPTIMO:1, // Índice del mejor valor del parámetro medido. Valor deseado para el parámetro monitorizado. Sirve para tener una idea rápida de cómo de correcto es el estado del sistema
MAXIMO:2, // Índice del mayor valor admisible en el gráfico SVG. Los valores mayores se saldrán del gráfico salvo por un margen que permite tener una idea de la tendencia
maximo_puntos:0, // Número máximo de vértices representados (pueden dibujarse menos si se recortan por la parte izquierda)
valor:[], // Últimos valores cargados del servidor
trazado:[], // Trazados representados (en principio se trata de representar uno con relleno y otro con línea)
cerrado:true, // Cuando vale true se dibuja el perfil y el área (la línea del gráfico y una zona rellena debajo). Cuando vale false solo se dibuja la línea
simbolo:[], // Objetos SVG utilizados para marcar los vértices de la línea (círculos)
linea_referencia:[], // Líneas horizontales en los valores de referencia
texto_referencia:[], // Objetos texto que rotulan los valores de referencia
fondo_referencia:[], // Rectángulos bajo los textos de los valores de referencia
medida_caja_svg:[100.0,100.0], // Ancho,Ancho del objeto SVG (aunque al dibujarlo con el código HTML tomará otras dimensiones ya que se dimensiona al 100% de su contenedor
correccion_caja_svg:[1.0,1.0], // Coeficientes para calcular la medida en el gráfico de un objeto del que se sabe el valor absoluto (Se usa el píxel como unidad)
tiempo_representado:0, // Milisegundos visibles en el gráfico empezando en el valor mayor (fecha y hora del último valor monitorizado)
valor_referencia:[0,0,0], // Valores de referencia [mínimo,óptimo,máximo]
margen_valor:0, // Zona por encima del valor mayor y por debajo del valor menor que se representa para tener una idea aproximada de la tendencia cuando los datos monitorizados rebasen los valores máximo y/o mínimo
escala_valor:[1.0,1.0], // Coeficientes que multiplican el valor monitorizado (y almacenado en el servidor) para calcular el representado según las dimensiones del gráfico
desplazamiento_valor:[0.0,0.0], // Desplazamiento que se suma al tiempo (X) y al valor (Y) para representarlo en el rango especificado para el gráfico
area_bajo_linea:true, // True para dibujar una zona rellena además del perfil (línea) del gráfico
medida_simbolo:[5,5], // Valor absoluto (en píxeles) del ancho y el alto del dibujo que se hace en los vértices de la línea del gráfico
tipografia:“sans-serif”, // Tipo de letra usado para rotular el gráfico (se usa solamente una tipografía por uniformidad)
altura_texto:10.0, // Altura de los textos en valor absoluto (píxeles). No todos los navegadores soportan todos los valores intermedios, será necesario coordinarlo con los valores que estén relacionados
escala_horizontal_texto:1, // Transformación que se aplica al texto para corregir la que implica la proporción del gráfico
color_fondo:“”, // Color de fondo del contenedor HTML del objeto SVG (Si es una cadena vacía no se cambia)
color_simbolos:“#000000”, // Color de los símbolos que se dibujan en los vértices de la línea del gráfico
color_referencia:[“#000000”,“#000000”,“#000000”], // Color de la referencia que indica el valor [mínimo,óptimo,máximo]
color_tipografia:“#FFFFFF”, // Color del tipo de letra con que se rotulan las referencias
grosor_trazado:4, // Grosor de la línea del gráfico
color_trazado:“#000000”, // Color de la línea del gráfico
color_relleno:“#000000”, // Color del relleno del gráfico
opacidad_relleno:1.0, // Opacidad del relleno del gráfico
grosor_referencia:0.5, // Grosor de la línea que se dibuja como referencia en valor absoluto (píxeles)
opacidad_referencia:1.0, // Opacidad de la línea de referencia. Si se dibuja sobre el gráfico con cierta transparencia permite ver el dibujo bajo ella
relleno_fondo_referencia:7.0, // Margen entre el fondo de la referencia y el texto medido en valor absoluto (la línea empieza en el margen izquierdo y recorre todo el gráfico)
margen_fondo_referencia:10.0, // Separación de la referencia y el borde izquierdo del gráfico medido en valor absoluto (la línea empieza en el margen izquierdo y recorre todo el gráfico)
ancho_fondo_referencia:50.0, // Medida horizontal del rectángulo que hace de fondo al texto de la referencia medido en valor absoluto
texto_valor:[“temperatura: “,” °C”], // Prefijo y sufico con los que se rotula el valor monitorizado
texto_hora:[“hora “,“”], // Prefijo y sufijo con los que se rotula la hora (cuando solamente se rotula la hora)
texto_fecha:[“”,“”], // Prefijo y sufijo con los que se rotula la fecha (cuando solamente se rotula la fecha)
texto_fecha_hora:[“el “,” a las “,” | “], // Prefijo con el que se rotula la hora, separador de fecha y hora y sufjo de la hora cuando se rotula la fecha y la hora
HORA:0, // Constante que representa la hora para el modo de la fecha/hora
FECHA:1, // Constante que representa la fehca para el modo de la fecha/hora
FECHA_Y_HORA:2, // Constante que representa rotular la fecha y la hora (para el modo de fecha/hora)
modo_fecha:2, // 0->hora, 1-> fecha 2-> fecha y hora
repetir:null, // Función llamada por setInterval
nueva_proporcion: // Calcular la nueva proporción (al iniciar y al redimensionar el contenedor del gráfico) para transformar el texto
function()
{
if(this.contenedor_svg!==undefined) // Se entiende que si no es undefined se ha asignado un objeto
{
if(this.contenedor_svg.nodeName===“svg”) // Si el objeto asignado es un SVG
{
this.escala_horizontal_texto=parseFloat(getComputedStyle(this.contenedor_svg).height)/parseFloat(getComputedStyle(this.contenedor_svg).width); // La escala debe calcularse cada vez ya que no se sabe si se ha redimensionado el objeto HTML que contiene al objeto SVG y que le da el tamaño
}
}
},
nueva_correccion_caja_svg:
function()
{
if(this.contenedor_svg!==undefined) // Se entiende que si no es undefined se ha asignado un objeto
{
if(this.contenedor_svg.nodeName.toLowerCase()===“svg”) // Si el objeto asignado es un SVG
{
this.correccion_caja_svg[0]=this.medida_caja_svg[0]/parseFloat(getComputedStyle(this.contenedor_svg).width);
this.correccion_caja_svg[1]=this.medida_caja_svg[1]/parseFloat(getComputedStyle(this.contenedor_svg).height);
}
}
},
nueva_escala:
function()
{
if(this.tiempo_representado>0) // Antes de inicializar el objeto el tiempo representado es cero
{
this.escala_valor[0]=this.medida_caja_svg[0]/this.tiempo_representado; // Coeficiente que multiplica a los valores horizontales (tiempo) para calcular las coordenadas X
this.escala_valor[1]=this.medida_caja_svg[1]/(Math.abs(this.valor_referencia[this.MAXIMO]–this.valor_referencia[this.MINIMO])+this.margen_valor*2); // Coeficiente que multiplica a los valores (vertical) para calcular las coordenadas Y
}
},
nuevo_desplazamiento:
function()
{
if(this.tiempo_representado>0) // Antes de inicializar el objeto el tiempo representado es cero
{
this.desplazamiento_valor[0]=this.valor[this.valor.length–1][0]–this.tiempo_representado; // Valor desde el que se empieza a contar el tiempo: el valor mayor (último) menos el rango de tiempo representado
this.desplazamiento_valor[1]=this.margen_valor–this.valor_referencia[this.MINIMO]; // Valor menor mostrado (al menor se le añade un margen para visualizar el principio de los valores fuera del rango permitido)
}
},
crear_svg: // Crear el objeto SVG dentro del objeto HTML
function()
{
if(this.contenedor_html!==null&&this.contenedor_html!==undefined) // Ya se ha asignado el objeto HTML
{
if(this.contenedor_html.nodeType===1) // Es un objeto HTML válido
{
// Color de fondo del objeto HTML
if(this.color_fondo!==“”)
{
this.contenedor_html.style.backgroundColor=this.color_fondo;
}
// Contenedor SVG
this.contenedor_svg=document.createElementNS(this.NS,“svg”); // Crear un objeto SVG
this.contenedor_svg.setAttribute(“width”,“100%”); // Ancho del objeto SVG
this.contenedor_svg.setAttribute(“height”,“100%”); // Alto del objeto SVG
this.contenedor_svg.setAttribute(“viewBox”,“0 0 “+this.medida_caja_svg[0]+” “+this.medida_caja_svg[1]); // Zona del SVG que se muestra
this.contenedor_svg.setAttribute(“preserveAspectRatio”,“none”); // No preservar la proporción para ocupar todo el objeto HTML que hace de contenedor
this.contenedor_html.appendChild(this.contenedor_svg); // Añadir al contenedor HTML el objeto SVG (que será el contenedor del gráfico)
// Trazado
this.trazado[0]=document.createElementNS(this.NS,“path”); // Crear el trazado que soporta el relleno
this.trazado[0].style.fill=this.color_relleno;
this.trazado[0].style.fillOpacity=this.opacidad_relleno;
this.trazado[0].style.stroke=“none”;
this.contenedor_svg.appendChild(this.trazado[0]); // Añadir el relleno al SVG
this.trazado[1]=document.createElementNS(this.NS,“path”); // Crear el trazado que soporta el perfil
this.trazado[1].setAttribute(“vector-effect”,“non-scaling-stroke”); // No mantener la proporción en el trazado (no deformarlo)
this.trazado[1].style.fill=“none”;
this.trazado[1].style.stroke=this.color_trazado;
this.trazado[1].style.strokeWidth=this.grosor_trazado;
//this.trazado[1].style.strokeOpacity=1.0; // La opacidad por defecto es 1.0
this.contenedor_svg.appendChild(this.trazado[1]); // Añadir el perfil al SVG
// Símbolos para los vértices
var ahora=Date.now()–30000; // Fecha y hora actual menos 30 segundos
for(var contador_vertices=0;contador_vertices<this.maximo_puntos;contador_vertices++)
{
this.valor[contador_vertices]=[];
this.valor[contador_vertices][0]=ahora; // Inicializar los valores a la fecha y hora actual menos 30 segundos
this.valor[contador_vertices][1]=this.valor_referencia[this.OPTIMO]; // Inicializar los valores al óptimo
this.simbolo[contador_vertices]=document.createElementNS(this.NS,“ellipse”); // Crear una elipse (círculo) para cada vértice
this.simbolo[contador_vertices].style.fill=this.color_simbolos;
this.simbolo[contador_vertices].style.fillOpacity=1.0;
this.simbolo[contador_vertices].style.stroke=“none”;
this.contenedor_svg.appendChild(this.simbolo[contador_vertices]); // Añadir el símbolo al SVG
}
//this.nuevo_desplazamiento(); // Necesario si se asignaran las alturas de las referencias
for(var contador_referencia=0;contador_referencia<this.valor_referencia.length;contador_referencia++)
{
// Línea de referencia
//var posicion_corregida=this.medida_caja_svg[1]-(this.valor_referencia[contador_referencia]+this.desplazamiento_valor[1])*this.escala_valor[1]; // Necesario si se asignaran las alturas de las referencias
this.linea_referencia[contador_referencia]=document.createElementNS(this.NS,“line”); // Crear la línea que representa el valor mínimo
this.linea_referencia[contador_referencia].style.stroke=this.color_referencia[contador_referencia];
this.linea_referencia[contador_referencia].style.strokeWidth=this.grosor_referencia;
this.linea_referencia[contador_referencia].style.strokeOpacity=this.opacidad_referencia;
this.linea_referencia[contador_referencia].setAttribute(“x1”,0.0); // Las líneas de referencia empiezan en el borde izquierdo…
this.linea_referencia[contador_referencia].setAttribute(“x2”,this.medida_caja_svg[0]); // …y terminan en el derecho
//this.linea_referencia[contador_referencia].setAttribute(“y1”,posicion_corregida); // Ambos extremos a la altura correspondiente a la referencia [mínimo,óptimo,máximo]
//this.linea_referencia[contador_referencia].setAttribute(“y2”,posicion_corregida); // Ambos extremos a la altura correspondiente a la referencia [mínimo,óptimo,máximo]
this.contenedor_svg.appendChild(this.linea_referencia[contador_referencia]);
// Rectángulo de fondo del texto que indica el valor de referencia
this.fondo_referencia[contador_referencia]=document.createElementNS(this.NS,‘rect’);
this.fondo_referencia[contador_referencia].style.fill=this.color_referencia[contador_referencia];
this.fondo_referencia[contador_referencia].style.fillOpacity=1.0;
this.fondo_referencia[contador_referencia].style.stroke=“none”;
this.contenedor_svg.appendChild(this.fondo_referencia[contador_referencia]);
// Texto de referencia
this.texto_referencia[contador_referencia]=document.createElementNS(this.NS,“text”); // Crear el texto para rotular la referencia
this.texto_referencia[contador_referencia].setAttribute(“font-family”,this.tipografia); // Asignar el tipo de letra
this.texto_referencia[contador_referencia].setAttribute(“fill”,this.color_tipografia); // Asignar el color
//this.texto_referencia[contador_referencia].textContent=””+(this.valor_referencia[contador_referencia]>=0?”+”:””)+this.valor_referencia[contador_referencia]; // Texto que se rotula (los valores máximo, óptimo y mínimo ya deben estar asignados)
this.contenedor_svg.appendChild(this.texto_referencia[contador_referencia]);
}
}
}
},
ajustar_prporcion:
function()
{
this.nueva_proporcion();
this.nueva_correccion_caja_svg();
var posicion_corregida;
for(var contador_vertices=0;contador_vertices<this.simbolo.length;contador_vertices++)
{
this.simbolo[contador_vertices].setAttribute(“rx”,this.medida_simbolo[0]*this.correccion_caja_svg[0]);
this.simbolo[contador_vertices].setAttribute(“ry”,this.medida_simbolo[1]*this.correccion_caja_svg[1]);
}
for(var contador_referencia=0;contador_referencia<this.valor_referencia.length;contador_referencia++)
{
posicion_corregida=this.medida_caja_svg[1]–(this.valor_referencia[contador_referencia]+this.desplazamiento_valor[1])*this.escala_valor[1];
this.fondo_referencia[contador_referencia].setAttribute(“x”,this.margen_fondo_referencia*this.correccion_caja_svg[0]);
this.fondo_referencia[contador_referencia].setAttribute(“y”,posicion_corregida–(this.altura_texto+this.relleno_fondo_referencia*2.0)*this.correccion_caja_svg[1]/2.0);
this.fondo_referencia[contador_referencia].setAttribute(“width”,this.ancho_fondo_referencia*this.correccion_caja_svg[0]);
this.fondo_referencia[contador_referencia].setAttribute(“height”,(this.altura_texto+this.relleno_fondo_referencia*2.0)*this.correccion_caja_svg[1]);
this.linea_referencia[contador_referencia].setAttribute(“y1”,posicion_corregida);
this.linea_referencia[contador_referencia].setAttribute(“y2”,posicion_corregida);
this.texto_referencia[contador_referencia].setAttribute(“transform”,“scale(“+this.escala_horizontal_texto+“,1.0)”);
this.texto_referencia[contador_referencia].setAttribute(“font-size”,this.altura_texto*this.correccion_caja_svg[1]);
this.texto_referencia[contador_referencia].setAttribute(“x”,(this.margen_fondo_referencia+this.relleno_fondo_referencia)*this.correccion_caja_svg[0]/this.escala_horizontal_texto);
this.texto_referencia[contador_referencia].setAttribute(“y”,posicion_corregida+this.altura_texto/2.0*this.correccion_caja_svg[1]);
this.texto_referencia[contador_referencia].textContent=(this.valor_referencia[contador_referencia]>=0?“+”:“”)+this.valor_referencia[contador_referencia]; // Texto que se rotula
}
},
rotar_valores:
function()
{
for(var contador_valor=0;contador_valor<this.valor.length–1;contador_valor++)
{
this.valor[contador_valor][0]=this.valor[contador_valor+1][0];
this.valor[contador_valor][1]=this.valor[contador_valor+1][1];
}
},
nuevo_valor_aleatorio:
function()
{
this.rotar_valores();
this.valor[this.valor.length–1][0]=this.valor[this.valor.length–2][0]+1000+Math.random()*1000; // El nuevo tiempo es el anterior más un segundo más un valor entre 0 y un segundo
this.valor[this.valor.length–1][1]=Math.random()*Math.abs(this.valor_referencia[this.MAXIMO]–this.valor_referencia[this.MINIMO])+this.valor_referencia[this.MINIMO]; // Un valor aleatorio entre el máximo y el mínimo
this.nuevo_desplazamiento(); // Cada nuevo valor cambia el desplazamiento en horizontal
this.dibujar_nuevo_valor(true);
this.dibujar_nuevo_valor(false);
this.rotular_nuevo_valor();
this.ritular_nueva_fecha();
},
cargar_nuevo_valor:
function()
{
var consulta=‘zona=”+this.consulta;
var resultado;
var ajax;
if(window.XMLHttpRequest)
{
ajax=new XMLHttpRequest(); // ajax=Object.create(XMLHttpRequest);
}
else // Versiones antiguas de MS Internet Explorer
{
ajax=new ActiveXObject(“Microsoft.XMLHTTP”);
}
ajax.onreadystatechange=
function()
{
if(ajax.readyState==4&&ajax.status==200&&ajax.responseType==“json”)
{
resultado=JSON.parse(ajax.responseText);
if(resultado.fecha>objeto_grafico.fecha[objeto_grafico.fecha.length–1])
{
this.rotar_valores();
this.valor[this.valor.length–1][0]=resultado.fecha;
this.valor[this.valor.length–1][1]=resultado.temperatura;
this.nuevo_desplazamiento(); // Cada nuevo valor cambia el desplazamiento en horizontal
this.dibujar_nuevo_valor(true);
this.dibujar_nuevo_valor(false);
this.rotular_nuevo_valor();
this.ritular_nueva_fecha();
}
}
}
ajax.open(“POST”,this.pagina_servidor);
ajax.setRequestHeader(“Method”,“POST “+this.pagina_servidor+” HTTP/1.1″);
ajax.setRequestHeader(“Content-type”,“application/x-www-form-urlencoded”);
ajax.setRequestHeader(“Content-length”,consulta.length);
ajax.setRequestHeader(“Connection”,“close”);
ajax.send(consulta);
},
rotular_nuevo_valor:
function()
{
var valor_redondeado=Math.round(this.valor[this.valor.length–1][1]*100.0)/100.0;
this.contenedor_valor.innerHTML=this.texto_valor[0]+valor_redondeado+this.texto_valor[1];
},
ritular_nueva_fecha:
function()
{
var fecha_lectura=new Date(this.valor[this.valor.length–1][0]);
var texto_fecha=this.texto_hora[0];
texto_fecha+=(fecha_lectura.getHours()<10?“0”:“”)+fecha_lectura.getHours();
texto_fecha+=“:”;
texto_fecha+=(fecha_lectura.getMinutes()<10?“0”:“”)+fecha_lectura.getMinutes();
texto_fecha+=“:”;
texto_fecha+=(fecha_lectura.getSeconds()<10?“0”:“”)+fecha_lectura.getSeconds();
texto_fecha+=this.texto_hora[1];
this.contenedor_fecha.innerHTML=texto_fecha;
},
dibujar_nuevo_valor: // Dibujar el gráfico cuando llega un nuevo valor (que estará almacenado en el vector valor
function(cerrado)
{
// Si cerrado es undefined se evalúa a false, que se toma como valor por defecto
var contador_valor=this.valor.length–1; // Variable para recorrer los valores (índice)
var sin_recortar=true; // False si la gráfico se sale de la caja (si se sale, si recorta, se hace false para no seguir dibujando puntos)
var posicion=[]; // Variable intermedia (para hacer más legible el código) con la que calcular la posición horizontal y vertical de un punto del trazado
var coordenadas_trazado=“M “; // Cadena de texto que representa la propiedad “d” del trazado SVG
if(cerrado) // Si el trazado que se está dibujando está cerrado (no confundir con this.cerrado que determina si se dibujan los dos trazados, la línea y el relleno)
{
coordenadas_trazado+=this.medida_caja_svg[0]+“,”+this.medida_caja_svg[1]+” L “; // …se empieza por la parte inferior de la caja
}
while(contador_valor>=0&&sin_recortar) // Mientras queden valores por representar y no se haya llegado al borde izquierdo del gráfico…
{
posicion[0]=(this.valor[contador_valor][0]–this.desplazamiento_valor[0])*this.escala_valor[0]; // Calcular la X restando al tiempo el desplazamiento y convirtiéndolo a la escala del gráfico con el coeficiente horizontal
posicion[1]=this.medida_caja_svg[1]–(this.valor[contador_valor][1]+this.desplazamiento_valor[1])*this.escala_valor[1]; // Calcular la Y restando del alto de la caja (la Y crece hacia abajo en SVG)
this.simbolo[contador_valor].setAttribute(“cx”,posicion[0]);
this.simbolo[contador_valor].setAttribute(“cy”,posicion[1]);
coordenadas_trazado+=posicion[0]+“,”+posicion[1]; // Formar la coordenada con la X y la Y
if(posicion[0]>0) // Si no se ha rebasado el margen izquierdo…
{
coordenadas_trazado+=contador_valor>0?” L “:“”; // …y quedan valores que representar, añadir una nueva línea (código L) para el próximo
contador_valor—; // Pasar al siguiente valor
}
else // Si se ha rebasado el margen izquierdo…
{
sin_recortar=false; // …abandonar el modo sin recorte (lo que terminará de calcular coordenadas)
}
}
if(cerrado) // Si se dibuja un trazado (path) cerrado…
{
coordenadas_trazado+=” L “+posicion[0]+“,”+this.medida_caja_svg[1]+” Z”; // …se termina por la parte inferior de la caja y se añade Z para cerrarlo en SVG
}
this.trazado[cerrado?0:1].setAttribute(“d”,coordenadas_trazado); // Cambiar las coordenadas del trazado (propiedad “d”) por las que se han calculado
for(;contador_valor>=0;contador_valor—)
{
this.simbolo[contador_valor].setAttribute(“cx”,–10000);
this.simbolo[contador_valor].setAttribute(“cy”,0);
}
}
}
var temperatura_frigorifico;
function iniciar_grafico(nombre_objeto_grafico,nombre_objeto_valor,nombre_objeto_fecha)
{
temperatura_frigorifico=Object.create(Grafico);
temperatura_frigorifico.contenedor_html=document.getElementById(nombre_objeto_grafico);
temperatura_frigorifico.contenedor_valor=document.getElementById(nombre_objeto_valor);
temperatura_frigorifico.contenedor_fecha=document.getElementById(nombre_objeto_fecha);
temperatura_frigorifico.color_fondo=“#A8C3EA”;
temperatura_frigorifico.color_trazado=“#205587”;
temperatura_frigorifico.grosor_trazado=3;
temperatura_frigorifico.color_relleno=“#205587”;
temperatura_frigorifico.opacidad_relleno=0.5;
temperatura_frigorifico.color_simbolos=“#205587”;
temperatura_frigorifico.color_referencia[Grafico.MINIMO]=“#621D87”;
temperatura_frigorifico.color_referencia[Grafico.OPTIMO]=“#1D8762”;
temperatura_frigorifico.color_referencia[Grafico.MAXIMO]=“#871E35”;
temperatura_frigorifico.grosor_referencia=1.5;
temperatura_frigorifico.opacidad_referencia=0.5;
temperatura_frigorifico.tipografia=“monospaced”;
temperatura_frigorifico.color_tipografia=“#A8C3EA”;
temperatura_frigorifico.cerrado=true; // Dibujar una línea y una zona rellena debajo (true por defecto)
temperatura_frigorifico.maximo_puntos=32; // Un espacio de 30 segundos representados con un intervalo mínimo de 1 segundo necesitan como máximo 31 puntos, se añade uno por seguridad (por si algún valor monitorizado estuviera por debajo del segundo)
temperatura_frigorifico.crear_svg();
temperatura_frigorifico.nueva_correccion_caja_svg();
temperatura_frigorifico.tiempo_representado=30000;
temperatura_frigorifico.valor_referencia[Grafico.MINIMO]=–5;
temperatura_frigorifico.valor_referencia[Grafico.OPTIMO]=5;
temperatura_frigorifico.valor_referencia[Grafico.MAXIMO]=10;
temperatura_frigorifico.margen_valor=3;
temperatura_frigorifico.nueva_escala();
temperatura_frigorifico.nuevo_desplazamiento(); // El desplazamiento se (re)calcula cada vez que se añade un valor
temperatura_frigorifico.ajustar_prporcion();
window.addEventListener(“resize”,function(){temperatura_frigorifico.ajustar_prporcion()}); // Versión moderna
temperatura_frigorifico.repetir=setInterval(function(){temperatura_frigorifico.nuevo_valor_aleatorio()},1000);
}
|
Posteaza un comentariu