Highchart angular directive doesn't redraw fro

2019-07-17 04:09发布

问题:

I have an AngularJs app that retrieves data from API and populates them into table. Then, I'm using Highcharts to draw chart based on that table. It is working fine if I'm using static data in the table, but it doesn't work when I update the table through AJAX. It doesn't get redraw.

Directive:

app.directive('highChart', function ($parse) {
    return {        
        link: function(scope, element, attrs) {
            scope.$watch('chartConfig', function (newVal) {
                if (newVal) {
                    var props = $parse(attrs.highChart)(scope);
                    props.chart.renderTo = element[0];
                    new Highcharts.Chart(props);
                }
            });
        }
    };
});

Controller:

appControllers.controller('SummaryController', ['$scope', '$timeout', 'Summaries',
    function ($scope, $timeout, Summaries) {
        // Request from API
        Summaries.get({ table: 'level'}).$promise.then(
            // success callback
            function(data) {
                $scope.levels = data.level[0][today];
                $scope.chartConfig = {
                    data: {
                        table: document.getElementById('daily-level')
                    },
                    chart: {
                        type: 'column'
                    },
                    title: {
                        text: 'Data extracted from a HTML table in the page'
                    },
                    yAxis: {
                        allowDecimals: false,
                        title: {
                            text: 'Units'
                        }
                    },
                    tooltip: {
                        formatter: function () {
                            return '<b>' + this.series.name + '</b><br/>' +
                                this.y + ' ' + this.x;
                        }
                    }
                 };
            }
    }]);

HTML

<div id="top-chart" high-chart="chartConfig"></div>

It just displays an empty chart. My table does get updated by the AJAX request. I even did check for console.log(props) inside the directive and props.data.table does show the updated table, but somehow the chart doesn't get redraw. I've tried multiple ways to do this, but all come down to the same result. Currently, I'm just using jQuery timeout to refresh the chart, it works but that means I'm using DOM manipulation inside controller. I'm trying to achieve this using directive.

回答1:

Ok. I managed to get it to work. I think the problem is that, Highcharts does not aware about the changes or if it knows about the changes, the DOM at that moment hasn't finish rendering. This is the working version of the code.

Directive:

app.directive('highchart', function($parse, $timeout) {
    return {
        restrict: 'E',
        template: '<div></div>',
        replace: true,
        link: function(scope, element, attrs) {
            var config = $parse(attrs.config)(scope);
            $(element[0]).highcharts(config);

            scope.$watch(attrs.watch, function(newVal) {
                if (newVal) {
                    var complete = function (options) {
                        var chart = $(element[0]).highcharts();
                        // capture all available series
                        var allSeries = chart.series;
                        for (var i = 0; i < allSeries.length; i++) {
                            allSeries[i].setData(options.series[i].data, false);
                        }

                        chart.redraw();
                    };

                    // doesn't work without the timeout 
                    $timeout(function() {
                        Highcharts.data({
                            table: config.data.table,
                            complete: complete
                        });   
                    }, 0);
                }
            });
        }
    };
});

In controller, we can setup the config.

    $scope.chartConfig = {
         // config here
    };

To use it:

<highchart config="chartConfig" watch="levels"></highchart>

where the attribute config is bind to the $scope.chartConfig and watch is the trigger for watch.$scope() in the directive. When $scope.levels changes in controller, it would re-render the chart.

I don't actually like the dependency of the directive to the watch attribute that I have right now, but I'm not sure what is the best or other way to do this. If anyone have a better idea, do let me know.

Please note that this will only work to convert table to highchart. For others, you might need to use other angularjs highcharts directive.



回答2:

In given below example, you can update your graph as per updated data in database using watch.It keeps on checking for new data and makes changes respectively:

app.directive('line',['$compile', function ($compile) {
    return {
        restrict: 'E',
        replace: true,
        template: '<div></div>',
        scope: {
            data: '=',
        },
        link: function (scope, element) {
            scope.lineGraphInstance = Highcharts.chart(element[0],{
                chart: {
                    type: 'spline',
                    style: {
                        fontSize: '8px'
                    },
                    spacingBottom: scope.data.spacingBottom,
                    spacingTop: scope.data.spacingTop,
                    spacingLeft: scope.data.spacingLeft,
                    spacingRight: scope.data.spacingRight,
                },
                title: {
                    text: '',
                },                      
                xAxis: {
                    type: 'datetime',
                    title: {
                        text: scope.data.xtitle,
                        enabled: false
                    },
                    labels: {
                        style: {
                            fontSize: '8px'
                        },                  
                        autoRotation: 0,
                        formatter: function () {
                            var x=this.value;
                            if (scope.data.hasOwnProperty("dateformat"))
                                x= Highcharts.dateFormat(scope.data.dateformat, this.value);
                            if (scope.data.hasOwnProperty("xSuffix"))
                                x=x+scope.data.xSuffix;         
                            if(scope.data.hasOwnProperty("xPrefix"))
                                x=scope.data.xPrefix+x;

                            return x;
                        },
                    enabled:scope.data.xtitleEnable
                    },
                    tickWidth: 3,
                    tickLength:scope.data.tickLength,
                    tickInterval: 3600 * 100
                        },
                yAxis: {
                    opposite: true,
                    title: {
                        text: scope.data.ytitle,
                        enabled: false
                    },
                    min: 0,
                    labels: {
                        style: {
                            fontSize: '10px'
                        },
                        formatter: function () {
                            var y=this.value;
                            if (scope.data.hasOwnProperty("ySuffix"))
                                y=y+scope.data.ySuffix;         
                            if(scope.data.hasOwnProperty("yPrefix"))
                                y=scope.data.yPrefix+y;

                            return y;
                        },
                    },
                    plotLines: [{
                        value: scope.data.ucl,
                        color: '#F39C12',
                        dashStyle: 'shortdash',
                        width: 1,
                    }, {
                        value: scope.data.lcl,
                        color: '#F39C12',
                        dashStyle: 'shortdash',
                        width: 1,
                    }]
                },
                tooltip: {
                    headerFormat: '<b>{series.name}</b><br>',
                    pointFormat: '{point.x:%e. %b}: {point.y:.2f} '
                },
                plotOptions: {
                    spline: {
                        marker: {
                            enabled: true
                        }
                    }
                },
                legend:{
                    enabled:false
                },
                series:[{data:scope.data.data,
                marker: {
                    enabled: true,
                    radius: 2
                },
                lineWidth: 1
                }],
                credits: {
                    enabled: false
                },  
                exporting: { enabled: false 
                }
            }); 
            ***scope.$watch('data', function (newValue, oldValue) {
                if(newValue && newValue.data){
                    var graphData=newValue.data[0].data.slice(-5);
                    scope.lineGraphInstance.series[0].setData(graphData, true);
                }
            }, true);
        }***
    };
}]); 


回答3:

Have you tried to set factory, which will use $resource http://docs.angularjs.org/api/ngResource.$resource inside and then iniliatize chart after calling getting values from factory?

EDIT: updated example http://jsfiddle.net/csTzc/184/