1
votes

We are using stock chart from AmCharts with multiple DataSets loaded asynchronously via ajax.

Unfortunately it looks like date range for the whole chart, by this I mean "From" and "To" is set only from the first one added to the array of all DataSets.

Imagine situation that you have 3 DataSets and each of them can have start and end date totally different:

 <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>

In above example you can see that data-set-2 has max end date from all 3 DataSets and data-set-3 has min start date from all 3 DataSets.

The end result:

<!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>

You can see that the whole chart is truncated to date range from data-set-1, chart is not showing date-set-2 point:

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

and also not showing date-set-3 point:

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

I tried to hack "From" and "To" inputs without any luck:

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

Any ideas how to force chart to lookup for minimum and maximum dates from all DataSets and not only from the first one added to the array of DataSets?

1

1 Answers

1
votes

Stock Chart will always use the range of the dates from the main selected data set, ignoring data points from compared data sets that do not "fit" within that range.

Furthermore, it will ignore data points that do not have direct timestamp match.

The obvious solution is to sync up the data in all data sets by adding "empty" data points in the main data set that would overlap the ones in compared data sets.

I know it might be frustratingly inconvenient and inefficient to do that on server-side, where data is generated.

Fortunately, we can implement a client-side wrapper that would do that.

I updated your example here:

<!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>

Please note that I also moved out the validateData() call out of the loop, so it gets called only once, rather than unnecessarily three times.