Challenge:
Create custom PI Vision symbol that enables user to see the difference between two attributes during time-series.
The restrictions are as follows:
- At least two attributes must be selected
- Attributes must use identical measurements (e.g. kW)
Basis
Standard PI Vision symbol template will be used as basis that is available here with symboltemplate.js:
https://pisquare.osisoft.com/docs/DOC-3310-exercise-template-fileszip
Update Symboltemplate.js
var definition = {…} part, which is responsible for visualization object creation, will be updated as follows:
var definition = {
typeName: “difference”, // links js file with html template file, hence existing file must be renamed to sym-difference.js and html template must be named as sym-difference-template.html
visObjectType: symbolVis,
datasourceBehavior: PV.Extensibility.Enums.DatasourceBehaviors.Multiple, //Use multiple-value custom symbol
getDefaultConfig: function(){
return {
DataShape: ‘Timeseries’, //take values from timeseries
Height: 400, //define height of the symbol object
Width: 600 //define width of the symbol object
}
}
}
Create getConfig function which returns config for the Chart:
function getConfig(valueAxesTitle){
return {
“type”: “serial”,
“categoryField”: “category”,
“startDuration”: 1,
“categoryAxis”: {
“gridPosition”: “start”
},
“trendLines”: [],
“graphs”: [
{
“balloonText”: “[[title]] on [[category]]:[[value]]”,
“fillAlphas”: 1,
“id”: “AmGraph-1”,
“title”: “lowest value”,
“type”: “column”,
“valueField”: “column-1”
},
{
“balloonText”: “[[title]] on [[category]]:[[value]]”,
“fillAlphas”: 1,
“id”: “AmGraph-2”,
“title”: “difference”,
“type”: “column”,
“valueField”: “column-2”
}
],
“guides”: [],
“valueAxes”: [
{
“id”: “ValueAxis-1”,
“stackType”: “regular”,
“title”: valueAxesTitle
}
],
“allLabels”: [],
“balloon”: {},
“legend”: {
“enabled”: true,
“useGraphSettings”: true
},
“dataProvider”: []
}
}
Update symbolVis.prototype.init = function(scope, elem) {…} function which adds logic to be performed at startup:
symbolVis.prototype.init = function(scope, elem) {
var container = elem.find(‘#container’)[0]; //link div with container id to Chart container
container.id = “barChart_” + scope.symbol.Name; //set unique name for Chart container
var chart = null; //initialize Chart
var isInitialData = true; //perform action only once
var dataByTime = [];
var lastTime = [];
function convertToChart(time,lowestValue,difference){ //create time based category for Chart
return {
“category”: time,
“column-1”: lowestValue,
“column-2”: difference
}
}
function updateTitle(label1, label2){ //create Title for Chart
return [{
text: ‘Difference between ‘ + label1 + ‘ and ‘ + label2
}]
}
this.onDataUpdate = dataUpdate; //link dataUpdate function to event onDataUpdate
function dataUpdate(data){
if(!data)return; //skip, since initially data is null
var firstAttribute = data.Data[0];
var secondAttribute = data.Data[1];
if(!data.Data[1]){ //at least two attributes shall be selected
scope.Container = ‘Use 2 attributes!’;
return;
}
if(firstAttribute.Label){ //sporadic updates
if(firstAttribute.Units != secondAttribute.Units) { //compare only similar units
scope.Container = ‘Units shall be equal!’;
return;
}
var time = data.Data[0].Values[data.Data[0].Values.length-1].Time; //avoid similar data
if(time==lastTime) return;
lastTime = time;
if( isInitialData ){ //performed only once, to get data for chart initialization
chart = AmCharts.makeChart(container.id, getConfig(firstAttribute.Units)); //initialize chart with unit as axis title
var title = updateTitle(firstAttribute.Label, secondAttribute.Label);
chart.titles = title; //update title
scope.Label1 = firstAttribute.Label;
scope.Label2 = secondAttribute.Label;
isInitialData = false;
}
//set new data for chart
var length = firstAttribute.Values.length;
var firstValue = data.Data[0].Values[data.Data[0].Values.length-1].Value;
var secondValue = data.Data[1].Values[data.Data[1].Values.length-1].Value;
var lowestValue = firstValue<secondValue?secondValue:firstValue;
var highestValue = firstValue>secondValue?secondValue:firstValue;
var difference = highestValue-lowestValue;
var dataprovider = convertToChart(time, lowestValue, difference);
chart.dataProvider.push(dataprovider);
chart.validateData(); //update chart
dataByTime.push({time:time, firstValue:firstValue, secondValue:secondValue, difference:difference});
scope.dataByTime = dataByTime;
} else return;
}
};
Create html template file naming it as required by var definition = {…} part – sym-difference-template.html:
<div id=’container’ style=”width: 100%; height: 100%”>
{{ Container }}
</div>
<div>
<style type=’text/css’>
#diffTable {
font-family:Verdana;
font-size:11px;border: 1px solid #1C6EA4;
}
#diffTable td {
border: 1px solid #1C6EA4;
}
</style>
<table id=’diffTable’>
<tr style=”font-weight: bold;text-align:center;”>
<td>Time<td> {{ Label1 }} <td> {{ Label2 }} <td>Difference
</tr>
<tr ng-repeat=’item in dataByTime’ >
<td> {{ item.time }} <td> {{ item.firstValue }} <td> {{ item.secondValue }} <td> {{ item.difference }}
</tr>
</table>
</div>
Deployment
After completion, these two files shall be placed in folder:
%pihome64%PIVision\Scripts\app\editor\symbols\ext
Drop down them on the display and wait for some time for the data:
Ways for improvement and conclusion
After a while, generated chart will become unreadable, due to overwhelming with displayed values:
The solution can be the FILO method, when new value is added, and oldest value is removed from the chart.
This custom PI Vision symbol can be used to get basic understanding of symbols development or as ready solution to obtain the difference of values of two attributes during some time span.
You should take part in a contest for one of the best blogs on the web. I will recommend this site!