Gere e modifique gráficos SVG de dados de sensores conectados à IoT com JavaScript
Nesta última parte da série de artigos sobre desenho gráficos com dados de sensores conectados à Internet das Coisas, é hora de falar sobre como gerar ou modificar com JavaScript desenhos em formato SVG e alguns dos elementos HTML que sirvam de container ou que apresentem informações complementares aos gráficos.
Os usuários-alvo deste tutorial devem formar um perfil de eletrônica e programação de computadores. microcontroladores, eles podem não estar familiarizados com HTML, APF o SVG; Por este motivo, nas edições anteriores foi feita uma breve introdução à linguagem ou à tecnologia correspondente. Nesta última parte a abordagem é um pouco diferente, já que os leitores com certeza sabem programar, é possível que utilizando a linguagem C + + que como JavaScript, compartilha sintaxe básica com C e pode ser usado como referência para pular a maior parte dos conceitos básicos de programação e assim focar nas diferenças e no uso específico que nos interessa para criar gráficos de sensores na IoT.
O nome dá uma pista para a primeira diferença: JavaScript É uma linguagem de programação escrita (hífen) e como tal, é interpretado, não há necessidade de compilá-lo; o contexto em que o escrita (um navegador web, por exemplo) irá ler, traduzir e executar as ordens. Para ser mais preciso, na maioria dos casos existe uma compilação em tempo de execução (JIT), mas para o processo de escrita de código JavaScript Isso não nos afeta, simplesmente escrevemos o código e ele pode funcionar.
O nome também contém a primeira confusão: JavaScript não tem a menor relação com Java. Inicialmente, quando foi desenvolvido Netscape para seu navegador, foi chamado primeiro de Mocha e depois de LiveScript, menos confuso. Após sua implementação bem-sucedida em navegadores, e transcendendo-os, foi padronizado como ECMAScript (Para ECMA-262, versão 6 no momento em que este artigo foi escrito) para se tornar neutro em relação aos navegadores que o implementam. Atualmente também existe um padrão ISO da versão 5, 2011 (ISO / IEC 16262:2011 no momento da redação do artigo)
Variáveis, tipos de dados básicos e objetos em JavaScript
Ao contrário do que acontece, por exemplo, em C + +, en JavaScript tipo de dados não incluído ao declarar uma variável e também o tipo associado a uma variável não é fixo, é possível atribuir um valor de tipo diferente ao longo da execução do programa.
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
|
No exemplo anterior, a variável "coisa" foi declarada (sem indicar o tipo de dados), então são atribuídos dados de um tipo diferente e são consultados com typeof
o tipo que JavaScript que ele interpretou. Para depurar o código você pode escrevê-lo no console do inspetor do navegador (o que não afetará a apresentação da web) com console.log()
.
Para forçar a conversão de dados para um tipo específico, especialmente texto para numérico, você pode usar funções como parseInt()
o parseFloat()
que são convertidos em números inteiros ou de ponto flutuante, respectivamente. A conversão oposta pode ser feita com String()
, embora seja improvável que seja necessário, pois a conversão automática geralmente é suficiente. Com parseFloat()
Por exemplo, você pode obter o valor de uma propriedade de página da Web, como a largura ou a altura de um objeto, que inclui unidades; Desta forma, a expressão parseFloat("50px");
retornará 50, um valor numérico, como resultado.
En JavaScript não há distinção entre aspas duplas e simples; O tipo de dados em ambos os casos é string
, e cada um deles pode incluir o outro sem a necessidade de códigos de 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
|
No exemplo anterior pode-se observar que uma variável, quando foi declarada (existe) mas não lhe foi atribuído nenhum valor, contém um tipo de dados indefinido (undefined
). Um objeto não atribuído tem o valor null
; Ou seja, o objeto existe, mas sem valor; uma variável que a referenciasse não teria um typeof
undefined
mas object
. Um objeto também pode estar vazio, ou seja, não ser nulo, mas não ter nenhuma propriedade.
Pára definir um objeto em JavaScript estão entre colchetes ({
y }
) as propriedades ou métodos, separados pelo sinal de dois pontos (:
) nome da propriedade valor da propriedade e por vírgula (,
) as diferentes propriedades. Você pode encontrar mais informações sobre esta forma de expressar um objeto no artigo sobre o Formato JSON.
Embora você possa usar uma sintaxe que possa levá-lo a pensar de outra forma, en JavaScript Não existem classes, mas protótiposOu seja, para que um objeto herde propriedades e métodos, é criado outro objeto (o protótipo) que os demais (os filhos) utilizam como referência. A sintaxe mais próxima do estilo de JavaScript usar um protótipo é Object.create
embora também seja possível (e às vezes útil) usar new
como em outras linguagens orientadas a objetos.
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));
|
Pára consultar se um objeto é uma instância de outro, se você usar como protótipo, se herdar suas propriedades, enfim, você pode usar instanceof
(criado com new
) Ou isPrototypeOf
(criado com Object.create
) que será avaliado como verdadeiro quando o objeto usar o protótipo e falso quando não o fizer.
Uma vez criado um objeto usando outro como protótipo, ou seja, uma vez instanciado um objeto, ele pode ser adicione novas propriedades ou substitua as propriedades do protótipo usando sintaxe de ponto como em gato.peso=2.5
.
La matrizes em JavaScript Eles são diferentes daqueles que você provavelmente conhece C. Para começar, são declarados sem a necessidade de indicar seu comprimento, apenas com os sinais de abertura e fechamento de colchetes ([
y ]
), os componentes podem ser heterogêneos (diferentes tipos de dados no mesmo array) e novos elementos podem ser adicionados sem ficarem restritos a um limite. As matrizes de JavaScript são na verdade listas (coleções) de elementos aos quais referenciado por um índice numérico ou por um nome. Um array pode conter simultaneamente índices numéricos e nomes de elementos, mas é comum usar objetos (propriedades) para explorar o segundo 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
|
Como pode ser visto no exemplo anterior, para saber se uma variável corresponde a uma instância de um array (é um objeto array) você pode usar instanceof
, como já foi usado com objetos genéricos ou, em versões mais recentes do JavaScript pode-se recorrer a Array.isArray()
Para acessar os elementos do array você pode usar seu índice (matriz[7]
) ou pelo nome da propriedade com o nome entre colchetes (matriz["nombre"]
) ou com a sintaxe de ponto usual para objetos (matriz.nombre
). Como o nome é uma string de texto, uma expressão, incluindo variáveis, pode ser usada para compô-lo. Para percorrer um array com propriedades, um loop com o formato pode ser usado 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 para o nosso objetivo tratar o objeto Date
, com o qual representa e gerencia data e hora em JavaScript. O objeto pode ser instanciado sem dados, portanto ele usará a data e hora atuais, ou pode ser criado indicando uma data como valor, seja em milissegundos desde 1º de janeiro de 1970 (como Hora Unix ou hora POSIX mas expresso em milissegundos em vez de segundos) ou especificando valores separados de ano, mês, dia, hora...
O objeto inclui uma série completa de métodos para consultar ou definir a data e hora:
-
now()
Retorna a data e hora atuais expressas em milissegundos desde 1º de janeiro de 1970 -
getTime()
|setTime()
Obtém ou altera, respectivamente, o valor do tempo em milissegundos desde 1º de janeiro de 1970. UsandovalueOf()
, que é um método presente na maioria dos objetos, também é obtido o valor do objeto Date correspondente, comogetTime()
com Hora Unix ou hora POSIX expresso em ms. -
getMilliseconds()
|setMilliseconds()
Usado para consultar ou definir a parte fracionária de milissegundos do objetoDate
em que é executado. Se consultado, o valor obtido está entre 0 e 999, mas podem ser atribuídos valores maiores que serão acumulados na data e hora total, portanto, como os demais métodos get, serve para aumentar o valor do objetoDate
(ou diminua, se forem usados valores negativos). -
getSeconds()
|setSeconds()
Retorna ou altera, respectivamente, o valor dos segundos do objetoDate
. -
getMinutes()
|setMinutes()
Utilizado para consultar ou acertar os minutos do objetoDate
. -
getHours()
|setHours()
Permite consultar ou modificar os horários (de 0 a 23) do objetoDate
. -
getDay()
Retorna o dia da semana da data, expresso como um valor de 0 a 6 (domingo a sábado). -
getDate()
|setDate()
Retorna ou altera o dia do mês do objetoDate
em que é aplicado. -
getMonth()
|setMonth()
Utilizado para consultar ou modificar o número do mês do objetoDate
. -
getFullYear()
|setFullYear()
Consulta ou define o valor do ano no objeto que contém a data e a hora.
Os métodos anteriores de Date
incluir uma versão UTC poder trabalhar diretamente com a hora universal sem ter que fazer cálculos intermediários. Nesse sentido, por exemplo, getHours()
tem uma versão getUTCHours()
o getMilliseconds()
uma alternativa getUTCMilliseconds()
trabalhar alternativamente com a hora oficial (legal) ou universal. Com getTimezoneOffset()
Você pode saber a diferença que existe entre a hora universal e a hora oficial local.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
var dia_semana=[“domingo”,“lunes”,“martes”,“miércoles”,“jueves”,“viernes”,“sábado”];
var nombre_mes=[“enero”,“febrero”,“marzo”,“abril”,“mayo”,“junio”,“julio”,“agosto”,“septiembre”,“octubre”,“noviembre”,“diciembre”];
var digitos_hora;
var hoy=new Date();
var texto_hoy=“”;
texto_hoy+=“Hoy es “;
texto_hoy+=dia_semana[hoy.getDay()];
texto_hoy+=“, “;
texto_hoy+=hoy.getDate();
texto_hoy+=” de “;
texto_hoy+=nombre_mes[hoy.getMonth()];
texto_hoy+=” de “;
texto_hoy+=hoy.getFullYear();
texto_hoy+=” y son las “;
digitos_hora=hoy.getHours();
texto_hoy+=digitos_hora>9?digitos_hora:“0”+digitos_hora;
texto_hoy+=“:”;
digitos_hora=hoy.getMinutes();
texto_hoy+=digitos_hora>9?digitos_hora:“0”+digitos_hora;
texto_hoy+=“:”;
digitos_hora=hoy.getSeconds();
texto_hoy+=digitos_hora>9?digitos_hora:“0”+digitos_hora;
|
Funções JavaScript
Se você está lendo isso com certeza sabe programar. microcontroladores en C o em C + + e conhecer o conceito de função. Embora a ideia básica seja a mesma, em JavaScript A forma como são definidos e usados é um pouco diferente. Para começar, já foi dito, JavaScript Ele não usa tipos de dados explicitamente, então você não precisa indicá-lo ao definir a função. Para seguir, Não é obrigatório que uma função tenha nome, elas podem ser anônimas. Eles podem ser associados a uma variável para invocá-los, mas também pode não ser necessário, pois, às vezes, é útil invocá-los imediatamente, para o que os parênteses e parâmetros são adicionados após a definição da função.
Para definir uma função, prefixe function
, se aplicável, escreva o nome, os argumentos (os parâmetros passados para a função) entre parênteses e o código que será executado quando a função for invocada entre colchetes.
1
2
3
4
5
|
function doble(numero)
{
var resultado=numero*2;
return resultado;
}
|
Certamente, no exemplo anterior a variável “resultado” não era necessária, mas é uma boa desculpa para lembrar o escopo variável, que funciona como você espera: a variável "resultado" só existe dentro da função "duplo". Em JavaScript também pode ser usado let
, ao invés de var
, para definir o escopo de uma variável para um contexto de bloco de código (entre chaves, {
y }
)
Ao falar sobre objetos na seção anterior, faltava algo fundamental: as propriedades foram definidas, mas os métodos não foram definidos. Como esperado, métodos de objeto são funções, eles não têm nome e são usados (invocados) a partir do nome (propriedade) atribuído pela definição do objeto.
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”);
}
}
|
No exemplo anterior já existe um método, "view_temperature", que exibe o valor da propriedade "current_temperature" através do console. Não é muito útil, mas dá uma ideia mais completa de como é a definição de um objeto em JavaScript.
Para acessar os métodos de um objeto (funções) às suas propriedades, use this
, como no exemplo anterior da linha 11, ao utilizar a propriedade “temperatura_atual”.
Acesse o Document Object Model (DOM) com JavaScript
Na JavaScript Você tem acesso ao conteúdo da página da Web em que ele é executado, bem como a alguns aspectos do navegador que exibe essa página, embora não aos recursos do sistema. A estrutura de dados que suporta as propriedades e métodos acessados de JavaScript parte do objeto janela, especificamente, o conteúdo do objeto (o documento HTML) corresponde ao objeto document
. Embora às vezes seja usado para maior clareza, não é necessário preceder window aos métodos ou propriedades para se referir a eles, basta, por exemplo, usar document
, não há necessidade de escrever o nome do objeto raiz como em window.document
, desde que a janela atual seja referenciada.
A forma mais utilizada de encontrar um objeto dentro do documento HTML É através do método getElementById()
, para o qual o id que foi indicado na criação do código é passado como argumento HTML. Pelo que foi explicado nas seções anteriores, é fácil assumir que você também pode acessar os componentes dentro do objeto document
usando sintaxe de ponto (document.componente
) ou colchetes usando o nome (document["componente"]
), os mais úteis, como o índice numérico, de difícil utilização e pouco prático no acesso ao conteúdo de uma página web composta manualmente.
Com JavaScript pode ser obtenha o elemento que contém outro elemento (elemento ou nó pai) consultando seu imóvel parentNode
ou sua propriedade parentElement
, a diferença é que o elemento pai (parentElement
) do elemento final da string DOM É nulo (null
) e o nó pai (parentNode
) é o próprio documento (document
).
Pára modificar o conteúdo de um elemento HTML, por exemplo, o de um rótulo <div>
, Isso pode ser usado innerHTML
e para alterar suas propriedades você pode escolher atribuir a ele uma classe diferente com className
ou alterar suas propriedades individualmente com style
. Consultar o estilo exibido por um elemento na página web não é necessariamente útil style
uma vez que pode depender de vários fatores ou simplesmente não ter sido explicitamente especificado. Para verificar o estilo de um elemento finalmente exibido na página web, o método getComputedStyle é usado.
Para um elemento de documento HTML Várias classes podem ser atribuídas a ele para determinar sua aparência e comportamento, para gerenciar a lista de classes de um objeto de JavaScript pode-se recorrer a classList
que oferece os métodos add
para adicionar uma nova classe à lista, remove
para removê-lo, toggle
substituí-lo ou consultar o conteúdo da lista de classes de um elemento com item
e com contains
, que retorna a classe que ocupa determinada posição na lista e um valor true
o false
se uma determinada classe está ou não na lista.
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”)
}
|
No exemplo anterior ele está localizado com getElementById
o objeto que você deseja manipular (um elemento <div>
sua id
), antes de alterar a aparência, o conteúdo é excluído atribuindo-se com innerHTML
uma string de texto vazia, é atribuída uma nova classe com className
e seu estilo é modificado com style
dependendo do valor do conteúdo (temperatura), alterando a cor, se for o caso, através da propriedade color
. Uma vez estabelecido o aspecto, o valor é escrito usando novamente innerHTML
.
Na segunda parte do exemplo acima (linhas 9 a 19) um elemento de código é acessado HTML usando a sintaxe document[]
e a propriedade id
do elemento para alterar sua lista de classes com o método classList.remove()
e com o métodoclassList.add()
, com base no resultado de diversas consultas realizadas em execuções condicionais, que comparam usando classList.contains()
.
Quando vai referir-se a um elemento HTML vários vezes ao longo do código JavaScript, é um pouco mais eficiente atribuí-lo a uma variável ou use seu índice em vez do nome, pois, caso contrário, o método que você usaria JavaScript obtê-la a cada vez exigiria a busca por seu nome, consumindo um pouco mais de tempo do que se uma variável fosse acessada.
Pára adicione novos objetos ao documento HTML, eles podem ser criados primeiro com o método createElement
de document
e posteriormente incorporá-los ao resto dos elementos no ponto da árvore que for necessário com appendChild
. Para criar um objeto XML, como objetos SVG que usamos para desenhar o gráfico dos sensores IoT, você pode usar createElementNS
(NS para espaço de nome). Conforme explicado ao falar sobre o formato SVG, o namespace que corresponde a ele (para a versão atual) é http://www.w3.org/2000/svg
, que deve ser passado para createElementNS
como um argumento junto com o tipo de elemento, svg
, neste caso.
Uma alternativa para innerHTML
para adicionar texto como conteúdo a um elemento do documento HTML é o método createTextNode()
do objeto document
. Com esta alternativa você pode criar novo texto (que é acessado posteriormente se for atribuído a uma variável) que é incorporado na árvore de objetos com o método appendChild()
. Curtir alternativa para appendChild()
, que adiciona o novo conteúdo ao final do que já existe no nó ao qual foi adicionado, você pode usar o método insertBefore()
, que adiciona um novo objeto na frente de um existente. Vestir insertBefore()
em vez de appendChild()
fornece um método que serve, por exemplo, para classificar novos objetos na frente dos existentes quando um elemento deve estar na frente de outro (como em uma lista) ou cobrir ou ser coberto por uma estrutura gráfica na qual existem elementos mais próximos do primeiro plano ou do fundo.
Reaja a eventos com JavaScript
Quando o caminho de usar uma página da web como contêiner para gráficos de sensores conectados à IoT utilizou-se onload
Na etiqueta <body>
para começar a desenhar o gráfico. Esta propriedade, associada aos objetos de código HTML, refere-se a eventos JavaScript. Como já explicado, ele executa uma função quando a página é carregada. Embora tenha sido associado ao código HTML para mantê-lo mais em mente, poderia ter sido escrito no código JavaScript como body.onload=dibujar;
ser dibujar
o nome da função que deve ser iniciada quando a página da web for carregada.
Nas versões mais recentes JavaScript eventos podem ser associados a funções usando addEventListener
com o formato objeto.addEventListener(evento,función);
ou usando a sintaxe objeto.evento=función;
que funciona também em implementações mais antigas. Para desvincular a função associada ao evento, você tem removeEventListener
que tem o mesmo formato que addEventListener
.
JavaScript É capaz de reagir a uma infinidade de eventos que podem ocorrer em uma página web. Por exemplo, ele pode detectar quando um elemento é clicado HTML com onmousedown
, ou quando clicado com onclick
, quando uma tecla é pressionada com onkeydown
, operando a barra de rolagem com onscroll
. Para o nosso propósito, basta-nos detectar o carregamento da página com onload
e seu redimensionamento com onresize
. Associaremos esses eventos aos objetos body
y window
De DOM respectivamente. O primeiro pode ser atribuído no código HTML, como visto e o segundo dentro do código JavaScript dentro da função chamada pelo primeiro e com o formato window.onresize=redimensionar;
ser redimensionar
a função que será chamada toda vez que a janela mudar de tamanho.
Executar após um intervalo de tempo
JavaScript tem dois recursos para execução diferida: setTimeout
, que executa uma função após um intervalo de tempo e setInterval
que executará uma função a cada determinado intervalo de tempo. Ambos os métodos requerem como parâmetros (1) a função invocada e (2) o intervalo de tempo expresso em milissegundos. Para interromper sua operação, você pode atribuir o resultado retornado por essas funções a variáveis e passá-las como argumento para clearTimeout
ou clearInterval
quando você não deseja invocá-los novamente (ou quando não deseja que eles sejam executados pela primeira vez) setTimeout
o setInterval
respectivamente.
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
}
|
No exemplo anterior o método é introduzido alert
que serve para exibir um sinal de alerta. Embora tenha sido amplamente utilizado no passado, atualmente está quase banido do código JavaScript por causa de quão agressivo (intrusivo) é cobrir a página da web com uma caixa de diálogo.
Em um programa escrito para um microcontrolador de uma pequena série (como a que está no prato Arduino Uno) é comum usar variáveis globais, como no exemplo anterior em JavaScript, uma vez que o código é breve e não particularmente confuso, porque muitas vezes as funções são implementadas ad hoc e porque a utilização de variáveis globais permite prever o uso da memória de uma forma muito simples e intuitiva, o que é crítico em sistemas com poucos recursos . Em vez de, en JavaScript É comum reduzir ao mínimo possível o uso de variáveis globais. porque não precisa apressar o uso da memória, pois funciona normalmente em um CPU com recursos muito superiores aos de uma MCU, porque é provável que coexista com muitos códigos de terceiros com os quais deve trabalhar sem interferir e por se tratar de um sistema aberto, o contexto de execução futuro não pode ser previsto (o programa de um microcontrolador small determina completamente seu funcionamento sem adicionar mais código uma vez em operação) e porque as dimensões das aplicações podem dificultar a leitura se o código não encapsular seu funcionamento, tornando os métodos o mais autocontidos possível.
Operações matemáticas com o objeto JavaScript Math
As operações matemáticas de cálculos matemáticos mais complicados são agrupadas no objeto Math
. Este objeto é utilizado diretamente, não é necessário instanciá-lo para utilizar os métodos ou propriedades (constantes) que incorpora.
Math.abs(n)
Valor absoluto do parâmetro nMath.acos(n)
Arco cosseno do parâmetro n (resultado em radianos)Math.asin(n)
Arco seno do parâmetro n (resultado em radianos)Math.atan(n)
Arco tangente do parâmetro n (resultado em radianos)Math.atan2(n,m)
Arctangente de n/m (resultado em radianos)Math.ceil(n)
Arredonde o parâmetro para o número inteiro mais próximoMath.cos(α)
Cosseno do parâmetro α (α em radianos)Math.E
número e (≃2.718281828459045)Math.exp(n)
e elevado ao parâmetro n: enMath.floor(n)
Arredonde o parâmetro n para o número inteiro mais próximoMath.log(n)
Logaritmo natural (base e) do parâmetro nMath.LN2
Logaritmo natural (base e) de 2 (≃0.6931471805599453)Math.LN10
Logaritmo natural (base e) de 10 (≃2.302585092994046)Math.LOG2E
Logaritmo de base 2 de e (≃1.4426950408889634)Math.LOG10E
Logaritmo de base 10 de e (≃0.4342944819032518)Math.max(a,b,c,…)
Maior valor da lista de parâmetros passadosMath.min(a,b,c,…)
Menor valor da lista de parâmetros passadosMath.PI
Número π (≃3.141592653589793)Math.pow(n,m)
Primeiro parâmetro n elevado ao segundo parâmetro m: nmMath.random()
(Quase) número aleatório entre 0.0 e 1.0Math.round(n)
Arredonde o parâmetro n para o número inteiro mais próximoMath.sin(α)
Seno do parâmetro α (α em radianos)Math.sqrt(n)
Raiz quadrada do parâmetro nMath.SQRT1_2
Raiz quadrada de 1/2 (≃0.7071067811865476)Math.SQRT2
Raiz quadrada de 2 (≃1.4142135623730951)Math.tan(α)
Tangente do parâmetro α (α em radianos)
Carregar dados do servidor com AJAX
O método seguido para desenhar as informações armazenadas na IoT consiste em carregar os dados do servidor de tempos em tempos e redesenhar o gráfico com o qual eles são representados. Para ler os dados do servidor, é usada tecnologia AJAX (JavaScript e XML assíncronos) através de um objeto XMLHttpRequest
de JavaScript. A plotagem do gráfico de dados é feita reutilizando um objeto SVG que já está no código HTML e que contém um gráfico cujas coordenadas são modificadas para que correspondam aos novos dados carregados.
No exemplo desta proposta, além de atualizar o desenho, também é atualizado um texto na página web que mostra a data e o valor dos últimos dados medidos para cada gráfico.
No lado do servidor existe um banco de dados que contém as informações que os sensores conectados à IoT estão monitorando. Este banco de dados é lido pela solicitação do objeto XMLHttpRequest
respondendo com informações codificadas no Formato JSON, embora o nome do método utilizado sugira uma relação com o formato XML.
No primeiro tutorial polaridad.es sobre o Armazenamento de dados IoT Você pode ver um exemplo de infraestrutura para gerenciar, do lado do servidor, as informações fornecidas pelos dispositivos conectados à Internet das Coisas. Nesta série de artigos, um servidor é usado como recurso apache a partir do qual você pode usar a linguagem de programação PHP acessar um banco de dados MySQL o MariaDB. Em servidores utilizados para suporte à IoT é muito comum encontrar bancos de dados MongoDB (NoSQL) e a linguagem de programação JavaScript em Node.js como infraestrutura de software.
A próxima função é responsável por solicitar os dados mais recentes de um dos sensores do servidor. Na chamada de função, o objeto é usado como argumento JavaScript que suporta os dados que são desenhados. Se o mesmo gráfico representar vários valores, por exemplo para procurar visualmente uma correlação, pode-se fazer uma solicitação ao servidor para retornar vários valores simultaneamente, um método mais ideal devido à forma como o servidor funciona. Protocolo 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);
}
|
Na terceira linha do exemplo anterior é preparada a consulta que será feita ao servidor, na qual será passado o argumento “zona”, cujo valor será o nome ou código do local monitorado desde que informações sobre o área pode coexistir no mesmo banco de dados sensores diferentes (por exemplo, termômetros que medem a temperatura em salas diferentes). Espera-se que o parâmetro passado para a função anterior, o objeto com os dados do gráfico, inclua uma propriedade com o nome da sala (“name_suffix”).
Entre as linhas 7 e 14 do código anterior, o objeto XMLHttpRequest
que é armazenado na variável "ajax". Antes de escolher como criar o objeto, você pesquisa window
por si XMLHttpRequest
não estava disponível (algo que acontecia nas versões antigas do explorer da Microsoft e embora esteja muito atrás, serve como exemplo de alternativas para criar o objeto usando a sintaxe (mais nativa)) Object.create
o new
, semelhante ao de outras linguagens orientadas a objetos.
Para poder gerenciar a resposta imediatamente, o código que a trata é preparado nas linhas 15 a 26 antes de fazer a solicitação ao servidor.
O caminho de faça a consulta HTTP para o servidor consiste em abrir uma conexão com open
indicando tipo e página (opcionalmente nome de usuário e senha), prepare os cabeçalhos do protocolo com setRequestHeader
y envie o pedido com send
. O cabeçalho HTTP Content-length
você precisará saber o comprimento da consulta (número de caracteres) que é calculado usando length
.
Quando o pedido AJAX está pronto, a função associada ao evento é executada onreadystatechange
. Em vez de atribuir uma função, no exemplo anterior é definida instantaneamente uma função anônima que gerenciará a recepção dos dados que chegam do servidor. Primeiramente, na linha 18, verifica-se que o status da solicitação é “finalizado”, o que corresponde ao valor 4
da propriedade readyState
, que o status é "OK" do Protocolo HTTP (código 200
) que pode ser obtido na propriedade status
e que os dados que chegaram são Formato JSON, consultando o imóvel responseType
.
Depois de verificado que o status da resposta é o esperado, na linha 20 do exemplo anterior cria um objeto com o resultado, convertendo o texto JSON. A resposta prevê uma data a ser retornada, isso nos permite ver se o resultado que o servidor envia já havia sido representado anteriormente no gráfico, o que é verificado na linha 21. Se os dados forem novos, na linha 23 A função que é responsável por redesenhar o gráfico com as novas informações é chamado.
A ideia ao propor este método de leitura é que os dados sejam atualizados com muita frequência. Se a informação apresentada corresponder a um longo prazo (como as temperaturas de um dia ou de uma semana), pode ser implementado um pedido inicial que recolhe todos os dados disponíveis e depois um, semelhante ao do exemplo, que os atualiza em o período correspondente.
Gere dados aleatórios para teste
Quando toda a infraestrutura de servidores e clientes estiver pronta, uma função como a da seção anterior se encarregará de ler os dados e desenhar o gráfico com eles, mas Na fase de testes pode ser mais prático usar números aleatórios dentro de uma faixa controlada para ver se o código que está sendo escrito está correto. A função a seguir pode servir de exemplo para obter dados durante a construção da aplicação final.
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);
}
|
Em vez de ler as informações de um banco de dados, o exemplo acima as gera aleatoriamente e as repassa para a função responsável por desenhar o gráfico. O dado inventado é um vetor formado por uma data expressa em milissegundos, o momento de registro das informações do sensor e os dados monitorados, que estão entre um valor máximo e um valor mínimo.
Neste exemplo, ao gerar uma data ela pode ser atrasada em até um segundo (1000 milissegundos) em relação à data no momento da invenção. Como Math.random()
gera um número entre 0.0 e 1.0, multiplicá-lo por 1000 produz um número entre 0 e 1000 que é então convertido em um número inteiro. Da mesma forma, o valor é obtido multiplicando o número aleatório pelo intervalo (máximo menos mínimo) e somando o mínimo.
Desenhe o gráfico dos sensores IoT com um gráfico SVG
Como vimos como podemos obter os valores que queremos representar (temperatura, no exemplo) e sua localização temporal, que podem ser expressas em conjunto na forma de coordenadas, o exemplo abaixo mostra uma função para traçar um caminho que une esses pontos e opcionalmente uma área colorida delimitada por essa linha no topo. O resultado seria como a imagem a seguir.
O eixo horizontal (X) do gráfico representa o tempo e o eixo vertical (Y) os valores que os sensores conectados à IoT vêm monitorando. O intervalo horizontal é de alguns segundos, pois nesta proposta o gráfico é atualizado com muita frequência (a cada segundo, por exemplo) para fornecer informações quase em tempo real sobre o estado dos sensores.
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
}
|
No código anterior existem dois aspectos interessantes, primeiro o cálculo que permite adaptar a faixa de valores que são representados e em segundo lugar o construção de propriedade d
que indica as coordenadas dos pontos no layout (path
).
Para adaptar a faixa de valores representados, eles são movidos de um mínimo e dimensionados para que a magnitude visível corresponda ao tamanho do gráfico. No caso do tempo, o deslocamento é obtido subtraindo o intervalo que se deseja exibir do tempo mais longo (a data e a hora mais próximas da atual) (20 segundos no exemplo). O deslocamento dos valores de temperatura é o da faixa inferior (um grau) menos o valor mais baixo, de forma que os dados abaixo sejam os mais semelhantes ao valor mais baixo permitido, mas deixando uma margem que nos permite apreciar o que é passado
O coeficiente que multiplica os valores de tempo para obter as coordenadas horizontais do gráfico é obtido dividindo a largura total do gráfico (100 unidades no exemplo) pelo intervalo de tempo representado (20 segundos no exemplo). Para obter o coeficiente com os valores escalares de temperatura, deve-se lembrar que a faixa representada vai de uma margem abaixo do valor mínimo até uma margem acima do máximo, um grau em ambos os casos. Desta forma, o coeficiente de escala vertical resulta da divisão da altura do gráfico (100 unidades no exemplo) pelo valor máximo, menos o mínimo mais a margem superior e inferior. Como esses valores podem evoluir completamente em temperaturas negativas, usamos Math.abs()
para usar o valor absoluto da diferença.
A propriedade d
do objeto path
É construído concatenando as coordenadas dos pontos em um texto. Cada par de coordenadas é precedido por um código SVG L
, que desenha uma linha da posição atual até um valor absoluto indicado pelas coordenadas. Os valores X e Y são separados por vírgulas e cada operação SVG é separado por um espaço do próximo.
Para iniciar o layout, use o código M
(mover para uma coordenada absoluta). No caso da plotagem fechada e preenchida, inicia-se no canto inferior direito, no caso da plotagem aberta que desenha o perfil de dados, inicia-se pelo último valor representado (o mais recente). Para finalizar o layout fechado, utiliza-se o código Z
somando como último ponto aquele que possui o mesmo valor da coordenada X do último ponto da reta e como coordenada Y o menor valor representado.
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
);
}
|
Neste exemplo, a função dibujar_grafico()
, que é a chamada no carregamento da página, obtém os valores iniciais para testar (não o último valor em tempo real) e prepara o intervalo em que os dados serão renderizados: 20 segundos (20000 ms) na horizontal e 15°C na vertical de -5°C a +10°C com margem superior e inferior de um grau. Faça duas ligações para actualizar_grafico()
, na primeira passagem true
como argumento, que indica que o gráfico deve ser fechado para representar uma área preenchida, e na segunda chamada passa false
estabelecer um limite. Em cada caso, o objeto path
modificado é aquele que tem o aspecto correspondente, com preenchimento e sem borda no primeiro caso e com certa espessura de linha e sem preenchimento no segundo.
A função actualizar_grafico()
trabalhar em um objeto SVG que usa o seguinte código como contêiner HTML. O objeto SVG contém dois caminhos, um para desenhar a linha e outro para desenhar a área preenchida. Ao carregar a página web, a partir do elemento <body>
a função anterior é chamada automaticamente, dibujar_grafico()
graças ao 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>
|
Na linha 10 do código HTML acima, é estabelecida no estilo uma largura (por exemplo) de 820 px e uma altura de 150 px (algo que, na versão final, será aconselhável fazer com uma classe e um documento APF). Parece estranho que as linhas 13 e 14 definam o tamanho do objeto SVG como 100% de largura e altura (que melhor corresponde às dimensões da janela, 100×100). Como já mencionado, a razão para fazer isso é trabalhar sempre com dimensões conhecidas e ajustar os valores representados a elas. As outras alternativas seriam calcular o espaço do gráfico a cada vez e depois reajustar os valores ou forçar dimensões fixas para o gráfico, às quais o documento deverá aderir.
Tendo optado por um gráfico cujas dimensões mudam de acordo com o código HTML, é necessário incluir a propriedade vector-effect
com a coragem non-scaling-stroke
evitar que as espessuras das linhas sejam deformadas quando o gráfico não mantém as proporções 1:1 escolhidas na página web em que é exibido, como ocorre na proposta anterior.
Para "cortar" o gráfico e mostrar apenas a área escolhida, use viewBox
. Neste caso optamos por ver a parte do gráfico que começa em 0,0 (canto superior esquerdo) e mede 100x100 para baixo e para a direita. A parte do desenho localizada em coordenadas com valores negativos ou maiores que 100 não será exibida na página web mesmo que existam no objeto SVG
Adicione novos elementos ao desenho SVG
No exemplo anterior, a função actualizar_grafico()
usar um layout SVG para o qual a propriedade é alterada d
, que é o que expressa a cadeia de coordenadas. A alternativa seria criar o objeto inteiro toda vez que ele for redesenhado. A vantagem da primeira opção é que a aparência gráfica (como espessura ou cor) é definida no código HTML, a limitação é que os objetos devem ser criados previamente.
Para criar objetos SVG, use createElementNS()
, o que permite incluir o espaço para nome. No exemplo abaixo, um novo objeto de texto é criado (text
) e está associado a um elemento SVG que já existe no código HTML do site. Depois que o novo elemento é criado, suas propriedades são atribuídas com setAttribute()
e é adicionado a SVG com 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]);
|
Modifique a proporção dos elementos do desenho
Se você tentou rotular com a função do exemplo da seção anterior, terá visto que o texto parece deformado quando a proporção do objeto na página web (width
y height
De código HTML) não é igual ao da área representada (viewBox
). Para adaptar a proporção é necessário conhecer as medidas do objeto SVG para o qual você pode consultar o estilo do objeto ou do contêiner HTML, se o objeto SVG transferir esta propriedade. Atribuindo propriedade transform
para os objetos SVG que dependem da proporção, a deformação pode ser corrigida aplicando uma operação de escala scale()
em que o coeficiente em X é diferente daquele em Y.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
function
rotular
(
objeto_grafico,
texto,
inicio=[0,0],
altura=10.0,
proporcion=1.0,
tipo_letra=“SircuitoRegularMedium”,
color_texto=“#000000”,
color_fondo=“#FFFFFF”
)
{
var escala_horizontal=parseFloat(getComputedStyle(objeto_grafico).height)/parseFloat(getComputedStyle(objeto_grafico).width);
nuevo_objeto_svg=document.createElementNS(“http://www.w3.org/2000/svg”,‘text’);
nuevo_objeto_svg.setAttribute(“transform”,“scale(“+escala_horizontal+“,1.0)”); // scale permite cambiar la escala en X e Y
//nuevo_objeto_svg.setAttribute(“transform”,”scaleX(“+escala_horizontal+”)”); // Como se sabe que sólo cambia la escala en X, se puede usar scaleX
nuevo_objeto_svg.setAttribute(“x”,inicio[0]);
nuevo_objeto_svg.setAttribute(“y”,inicio[1]);
nuevo_objeto_svg.setAttribute(“font-family”,tipo_letra);
nuevo_objeto_svg.setAttribute(“font-size”,altura);
nuevo_objeto_svg.setAttribute(“fill”,color_texto);
nuevo_objeto_svg.textContent=texto;
objeto_grafico.appendChild(nuevo_objeto_svg);
}
//rotular(document.getElementById(“cosa_svg”),”HOLA”,[10,10]);
|
SVG permite que vários objetos sejam agrupados formando um novo elemento composto que também suporta propriedades, como objetos simples. Para aplicar a mesma transformação a uma série de objetos de uma vez em vez de cada objeto separadamente, você pode agrupá-los de acordo com este recurso e aplicar uma única propriedade transform
para todos eles.
Como explicado ao falar sobre Formato SVG, os elementos de um grupo são colocados entre os rótulos <g>
y </g>
. Para adicionar de JavaScript elementos para um grupo SVG é usado, como visto no exemplo anterior, appendChild()
assim que o novo objeto for definido.
Para estabelecer uma origem ao aplicar transformações, a propriedade pode ser usada em objetos SVG transform-origin
, cujo valor são as coordenadas X e Y do ponto de início da transformação. Caso não seja indicado expressamente um valor para a origem da transformação (no navegador web), utiliza-se o centro de coordenadas. Infelizmente, no momento em que este artigo foi escrito, especificar o comportamento das transformações usando uma fonte diferente da padrão não é homogêneo entre navegadores e deve ser usado com cautela.
Junto com a transformação de escala com scale
Existem outros, como rotação com rotation
e o movimento com translate
, que oferecem um alternativa à representação gráfica: em vez de obter novas coordenadas, você pode representá-las em seu próprio espaço e transformar o gráfico para caber no formato em que deseja representá-las.
Adicione referências ao gráfico
Agora que a parte principal do gráfico está resolvida traçando os valores com um perfil e uma área preenchida, ele pode ser completado com referências que auxiliam sua leitura. Como exemplo, vamos começar traçando algumas referências horizontais (linhas) que marcam os valores máximo e mínimo aceitáveis, bem como um valor desejado. Conforme explicado, você pode optar por adicionar os objetos ao SVG direto de JavaScript ou inclua-os manualmente no código HTML e modificá-los mais tarde com 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();
|
Parece lógico rotular essas referências horizontais com um texto que esclareça o valor que representam. Para destacar o texto, você pode usar retângulos que se destaquem do fundo e do gráfico. Como os textos deverão ser dimensionados para compensar a deformação, todos poderão ser agrupados em um objeto ao qual será aplicada a escala; A principal vantagem de fazer desta forma é poder modificá-los em uma única operação caso o contêiner do gráfico (a janela do navegador) seja redimensionado e mude a proporção que a escala corrige.
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();
|
Existem vários aspectos interessantes no código de exemplo acima. Primeiramente comente que constantes (variáveis globais) foram utilizadas para tornar o exemplo mais legível para usuários vindos da programação. microcontroladores en C o em C + +. Como será visto mais adiante, a maneira ideal de programá-lo em JavaScript Seria utilizar objetos que conteriam esses valores e métodos que gerenciariam as referências neste exemplo ou o gráfico, em geral, em um sistema de produção.
Por outro lado, avançando qual seria o código mais genérico, foram desenvolvidas funções separadas que calculam os diferentes coeficientes que corrigem a proporção do gráfico para ajustar o texto proporcion_grafico()
, a escala dos valores dependendo do seu intervalo escala()
e um fator de correção para medições conhecidas em valor absoluto, como medições em referências medida_grafico()
.
A leitura deste código deve ajudar a esclarecer o contexto em que funciona uma aplicação como esta, que desenha gráficos em tempo real e deve ser flexível para ser apresentada em vários contextos gráficos (vários tamanhos e proporções, no mínimo). Primeiro de tudo, os objetos devem ser gerados SVG, seja "manualmente" no código HTML, seja por meio de código JavaScript e em qualquer caso, as referências a esses objetos devem ser obtidas posteriormente para manipulá-los a partir de JavaScript para que novos gráficos possam ser desenhados e a representação de um gráfico já desenhado possa ser adaptada a uma mudança no meio em que é apresentado.
Outra referência que pode ajudar a interpretar facilmente um gráfico são os pontos que representam valores específicos (os nós da reta). Neste exemplo, em que representamos uma única magnitude, a escolha de um símbolo não é crítica, mas se vários valores diferentes forem sobrepostos para buscar correlação, é interessante distinguir, além de utilizar outros recursos como cor , desenhando símbolos diferentes. Os gráficos utilizados para o nó de linha devem ser modificados em tamanho e proporção, como ocorre, por exemplo, com os textos, para que suas dimensões sejam absolutas e para que suas proporções sejam mantidas mesmo que as da caixa que contém mudem.
No exemplo anterior já vimos como calcular os diferentes coeficientes para redimensionar e corrigir a proporção do desenho; Quanto a como implementar o gerenciamento dos símbolos dos nós ou vértices do grafo, uma possível solução pode ser armazenar os objetos SVG em um vetor e modificar sua posição quando o gráfico for atualizado lendo um novo valor, ou quando for redesenhado redimensionando o contêiner. No primeiro caso a sua posição teria que ser modificada e no segundo a sua proporção com a propriedade transform
e o valor de scale
. O código a seguir é uma modificação da função actualizar_grafico()
para incluir o reposicionamento dos símbolos dos vértices do gráfico.
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);
}
}
|
Modificações feitas na função actualizar_grafico()
para obter a nova função actualizar_grafico_puntos()
São os destacados no código do exemplo anterior. Primeiro, na linha 5, pegamos um vetor de objetos SVG como parâmetro. Este vetor conterá os símbolos que precisam ser reposicionados nos novos nós do gráfico.
Nas linhas 39 e 40 são atribuídas as novas coordenadas do centro, cx
y cy
, aos dos valores que estão sendo representados. Se o símbolo não estiver baseado no centro, provavelmente será necessário adicionar um deslocamento no cx
metade da largura e em cy
de metade da altura para reposicioná-los exatamente no nó do gráfico.
Nas linhas 57 a 61, os pontos que correspondem às coordenadas que não são desenhadas por estarem recortadas pela borda esquerda são reposicionados fora do gráfico. A coordenada de cy
para zero e o de cx
para qualquer número negativo (maior que o próprio ponto) para que não seja mostrado quando cortado, como a parte esquerda do gráfico, pela janela do SVG.
Gerencie o gráfico a partir de um objeto com JavaScript
Todas as operações explicadas até agora podem ser integradas em um objeto para gerenciar o gráfico com um estilo mais típico das novas versões do JavaScript. Esta alternativa de implementação tem a vantagem adicional de simplificar a incorporação de vários gráficos, de valores diferentes, numa mesma página web.
Antes de discutir a implementação, vamos revisar as formas mais comuns de criar objetos com JavaScript e algumas peculiaridades das funções que afetam a proposta de desenho de gráficos de sensores IoT.
Já foi explicado que a nova forma de criar objetos em JavaScript (disponível desde a versão 5 do ECMAScript) consiste em usar Object.create
, que você deve se acostumar a usar em vez do "clássico" new
, que obviamente ainda funciona corretamente, embora seu objetivo seja mais simular o estilo de linguagens com objetos baseados em classes (JavaScript baseia a criação de objetos em protótipos) do que uma alternativa funcional.
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();
}
|
O código anterior permite lembrar as diferenças entre criar os objetos com Object.create
ou com new
. Serve também para enfatizar que, embora a função com a qual o objeto é criado com new
pode estar em qualquer lugar do código, o objeto já deve existir antes de poder ser instanciado com Object.create
(O objeto ES5_Object não é uma função).
Nas linhas 3 e 4, para definir um valor padrão para as propriedades na função que cria o objeto com new
, cada propriedade é atribuída ao valor do argumento correspondente ou (||
), se nenhum argumento tiver sido passado, ou seja, se forem indefinidos (undefined
), já que essa circunstância é avaliada como false
, o valor padrão será atribuído.
O contexto em que uma função é executada JavaScript levanta duas questões que é importante ter em mente e que também podem ser confusas ao usar esta linguagem de programação depois de ter trabalhado com outras, como C o C + +, no nosso caso. O contexto inclui as variáveis definidas no escopo da função (e as globais) o que, aliás, levanta um conceito interessante, os "fechamentos" que estabelecem todo um estilo de programação em JavaScript. Dito isto, seria de esperar que this
, que se refere ao objeto quando utilizado dentro do código que o define, o contexto de execução no qual foi definido é mantido, mas aquele que utiliza é o contexto a partir do qual a função é chamada. Este comportamento é transparente na maioria dos casos, mas há duas circunstâncias em que pode ser confuso: uma função definida dentro de outra função e um método chamado a partir de um evento de objeto. 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
*/
|
Ao executar o código anterior, o texto comentado ao final é exibido no console. As duas linhas marcadas refletem um comportamento que pode ser confuso: o contexto de execução da função probar_dentro()
não probar()
, como seria de esperar, mas window
, que mostra as variáveis globais e não as propriedades de mesmo nome. Se você não quiser esse comportamento, simplesmente crie uma variável na função de nível mais alto e atribua-a a this
, como no código a seguir.
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
*/
|
Para controlar o contexto de execução quando um método é chamado a partir de um evento window
, por exemplo redimensionando a janela do navegador, outra peculiaridade do JavaScript: a possibilidade de programar "fábricas de funções", ou seja, funções que geram outras funções, retornando-as com 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
|
No código de exemplo acima, o método llamar()
de objetos Contexto
Ele não faz o trabalho, mas retorna uma função anônima que cuida disso. Para verificar se tudo funciona conforme o esperado, existe uma variável global com o mesmo nome da propriedade que a função exibe no console; Se o contexto estiver correto, será exibido o valor da propriedade e não o da variável global.
JavaScript Tente corrigir os sinais de ponto e vírgula que omitimos no final das frases. Isso permite um estilo de escrita descontraído, mas é uma faca de dois gumes que deve ser tratada com cuidado. Na maioria dos casos, para evitar os efeitos indesejáveis que isso produz em expressões que ocupam várias linhas, pode-se usar parênteses ou preceder a forma como JavaScript interpretará o código; É por isso que a linha 8 do exemplo inclui function
atrás de return
, se eu tivesse usado outra linha o significado seria muito diferente. Na minha opinião, a solução mais legível é usar uma variável intermediária (dispensável) como na versão a seguir; Obviamente, uma vez compreendido o comportamento, a decisão cabe ao programador.
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);
|
No mesmo sentido de avaliar uma expressão como uma função, ou seja, retornar uma função e não o valor que a função retorna; na linha 21 do último exemplo (estava na linha 19 do anterior) para com clearInterval
a função chamada com setInterval
. Para que atue por 30 segundos, a parada é diferida com setTimeout
, que por sua vez precisa de uma função como primeiro argumento; para entregar a execução como parâmetro clearInterval
com a variável que contém a chamada periódica (e não a função clearInterval
) é para isso que a função anônima na última linha foi criada.
A escolha entre escrever o código integrando a definição da função, mais compacta (como na linha 21) ou utilizando uma variável auxiliar, na minha opinião, mais legível (como nas linhas 19 e 20) varia pouco no desempenho e depende de mais estilo e legibilidade para manutenção.
Para testar o código, antes de ter os dados no servidor, você pode utilizar um gerador de valores aleatórios no intervalo desejado ou preparar tabelas com valores controlados que simulem o funcionamento nas condições desejadas. O exemplo a seguir usa um gerador de dados simples em todo o intervalo, por isso eles parecem um pouco exagerados.
Para testar, você pode baixe o código completo do exemplo formado por uma página web escrita em HTML, o estilo APF e o código JavaScript. Este último é o mais relevante, pois os demais componentes são apenas de suporte mínimo, muito simplificados e muito mais desenvolvidos nos artigos das seções correspondentes.
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);
}
|
Postar Comentário