Help required on HabPanel Widget using angular directive

I am trying to create a new widget to show Agile Octopus energy prices based upon an example found in this forum using Apache ECharts. When running standalone with hard-coded data, I achieve the (basic) chart that I’m testing with. I’m struggling to integrate this with items from OpenHab.

Can anyone offer some assistance, please?

The code below has a hardcoded dataset “data” as all the OHService functions I invoke appear to resolve to null.

widget html:

<div style="width:700px;height:180px"
     oc-lazy-load="{ serie: true, files: ['/static/echarts.min.js', '/static/extension/dataTool.min.js', '/static/extension/bmap.min.js', '/static/heatMap.js']}">
  <energy-clock></energy-clock>
</div>

heatMap.js:

angular
  .module('app.widgets')
  .directive('energyClock', ['OHService', function (OHService) {
    return {
        restrict: 'E',
        scope: {
        // optionally add attributes here
        // orientation: '=',
        // ...
        },
        link: function (scope, elem, attrs) {
            var hours = ['0000', '0030', '0100', '0130', '0200', '0230', '0300','0330', '0400', '0430','0500','0530','0600', '0630', '0700', '0730', '0800', '08
30','0900', '0930', '1000', '1030', '1100', '1130','1200', '1230', '1300', '1330', '1400', '1430', '1500','1530', '1600', '1630','1700','1730','1800', '1830', '
1900', '1930', '2000', '2030','2100', '2130', '2200', '2230', '2300', '2330'];
            var myChart = echarts.init(elem[0].parentElement, 'dark');
            var data = [
		    [0,44,8.6415],[0,45,5.292],[0,46,8.1375],[0,47,4.872],[1,0,5.733],[1,1,5.733],[1,2,5.838],[1,3,5.733],[1,4,6.237],[1,5,4.41],[1,6,5.733],[1,
7,3.99],[1,8,4.1895],[1,9,3.087],[1,10,3.087],[1,11,5.4915],[1,12,6.615],[1,13,9.702],[1,14,8.82],[1,15,11.466],[1,16,9.345],[1,17,9.702],[1,18,9.8805],[1,19,9.
8385],[1,20,8.9985],[1,21,8.82],[1,22,9.9225],[1,23,10.584],[1,24,11.025],[1,25,11.2455],[1,26,10.8255],[1,27,9.9225],[1,28,9.366],[1,29,8.4],[1,30,8.82],[1,31,
10.143],[1,32,22.05],[1,33,28.539],[1,34,29.3475],[1,35,29.085],[1,36,26.88],[1,37,23.373],[1,38,11.004],[1,39,8.82],[1,40,10.059],[1,41,9.702],[1,42,7.938],[1,
43,5.733],[1,44,8.379],[1,45,3.528]
	    	];
	    console.log("Started energyClock");
            console.log(OHService.getItems());
	    var dataIn = OHService.getItem('OctopusMQTTThing_OctopusDataSet');
	    console.log(dataIn);
	    var data2 = JSON.parse(dataIn);
	    console.log(data2);
	    //var peakLow = OHService.getItem('OctopusMQTTThing_OctopusPeakLow');
	    var peakLow = 22.2;
	    console.log(peakLow);
	    //var average = OHService.getItem('OctopusMQTTThing_OctopusAverage');
	    var average = 10.4;
	    console.log(average);
            data = data.map(function(item) {
                return [item[1], item[0], item[2] || '-'];
            })
            myChart.setOption(
                option = {
                    tooltip: {
                        trigger: 'item',
                    },
                    grid: {
                        height: '50%',
                        top: '10%'
                    },
                    xAxis: {
                        type: 'category',
                        data: hours,
                        splitArea: {
                            show: true
                        }
                    },
                    yAxis: {
                        type: 'category',
                        data: ['tomorrow','today'],
                        splitArea: {
                            show: true
                        }
                    },
                    visualMap: {
			type: 'piecewise',
			itemGap: 5,
                        min: -10,
			max: 20,
                        calculable: true,
			itemWidth: 10,
			itemGap: 15,
			pieces: [
				{max: 0, color: 'blue', label: 'Negative'},
				{min: 0, max: average,  color: 'green', label: 'Below Mean'},
				{min: average, max: peakLow,  color: 'orange', label: 'Above Mean'},
				{min: peakLow, color: 'red', label: 'High'}
			],
                        orient: 'horizontal',
                        left: 'center',
                        bottom: '10%'
                    },
                    series: [{
                        name: 'Price',
                        type: 'heatmap',
                        data: data,
                        label: {
                            show: false
                        },
                        emphasis: {
                            itemStyle: {
                                shadowBlur: 10,
                                shadowColor: 'rgba(0, 0, 0, 0.5)'
                            }
                        }
                    }]
                }
            );
        }
   };
}]);

I’ve made some progress, it would appear that the OHService is not populated until the page has been updated a few times. I will continue to investigate as to how to ensure that it is available from the first instance. I also notice that an update to the Items does not reflect in the page, I will continue to investigate this too.

If anyone has any tips on how not to “reinvent the wheel”, please let me know. Thanks.

Done and working as expected. Using the timeline.widget.js GitHub code as reference I managed to work out how to get this done and it’s all working.

For reference, in case anyone is looking to do something similar, here is the javascript:

angular
    .module('app.widgets')
    .directive('widgetEnergyClock', widgetEnergyClock)
    // .controller('WidgetSettingsCtrl-energyClock', WidgetSettingsCtrlEnergyClock)
    .config(function (WidgetsProvider) { 
        WidgetsProvider.$get().registerType({
            type: 'energyClock',
            displayName: 'EnergyClock',
            icon: 'tasks',
            description: 'Displays a graphical representation of Agile Octopus Rates'
        });
    });

    widgetEnergyClock.$inject = ['$rootScope', '$interval', 'OHService', 'tmhDynamicLocaleCache'];
    function widgetEnergyClock($rootScope, $interval, OHService, tmhDynamicLocaleCache) {
        var directive = {
            bindToController: true,
            controller: EnergyClockController,
            controllerAs: 'vm',
            link: link,
            restrict: 'AE',
            scope: {
                ngModel: '='
            }
        };
        return directive;


        function link(scope, element, attrs) {
            console.log("Widget: in link()");

            function init() {
                var el = element[0].parentNode;
                scope.myChart = echarts.init(el, 'dark');                
            }

            function redraw() {
                
                scope.myChart.setOption(
                    option = {
                        tooltip: {
                            trigger: 'item',
                        },
                        grid: {
                            height: '50%',
                            top: '10%'
                        },
                        xAxis: {
                            type: 'category',
                            data: scope.hours,
                            splitArea: {
                                show: true
                            }
                        },
                        yAxis: {
                            type: 'category',
                            data: ['tomorrow','today'],
                            splitArea: {
                                show: true
                            }
                        },
                        visualMap: {
                    		type: 'continuous',
                            min: -10,
                		    max: 20,
                            calculable: true,
                            pieces: [
                    			{max: 0, color: 'blue', label: 'Negative'},
                    			{min: 0, max: scope.average,  color: 'green', label: 'Below Mean'},
                    			{min: scope.average, max: scope.peakLow,  color: 'orange', label: 'Above Mean'},
                    			{min: scope.peakLow, color: 'red', label: 'High'}
                    		],
                            orient: 'horizontal',
                            left: 'center',
                            bottom: '10%'
                        },
                        series: [{
                            name: 'Price',
                            type: 'heatmap',
                            data: scope.data,
                            label: {
                                show: false
                            },
                            emphasis: {
                                itemStyle: {
                                    shadowBlur: 10,
                                    shadowColor: 'rgba(0, 0, 0, 0.5)'
                                }
                            }
                        }]
                    }
                );
            }

            var dataWatcher = scope.$watch("data", redraw);
            var resizeHandler = scope.$on('gridster-resized', redraw);

            init();

            scope.$on('$destroy', dataWatcher);
            scope.$on('$destroy', resizeHandler);
            if (scope.vm.refreshInterval) {
                element.on('$destroy', function () {
                    $interval.cancel(scope.vm.refreshInterval);
                });
            }

        }
    }

    EnergyClockController.$inject = ['$rootScope', '$scope', '$timeout', '$interval', '$http', '$q', 'OHService'];
    function EnergyClockController ($rootScope, $scope, $timeout, $interval, $http, $q, OHService) {
        var vm = this;
        this.widget = this.ngModel;

        $scope.hours = ['0000', '0030', '0100', '0130', '0200', '0230', '0300','0330', '0400', '0430', '0500','0530','0600', '0630', '0700', '0730', '0800', '0830','0900', '0930', '1000', '1030', '1100', '1130','1200', '1230', '1300', '1330', '1400', '1430', '1500','1530', '1600', '1630', '1700','1730','1800', '1830', '1900', '1930', '2000', '2030','2100', '2130', '2200', '2230', '2300', '2330'];
        $scope.myChart = null;

        function getData() {

            var peakLowItem = OHService.getItem('OctopusMQTTThing_OctopusPeakLow');
            peakLow = 99.0;
            average = 0.0;
            if(peakLowItem)
                $scope.peakLow = parseFloat(peakLowItem.state);
            var averageItem = OHService.getItem('OctopusMQTTThing_OctopusAverage');
            if(averageItem)
                $scope.average = parseFloat(averageItem.state);

            vm.dataIn = OHService.getItem('OctopusMQTTThing_OctopusDataSet');
            data = [];

            if(vm.dataIn && vm.dataIn.state) {
                data = JSON.parse("[" + vm.dataIn.state + "]");  
            }
            data = data.map(function(item) {
                return [item[1], item[0], item[2] || '-'];
            });
            $scope.data = data;
        }

        OHService.getLocale().then(function (locale) {
            $scope.locale = locale;
            vm.refreshInterval = $interval(getData, 60000);
            getData();
        });
    }

and this is a snapshot of the output:

It removes blocks as they go into the past and pulls the latest data from OpenHab which I push in from Octopus Energy on a daily basis using a NodeRed flow.