Alibek

Create custom PI Vision symbol that enables user to see the difference between two attributes using time-series

Blog Post created by Alibek on Feb 10, 2019

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.

Outcomes