• MattFox MattFox

    Correct, look under API Docs -> Components -> maSerialChart
    They have a complete guideline of what attribute tags to use to apply values. You will want the series-x-values where the x is the chart number.
    The Chart update rate is affected by the maPointValues components which you can also read about. The realtime="true" attribute will allow datapoints to update into the chart at the rate they change or are updated.
    If you don't want to average, make sure you set the rollup type to NONE. Alternatively, use the rollup-interval to set the interval duration between datapoints in the chart, note that changing from the live updating rate may mean you;ll need to average.

    posted in Dashboard Designer & Custom AngularJS Pages read more
  • MattFox MattFox

    This will be a handy post for those who are running cloud hosted solutions as there isn't a mangoES settings section for custom installs.

    posted in User help read more
  • MattFox MattFox

    OK!
    As a starting point to work from. I'll work with you so you learn how it all works as you go...
    (All code is placed in the /opt/mango/overrides/web... directories)
    /modules/mangoUI/web/dev/components/settingsModal.js

    define(['angular', 'require'], function(angular, require) {
    'use strict';
    
    /* Date: 1/9/18
    	   Author: Matt "Fox" Fox
    	   Desc: component written to launch a modal dialog with either a built in template or just spit out a list of points to save one by one.
    	*/
    	settingsModalController.$inject = ['$mdDialog','maJsonStore'];
    		function settingsModalController ($mdDialog,maJsonStore)
    		{
    			var ctrl = this;
    			ctrl.mdDialog = $mdDialog;
    			ctrl.parsedOptions=[];
    			
    			ctrl.values={};
    			
    			this.$onInit = function()
    			{
    				if(this.points && !angular.isArray(this.points))
    				{
    					console.log('Error: points field not an array of point objects');
    				}
    				if(this.pointOptions && !angular.isArray(this.pointOptions) )
    				{
    					console.log('Error: points options field not an array');
    				}
    				ctrl.clickOffClose = ctrl.clickOffClose===true?true:false;
    				
    				
    				
    				
    				
    			};
    			
    			this.$onChanges = function(e)
    			{
    				console.log(e);
    				checkPoints();
    			}
    			
    			function checkPoints()
    			{
    				ctrl.parsedOptions=[];
    				if(ctrl.points && angular.isArray(ctrl.points))
    				{
    					if(ctrl.pointOptions && angular.isArray(ctrl.pointOptions) )
    					{
    						var ptName,ptDesc;
    						for(var i=0; i<ctrl.points.length; i++)
    						{
    							ptName=ctrl.points[ i ].name;
    							ptDesc='';
    							if(ctrl.pointOptions[ i ] && ctrl.pointOptions[ i ].hasOwnProperty('name') )
    							{
    								ptName = ctrl.pointOptions[ i ].name;
    							}
    							if(ctrl.pointOptions[ i ] && ctrl.pointOptions[ i ].hasOwnProperty('desc') )
    							{
    								ptDesc = ctrl.pointOptions[ i ].desc;
    							}
    							ctrl.parsedOptions.push( {name:ptName,desc:ptDesc} );
    						}
    					}
    					else
    					{
    						for(var i=0; i<ctrl.points.length; i++)
    						{
    							ptName=ctrl.points[ i ].name;
    							ptDesc='';
    						
    							ctrl.parsedOptions.push( {name:ptName,desc:ptDesc} );
    						}
    					}
    				}
    				else if(ctrl.pointOptions && angular.isArray(ctrl.pointOptions) && !ctrl.points )
    				{	
    					var ptId, ptName, ptDesc;
    					for(var i=0; i<ctrl.pointOptions.length; i++)
    					{
    						if(ctrl.pointOptions[ i ] && !ctrl.pointOptions[ i ].hasOwnProperty('name') )
    						{
    							// ctrl.pointOptions=[];
    							ctrl.parsedOptions=[];
    							return;
    						}
    						ptId=i;
    						ptDesc='';
    						ptName=ctrl.pointOptions[ i ].name;
    						if(ctrl.pointOptions[ i ] && ctrl.pointOptions[ i ].hasOwnProperty('id') )
    						{
    							ptId = ctrl.pointOptions[ i ].id;
    						}
    						if(ctrl.pointOptions[ i ] && ctrl.pointOptions[ i ].hasOwnProperty('desc') )
    						{
    							ptDesc = ctrl.pointOptions[ i ].desc;
    						}						
    						ctrl.parsedOptions.push( {id:ptId,name:ptName,desc:ptDesc} );
    					}
    				}
    			}
    			
    			/* DIALOG CODE */
    			ctrl.showDialogue = function(ev) {
    			   // ctrl.updateModals();
    			   if( ctrl.altTemplateUrl!==undefined  )
    			   {
    				   try
    				   {
    					   
    					ctrl.mdDialog.show({
    					  templateUrl:ctrl.altTemplateUrl, 
    					  parent: angular.element(document.body),
    					  targetEvent: ev,
    					  clickOutsideToClose: ctrl.clickOffClose
    					}).then(
    						function()
    						{
    							console.log('function if closed/hidden manually'); 
    						},
    						function()
    						{
    							console.log('function to fire if closed from clicking outside/cancelled');
    						}
    					);
    				   }catch(e){console.warn(e);}
    			   }
    			   else
    			   {
    				   try{
    					   
    					ctrl.mdDialog.show({
    					  contentElement: '#settingsModal',
    					  parent: angular.element(document.body),
    					  targetEvent: ev,
    					  clickOutsideToClose: ctrl.clickOffClose
    					}).then(
    						function()
    						{
    							console.log('function if closed manually'); 
    						},
    						function()
    						{
    							console.log('function to fire if closed from clicking outside/cancelled');
    						}
    					);
    				   }catch(e){console.warn(e);}
    				}
    			};
    			
    			ctrl.hideDialogue = function() {
    				ctrl.mdDialog.hide();
    			};
    
    			ctrl.cancel = function() {	  
    				ctrl.mdDialog.cancel();
    			};
    			/* DIALOG CODE END*/
    			
    			/* JSON Store Stuff: */
    			ctrl.saveToStore = function(data=null)
    			{
    				
    				var item = maJsonStore.newItem( ctrl.jsonStore.toLowerCase() );
    				item.jsonData = data===null ? ctrl.values : data;
    				item.readPermission= "user";
    				item.editPermission= "user"; //can change this at your leisure or we can break it out into a setting...
    				
    				var r = item.$save();
    				
    				if(r.status)
    				{ 
    				}
    				else
    				{ 
    				}
    			};
    			
    			ctrl.loadFromStore = function()
    			{
    				maJsonStore.get({xid: ctrl.jsonStore.toLowerCase() }).$promise.then(function(item) {
    				ctrl.values = item.jsonData;
    			});
    			/* JSON Store Stuff END: */
    			};
    			
    			/* Virtual Point Save stuff */
    			ctrl.saveToPoint = function()
    			{
    				if(ctrl.savePoint===undefined)
    				{
    					alert('Point to save to not set as attribute!');
    					return;
    				}
    				ctrl.savePoint.setValue( JSON.stringify(ctrl.values) );
    			};	
    			
    			/* Virtual Point Save stuff END */
    	}
    		
    		
    		
    	
    	var settingsModal={
    		bindings:{
    			jsonStore: '<?', 		//Store name to save settings to
    			savePoint: '<?', 		//alternatively, save my JSON to a virtual pt, MUST BE TYPE DATAPOINT...
    			altTemplateUrl:'=?', 	//If we want to use the popup to show something else: use this template...
    			modalTitle:'=?', 		//seemed like a good idea at the time... will only work if altTemplateUrl is empty
    			clickOffClose:'=?', 	//set to true to click outside modal to shut it
    			pointOptions:'<?', 	//Alternate names for points,  (object array) [{id:'',name:'',desc:''}], id used for key name if using json/virtual point save
    			points: '<?',			 //Array of points to set individually
    		},
    		controller: settingsModalController,
    		templateUrl:'/modules/mangoUI/web/dev/views/modalDemo.html'
    	};
    	
    	return settingsModal;
    });
    

    and the template markup to add:
    /modules/mangoUI/web/dev/views/modalDemo.html

     <ma-button  hue="hue-2" palette="primary" label="Open Dialogue" ng-click="$ctrl.showDialogue()" raised="true"></ma-button>
     <div ng-if="!$ctrl.altTemplateUrl" style="visibility: hidden">
        <div class="md-dialog-container" id="settingsModal">
          <md-dialog layout-padding>
    			<md-dialog-actions><md-button ng-if="!$ctrl.clickOffClose" ng-click="$ctrl.cancel()" class="md-primary">x</md-button></md-dialog-actions>
     <div ng-if="!$ctrl.points && !$ctrl.parsedOptions">
    		Custom markup needed!
    		</div>
    		 
           <h1 ng-if="$ctrl.modalTitle!==undefined">{{$ctrl.modalTitle}}</h1> <small ng-if="$ctrl.clickOffClose">Click outside dialogue to close</small>
    		
    		<div ng-if="$ctrl.points">
    		<md-input-container ng-repeat="(index,point) in $ctrl.points" style="display:block;width:100%;">
    	   {{$ctrl.parsedOptions[index].name}}
    			<br/>	
    	   {{$ctrl.parsedOptions[index].desc}}
    			<br/>	
    			
    		<span style="display:inline-block;">Current Value: <ma-point-value style="display:inline-block;" point="point" ></ma-point-value></span>
    		 <ma-set-point-value  point="point" show-button="true"></ma-set-point-value> 
    		</md-input-container>
    		</div>
    		<div ng-if="!$ctrl.points && $ctrl.parsedOptions.length>0">
    			<md-input-container ng-repeat="input in $ctrl.parsedOptions">
    			{{input.name}}<br/>{{input.desc}}
    			<input ng-model="$ctrl.values[ input.id ]"/>
    			</md-input-container>
    		</div>
    		
          </md-dialog>
        </div>
      </div>
    

    More than happy to help you work through this (it will be a bit of a crash course)
    I've not added the buttons on the template for saving using a virtual point or JSON store, but that can be added quite easily.
    Basically you can generate a popup which will populate with inputs that can be named, given an ID and a description if necessary. This can also be used to override point names to more 'user friendly' equivalents for your users if they're filling out items in the dialogue box.

    inside the userModule.js directory (assuming that's the name of the file you've followed from Jared's tutorial) you'll want this structure:

    /userModule.js
    /components/settingsModal.js
    /views/modalDemo.html

    and inside userModule.js you'll want this:

    define(['angular', 'require','./components/settingsModal.js'], function(angular, require,settingsModal) {
        'use strict';
     var userModule = angular.module('userModule', ['maUiApp']);
    try {	
    	userModule.component('settingsModal', settingsModal);
    }
    catch(e){console.log(e);}	
        return userModule;
    });
    

    Markup in the dashboard page builder will be:

    <settings-modal point-options="[{id:'test',name:'input 1',desc:'input test 1'},{id:'test2',name:'input 2',desc:'input test 2'}]" points="vp" click-off-close="true"></settings-modal>
    

    See if you can get the popup showing for you first. I'm using 3.4.1 so I'm not completely sure if this will be affected in your version of mangoUI...

    Fox

    EDIT: sorry I'll be sure to add some more code comments later...
    New Edit: added data to save to store as an optional argument

    posted in Wishlist read more
  • MattFox MattFox

    Fixed, just mixed up the full path of the file with the userModule URL after closer inspection.

    posted in User help read more
  • MattFox MattFox

    6 Core xeon 15GB server
    Given Mango 10G to run with, htop seems to show it asking for 14 in a virtual sense, not sure if that means I need to crank it up or not..
    377 Datasources, A little over 8200 points, About half of those being updated by API from a third party system. All points are updating ranging from 20 seconds to 15 minutes. I'd argue about 500 points are meta points.
    193 users, I've always got about 2/3 thirds having active sessions on the system.
    CPU hits approx 20% every once in a while. I've got 700 max threads running in settings to ensure mango can just smack everything out as it needs. But after having issues last week with tags being saved to show status information I was beginning to wonder if there's more I'm missing....

    posted in Mango feedback read more
  • MattFox MattFox

    Congrats for making it through the other side. Would be good to have a chat considering currently our system doesn't scale well with mango. If the merger can help with this that'd be grand. It's the bottleneck in our system and I'm struggling to streamline what we have.

    Fox

    posted in Announcements read more
  • MattFox MattFox

    Seeing how amcharts have kindly given the source code on how to do this; you would need to get your required data points. Use a get point values query for the respective data points required. Use a "1 YEARS" AVERAGE/SUM rollup if you have multiple values for a given year depending on what you are calculating.

    Next write an angular component/directive which creates a time-line pie instance, and format the incoming point values with their points into the
    "[ 'year' : [ { 'value' : 'datapointValue1', 'label': 'point 1 name'}, { 'value' : 'datapointValue2, 'label': 'point 2 name'} ]" format.

    The supplied source code on amcharts will enable you to create animate effects etc.
    To get yourself started, I suggest you look at Jared's pie chart directive then build on top of that in a new file, just be sure to change the name from pieChart to timePieChart so you don't cause any problems with the native UI components.

    Hope that helps!

    Fox

    posted in Dashboard Designer & Custom AngularJS Pages read more
  • MattFox MattFox

    @psysak If youn mean you have a mango unit publishing data and you want to be able to set back the other way, The only way to do so is to create a publisher going back the other way. That way when you set the datapoint on the mango unit receiving the published data the changes are pushed back to the initial mango unit doing the publishing. This does mean you would have a new datasource to capture and hold the set data.

    posted in User help read more
  • MattFox MattFox

    Need to access through the side with a precision screwdriver and carefully push the angled plastic away from the grey tab. It's inside the case, not through the bottom like the rest.

    Fox

    posted in Hardware read more
  • MattFox MattFox

    It's a bit of a hack, but this might be what you want. There's supposed to be an option regarding balloons in graph options but I think as it applies to the balloons which are generated prior to the graph being instantiated you'd have to do it in JS.

    <style>.amcharts-balloon-bg.amcharts-balloon-bg-categoryAxis,
        .amcharts-balloon-div.amcharts-balloon-div-categoryAxis
        {
            display:none!important;
            visibility:hidden!important;
            text-align: center;width: 0px; 
        }
    </style>
    

    Fox

    posted in Scripting general Discussion read more
  • MattFox MattFox

    correct, you need to user the strictMinMax:true property to force the axes to be a given range

    posted in Dashboard Designer & Custom AngularJS Pages read more
  • MattFox MattFox

    /ui/docs/ng-mango/ma-serial-chart
    

    Read the docs under API Docs -> components -> maSerialChart

    there's an attribute you can set to alter the format:

    time-format
    (optional)
    string	
    The moment.js time format to be used in displaying timestamps on the X axis.
    

    Put your moment.js format in there and that will let you set the format to 'DD mm' etc

    posted in Mango Automation general Discussion read more
  • MattFox MattFox

    I can tell you that the ma-gauge-chart tag is an angularJS component so it will not show anything between the tags, and thus not be rendered.
    Units usually show if you have set the unit field on the individual datapoint under /data_sources.shtm.
    Feel free to take screenshots and describe further what you are trying to accomplish.

    Fox

    posted in User help read more
  • MattFox MattFox

    Not true:

    @pyeager said in When does a change to a global script become effective?:

    I suspect the reason for this is obvious to those who are intimately familiar with the internal architecture of Mango, Not so obvious to those of us who are trying to figure things out with the available "documentation" and the forum.

    https://help.infiniteautomation.com/scripting-data-source

    Paragraph six under the list points

    Global scripts are automatically included in the script execution, such that functions and variables defined there are available. Note that if global scripts are changed, the scripting data source must be restarted to reload them.

    posted in Scripting general Discussion read more
  • MattFox MattFox

    OK! Here is what the code would be like:
    You need to have a directory structure like this to make it work with my code:
    Use winscp to connect with SSH to your mango install.
    -/opt/mango/overrides/web/modules/mangoUI/web/userModule
    |->/opt/mango/overrides/web/modules/mangoUI/web/userModule/userModule.js
    |->->/opt/mango/overrides/web/modules/mangoUI/web/userModule/directives/gaugeChart.js

    Once configured, make sure you add userModule.js to the administration->UI Settings page under user module url.
    enter this:

    /modules/mangoUI/web/userModule/userModule.js
    

    in the userModule.js file paste this code:

    define(['angular', 'require','./directives/gaugeChart.js'], function(angular, require,gaugeChart) {
    var userModule = angular.module('userModule', ['maUiApp']);
    try
    {
    userModule.directive('gaugeChart', gaugeChart);
     return userModule;
    }catch(e){console.log(e);}
    });
    

    In the directives file, use the code in the post above.

    Now, to use this in your dashboard, simply do the same as above, except the tag would just be 'gauge-chart'

     <gauge-chart id="44cf2d0c-c777-41e0-b20d-ea11a3f9f0cb" style="position: absolute; width: 200px; height: 200px; left: 145px; top: 8px;" point-xid="DP_866585" start="180" end="260" 
      options="{
    	axes: [ {
        axisThickness: 1,
        axisAlpha: 0.2,
        tickAlpha: 0.2,
        valueInterval: 5,
        bands: [ {
          color: '#cc4748',
          endValue: 191,
          startValue: 180
        }, {
          color: '#fdd400',
          endValue: 202,
          startValue: 191
        }, {
          color: '#84b761',
          endValue: 232,
          innerRadius: '95%',
          startValue: 202
        }, {
          color: '#fdd400',
          endValue: 232,
          startValue: 234
        }, {
          color: '#cc4748',
          endValue: 260,
          startValue: 234
        }],
        bottomText: '[[value]] km/h',
        bottomTextYOffset: 0,
        startValue:180,
        endValue: 260
      } ]
    }
    "></gauge-chart>
    

    posted in Dashboard Designer & Custom AngularJS Pages read more
  • MattFox MattFox

    @phildunlap said in Create event detector that applies to all/specific datapoints:

    You'd have a bunch of options to hack it up.

    You had me at hack
    I've got a status dashboard doing something similar already. Although I can likely extend what you've done here and implement an email with a template listing all iffy datapoints. It's not an alarm per se but it sure is a darn good start.
    I'll put it in the wishlist. I think it'd be good to datasource/point template wide alarm settings that individual points have.
    Inidividual point alarms override template -> template override datasource. That's my idea.

    Thanks again Phil!

    Fox

    posted in User help read more
  • MattFox MattFox

    Your power calculation is incorrect at first glance, use Math.pow(num,exp).
    The ^ I believe is treated as a bitwise exclusive OR.
    Also did the mango JavaScript provide any info as there is statistical analysis available.
    Look at the help icon and have a glance there.

    Also:

    var sumDiffSQuotient = sumDiffSquared/299;
    Power_StdDev = Math.sqrt(sumDiffSQuotient);                  // set std to calculated value
        
    

    Take these outside of the loop!

    Var sumDiffSquared=0;
    for...
    {..}
    var sumDiffSQuotient = sumDiffSquared/299;
    Power_StdDev = Math.sqrt(sumDiffSQuotient);                  // set std to calculated value
        
    

    You reset their values every time your for loop runs, you may as well wait till after the loop since you're not adding those calculations inside the loop and only run them once.

    Fox

    posted in Scripting general Discussion read more
  • MattFox MattFox

    Thanks for doing this on such short notice Jared

    posted in Dashboard Designer & Custom AngularJS Pages read more
  • MattFox MattFox

    That wholly depends on your preferred context setting. If you want updates with the timestamp, use update for the context, otherwise set to change instead.

    change the dropdown under the meta point script for context to only work on changes as opposed to value updates.

    0_1538946589483_7a30d5ed-c6a2-4b33-abb0-4d89389f6d5f-image.png

    To
    0_1538946615470_c7b58929-268f-4850-9571-0150e6754fa2-image.png

    However to make things easier for yourself and ensure your values are the same and prevent running code as well, you could run a series of if statements to check if

    point.value!==point.lastValue
    

    and simply return the metapoint's current value if the statement is false.

    That's my ten cents worth.

    Fox

    posted in User help read more