AmCharts 股票图表多个 DataSets 日期范围不正确

AmCharts stock chart multiple DataSets date range is incorrect

我们使用来自 AmCharts 的股票图表,通过 ajax 异步加载多个数据集。

不幸的是,它看起来像是整个图表的日期范围,我的意思是 "From" 和 "To" 仅从添加到所有数据集数组的第一个开始设置。

假设您有 3 个数据集,并且每个数据集的开始和结束日期都可能完全不同:

 <script id="data-set-1" type="application/json">
        [
            {"date": "2016-10-02T10:00:00", "value": 23.8},
            {"date": "2016-10-02T10:05:00", "value": 16.8},
            {"date": "2016-10-02T10:10:00", "value": 20.5}
        ] 
</script>
<!-- This dataset has max end date from all 3 datasets -->
<script id="data-set-2" type="application/json">
        [
            {"date": "2016-10-02T10:00:00", "value": 15.2},
            {"date": "2016-10-02T10:05:00", "value": 21.4},
            {"date": "2016-10-02T10:15:00", "value": 18.1}
        ]
</script>
<!-- This dataset has min start date from all 3 datasets -->
<script id="data-set-3" type="application/json">
        [
            {"date": "2016-10-02T09:55:00", "value": 12.4},
            {"date": "2016-10-02T10:00:00", "value": 17.7},
            {"date": "2016-10-02T10:05:00", "value": 14.6}
        ]
</script>

在上面的示例中,您可以看到数据集 2 具有所有 3 个数据集中的最大结束日期,数据集 3 具有所有 3 个数据集中的最小开始日期。

最终结果:

<!DOCTYPE html>
<html>
<head>
    <title>AmCharts multiple datasets min-max date range issue</title>
    <meta charset="utf-8" />
    <style type="text/css">
        #chartdiv {
            width: 100%;
            height: 500px;
        }
    </style>
    <script id="data-set-1" type="application/json">
            [
                {"date": "2016-10-02T10:00:00", "value": 23.8},
                {"date": "2016-10-02T10:05:00", "value": 16.8},
                {"date": "2016-10-02T10:10:00", "value": 20.5}
            ] 
    </script>
    <!-- This dataset has max end date from all 3 datasets -->
    <script id="data-set-2" type="application/json">
            [
                {"date": "2016-10-02T10:00:00", "value": 15.2},
                {"date": "2016-10-02T10:05:00", "value": 21.4},
                {"date": "2016-10-02T10:15:00", "value": 18.1}
            ]
    </script>
    <!-- This dataset has min start date from all 3 datasets -->
    <script id="data-set-3" type="application/json">
            [
                {"date": "2016-10-02T09:55:00", "value": 12.4},
                {"date": "2016-10-02T10:00:00", "value": 17.7},
                {"date": "2016-10-02T10:05:00", "value": 14.6}
            ]
    </script>
    <link rel="stylesheet" href="https://www.amcharts.com/lib/3/plugins/export/export.css" type="text/css" media="all" />
</head>
<body>
    <div id="chartdiv"></div>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>
    <script src="https://www.amcharts.com/lib/3/amcharts.js"></script>
    <script src="https://www.amcharts.com/lib/3/serial.js"></script>
    <script src="https://www.amcharts.com/lib/3/amstock.js"></script>
    <script src="https://www.amcharts.com/lib/3/plugins/export/export.min.js"></script>
    <script type="text/javascript">

        $(document).ready(function () {
            AmCharts.useUTC = true; // this will prevent adding timezone hours to ech date

            var chart = AmCharts.makeChart("chartdiv", {
                "pathToImages": "http://cdn.amcharts.com/lib/3/images/",
                "type": "stock",
                "theme": "light",
                "categoryAxesSettings": {
                    "minPeriod": "mm" // precision to minutes
                },

                "dataSets": [], // empty, we will add each based on single sensor

                "panels": [{
                    "recalculateToPercents": "never", // show value on scale not percent
                    "showCategoryAxis": true,
                    "title": "",
                    "percentHeight": 70,

                    "stockGraphs": [{ // generic config for all lines
                        "id": "g1",
                        "connect": true, // show gaps in data
                        "comparable": true, // must be true to disable/enable each dataset
                        "compareField": "value",
                        "valueField": "value",
                        "type": "smoothedLine",
                        "lineThickness": 2,
                        "bullet": "round"
                    }],

                    "stockLegend": {
                        "periodValueTextRegular": "[[value.close]]" // what will be shown at top legend
                    }
                }],

                "chartScrollbarSettings": {
                    "graph": "g1",
                    "usePeriod": "10mm",
                    "position": "top"
                },

                "chartCursorSettings": {
                    "valueBalloonsEnabled": true
                },

                "periodSelector": {
                    "fromText": "",
                    "toText": "",
                    "periodsText": "",
                    "position": "top",
                    "dateFormat": "YYYY-MM-DD JJ:NN",
                    "inputFieldWidth": 150,
                    "periods": [{
                        "period": "hh",
                        "count": 1,
                        "label": "1 H",
                        "selected": true
                    }, {
                        "period": "hh",
                        "count": 8,
                        "label": "8 H"
                    }, {
                        "period": "DD",
                        "count": 1,
                        "label": "1 D"
                    }, {
                        "period": "DD",
                        "count": 10,
                        "label": "10 D"
                    }, {
                        "period": "MM",
                        "selected": true,
                        "count": 1,
                        "label": "1 M"
                    }, {
                        "period": "YYYY",
                        "count": 1,
                        "label": "1 Y"
                    }, {
                        "period": "YTD",
                        "label": "YTD"
                    }, {
                        "period": "MAX",
                        "label": "MAX"
                    }]

                },

                "panelsSettings": {
                    "usePrefixes": true
                },

                "export": {
                    "enabled": true,
                    "exportTitles": true,
                    "libs": {
                        "path": "http://www.amcharts.com/lib/3/plugins/export/libs/"
                    },
                    "position": "bottom-right"
                }
            });

            for (var i = 1; i <= 3; i++) {
                var dataset = new AmCharts.DataSet();
                dataset.compared = true;
                dataset.title = "DataSet " + i;
                dataset.categoryField = "date";
                dataset.fieldMappings = JSON.parse('[{"fromField": "value", "toField": "value"}]');

                var data = JSON.parse($("#data-set-" + i).html());
                dataset.dataProvider = data;
                chart.dataSets.push(dataset);
                chart.validateData();
            }
        });
    </script>
</body>
</html>

您可以看到整个图表被截断为 data-set-1 的日期范围,图表未显示 date-set-2 点:

{"date": "2016-10-02T10:15:00", "value": 18.1}

并且也没有显示 date-set-3 点:

{"date": "2016-10-02T09:55:00", "value": 12.4}

我试图破解 "From" 和 "To" 输入但没有任何运气:

chart.addListener("init",
    function (e) {
        //e.chart.startDate = moment("2016-10-01 00:00");
        //e.chart.endDate = moment("2016-10-03 00:00");
    });

有什么想法可以强制图表从所有数据集中查找最小和最大日期,而不仅仅是从添加到数据集数组的第一个数据集中查找吗?

股票图表将始终使用主要选定数据集中的日期范围,忽略比较数据集中不"fit"在该范围内的数据点。

此外,它会忽略没有直接时间戳匹配的数据点。

显而易见的解决方案是通过在主数据集中添加 "empty" 个数据点来同步所有数据集中的数据,这些数据点将与比较数据集中的数据点重叠。

我知道在生成数据的服务器端执行此操作可能非常不方便且效率低下。

幸运的是,我们可以实现一个可以做到这一点的客户端包装器。

我在这里更新了你的例子:

<!DOCTYPE html>
<html>
<head>
    <title>AmCharts multiple datasets min-max date range issue</title>
    <meta charset="utf-8" />
    <style type="text/css">
        #chartdiv {
            width: 100%;
            height: 500px;
        }
    </style>
  
    <script id="data-set-1" type="application/json">
            [
                {"date": "2016-10-02T10:00:00", "value": 23.8},
                {"date": "2016-10-02T10:05:00", "value": 16.8},
                {"date": "2016-10-02T10:10:00", "value": 20.5}
            ] 
    </script>
    <!-- This dataset has max end date from all 3 datasets -->
    <script id="data-set-2" type="application/json">
            [
                {"date": "2016-10-02T10:00:00", "value": 15.2},
                {"date": "2016-10-02T10:05:00", "value": 21.4},
                {"date": "2016-10-02T10:15:00", "value": 18.1}
            ]
    </script>
    <!-- This dataset has min start date from all 3 datasets -->
    <script id="data-set-3" type="application/json">
            [
                {"date": "2016-10-02T09:55:00", "value": 12.4},
                {"date": "2016-10-02T10:00:00", "value": 17.7},
                {"date": "2016-10-02T10:05:00", "value": 14.6}
            ]
    </script>
    <link rel="stylesheet" href="https://www.amcharts.com/lib/3/plugins/export/export.css" type="text/css" media="all" />
</head>
<body>
    <div id="chartdiv"></div>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>
    <script src="https://www.amcharts.com/lib/3/amcharts.js"></script>
    <script src="https://www.amcharts.com/lib/3/serial.js"></script>
    <script src="https://www.amcharts.com/lib/3/amstock.js"></script>
    <script src="https://www.amcharts.com/lib/3/plugins/export/export.min.js"></script>
    <script type="text/javascript">
        function syncDataTimestamps(chart) {

          // check if plugin is enabled
          if (chart.syncDataTimestamps !== true)
            return;

          // go thorugh all data sets and collect all the different timestamps
          var dates = {};
          for (var i = 0; i < chart.dataSets.length; i++) {
            var ds = chart.dataSets[i];
            for (var x = 0; x < ds.dataProvider.length; x++) {
              var date = ds.dataProvider[x][ds.categoryField];
              if ( !(date instanceof Date))
                date = new Date(date);
              if (dates[date.getTime()] === undefined)
                dates[date.getTime()] = {};
              dates[date.getTime()][i] = ds.dataProvider[x];
            }
          }
          
          // iterate through data sets again and fill in the blanks
          for (var i = 0; i < chart.dataSets.length; i++) {
            var ds = chart.dataSets[i];
            var dp = [];
            for (var ts in dates) {
              if (!dates.hasOwnProperty(ts))
                continue;
              var row = dates[ts];
              if (row[i] === undefined) {
                row[i] = {};
                var d = new Date();
                d.setTime(ts);
                row[i][ds.categoryField] = d;
              }
              dp.push(row[i]);
            }
            dp.sort(function(a,b){
              return new Date(a[ds.categoryField]) - new Date(b[ds.categoryField]);
            });
            ds.dataProvider = dp;
          }

        };

        $(document).ready(function () {
            AmCharts.useUTC = true; // this will prevent adding timezone hours to ech date

            var chart = AmCharts.makeChart("chartdiv", {
                "type": "stock",
                "theme": "light",
                "categoryAxesSettings": {
                    "minPeriod": "mm" // precision to minutes
                },

                "syncDataTimestamps": true,
                "dataSets": [], // empty, we will add each based on single sensor

                "panels": [{
                    "recalculateToPercents": "never", // show value on scale not percent
                    "showCategoryAxis": true,
                    "title": "",
                    "percentHeight": 70,

                    "stockGraphs": [{ // generic config for all lines
                        "id": "g1",
                        "connect": true, // show gaps in data
                        "comparable": true, // must be true to disable/enable each dataset
                        "compareField": "value",
                        "valueField": "value",
                        "type": "smoothedLine",
                        "lineThickness": 2,
                        "bullet": "round"
                    }],

                    "stockLegend": {
                        "periodValueTextRegular": "[[value.close]]" // what will be shown at top legend
                    }
                }],

                "chartScrollbarSettings": {
                    "graph": "g1",
                    "usePeriod": "10mm",
                    "position": "top"
                },

                "chartCursorSettings": {
                    "valueBalloonsEnabled": true
                },

                "periodSelector": {
                    "fromText": "",
                    "toText": "",
                    "periodsText": "",
                    "position": "top",
                    "dateFormat": "YYYY-MM-DD JJ:NN",
                    "inputFieldWidth": 150,
                    "periods": [{
                        "period": "hh",
                        "count": 1,
                        "label": "1 H",
                        "selected": true
                    }, {
                        "period": "hh",
                        "count": 8,
                        "label": "8 H"
                    }, {
                        "period": "DD",
                        "count": 1,
                        "label": "1 D"
                    }, {
                        "period": "DD",
                        "count": 10,
                        "label": "10 D"
                    }, {
                        "period": "MM",
                        "selected": true,
                        "count": 1,
                        "label": "1 M"
                    }, {
                        "period": "YYYY",
                        "count": 1,
                        "label": "1 Y"
                    }, {
                        "period": "YTD",
                        "label": "YTD"
                    }, {
                        "period": "MAX",
                        "label": "MAX"
                    }]

                },

                "panelsSettings": {
                    "usePrefixes": true
                },

                "export": {
                    "enabled": true,
                    "exportTitles": true,
                    "libs": {
                        "path": "http://www.amcharts.com/lib/3/plugins/export/libs/"
                    },
                    "position": "bottom-right"
                }
            });

            for (var i = 1; i <= 3; i++) {
                var dataset = new AmCharts.DataSet();
                dataset.compared = true;
                dataset.title = "DataSet " + i;
                dataset.categoryField = "date";
                dataset.fieldMappings = JSON.parse('[{"fromField": "value", "toField": "value"}]');

                var data = JSON.parse($("#data-set-" + i).html());
                dataset.dataProvider = data;
                chart.dataSets.push(dataset);
            }

            syncDataTimestamps(chart);
            chart.validateData();
        });
    </script>
</body>
</html>

请注意,我还将 validateData() 调用移出了循环,因此它只被调用一次,而不是不必要的三次。