DataTable Charts

The ui.Chart function renders charts from a client-side JSON object that follows the same structure as the Google Charts DataTable, class, but lacks DataTable methods and mutability. It is essentially a 2-D table with rows that represent observations and columns that represent observation attributes. It provides a flexible, base interface to charting in Earth Engine. It is a good option when a high degree of chart customization is required.

DataTable schema

There are two ways to define a pseudo-DataTable in Earth Engine: a JavaScript 2-D array and a JavaScript literal object. For most applications, constructing a 2-D array will be the simplest approach. In both cases, the table passed to ui.Chart must be a client-side object. A manually coded table will be inherently client-side, whereas a computed object will need to be transferred client-side using evaluate. Please see the Client vs. Server page for more information on the distinction between server-side and client-side objects.

JavaScript array

A 2-D DataTable is composed of an array of rows and columns. Rows are observations and columns are attributes. The first column defines values for the x-axis, while additional columns define values for y-axis series. The first row is expected to be a column header. The simplest header is a series of column labels, demonstrated in the following array DataTable relating population by selected states.

var dataTable = [
  ['State', 'Population'],
  ['CA', 37253956],
  ['NY', 19378102],
  ['IL', 12830632],
  ['MI', 9883640],
  ['OR', 3831074],
];

Optionally, columns can be designated to a role other than defining the domain (x-axis) and data (y-axis series) e.g. annotation, intervals, tooltips, or style. In the following example, the header array is presented as a series of objects, where the role of each column is explicitly defined. Acceptable column roles for each Google Chart type can be found in their respective documentation e.g. Column Chart data format.

var dataTable = [
  [{role: 'domain'}, {role: 'data'}, {role: 'annotation'}],
  ['CA', 37253956, '37.2e6'],
  ['NY', 19378102, '19.3e6'],
  ['IL', 12830632, '12.8e6'],
  ['MI', 9883640, '9.8e6'],
  ['OR', 3831074, '3.8e6'],
];

Column properties are specified as follows:

Parameter Type Definition
type string, recommended Column data type: 'string', 'number', 'boolean', 'date', 'datetime', or 'timeofday'.
label string, recommended A label for the column, series label in the chart legend.
role string, recommended A role for the column (e.g., roles for Column Chart).
pattern string, optional A number (or date) format string specifying how to display the column value.

JavaScript object

A DataTable can be formatted as a JavaScript literal object where arrays of row and column objects are provided. See this guide for instructions on specifying column and row parameters.

var dataTable = {
  cols: [{id: 'name', label: 'State', type: 'string'},
         {id: 'pop', label: 'Population', type: 'number'}],
  rows: [{c: [{v: 'CA'}, {v: 37253956}]},
         {c: [{v: 'NY'}, {v: 19378102}]},
         {c: [{v: 'IL'}, {v: 12830632}]},
         {c: [{v: 'MI'}, {v: 9883640}]},
         {c: [{v: 'OR'}, {v: 3831074}]}]
};

Manual DataTable chart

Suppose you have a small amount of static data you want to display to a chart. Use either the JavaScript array or object specifications to construct an input to pass to the ui.Chart function. Here, selected state populations from the U.S. 2010 census are encoded as a JavaScript array with column header objects that define column properties. Note that the third column is designated to the role of 'annotation', which adds the population as an annotation to each observation in the chart.

Code Editor (JavaScript)

// Define a DataTable using a JavaScript array with a column property header.
var dataTable = [
  [
    {label: 'State', role: 'domain', type: 'string'},
    {label: 'Population', role: 'data', type: 'number'},
    {label: 'Pop. annotation', role: 'annotation', type: 'string'}
  ],
  ['CA', 37253956, '37.2e6'],
  ['NY', 19378102, '19.3e6'],
  ['IL', 12830632, '12.8e6'],
  ['MI', 9883640, '9.8e6'],
  ['OR', 3831074, '3.8e6']
];

// Define the chart and print it to the console.
var chart = ui.Chart(dataTable).setChartType('ColumnChart').setOptions({
  title: 'State Population (US census, 2010)',
  legend: {position: 'none'},
  hAxis: {title: 'State', titleTextStyle: {italic: false, bold: true}},
  vAxis: {title: 'Population', titleTextStyle: {italic: false, bold: true}},
  colors: ['1d6b99']
});
print(chart);

Computed DataTable chart

A DataTable array can be created from a 2-D ee.List passed from the server to the client via evaluate. A common scenario is to convert properties of an ee.FeatureCollection, ee.ImageCollection, or element-wise reduction of these into a DataTable. The strategy applied in the following examples maps a function over an ee.ImageCollection that reduces the given element, assembles an ee.List from the reduction results, and attaches the list as a property called 'row' to the returned element. Each element of the new collection has a 1-D ee.List that represents a row in a DataTable. The aggregate_array() function is used to aggregate all of the 'row' properties into a parent ee.List to create a 2-D server-side ee.List in the shape required for DataTable. A custom column header is concatenated to the table and the result is transferred client-side with evaluate, where it is rendered using the ui.Chart function.

Time series by region

This example shows a time series of MODIS-derived NDVI and EVI vegetation indices for a forested ecoregion. Each image in the series is reduced by the ecoregion and its results assembled as a 'row' property that is aggregated into a DataTable for passing to the client and charting with ui.Chart. Note that this snippet produces the same chart generated by the ui.Chart.image.series chart example.

Code Editor (JavaScript)

// Import the example feature collection and subset the forest feature.
var forest = ee.FeatureCollection('projects/google/charts_feature_example')
                 .filter(ee.Filter.eq('label', 'Forest'));

// Load MODIS vegetation indices data and subset a decade of images.
var vegIndices = ee.ImageCollection('MODIS/006/MOD13A1')
                     .filter(ee.Filter.date('2010-01-01', '2020-01-01'))
                     .select(['NDVI', 'EVI']);

// Define a function to format an image timestamp as a JavaScript Date string.
function formatDate(img) {
  var millis = img.date().millis().format();
  return ee.String('Date(').cat(millis).cat(')');
}

// Build a feature collection where each feature has a property that represents
// a DataFrame row.
var reductionTable = vegIndices.map(function(img) {
  // Reduce the image to the mean of pixels intersecting the forest ecoregion.
  var stat = img.reduceRegion(
      {reducer: ee.Reducer.mean(), geometry: forest, scale: 500});

  // Extract the reduction results along with the image date.
  var date = formatDate(img);   // x-axis values.
  var evi = stat.get('EVI');    // y-axis series 1 values.
  var ndvi = stat.get('NDVI');  // y-axis series 2 values.

  // Make a list of observation attributes to define a row in the DataTable.
  var row = ee.List([date, evi, ndvi]);

  // Return the row as a property of an ee.Feature.
  return ee.Feature(null, {'row': row});
});

// Aggregate the 'row' property from all features in the new feature collection
// to make a server-side 2-D list (DataTable).
var dataTableServer = reductionTable.aggregate_array('row');

// Define column names and properties for the DataTable. The order should
// correspond to the order in the construction of the 'row' property above.
var columnHeader = ee.List([[
  {label: 'Date', role: 'domain', type: 'date'},
  {label: 'EVI', role: 'data', type: 'number'},
  {label: 'NDVI', role: 'data', type: 'number'}
]]);

// Concatenate the column header to the table.
dataTableServer = columnHeader.cat(dataTableServer);

// Use 'evaluate' to transfer the server-side table to the client, define the
// chart and print it to the console.
dataTableServer.evaluate(function(dataTableClient) {
  var chart = ui.Chart(dataTableClient).setOptions({
    title: 'Average Vegetation Index Value by Date for Forest',
    hAxis: {
      title: 'Date',
      titleTextStyle: {italic: false, bold: true},
    },
    vAxis: {
      title: 'Vegetation index (x1e4)',
      titleTextStyle: {italic: false, bold: true}
    },
    lineWidth: 5,
    colors: ['e37d05', '1d6b99'],
    curveType: 'function'
  });
  print(chart);
});

Interval chart

This chart takes advantage of the DataTable column 'role' property to generate an interval chart. The chart relates the annual NDVI profile and inter-annual variance for a pixel near Monterey, CA. Inter-annual median is presented as a line, while the absolute and interquartile ranges are shown as bands. Table columns representing each interval are assigned as such by setting the 'role' column property as 'interval'. Bands are drawn around the median line by setting the intervals.style chart property as 'area'.

Code Editor (JavaScript)

// Define a point to extract an NDVI time series for.
var geometry = ee.Geometry.Point([-121.679, 36.479]);

// Define a band of interest (NDVI), import the MODIS vegetation index dataset,
// and select the band.
var band = 'NDVI';
var ndviCol = ee.ImageCollection('MODIS/006/MOD13Q1').select(band);

// Map over the collection to add a day of year (doy) property to each image.
ndviCol = ndviCol.map(function(img) {
  var doy = ee.Date(img.get('system:time_start')).getRelative('day', 'year');
  // Add 8 to day of year number so that the doy label represents the middle of
  // the 16-day MODIS NDVI composite.
  return img.set('doy', ee.Number(doy).add(8));
});

// Join all coincident day of year observations into a set of image collections.
var distinctDOY = ndviCol.filterDate('2013-01-01', '2014-01-01');
var filter = ee.Filter.equals({leftField: 'doy', rightField: 'doy'});
var join = ee.Join.saveAll('doy_matches');
var joinCol = ee.ImageCollection(join.apply(distinctDOY, ndviCol, filter));

// Calculate the absolute range, interquartile range, and median for the set
// of images composing each coincident doy observation group. The result is
// an image collection with an image representative per unique doy observation
// with bands that describe the 0, 25, 50, 75, 100 percentiles for the set of
// coincident doy images.
var comp = ee.ImageCollection(joinCol.map(function(img) {
  var doyCol = ee.ImageCollection.fromImages(img.get('doy_matches'));

  return doyCol
      .reduce(ee.Reducer.percentile(
          [0, 25, 50, 75, 100], ['p0', 'p25', 'p50', 'p75', 'p100']))
      .set({'doy': img.get('doy')});
}));

// Extract the inter-annual NDVI doy percentile statistics for the
// point of interest per unique doy representative. The result is
// is a feature collection where each feature is a doy representative that
// contains a property (row) describing the respective inter-annual NDVI
// variance, formatted as a list of values.
var reductionTable = comp.map(function(img) {
  var stats = ee.Dictionary(img.reduceRegion(
      {reducer: ee.Reducer.first(), geometry: geometry, scale: 250}));

  // Order the percentile reduction elements according to how you want columns
  // in the DataTable arranged (x-axis values need to be first).
  var row = ee.List([
    img.get('doy'),            // x-axis, day of year.
    stats.get(band + '_p50'),  // y-axis, median.
    stats.get(band + '_p0'),   // y-axis, min interval.
    stats.get(band + '_p25'),  // y-axis, 1st quartile interval.
    stats.get(band + '_p75'),  // y-axis, 3rd quartile interval.
    stats.get(band + '_p100')  // y-axis, max interval.
  ]);

  // Return the row as a property of an ee.Feature.
  return ee.Feature(null, {row: row});
});

// Aggregate the 'row' properties to make a server-side 2-D array (DataTable).
var dataTableServer = reductionTable.aggregate_array('row');

// Define column names and properties for the DataTable. The order should
// correspond to the order in the construction of the 'row' property above.
var columnHeader = ee.List([[
  {label: 'Day of year', role: 'domain'},
  {label: 'Median', role: 'data'},
  {label: 'p0', role: 'interval'},
  {label: 'p25', role: 'interval'},
  {label: 'p75', role: 'interval'},
  {label: 'p100', role: 'interval'}
]]);

// Concatenate the column header to the table.
dataTableServer = columnHeader.cat(dataTableServer);

// Use 'evaluate' to transfer the server-side table to the client, define the
// chart and print it to the console.
dataTableServer.evaluate(function(dataTableClient) {
  var chart = ui.Chart(dataTableClient).setChartType('LineChart').setOptions({
    title: 'Annual NDVI Time Series with Inter-Annual Variance',
    intervals: {style: 'area'},
    hAxis: {
      title: 'Day of year',
      titleTextStyle: {italic: false, bold: true},
    },
    vAxis: {title: 'NDVI (x1e4)', titleTextStyle: {italic: false, bold: true}},
    colors: ['0f8755'],
    legend: {position: 'none'}
  });
  print(chart);
});

There are many ways to represent intervals. In the following example, boxes are used instead of bands by changing the intervals.style property to 'boxes' with respective box styling.

dataTableServer.evaluate(function(dataTableClient) {
  var chart = ui.Chart(dataTableClient).setChartType('LineChart').setOptions({
    title: 'Annual NDVI Time Series with Inter-Annual Variance',
    intervals: {style: 'boxes', barWidth: 1, boxWidth: 1, lineWidth: 0},
    hAxis: {
      title: 'Day of year',
      titleTextStyle: {italic: false, bold: true},
    },
    vAxis: {title: 'NDVI (x1e4)', titleTextStyle: {italic: false, bold: true}},
    colors: ['0f8755'],
    legend: {position: 'none'}
  });
  print(chart);
});