Syncing chart data with table/grid

This tutorial will look at ways on how we can grab chart data and display it in an HTML table. We will then build on it to enhance for display in a Grid component. Finally, we'll make chart data sync up with editable grid.

Introduction

Since amCharts is not in the data grid business, we will be relying on 3rd parties for this specific functionality.

We will be looking at different libraries and solutions, just to give an idea on how it can be universally used.

Let's use a common scenario char - an XYChart with date-based data and multi-series:

See the Pen amCharts 4: Display chart data in a table by amCharts team (@amcharts) on CodePen.24419

Chart data in a table

The first step in everything we will do in this tutorial is getting chart data into an HTML table.

Building data table

One way would be to grab chart.data, iterate through it, and build HTML for it which we would then plop onto our document.

Doable, and quite easy, but we have a better solution: using built-in export API.

Among other things it supports exporting data as an HTML table markup, essentially doing all the job for us.

The export function that will give us that is called chart.exporting.getHTML().

This method returns a Promise, which means it's asynchronous, requiring us to either use await or then() syntax:

chart.exporting.getHTML("html", {
  addColumnNames: true
}, false).then(function(html) {
  // html will contain fully formatted markup for data in a <table>
});
chart.exporting.getHTML("html", {
  addColumnNames: true
}, false).then(function(html) {
  // html will contain fully formatted markup for data in a <table>
});

NOTE Notice the third parameter (false) in getHTML() call? It's important. It tells the function not to return the result as a "data:uri", but rather HTML in string format.

Adding table to document

Now that we know how to grab chart's data as a <table>, all we need to do is to plop it onto our document.

Let's put our table into a separate <div> so that we can style and size it separately from the chart.

<div id="chartdiv"></div>
<div id="chartdata"></div>

Let's also add very basic styling, to limit height of the resulting table.

#chartdata {
  max-height: 400px;
  overflow: auto;
}

#chartdata table {
  width: 100%;
}

Now that we have our data container, let's make the magic happen.

One thing before we start: when chart is created it hasn't had a chance to parse it's data yet.

Let's use chart's datavalidated event, to start our export instead.

This events kicks in whenever data is fully parsed and ready to be used.

An added bonus is that it will automatically update our table if/whenever chart's data is updated, so it's basically a healthy thing to do.

chart.events.on("datavalidated", function(ev) {
  chart.exporting.getHTML("html", {
    addColumnNames: true
  }, false).then(function(html) {
    let div = document.getElementById("chartdata");
    div.innerHTML = html;
  });
});
chart.events.on("datavalidated", function(ev) {
  chart.exporting.getHTML("html", {
    addColumnNames: true
  }, false).then(function(html) {
    var div = document.getElementById("chartdata");
    div.innerHTML = html;
  });
});
{
  // ...
  "events": {
    "datavalidated": function(ev) {
      var chart = ev.target;
      chart.exporting.getHTML("html", {
        addColumnNames: true
      }, false).then(function(html) {
        var div = document.getElementById("chartdata");
        div.innerHTML = html;
      });
    }
  }
}

Let's see how this works in real life:

See the Pen amCharts 4: Display chart data in a table by amCharts team (@amcharts) on CodePen.24419

Styling the table

Looks a bit dull, doesn't it?

By default, <table> comes without any styling or even class names attached.

However, we can pass in class names to be attached to each of the elements of the table, via format options (second parameter to the getHTML() function):

TagOption
<table>tableClass
<tr>rowClass
<th> and <td>cellClass

Let's class-up our table for use with Bootstrap.

chart.events.on("datavalidated", function(ev) {
  chart.exporting.getHTML("html", {
    addColumnNames: true,
    tableClass: "table table-striped table-hover table-sm"
  }, false).then(function(html) {
    let div = document.getElementById("chartdata");
    div.innerHTML = html;
  });
});
chart.events.on("datavalidated", function(ev) {
  chart.exporting.getHTML("html", {
    addColumnNames: true,
    tableClass: "table table-striped table-hover table-sm"
  }, false).then(function(html) {
    var div = document.getElementById("chartdata");
    div.innerHTML = html;
  });
});
{
  // ...
  "events": {
    "datavalidated": function(ev) {
      var chart = ev.target;
      chart.exporting.getHTML("html", {
        addColumnNames: true,
        tableClass: "table table-striped table-hover table-sm"
      }, false).then(function(html) {
        var div = document.getElementById("chartdata");
        div.innerHTML = html;
      });
    }
  }
}

Let's see how it looks like now:

See the Pen amCharts 4: Display chart data in a table by amCharts team (@amcharts) on CodePen.24419

3rd party libraries

Let's keep on moving. Let's turn our static table into a fully-featured grid.

As there are hundreds, or even thousands, of grid libraries out there, let's explore ones that we like best.

DataTables

DataTables is a free jQuery-based grid plugin. It can instantly turn a regular <table> element into a searchable, sortable table with paging and other neat features.

We don't need to do much for it:

  1. Include jQuery, as well as CSS and JS for DataTables.
  2. Turn our plain table into a DataTable: $("#chartdata table").DataTable().
chart.events.on("datavalidated", function(ev) {
  chart.exporting.getHTML("html", {
    addColumnNames: true
  }, false).then(function(html) {
    let div = document.getElementById("chartdata");
    div.innerHTML = html;
    $("#chartdata table").DataTable();
  });
});
chart.events.on("datavalidated", function(ev) {
  chart.exporting.getHTML("html", {
    addColumnNames: true
  }, false).then(function(html) {
    var div = document.getElementById("chartdata");
    div.innerHTML = html;
    $("#chartdata table").DataTable();
  });
});
{
  // ...
  "events": {
    "datavalidated": function(ev) {
      var chart = ev.target;
      chart.exporting.getHTML("html", {
        addColumnNames: true
      }, false).then(function(html) {
        var div = document.getElementById("chartdata");
        div.innerHTML = html;
        $("#chartdata table").DataTable();
      });
    }
  }
}

See the Pen amCharts 4: Display chart data in a table by amCharts team (@amcharts) on CodePen.24419

DataGridXL

DataGridXL is a no-nonsense light and super fast grid library, that does not require any additional dependencies, like jQuery.

It's also allows editing any data displayed in it. We will explore this as well, to give it a two way communication between chart and grid.

Displaying data in a grid

Unlike DataTables we explored before, DataGridXL does not support turning a <table> into grid.

Instead it will accept data in a JavaScript array, which means we can skip the whole getHTML() step, and just dump chart's data to the grid:

chart.events.on("datavalidated", function(ev) {
  let grid = new DataGridXL("chartdata", {
    data: chart.data
  });
});
chart.events.on("datavalidated", function(ev) {
  var grid = new DataGridXL("chartdata", {
    data: chart.data
  });
});
{
  // ...
  "events": {
    "datavalidated": function(ev) {
      var chart = ev.target;
      var grid = new DataGridXL("chartdata", {
        data: chart.data
      });
    }
  }
}

Live example:

See the Pen amCharts 4: Display chart data in a table by amCharts team (@amcharts) on CodePen.24419

Pushing data edits back to chart

DataGridXL allows editing value sin any cell.

Whenever it happens it triggers a cellvaluechange event.

At that point we could just use grid's getData() method, then set chart's data to it.

However, that would be very inneficient.

The parameter passed into handler function of cellvaluechange contains all the new values as well as identifiers of cells that were chainged.

Using those, we can modify items in chart's data array individually, then call its invalidateRawData() method to take in just the changed values:

chart.events.on("datavalidated", function(ev) {
  let grid = new DataGridXL("chartdata", {
    data: chart.data
  });
  
  grid.events.on("cellvaluechange", function(ev) {
    for(let r = 0; r < ev.rowIds.length; r++) {
      var row = chart.data[ev.rowIds[r]];
      for(let c = 0; c < ev.colIds.length; c++) {
        var col = grid.getColFromStore(ev.colIds[c]).field;
        row[col] = ev.changes[r][c];
      }
    }
    chart.invalidateRawData();
  });

});
chart.events.on("datavalidated", function(ev) {
  var grid = new DataGridXL("chartdata", {
    data: chart.data
  });
  
  grid.events.on("cellvaluechange", function(ev) {
    for(var r = 0; r < ev.rowIds.length; r++) {
      var row = chart.data[ev.rowIds[r]];
      for(var c = 0; c < ev.colIds.length; c++) {
        var col = grid.getColFromStore(ev.colIds[c]).field;
        row[col] = ev.changes[r][c];
      }
    }
    chart.invalidateRawData();
  });

});
{
  // ...
  "events": {
    "datavalidated": function(ev) {
      var chart = ev.target;
      var grid = new DataGridXL("chartdata", {
        data: chart.data
      });
  
      grid.events.on("cellvaluechange", function(ev) {
        var grid = this;
        for(var r = 0; r < ev.rowIds.length; r++) {
          var row = chart.data[ev.rowIds[r]];
          for(var c = 0; c < ev.colIds.length; c++) {
            var col = grid.getColFromStore(ev.colIds[c]).field;
            row[col] = ev.changes[r][c];
          }
        }
        chart.invalidateRawData();
      });
    }
  }
}

Go ahead, try changing values in the grid:

See the Pen amCharts 4: Display chart data in a table by amCharts team (@amcharts) on CodePen.24419

Toggling data table

Let's add one final touch: a switch which would toggle data table on and off.

amCharts 4 has a perfect control for it: SwitchButton.

Let add one to our chart, as well as attach an event that would toggle data table:

// A button to toggle the data table
let button = chart.createChild(am4core.SwitchButton);
button.align = "right";
button.leftLabel.text = "Show data";
button.isActive = false;

// Set toggling of data table
button.events.on("toggled", function(ev) {
  let div = document.getElementById("chartdata");
  if (button.isActive) {
    div.style.display = "block";
  }
  else {
    div.style.display = "none";
  }
});
// A button to toggle the data table
var button = chart.createChild(am4core.SwitchButton);
button.align = "right";
button.leftLabel.text = "Show data";
button.isActive = false;

// Set toggling of data table
button.events.on("toggled", function(ev) {
  var div = document.getElementById("chartdata");
  if (button.isActive) {
    div.style.display = "block";
  }
  else {
    div.style.display = "none";
  }
});

See the Pen amCharts 4: Display chart data in a table by amCharts team (@amcharts) on CodePen.24419