尽管已排序数据,如何提高 Highcharts 的性能并避免错误 15?

How to improve performance of Highcharts and avoid error 15 inspite of sorted data?

我正在尝试使用 navigator 在 highcharts 中创建甘特图表示。我从服务器收到 JSON 响应(以下是典型的响应结构)。为了创建甘特图表示,我在两点之间创建了一条线。每个点都有一个 start_dateend_date,为了创建这种表示,我在每个点的 start_dateend_date 之间画了一条线(我已经完成了)。

来自服务器的响应结构

{
  "took": 312,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 4115,
    "max_score": 1,
    "hits": [

    ]
  },
  "aggregations": {
    "top-tags": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "Process1",
          "doc_count": 6,
          "process": {
            "value": {
              "1449878649000": {
                "start_date": 1449878649000,
                "process_initiator": "lol@surg.com",
                "end_date": 1449878734000,
                "total_seconds": 85
              },
              "1449879753000": {
                "start_date": 1449879753000,
                "process_initiator": "lol@surg.com",
                "end_date": 1449879850000,
                "total_seconds": 97
              },
              "1449881550000": {
                "start_date": 1449881550000,
                "process_initiator": "lol@surg.com",
                "end_date": 1449881631000,
                "total_seconds": 81
              }
            }
          }
        },
        {
          "key": "Process2",
          "doc_count": 1,
          "process": {
            "value": {
              "1449971262000": {
                "start_date": 1449971262000,
                "process_initiator": "lol@surg.com",
                "end_date": 1449971266000,
                "total_seconds": 4
              }
            }
          }
        }
      ]
    }
  }
}

代码也sharing a plunker demo

var app = angular.module('app', []);

app.directive('operationalhighstackstock', function() {
    return {
        restrict: 'E',
        scope: true,
        link: function postLink(scope, element, attrs) {
            scope.$watch('operationHighChartsData', function(values) {
                new Highcharts.StockChart(values);
            });
        }
    };
});
//2014-11-30T18:15:25.000-08:00
app.controller('MainCtrl', ['$scope', function($scope) {
    $scope.excludeValue = {
        data: 0
    };
    $scope.isExcludeNeeded = true;
    var opExcludeMinutes = 1,
        AGENT_NAMES = "agent_names",
        colorCodes = ["#8CC051", "#967BDC", "#5D9CEC", "#FB6E52", "#EC87BF", "#46CEAD", "#FFCE55", "#193441", "#193441", "#BEEB9F", "#E3DB9A", "#917A56"];

    var setSummaryDisplay = function(e) {
        if (e.min === null || e.max === null)
            $scope.hideRangeSlider = true;
        else
            $scope.hideRangeSlider = false;
        $scope.minimumSelectedValue = e.min;
        $scope.maximumSelectedValue = e.max;
    }

    var getHichartsData = function(result) {
        var tasksArr = [],
            seriesArr = [],
            userArr = [],
            processArr = [];

        var agentSeries = [],
            agentData = {},
            processSeries = [],
            taskData = {},
            idx = 0,
            opProcessBucket = esResponse.aggregations["top-tags"].buckets,
            seriesData = {};
        var opBucketLength = opProcessBucket.length;
        for (var opProcessBucketIndex = 0; opProcessBucketIndex < opBucketLength; ++opProcessBucketIndex) {
            //opProcessBucket.forEach(function(processEntry) {
            //if (opProcessBucket[opProcessBucketIndex]["key"] == $scope.gpDropDownTitle) {
            var intervalBucket = opProcessBucket[opProcessBucketIndex]["process"]["value"], //opProcessBucket[opProcessBucketIndex]["top_tag_hits"]["hits"]["hits"],
                intervalArr = [],
                tasksIntervalArr = [],
                opTaskidObj = {},
                opTaskidIntervalObj = {},
                process_name = null,
                sortElementArr = [];
            for (var key in intervalBucket) {
                //intervalBucket.forEach(function(intervalEntry, intervalIndex) {
                var intervalObj = {},
                    intervalObj2ndpoint = {},
                    processIntervalObj = {},
                    tintervalArr = [],
                    intervalIndex = 0,
                    start_temp = parseInt(key),
                    end_temp = intervalBucket[key].end_date; //start_temp = intervalBucket[key].start_date, end_temp = intervalBucket[key].end_date;
                //added here since response contains null value and data load will take almost 1 date, verified with Bhavesh
                $scope.currentDateTime = new Date().getTime();
                if (end_temp == null)
                    end_temp = $scope.currentDateTime;
                var st = new Date(moment(start_temp).valueOf()).getTime();
                var et = new Date(moment(end_temp).valueOf()).getTime();
                var duration = moment.duration(moment(et).diff(moment(st)));
                var minutes = duration.asMinutes();
                if (minutes > $scope.excludeValue.data && $scope.isExcludeNeeded) {
                    if (intervalIndex == 0 || process_name == null) {
                        process_name = opProcessBucket[opProcessBucketIndex]["key"];
                        processArr.push(opProcessBucket[opProcessBucketIndex]["key"]);
                    }
                    userArr.push(intervalBucket[key].process_initiator);
                    processIntervalObj["task_id"] = opProcessBucket[opProcessBucketIndex]["key"];
                    processIntervalObj["from"] = st;
                    var lFromtime = moment.utc(st).toDate();
                    lFromtime = moment(lFromtime).format('MM/DD/YY HH:mm');
                    var lTotime = moment.utc(et).toDate();
                    lTotime = moment(lTotime).format('MM/DD/YY HH:mm');
                    processIntervalObj["to"] = et;
                    processIntervalObj["color"] = "#FFCC4E";
                    processIntervalObj["fromDateString"] = lFromtime;
                    processIntervalObj["toDateString"] = lTotime;
                    processIntervalObj["process_initiator"] = intervalBucket[key].process_initiator == null ? 'Unknown' : intervalBucket[key].process_initiator;
                    processIntervalObj["total_seconds"] = intervalBucket[key].total_seconds;
                    //sortElementArr.push(intervalEntry["sort"][0]);
                    tasksIntervalArr.push(processIntervalObj);
                }
            }
            opTaskidObj["name"] = process_name;
            opTaskidIntervalObj["name"] = process_name;
            opTaskidObj["data"] = [];
            opTaskidIntervalObj["intervals"] = tasksIntervalArr;
            opTaskidIntervalObj["intervals"] = tasksIntervalArr;
            idx++;
            if (tasksIntervalArr.length > 0) {
                processSeries.push(opTaskidIntervalObj);
                agentSeries.push(opTaskidObj);
            }
            //}
        }

        seriesData["title"] = "Test"; //item["key"];

        var series = [];
        (processSeries.reverse()).forEach(function(task, i) {
            var item = {
                name: task.name,
                data: [],
                turboThreshold: 1100000
            };
            (task.intervals).forEach(function(interval, j) {
                item.data.push({
                    task_id: interval.task_id,
                    x: interval.from,
                    y: i,
                    from: interval.from,
                    to: interval.to,
                    color: interval.color,
                    fromDateString: interval.fromDateString,
                    toDateString: interval.toDateString,
                    total_seconds: interval.total_seconds,
                    process_initiator: interval.process_initiator
                }, {
                    task_id: interval.task_id,
                    x: interval.to,
                    y: i,
                    from: interval.from,
                    to: interval.to,
                    color: interval.color,
                    fromDateString: interval.fromDateString,
                    toDateString: interval.toDateString,
                    total_seconds: interval.total_seconds,
                    process_initiator: interval.process_initiator
                });
                // add a null value between intervals
                if (task.intervals[j + 1]) {
                    item.data.push([(interval.to + task.intervals[j + 1].from) / 2, null]);
                }
            });
            series.push(item);
        })
        seriesData["data"] = series;
        seriesData["tasks"] = processSeries;
        seriesArr.push(seriesData);
        return seriesArr;
    }

    $scope.agentSeriesData = getHichartsData(esResponse);

    var tasks = $scope.agentSeriesData[0].tasks;
    var seriesData = $scope.agentSeriesData[0].data;
    var xAxisStepping = 1 * 3600 * 1000;
    var chart = new Highcharts.StockChart({
        chart: {
            renderTo: 'container',
            height: 600,
            events: {
                load: function(e) {
                    var max = this.xAxis[0].max;
                    var range = (24 * 3600 * 1000) * 7; // one day * 7
                    if ($scope.isInit || $scope.filterReseted) {
                        $scope.filterReseted = false;
                        this.xAxis[0].setExtremes(max - range, max);
                    }
                    setSummaryDisplay.call(this.xAxis[0], {
                        trigger: "navigator",
                        min: this.xAxis[0].min,
                        max: this.xAxis[0].max
                    });
                }
            }
        },
        title: {},
        credits: {
            enabled: false
        },
        xAxis: {
            type: 'datetime',
            gridLineWidth: 1,
            tickInterval: xAxisStepping,
            //ordinal:false,
            dateTimeLabelFormats: {
                month: '%b %e, %Y'
            },
            events: {
                afterSetExtremes: setSummaryDisplay
            },
            minRange: 1000
        },
        yAxis: {
            tickInterval: 1,
            gridLineWidth: 1,
            labels: {
                enabled: false,
                formatter: function() {
                    if (tasks[this.value]) {
                        return tasks[this.value].name;
                    }
                }
            },
            startOnTick: false,
            endOnTick: false,
            title: {
                text: 'Process'
            }
        },
        animation: false,
        rangeSelector: {
            enabled: false
        },
        navigator: {
            enabled: true
        },
        legend: {
            enabled: false
        },
        tooltip: {
            shared: false,
            formatter: function() {
                var str = '';
                str += 'Process: ' + this.series.name + '<br>';
                str += 'From: ' + Highcharts.dateFormat('%m/%d/%y %H:%M:%S', this.point.from) + '<br>';
                str += 'To: ' + Highcharts.dateFormat('%m/%d/%y %H:%M:%S', this.point.to) + '<br>';
                return str;
            }
        },
        plotOptions: {
            line: {
                lineWidth: 10,
                marker: {
                    enabled: false
                },
                dataLabels: {
                    enabled: false,
                    borderRadius: 5,
                    borderWidth: 1,
                    y: -6,
                    formatter: function() {
                        return this.series.name;
                    }
                },
                states: {
                    hover: {
                        lineWidth: 10
                    }
                }
            },
            series: {
                cursor: 'pointer',
                animation: false,
                point: {
                    events: {
                        click: function() {
                            $scope.selectedGuide = this.series.name;
                            //$scope.showTableView();
                        }
                    }
                },
                turboThreshold: 100000000,
                dataGrouping: {
                    enabled: false
                }
            }
        },
        scrollbar: {
            enabled: false
        },
        series: seriesData
    });

    $scope.operationHighChartsData = chart;

}]);

我已经对数据进行了排序(升序),但我仍然收到 Highcharts error #15: www.highcharts.com/errors/15 数以千计的错误(主要是 80k +),导致浏览器挂起。

可能是什么问题,我怎样才能摆脱它并提高性能? Sharing a plunker which has code 并且错误数量相对较少。

注意:我正在使用Highstock JS v2.1.5

这段代码有两个问题: 首先,您需要按 X 的升序对系列进行排序。我不想调试有关如何构建数据的代码,所以我在最后添加了一个简单的循环来对所有内容进行排序。

for (var i in seriesData) {
  seriesData[i].data.sort(function(a, b) {
    if (a.x > b.x) {
      return 1;
    }

    if (b.x > a.x) {
      return -1;
    }

    return 0;
  });
}

另一个问题是数据数组包含正确的数据,因为这一行

if (task.intervals[j + 1]) {
    item.data.push([(interval.to + task.intervals[j + 1].from) / 2, null]);
}

所以我改成了这个

// add a null value between intervals
if (task.intervals[j + 1]) {
    item.data.push({
        task_id: interval.task_id,
        x: (interval.to + task.intervals[j + 1].from) / 2,
        y: null,
        from: (interval.to + task.intervals[j + 1].from) / 2,
        to: (interval.to + task.intervals[j + 1].from) / 2
    });
}

这是固定的plnkr http://plnkr.co/edit/OEMuVfTMhHNQsTYGUyuy?p=preview

read this link提高图表性能。几个月前,Highcharts 发布了 boost.js 以提高具有数百万数据点的图表性能。