• Recent
    • Tags
    • Popular
    • Register
    • Login

    Please Note This forum exists for community support for the Mango product family and the Radix IoT Platform. Although Radix IoT employees participate in this forum from time to time, there is no guarantee of a response to anything posted here, nor can Radix IoT, LLC guarantee the accuracy of any information expressed or conveyed. Specific project questions from customers with active support contracts are asked to send requests to support@radixiot.com.

    Radix IoT Website Mango 3 Documentation Website Mango 4 Documentation Website Mango 5 Documentation Website

    How to chart with data points in the X axis instead of time

    Dashboard Designer & Custom AngularJS Pages
    3
    9
    2.5k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • A
      alexzer
      last edited by

      Hello
      Im trying to make a simple chart that would have in the x axis the distinct integral values of each individual data point of my point query and not the time that is default
      Is there an easy way to do that ?
      Basically if i have lets say 63 data points , i want to get the integral of the day of each one and put it in a chart and sort them by the smaller to the bigger
      So i can have at a glance an idea which of the data points are under performing comparing to the others
      Please guide me through
      thank you

      1 Reply Last reply Reply Quote 0
      • phildunlapP
        phildunlap
        last edited by phildunlap

        Hi alexzer, welcome to the forum!

        Interesting request. I found two ways to hack this into the ma-serial-chart component,

        1. Purely through UI setup:
        <md-input-container flex style="display:none">
            <label>Preset</label>
            <ma-date-range-picker from="from" to="to" preset="PREVIOUS_DAY" update-interval="30 minutes"></ma-date-range-picker>
        </md-input-container>
        <ma-get-point-value point-xid="point1-xid" point="point1"></ma-get-point-value>
        <ma-point-statistics point="point1" rendered=false from="from" to="to" statistics="statsObj1"></ma-point-statistics>
        <ma-serial-chart style="height: 300px; width: 100%" series-1-values='[{"value":statsObj1.integral.value, "timestamp":point1.deviceName + "-" + point1.name}]' default-type="column" options='{"categoryAxis":{"parseDates":false}}'></ma-serial>
        

        Then you would extend this example with the other 62 points by either adding them as ma-get-point-value / ma-point-statistics for each point, or you could look into using an ma-point-query with the ma-point-statistics (it can take an array of points as an argument instead of just one. Check out the API docs!). This example also won't sort them for you, so you probably want to use a filter like I do in the second example, but you may have to be on guard for an infinite digest loop, which may require modifying the user module below (probably need to not call sort on input if it's already sorted). You could also add a date selector if you needed to look at specific intervals other than just "Yesterday."

        1. Using an alphanumeric meta point to compute the integrals array for you. This will have the advantage of having a history you can easily look back through.
        <ma-get-point-value point-xid="meta-point-xid" point="point1"></ma-get-point-value>
        <ma-serial-chart style="height: 300px; width: 100%" series-1-values='point1.value|fromJson|sortByNumField:"value":true' default-type="column" options='{"categoryAxis":{"parseDates":false}}'></ma-serial-chart>
        

        and then the alphanumeric meta point is going to do something like this on perhaps a 'start of day' update event:

        values = [];
        //You could also do a DataPointQuery.query if you don't want to worry about keeping the meta
        // point's context up to date
        for(var point : CONTEXT_POINTS) { 
          values.push( {"value" : this[point].past(DAY).integral, "timestamp": this[point].getDataPointWrapper().getExtendedName() });
        }
        return JSON.stringify(values);
        

        In this example there are two angular filters, fromJson and sortByNumField defined in a userModule.js file in my filestore, and linked to by my UI settings:

        define(['angular', 'require'], function(angular, require) {
        'use strict';
        
        var userModule = angular.module('userModule', ['maUiApp']);
        
        userModule.filter('fromJson', ['$filter', function($filter) {
            return function(input) {
                //console.log(input);
                try {
                    return JSON.parse(input);
                } catch {
                    console.log("Parsing error in fromJson filter for input: " + input);
                    return null;
                }
            };
        }]);
        
        userModule.filter('sortByNumField', ['$filter', function($filter) {
            return function(input, field, asc) {
                try {
                    return input.sort(function(a,b){ 
                        if(asc) return a[field]-b[field];
                        return b[field]-a[field];
                    });
                } catch {
                    console.log("Error sorting input: " + input);
                    return input;
                }
            };
        }]);
        
        return userModule;
        
        }); // define
        

        Hope that helps!

        1 Reply Last reply Reply Quote 0
        • A
          alexzer
          last edited by

          Hi philip thanks a lot for the fast response, much appreciated ...
          Im playing around with the features of mango to see if its what my clients needs and as soon as i confirm him we will buy the licence
          I tried the first solution you suggested
          On a static trial its working fine

          <div layout="row">
              <md-input-container flex="20">
                  <label>Choose a first point</label>
                  <ma-point-list limit="200" ng-model="point1"></ma-point-list>
              </md-input-container>
              <md-input-container flex="20">
                  <label>Choose a second point</label>
                  <ma-point-list limit="200" ng-model="point2"></ma-point-list>
              </md-input-container>
               <md-input-container flex="20">
                  <label>Choose a third point</label>
                  <ma-point-list limit="200" ng-model="point3"></ma-point-list>
              </md-input-container>
               <md-input-container flex="20">
                  <label>Choose a fourth point</label>
                  <ma-point-list limit="200" ng-model="point4"></ma-point-list>
              </md-input-container>
              
          </div>
          
          
          <ma-get-point-value point-xid="point1-xid" point="point1" from="dateBar.from" to="dateBar.to" rollup="{{dateBar.rollupType}}" rollup-interval="{{dateBar.rollupIntervals}} {{dateBar.rollupIntervalPeriod}}"></ma-get-point-value>
          <ma-get-point-value point-xid="point2-xid" point="point2" from="dateBar.from" to="dateBar.to" rollup="{{dateBar.rollupType}}" rollup-interval="{{dateBar.rollupIntervals}} {{dateBar.rollupIntervalPeriod}}"></ma-get-point-value>
          <ma-get-point-value point-xid="point3-xid" point="point3" from="dateBar.from" to="dateBar.to" rollup="{{dateBar.rollupType}}" rollup-interval="{{dateBar.rollupIntervals}} {{dateBar.rollupIntervalPeriod}}"></ma-get-point-value>
          <ma-get-point-value point-xid="point4-xid" point="point4" from="dateBar.from" to="dateBar.to" rollup="{{dateBar.rollupType}}" rollup-interval="{{dateBar.rollupIntervals}} {{dateBar.rollupIntervalPeriod}}"></ma-get-point-value>
          <ma-point-statistics point="point1" rendered=false from="dateBar.from" to="dateBar.to" statistics="statsObj1"></ma-point-statistics>
          <ma-point-statistics point="point2" rendered=false from="dateBar.from" to="dateBar.to" statistics="statsObj2"></ma-point-statistics>
          <ma-point-statistics point="point3" rendered=false from="dateBar.from" to="dateBar.to" statistics="statsObj3"></ma-point-statistics>
          <ma-point-statistics point="point4" rendered=false from="dateBar.from" to="dateBar.to" statistics="statsObj4"></ma-point-statistics>
          <ma-serial-chart style="height: 300px; width: 100%" series-1-values='[{"value":statsObj1.integral.value, "timestamp":point1.deviceName + "-" + point1.name}]' series-2-values='[{"value":statsObj2.integral.value, "timestamp":point2.deviceName + "-" + point2.name}]' series-3-values='[{"value":statsObj3.integral.value, "timestamp":point3.deviceName + "-" + point3.name}]' series-4-values='[{"value":statsObj4.integral.value, "timestamp":point4.deviceName + "-" + point4.name}]' default-type="column" options='{"categoryAxis":{"parseDates":false}}'></ma-serial>
          

          Gives an output which looks correct

          0_1526650864445_763065a6-b4ca-4c18-8240-f618567f80ac-image.png


          But when i try to make it via a point query it does not give me each data point as an individual bar but combine them together ..
          I have a limit of 3 to make it more readable

          <div layout="row">
              <md-input-container flex="50">
                  <label>Device name</label>
                  <input ng-init="dvName=''" ng-model="dvName" ng-model-options="{debounce:1000}">
              </md-input-container>
              <md-input-container flex="50">
                  <label>Point name</label>
                  <input ng-init="ptName=''" ng-model="ptName" ng-model-options="{debounce:1000}">
              </md-input-container>
          </div>
          
          <ma-point-query query="{$and: true, deviceName:dvName, name:ptName}" limit="3" points="points"></ma-point-query>
          <ma-point-values points="points" values="combined" from="dateBar.from" to="dateBar.to" rollup="{{dateBar.rollupType}}" rollup-interval="{{dateBar.rollupIntervals}} {{dateBar.rollupIntervalPeriod}}">
          </ma-point-values>
          <ma-point-statistics points="points" rendered=false from="dateBar.from" to="dateBar.to" statistics="statsObj"></ma-point-statistics>
          
          
          <ma-serial-chart style="height: 300px; width: 100%" points="points" values="combined"  series-1-values='[{"value":statsObj.integral.value, "timestamp":points.deviceName + "-" + points.name}]' default-type="column" options='{"categoryAxis":{"parseDates":false}}'></ma-serial>
          
          

          And this give an output of :
          0_1526651217061_be8666b5-0874-4f25-a592-63fbbc7d4afe-image.png

          I think my series-x-values is not correct or what ?
          thank you

          1 Reply Last reply Reply Quote 0
          • phildunlapP
            phildunlap
            last edited by phildunlap

            Hi alexzer,

            What follows is perhaps not the ideal solution, but it does work (you may want to change the inputs back).

            <ma-point-query query="{$and: true, deviceName:'Mango Internal', name:'oint'}" limit="3" points="points"></ma-point-query>
            
            <br/>
            <ma-point-statistics points="points" rendered=false from="dateBar.from" to="dateBar.to" statistics="statsObj"></ma-point-statistics>
            <ma-calc input="outputArray|collateIntegrals:points:statsObj" output="outputArray"></ma-calc>
            <ma-serial-chart  style="height: 300px; width: 100%" series-1-values="outputArray" default-type="column" 
            options='{"categoryAxis":{"parseDates":false}}'></ma-serial-chart>
            

            and this required a filter in a user module again: https://help.infiniteautomation.com/getting-started-with-a-user-module/

            Here's my user module from this question now:

            define(['angular', 'require'], function(angular, require) {
            'use strict';
            
            var userModule = angular.module('userModule', ['maUiApp']);
            
            userModule.filter('collateIntegrals', ['$filter', function($filter) {
                return function(input, points, statistics) {
                    if(!input)
                        input = [];
                    
                    if(!points || !statistics)
                        return;
                        
                    var output = [];
                    var minLength = points.length < statistics.length ? points.length : statistics.length;
                    for(var k = 0; k < minLength; k+=1) {
                        output.push({"value": statistics[k].integral.value, "timestamp": points[k].deviceName + " - " + points[k].name});
                    }
                    if(output.length != input.length) {
                        return output;
                    } for(var k = 0; k < output.length; k+=1)
                        if(output[k].value !== input[k].value || output[k].timestamp !== input[k].timestamp) {
                            return output;
                        }
                    return input; //Prevent infinite angular digest loop
                };
            }]);
            
            userModule.filter('fromJson', ['$filter', function($filter) {
                return function(input) {
                    //console.log(input);
                    try {
                        return JSON.parse(input);
                    } catch {
                        console.log("Parsing error in fromJson filter for input: " + input);
                        return null;
                    }
                };
            }]);
            
            userModule.filter('sortByNumField', ['$filter', function($filter) {
                return function(input, field, asc) {
                    try {
                        return input.sort(function(a,b){ 
                            if(asc) return a[field]-b[field];
                            return b[field]-a[field];
                        });
                    } catch {
                        console.log("Error sorting input: " + input);
                        return input;
                    }
                };
            }]);
            
            return userModule;
            
            }); // define
            

            Notice that series-1-values is the ouput array from the filter, so all points will be in a single series. It's possible this would be easier as a custom component, and example of which is in the next post....

            1 Reply Last reply Reply Quote 0
            • phildunlapP
              phildunlap
              last edited by phildunlap

              Or you could use a component. It's about the same, but you're less likely to be troubled by infinite digest loop issues during development. Here's the markup:

              <ma-point-query query="{$and: true, deviceName:'Mango Internal', name:'oint'}" limit="3" points="points"></ma-point-query>
              
              <br/>
              <ma-point-statistics points="points" rendered=false from="dateBar.from" to="dateBar.to" statistics="statsObj"></ma-point-statistics>
              <integral-collator points="points" statistics="statsObj" output-var="outputArray2"></integral-collator>
              <ma-serial-chart  style="height: 300px; width: 100%" series-1-values="outputArray2" default-type="column" 
              options='{"categoryAxis":{"parseDates":false}}'></ma-serial-chart>
              

              And now we'll need to define this component in our user module,

              define(['angular', 'require'], function(angular, require) {
              'use strict';
              
              var userModule = angular.module('userModule', ['maUiApp']);
              
              class IntegralCollatorController {
                  static get $$ngIsClass() { return true; }
                  
                  $onChanges(changes) {
                      if (changes.points || changes.statistics && (this.points && this.statistics)) {
                          this.calculateOutput();
                      }
                  }
                  
                  calculateOutput() {
                      if(!this.points || !this.statistics)
                          return;
                          
                      var output = [];
                      var minLength = this.points.length < this.statistics.length ? this.points.length : this.statistics.length;
                      for(var k = 0; k < minLength; k+=1) {
                          output.push({"value": this.statistics[k].integral.value, "timestamp": this.points[k].deviceName + " - " + this.points[k].name});
                      }
                      //TODO sort output
                      this.outputVar = output;
                  }
              }
              
              userModule.component('integralCollator', {
                  bindings: {
                      points: '<',
                      statistics: '<',
                      outputVar: '='
                  },
                  controller: IntegralCollatorController
              });
              
              return userModule;
              
              }); // define
              

              This is probably the right solution, but both techniques are interesting.

              1 Reply Last reply Reply Quote 0
              • A
                alexzer
                last edited by

                Hi ! yes the second solution works fine !
                thank you :)
                No control of the sorting output thought yet !

                1 Reply Last reply Reply Quote 0
                • phildunlapP
                  phildunlap
                  last edited by phildunlap

                  You can see I put a //TODO sort output in there. You could pretty easily lift that sort function from the sortByNumField filter I showed, or you could use that filter in the ma-serial-chart with series-1-values='outputArray2|sortByNumField:"value":true'

                  1 Reply Last reply Reply Quote 0
                  • A
                    alexzer
                    last edited by

                    Hi, my client finally bought the licence so we are starting to develop.
                    I have the following error in my console but also some times rendered as html instead of my page ... Im not sure what is this mismatch but it has to do with the component you wrote for me
                    Any idea how to fix this ?
                    thank you

                    Error bootstrapping Mango app: Mismatched anonymous define() module: function(angular, require) { 'use strict'; var userModule = angular.module('userModule', ['maUiApp']); class IntegralCollatorController { static get $$ngIsClass() { return true; } $onChanges(changes) { if (changes.points || changes.statistics && (this.points && this.statistics)) { this.calculateOutput(); } } calculateOutput() { if(!this.points || !this.statistics) return; var output = []; var minLength = this.points.length < this.statistics.length ? this.points.length : this.statistics.length; for(var k = 0; k < minLength; k+=1) { output.push({"value": this.statistics[k].integral.value, "timestamp": this.points[k].name}); } //TODO sort output this.outputVar = output; console.log(output); } } userModule.component('integralCollator', { bindings: { points: '<', statistics: '<', outputVar: '=' }, controller: IntegralCollatorController }); return userModule; } http://requirejs.org/docs/errors.html#mismatch
                    Show stack trace
                    Error: Mismatched anonymous define() module: function(angular, require) {
                    'use strict';
                    
                    var userModule = angular.module('userModule', ['maUiApp']);
                    
                    class IntegralCollatorController {
                        static get $$ngIsClass() { return true; }
                        
                        $onChanges(changes) {
                            if (changes.points || changes.statistics && (this.points && this.statistics)) {
                                this.calculateOutput();
                            }
                        }
                        
                        calculateOutput() {
                            if(!this.points || !this.statistics)
                                return;
                                
                            var output = [];
                            var minLength = this.points.length < this.statistics.length ? this.points.length : this.statistics.length;
                            for(var k = 0; k < minLength; k+=1) {
                                output.push({"value": this.statistics[k].integral.value, "timestamp": this.points[k].name});
                            }
                            //TODO sort output
                            this.outputVar = output;
                            console.log(output);
                        }
                    }
                    
                    userModule.component('integralCollator', {
                        bindings: {
                            points: '<',
                            statistics: '<',
                            outputVar: '='
                        },
                        controller: IntegralCollatorController
                    });
                    
                    return userModule;
                    
                    }
                    http://requirejs.org/docs/errors.html#mismatch
                        at makeError (http://2.38.152.46:8082/modules/mangoUI/web/mangoUi~ngMango~ngMangoServices.js?v=4b59ed889675c8a103ef:39:108173)
                        at O (http://2.38.152.46:8082/modules/mangoUI/web/mangoUi~ngMango~ngMangoServices.js?v=4b59ed889675c8a103ef:39:115062)
                        at Object.a [as require] (http://2.38.152.46:8082/modules/mangoUI/web/mangoUi~ngMango~ngMangoServices.js?v=4b59ed889675c8a103ef:39:121430)
                        at requirejs (http://2.38.152.46:8082/modules/mangoUI/web/mangoUi~ngMango~ngMangoServices.js?v=4b59ed889675c8a103ef:39:108712)
                        at Function.b.configureLocale (http://2.38.152.46:8082/modules/mangoUI/web/mangoUi~ngMango~ngMangoServices.js?v=4b59ed889675c8a103ef:76:52295)
                        at Function.b.setUser (http://2.38.152.46:8082/modules/mangoUI/web/mangoUi~ngMango~ngMangoServices.js?v=4b59ed889675c8a103ef:76:51806)
                        at m.r [as $get] (http://2.38.152.46:8082/modules/mangoUI/web/mangoUi~ngMango~ngMangoServices.js?v=4b59ed889675c8a103ef:76:55709)
                        at Object.invoke (http://2.38.152.46:8082/modules/mangoUI/web/mangoUi~ngMango~ngMangoServices.js?v=4b59ed889675c8a103ef:269:26286)
                        at http://2.38.152.46:8082/modules/mangoUI/web/mangoUi~ngMango~ngMangoServices.js?v=4b59ed889675c8a103ef:269:24185
                        at u (http://2.38.152.46:8082/modules/mangoUI/web/mangoUi~ngMango~ngMangoServices.js?v=4b59ed889675c8a103ef:269:25665)
                    
                    1 Reply Last reply Reply Quote 0
                    • Jared WiltshireJ
                      Jared Wiltshire
                      last edited by

                      @alexzer how are you including the user module in your application? You can't just put it in as a script tag. Please see https://help.infiniteautomation.com/getting-started-with-a-user-module/

                      Developer at Radix IoT

                      1 Reply Last reply Reply Quote 0
                      • First post
                        Last post