Genera e modifica grafica SVG dei dati provenienti da sensori collegati all'IoT con JavaScript
In quest'ultima parte della serie di articoli sul disegno grafica con dati provenienti da sensori collegati all'Internet of Things, è tempo di parlare di come generare o modificare con JavaScript disegni in formato SVG e alcuni degli elementi HTML che fungono da contenitore o che presentano informazioni complementari alla grafica.
Si suppone che gli utenti target di questo tutorial formino un profilo di programmazione elettronica e informatica. microcontrollori, potrebbero non avere familiarità HTML, CSS o SVG; Per questo motivo nelle puntate precedenti è stata fatta una breve introduzione al linguaggio o alla tecnologia corrispondente. In quest'ultima parte l'approccio è un po' diverso, poiché i lettori sicuramente sanno programmare, è possibile che utilizzino il linguaggio C++ cosa come JavaScript, condivide la sintassi di base con C e può essere utilizzato come riferimento per tralasciare la maggior parte dei concetti di programmazione di base e concentrarsi quindi sulle differenze e sull'uso specifico che ci interessa creare la grafica dei sensori nell'IoT.
Il nome dà un indizio sulla prima differenza: JavaScript È un linguaggio di programmazione copione (trattino) e come tale lo è interpretato, non è necessario compilarlo; il contesto in cui il copione (un browser web, ad esempio) leggerà, tradurrà ed eseguirà gli ordini. Per essere precisi, nella maggior parte dei casi esiste a compilazione runtime (JIT), ma per il processo di scrittura del codice JavaScript Non ci riguarda, scriviamo semplicemente il codice e può funzionare.
Il nome contiene anche la prima confusione: JavaScript non ha la minima relazione con Java. Inizialmente, quando è stato sviluppato Netscape per il suo browser si chiamava prima Mocha e poi il meno confuso LiveScript. Dopo la sua implementazione di successo nei browser, e trascendendoli, è stato standardizzato come ECMAScript (A ECMA-262, versione 6 al momento in cui scrivo) di divenire neutrale rispetto ai browser che lo implementano. Attualmente esiste anche uno standard ISO dalla versione 5, 2011 (ISO / IEC 16262: 2011 al momento della stesura dell'articolo)
Variabili, tipi di dati di base e oggetti in JavaScript
A differenza di quanto avviene, ad esempio, in C++, en JavaScript tipo di dati non incluso quando si dichiara una variabile ed inoltre il tipo associato ad una variabile non è fisso, è possibile assegnare un valore di tipo diverso durante l'esecuzione del programma.
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
|
Nell'esempio precedente si dichiara la variabile "cosa" (senza indicare il tipo di dato), poi si assegnano dati di tipo diverso e si consultano con typeof
il tipo quello JavaScript che ha interpretato. Per eseguire il debug del codice puoi scriverlo nella console di ispezione del browser web (che non influirà sulla presentazione del web) con console.log()
.
Per forzare la conversione dei dati in un tipo specifico, in particolare da testo a numerico, è possibile utilizzare funzioni come parseInt()
o parseFloat()
che vengono convertiti rispettivamente in numeri interi o in virgola mobile. Si può fare la conversione opposta String()
, anche se è improbabile che sia necessario poiché in genere è sufficiente la conversione automatica. Con parseFloat()
Ad esempio, puoi ottenere il valore di una proprietà di una pagina web, come la larghezza o l'altezza di un oggetto, che include unità; In questo modo, l'espressione parseFloat("50px");
restituirà 50, un valore numerico, come risultato.
En JavaScript non c'è distinzione tra virgolette doppie e singole; Il tipo di dati in entrambi i casi è string
e ciascuno di essi può includere l'altro senza la necessità di codici di escape.
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
|
Nell'esempio precedente si può vedere che una variabile, quando è stata dichiarata (esiste) ma non le è stato assegnato alcun valore, contiene un tipo di dato non definito (undefined
). Un oggetto non assegnato ha il valore null
; Cioè l'oggetto esiste, ma senza valore; una variabile che fa riferimento ad esso non avrebbe a typeof
undefined
Sino object
. Un oggetto può anche essere vuoto, cioè non nullo ma non avere alcuna proprietà.
a definire un oggetto in JavaScript sono racchiusi tra parentesi graffe ({
y }
) le proprietà o i metodi, separati dal segno dei due punti (:
) nome della proprietà valore della proprietà e tramite virgola (,
) le diverse proprietà. Puoi trovare maggiori informazioni su questo modo di esprimere un oggetto nell'articolo su Formato JSON.
Anche se puoi usare una sintassi che potrebbe portarti a pensare diversamente, en JavaScript Non ci sono classi ma prototipiCioè, affinché un oggetto erediti proprietà e metodi, viene creato un altro oggetto (il prototipo) che gli altri (i figli) usano come riferimento. La sintassi più vicina allo stile di JavaScript utilizzare un prototipo è Object.create
sebbene sia anche possibile (e talvolta utile) utilizzarlo new
come in altri linguaggi orientati agli oggetti.
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));
|
a interrogare se un oggetto è un'istanza di un altro, se lo usi come prototipo, se ne erediti le proprietà, insomma, puoi usarlo instanceof
(creato con new
) O isPrototypeOf
(creato con Object.create
) che valuterà true quando l'oggetto utilizza il prototipo e false quando non lo fa.
Una volta che un oggetto è stato creato utilizzandone un altro come prototipo, cioè una volta che un oggetto è stato istanziato, può esserlo aggiungere nuove proprietà o sovrascrivere le proprietà del prototipo utilizzando la sintassi del punto come in gato.peso=2.5
.
La array in JavaScript Sono diversi da quelli che probabilmente conosci C. Innanzitutto vengono dichiarati senza bisogno di indicarne la lunghezza, solo con i segni di parentesi quadre aperte e chiuse ([
y ]
), i componenti possono essere eterogenei (diversi tipi di dati nello stesso array) e nuovi elementi possono essere aggiunti senza essere vincolati da un limite. Le matrici di JavaScript sono in realtà elenchi (raccolte) di elementi a cui referenziato da un indice numerico o da un nome. Un array può contenere contemporaneamente indici numerici e nomi di elementi, ma è comune utilizzare oggetti (proprietà) per sfruttare il secondo tipo.
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
|
Come si può vedere nell'esempio precedente, per sapere se una variabile corrisponde ad un'istanza di un array (è un oggetto array) si può usare instanceof
, come è già stato utilizzato con oggetti generici o, nelle versioni più recenti di JavaScript si può ricorrere Array.isArray()
Per accedere agli elementi dell'array è possibile utilizzare il suo indice (matriz[7]
) oppure dal nome della struttura con il nome tra parentesi quadre (matriz["nombre"]
) o con la consueta sintassi del punto per gli oggetti (matriz.nombre
). Poiché il nome è una stringa di testo, per comporlo è possibile utilizzare un'espressione, incluse le variabili. Per scorrere un array con proprietà, è possibile utilizzare un ciclo con il formato 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
*/
|
È interessante trattare per il nostro obiettivo l'oggetto Date
, con cui rappresentare e gestire data e ora in JavaScript. L'oggetto può essere istanziato senza dati, quindi prenderà la data e l'ora correnti, oppure può essere creato indicando una data come valore, in millisecondi dal 1 gennaio 1970 (come ad esempio Ora Unix o ora POSIX ma espresso in millisecondi invece che in secondi) o specificando valori separati di anno, mese, giorno, ora...
L'oggetto comprende una serie completa di metodi per interrogare o impostare la data e l'ora:
-
now()
Restituisce la data e l'ora correnti espresse in millisecondi dal 1 gennaio 1970 -
getTime()
|setTime()
Ottiene o modifica, rispettivamente, il valore temporale in millisecondi dal 1 gennaio 1970. UsingvalueOf()
, che è un metodo presente nella maggior parte degli oggetti, si ottiene anche il valore dell'oggetto Date corrispondente, ad esempiogetTime()
con Ora Unix o ora POSIX espresso nel ms. -
getMilliseconds()
|setMilliseconds()
Utilizzato per interrogare o impostare la parte frazionaria in millisecondi dell'oggettoDate
su cui viene eseguito. Se consultato il valore ottenuto è compreso tra 0 e 999 ma si possono assegnare valori maggiori che si accumuleranno nel totale di data e ora quindi, come il resto dei metodi get, serve ad aumentare il valore dell'oggettoDate
(o diminuirlo, se si utilizzano valori negativi). -
getSeconds()
|setSeconds()
Restituisce o modifica, rispettivamente, il valore dei secondi dell'oggettoDate
. -
getMinutes()
|setMinutes()
Utilizzato per consultare o impostare i verbali dell'oggettoDate
. -
getHours()
|setHours()
Permette di consultare o modificare gli orari (da 0 a 23) dell'oggettoDate
. -
getDay()
Restituisce il giorno della settimana per la data, espresso come valore compreso tra 0 e 6 (da domenica a sabato). -
getDate()
|setDate()
Restituisce o modifica il giorno del mese dell'oggettoDate
su cui viene applicato. -
getMonth()
|setMonth()
Utilizzato per consultare o modificare il numero del mese dell'oggettoDate
. -
getFullYear()
|setFullYear()
Interroga o imposta il valore dell'anno sull'oggetto contenente la data e l'ora.
I metodi precedenti di Date
includere una versione UTC poter lavorare direttamente con il tempo universale senza dover fare calcoli intermedi. In tal senso, ad es. getHours()
ha una versione getUTCHours()
o getMilliseconds()
un'alternativa getUTCMilliseconds()
per lavorare alternativamente con l'ora ufficiale (legale) o universale. Con getTimezoneOffset()
Puoi conoscere la differenza che esiste tra l'ora universale e l'ora ufficiale locale.
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;
|
Funzioni JavaScript
Se stai leggendo questo articolo sicuramente sai come programmare. microcontrollori en C o su C++ e conoscere il concetto di funzione. Sebbene l'idea di base sia la stessa, in JavaScript Il modo in cui vengono definiti e utilizzati è leggermente diverso. Tanto per cominciare, è già stato detto: JavaScript Non utilizza esplicitamente i tipi di dati, quindi non è necessario indicarlo quando si definisce la funzione. Da seguire, Non è obbligatorio che una funzione abbia un nome, possono essere anonime. Si possono associare ad una variabile per invocarle ma può anche non essere necessario poiché, a volte, è utile invocarle subito, per cui le parentesi ed i parametri vengono aggiunti dopo la definizione della funzione.
Per definire una funzione, prefisso function
, se applicabile, scrivi il nome, gli argomenti (i parametri passati alla funzione) tra parentesi e il codice che verrà eseguito quando la funzione viene invocata tra parentesi graffe.
1
2
3
4
5
|
function doble(numero)
{
var resultado=numero*2;
return resultado;
}
|
Certamente nell’esempio precedente la variabile “risultato” non serviva affatto, ma è una buona scusa per ricordare il portata variabile, che funziona come previsto: la variabile "risultato" esiste solo all'interno della funzione "double". In JavaScript può anche essere utilizzato let
, invece di var
, per definire l'ambito di una variabile in un contesto di blocco di codice (racchiuso tra parentesi graffe, {
y }
)
Parlando degli oggetti nella sezione precedente mancava qualcosa di fondamentale: sono state definite le proprietà ma non sono stati definiti i metodi. Come previsto, i metodi degli oggetti sono funzioni, non hanno nome e vengono utilizzati (invocati) dal nome (proprietà) assegnato dalla definizione dell'oggetto.
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”);
}
}
|
Nell'esempio precedente esiste già un metodo, "view_temperature", che visualizza il valore della proprietà "current_temperature" attraverso la console. Non è molto utile, ma dà un'idea più completa di come sia la definizione di un oggetto JavaScript.
Per accedere ai metodi di un oggetto (funzioni) alle sue proprietà, utilizzare this
, come nell'esempio precedente alla riga 11, quando si utilizza la proprietà “current_temperature”.
Accedi al Document Object Model (DOM) con JavaScript
Noi di JavaScript Hai accesso al contenuto della pagina web su cui viene eseguito, nonché ad alcuni aspetti del browser che visualizza quella pagina, ma non alle risorse di sistema. La struttura dati che supporta le proprietà e i metodi da cui si accede JavaScript parte dell'oggetto finestra, nello specifico, il contenuto dell'oggetto (il document HTML) corrisponde all'oggetto document
. Anche se talvolta viene utilizzato per chiarezza, non è necessario precedere window ai metodi o alle proprietà per fare riferimento ad essi, è sufficiente, ad esempio, utilizzare document
, non è necessario scrivere il nome dell'oggetto root come in window.document
, purché si faccia riferimento alla finestra corrente.
La forma più utilizzata di trovare un oggetto all'interno del documento HTML È attraverso il metodo getElementById()
, a cui viene passato come argomento l'id indicato in fase di creazione del codice HTML. Da quanto spiegato nei paragrafi precedenti è facile supporre che sia possibile accedere anche ai componenti interni all'oggetto document
utilizzando la sintassi del punto (document.componente
) o parentesi utilizzando sia il nome (document["componente"]
), quelli più utili, come l'indice numerico, difficile da usare e poco pratico quando si accede al contenuto di una pagina web composta manualmente.
Con JavaScript possono essere ottieni l'elemento che contiene un altro elemento (elemento o nodo genitore) consultando il tuo immobile parentNode
o la tua proprietà parentElement
, la differenza è che l'elemento genitore (parentElement
) dell'elemento finale della stringa DOM È nullo (null
) e il nodo genitore (parentNode
) è il documento stesso (document
).
a modificare il contenuto di un elemento HTML, ad esempio quello di un'etichetta <div>
, Può essere usato innerHTML
e per modificarne le proprietà puoi scegliere di assegnargli una classe diversa className
o alterare le sue proprietà individualmente con style
. Consultare lo stile visualizzato da un elemento nella pagina web non è necessariamente utile style
poiché può dipendere da diversi fattori o semplicemente non essere stato esplicitamente specificato. Per verificare lo stile di un elemento finalmente visualizzato sulla pagina web, viene utilizzato il metodo getComputedStyle.
A un elemento del documento HTML È possibile assegnargli diverse classi per determinarne l'aspetto e il comportamento gestire l'elenco delle classi di un oggetto da JavaScript si può ricorrere classList
che offre i metodi add
per aggiungere una nuova classe all'elenco, remove
per rimuoverlo, toggle
per sostituirlo o consultare il contenuto dell'elenco delle classi di un elemento con item
e contains
, che restituisce la classe che occupa una determinata posizione nell'elenco e un valore true
o false
se una determinata classe è o meno nell'elenco.
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”)
}
|
Nell'esempio precedente si trova con getElementById
l'oggetto che vuoi manipolare (un elemento <div>
grazie alla id
), prima di modificare l'aspetto, il contenuto viene eliminato assegnandolo con innerHTML
una stringa di testo vuota, le viene assegnata una nuova classe className
e il suo stile viene modificato con style
a seconda del valore del contenuto (temperatura), cambiando il colore, se applicabile, attraverso la proprietà color
. Una volta stabilito l'aspetto, il valore viene scritto utilizzando nuovamente innerHTML
.
Nella seconda parte dell'esempio precedente (righe da 9 a 19) si accede ad un elemento di codice HTML utilizzando la sintassi document[]
e la proprietà id
dell'elemento per modificare la sua lista di classi con il metodo classList.remove()
e con il metodoclassList.add()
, in base al risultato di diverse query eseguite in esecuzioni condizionali, che confrontano utilizzando classList.contains()
.
Quando succederà fare riferimento ad un elemento HTML alcuni volte in tutto il codice JavaScript, è un po' più efficiente assegnarlo a una variabile oppure usa il suo indice al posto del nome poiché, altrimenti, il metodo che utilizzeresti JavaScript per ottenerlo ogni volta sarebbe necessario cercarne il nome, consumando un po' più tempo che se si accedesse a una variabile.
a aggiungere nuovi oggetti al documento HTML, possono essere creati prima con il metodo createElement
de document
e successivamente incorporarli al resto degli elementi nel punto dell'albero necessario appendChild
. Per creare un oggetto XML, come gli oggetti SVG che usiamo per disegnare il grafico dei sensori IoT, puoi utilizzare createElementNS
(NS per spazio dei nomi). Come spiegato parlando del formato SVG, lo spazio dei nomi che corrisponde ad esso (per la versione corrente) è http://www.w3.org/2000/svg
, a cui dovrebbe essere passato createElementNS
come argomento insieme al tipo di elemento, svg
, in questo caso.
Un in alternativa a innerHTML
per aggiungere testo come contenuto a un elemento del documento HTML è il metodo createTextNode()
dell'oggetto document
. Con questa alternativa puoi creare un nuovo testo (a cui si accede successivamente se assegnato a una variabile) che viene incorporato nell'albero degli oggetti con il metodo appendChild()
. come in alternativa a appendChild()
, che aggiunge il nuovo contenuto alla fine di quello già esistente nel nodo a cui viene aggiunto, puoi utilizzare il metodo insertBefore()
, che aggiunge un nuovo oggetto davanti a uno esistente. Indossare insertBefore()
al posto di appendChild()
fornisce un metodo che serve, ad esempio, a ordinare i nuovi oggetti davanti a quelli esistenti quando un elemento deve stare davanti ad un altro (come in una lista) oppure coprire o essere coperto in una struttura grafica in cui sono presenti elementi più vicini al primo piano o allo sfondo.
Reagire agli eventi con JavaScript
Quando il modo di utilizzare una pagina web come contenitore per i grafici dei sensori connessi all'IoT è stato usato onload
Nell'etichetta <body>
per iniziare a disegnare il grafico. Questa proprietà, associata agli oggetti del codice HTML, si riferisce a Eventi JavaScript. Come già spiegato, esegue una funzione quando la pagina viene caricata. Sebbene sia stato associato al codice HTML per tenerlo più presente, avrebbe potuto essere scritto nel codice JavaScript come body.onload=dibujar;
siendo dibujar
il nome della funzione che dovrebbe essere avviata al caricamento della pagina web.
Nelle versioni più recenti di JavaScript gli eventi possono essere associati alle funzioni utilizzando addEventListener
con il formato objeto.addEventListener(evento,función);
o utilizzando la sintassi objeto.evento=función;
che funziona anche nelle implementazioni precedenti. Per scollegare la funzione associata all'evento, devi removeEventListener
che ha lo stesso formato di addEventListener
.
JavaScript È in grado di reagire a una moltitudine di eventi che possono verificarsi su una pagina web. Ad esempio, può rilevare quando viene fatto clic su un elemento HTML con onmousedown
o quando si fa clic con onclick
, quando si preme un tasto con onkeydown
, agendo sulla barra di scorrimento con onscroll
. Per il nostro scopo ci basta farlo rilevare il caricamento della pagina con onload
e il suo ridimensionamento con onresize
. Associamo questi eventi agli oggetti body
y window
del DOM rispettivamente. Il primo può essere assegnato nel codice HTML, come visto e il secondo all'interno del codice JavaScript all'interno della funzione chiamata dal primo e con il formato window.onresize=redimensionar;
siendo redimensionar
la funzione che verrà chiamata ogni volta che la finestra cambia dimensione.
Esegui dopo un intervallo di tempo
JavaScript ha due risorse per esecuzione differita: setTimeout
, che esegue una funzione dopo un intervallo di tempo e setInterval
che eseguirà una funzione ogni determinato intervallo di tempo. Entrambi i metodi richiedono come parametri (1) la funzione invocata e (2) l'intervallo di tempo espresso in millisecondi. Per interrompere la loro operazione, puoi assegnare il risultato restituito da queste funzioni a variabili e passarle come argomento clearTimeout
oppure a clearInterval
quando non vuoi richiamarli nuovamente (o quando non vuoi che vengano eseguiti per la prima volta) setTimeout
o setInterval
rispettivamente.
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
}
|
Nell'esempio precedente viene introdotto il metodo alert
che serve per esporre un segnale di avvertimento. Sebbene fosse ampiamente utilizzato in passato, attualmente è quasi bandito dal codice JavaScript a causa di quanto sia aggressivo (intrusivo) coprire la pagina web con una finestra di dialogo.
In un programma scritto per a microcontrollore di una piccola serie (come quella del piatto Arduino Uno) è comune utilizzare variabili globali, come nell'esempio precedente in JavaScript, poiché il codice è breve e non particolarmente confuso, perché molte volte le funzioni sono implementate ad hoc e perché l'utilizzo di variabili globali permette di prevedere in modo molto semplice ed intuitivo l'utilizzo della memoria, cosa fondamentale in sistemi con poche risorse . Invece, en JavaScript È prassi comune ridurre al minimo possibile l'uso delle variabili globali. perché non ha bisogno di affrettare l'utilizzo della memoria, poiché funziona normalmente su a CPU con risorse di gran lunga superiori a quelle di a MCU, perché è probabile che coesista con molto codice di terze parti con cui deve interagire senza interferire e poiché è un sistema aperto, non è possibile prevedere il futuro contesto di esecuzione (il programma di un microcontrollore small determina completamente il suo funzionamento senza aggiungere altro codice una volta in funzione) sia perché le dimensioni delle applicazioni potrebbero renderne difficoltosa la lettura se il codice non incapsula il suo funzionamento, rendendo i metodi quanto più autocontenuti possibile.
Operazioni matematiche con l'oggetto JavaScript Math
Nell'oggetto sono raggruppate le operazioni matematiche di calcolo matematico più complesse Math
. Questo oggetto viene utilizzato direttamente, non è necessario istanziarlo per utilizzare i metodi o le proprietà (costanti) che incorpora.
Math.abs(n)
Valore assoluto del parametro nMath.acos(n)
Arcocoseno del parametro n (risultato in radianti)Math.asin(n)
Arcoseno del parametro n (risultato in radianti)Math.atan(n)
Arcotangente del parametro n (risultato in radianti)Math.atan2(n,m)
Arcotangente di n/m (risultato in radianti)Math.ceil(n)
Arrotondare il parametro all'intero più vicinoMath.cos(α)
Coseno del parametro α (α in radianti)Math.E
e numero (≃2.718281828459045)Math.exp(n)
e elevato al parametro n: enMath.floor(n)
Arrotonda per difetto il parametro n all'intero più vicinoMath.log(n)
Logaritmo naturale (base e) del parametro nMath.LN2
Logaritmo naturale (base e) di 2 (≃0.6931471805599453)Math.LN10
Logaritmo naturale (base e) di 10 (≃2.302585092994046)Math.LOG2E
Logaritmo in base 2 di e (≃1.4426950408889634)Math.LOG10E
Logaritmo in base 10 di e (≃0.4342944819032518)Math.max(a,b,c,…)
Valore più grande dell'elenco di parametri passatiMath.min(a,b,c,…)
Valore più piccolo dell'elenco di parametri passatiMath.PI
Numero π (≃3.141592653589793)Math.pow(n,m)
Primo parametro n elevato a secondo parametro m: nmMath.random()
Numero (quasi) casuale compreso tra 0.0 e 1.0Math.round(n)
Arrotondare il parametro n all'intero più vicinoMath.sin(α)
Seno del parametro α (α in radianti)Math.sqrt(n)
Radice quadrata del parametro nMath.SQRT1_2
Radice quadrata di 1/2 (≃0.7071067811865476)Math.SQRT2
Radice quadrata di 2 (≃1.4142135623730951)Math.tan(α)
Tangente del parametro α (α in radianti)
Carica i dati dal server con AJAX
Il metodo seguito per ricavare le informazioni archiviate nell'IoT consiste nel caricare di volta in volta i dati dal server e ridisegnare il grafico con cui vengono rappresentati. Per leggere i dati dal server, viene utilizzata la tecnologia AJAX (JavaScript asincrono e XML) attraverso un oggetto XMLHttpRequest
de JavaScript. La tracciatura del grafico dei dati viene eseguita riutilizzando un oggetto SVG che è già nel codice HTML e che contiene un grafico le cui coordinate vengono modificate per farle corrispondere ai nuovi dati caricati.
Nell'esempio di questa proposta, oltre ad aggiornare il disegno, viene aggiornato anche un testo nella pagina web che riporta per ogni grafico la data e il valore dell'ultimo dato misurato.
Lato server c'è un database che contiene le informazioni che i sensori collegati all'IoT hanno monitorato. Questo database viene letto dalla richiesta dell'oggetto XMLHttpRequest
rispondendo con le informazioni codificate nel file Formato JSON, sebbene il nome del metodo utilizzato suggerisca una relazione con il formato XML.
Nel primo tutorial di polaridad.es sul Archiviazione dei dati dell'IoT Puoi vedere un esempio di infrastruttura per gestire, lato server, le informazioni fornite dai dispositivi connessi all'Internet of Things. In questa serie di articoli un server viene utilizzato come risorsa Apache da cui è possibile utilizzare il linguaggio di programmazione PHP per accedere ad un database MySQL o MariaDB. Sui server utilizzati per supportare l'IoT è molto comune trovare database MongoDB (NoSQL) e il linguaggio di programmazione JavaScript su Node.js come infrastruttura software.
La funzione successiva è responsabile della richiesta dei dati più recenti da uno dei sensori al server. Nella chiamata di funzione, l'oggetto viene utilizzato come argomento JavaScript che supporta i dati disegnati. Se lo stesso grafico rappresenta più valori, ad esempio per cercare visivamente una correlazione, è possibile richiedere al server di restituirne più contemporaneamente, metodo più ottimale per il modo in cui funziona il server. Protocollo 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);
}
|
Nella terza riga dell'esempio precedente viene preparata la query che verrà fatta al server, nella quale verrà passato l'argomento "zona", il cui valore sarà il nome o il codice del luogo monitorato poiché le informazioni sulla Nello stesso database possono coesistere sensori diversi (ad esempio termometri che misurano la temperatura in stanze diverse). Il parametro passato alla funzione precedente, l'oggetto con i dati del grafico, dovrebbe includere una proprietà con il nome della stanza ("name_suffix").
Tra le righe 7 e 14 del codice precedente, l' oggetto XMLHttpRequest
che è memorizzato nella variabile "ajax". Prima di scegliere come creare l'oggetto, si effettua una ricerca window
Se XMLHttpRequest
non era disponibile (cosa che accadeva nelle vecchie versioni di Explorer di Microsoft e sebbene sia molto indietro, serve come esempio di alternative per creare l'oggetto utilizzando la sintassi (più nativa)) Object.create
o new
, simile a quello di altri linguaggi orientati agli oggetti.
Per poter gestire immediatamente la risposta, il codice che la gestisce viene preparato nelle righe da 15 a 26 prima di effettuare la richiesta al server.
La forma di eseguire la query HTTP al server è costituito da aprire una connessione con open
indicando tipologia e pagina (a scelta nome utente e password), preparare le intestazioni del protocollo con setRequestHeader
y inviare la richiesta con send
. L'intestazione HTTP Content-length
dovrai conoscere la lunghezza della query (numero di caratteri) calcolata utilizzando length
.
Quando la richiesta AJAX è pronto, viene eseguita la funzione associata all'evento onreadystatechange
. Invece di assegnare una funzione, nell'esempio precedente viene definita al volo una funzione anonima che gestirà la ricezione dei dati in arrivo dal server. Innanzitutto, alla riga 18, si verifica che lo stato della richiesta sia "finita", che corrisponde al valore 4
della proprietà readyState
, che lo stato è "OK" del Protocollo HTTP (codice 200
) ottenibile dalla proprietà status
e che i dati che sono arrivati lo sono Formato JSON, consultando la proprietà responseType
.
Una volta verificato che lo stato della risposta sia quello previsto, nella riga 20 dell'esempio precedente crea un oggetto con il risultato, convertendo il testo JSON. La risposta prevede che venga restituita una data, questo permette di vedere se il risultato che il server invia era già stato precedentemente rappresentato nel grafico, che viene verificato alla riga 21. Se il dato è nuovo, alla riga 23 La funzione che è responsabile di ridisegnare il grafico con le nuove informazioni.
L'idea nel proporre questo metodo di lettura è che i dati verranno aggiornati molto frequentemente. Se le informazioni presentate corrispondono ad un lungo periodo (come ad esempio le temperature di un giorno o di una settimana), si può implementare una prima richiesta che raccolga tutti i dati disponibili e poi una, simile a quella dell'esempio, che li aggiorni entro il periodo corrispondente.
Genera dati casuali per i test
Quando tutta l'infrastruttura server e client sarà pronta, una funzione come quella della sezione precedente si occuperà di leggere i dati e di disegnare con essi il grafico, ma Nella fase di test potrebbe essere più pratico utilizzare numeri casuali all'interno di un intervallo controllato per vedere se il codice scritto è corretto. La seguente funzione può servire da esempio per ottenere dati durante la creazione dell'applicazione 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);
}
|
Invece di leggere le informazioni da un database, l'esempio sopra le genera in modo casuale e le passa alla funzione incaricata di disegnare il grafico. Il dato inventato è un vettore formato da una data espressa come valore in millisecondi, il momento della registrazione delle informazioni del sensore, e il dato monitorato, che è compreso tra un valore massimo e un valore minimo.
In questo esempio, quando si genera una data, questa può essere ritardata fino a un secondo (1000 millisecondi) rispetto alla data al momento dell'invenzione. COME Math.random()
genera un numero compreso tra 0.0 e 1.0, moltiplicandolo per 1000 produce un numero compreso tra 0 e 1000 che viene poi convertito in un numero intero. Allo stesso modo, il valore si ottiene moltiplicando il numero casuale per l'intervallo (massimo meno minimo) e aggiungendo il minimo.
Disegna il grafico dei sensori IoT con un grafico SVG
Visto che abbiamo visto come possiamo ottenere i valori che vogliamo rappresentare (la temperatura, nell'esempio) e la loro collocazione temporale, che possono essere espressi insieme sotto forma di coordinate, nell'esempio seguente viene mostrata una funzione per disegnare un percorso che unisce quei punti ed eventualmente un'area colorata delimitata da quella linea in alto. Il risultato sarebbe come l'immagine seguente.
L'asse orizzontale (X) del grafico rappresenta il tempo e l'asse verticale (Y) i valori che i sensori collegati all'IoT hanno monitorato. L'intervallo orizzontale è di pochi secondi poiché in questa proposta il grafico viene aggiornato molto frequentemente (ogni secondo, ad esempio) per fornire informazioni quasi in tempo reale sullo stato dei sensori.
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
}
|
Nel codice precedente ci sono due aspetti interessanti, in primo luogo il calcolo che permette adattare l'intervallo di valori rappresentati e in secondo luogo il costruzione di immobili d
che indica le coordinate dei punti sul tracciato (path
).
Per adattare l'intervallo di valori rappresentati, questi vengono spostati da un minimo e scalati in modo che la grandezza visibile corrisponda alla dimensione del grafico. Nel caso dell'orario, l'offset si ottiene sottraendo l'intervallo che si vuole visualizzare all'orario più lungo (data e ora più vicine a quella attuale) (20 secondi nell'esempio). Lo spostamento dei valori di temperatura è quello dell'intervallo inferiore (un grado) meno il valore più basso, in modo che i dati riportati di seguito siano i più simili al valore più basso consentito ma lasciando un margine che ci permetta di apprezzare quanto passato
Il coefficiente che moltiplica i valori temporali per ottenere le coordinate orizzontali del grafico si ottiene dividendo la larghezza totale del grafico (100 unità nell'esempio) per l'intervallo temporale rappresentato (20 secondi nell'esempio). Per ottenere il coefficiente con i valori scalari della temperatura occorre ricordare che l'intervallo rappresentato va da un margine inferiore al valore minimo ad un margine superiore al massimo, un grado in entrambi i casi. In questo modo il coefficiente di scala verticale risulta dividendo l'altezza del grafico (100 unità nell'esempio) per il valore massimo, meno il minimo più il margine superiore e inferiore. Poiché questi valori potrebbero svilupparsi completamente a temperature negative, utilizziamo Math.abs()
utilizzare il valore assoluto della differenza.
La proprietà d
dell'oggetto path
Si costruisce concatenando le coordinate dei punti in un testo. Ogni coppia di coordinate è preceduta da un codice SVG L
, che traccia una linea dalla posizione corrente a un valore assoluto indicato dalle coordinate. I valori X e Y sono separati da virgole e ciascuna operazione SVG è separato da uno spazio dal successivo.
Per avviare il layout, utilizzare il codice M
(spostarsi su una coordinata assoluta). Nel caso del grafico chiuso e riempito si inizia in basso a destra, nel caso del grafico aperto che disegna il profilo dei dati si inizia dall'ultimo valore rappresentato (il più recente). Per completare il layout chiuso, viene utilizzato il codice Z
aggiungendo come ultimo punto quello che ha lo stesso valore di coordinata X dell'ultimo punto della linea e come coordinata Y il valore più piccolo rappresentato.
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
);
}
|
In questo esempio, la funzione dibujar_grafico()
, che è la chiamata al caricamento della pagina, ottiene i valori iniziali da testare (non l'ultimo valore in tempo reale) e prepara l'intervallo in cui verranno visualizzati i dati: 20 secondi (20000 ms) in orizzontale e 15°C in verticale da -5°C a +10°C con margine superiore e inferiore di un grado. Fai due chiamate a actualizar_grafico()
, al primo passaggio true
come argomento, che indica che il grafico deve essere chiuso per rappresentare un'area piena, e alla seconda chiamata passa false
per tracciare la linea. In ogni caso, l'oggetto path
modificato è quello che ha l'aspetto corrispondente, con riempimento e senza bordo nel primo caso e con un certo spessore di linea e senza riempimento nel secondo.
La funzione actualizar_grafico()
lavorare su un oggetto SVG che utilizza il seguente codice come contenitore HTML. L'oggetto SVG contiene due percorsi, uno per disegnare la linea e un altro per disegnare l'area riempita. Quando si carica la pagina web, dall'elemento <body>
la funzione precedente viene chiamata automaticamente, dibujar_grafico()
grazie all'evento 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>
|
Alla riga 10 del codice HTML sopra, nello stile è stabilita una larghezza (per esempio) di 820 px e un'altezza di 150 px (cosa che, nella versione finale, sarà opportuno fare con una classe e un documento CSS). Sembra strano che le righe 13 e 14 definiscano la dimensione dell'oggetto SVG come larghezza e altezza al 100% (che corrisponde meglio alle dimensioni della finestra, 100×100). Come già accennato, lo scopo di ciò è lavorare sempre con dimensioni note e adattare ad esse i valori rappresentati. Le altre alternative sarebbero calcolare ogni volta lo spazio del grafico e poi riadattare i valori o forzare dimensioni fisse del grafico, alle quali il documento dovrà attenersi.
Avendo optato per un grafico le cui dimensioni cambiano a seconda del codice HTML, è necessario includere l'immobile vector-effect
con il valore non-scaling-stroke
per evitare che gli spessori delle linee si deformino quando il grafico non mantiene le proporzioni 1:1 scelte nella pagina web in cui viene visualizzato, come avviene nella proposta precedente.
Per "ritagliare" il grafico e mostrare solo l'area scelta, utilizza viewBox
. In questo caso abbiamo scelto di vedere la parte del grafico che inizia da 0,0 (angolo in alto a sinistra) e misura 100x100 in basso e a destra. La parte del disegno situata in coordinate con valori negativi o superiori a 100 non verrà visualizzata nella pagina web anche se esistono nell'oggetto SVG
Aggiungi nuovi elementi al disegno SVG
Nell'esempio precedente, la funzione actualizar_grafico()
utilizzare un layout SVG a cui viene cambiata la proprietà d
, che è ciò che esprime la catena di coordinate. L'alternativa sarebbe creare l'intero oggetto ogni volta che viene ridisegnato. Il vantaggio della prima opzione è che l'aspetto grafico (come spessore o colore) è definito nel codice HTML, la limitazione è che gli oggetti devono essere creati precedentemente.
Per creare oggetti SVG, utilizzare createElementNS()
, che consente di includere il spazio dei nomi. Nell'esempio seguente viene creato un nuovo oggetto di testo (text
) ed è associato a un elemento SVG che già esiste nel codice HTML del sito web. Una volta creato il nuovo elemento, le sue proprietà vengono assegnate con setAttribute()
e viene aggiunto a SVG con 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 la proporzione degli elementi del disegno
Se hai provato ad etichettare con la funzione dell'esempio della sezione precedente, avrai visto che il testo appare deformato quando la proporzione dell'oggetto sulla pagina web (width
y height
Di codice HTML) non è uguale a quella dell'area rappresentata (viewBox
). Per adattare la proporzione è necessario conoscere le misure dell'oggetto SVG per cui puoi consultare lo stile dell'oggetto, ovvero del contenitore HTML, se l'oggetto SVG trasferire questa proprietà. Assegnazione della proprietà transform
agli oggetti SVG che dipendono dalla proporzione, la deformazione può essere corretta applicando un'operazione di ridimensionamento scale()
in cui il coefficiente in X è diverso da quello in 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 consente di raggruppare più oggetti formando un nuovo elemento composito che supporta anche le proprietà, come oggetti semplici. Per applicare la stessa trasformazione a una serie di oggetti contemporaneamente invece che a ciascun oggetto separatamente, puoi raggrupparli in base a questa risorsa e applicare un'unica proprietà transform
a tutti loro.
Come spiegato quando si parla di Formato SVG, gli elementi di un gruppo sono racchiusi all'interno delle etichette <g>
y </g>
. Per aggiungere da JavaScript elementi ad un gruppo SVG viene utilizzato, come visto nell'esempio precedente, appendChild()
una volta definito il nuovo oggetto.
Per stabilire un'origine quando si applicano le trasformazioni, la proprietà può essere utilizzata sugli oggetti SVG transform-origin
, il cui valore sono le coordinate X e Y del punto da cui inizia la trasformazione. Se non è espressamente indicato un valore per l'origine della trasformazione (nel browser web), viene utilizzato il centro delle coordinate. Sfortunatamente, al momento della stesura di questo articolo, la specifica del comportamento delle trasformazioni utilizzando una fonte diversa da quella predefinita non è omogenea tra i browser e deve essere utilizzata con cautela.
Insieme alla trasformazione della scala con scale
Ce ne sono altri, come la rotazione con rotation
e il movimento con translate
, che offrono a alternativa alla rappresentazione grafica: invece di ottenere nuove coordinate, puoi rappresentarle nel loro spazio e trasformare il grafico per adattarlo al formato in cui desideri rappresentarle.
Aggiungi riferimenti al grafico
Ora che la parte principale del grafico è risolta tracciando i valori con un profilo e un'area riempita, è possibile completarlo con dei riferimenti che ne aiutino la lettura. A titolo di esempio, iniziamo disegnando alcuni riferimenti orizzontali (linee) che segnano i valori massimi e minimi accettabili nonché un valore desiderato. Come spiegato, puoi scegliere di aggiungere gli oggetti al file SVG direttamente dal JavaScript oppure includerli manualmente nel codice HTML e modificarli successivamente con 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();
|
Sembra logico etichettare questi riferimenti orizzontali con un testo che chiarisca il valore che rappresentano. Per evidenziare il testo, puoi utilizzare dei rettangoli che risaltano rispetto allo sfondo e alla grafica. Poiché i testi dovranno essere scalati per compensare la deformazione, potranno essere tutti raggruppati in un oggetto a cui verrà applicata la scala; Il vantaggio principale di farlo in questo modo è quello di poterli modificare in un'unica operazione se il contenitore del grafico (la finestra del browser) viene ridimensionato e cambia quella proporzione che la scala corregge.
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();
|
Ci sono diversi aspetti interessanti nel codice di esempio sopra. Prima di tutto, commenta che le costanti (variabili globali) sono state utilizzate per rendere l'esempio più leggibile per gli utenti che provengono dalla programmazione. microcontrollori en C o su C++. Come si vedrà più avanti, il modo ottimale per programmarlo JavaScript Utilizzerebbero oggetti che conterrebbero questi valori e metodi che gestirebbero i riferimenti in questo esempio o il grafico, in generale, in un sistema di produzione.
D'altra parte, anticipando quello che sarebbe il codice più generico, sono state sviluppate funzioni separate che calcolano i diversi coefficienti che correggono la proporzione del grafico per adattare il testo proporcion_grafico()
, la scala dei valori a seconda del loro intervallo escala()
e un fattore di correzione per le misurazioni note in valore assoluto, come le misurazioni nei riferimenti medida_grafico()
.
La lettura di questo codice dovrebbe aiutare a chiarire il contesto in cui funziona un'applicazione come questa, che disegna grafici in tempo reale e deve essere flessibile per essere presentata in vari contesti grafici (almeno varie dimensioni e proporzioni). Prima di tutto bisogna generare gli oggetti SVG, o "manualmente" nel codice HTML, sia tramite codice JavaScript e in ogni caso è necessario successivamente ottenere riferimenti a questi oggetti per manipolarli JavaScript in modo che si possano disegnare nuovi grafici e la rappresentazione di un grafico già disegnato possa essere adattata a un cambiamento nel mezzo in cui viene presentato.
Un altro riferimento che può aiutare a interpretare facilmente un grafico sono i punti che rappresentano valori specifici (i nodi della retta). In questo esempio, in cui rappresentiamo un'unica magnitudo, la scelta di un simbolo non è fondamentale, ma se si sovrappongono più valori diversi per cercare correlazione, è interessante distinguere, oltre ad utilizzare altre risorse come il colore , disegnando simboli diversi. La grafica utilizzata per il nodo linea deve essere modificata in dimensione e proporzione, come avviene, ad esempio, con i testi, in modo che le sue dimensioni siano assolute e in modo che le sue proporzioni siano mantenute anche se cambiano quelle della scatola che contiene.
Nell'esempio precedente abbiamo già visto come calcolare i diversi coefficienti per riscalare e correggere la proporzione del disegno; Per quanto riguarda come implementare la gestione dei simboli dei nodi o dei vertici del grafo, una possibile soluzione potrebbe essere quella di memorizzare gli oggetti SVG in un vettore e modificarne la posizione quando il grafico viene aggiornato leggendo un nuovo valore, oppure quando viene ridisegnato ridimensionando il contenitore. Nel primo caso bisognerebbe modificare la sua posizione e nel secondo la sua proporzione con la proprietà transform
e il valore di scale
. Il codice seguente è una modifica della funzione actualizar_grafico()
includere il riposizionamento dei simboli dei vertici del grafico.
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);
}
}
|
Modifiche apportate alla funzione actualizar_grafico()
per ottenere la nuova funzione actualizar_grafico_puntos()
Sono quelli evidenziati nel codice dell'esempio precedente. Innanzitutto, nella riga 5, prendiamo un vettore di oggetti SVG come parametro. Questo vettore conterrà i simboli che dovranno essere riposizionati nei nuovi nodi del grafico.
Nelle righe 39 e 40 vengono assegnate le nuove coordinate del centro, cx
y cy
, a quelli dei valori che vengono rappresentati. Se il simbolo non fosse basato sul centro, probabilmente sarà necessario aggiungere un offset cx
metà della larghezza e dentro cy
di metà altezza per riposizionarli esattamente sul nodo del grafico.
Nelle righe da 57 a 61, i punti che corrispondono a coordinate non disegnate perché tagliati dal bordo sinistro vengono riposizionati all'esterno del grafico. La coordinata di cy
a zero e quello di cx
a qualsiasi numero negativo (maggiore del punto stesso) in modo che non venga visualizzato quando tagliato, come la parte sinistra del grafico, dalla finestra del SVG.
Gestisci il grafico da un oggetto con JavaScript
Tutte le operazioni fin qui spiegate possono essere integrate in un oggetto per gestire il grafico con uno stile più tipico delle nuove versioni di JavaScript. Questa alternativa di implementazione ha l'ulteriore vantaggio di semplificare l'incorporazione di più grafici, di valori diversi, sulla stessa pagina web.
Prima di discutere l'implementazione, esaminiamo i modi più comuni per creare oggetti con JavaScript e alcune peculiarità delle funzioni che interessano la proposta di disegno grafico dei sensori IoT.
È stato già spiegato che il nuovo modo di creare oggetti in JavaScript (disponibile dalla versione 5 di ECMAScript) consiste nell'utilizzare Object.create
, a cui bisognerebbe abituarsi al posto del "classico" new
, che ovviamente funziona ancora correttamente, sebbene il suo scopo sia più quello di simulare lo stile dei linguaggi con oggetti basati su classi (JavaScript basa la creazione di oggetti su prototipi) piuttosto che un'alternativa funzionante.
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();
}
|
Il codice precedente consente di ricordare le differenze tra la creazione degli oggetti con Object.create
oppure con new
. Serve anche a sottolinearlo, pur con la funzione con cui viene creato l'oggetto new
può essere ovunque nel codice, l'oggetto deve già esistere prima di poterne creare un'istanza Object.create
(L'oggetto ES5_Object non è una funzione).
Nelle righe 3 e 4, per impostare un valore predefinito per le proprietà nella funzione con cui crea l'oggetto new
, a ciascuna proprietà viene assegnato il valore dell'argomento corrispondente o (||
), se non sono stati passati argomenti, ovvero se non sono definiti (undefined
), poiché tale circostanza viene valutata come false
, viene assegnato il valore predefinito.
Il contesto in cui viene eseguita una funzione JavaScript solleva due questioni che è importante tenere a mente e che possono anche creare confusione quando si utilizza questo linguaggio di programmazione dopo aver lavorato con altri, come ad esempio C o C++, nel nostro caso. Il contesto include le variabili definite nell'ambito della funzione (e quelle globali) che, tra l'altro, solleva un concetto interessante, le "chiusure" che stabiliscono un intero stile di programmazione in JavaScript. Detto questo, c'era da aspettarselo this
, che fa riferimento all'oggetto quando utilizzato all'interno del codice che lo definisce, viene mantenuto il contesto di esecuzione in cui è stato definito ma quello che utilizza è il contesto da cui viene chiamata la funzione. Questo comportamento è trasparente nella maggior parte dei casi, ma ci sono due circostanze in cui può creare confusione: una funzione definita all'interno di un'altra funzione e un metodo chiamato da un oggetto evento. 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
*/
|
Quando si esegue il codice precedente, nella console viene visualizzato il testo commentato alla fine. Le due linee contrassegnate riflettono un comportamento che può creare confusione: il contesto di esecuzione della funzione probar_dentro()
non probar()
, come ci si potrebbe aspettare, ma window
, che mostra le variabili globali e non le proprietà con lo stesso nome. Se non desideri questo comportamento, crea semplicemente una variabile nella funzione di livello più alto e assegnala a this
, come nel codice seguente.
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
*/
|
Per controllare il contesto di esecuzione quando un metodo viene chiamato da un evento window
, ad esempio ridimensionando la finestra del browser, altra peculiarità di JavaScript: la possibilità di programmare "fabbriche di funzioni", cioè funzioni che generano altre funzioni, restituendole con 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
|
Nel codice di esempio sopra, il metodo llamar()
degli oggetti Contexto
Non fa il lavoro ma restituisce una funzione anonima che se ne occupa. Per verificare che tutto funzioni come previsto esiste una variabile globale con lo stesso nome della proprietà che la funzione visualizza nella console; Se il contesto è corretto verrà visualizzato il valore della proprietà e non quello della variabile globale.
JavaScript Prova a correggere i segni del punto e virgola che omettiamo alla fine delle frasi. Ciò consente uno stile di scrittura rilassato ma è un’arma a doppio taglio che deve essere trattata con attenzione. Nella maggior parte dei casi, per evitare gli effetti indesiderati che ciò produce nelle espressioni che occupano più righe, è possibile utilizzare parentesi o precedere il modo in cui JavaScript interpreterà il codice; Ecco perché la riga 8 dell'esempio include function
dietro return
, se avessi usato un'altra riga il significato sarebbe molto diverso. A mio avviso la soluzione più leggibile è utilizzare una variabile intermedia (dispensabile) come nella versione seguente; Ovviamente, una volta compreso il comportamento, la decisione spetta al programmatore.
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);
|
Nello stesso senso di valutare un'espressione come una funzione, cioè restituire una funzione e non il valore che la funzione restituisce; alla riga 21 dell'ultimo esempio (era alla riga 19 del precedente) si ferma con clearInterval
la funzione chiamata con setInterval
. Affinché possa agire per 30 secondi, lo stop viene differito con setTimeout
, che a sua volta necessita di una funzione come primo argomento; per fornire l'esecuzione come parametro clearInterval
con la variabile che contiene la chiamata periodica (e non la funzione clearInterval
) è lo scopo per cui viene creata la funzione anonima nell'ultima riga.
La scelta tra scrivere il codice integrando la definizione della funzione, più compatto (come alla riga 21) o utilizzando una variabile ausiliaria, a mio avviso, più leggibile (come alle righe 19 e 20) varia poco nelle prestazioni e dipende più dallo stile e dalla leggibilità per manutenzione.
Per testare il codice, prima di avere i dati sul server, si può utilizzare un generatore di valori casuali nell'intervallo desiderato oppure preparare tabelle con valori controllati che simulano il funzionamento nelle condizioni desiderate. L'esempio seguente utilizza un semplice generatore di dati sull'intera gamma, motivo per cui appaiono un po' esagerati.
Per testare, puoi scarica il codice completo dell'esempio formato da una pagina web scritta in HTML, lo stile CSS e il codice JavaScript. Quest'ultima è la più rilevante, poiché le altre componenti sono solo un supporto minimo, molto semplificate e sono molto più sviluppate negli articoli delle sezioni corrispondenti.
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);
}
|
Invia commento