Creating multiple client side charts for imageryLayers

1127
6
06-01-2021 07:26 AM
IGCMCGIS
New Contributor III

Hello Everyone,

We are trying to implement the following sample ImageryLayer - client side chart with multiple imageryLayers such that we can have the same query and results displayed via multiple charts. 

Referring to the above sample we have made some modifications where we have essentially repeated the query for two image services with similar parameters. Unfortunatly it seems that only one of the services return the results based on the buffer and mouse event and draw a chart while the other service is left out.

Looks something like this :

IGCMCGIS_0-1622557269782.png

Unfortunately the second chart does not draw:

IGCMCGIS_1-1622557373159.png

Chart elemnts have been created for both layers and it would be great if you could let us know why this has been occuring.

This is the code snippet we are using:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />
    <title>
      ImageryLayer - client side chart | Sample | ArcGIS API for JavaScript 4.16
    </title>

    <link
      rel="stylesheet"
    />
    <script src="https://js.arcgis.com/4.16/"></script>

    <style>
      html,
      body,
      #viewDiv {
        padding0;
        margin0;
        height100%;
        width100%;
      }

      #titleDiv,
      #infoDiv {
        padding10px;
        width320px;
      }

      #infoDiv {
        height425px;
        font-size14px;
        displaynone;
        overflow-yauto;
        overflow-xhidden;
      }

      #titleText {
        font-size20pt;
        font-weight30;
        padding-bottom10px;
      }
      .chart-legend li span {
        displayinline-block;
        width12px;
        height12px;
        margin-right5px;
      }

      ul {
        list-style-typenone;
      }
    </style>
    <script>
      require([
        "esri/Map",
        "esri/views/MapView",
        "esri/layers/ImageryLayer",
        "esri/widgets/LayerList",
        "esri/Graphic",
        "esri/geometry/Circle",
        "esri/layers/support/TileInfo",
        "esri/core/promiseUtils",
        "esri/widgets/Expand"
      ], function (
        Map,
        MapView,
        ImageryLayer,
        LayerList,
        Graphic,
        Circle,
        TileInfo,
        promiseUtils,
        Expand
      ) {
        let landCoverChartfragCoverChartpixelValCountpixelData;
        let rasterAttributeFeatures = {};

        const map = new Map();

        const view = new MapView({
          container: "viewDiv",
          map: map,
          center: [-8040.5],
          zoom: 11,
          constraints: {
            rotationEnabled: false,
            minScale: 36978595// U.S.
            // There is no basemap so lods are not set.
            // So specify AGOL lods for the view
            lods: TileInfo.create().lods
          }
        });

        // initialize the imagery layer with lerc format
        const imageryLayer = new ImageryLayer({
          url:
          format: "lerc"
        });

        const imageryLayer2 = new ImageryLayer({
          url:
          format: "lerc"
        });
        map.add(imageryLayer2);
        map.add(imageryLayer);

        // graphic that will represent one mile buffer as user drags the pointer
        // over the map to view land cover types within one mile of the pointer
        const graphic = new Graphic({
          geometry: null,
          symbol: {
            type: "simple-fill",
            color: null,
            style: "solid",
            outline: {
              color: "blue",
              width: 2
            }
          }
        });
        view.graphics.add(graphic);

        // add the UI for titles, stats and chart.
        view.ui.add("titleDiv""top-right");
        const infoDiv = document.getElementById("infoDiv");
        view.ui.add(infoDiv"top-right");

        view.whenLayerView(imageryLayer).then(layerLoaded);
        function layerLoaded(layerView) {
          // watch for the imagery layer view's updating property
          // to get the updated pixel values
          layerView.watch("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) {
              event.stopPropagation();
              getFragCoverPixelInfo(event).then(updatefragCoverChart);
            }
          });
          // 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) {
              var hexColor = rgbToHex(
                attributesData[index].attributes.Red,
                attributesData[index].attributes.Green,
                attributesData[index].attributes.Blue
              );
              rasterAttributeFeatures[
                attributesData[index].attributes.Value
              ] = {
                ClassName: attributesData[index].attributes.FragClass,
                hexColor: hexColor
              };
            }
          }
          // initialize the land cover pie chart
          createfragCoverChart();
        }

        // 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 getFragCoverPixelInfo = 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(16093 / 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 = 0i <= bufferlengthi++) {
              for (var j = 0j <= bufferlengthj++) {
                // check if the given pixel location is in within one mile of the pointer
                // add its value to pixelValue.
                if (
                  Math.pow(i - bufferDim2+ Math.pow(j - bufferDim2<=
                  radius2
                ) {
                  var pixelValue =
                    pixels[Math.floor(startPixel + i * width + j)];
                }
                if (pixelValue !== undefined) {
                  oneMilePixelValues.push(pixelValue);
                }
              }
            }
          } else {
            oneMilePixelValues.push(pixels[startPixel]);
          }
          pixelValCount = {};
          // get the count of each land type returned within one mile raduis
          for (var i = 0i < oneMilePixelValues.lengthi++) {
            pixelValCount[oneMilePixelValues[i]] =
              1 + (pixelValCount[oneMilePixelValues[i]] || 0);
          }
          var circle = new Circle({
            center: point,
            radius: bufferDim * pixelSizeX
          });

          graphic.geometry = circle;
        });

        // This function is called once pixel values within one mile of the pointer
        // location are processed and ready for the chart update.
        function updatefragCoverChart() {
          if (infoDiv.style.display != "block") {
            infoDiv.style.display = "block";
          }
          fragCoverChart.data.datasets[0].data = [];
          var dataset = [];
          var landCoverTypeColors = [];
          var landCoverTypeLabels = [];

          // 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) {
              landCoverTypeColors.push("rgba(255,255,255,1");
              landCoverTypeLabels.push("NoData");
            } else {
              var color = rasterAttributeFeatures[index].hexColor;
              landCoverTypeColors.push(color);
              landCoverTypeLabels.push(
                rasterAttributeFeatures[index].ClassName
              );
            }
            fragCoverChart.data.datasets[0].data.push(pixelValCount[index]);
          }
          fragCoverChart.data.datasets[0].backgroundColor = landCoverTypeColors;
          fragCoverChart.data.labels = landCoverTypeLabels;
          fragCoverChart.update(0);
          document.getElementById(
            "chartLegend"
          ).innerHTML = fragCoverChart.generateLegend();
        }

        // This function is called when the ImageryLayer is loaded.
        // It sets up the land cover types chart.
        function createfragCoverChart() {
          const landCoverCanvas = document.getElementById("landcover-chart1");
          fragCoverChart = new Chart(landCoverCanvas.getContext("2d"), {
            type: "horizontalBar",
            data: {
              labels: [],
              datasets: [
                {
                  data: [],
                  backgroundColor: [],
                  borderColor: "rgb(0, 0, 0, 0, 1)",
                  borderWidth: 0.5
                }
              ]
            },
            options: {
              responsive: false,
              cutoutPercentage: 35,
              legend: {
                display: false
              },
              title: {
                display: true,
                text: "Fragmentaton Types"
              },
              plugins: {
                datalabels: {
                  formatter: function (valuectx) {
                    let datasets = ctx.chart.data.datasets;
                    if (datasets.indexOf(ctx.dataset=== datasets.length - 1) {
                      let sum = datasets[0].data.reduce(function (ab) {
                        return a + b;
                      });
                      let percentage = Math.round((value / sum* 100);
                      if (percentage > 15) {
                        return percentage + "%";
                      } else {
                        return "";
                      }
                    } else {
                      return percentage;
                    }
                  },
                  color: "#4c4c4c"
                }
              }
            }
          });
        }

        view.whenLayerView(imageryLayer2).then(layerLoaded);
        function layerLoaded(layerView) {
          // watch for the imagery layer view's updating property
          // to get the updated pixel values
          layerView.watch("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) {
              event.stopPropagation();
              getLandCoverPixelInfo(event).then(updatelandCoverChart);
            }
          });
          // raster attributes table returns categorical mapping of pixel values such as class and group
          const attributesData =
            imageryLayer2.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) {
              var hexColor = rgbToHex(
                attributesData[index].attributes.Red,
                attributesData[index].attributes.Green,
                attributesData[index].attributes.Blue
              );
              rasterAttributeFeatures[
                attributesData[index].attributes.Value
              ] = {
                ClassName: attributesData[index].attributes.ClassName,
                hexColor: hexColor
              };
            }
          }
          // initialize the land cover pie chart
          createlandCoverChart();
        }

        // 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(16093 / 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 = 0i <= bufferlengthi++) {
              for (var j = 0j <= bufferlengthj++) {
                // check if the given pixel location is in within one mile of the pointer
                // add its value to pixelValue.
                if (
                  Math.pow(i - bufferDim2+ Math.pow(j - bufferDim2<=
                  radius2
                ) {
                  var pixelValue =
                    pixels[Math.floor(startPixel + i * width + j)];
                }
                if (pixelValue !== undefined) {
                  oneMilePixelValues.push(pixelValue);
                }
              }
            }
          } else {
            oneMilePixelValues.push(pixels[startPixel]);
          }
          pixelValCount = {};
          // get the count of each land type returned within one mile raduis
          for (var i = 0i < oneMilePixelValues.lengthi++) {
            pixelValCount[oneMilePixelValues[i]] =
              1 + (pixelValCount[oneMilePixelValues[i]] || 0);
          }
          var circle = new Circle({
            center: point,
            radius: bufferDim * pixelSizeX
          });

          graphic.geometry = circle;
        });

        function updatelandCoverChart() {
          if (infoDiv.style.display != "block") {
            infoDiv.style.display = "block";
          }
          landCoverChart.data.datasets[0].data = [];
          var dataset = [];
          var landCoverTypeColors = [];
          var landCoverTypeLabels = [];

          // 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) {
              landCoverTypeColors.push("rgba(255,255,255,1");
              landCoverTypeLabels.push("NoData");
            } else {
              var color = rasterAttributeFeatures[index].hexColor;
              landCoverTypeColors.push(color);
              landCoverTypeLabels.push(
                rasterAttributeFeatures[index].ClassName
              );
            }
            landCoverChart.data.datasets[0].data.push(pixelValCount[index]);
          }
          landCoverChart.data.datasets[0].backgroundColor = landCoverTypeColors;
          landCoverChart.data.labels = landCoverTypeLabels;
          landCoverChart.update(0);
          document.getElementById(
            "chartLegend2"
          ).innerHTML = landCoverChart.generateLegend();
        }

        function createlandCoverChart() {
          const landCoverCanvas = document.getElementById("landcover-chart2");
          landCoverChart = new Chart(landCoverCanvas.getContext("2d"), {
            type: "doughnut",
            data: {
              labels: [],
              datasets: [
                {
                  data: [],
                  backgroundColor: [],
                  borderColor: "rgb(0, 0, 0, 0, 1)",
                  borderWidth: 0.5
                }
              ]
            },
            options: {
              responsive: false,
              cutoutPercentage: 35,
              legend: {
                display: false
              },
              title: {
                display: true,
                text: "Classification Types"
              },
              plugins: {
                datalabels: {
                  formatter: function (valuectx) {
                    let datasets = ctx.chart.data.datasets;
                    if (datasets.indexOf(ctx.dataset=== datasets.length - 1) {
                      let sum = datasets[0].data.reduce(function (ab) {
                        return a + b;
                      });
                      let percentage = Math.round((value / sum* 100);
                      if (percentage > 15) {
                        return percentage + "%";
                      } else {
                        return "";
                      }
                    } else {
                      return percentage;
                    }
                  },
                  color: "#4c4c4c"
                }
              }
            }
          });
        }

        function componentToHex(c) {
          var hex = c.toString(16);
          return hex.length == 1 ? "0" + hex : hex;
        }

        function rgbToHex(rgb) {
          return (
            "#" + componentToHex(r+ componentToHex(g+ componentToHex(b)
          );
        }

        const instructionsExpand = new Expand({
          expandIconClass: "esri-icon-question",
          expandTooltip: "How to use this sample",
          view: view,
          expanded: true,
          content:
            "<div style='width:200px; padding:10px; background-color:white'><b>Drag</b> the pointer over the data or <b>click</b> to view the land cover types within 1 mile of the pointer location. <br><br><b>Click</b> the button below to toggle between view panning and the chart.</div>"
        });
        view.ui.add(instructionsExpand"top-left");
        // Close the 'help' popup when view is focused
        view.watch("focused"function (isFocused) {
          if (isFocused) {
            instructionsExpand.expanded = false;
          }
        });

        let chartEnabled = true;
        let removeChartEvents;
        
        const enableChartButton = document.getElementById("enableChart");
        view.ui.add(enableChartButton"top-left");
        enableChartButton.addEventListener("click"function () {
          chartEnabled = chartEnabled ? false : true;
          if (chartEnabled) {
            enableChartButton.classList.add("esri-icon-pie-chart");
            enableChartButton.classList.remove("esri-icon-pan");
            removeChartEvents = view.on(["drag""click"], function (event) {
              if (pixelData) {
                event.stopPropagation();
                getLandCoverPixelInfo(event).then(updatelandCoverChartupdatefragCoverChart);
              }
            });
          } else {
            removeChartEvents.remove();
            removeChartEvents = null;
            enableChartButton.classList.remove("esri-icon-pie-chart");
            enableChartButton.classList.add("esri-icon-pan");
            graphic.geometry = null;
          }
        });

        const expand = new Expand({
          view: view,
          content: document.getElementById("infoDiv"),
          expanded: true,
          expandIconClass: "esri-icon-settings2"
        })

        view.ui.add(expand"top-right");

      });
    </script>
  </head>
  <body>
    <div id="viewDiv"></div>
    <div id="titleDiv" class="esri-widget">
      <div id="titleText">Testing Application</div>
      <div>100 km radius</div>
    </div>
    <div id="infoDiv" class="esri-widget">
      <canvas
        id="landcover-chart2"
        height="300"
        width="300"
        class="esri-widget"
      ></canvas>
      <div id="chartLegend2" class="chart-legend esri-widget"></div>
      <canvas
        id="landcover-chart1"
        height="300"
        width="300"
        class="esri-widget"
      ></canvas>
      <div id="chartLegend" class="chart-legend esri-widget"></div>
    </div>
    <div
      id="enableChart"
      class="esri-widget--button esri-icon-pie-chart"
      title="Toggle between panning and chart"
    ></div>
  </body>
</html>

 

 

0 Kudos
6 Replies
KenBuja
MVP Esteemed Contributor

You've got two functions with the same name (layerLoaded) that do different things. Renaming one of them brings up the Fragmentation chart. Or instead of using

 

view.whenLayerView(imageryLayer).then(layerLoaded);
function layerLoaded(layerView) {
...
}

 

use this syntax

 

view.whenLayerView(imageryLayer).then((layerView) => {
...
}

 

IGCMCGIS
New Contributor III

Thanks @KenBuja ,

This did help us populate attributes for the fragmentation chart but unfortunately it showed the dataset from the second layer (General Classification from sampleserver6).

We did exactly as you had recommended by using the following syntax.

view.whenLayerView(imageryLayer).then((layerView) => {
...
}

The chart shows up something like this : 

IGCMCGIS_0-1622570045310.png

In addition to this, if we click the toggle button (used to switch between query and navigation), the Classification chart is triggered and the Frgamentation types chart just freezes up.

IGCMCGIS_1-1622570195369.png

It would be wonderful if you could let us know where we are going wrong. Other that replacing the recommended syntax, the rest of the snippet remains the same.

Thanks again!

 

0 Kudos
IGCMCGIS
New Contributor III

Hi @KenBuja 

In addition to this, it seems that this issue occurs based on whichover layer is first interacted over a mouse event.

We ran a short test where our first click was over a fragmentation layer and the fragmentation charts show up. 
Once again as soon as we click the toggle button both charts show Fragmentation classes even though the mouse is hovered over the Classification layer.

IGCMCGIS_0-1622571831775.png

Ideally what we would like is to be able to query multiple imageryLayers (about four or more) simultanously thorugh multiple charts where each chart is catered to a single layer only.

Once again it would be great if you could let us know wheere we went wrong. 
As per your remonnedation we used a differnt name as well.

view.whenLayerView(imageryLayer).then(layerLoad);
        function layerLoad(layerView) {
          // watch for the imagery layer view's updating property
          // to get the updated pixel values
          layerView.watch("updating"function (value) {
            if (!value) {
              pixelData = layerView.pixelData;
            }
          });
 
Unfortunately the issue remains the same 😩
 

Thanks again!

 

0 Kudos
KenBuja
MVP Esteemed Contributor

I'll have to dig into this a little more, but one thing I am noticing is that the navigation/chart button on the original sample doesn't work correctly either. Once you click to switch to panning, it doesn't return to charting after you click it again.

IGCMCGIS
New Contributor III

Hi @KenBuja 

Post a few tests it seems that this issue crops up as soon as we try and add a query syntax for the second service. As long as we use a single service with a single chart the navigation issue does not crop up.

We just tried a short test where only the fragmentation layer is used and the navigation and chart switch works absolutly fine. 

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />
    <title>
      ImageryLayer - client side chart | Sample | ArcGIS API for JavaScript 4.16
    </title>

    <link
      rel="stylesheet"
    />
    <script src="https://js.arcgis.com/4.16/"></script>

    <style>
      html,
      body,
      #viewDiv {
        padding0;
        margin0;
        height100%;
        width100%;
      }

      #titleDiv,
      #infoDiv {
        padding10px;
        width320px;
      }

      #infoDiv {
        height425px;
        font-size14px;
        displaynone;
        overflow-yauto;
        overflow-xhidden;
      }

      #titleText {
        font-size20pt;
        font-weight30;
        padding-bottom10px;
      }
      .chart-legend li span {
        displayinline-block;
        width12px;
        height12px;
        margin-right5px;
      }

      ul {
        list-style-typenone;
      }
    </style>
    <script>
      require([
        "esri/Map",
        "esri/views/MapView",
        "esri/layers/ImageryLayer",
        "esri/widgets/LayerList",
        "esri/Graphic",
        "esri/geometry/Circle",
        "esri/layers/support/TileInfo",
        "esri/core/promiseUtils",
        "esri/widgets/Expand"
      ], function (
        Map,
        MapView,
        ImageryLayer,
        LayerList,
        Graphic,
        Circle,
        TileInfo,
        promiseUtils,
        Expand
      ) {
        let landCoverChartfragCoverChartpixelValCountpixelData;
        let rasterAttributeFeatures = {};

        const map = new Map({
            basemap: "streets-night-vector"
          });

        const view = new MapView({
          container: "viewDiv",
          map: map,
          center: [-8040.5],
          zoom: 2
        });

        // initialize the imagery layer with lerc format
        const imageryLayer = new ImageryLayer({
          url:
          format: "lerc"
        });

        const imageryLayer2 = new ImageryLayer({
          url:
          format: "lerc"
        });
        map.add(imageryLayer2);
        map.add(imageryLayer);

        // graphic that will represent one mile buffer as user drags the pointer
        // over the map to view land cover types within one mile of the pointer
        const graphic = new Graphic({
          geometry: null,
          symbol: {
            type: "simple-fill",
            color: null,
            style: "solid",
            outline: {
              color: "blue",
              width: 2
            }
          }
        });
        view.graphics.add(graphic);

        // add the UI for titles, stats and chart.
        view.ui.add("titleDiv""top-right");
        const infoDiv = document.getElementById("infoDiv");
        view.ui.add(infoDiv"top-right");

        view.whenLayerView(imageryLayer).then(layerLoaded);
        function layerLoaded(layerView) {
          // watch for the imagery layer view's updating property
          // to get the updated pixel values
          layerView.watch("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) {
              event.stopPropagation();
              getLandCoverPixelInfo(event).then(updatefragCoverChart);
            }
          });
          // 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) {
              var hexColor = rgbToHex(
                attributesData[index].attributes.Red,
                attributesData[index].attributes.Green,
                attributesData[index].attributes.Blue
              );
              rasterAttributeFeatures[
                attributesData[index].attributes.Value
              ] = {
                ClassName: attributesData[index].attributes.FragClass,
                hexColor: hexColor
              };
            }
          }
          // initialize the land cover pie chart
          createfragCoverChart();
        }

        // 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(16093 / 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 = 0i <= bufferlengthi++) {
              for (var j = 0j <= bufferlengthj++) {
                // check if the given pixel location is in within one mile of the pointer
                // add its value to pixelValue.
                if (
                  Math.pow(i - bufferDim2+ Math.pow(j - bufferDim2<=
                  radius2
                ) {
                  var pixelValue =
                    pixels[Math.floor(startPixel + i * width + j)];
                }
                if (pixelValue !== undefined) {
                  oneMilePixelValues.push(pixelValue);
                }
              }
            }
          } else {
            oneMilePixelValues.push(pixels[startPixel]);
          }
          pixelValCount = {};
          // get the count of each land type returned within one mile raduis
          for (var i = 0i < oneMilePixelValues.lengthi++) {
            pixelValCount[oneMilePixelValues[i]] =
              1 + (pixelValCount[oneMilePixelValues[i]] || 0);
          }
          var circle = new Circle({
            center: point,
            radius: bufferDim * pixelSizeX
          });

          graphic.geometry = circle;
        });

        // This function is called once pixel values within one mile of the pointer
        // location are processed and ready for the chart update.
        function updatefragCoverChart() {
          if (infoDiv.style.display != "block") {
            infoDiv.style.display = "block";
          }
          fragCoverChart.data.datasets[0].data = [];
          var dataset = [];
          var landCoverTypeColors = [];
          var landCoverTypeLabels = [];

          // 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) {
              landCoverTypeColors.push("rgba(255,255,255,1");
              landCoverTypeLabels.push("NoData");
            } else {
              var color = rasterAttributeFeatures[index].hexColor;
              landCoverTypeColors.push(color);
              landCoverTypeLabels.push(
                rasterAttributeFeatures[index].ClassName
              );
            }
            fragCoverChart.data.datasets[0].data.push(pixelValCount[index]);
          }
          fragCoverChart.data.datasets[0].backgroundColor = landCoverTypeColors;
          fragCoverChart.data.labels = landCoverTypeLabels;
          fragCoverChart.update(0);
          document.getElementById(
            "chartLegend"
          ).innerHTML = fragCoverChart.generateLegend();
        }

        // This function is called when the ImageryLayer is loaded.
        // It sets up the land cover types chart.
        function createfragCoverChart() {
          const landCoverCanvas = document.getElementById("landcover-chart1");
          fragCoverChart = new Chart(landCoverCanvas.getContext("2d"), {
            type: "bar",
            data: {
              labels: [],
              datasets: [
                {
                  data: [],
                  backgroundColor: [],
                  borderColor: "rgb(0, 0, 0, 0, 1)",
                  borderWidth: 0.5
                }
              ]
            },
            options: {
              responsive: false,
              cutoutPercentage: 35,
              legend: {
                display: false
              },
              title: {
                display: true,
                text: "Fragmentaton Types"
              },
              plugins: {
                datalabels: {
                  formatter: function (valuectx) {
                    let datasets = ctx.chart.data.datasets;
                    if (datasets.indexOf(ctx.dataset=== datasets.length - 1) {
                      let sum = datasets[0].data.reduce(function (ab) {
                        return a + b;
                      });
                      let percentage = Math.round((value / sum* 100);
                      if (percentage > 15) {
                        return percentage + "%";
                      } else {
                        return "";
                      }
                    } else {
                      return percentage;
                    }
                  },
                  color: "#4c4c4c"
                }
              }
            }
          });
        }
        
        function componentToHex(c) {
          var hex = c.toString(16);
          return hex.length == 1 ? "0" + hex : hex;
        }

        function rgbToHex(rgb) {
          return (
            "#" + componentToHex(r+ componentToHex(g+ componentToHex(b)
          );
        }

        const instructionsExpand = new Expand({
          expandIconClass: "esri-icon-question",
          expandTooltip: "How to use this sample",
          view: view,
          expanded: true,
          content:
            "<div style='width:200px; padding:10px; background-color:white'><b>Drag</b> the pointer over the data or <b>click</b> to view the land cover types within 1 mile of the pointer location. <br><br><b>Click</b> the button below to toggle between view panning and the chart.</div>"
        });
        view.ui.add(instructionsExpand"top-left");
        // Close the 'help' popup when view is focused
        view.watch("focused"function (isFocused) {
          if (isFocused) {
            instructionsExpand.expanded = false;
          }
        });

        let chartEnabled = true;
        let removeChartEvents;
        
        const enableChartButton = document.getElementById("enableChart");
        view.ui.add(enableChartButton"top-left");
        enableChartButton.addEventListener("click"function () {
          chartEnabled = chartEnabled ? false : true;
          if (chartEnabled) {
            enableChartButton.classList.add("esri-icon-pie-chart");
            enableChartButton.classList.remove("esri-icon-pan");
            removeChartEvents = view.on(["drag""click"], function (event) {
              if (pixelData) {
                event.stopPropagation();
                getLandCoverPixelInfo(event).then(updatefragCoverChart);
              }
              else if (pixelData){
                event.stopPropagation();
                getLandCoverPixelInfo(event).then(updatelandCoverChart);
              }
            });
          } else {
            removeChartEvents.remove();
            removeChartEvents = null;
            enableChartButton.classList.remove("esri-icon-pie-chart");
            enableChartButton.classList.add("esri-icon-pan");
            graphic.geometry = null;
          }
        });

        const expand = new Expand({
          view: view,
          content: document.getElementById("infoDiv"),
          expanded: true,
          expandIconClass: "esri-icon-settings2"
        })

        view.ui.add(expand"top-right");

      });
    </script>
  </head>
  <body>
    <div id="viewDiv"></div>
    <div id="titleDiv" class="esri-widget">
      <div id="titleText">Foreast Forward Testing</div>
      <div>100 km radius</div>
    </div>
    <div id="infoDiv" class="esri-widget">
      <canvas
        id="landcover-chart2"
        height="300"
        width="300"
        class="esri-widget"
      ></canvas>
      <div id="chartLegend2" class="chart-legend esri-widget"></div>
      <canvas
        id="landcover-chart1"
        height="300"
        width="300"
        class="esri-widget"
      ></canvas>
      <div id="chartLegend" class="chart-legend esri-widget"></div>
    </div>
    <div
      id="enableChart"
      class="esri-widget--button esri-icon-pie-chart"
      title="Toggle between panning and chart"
    ></div>
  </body>
</html>
 
DO let us know if you are able to replicate this at your end.

 

0 Kudos
IGCMCGIS
New Contributor III

Hello @KenBuja ,

Just wanted to know if you have had any success with testing this issue.

Thanks again!

0 Kudos