Create KPI indicators component

Creating general KPI meta data points and showing them in cards.

July 13th, 2020


This is the 6th article of a Dashboard development series. You can check all the articles by clicking here

In this article we are going to build the next KPI indicadors component:

General KPI indicators

This component shows general Key performance indicators in 3 cards, occupied sites, máximum kW/ton ratio, and consumed energy. Let's start by creating the kpiIndicators.js and kpiIndicators.html files in the components/kpiIndicators directory:

kpiIndicators.js

/**
 * @copyright 2020 {@link http://infiniteautomation.com|Infinite Automation Systems, Inc.} All rights reserved.
 * @author Luis Güette
 */

define(['angular', 'require'], (angular, require) => {
    'use strict';

    class KpiIndicatorsController {
        static get $$ngIsClass() {
            return true;
        }

        static get $inject() {
            return [];
        }

        constructor() {

        }

        $onInit() {

        }
    }

    return {
        bindings: {

        },
        controller: KpiIndicatorsController,
        templateUrl: require.toUrl('./kpiIndicators.html')
    };
});

kpiIndicators.html

<h1>KPI Indicators</h1>

Then, we need to import it in hvac.js module:

hvac.js

/**
 * @copyright 2020 {@link http://infiniteautomation.com|Infinite Automation Systems, Inc.} All rights reserved.
 * @author Luis Güette
 */

define([
    'angular',
    'require',

    './pages/overview/overview.js',

    './components/map/map.js',
    './components/selectedUnitCard/selectedUnitCard.js',
    './components/unitsTable/unitsTable.js',
    './components/kpiIndicators/kpiIndicators.js',

    './services/unit.js'
], (
    angular,
    require,
    overview,
    map,
    selectedUnitCard,
    unitsTable,
    kpiIndicators,
    unitService
) => {
    'use strict';

    const hvacModule = angular
        .module('hvacModule', ['maUiApp'])
        .component('hvacOverview', overview)
        .component('hvacMap', map)
        .component('hvacSelectedUnitCard', selectedUnitCard)
        .component('hvacUnitsTable', unitsTable)
        .component('hvacKpiIndicators', kpiIndicators)

        .factory('hvacUnit', unitService);

    hvacModule.config([
        'maUiMenuProvider',
        (maUiMenuProvider) => {
            maUiMenuProvider.registerMenuItems([
                {
                    name: 'ui.overview',
                    url: '/overview',
                    menuIcon: 'map',
                    template: '<hvac-overview></hvac-overview>',
                    menuText: 'Overview',
                    weight: 100
                },
            ]);
        }
    ]);

    return hvacModule;
}); // define

Let's call the hvac-kpi-indicators component in the overview.html file:

overview.html

<div layout="row" layout-wrap layout-align="space-between start">
    <div flex="100" flex-gt-sm="50" flex-gt-md="60">
        <div>
            <hvac-kpi-indicators></hvac-kpi-indicators>
        </div>

        <md-card>
            <md-card-header>
                <p md-colors="::{color: 'accent'}">Active Alarms</p>
            </md-card-header>

            <md-card-content>
                <hvac-map units="$ctrl.units" on-select-unit="$ctrl.onSelectUnit(unit)"></hvac-map>
            </md-card-content>
        </md-card>

        <hvac-selected-unit-card ng-if="$ctrl.selectedUnit" unit="$ctrl.selectedUnit"></hvac-selected-unit-card>
    </div>
    <div flex="100" flex-gt-sm="50" flex-gt-md="40">
        <md-card>
            <md-card-header>
                <p md-colors="::{color: 'accent'}">Units</p>
            </md-card-header>

            <md-card-content>
                <hvac-units-table units="$ctrl.units"></hvac-units-table>
            </md-card-content>
        </md-card>
    </div>
</div>

Reload the page, and you should see something like this:

Overview page

Now, let's create the meta data points that store this KPIs. Go to the Data sources page and "General" meta data source:

Create General meta data source

Occupied units data point

The occupied units data point returns the number of units where the occupancy are equal to true.

For this data point, set the Name as Occupied Units, and the Device name leave it as General.

Occupied unit data point basic information

Go to the Meta data point tab, set the Data type as Numeric, and the Variable name as occupiedUnits.

In the External context points, add all the unit occupancies like this:

Externals context points

In the Sctript section, add the next code:

var values = [u1.value, u2.value, u3.value, u4.value, u5.value, u6.value, u7.value, u8.value, u9.value, u10.value];

return values.filter(function (item) {return item === true}).length;

Now, set the Context event type as Change so it updates only when one of the context points change its value.

Finally, go to the Text renderer properties and set the Format as #, so it shows a number without decimals. Click Save.

Maximum kW/ton ratio data point

This data point calculates the max kW/ton ratio among all units.

Set the Name as Max kW/ton, and the Device name leave it as General.

Max kW/ton data point basic information

Go to the Meta data point tab, set the Data type as Numeric, and the Variable name as maxKwTon.

In the External context points, add all the unit kW/ton ratios like this:

External context points

In the Sctript section, add the next code:

var values = [u1.value, u2.value, u3.value, u4.value, u5.value, u6.value, u7.value, u8.value, u9.value, u10.value];

return Math.max.apply(null, values);

Now, set the Context event type as Change so it updates only when one of the context points change its value.

Finally, go to the Text renderer properties and set the Suffix as kW/ton. Click Save.

Consumed energy data point

This data point calculates the total kWh of all the units together.

Set the Name as Consumed Energy, and the Device name leave it as General.

Consumed energy data point basic information

Go to the Meta data point tab, set the Data type as Numeric, and the Variable name as consumedEnergy.

In the External context points, add all the unit powers like this:

External context points

This data point needs to starts its value in 0. So we check the Settable checkbox. Then, In the Sctript section, add the next code for now:

return 0;

Click Save and go to the data point details page to set its value in 0. Then, return to edit the data point script like this:

return consumedEnergy.value 
    + (u1.past(MINUTE, 5).average / 12)
    + (u2.past(MINUTE, 5).average / 12)
    + (u3.past(MINUTE, 5).average / 12)
    + (u4.past(MINUTE, 5).average / 12) 
    + (u5.past(MINUTE, 5).average / 12)
    + (u6.past(MINUTE, 5).average / 12)
    + (u7.past(MINUTE, 5).average / 12)
    + (u8.past(MINUTE, 5).average / 12)
    + (u9.past(MINUTE, 5).average / 12)
    + (u10.past(MINUTE, 5).average / 12);

Now, set the Timer update event as Cron pattern and set the Cron patter as * 0/5 * * * ? so it updates every 5 minutes.

Finally, go to the Text renderer properties and set the Suffix as kWh. Click Save.

Now that we have the data points. Let's get them in the kpiIndicators component. We also need to pass the total number of units to the component.

kpiIndicators.js

/**
 * @copyright 2020 {@link http://infiniteautomation.com|Infinite Automation Systems, Inc.} All rights reserved.
 * @author Luis Güette
 */

define(['angular', 'require'], (angular, require) => {
    'use strict';

    const POINT_KEYS = {
        'Occupied Units': 'occupiedUnits',
        'Max kW/ton': 'maxKwTon',
        'Consumed Energy': 'energy',
    }

    class KpiIndicatorsController {
        static get $$ngIsClass() {
            return true;
        }

        static get $inject() {
            return ['maPoint'];
        }

        constructor(Point) {
            this.Point = Point;
        }

        $onInit() {
            this.Point
                .buildQuery()
                .eq('deviceName', 'General')
                .limit(1000)
                .query()
                .then(points => {
                    this.points = {}

                    points.forEach(point => {
                        this.points[POINT_KEYS[point.name]] = point
                    })
                })
        }
    }

    return {
        bindings: {
            unitsCount: '<'
        },
        controller: KpiIndicatorsController,
        templateUrl: require.toUrl('./kpiIndicators.html')
    };
});

Finally, we update the kpiIndicators.html and add some styles in hvac.css:

kpiIndicators.html

<div layout="row" layout-wrap layout-align="space-between start">
    <md-card flex="100" flex-gt-md="30">
        <md-card-content>
            <p>Occupied Sites</p>
            <span>
                <ma-point-value point="$ctrl.points.occupiedUnits"></ma-point-value>
                of
                {{ $ctrl.unitsCount }}
            </span>
        </md-card-content>
    </md-card>

    <md-card flex="100" flex-gt-md="30">
        <md-card-content>
            <p>Max kW/ton</p>
            <ma-point-value point="$ctrl.points.maxKwTon"></ma-point-value>
        </md-card-content>
    </md-card>

    <md-card flex="100" flex-gt-md="30">
        <md-card-content>
            <p>Consumed Energy</p>
            <ma-point-value point="$ctrl.points.energy"></ma-point-value>
        </md-card-content>
    </md-card>
</div>

hvac.css

...
hvac-kpi-indicators div md-card md-card-content p {
    margin: 0;
    font-size: 1.25rem;
    font-weight: 700;
    color: var(--ma-accent);
    text-transform: uppercase;
}

hvac-kpi-indicators div md-card md-card-content span {
    color: var(--ma-primary-700);
}
...

Don't forget to pass the units-count parameter to the component:

overview.html

<div layout="row" layout-wrap layout-align="space-between start">
    <div flex="100" flex-gt-sm="50" flex-gt-md="60">
        <hvac-kpi-indicators units-count="$ctrl.units.length"></hvac-kpi-indicators>

        <md-card>
            <md-card-header>
                <p md-colors="::{color: 'accent'}">Active Alarms</p>
            </md-card-header>

            <md-card-content>
                <hvac-map units="$ctrl.units" on-select-unit="$ctrl.onSelectUnit(unit)"></hvac-map>
            </md-card-content>
        </md-card>

        <hvac-selected-unit-card ng-if="$ctrl.selectedUnit" unit="$ctrl.selectedUnit"></hvac-selected-unit-card>
    </div>
    <div flex="100" flex-gt-sm="50" flex-gt-md="40">
        <md-card>
            <md-card-header>
                <p md-colors="::{color: 'accent'}">Units</p>
            </md-card-header>

            <md-card-content>
                <hvac-units-table units="$ctrl.units"></hvac-units-table>
            </md-card-content>
        </md-card>
    </div>
</div>

Reload the page, and you will see something like this:

Overview page

Go to Create energy chart component

Copyright © 2020 Radix IoT, LLC.