Generate and modify SVG graphics of data from sensors connected to the IoT with JavaScript
In this last part of the series of articles on drawing graphics with data from sensors connected to the Internet of Things, it's time to talk about how to generate or modify with JavaScript drawings in format SVG and some of the elements HTML that serve as a container or that present complementary information to the graphics.
The target users of this tutorial are supposed to form an electronics and computer programming profile. microcontrollers, they may not be familiar with HTML, CSS o SVG; For this reason, in the previous installments a short introduction to the language or the corresponding technology was made. In this last part the approach is a little different, since the readers surely know how to program, it is possible that using the language C++ that how JavaScript, shares basic syntax with C and it can be taken as a reference to skip most of the basic programming concepts and thus focus on the differences and the specific use that interests us to create sensor graphics in the IoT.
The name gives a clue to the first difference: JavaScript It is a programming language script (hyphen) and as such, it is interpreted, there is no need to compile it; the context in which the script (a web browser, for example) will read, translate and execute the orders. To be precise, in most cases there is a runtime compilation (JIT), but for the code writing process JavaScript It doesn't affect us, we simply write the code and it can work.
The name also contains the first confusion: JavaScript has not the slightest relationship with Java. Initially, when he developed it Netscape for its browser, it was called first Mocha and then the less confusing LiveScript. After its successful implementation in browsers, and transcending them, it was standardized as ECMAScript (The ECMA-262, version 6 at the time of writing) to become neutral with respect to browsers that implement it. Currently there is also a standard ISO from version 5, 2011 (ISO / IEC 16262: 2011 at the time of writing the article)
Variables, basic data types and objects in JavaScript
Unlike what happens, for example, in C++, en JavaScript data type not included when declaring a variable and also the type associated with a variable is not fixed, it is possible to assign a value of a different type throughout the execution of the program.
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
|
In the previous example, the variable "thing" has been declared (without indicating the data type) then data of a different type is assigned and it is consulted with typeof
the type that JavaScript that he has interpreted. To debug the code you can write it in the web browser's inspector console (which will not affect the presentation of the web) with console.log()
.
To force the conversion of data to a specific type, especially text to numeric, you can use functions such as parseInt()
o parseFloat()
which convert to integers or floating point numbers respectively. The opposite conversion can be done with String()
, although it is unlikely to be necessary as automatic conversion is usually sufficient. With parseFloat()
For example, you can get the value of a web page property, such as the width or height of an object, that includes units; In this way, the expression parseFloat("50px");
will return 50, a numeric value, as a result.
En JavaScript there is no distinction between double and single quotes; The data type in both cases is string
, and each of them can include the other without the need for escape codes.
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
|
In the previous example it can be seen that a variable, when it has been declared (exists) but has not been assigned any value, contains an undefined data type (undefined
). An unassigned object has the value null
; That is, the object exists, but without value; a variable that referenced it would not have a typeof
undefined
but object
. An object can also be empty, that is, not null but not have any properties.
For define an object in JavaScript are enclosed in braces ({
y }
) the properties or methods, separated by the colon sign (:
) property name property value and by comma (,
) the different properties. You can find more information about this way of expressing an object in the article on the JSON format.
Although you can use syntax that might lead you to think otherwise, en JavaScript There are no classes but prototypesThat is, for an object to inherit properties and methods, another object is created (the prototype) that the others (the children) use as a reference. The syntax closest to the style of JavaScript to use a prototype is Object.create
although it is also possible (and sometimes useful) to use new
as in other object-oriented languages.
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));
|
For query if one object is an instance of another, if you use it as a prototype, if you inherit its properties, in short, you can use instanceof
(created with new
) or isPrototypeOf
(created with Object.create
) which will evaluate to true when the object uses the prototype and false when it does not.
Once an object has been created using another as a prototype, that is, once an object has been instantiated, it can be add new properties or override prototype properties using dot syntax as in gato.peso=2.5
.
La arrays in JavaScript They are different from those you probably know in C. To begin with, they are declared without the need to indicate their length, only with the signs of opening and closing square brackets ([
y ]
), components can be heterogeneous (different data types in the same array) and new elements can be added without being restricted to a limit. The matrices of JavaScript are actually lists (collections) of elements to which referenced by a numerical index or by a name. An array can simultaneously contain numeric indexes and element names, but it is common to use objects (properties) to exploit the second type.
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
|
As can be seen in the previous example, to know if a variable corresponds to an instance of an array (it is an array object) you can use instanceof
, as has already been used with generic objects or, in more recent versions of JavaScript can be resorted to Array.isArray()
To access the elements of the array you can use its index (matriz[7]
) or by the property name with the name in square brackets (matriz["nombre"]
) or with the usual dot syntax for objects (matriz.nombre
). Since the name is a text string, an expression, including variables, can be used to compose it. To loop through an array with properties, a loop with the format can be used 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
*/
|
It is interesting for our objective to treat object Date
, with which to represent and manage date and time in JavaScript. The object can be instantiated without data, so it will take the current date and time, or it can be created by indicating a date as a value, either in milliseconds since January 1, 1970 (such as Unix time or POSIX time but expressed in milliseconds instead of seconds) or specifying separate values of year, month, day, hour...
The object includes a complete series of methods to query or set the date and time:
-
now()
Returns the current date and time expressed in milliseconds since January 1, 1970 -
getTime()
|setTime()
Gets or changes, respectively, the time value in milliseconds since January 1, 1970. UsingvalueOf()
, which is a method present in most objects, the value of the corresponding Date object is also obtained, such asgetTime()
with Unix time or POSIX time expressed in ms. -
getMilliseconds()
|setMilliseconds()
Used to query or set the fractional millisecond part of the objectDate
on which it is executed. If consulted, the value obtained is between 0 and 999 but larger values can be assigned that will accumulate in the total date and time so, like the rest of the get methods, it serves to increase the value of the objectDate
(or decrease it, if negative values are used). -
getSeconds()
|setSeconds()
Returns or changes, respectively, the value of the object's secondsDate
. -
getMinutes()
|setMinutes()
Used to consult or set the minutes of the objectDate
. -
getHours()
|setHours()
Allows you to consult or modify the hours (from 0 to 23) of the objectDate
. -
getDay()
Returns the day of the week for the date, expressed as a value from 0 to 6 (Sunday to Saturday). -
getDate()
|setDate()
Returns or changes the day of the month of the objectDate
on which it is applied. -
getMonth()
|setMonth()
Used to consult or modify the month number of the objectDate
. -
getFullYear()
|setFullYear()
Queries or sets the year value on the object containing the date and time.
The previous methods of Date
include a version UTC to be able to work directly with universal time without having to do intermediate calculations. In that sense, for example, getHours()
has a version getUTCHours()
o getMilliseconds()
an alternative getUTCMilliseconds()
to work alternatively with the official (legal) or universal time. With getTimezoneOffset()
You can know the difference that exists between universal time and local official time.
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;
|
JavaScript functions
If you are reading this you surely know how to program. microcontrollers en C or in C++ and know the concept of function. Although the basic idea is the same, in JavaScript The way they are defined and used is a little different. To begin with, it has already been said, JavaScript It does not explicitly use data types so you do not have to indicate it when defining the function. To follow, It is not mandatory for a function to have a name, they can be anonymous. They can be associated with a variable to invoke them but it may also not be necessary since, sometimes, it is useful to invoke them immediately, for which the parentheses and parameters are added after the definition of the function.
To define a function, prefix function
, if applicable, write the name, the arguments (the parameters passed to the function) in parentheses, and the code that will be executed when the function is invoked in braces.
1
2
3
4
5
|
function doble(numero)
{
var resultado=numero*2;
return resultado;
}
|
Certainly, in the previous example the "result" variable was not needed at all, but it is a good excuse to remember the variable scope, which works as you expect: the "result" variable only exists within the "double" function. In JavaScript can also be used let
, instead of var
, to scope a variable to a code block context (enclosed in curly braces, {
y }
)
When talking about objects in the previous section, something fundamental was missing: properties have been defined but methods have not been defined. As expected, object methods are functions, they have no name and are used (invoked) from the (property) name assigned by the object definition.
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”);
}
}
|
In the previous example, there is already a method, "view_temperature", that displays the value of the "current_temperature" property through the console. It's not very useful, but it gives a more complete idea of what the definition of an object is like in JavaScript.
To access the methods of an object (functions) to its properties, use this
, as in the previous example on line 11, when using the “current_temperature” property.
Access the Document Object Model (DOM) with JavaScript
From JavaScript You have access to the content of the web page on which it runs, as well as some aspects of the browser that displays that page, although not to system resources. The data structure that supports the properties and methods accessed from JavaScript part of the window object, specifically, the content of the object (the document HTML) corresponds to the object document
. Although it is sometimes used for clarity, it is not necessary to precede window to the methods or properties to refer to them, it is enough, for example, to use document
, there is no need to write the name of the root object as in window.document
, as long as the current window is referenced.
The most used form of find an object within the document HTML It is through the method getElementById()
, to which the id that was indicated when creating the code is passed as an argument HTML. From what was explained in previous sections, it is easy to assume that you can also access the components inside the object document
using dot syntax (document.componente
) or brackets using both the name (document["componente"]
), the most useful, such as the numerical index, difficult to use and impractical when accessing the content of a manually composed web page.
With JavaScript can be get the element that contains another element (element or parent node) consulting your property parentNode
or your property parentElement
, the difference is that the parent element (parentElement
) of the final element of the string SUN It is null (null
) and the parent node (parentNode
) is the document itself (document
).
For modify the content of an element HTML, for example that of a label <div>
, It can be used innerHTML
and to change its properties you can choose to assign it a different class with className
or alter its properties individually with style
. To consult the style displayed by an element on the web page is not necessarily useful style
since it may depend on several factors or simply not have been explicitly specified. To check the style of an element finally displayed on the web page, the getComputedStyle method is used.
To a document element HTML Several classes can be assigned to it to determine its appearance and behavior, to manage the list of classes of an object from JavaScript can be resorted to classList
that offers the methods add
to add a new class to the list, remove
to remove it, toggle
to replace it or consult the content of the class list of an element with item
and with contains
, which returns the class that occupies a certain position in the list and a value true
o false
whether or not a certain class is on the list.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
var contenedor_temperatura=document.getElementById(“temperatura”); // Encontrar el div con id=”temperatura”
contenedor_temperatura.innerHTML=“”; // Eliminar el contenido provisionalmente para que no se vean los cambios
contenedor.className=“bloque_temperatura”; // Asignar una nueva clase
if(temperatura>20) // Si la temperatura es mayor que 20 °C…
{
contenedor.style.color=“#FF6666”; // …usar el color rojo en lugar del color normal de la clase
}
contenedor_temperatura.innerHTML=“La temperatura es “+temperatura+” °C”; // Cuando el aspecto esté preparado mostrar el valor
if(document[“titulo”].classList.contains(“estilo_titulo”)) // Si el objeto “titulo” tiene la clase “estilo_titulo”…
{
if(document[“titulo”].classList.contains(“general”)) // …y la clase “general”…
{
document[“titulo”].classList.remove(“general”); // …quitar la clase “general”
}
}
else // Si el objeto “titulo” no tiene la clase “estilo_titulo”…
{
document[“titulo”].classList.add(“estilo_titulo”); // …añadir la clase “estilo_titulo” (da igual que tenga o no la clase “general”)
}
|
In the previous example it is located with getElementById
the object you want to manipulate (an element <div>
its id
), before changing the appearance, the content is deleted by assigning with innerHTML
an empty text string, it is assigned a new class with className
and its style is modified with style
depending on the value of the content (temperature), changing the color, if applicable, through the property color
. Once the aspect is established, the value is written using again innerHTML
.
In the second part of the example above (lines 9 to 19) a code element is accessed HTML using the syntax document[]
and the property id
of the element to alter its class list with the method classList.remove()
and with the methodclassList.add()
, based on the result of several queries that are performed in conditional executions, which they compare using classList.contains()
.
When is it going to refer to an element HTML several times throughout the code JavaScript, it's a little more efficient to assign it to a variable or use its index instead of the name since, otherwise, the method you would use JavaScript to obtain it each time would require searching for its name, consuming a little more time than if a variable were accessed.
For add new objects to the document HTML, they can be created first with the method createElement
de document
and later incorporate them to the rest of the elements at the point of the tree that is necessary with appendChild
. To create an object XML, like objects SVG that we use to draw the graph of the IoT sensors, you can use createElementNS
(NS for name space). As explained when talking about the format SVG, the namespace that corresponds to it (for the current version) is http://www.w3.org/2000/svg
, which should be passed to createElementNS
as an argument along with the element type, svg
, in this case.
Una alternative to innerHTML
to add text as content to a document element HTML is the method createTextNode()
Object document
. With this alternative you can create new text (which is later accessed if it is assigned to a variable) that is incorporated into the object tree with the method appendChild()
. As alternative to appendChild()
, which adds the new content to the end of what already exists in the node to which it is added, you can use the method insertBefore()
, which adds a new object in front of an existing one. Wear insertBefore()
instead of appendChild()
provides a method that serves, for example, to sort new objects in front of existing ones when an element must be in front of another (as in a list) or cover or be covered in a graphic structure in which there are elements closer to the foreground or background.
React to events with JavaScript
When the way of use a web page as a container for IoT connected sensor graphs it was used onload
In the label <body>
to start drawing the graph. This property, associated with the code objects HTML, refers to the events JavaScript. As already explained, it executes a function when the page has loaded. Although it has been associated with the code HTML to keep it more in mind, it could have been written in the code JavaScript as body.onload=dibujar;
siendo dibujar
the name of the function that should be started when the web page loads.
In the most recent versions of JavaScript events can be associated with functions using addEventListener
with the format objeto.addEventListener(evento,función);
or using the syntax objeto.evento=función;
which works also in older implementations. To unlink the function associated with the event, you have removeEventListener
which has the same format as addEventListener
.
JavaScript It is capable of reacting to a multitude of events that can occur on a web page. For example, it can detect when an element is clicked HTML with onmousedown
, or when clicked with onclick
, when a key is pressed with onkeydown
, by operating the scroll bar with onscroll
. For our purpose it is enough for us to detect page load with onload
and its resizing with onresize
. We will associate these events with the objects body
y window
of the SUN respectively. The first can be assigned in the code HTML, as seen and the second within the code JavaScript inside the function called by the first and with the format window.onresize=redimensionar;
siendo redimensionar
the function that will be called every time the window changes size.
Run after a time interval
JavaScript has two resources for deferred execution: setTimeout
, which executes a function after a time interval and setInterval
which will execute a function every certain time interval. Both methods require as parameters (1) the invoked function and (2) the time interval expressed in milliseconds. To stop their operation, you can assign the result returned by these functions to variables and pass them as an argument to clearTimeout
and clearInterval
when you do not want to invoke them again (or when you do not want them to be executed for the first time) setTimeout
o setInterval
respectively.
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
}
|
In the previous example the method is introduced alert
which serves to display a warning sign. Although it was widely used in the past, it is currently almost banned from the code JavaScript because of how aggressive (intrusive) it is to cover the web page with a dialog box.
In a program written for a microcontroller of a small series (such as the one on the plate Arduino Uno) it is common to use global variables, as in the previous example in JavaScript, since the code is brief and not particularly confusing, because many times the functions are implemented ad hoc and because the use of global variables makes it possible to predict memory use in a very simple and intuitive way, which is critical in systems with few resources. Instead, en JavaScript It is common to reduce the use of global variables to the minimum possible. because it does not need to rush memory usage, since it runs normally on a CPU with resources far superior to those of a MCU, because it is likely to coexist with a lot of third-party code with which it must work without interfering and since it is an open system, the future execution context cannot be predicted (the program of a microcontroller small completely determines its operation without adding more code once it is in operation) and because the dimensions of the applications could make reading difficult if the code does not encapsulate its operation, making the methods as self-contained as possible.
Mathematical operations with the JavaScript Math object
The mathematical operations of more complicated mathematical calculation are grouped in the object Math
. This object is used directly, it is not necessary to instantiate it to use the methods or properties (constants) it incorporates.
Math.abs(n)
Absolute value of parameter nMath.acos(n)
Arccosine of parameter n (result in radians)Math.asin(n)
Arcsine of parameter n (result in radians)Math.atan(n)
Arctangent of parameter n (result in radians)Math.atan2(n,m)
Arctangent of n/m (result in radians)Math.ceil(n)
Round the parameter to the nearest integer upMath.cos(α)
Cosine of parameter α (α in radians)Math.E
e number (≃2.718281828459045)Math.exp(n)
e raised to the parameter n: enMath.floor(n)
Round parameter n to the nearest integer downMath.log(n)
Natural logarithm (base e) of parameter nMath.LN2
Natural logarithm (base e) of 2 (≃0.6931471805599453)Math.LN10
Natural logarithm (base e) of 10 (≃2.302585092994046)Math.LOG2E
Base 2 logarithm of e (≃1.4426950408889634)Math.LOG10E
Base 10 logarithm of e (≃0.4342944819032518)Math.max(a,b,c,…)
Largest value of the list of parameters passedMath.min(a,b,c,…)
Smallest value of the list of parameters passedMath.PI
Number π (≃3.141592653589793)Math.pow(n,m)
First parameter n raised to the second parameter m: nmMath.random()
(Almost) random number between 0.0 and 1.0Math.round(n)
Round parameter n to the nearest integerMath.sin(α)
Sine of parameter α (α in radians)Math.sqrt(n)
Square root of parameter nMath.SQRT1_2
Square root of 1/2 (≃0.7071067811865476)Math.SQRT2
Square root of 2 (≃1.4142135623730951)Math.tan(α)
Tangent of parameter α (α in radians)
Load data from server with AJAX
The method followed to draw the information stored in the IoT consists of loading the data from the server from time to time and redrawing the graph with which they are represented. To read data from the server, technology is used AJAX (Asynchronous JavaScript And XML) through an object XMLHttpRequest
de JavaScript. Plotting the data graph is done by reusing an object SVG which is already in the code HTML and that contains a plot whose coordinates are modified to make them correspond to the new data loaded.
In the example of this proposal, in addition to updating the drawing, a text on the web page is also updated that shows the date and value of the last measured data for each graph.
On the server side there is a database that contains the information that the sensors connected to the IoT have been monitoring. This database is read by the object request XMLHttpRequest
responding with information encoded in the JSON format, although the name of the method used suggests a relationship with the format XML.
In the first polaridad.es tutorial on the IoT data storage You can see an example of an infrastructure to manage, from the server side, the information provided by devices connected to the Internet of Things. In this series of articles a server is used as a resource Apache from which you can use the programming language PHP to access a database MySQL o MariaDB. On servers used to support IoT it is very common to find databases MongoDB (NoSQL) and the programming language JavaScript about Node.js as software infrastructure.
The next function is responsible for requesting the latest data from one of the sensors from the server. In the function call, the object is used as an argument JavaScript that supports the data that is drawn. If the same graph represents several values, for example to visually search for a correlation, a request can be made to the server to return several simultaneously, a more optimal method due to the way the server works. HTTP protocol.
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);
}
|
In the third line of the previous example, the query that will be made to the server is prepared, in which the argument "zone" will be passed, the value of which will be the name or code of the monitored place since information about the area may coexist in the same database. different sensors (for example, thermometers that measure the temperature in different rooms). The parameter passed to the previous function, the object with the chart data, is expected to include a property with the name of the room ("name_suffix").
Between lines 7 and 14 of the previous code, the object XMLHttpRequest
which is stored in the variable "ajax". Before choosing how to create the object, you search window
if XMLHttpRequest
was not available (something that happened in old versions of Microsoft's explorer and although it is far behind, it serves as an example of alternatives to create the object using the (more native) syntax. Object.create
o new
, similar to that of other object-oriented languages.
In order to be able to manage the response immediately, the code that handles it is prepared in lines 15 to 26 before making the request to the server.
The way of perform the query HTTP to the server consists of open a connection with open
indicating type and page (optionally username and password), prepare the headers of the protocol with setRequestHeader
y send the request with send
. The header HTTP Content-length
you will need to know the length of the query (number of characters) which is calculated using length
.
When the request AJAX is ready, the function associated with the event is executed onreadystatechange
. Instead of assigning a function, in the previous example an anonymous function is defined on the fly that will manage the reception of data arriving from the server. First of all, on line 18, it is verified that the status of the request is "finished", which corresponds to the value 4
of the property readyState
, that the status is "OK" of the HTTP protocol (code 200
) that can be obtained from the property status
and that the data that has arrived is JSON format, consulting the property responseType
.
Once verified that the status of the response is as expected, in line 20 of the previous example creates an object with the result, converting the text JSON. The response provides for a date to be returned, this allows us to see if the result that the server sends had already been previously represented in the graph, which is verified on line 21. If the data is new, on line 23 The function that is responsible for redrawing the graph with the new information is called.
The idea when proposing this reading method is that the data will be refreshed very frequently. If the information presented corresponds to a long term (such as the temperatures of a day or a week), an initial request can be implemented that collects all the available data and then one, similar to the one in the example, that updates it in the period correspondent.
Generate random data for testing
When all the server and client infrastructure is ready, a function like the one in the previous section will be in charge of reading the data and drawing the graph with it, but In the testing phase it may be more practical to use random numbers within a controlled range to see if the code being written is correct. The following function can serve as an example to obtain data while building the final application.
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);
}
|
Instead of reading the information from a database, the example above generates them randomly and passes them to the function in charge of drawing the graph. The invented data is a vector formed by a date expressed as a value in milliseconds, the moment of recording the sensor information, and the monitored data, which is between a maximum value and a minimum value.
In this example, when generating a date it can be delayed up to one second (1000 milliseconds) with respect to the date at the time of invention. As Math.random()
generates a number between 0.0 and 1.0, multiplying it by 1000 produces a number between 0 and 1000 which is then converted into an integer. In the same way, the value is obtained by multiplying the random number by the range (maximum minus minimum) and adding the minimum.
Draw the IoT sensors graph with an SVG plot
Since we have seen how we can obtain the values that we want to represent (temperature, in the example) and their temporal location, which can be expressed together in the form of coordinates, the example below shows a function to draw a path that joins those points and optionally a colored area delimited by that line at the top. The result would be like the following image.
The horizontal axis (X) of the graph represents time and the vertical axis (Y) the values that the sensors connected to the IoT have been monitoring. The horizontal interval is a few seconds since in this proposal the graph is updated very frequently (every second, for example) to provide almost real-time information on the status of the sensors.
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
}
|
In the previous code there are two interesting aspects, firstly the calculation that allows adapt the range of values that are represented and secondly the property construction d
which indicates the coordinates of the points on the layout (path
).
To adapt the range of values represented, they are moved from a minimum and scaled so that the visible magnitude corresponds to the size of the graph. In the case of time, the offset is obtained by subtracting the range that you want to display from the longest time (the date and time closest to the current one) (20 seconds in the example). The displacement of the temperature values is that of the lower range (one degree) minus the lowest value, so that the data shown below are the most similar to the lowest value allowed but leaving a margin that allows us to appreciate what is pass
The coefficient that multiplies the time values to obtain the horizontal coordinates of the graph is obtained by dividing the total width of the graph (100 units in the example) by the time range represented (20 seconds in the example). To obtain the coefficient with the scalar temperature values, it must be remembered that the range represented goes from a margin below the minimum value to a margin above the maximum, one degree in both cases. In this way, the vertical scale coefficient results from dividing the height of the graph (100 units in the example) by the maximum value, minus the minimum plus the upper and lower margin. Since these values could develop completely at negative temperatures, we use Math.abs()
to use the absolute value of the difference.
The property d
Object path
It is constructed by concatenating the coordinates of the points in a text. Each pair of coordinates is preceded by a code SVG L
, which draws a line from the current position to an absolute value that is indicated by the coordinates. The X and Y values are separated by commas and each operation SVG is separated by a space from the next.
To start the layout, use the code M
(move to an absolute coordinate). In the case of the closed and filled plot, you start at the bottom right, in the case of the open plot that draws the data profile, you start with the last value represented (the most recent). To finish the closed layout, the code is used Z
adding as the last point the one that has the same X coordinate value as the last point of the line and as the Y coordinate the smallest value represented.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
function dibujar_grafico()
{
var tiempo_mostrado=20000; // Se representan 20 segundos (20000 milisegundos)
var valor_maximo=10; // 10 grados sobre cero
var valor_minimo=–5; // Cinco grados bajo cero
var fecha_hora=Date.now(); // Hora actual
var matriz_de_coordenadas_de_prueba=[]; // Preparar el vector de coordenadas
for(var contador=0;contador<20;contador++)
{
fecha_hora+=500+1500*Math.random(); // Añadir medio segundo a la hora anterior y entre 0 y segundo y medio aleatoriamente
matriz_de_coordenadas_de_prueba[contador]=[]; // Preparar el siguiente punto del vector de coordenadas
matriz_de_coordenadas_de_prueba[contador][0]=fecha_hora; // En la coordenada horizontal, situar la hora
matriz_de_coordenadas_de_prueba[contador][1]=Math.random()*Math.abs(valor_maximo–valor_minimo)+valor_minimo; // En la coordenada vertical situar el valor del sensor
}
actualizar_grafico
(
document.getElementById(“relleno_temperatura”), // Trazado para el relleno definido en el código HTML
matriz_de_coordenadas_de_prueba,
tiempo_mostrado,
valor_maximo, // Diez grados de valor máximo
valor_minimo, // Cinco grados bajo cero como valor mínimo
1, // Un grado por encima y por debajo de las temperatura mínimas y máximas respectivamente
true // Cerrar el trazado para representar el área rellena
);
actualizar_grafico
(
document.getElementById(“linea_temperatura”), // Trazado para el relleno definido en el código HTML
matriz_de_coordenadas_de_prueba,
tiempo_mostrado,
valor_maximo, // Diez grados de valor máximo
valor_minimo, // Cinco grados bajo cero como valor mínimo
1, // Un grado por encima y por debajo de las temperatura mínimas y máximas respectivamente
false // No cerrar el trazado para representar la linea que une los puntos que representan las temperaturas
);
}
|
In this example, the function dibujar_grafico()
, which is the call on page load, gets the initial values to test (not the last real-time value) and prepares the range in which the data will be rendered: 20 seconds (20000 ms) horizontally and 15°C in vertical from -5°C to +10°C with one degree top and bottom margin. Make two calls to actualizar_grafico()
, in the first pass true
as an argument, which indicates that the chart should be closed to represent a filled area, and on the second call it passes false
to draw the line. In each case, the object path
modified is the one that has the corresponding appearance, with a fill and no border in the first case and with a certain line thickness and no fill in the second.
The function actualizar_grafico()
work on an object SVG which uses the following code as a container HTML. The object SVG contains two paths, one to draw the line and another to draw the filled area. When loading the web page, from the element <body>
the previous function is automatically called, dibujar_grafico()
thanks to the event 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>
|
On line 10 of the code HTML above, a width (as an example) of 820 px and a height of 150 px is established in the style (something that, in the final version, it will be advisable to do with a class and a document CSS). It seems strange that lines 13 and 14 define the size of the object SVG like 100% width and height (which best matches the window dimensions, 100×100). As already mentioned, the reason for doing this is to always work with known dimensions and adjust the represented values to it. The other alternatives would be to calculate the space of the graph each time and then readjust the values or force fixed dimensions for the graph, which the document will have to adhere to.
Having opted for a graph whose dimensions change according to the code HTML, it is necessary to include the property vector-effect
with the value non-scaling-stroke
to prevent line thicknesses from being deformed when the graph does not maintain the chosen 1:1 proportions on the web page on which it is displayed, as occurs in the previous proposal.
To "crop" the graph and show only the area you choose, use viewBox
. In this case we have chosen to see the part of the graph that starts at 0,0 (upper left corner) and measures 100x100 down and to the right. The part of the drawing located in coordinates with negative values or greater than 100 will not be displayed on the web page even if they exist in the object SVG
Add new elements to SVG drawing
In the previous example, the function actualizar_grafico()
use a layout SVG to which ownership is changed d
, which is what expresses the coordinate chain. The alternative would be to create the entire object every time it is redrawn. The advantage of the first option is that the graphic appearance (such as thickness or color) is defined in the code HTML, the limitation is that the objects must be previously created.
To create SVG objects, use createElementNS()
, which allows including the namespace. In the example below a new text object is created (text
) and is associated with an element SVG which already exists in the code HTML of the website. Once the new element is created, its properties are assigned with setAttribute()
and is added to SVG with 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]);
|
Modify the proportion of drawing elements
If you have tried labeling with the function in the example in the previous section, you will have seen that the text appears deformed when the proportion of the object on the web page (width
y height
Of code HTML) is not equal to that of the area represented (viewBox
). To adapt the proportion it is necessary to know the measurements of the object SVG for which you can consult the style of the object, or the container HTML, if the object SVG transfer this property. Assigning ownership transform
to objects SVG that depend on the proportion, the deformation can be corrected by applying a scaling operation scale()
in which the coefficient in X is different from that in Y.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
function
rotular
(
objeto_grafico,
texto,
inicio=[0,0],
altura=10.0,
proporcion=1.0,
tipo_letra=“SircuitoRegularMedium”,
color_texto=“#000000”,
color_fondo=“#FFFFFF”
)
{
var escala_horizontal=parseFloat(getComputedStyle(objeto_grafico).height)/parseFloat(getComputedStyle(objeto_grafico).width);
nuevo_objeto_svg=document.createElementNS(“http://www.w3.org/2000/svg”,‘text’);
nuevo_objeto_svg.setAttribute(“transform”,“scale(“+escala_horizontal+“,1.0)”); // scale permite cambiar la escala en X e Y
//nuevo_objeto_svg.setAttribute(“transform”,”scaleX(“+escala_horizontal+”)”); // Como se sabe que sólo cambia la escala en X, se puede usar scaleX
nuevo_objeto_svg.setAttribute(“x”,inicio[0]);
nuevo_objeto_svg.setAttribute(“y”,inicio[1]);
nuevo_objeto_svg.setAttribute(“font-family”,tipo_letra);
nuevo_objeto_svg.setAttribute(“font-size”,altura);
nuevo_objeto_svg.setAttribute(“fill”,color_texto);
nuevo_objeto_svg.textContent=texto;
objeto_grafico.appendChild(nuevo_objeto_svg);
}
//rotular(document.getElementById(“cosa_svg”),”HOLA”,[10,10]);
|
SVG allows several objects to be grouped forming a new composite element that also supports properties, like simple objects. To apply the same transformation to a series of objects at once instead of each object separately, you can group them according to this resource and apply a single property transform
to all of them.
As explained when talking about SVG format, the elements of a group are enclosed within the labels <g>
y </g>
. To add from JavaScript elements to a group SVG is used, as seen in the previous example, appendChild()
once the new object is defined.
To establish an origin when applying transformations, the property can be used on objects SVG transform-origin
, whose value is the X and Y coordinates of the point from which the transformation begins. If a value for the origin of the transformation is not expressly indicated (in the web browser), the center of coordinates is used. Unfortunately, at the time of writing, specifying the behavior of transformations using a source other than the default one is not homogeneous across browsers and should be used with caution.
Along with the scale transformation with scale
There are others, such as rotation with rotation
and the movement with translate
, which offer a alternative to graph representation: instead of obtaining new coordinates, you can represent them in their own space and transform the graph to fit the format in which you want to represent them.
Add references to the chart
Now that the main part of the graph is resolved by plotting the values with a profile and a filled area, it can be completed with references that help its reading. As an example, let's start by drawing some horizontal references (lines) that mark the maximum and minimum acceptable values as well as a desired value. As explained, you can choose to add the objects to the SVG directly from JavaScript or include them manually in the code HTML and modify them later with 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();
|
It seems logical to label these horizontal references with text that clarifies the value they represent. To highlight the text, you can use rectangles that will stand out from the background and the graphic. As the texts will have to be scaled to compensate for the deformation, they can all be grouped into an object to which the scale will be applied; The main advantage of doing it this way is to be able to modify them in a single operation if the graph container (the browser window) is resized and changes that proportion that the scale corrects.
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();
|
There are several interesting aspects in the above example code. First of all, comment that constants (global variables) have been used to make the example more readable to users coming from programming. microcontrollers en C or in C++. As will be seen later, the optimal way to program it in JavaScript It would be using objects that would contain these values and methods that would manage the references in this example or the graph, in general, in a production system.
On the other hand, advancing what the more generic code would be, separate functions have been developed that calculate the different coefficients that correct the proportion of the graph to adjust the text proporcion_grafico()
, the scale of the values depending on their range escala()
and a correction factor for measurements that are known in absolute value, such as measurements in references medida_grafico()
.
Reading this code should help clarify the context in which an application like this works, which draws graphics in real time and must be flexible to be presented in various graphical contexts (various sizes and proportions, at the very least). First of all, the objects must be generated SVG, either "manually" in the code HTML, either through code JavaScript and in any case, references to these objects must subsequently be obtained to manipulate them from JavaScript so that new graphs can be drawn and the representation of an already drawn graph can be adapted to a change in the medium in which it is presented.
Another reference that can help easily interpret a graph are the points that represent specific values (the nodes of the line). In this example, in which we represent a single magnitude, the choice of a symbol is not critical, but if several different values are superimposed to look for correlation, it is interesting to distinguish, in addition to using other resources such as color, by drawing different symbols. The graphics used for the line node must be modified in size and proportion, as occurs, for example, with texts, so that its dimensions are absolute and so that its proportions are maintained even if those of the box it contains change. the graphic.
In the previous example we already saw how to calculate the different coefficients to rescale and correct the proportion of the drawing; Regarding how to implement the management of the symbols of the nodes or vertices of the graph, a possible solution may be to store the objects SVG into a vector and modify its position when the graph is updated by reading a new value, or when it is redrawn by resizing the container. In the first case its position would have to be modified and in the second its proportion with the property transform
and the value of scale
. The following code is a modification of the function actualizar_grafico()
to include repositioning of the graph vertex symbols.
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);
}
}
|
Modifications made to the function actualizar_grafico()
to get the new function actualizar_grafico_puntos()
They are the ones highlighted in the code of the previous example. First, in line 5, we take a vector of objects SVG as a parameter. This vector will contain the symbols that need to be repositioned in the new nodes of the graph.
In lines 39 and 40 the new coordinates of the center are assigned, cx
y cy
, to those of the values that are being represented. If the symbol were not based on the center, it will probably be necessary to add an offset in cx
half the width and in cy
of half the height to reposition them exactly on the graph node.
In lines 57 to 61, the points that correspond to coordinates that are not drawn because they are cut off by the left edge are repositioned outside the graph. The coordinate of cy
to zero and that of cx
to any negative number (greater than the point itself) so that it is not shown when cut, like the left part of the graph, by the window of the SVG.
Manage the chart from an object with JavaScript
All the operations that have been explained so far can be integrated into an object to manage the graph with a style more typical of the new versions of JavaScript. This implementation alternative has the added advantage of simplifying the incorporation of several graphs, of different values, on the same web page.
Before discussing the implementation, let's review the most common ways to create objects with JavaScript and some of the peculiarities of the functions that affect the proposal for drawing IoT sensor graphics.
It was already explained that the new way of creating objects in JavaScript (available since version 5 of ECMAScript) consists of using Object.create
, which should get used to using instead of the "classic" new
, which of course still works correctly, although its purpose is more to simulate the style of languages with class-based objects (JavaScript bases the creation of objects on prototypes) than a working alternative.
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();
}
|
The previous code allows you to remember the differences between creating the objects with Object.create
or with new
. It also serves to emphasize that, while the function with which the object is created with new
can be anywhere in the code, the object must already exist before it can be instantiated with Object.create
(ES5_Object object is not a function).
On lines 3 and 4, to set a default value to the properties in the function that creates the object with new
, each property is assigned to the value of the corresponding argument or (||
), if no arguments have been passed, that is, if they are undefined (undefined
), as that circumstance is evaluated as false
, the default value is assigned.
The context in which a function is executed JavaScript raises two issues that are important to keep in mind and that can also be confusing when using this programming language after having worked with others, such as C o C++, in our case. The context includes the variables defined in the scope of the function (and the global ones) which, by the way, raises an interesting concept, the "closures" that establishes an entire programming style in JavaScript. That said, it could be expected that this
, which refers to the object when used within the code that defines it, the execution context in which it has been defined is maintained but the one it uses is the context from which the function is called. This behavior is transparent in most cases, but there are two circumstances in which it can be confusing: a function defined inside another function and a method called from an object event. 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
*/
|
When executing the previous code, the commented text at the end is displayed in the console. The two marked lines reflect behavior that can be confusing: the function execution context probar_dentro()
not probar()
, as might be expected, but window
, which shows the global variables and not the properties of the same name. If you do not want this behavior, simply create a variable in the highest level function and assign it to this
, as in the following code.
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
*/
|
To control the execution context when a method is called from an event window
, for example by resizing the browser window, another peculiarity of JavaScript: the possibility of programming "function factories", that is, functions that generate other functions, returning them with 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
|
In the example code above, the method llamar()
of the objects Contexto
It does not do the work but returns an anonymous function that takes care of it. To verify that everything works as expected, there is a global variable with the same name as the property that the function displays in the console; If the context is correct, the value of the property will be displayed and not that of the global variable.
JavaScript Try to correct the semicolon signs that we omit at the end of the sentences. This allows for a relaxed writing style but is a double-edged sword that must be treated carefully. In most cases, to avoid the undesirable effects that this produces in expressions that occupy several lines, you can use parentheses or precede the way in which JavaScript will interpret the code; That's why line 8 of the example includes function
behind return
, if I had used another line the meaning would be very different. In my opinion, the most readable solution is to use an intermediate (dispensable) variable as in the following version; Obviously, once the behavior is understood, the decision corresponds to the programmer.
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);
|
In the same sense of evaluating an expression as a function, that is, returning a function and not the value that the function returns; on line 21 of the last example (it was on line 19 of the previous one) it stops with clearInterval
the function called with setInterval
. In order for it to act for 30 seconds, the stop is deferred with setTimeout
, which in turn needs a function as the first argument; to deliver the execution as a parameter clearInterval
with the variable that contains the periodic call (and not the function clearInterval
) is what the anonymous function in the last line is created for.
The choice between writing the code integrating the function definition, more compact (as in line 21) or using an auxiliary variable, in my opinion, more readable (as in lines 19 and 20) varies little in performance and depends more style and readability for maintenance.
To test the code, before having data on the server, you can use a generator of random values in the desired range or prepare tables with controlled values that simulate operation under the desired conditions. The following example uses a simple data generator across the entire range, which is why they appear a bit exaggerated.
To test, you can download the full code of the example formed by a web page written in HTML, the style CSS and the code JavaScript. The latter is the most relevant, since the other components are only minimal support, very simplified and are much more developed in the articles in the corresponding sections.
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);
}
|
Post Comment