The following example is used to create a PI Coresight symbol that uses Highcharts. These instructions build off the Simple Value Symbol Instructions, so please review those first.
-
Create a new file called sym-timeserieschart.js in your PI Coresight installation folder,
INSTALLATION_FOLDER\Scripts\app\editor\symbols\ext
. If theext
folder does not exist, create it. -
Below is the basic skeleton of a new PI Coresight symbol for the time series chart. It sets up the
typeName
,datasourceBehavior
, andgetDefaultConfig
definition options and registers them with the PI Coresight application. For theDataShape
, we are using a TimeSeries shape, which will provide us with raw time series data that the chart will use. We are also specifying theDataQueryMode
to beModePlotValues
. This will return data suitable for plotting over a specified number of intervals.(function (CS) { 'use strict'; var defintion = { typeName: 'timeserieschart', datasourceBehavior: CS.DatasourceBehaviors.Multiple, getDefaultConfig: function() { return { DataShape: 'TimeSeries', DataQueryMode: CS.DataQueryMode.ModePlotValues, Interval: 400, Height: 200, Width: 400 }; } }; CS.symbolCatalog.register(defintion); })(window.Coresight);
-
The next step is to create the HTML template for this symbol. The chart that we will be using only needs a
div
tag to attach to. So we will create a HTML file in the same directory as our JavaScript file and name itsym-timeserieschart-template.html
. We are setting the height and width of thisdiv
to take up the full space available.<div id="container" style="width:100%;height:100%"></div>
-
Now we need to initialize the symbol. We will add an
init
to the definition object and define theinit
function. Theinit
function will have stubs for data updates and resizing events. The resize function is called by the PI Coresight infrastructure anytime the symbol is resized. The resize function is passed the new width and height of the symbol.(function (CS) { 'use strict'; var defintion = { typeName: 'timeserieschart', datasourceBehavior: CS.DatasourceBehaviors.Multiple, getDefaultConfig: function() { return { DataShape: 'TimeSeries', DataQueryMode: CS.DataQueryMode.ModePlotValues, Interval: 400, Height: 200, Width: 400 }; }, init: init }; function init(scope, elem) { function dataUpdate(data) { } function resize(width, height) { } return { dataUpdate: dataUpdate, resize: resize }; } CS.symbolCatalog.register(defintion); })(window.Coresight);
-
Next we must include the HighCharts library so that our symbol can use it. This code comes from Highcharts 4.2.3. Extract the files from the zip and place highcharts.js into
INSTALLATION_FOLDER\Scripts\app\editor\symbols\ext\libraries
. This will make Highcharts available to PI Coresight symbols. -
In the
init
function, we can now begin to define the chart. Highcharts expects to be passed an HTML selector to create the chart. We need to give thediv
element a unique id and then create the chart. Below we are using JavaScript to create a unique id for thediv
element.function init(scope, elem) { var container = elem.find('#container')[0]; var id = 'timeseries_' + Math.random().toString(36).substr(2, 16); container.id = id; function dataUpdate(data) { } function resize(width, height) { } return { dataUpdate: dataUpdate, resize: resize }; }
-
Next we want take the data that is passed to the PI Coresight dataUpdate function and convert this to a format that the chart is expected.
function convertToChartData(data) { var series = []; data.Data.forEach(function(item) { var t = {}; t.name = item.Label; t.data = item.Values.map(function(obj) { var date = new Date(obj.Time); return [Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()), Number(obj.Value)]; }); series.push(t); }); return series; }
-
Now we want to use this convert function in the dataUpdate. We add this function to
init
and we create the cachechart
variable for creating the chart. On the first update,chart
will not be defined, so we must create the chart.function init(scope, elem) { var container = elem.find('#container')[0]; var id = 'timeseries_' + Math.random().toString(36).substr(2, 16); container.id = id; function convertToChartData(data) { var series = []; data.Data.forEach(function(item) { var t = {}; t.name = item.Label; t.data = item.Values.map(function(obj) { var date = new Date(obj.Time); return [Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()), Number(obj.Value)]; }); series.push(t); }); return series; } var chart; function dataUpdate(data) { if(data) { var series = convertToChartData(data); if(!chart) { chart = new Highcharts.Chart({ chart: { type: 'spline', renderTo: id }, title: { text: '' }, xAxis: { type: 'datetime', dateTimeLabelFormats: { // don't display the dummy year month: '%e. %b', year: '%b' }, title: { text: 'Date' } }, plotOptions: { spline: { marker: { enabled: true } } }, series: series }); } } } function resize(width, height) { } return { dataUpdate: dataUpdate, resize: resize }; }
-
Now we have the initial creation of the chart, we must handle subsequent updates. This will happen in data update, when the
chart
variable is already defined. For this, we need to add an else condition toif(!chart)
. Here we will loop through each entry in the series and update the data if it exist, or add it if it does not.function dataUpdate(data) { if(data) { var series = convertToChartData(data); if(!chart) { chart = new Highcharts.Chart({ chart: { type: 'spline', renderTo: id }, title: { text: '' }, xAxis: { type: 'datetime', dateTimeLabelFormats: { // don't display the dummy year month: '%e. %b', year: '%b' }, title: { text: 'Date' } }, plotOptions: { spline: { marker: { enabled: true } } }, series: series }); } else { series.forEach(function(item, index) { if(chart.series[index]) { chart.series[index].setData(item.data); } else { chart.addSeries(item); } }); } } }
-
Lastly, we want to handle resizing the chart appropriately. To do this, we need to use Highcharts setSize method.
function resize(width, height) { if(chart) { chart.setSize(width, height); } }
-
Below is the full version of the implementation file.
(function (CS) { 'use strict'; var def = { typeName: 'timeserieschart', model: CS.MultiSourceSymbol, datasourceBehavior: CS.DatasourceBehaviors.Multiple, getDefaultConfig: function () { return { DataShape: 'TimeSeries', DataQueryMode: CS.DataQueryMode.ModePlotValues, Interval: 400, Height: 200, Width: 400 }; }, init: init }; function init(scope, elem) { var container = elem.find('#container')[0]; var id = 'timeseries_' + Math.random().toString(36).substr(2, 16); container.id = id; function convertToChartData(data) { var series = []; data.Data.forEach(function(item) { var t = {}; t.name = item.Label; t.data = item.Values.map(function(obj) { var date = new Date(obj.Time); return [Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()), Number(obj.Value)]; }); series.push(t); }); return series; } var chart; function dataUpdate(data) { if(data) { var series = convertToChartData(data); if(!chart) { chart = new Highcharts.Chart({ chart: { type: 'spline', renderTo: id }, title: { text: '' }, xAxis: { type: 'datetime', dateTimeLabelFormats: { // don't display the dummy year month: '%e. %b', year: '%b' }, title: { text: 'Date' } }, plotOptions: { spline: { marker: { enabled: true } } }, series: series }); } else { series.forEach(function(item, index) { if(chart.series[index]) { chart.series[index].setData(item.data); } else { chart.addSeries(item); } }); } } } function resize(width, height) { if(chart) { chart.setSize(width, height); } } return { dataUpdate: dataUpdate, resize: resize }; } CS.symbolCatalog.register(def); }(window.Coresight));