Search code examples
amchartsamcharts4

amCharts 4: External Data Source - JSON Output from Database


Still trying to get to grips with amCharts 4 using external data sources, and this is a follow on from my previous question... amCharts 4: External Data Source

I've created a horizontal stacked chart using static data, which works fine (code snippet below).

However, when using the external data source on the same chart, I don't get same result, as the JSON output is different.

This is because the external database table columns and rows are the opposite way around (from the static data) and unfortunately can't be changed at source.

I've gone through the amCharts documentation and demo charts, and tried all sorts to achieve the same chart as the static data version with no success.

e.g. Tried the inversed opposite rotate properties, as well as reconfiguring the chart series and axes.

Maybe, the parseended event can be used to re-map the external data, but to be honest I don't know how.

I've come to a dead-end, and not sure how, or what is the correct (or best practice) method of doing this?

The code (and data structure) for each chart is shown below.

Any help would be very much appreciated. Thanks in advance.


STATIC DATA CHART (Correct)

// Create chart instance
var chart = am4core.create("chartdiv", am4charts.XYChart);

// Add data
var chartdata = [{
  "Name": "Question 1 Shows Here",
  "Yes Please": 75,
  "Maybe": 45,
  "No Thanks": 19
}, {
  "Name": "Question 2 Shows Here",
  "Yes Please": 35,
  "Maybe": 43,
  "No Thanks": 26
}, {
  "Name": "Question 3 Shows Here",
  "Yes Please": 57,
  "Maybe": 24,
  "No Thanks": 8
}];

chart.data = chartdata;

// Create axes
var categoryAxis = chart.yAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "Name";
categoryAxis.renderer.grid.template.location = 0;
categoryAxis.renderer.minGridDistance = 20;

var valueAxis = chart.xAxes.push(new am4charts.ValueAxis());
valueAxis.numberFormatter.numberFormat = "#";
valueAxis.min = 0;
valueAxis.transitionDuration = 200;
valueAxis.interpolationDuration = 200;
valueAxis.rangeChangeDuration = 200;

// Create series
var series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.valueX = "Yes Please";
series.dataFields.categoryY = "Name";
series.name = "Yes Please";
series.tooltipText = "{name}: [bold]{valueX}[/]";
series.stacked = true;

var series2 = chart.series.push(new am4charts.ColumnSeries());
series2.dataFields.valueX = "Maybe";
series2.dataFields.categoryY = "Name";
series2.name = "Maybe";
series2.tooltipText = "{name}: [bold]{valueX}[/]";
series2.stacked = true;

var series3 = chart.series.push(new am4charts.ColumnSeries());
series3.dataFields.valueX = "Don’t Know";
series3.dataFields.categoryY = "Name";
series3.name = "Don’t Know";
series3.tooltipText = "{name}: [bold]{valueX}[/]";
series3.stacked = true;

var series4 = chart.series.push(new am4charts.ColumnSeries());
series4.dataFields.valueX = "No Thanks";
series4.dataFields.categoryY = "Name";
series4.name = "No Thanks";
series4.tooltipText = "{name}: [bold]{valueX}[/]";
series4.stacked = true;

// Add cursor
chart.cursor = new am4charts.XYCursor();
chart.cursor.behavior = "zoomXY";

// Add legend
chart.legend = new am4charts.Legend();
chart.legend.position = "bottom";
<script src="//www.amcharts.com/lib/4/core.js"></script>
<script src="//www.amcharts.com/lib/4/charts.js"></script>
<div id="chartdiv" style="width: 90%; height: 300px";></div>

EXTERNAL DATA CHART (Incorrect)

// Create chart instance
var chart = am4core.create("chartdiv", am4charts.XYChart);

// Add data
var chartdata = [{
  "Name": "Yes Please",
  "Question 1 Shows Here": 75,
  "Question 2 Shows Here": 45,
  "Question 3 Shows Here": 19
}, {
  "Name": "Maybe",
  "Branding and Logos Score": 367,
  "Question 1 Shows Here": 35,
  "Question 2 Shows Here": 43,
  "Question 3 Shows Here": 26
}, {
  "Name": "Don’t Know",
  "Question 1 Shows Here": 42,
  "Question 2 Shows Here": 31,
  "Question 3 Shows Here": 12
}, {
  "Name": "No Thanks",
  "Question 1 Shows Here": 17,
  "Question 2 Shows Here": 27,
  "Question 3 Shows Here": 15
}];

chart.data = chartdata;

// Create category axes
var categoryAxis = chart.yAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "Name";
categoryAxis.renderer.grid.template.location = 0;
categoryAxis.renderer.minGridDistance = 20;

// Create value axes
var valueAxis = chart.xAxes.push(new am4charts.ValueAxis());
valueAxis.numberFormatter.numberFormat = "#";
valueAxis.min = 0;
valueAxis.transitionDuration = 200;
valueAxis.interpolationDuration = 200;
valueAxis.rangeChangeDuration = 200;

// Create series
var series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.valueX = "Question 1 Shows Here";
series.dataFields.categoryY = "Name";
series.name = "Question 1 Shows Here";
series.tooltipText = "{name}: [bold]{valueX}[/]";
series.stacked = true;

var series2 = chart.series.push(new am4charts.ColumnSeries());
series2.dataFields.valueX = "Question 2 Shows Here";
series2.dataFields.categoryY = "Name";
series2.name = "Question 2 Shows Here";
series2.tooltipText = "{name}: [bold]{valueX}[/]";
series2.stacked = true;

var series3 = chart.series.push(new am4charts.ColumnSeries());
series3.dataFields.valueX = "Question 3 Shows Here";
series3.dataFields.categoryY = "Name";
series3.name = "Question 3 Shows Here";
series3.tooltipText = "{name}: [bold]{valueX}[/]";
series3.stacked = true;

// Add cursor
chart.cursor = new am4charts.XYCursor();
chart.cursor.behavior = "zoomXY";

chart.legend = new am4charts.Legend();
chart.legend.position = "bottom";
<script src="//www.amcharts.com/lib/4/core.js"></script>
<script src="//www.amcharts.com/lib/4/charts.js"></script>
<div id="chartdiv" style="width: 90%; height: 300px";></div>


Solution

  • parseended is the way to go in this situation. You'll need to set up a lookup table for your questions and map them to objects containing the values mapped to each response, for example:

    chart.dataSource.events.on("parseended", function(ev) {
      var questionMap = {}; //lookup table to map questions to data elements
    
      ev.target.data.forEach(function(item) {
        Object.keys(item).forEach(function(key) {
            if (key.indexOf('Name') == -1) { //act on non-response keys
                if (!questionMap[key]) {
                    questionMap[key] = {"Name": key}; //create an object containing the name/question pair if it doesn't exist
                }
                questionMap[key][item.Name] = item[key]; // assign response+value to the object (e.g. "Yes, Please": 75)
            }
        });
      });
      //remap lookup table as array
      ev.target.data = Object.keys(questionMap).map(function(question) {
        return questionMap[question];
      });
    }); 
    

    Demo below:

    // Create chart instance
    var chart = am4core.create("chartdiv", am4charts.XYChart);
    
    chart.dataSource.url = dataURI(); //fake URL for demonstration purposes
    chart.dataSource.events.on("parseended", function(ev) {
      var questionMap = {}; //lookup table to map questions to data elements
    
      ev.target.data.forEach(function(item) {
        Object.keys(item).forEach(function(key) {
          if (key.indexOf('Name') == -1) { //act on non-response keys
            if (!questionMap[key]) {
              questionMap[key] = {
                "Name": key
              }; //create an object containing the name/question pair if it doesn't exist
            }
            questionMap[key][item.Name] = item[key]; // assign response+value to the object (e.g. "Yes, Please": 75)
          }
        });
      });
      //remap lookup table as array
      ev.target.data = Object.keys(questionMap).map(function(question) {
        return questionMap[question];
      });
    });
    // Create axes
    var categoryAxis = chart.yAxes.push(new am4charts.CategoryAxis());
    categoryAxis.dataFields.category = "Name";
    categoryAxis.renderer.grid.template.location = 0;
    categoryAxis.renderer.minGridDistance = 20;
    
    var valueAxis = chart.xAxes.push(new am4charts.ValueAxis());
    valueAxis.numberFormatter.numberFormat = "#";
    valueAxis.min = 0;
    valueAxis.transitionDuration = 200;
    valueAxis.interpolationDuration = 200;
    valueAxis.rangeChangeDuration = 200;
    
    // Create series
    var series = chart.series.push(new am4charts.ColumnSeries());
    series.dataFields.valueX = "Yes Please";
    series.dataFields.categoryY = "Name";
    series.name = "Yes Please";
    series.tooltipText = "{name}: [bold]{valueX}[/]";
    series.stacked = true;
    
    var series2 = chart.series.push(new am4charts.ColumnSeries());
    series2.dataFields.valueX = "Maybe";
    series2.dataFields.categoryY = "Name";
    series2.name = "Maybe";
    series2.tooltipText = "{name}: [bold]{valueX}[/]";
    series2.stacked = true;
    
    var series3 = chart.series.push(new am4charts.ColumnSeries());
    series3.dataFields.valueX = "Don’t Know";
    series3.dataFields.categoryY = "Name";
    series3.name = "Don’t Know";
    series3.tooltipText = "{name}: [bold]{valueX}[/]";
    series3.stacked = true;
    
    var series4 = chart.series.push(new am4charts.ColumnSeries());
    series4.dataFields.valueX = "No Thanks";
    series4.dataFields.categoryY = "Name";
    series4.name = "No Thanks";
    series4.tooltipText = "{name}: [bold]{valueX}[/]";
    series4.stacked = true;
    
    // Add cursor
    chart.cursor = new am4charts.XYCursor();
    chart.cursor.behavior = "zoomXY";
    
    // Add legend
    chart.legend = new am4charts.Legend();
    chart.legend.position = "bottom";
    
    function dataURI() {
      return "data:application/json;base64,W3sKICAiTmFtZSI6ICJZZXMgUGxlYXNlIiwKICAiUXVlc3Rpb24gMSBTaG93cyBIZXJlIjogNzUsCiAgIlF1ZXN0aW9uIDIgU2hvd3MgSGVyZSI6IDQ1LAogICJRdWVzdGlvbiAzIFNob3dzIEhlcmUiOiAxOQp9LCB7CiAgIk5hbWUiOiAiTWF5YmUiLAogICJCcmFuZGluZyBhbmQgTG9nb3MgU2NvcmUiOiAzNjcsCiAgIlF1ZXN0aW9uIDEgU2hvd3MgSGVyZSI6IDM1LAogICJRdWVzdGlvbiAyIFNob3dzIEhlcmUiOiA0MywKICAiUXVlc3Rpb24gMyBTaG93cyBIZXJlIjogMjYKfSwgewogICJOYW1lIjogIkRvbuKAmXQgS25vdyIsCiAgIlF1ZXN0aW9uIDEgU2hvd3MgSGVyZSI6IDQyLAogICJRdWVzdGlvbiAyIFNob3dzIEhlcmUiOiAzMSwKICAiUXVlc3Rpb24gMyBTaG93cyBIZXJlIjogMTIKfSwgewogICJOYW1lIjogIk5vIFRoYW5rcyIsCiAgIlF1ZXN0aW9uIDEgU2hvd3MgSGVyZSI6IDE3LAogICJRdWVzdGlvbiAyIFNob3dzIEhlcmUiOiAyNywKICAiUXVlc3Rpb24gMyBTaG93cyBIZXJlIjogMTUKfV0=";
    }
    <script src="//www.amcharts.com/lib/4/core.js"></script>
    <script src="//www.amcharts.com/lib/4/charts.js"></script>
    <div id="chartdiv" style="width: 90%; height: 300px" ;></div>