Generate and modify SVG graphics of data from sensors connected to the IoT with JavaScript

Generate and modify SVG graphics of data from sensors connected to the IoT with JavaScript

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.

Table of Contents

    Data graphs from sensors connected to the Internet of Things (IoT) container in HTMLGraphs of data from sensors connected to the Internet of Things (IoT) definition of appearance in CSSData graphs from sensors connected to the Internet of Things (IoT) drawing with SVGData graphs from sensors connected to the Internet of Things (IoT) Generation and modification with JavaScript

    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.

    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.

    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.

    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.

    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).

    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. Using valueOf(), which is a method present in most objects, the value of the corresponding Date object is also obtained, such as getTime() with Unix time or POSIX time expressed in ms.

    • getMilliseconds() | setMilliseconds()
      Used to query or set the fractional millisecond part of the object Date 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 object Date (or decrease it, if negative values ​​are used).

    • getSeconds() | setSeconds()
      Returns or changes, respectively, the value of the object's seconds Date.

    • getMinutes() | setMinutes()
      Used to consult or set the minutes of the object Date.

    • getHours() | setHours()
      Allows you to consult or modify the hours (from 0 to 23) of the object Date.

    • 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 object Date on which it is applied.

    • getMonth() | setMonth()
      Used to consult or modify the month number of the object Date.

    • 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.

    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.

    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.

    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.

    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.

    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 n
    • Math.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 up
    • Math.cos(α) Cosine of parameter α (α in radians)
    • Math.E e number (≃2.718281828459045)
    • Math.exp(n) e raised to the parameter n: en
    • Math.floor(n) Round parameter n to the nearest integer down
    • Math.log(n) Natural logarithm (base e) of parameter n
    • Math.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 passed
    • Math.min(a,b,c,…) Smallest value of the list of parameters passed
    • Math.PI Number π (≃3.141592653589793)
    • Math.pow(n,m) First parameter n raised to the second parameter m: nm
    • Math.random() (Almost) random number between 0.0 and 1.0
    • Math.round(n) Round parameter n to the nearest integer
    • Math.sin(α) Sine of parameter α (α in radians)
    • Math.sqrt(n) Square root of parameter n
    • Math.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.

    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.

    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.

    Example of a graph generated with SVG and JavaScript to represent data from IoT sensors

    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.

    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.

    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.

    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().

    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.

    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.

    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.

    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.

    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.

    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.

    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.

    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.

    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.

    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.

    Post Comment

    You May Have Missed