ImageryLayer - client side chart

Explore in the sandboxView live

This sample shows how to generate a chart on the client-side for an ImageryLayer. As the user clicks on or drags the pointer across the view, the app reads and processes the pixel data that is within one mile of the pointer location. The Land Cover Types chart immediately updates with new data as the pointer location changes.

This app displays National Land Cover Database (NLCD) 2001 land cover classification rasters of the conterminous US.

How it works

The land cover imagery layer is initialized with lerc format which returns the raw pixel values for the requested images.

var imageryLayer = new ImageryLayer({
  url: "",
  format: "lerc"

Since lerc format returns raw pixel values of images, we can access the pixel values in the browser. These raw values can be accessed via ImageryLayerView's pixelData and they are updated whenever user zooms or pans the view. The application watches the LayerView's updating property to get the updated pixel values. These values then can be used to create the Land Cover Types chart whenever the user clicks on or drags the pointer over the ImageryLayer.

function layerLoaded(layerView) {
  // watch for the imagery layer view's updating property
  // to get the updated pixel values"updating", function(value) {
    if (!value) {
      pixelData = layerView.pixelData;

  // when the layer loads, listen to the view's drag and click
  // events to update the land cover types chart to reflect an
  // area within 1 mile of the pointer location.
  removeChartEvents = view.on(["drag", "click"], function(event) {
    if (pixelData){
  // raster attributes table returns categorical mapping of pixel values such as class and group
  const attributesData = imageryLayer.serviceRasterInfo.attributeTable.features;

  // rasterAttributeFeatures will be used to add legend labels and colors for each
  // land use type
  for (var index in attributesData) {
    if (attributesData) {
      rasterAttributeFeatures[attributesData[index].attributes.Value] = {
        "Blue": attributesData[index].attributes.Blue,
        "ClassName": attributesData[index].attributes.ClassName,
        "Green": attributesData[index].attributes.Green,
        "Red": attributesData[index].attributes.Red
  // initialize the land cover pie chart

As user clicks on or drags the pointer over the view, the getLandCoverPixelInfo() function is called. In this function, we execute a logic to read and store pixel values that fall within one mile of the pointer location.

// This function is called as user drags the pointer over or clicks on the view.
// Here we figure out which pixels fall within one mile of the
// pointer location and update the chart accordingly
const getLandCoverPixelInfo = promiseUtils.debounce(function (event) {

  var currentExtent = pixelData.extent;
  var pixelBlock = pixelData.pixelBlock;
  const height = pixelBlock.height;
  const width = pixelBlock.width;

  // map point for the pointer location.
  const point = view.toMap({
    x: event.x,
    y: event.y
  // pointer x, y in pixels
  const reqX = Math.ceil(event.x);
  const reqY = Math.ceil(event.y);

  // calculate how many meters are represented by 1 pixel.
  const pixelSizeX = Math.abs(currentExtent.xmax - currentExtent.xmin) / width;

  // calculate how many pixels represent one mile
  const bufferDim = Math.ceil(1609 / pixelSizeX);

  // figure out 2 mile extent around the pointer location
  const xmin = (reqX - bufferDim < 0) ? 0 : reqX - bufferDim;
  const ymin = (reqY - bufferDim < 0) ? 0 : reqY - bufferDim;
  const startPixel = ymin * width + xmin;
  const bufferlength = bufferDim * 2;
  const pixels = pixelBlock.pixels[0];
  let oneMilePixelValues = [];
  const radius2 = bufferDim * bufferDim;

  // cover pixels within to 2 mile rectangle
  if (bufferlength) {
    for (var i = 0; i <= bufferlength; i++) {
      for (var j = 0; j <= bufferlength; j++) {
        // check if the given pixel location is in within one mile of the pointer
        // add its value to pixelValue.
        if ((Math.pow(i - bufferDim, 2) + Math.pow(j - bufferDim, 2)) <= radius2){
          var pixelValue = pixels[Math.floor(startPixel + i * width + j)];
        if (pixelValue !== undefined) {
  } else {
  pixelValCount = {};
  // get the count of each land type returned within one mile radius
  for (var i = 0; i < oneMilePixelValues.length; i++) {
    pixelValCount[oneMilePixelValues[i]] = 1 + (pixelValCount[oneMilePixelValues[i]] || 0);
  var circle = new Circle({
    center: point,
    radius: bufferDim * pixelSizeX
  graphic.geometry = circle;

Once the raw pixel values are processed for the chart, updateLandCovertChart() is called and the chart is updated to reflect the land cover types for the new location.

// This function is called once pixel values within one mile of the pointer
// location are processed and ready for the chart update.
function updateLandCoverChart() {
  // pixelValCount object contains land cover types and count of pixels
  // that represent that type in within one mile.
  for (var index in pixelValCount) {
    if (index == 0) {
    } else {
      var color = 'rgba(' + rasterAttributeFeatures[index].Red + ', '
      + rasterAttributeFeatures[index].Green + ', ' + rasterAttributeFeatures[index].Blue + ', 1)';
  }[0].backgroundColor = landCoverTypeColors; = landCoverTypeLabels;