OutStatistics with Time Slider

1563
7
Jump to solution
08-19-2021 02:55 PM
cschooley
New Contributor III

Hey there. Learning the Javascript API still and I'm trying to calculate some out statistics that then displays that info in an information box. These statistics are the GDP, Employment numbers, etc of each year for the Oil and Gas industry in Utah. 

I keep getting an error related to the outStatistics when I try. 

Here's the error in my console:

cschooley_0-1629409722813.png

Here's the link to the feature layer:

Oil and Gas Well Locations Feature Layer 

Here's the link to the website:

Oil and Gas Time Lapse Application 

Here's Javascript Code:

require([
  "esri/Map",
  "esri/views/MapView",
  "esri/layers/FeatureLayer",
  "esri/widgets/Expand",
  "esri/widgets/Legend",
  "esri/widgets/TimeSlider"
], function (Map, MapView, FeatureLayer, Expand, Legend, TimeSlider) {

let OGLayerView;

  const layer = new FeatureLayer({
    portalItem: {
      id: "dd28d5595a2940929574e79522bb4245"
    }
  });

  // Create Map
const map = new Map({
  basemap: "topo",
  layers: [layer]
});

 // Set the map view
const view = new MapView({
  container: "viewDiv",
  map: map,
  center: [-111.65124179920204, 39.707361735142236],
  zoom:7
});

// Create a collapsible legend
const legendExpand = new Expand({
  collapsedIconClass: "esri-icon-collapse",
  expandIconClass: "esri-icon-expand",
  expandTooltip: "Legend",
  view: view,
  content: new Legend({
    view: view
  }),
  expanded: false
});
view.ui.add(legendExpand, "top-left");

// Create events for the time slider
const events = [
  {name:`Great Recession`, date: 2008},
  {name: `Covid-19 Pandemic`, date: 2020}
];

// Create time slider with interval set to 5 years
const timeSlider = new TimeSlider({
    container: "timeSlider",
    playRate: 750,
    stops: {
      interval: {
        value: 5,
        unit: "years"
      }
    },

    // time slider time extent
    //fullTimeExtent: {
       // start: new Date(1900,0,1),
       // end: new Date(2022,0,1)
    //},

    // configure ticks for dates
    tickConfigs: [{
      mode: "position",
      values: [
        new Date(1900, 0, 1), new Date(1910, 0, 1), new Date(1920, 0, 1), new Date(1930, 0, 1), new Date(1940, 0, 1), new Date(1950, 0, 1), new Date(1960, 0, 1), new Date(1970, 0, 1),
        new Date(1980, 0 , 1), new Date(1990, 0, 1), new Date(2000, 0, 1), new Date(2010, 0, 1), new Date(2020, 0, 1)
      ].map((date) => date.getTime()),
      labelsVisible: true,
      labelFormatFunction: (value) => {
        const date = new Date(value);
        return `${date.getUTCFullYear()}`;
      },
      tickCreatedFunction: (value, tickElement, labelElement) => {
        tickElement.classList.add("custom-ticks2");
        labelElement.classList.add("custom-labels2");
      }
    }, {
      mode: "position",
      values: events
        .map((event) => new Date(event.date, 0, 1))
        .map((date) => date.getTime()),
      labelsVisible: true,
      labelFormatFunction: (value) => {
        const event = events.find(
          (s) => new Date(s.date, 0, 1).getTime() === value
        );
        return event.name;
      },
      tickCreatedFunction: (value, tickElement, labelElement) => {
        tickElement.classList.add("custom-ticks");
        labelElement.classList.add("custom-labels");
      }
    }
  ]
});

// add time slider to view
view.ui.add(timeSlider);

// Creating view layer???
view.whenLayerView(layer).then((layerView) => {
  OGLayerView = layerView;

// Setting start date for time slider
  const start = new Date(1900, 0, 1);

 // Extent for time Slider 
  timeSlider.fullTimeExtent = {
    start: start,
    end: layer.timeInfo.fullTimeExtent.end
  };

// Show 5 year intervals

let end = new Date(start);

end.setDate(end.getDate() + 1825); // the number here is in days (1825 = 5 years)

timeSlider.timeExtent = {start,end};

});

// watch timeslider timeExtent change
timeSlider.watch("timeExtent", () => {
  //oil wells that popped up before the end of the current time extent
OGLayerView.filter = {
  where: "OrigComplDate <=" + timeSlider.timeExtent.end.getTime(),
}
// add grayscale effect to old wells (may or may not keep this)
  OGLayerView.effect = {
    filter: {
      timeExtent:timeSlider.timeExtent,
      geometry: view.extent
    },
    excludedEffect: "grayscale(80%) opacity(20%)"
  };
});

// Run statistics for GDP within current time extent
const statQuery = OGLayerView.effect.filter.createQuery();
statQuery.outStatistics = [
GDPAvg
];

layer.queryFeatures(statQuery).then((result) => {
let htmls = [];
statsDiv.innerHTML = "";
if (result.error) {
  return result.error;
} else {
  if (result.feature.length >= 1) {
    const attributes = result.features[0].attributes;
    for (stat in statsFields) {
      if (attributes[name] && attributes[name] != null) {
        const html =
        "<br/>" +
        statsFields[name] +
        ": <b><span>" + // setting bolding and styling
        attributes[name].toFixed(0) + // How many decimal places
        "</span></b>"; // setting bolding and styling to attribute information
        htmls.push(html) // push html into code into information box with attribute information
      }
    }
    const yearHtml =
      "<span>" +
      result.features[0].attributes["GDP_Average"] +
      "</span> billion dollars was added to Utah's GDP by the Oil and Gas Industry between" +
      timeSlider.timeExtent.start.toLocaleDateString() +
      " and " +
      timeSlider.timeExtent.end.toLocaleDateString() +
      ".<br/>";

    if (htmls[0] == undefined) {
      statsDiv.innerHTML = yearHtml;
    } else {
      statsDiv.innerHTML =
        yearHtml + htmls[0];
  }
}
}
})
.catch((error) => {
console.log(error);
});

const GDPAvg = {
  onstatisticField: "GDP_billions_", //using field name instead of field alias??? Don't know if I should. I have tried both ways now
  outStatisticFieldName: "GDP_Average",
  statisticType: "avg"
};

const statsFields = {
  GDP_Average: "GDP Average"
};

const statsDiv = document.getElementById("statsDiv");
      const infoDiv = document.getElementById("infoDiv");
      const infoDivExpand = new Expand({
        collapsedIconClass: "esri-icon-collapse",
        expandIconClass: "esri-icon-expand",
        expandTooltip: "Expand Oil and Gas Industry info",
        view: view,
        content: infoDiv,
        expanded: true
      });
      view.ui.add(infoDivExpand, "top-right");

});

 

If you want:

HTML Code:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
  <title>OGM Time Lapse</title>
  <link rel="stylesheet" href="https://js.arcgis.com/4.20/esri/themes/light/main.css">
  <link rel="stylesheet" href="main.css">
  <script src="https://js.arcgis.com/4.20/"></script>
  <script type = "text/javascript" src="main.js"></script>
</head>

<body>
  <div id="viewDiv"></div>
  <div id="timeSlider" class = "sample-slider"></div>
  <div id="infoDiv" class="esri-widget">
    <div> <b>Growth of the Oil and Gas Industry</b></div><br/>
    <div id="statsDiv" class="esri-widget"></div>
  </div>
</body>

</html>

 

CSS Code:

html,
body,
#viewDiv {
	padding: 0;
	margin: 0;
	height: 100%;
	width: 100%;
}
.sample-slider {
	position: absolute;
	left: 2%;
	right: 2%;
	bottom: 20px;
	margin: 20px ;
  }

#timeSlider .custom-labels{
	font-family: Georgia, 'Times New Roman', Times, serif;
	font-size: 10px;
	color: red;
	margin-top: -35px;
}

#timeSlider .custom-ticks{
	background-color: black;
	width: 1px;
	height: 8px;
	margin-top: -30px;
}

#timeSlider .custom-labels2{
	font-family: Georgia, 'Times New Roman', Times, serif;
	font-size: 12px;
	font-weight: bold;
	color: black;
	margin-top: 7 px;
}

#timeSlider .custom-ticks2{
	background-color: black;
	width: 1px;
	height: 8px;
	margin-top: 2px;
}

.esri-time-slider__slider .esri-slider {
    margin-top: -5px;
}

#infoDiv {
	height: 200px;
	padding: 10px;
	width: 280px;
}

#infoDiv span {
	color: #77496B;
	font-size: 12pt;
	font-weight: bolder;
}

#titleDiv {
	padding: 10px
}

#titleText {
	font-family: Georgia, 'Times New Roman', Times, serif;
	font-size: 20pt;
	font-weight: 60;
	padding-bottom: 10px;
}

 

Link to sample code from Esri that I've been referencing:

Esri Filter with Time Slider Example 

 

Whole code if you want:

<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no"/>
    <title>Filter features with TimeSlider | Sample | ArcGIS API for JavaScript 4.20</title>

    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
      }

      #timeSlider {
        width: 400px;
      }

      #infoDiv {
        height: 200px;
        padding: 10px;
        width: 280px;
      }

      #infoDiv span {
        color: #F9C653;
        font-size: 12pt;
        font-weight: bolder;
      }
    </style>

    <link rel="stylesheet" href="https://js.arcgis.com/4.20/esri/themes/dark/main.css" />
    <script src="https://js.arcgis.com/4.20/"></script>
    <script>
      require([
        "esri/Map",
        "esri/views/MapView",
        "esri/layers/GeoJSONLayer",
        "esri/widgets/TimeSlider",
        "esri/widgets/Expand",
        "esri/widgets/Legend"
      ], (Map, MapView, GeoJSONLayer, TimeSlider, Expand, Legend) => {
        let layerView;

        // set the timeInfo on GeoJSONLayer at the time initialization
        const layer = new GeoJSONLayer({
          url: "https://bsvensson.github.io/various-tests/geojson/usgs-earthquakes-06182019.geojson",
          copyright: "USGS Earthquakes",
          title: "USGS Earthquakes",
          // set the CSVLayer's timeInfo based on the date field
          timeInfo: {
            startField: "time", // name of the date field
            interval: {
              // set time interval to one day
              unit: "days",
              value: 1
            }
          },
          renderer: {
            type: "simple",
            field: "mag",
            symbol: {
              type: "simple-marker",
              color: "orange",
              outline: null
            },
            visualVariables: [
              {
                type: "size",
                field: "mag",
                stops: [
                  {
                    value: 1,
                    size: "5px"
                  },
                  {
                    value: 2,
                    size: "15"
                  },
                  {
                    value: 3,
                    size: "35"
                  }
                ]
              },
              {
                type: "color",
                field: "depth",
                stops: [
                  {
                    value: 2.5,
                    color: "#F9C653",
                    label: "<2km"
                  },
                  {
                    value: 3.5,
                    color: "#F8864D",
                    label: "3km"
                  },
                  {
                    value: 4,
                    color: "#C53C06",
                    label: ">4km"
                  }
                ]
              }
            ]
          },
          popupTemplate: {
            title: "{title}",
            content: [
              {
                type: "fields",
                fieldInfos: [
                  {
                    fieldName: "place",
                    label: "Location",
                    visible: true
                  },
                  {
                    fieldName: "mag",
                    label: "Magnitude",
                    visible: true
                  },
                  {
                    fieldName: "depth",
                    label: "Depth",
                    visible: true
                  }
                ]
              }
            ]
          }
        });

        const map = new Map({
          basemap: "dark-gray-vector",
          layers: [layer]
        });

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


        // create a new time slider widget
        // set other properties when the layer view is loaded
        // by default timeSlider.mode is "time-window" - shows
        // data falls within time range
        const timeSlider = new TimeSlider({
          container: "timeSlider",
          playRate: 50,
          stops: {
            interval: {
              value: 1,
              unit: "hours"
            }
          }
        });
        view.ui.add(timeSlider, "bottom-left");

        // wait till the layer view is loaded
        view.whenLayerView(layer).then((lv) => {
          layerView = lv;

          // start time of the time slider - 5/25/2019
          const start = new Date(2019, 4, 25);
          // set time slider's full extent to
          // 5/25/5019 - until end date of layer's fullTimeExtent
          timeSlider.fullTimeExtent = {
            start: start,
            end: layer.timeInfo.fullTimeExtent.end
          };

          // We will be showing earthquakes with one day interval
          // when the app is loaded we will show earthquakes that
          // happened between 5/25 - 5/26.
          let end = new Date(start);
          // end of current time extent for time slider
          // showing earthquakes with one day interval
          end.setDate(end.getDate() + 1);

          // timeExtent property is set so that timeslider
          // widget show the first day. We are setting
          // the thumbs positions.
          timeSlider.timeExtent = {start, end};
        });


        // watch for time slider timeExtent change
        timeSlider.watch("timeExtent", () => {
          // only show earthquakes happened up until the end of
          // timeSlider's current time extent.
          layer.definitionExpression =
            "time <= " + timeSlider.timeExtent.end.getTime();

          // now gray out earthquakes that happened before the time slider's current
          // timeExtent... leaving footprint of earthquakes that already happened
          layerView.effect = {
            filter: {
              timeExtent: timeSlider.timeExtent,
              geometry: view.extent
            },
            excludedEffect: "grayscale(20%) opacity(12%)"
          };

          // run statistics on earthquakes fall within the current time extent
          const statQuery = layerView.effect.filter.createQuery();
          statQuery.outStatistics = [
            magMax,
            magAvg,
            magMin,
            tremorCount,
            avgDepth
          ];

          layer.queryFeatures(statQuery).then((result) => {
              let htmls = [];
              statsDiv.innerHTML = "";
              if (result.error) {
                return result.error;
              } else {
                if (result.features.length >= 1) {
                  const attributes = result.features[0].attributes;
                  for (name in statsFields) {
                    if (attributes[name] && attributes[name] != null) {
                      const html =
                        "<br/>" +
                        statsFields[name] +
                        ": <b><span> " +
                        attributes[name].toFixed(2) +
                        "</span></b>";
                      htmls.push(html);
                    }
                  }
                  const yearHtml =
                    "<span>" +
                    result.features[0].attributes["tremor_count"] +
                    "</span> earthquakes were recorded between " +
                    timeSlider.timeExtent.start.toLocaleDateString() +
                    " - " +
                    timeSlider.timeExtent.end.toLocaleDateString() +
                    ".<br/>";

                  if (htmls[0] == undefined) {
                    statsDiv.innerHTML = yearHtml;
                  } else {
                    statsDiv.innerHTML =
                      yearHtml + htmls[0] + htmls[1] + htmls[2] + htmls[3];
                  }
                }
              }
            })
            .catch((error) => {
              console.log(error);
            });
        });

        const avgDepth = {
          onStatisticField: "depth",
          outStatisticFieldName: "Average_depth",
          statisticType: "avg"
        };

        const magMax = {
          onStatisticField: "mag",
          outStatisticFieldName: "Max_magnitude",
          statisticType: "max"
        };

        const magAvg = {
          onStatisticField: "mag",
          outStatisticFieldName: "Average_magnitude",
          statisticType: "avg"
        };

        const magMin = {
          onStatisticField: "mag",
          outStatisticFieldName: "Min_magnitude",
          statisticType: "min"
        };

        const tremorCount = {
          onStatisticField: "mag",
          outStatisticFieldName: "tremor_count",
          statisticType: "count"
        };

        const statsFields = {
          Max_magnitude: "Max magnitude",
          Average_magnitude: "Average magnitude",
          Min_magnitude: "Min magnitude",
          Average_depth: "Average Depth"
        };

        // add a legend for the earthquakes layer
        const legendExpand = new Expand({
          collapsedIconClass: "esri-icon-collapse",
          expandIconClass: "esri-icon-expand",
          expandTooltip: "Legend",
          view: view,
          content: new Legend({
            view: view
          }),
          expanded: false
        });
        view.ui.add(legendExpand, "top-left");

        const statsDiv = document.getElementById("statsDiv");
        const infoDiv = document.getElementById("infoDiv");
        const infoDivExpand = new Expand({
          collapsedIconClass: "esri-icon-collapse",
          expandIconClass: "esri-icon-expand",
          expandTooltip: "Expand earthquakes info",
          view: view,
          content: infoDiv,
          expanded: true
        });
        view.ui.add(infoDivExpand, "top-right");
      });
    </script>
  </head>

  <body>
    <div id="viewDiv"></div>
    <div id="timeSlider"></div>
    <div id="infoDiv" class="esri-widget">
      <div> <b>USGS Earthquakes</b></div><br/>
      <div id="statsDiv" class="esri-widget"></div>
    </div>
  </body>
</html>

 

Any and all suggestions are much appreciated. Javascript isn't a language I am well-versed with. Still in the beginner stages of learning how it all works. Pretty good with python, so I understand the basics of loops, functions, etc. 

 

 

0 Kudos
1 Solution

Accepted Solutions
KenBuja
MVP Esteemed Contributor

The reason you were getting an error is due to a case problem

const GDPAvg = {
  onstatisticField: "GDP_billions_", //using field name instead of field alias??? Don't know if I should. I have tried both ways now
  outStatisticFieldName: "GDP_Average",
  statisticType: "avg"
};

Line 2 should  be "onStatisticField"

You closed off the timeSlider watch function too early (at line 144 of your first code fragment instead of 192)

Another problem is on line 158 , which should be "features" instead of "feature"

if (result.features.length >= 1) {

 

Here's the code I'm working with, which is still giving me a Null for the line 282 where it reports the result

<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
  <title>Filter features with TimeSlider | Sample | ArcGIS API for JavaScript 4.20</title>

  <style>
    html,
    body,
    #viewDiv {
      padding: 0;
      margin: 0;
      height: 100%;
      width: 100%;
    }

    #timeSlider {
      width: 400px;
    }

    #infoDiv {
      height: 200px;
      padding: 10px;
      width: 280px;
    }

      #infoDiv span {
        color: #F9C653;
        font-size: 12pt;
        font-weight: bolder;
      }

    html,
    body,
    #viewDiv {
      padding: 0;
      margin: 0;
      height: 100%;
      width: 100%;
    }

    .sample-slider {
      position: absolute;
      left: 2%;
      right: 2%;
      bottom: 20px;
      margin: 20px;
    }

    #timeSlider .custom-labels {
      font-family: Georgia, 'Times New Roman', Times, serif;
      font-size: 10px;
      color: red;
      margin-top: -35px;
    }

    #timeSlider .custom-ticks {
      background-color: black;
      width: 1px;
      height: 8px;
      margin-top: -30px;
    }

    #timeSlider .custom-labels2 {
      font-family: Georgia, 'Times New Roman', Times, serif;
      font-size: 12px;
      font-weight: bold;
      color: black;
      margin-top: 7 px;
    }

    #timeSlider .custom-ticks2 {
      background-color: black;
      width: 1px;
      height: 8px;
      margin-top: 2px;
    }

    .esri-time-slider__slider .esri-slider {
      margin-top: -5px;
    }

    #infoDiv {
      height: 200px;
      padding: 10px;
      width: 280px;
    }

      #infoDiv span {
        color: #77496B;
        font-size: 12pt;
        font-weight: bolder;
      }

    #titleDiv {
      padding: 10px
    }

    #titleText {
      font-family: Georgia, 'Times New Roman', Times, serif;
      font-size: 20pt;
      font-weight: 60;
      padding-bottom: 10px;
    }
  </style>

  <link rel="stylesheet" href="https://js.arcgis.com/4.20/esri/themes/dark/main.css" />
  <script src="https://js.arcgis.com/4.20/"></script>
  <script>
    require([
      "esri/Map",
      "esri/views/MapView",
      "esri/layers/FeatureLayer",
      "esri/widgets/Expand",
      "esri/widgets/Legend",
      "esri/widgets/TimeSlider"
    ], function (Map, MapView, FeatureLayer, Expand, Legend, TimeSlider) {

      let OGLayerView;

      const layer = new FeatureLayer({
        portalItem: {
          id: "dd28d5595a2940929574e79522bb4245"
        }
      });

      // Create Map
      const map = new Map({
        basemap: "topo",
        layers: [layer]
      });

      // Set the map view
      const view = new MapView({
        container: "viewDiv",
        map: map,
        center: [-111.65124179920204, 39.707361735142236],
        zoom: 7
      });

      // Create a collapsible legend
      const legendExpand = new Expand({
        collapsedIconClass: "esri-icon-collapse",
        expandIconClass: "esri-icon-expand",
        expandTooltip: "Legend",
        view: view,
        content: new Legend({
          view: view
        }),
        expanded: false
      });
      view.ui.add(legendExpand, "top-left");

      // Create events for the time slider
      const events = [
        { name: `Great Recession`, date: 2008 },
        { name: `Covid-19 Pandemic`, date: 2020 }
      ];

      // Create time slider with interval set to 5 years
      const timeSlider = new TimeSlider({
        container: "timeSlider",
        playRate: 750,
        stops: {
          interval: {
            value: 5,
            unit: "years"
          }
        },

        // time slider time extent
        //fullTimeExtent: {
        // start: new Date(1900,0,1),
        // end: new Date(2022,0,1)
        //},

        // configure ticks for dates
        tickConfigs: [{
          mode: "position",
          values: [
            new Date(1900, 0, 1), new Date(1910, 0, 1), new Date(1920, 0, 1), new Date(1930, 0, 1), new Date(1940, 0, 1), new Date(1950, 0, 1), new Date(1960, 0, 1), new Date(1970, 0, 1),
            new Date(1980, 0, 1), new Date(1990, 0, 1), new Date(2000, 0, 1), new Date(2010, 0, 1), new Date(2020, 0, 1)
          ].map((date) => date.getTime()),
          labelsVisible: true,
          labelFormatFunction: (value) => {
            const date = new Date(value);
            return `${date.getUTCFullYear()}`;
          },
          tickCreatedFunction: (value, tickElement, labelElement) => {
            tickElement.classList.add("custom-ticks2");
            labelElement.classList.add("custom-labels2");
          }
        }, {
          mode: "position",
          values: events
            .map((event) => new Date(event.date, 0, 1))
            .map((date) => date.getTime()),
          labelsVisible: true,
          labelFormatFunction: (value) => {
            const event = events.find(
              (s) => new Date(s.date, 0, 1).getTime() === value
            );
            return event.name;
          },
          tickCreatedFunction: (value, tickElement, labelElement) => {
            tickElement.classList.add("custom-ticks");
            labelElement.classList.add("custom-labels");
          }
        }
        ]
      });

      // add time slider to view
      view.ui.add(timeSlider);

      // Creating view layer???
      view.whenLayerView(layer).then((layerView) => {
        OGLayerView = layerView;

        // Setting start date for time slider
        const start = new Date(1900, 0, 1);

        // Extent for time Slider
        timeSlider.fullTimeExtent = {
          start: start,
          end: layer.timeInfo.fullTimeExtent.end
        };

        // Show 5 year intervals

        let end = new Date(start);

        end.setDate(end.getDate() + 1825); // the number here is in days (1825 = 5 years)

        timeSlider.timeExtent = { start, end };

      });

      // watch timeslider timeExtent change
      timeSlider.watch("timeExtent", () => {
        //oil wells that popped up before the end of the current time extent
        OGLayerView.filter = {
          where: "OrigComplDate <=" + timeSlider.timeExtent.end.getTime(),
        }
        // add grayscale effect to old wells (may or may not keep this)
        OGLayerView.effect = {
          filter: {
            timeExtent: timeSlider.timeExtent,
            geometry: view.extent
          },
          excludedEffect: "grayscale(80%) opacity(20%)"
        };


        // Run statistics for GDP within current time extent
        const statQuery = OGLayerView.effect.filter.createQuery();
        statQuery.outStatistics = [
          GDPAvg
        ];

        layer.queryFeatures(statQuery).then((result) => {
          let htmls = [];
          statsDiv.innerHTML = "";
          if (result.error) {
            return result.error;
          } else {
            if (result.features.length >= 1) {
              const attributes = result.features[0].attributes;
              for (stat in statsFields) {
                if (attributes[name] && attributes[name] != null) {
                  const html =
                    "<br/>" +
                    statsFields[name] +
                    ": <b><span>" + // setting bolding and styling
                    attributes[name].toFixed(0) + // How many decimal places
                    "</span></b>"; // setting bolding and styling to attribute information
                  htmls.push(html) // push html into code into information box with attribute information
                }
              }
              const yearHtml =
                "<span>" +
                result.features[0].attributes["GDP_Average"] +
                "</span> billion dollars was added to Utah's GDP by the Oil and Gas Industry between" +
                timeSlider.timeExtent.start.toLocaleDateString() +
                " and " +
                timeSlider.timeExtent.end.toLocaleDateString() +
                ".<br/>";

              if (htmls[0] == undefined) {
                statsDiv.innerHTML = yearHtml;
              } else {
                statsDiv.innerHTML =
                  yearHtml + htmls[0];
              }
            }
          }
        })
          .catch((error) => {
            console.log(error);
          });
      });

        const GDPAvg = {
          onStatisticField: "GDP_billions_", //using field name instead of field alias??? Don't know if I should. I have tried both ways now
          outStatisticFieldName: "GDP_Average",
          statisticType: "avg"
        };

        const statsFields = {
          GDP_Average: "GDP Average"
        };

      const statsDiv = document.getElementById("statsDiv");
      const infoDiv = document.getElementById("infoDiv");
      const infoDivExpand = new Expand({
        collapsedIconClass: "esri-icon-collapse",
        expandIconClass: "esri-icon-expand",
        expandTooltip: "Expand Oil and Gas Industry info",
        view: view,
        content: infoDiv,
        expanded: true
      });
      view.ui.add(infoDivExpand, "top-right");

    });
  </script>
</head>

<body>
  <div id="viewDiv"></div>
  <div id="timeSlider" class="sample-slider"></div>
  <div id="infoDiv" class="esri-widget">
    <div> <b>Growth of the Oil and Gas Industry</b></div><br />
    <div id="statsDiv" class="esri-widget"></div>
  </div>
</body>
</html>

 

View solution in original post

7 Replies
KenBuja
MVP Esteemed Contributor

The reason you were getting an error is due to a case problem

const GDPAvg = {
  onstatisticField: "GDP_billions_", //using field name instead of field alias??? Don't know if I should. I have tried both ways now
  outStatisticFieldName: "GDP_Average",
  statisticType: "avg"
};

Line 2 should  be "onStatisticField"

You closed off the timeSlider watch function too early (at line 144 of your first code fragment instead of 192)

Another problem is on line 158 , which should be "features" instead of "feature"

if (result.features.length >= 1) {

 

Here's the code I'm working with, which is still giving me a Null for the line 282 where it reports the result

<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
  <title>Filter features with TimeSlider | Sample | ArcGIS API for JavaScript 4.20</title>

  <style>
    html,
    body,
    #viewDiv {
      padding: 0;
      margin: 0;
      height: 100%;
      width: 100%;
    }

    #timeSlider {
      width: 400px;
    }

    #infoDiv {
      height: 200px;
      padding: 10px;
      width: 280px;
    }

      #infoDiv span {
        color: #F9C653;
        font-size: 12pt;
        font-weight: bolder;
      }

    html,
    body,
    #viewDiv {
      padding: 0;
      margin: 0;
      height: 100%;
      width: 100%;
    }

    .sample-slider {
      position: absolute;
      left: 2%;
      right: 2%;
      bottom: 20px;
      margin: 20px;
    }

    #timeSlider .custom-labels {
      font-family: Georgia, 'Times New Roman', Times, serif;
      font-size: 10px;
      color: red;
      margin-top: -35px;
    }

    #timeSlider .custom-ticks {
      background-color: black;
      width: 1px;
      height: 8px;
      margin-top: -30px;
    }

    #timeSlider .custom-labels2 {
      font-family: Georgia, 'Times New Roman', Times, serif;
      font-size: 12px;
      font-weight: bold;
      color: black;
      margin-top: 7 px;
    }

    #timeSlider .custom-ticks2 {
      background-color: black;
      width: 1px;
      height: 8px;
      margin-top: 2px;
    }

    .esri-time-slider__slider .esri-slider {
      margin-top: -5px;
    }

    #infoDiv {
      height: 200px;
      padding: 10px;
      width: 280px;
    }

      #infoDiv span {
        color: #77496B;
        font-size: 12pt;
        font-weight: bolder;
      }

    #titleDiv {
      padding: 10px
    }

    #titleText {
      font-family: Georgia, 'Times New Roman', Times, serif;
      font-size: 20pt;
      font-weight: 60;
      padding-bottom: 10px;
    }
  </style>

  <link rel="stylesheet" href="https://js.arcgis.com/4.20/esri/themes/dark/main.css" />
  <script src="https://js.arcgis.com/4.20/"></script>
  <script>
    require([
      "esri/Map",
      "esri/views/MapView",
      "esri/layers/FeatureLayer",
      "esri/widgets/Expand",
      "esri/widgets/Legend",
      "esri/widgets/TimeSlider"
    ], function (Map, MapView, FeatureLayer, Expand, Legend, TimeSlider) {

      let OGLayerView;

      const layer = new FeatureLayer({
        portalItem: {
          id: "dd28d5595a2940929574e79522bb4245"
        }
      });

      // Create Map
      const map = new Map({
        basemap: "topo",
        layers: [layer]
      });

      // Set the map view
      const view = new MapView({
        container: "viewDiv",
        map: map,
        center: [-111.65124179920204, 39.707361735142236],
        zoom: 7
      });

      // Create a collapsible legend
      const legendExpand = new Expand({
        collapsedIconClass: "esri-icon-collapse",
        expandIconClass: "esri-icon-expand",
        expandTooltip: "Legend",
        view: view,
        content: new Legend({
          view: view
        }),
        expanded: false
      });
      view.ui.add(legendExpand, "top-left");

      // Create events for the time slider
      const events = [
        { name: `Great Recession`, date: 2008 },
        { name: `Covid-19 Pandemic`, date: 2020 }
      ];

      // Create time slider with interval set to 5 years
      const timeSlider = new TimeSlider({
        container: "timeSlider",
        playRate: 750,
        stops: {
          interval: {
            value: 5,
            unit: "years"
          }
        },

        // time slider time extent
        //fullTimeExtent: {
        // start: new Date(1900,0,1),
        // end: new Date(2022,0,1)
        //},

        // configure ticks for dates
        tickConfigs: [{
          mode: "position",
          values: [
            new Date(1900, 0, 1), new Date(1910, 0, 1), new Date(1920, 0, 1), new Date(1930, 0, 1), new Date(1940, 0, 1), new Date(1950, 0, 1), new Date(1960, 0, 1), new Date(1970, 0, 1),
            new Date(1980, 0, 1), new Date(1990, 0, 1), new Date(2000, 0, 1), new Date(2010, 0, 1), new Date(2020, 0, 1)
          ].map((date) => date.getTime()),
          labelsVisible: true,
          labelFormatFunction: (value) => {
            const date = new Date(value);
            return `${date.getUTCFullYear()}`;
          },
          tickCreatedFunction: (value, tickElement, labelElement) => {
            tickElement.classList.add("custom-ticks2");
            labelElement.classList.add("custom-labels2");
          }
        }, {
          mode: "position",
          values: events
            .map((event) => new Date(event.date, 0, 1))
            .map((date) => date.getTime()),
          labelsVisible: true,
          labelFormatFunction: (value) => {
            const event = events.find(
              (s) => new Date(s.date, 0, 1).getTime() === value
            );
            return event.name;
          },
          tickCreatedFunction: (value, tickElement, labelElement) => {
            tickElement.classList.add("custom-ticks");
            labelElement.classList.add("custom-labels");
          }
        }
        ]
      });

      // add time slider to view
      view.ui.add(timeSlider);

      // Creating view layer???
      view.whenLayerView(layer).then((layerView) => {
        OGLayerView = layerView;

        // Setting start date for time slider
        const start = new Date(1900, 0, 1);

        // Extent for time Slider
        timeSlider.fullTimeExtent = {
          start: start,
          end: layer.timeInfo.fullTimeExtent.end
        };

        // Show 5 year intervals

        let end = new Date(start);

        end.setDate(end.getDate() + 1825); // the number here is in days (1825 = 5 years)

        timeSlider.timeExtent = { start, end };

      });

      // watch timeslider timeExtent change
      timeSlider.watch("timeExtent", () => {
        //oil wells that popped up before the end of the current time extent
        OGLayerView.filter = {
          where: "OrigComplDate <=" + timeSlider.timeExtent.end.getTime(),
        }
        // add grayscale effect to old wells (may or may not keep this)
        OGLayerView.effect = {
          filter: {
            timeExtent: timeSlider.timeExtent,
            geometry: view.extent
          },
          excludedEffect: "grayscale(80%) opacity(20%)"
        };


        // Run statistics for GDP within current time extent
        const statQuery = OGLayerView.effect.filter.createQuery();
        statQuery.outStatistics = [
          GDPAvg
        ];

        layer.queryFeatures(statQuery).then((result) => {
          let htmls = [];
          statsDiv.innerHTML = "";
          if (result.error) {
            return result.error;
          } else {
            if (result.features.length >= 1) {
              const attributes = result.features[0].attributes;
              for (stat in statsFields) {
                if (attributes[name] && attributes[name] != null) {
                  const html =
                    "<br/>" +
                    statsFields[name] +
                    ": <b><span>" + // setting bolding and styling
                    attributes[name].toFixed(0) + // How many decimal places
                    "</span></b>"; // setting bolding and styling to attribute information
                  htmls.push(html) // push html into code into information box with attribute information
                }
              }
              const yearHtml =
                "<span>" +
                result.features[0].attributes["GDP_Average"] +
                "</span> billion dollars was added to Utah's GDP by the Oil and Gas Industry between" +
                timeSlider.timeExtent.start.toLocaleDateString() +
                " and " +
                timeSlider.timeExtent.end.toLocaleDateString() +
                ".<br/>";

              if (htmls[0] == undefined) {
                statsDiv.innerHTML = yearHtml;
              } else {
                statsDiv.innerHTML =
                  yearHtml + htmls[0];
              }
            }
          }
        })
          .catch((error) => {
            console.log(error);
          });
      });

        const GDPAvg = {
          onStatisticField: "GDP_billions_", //using field name instead of field alias??? Don't know if I should. I have tried both ways now
          outStatisticFieldName: "GDP_Average",
          statisticType: "avg"
        };

        const statsFields = {
          GDP_Average: "GDP Average"
        };

      const statsDiv = document.getElementById("statsDiv");
      const infoDiv = document.getElementById("infoDiv");
      const infoDivExpand = new Expand({
        collapsedIconClass: "esri-icon-collapse",
        expandIconClass: "esri-icon-expand",
        expandTooltip: "Expand Oil and Gas Industry info",
        view: view,
        content: infoDiv,
        expanded: true
      });
      view.ui.add(infoDivExpand, "top-right");

    });
  </script>
</head>

<body>
  <div id="viewDiv"></div>
  <div id="timeSlider" class="sample-slider"></div>
  <div id="infoDiv" class="esri-widget">
    <div> <b>Growth of the Oil and Gas Industry</b></div><br />
    <div id="statsDiv" class="esri-widget"></div>
  </div>
</body>
</html>

 

cschooley
New Contributor III

Thank you. 

I need to take some more time reading through my code for the little case and spelling errors like that. I wrote and rewrote the code, trying to understand how it was working (versus just copy and pasting). I think I got tunnel-visioned.

And thanks for the note on closing the timeSlider. The opening and closing of blocks still confuse me a bit.

This worked for me, after making those changes. You are still getting a null value because, in my data, I don't have GDP data until about 1967. I'm still tracking down earlier data in data archives, non-digital ones. If you hit play on the timeSlider, around the 1960s the statistics start to show up. 

0 Kudos
KenBuja
MVP Esteemed Contributor

There's also a mistake on Line 269.

for (stat in statsFields) {
  if (attributes[name] && attributes[name] != null) {

but you're using "name" in the loop

0 Kudos
cschooley
New Contributor III
Thank you. I just caught that one.
0 Kudos
KenBuja
MVP Esteemed Contributor

And once I let it run to the end, I got the statistics showing up. You should format that statistic so you don't get "943.345425518852 billion dollars"

0 Kudos
cschooley
New Contributor III

I just added a .toFixed() into the code to help with that. 

Thank you!

const yearHtml =
      "<span>" +
      result.features[0].attributes["GDP_Average"].toFixed(1) +
      "</span> billion dollars was added to Utah's GDP by the Oil and Gas Industry between" +
      timeSlider.timeExtent.start.toLocaleDateString() +
      " and " +
      timeSlider.timeExtent.end.toLocaleDateString() +
      ".<br/>";
cschooley
New Contributor III

If you're still around or someone else feels like answering it. I am having an issue with the statistics side of this. I have the effect working correctly, but the statistics are being done on everything before the end of the current time extent, not for each year as I would like. There are notes in the code itself as well. 

require([
  "esri/Map",
  "esri/views/MapView",
  "esri/layers/FeatureLayer",
  "esri/widgets/Expand",
  "esri/widgets/Legend",
  "esri/widgets/TimeSlider"
], function (Map, MapView, FeatureLayer, Expand, Legend, TimeSlider) {

let OGLayerView;

  const layer = new FeatureLayer({
    portalItem: {
      id: "dd28d5595a2940929574e79522bb4245"
    }
  });

  // Create Map
const map = new Map({
  basemap: "topo",
  layers: [layer]
});

 // Set the map view
const view = new MapView({
  container: "viewDiv",
  map: map,
  center: [-110.16577367032687, 39.014084295303114],    // Centered on Green River for now
  zoom:6.999
});

// Create a collapsible legend
const legendExpand = new Expand({
  collapsedIconClass: "esri-icon-collapse",
  expandIconClass: "esri-icon-expand",
  expandTooltip: "Legend",
  view: view,
  content: new Legend({
    view: view
  }),
  expanded: false
});
view.ui.add(legendExpand, "top-left");

// Create events for the time slider
const events = [
  {name:`Great Recession`, date: 2008},
  {name: `Covid-19 Pandemic`, date: 2020}
];

// Create time slider with interval set to 5 years
const timeSlider = new TimeSlider({
    container: "timeSlider",
    playRate: 750,
    stops: {
      interval: {
        value: 1,
        unit: "years"
      }
    },

    // time slider time extent
    //fullTimeExtent: {
       // start: new Date(1900,0,1),
       // end: new Date(2022,0,1)
    //},

    // configure ticks for dates
    tickConfigs: [{
      mode: "position",
      values: [
        new Date(1970, 0, 1), new Date(1980, 0 , 1), new Date(1990, 0, 1), new Date(2000, 0, 1), new Date(2010, 0, 1), new Date(2020, 0, 1)
      ].map((date) => date.getTime()),
      labelsVisible: true,
      labelFormatFunction: (value) => {
        const date = new Date(value);
        return `${date.getUTCFullYear()}`;
      },
      tickCreatedFunction: (value, tickElement, labelElement) => {
        tickElement.classList.add("custom-ticks2");
        labelElement.classList.add("custom-labels2");
      }
    }, {
      mode: "position",
      values: events
        .map((event) => new Date(event.date, 0, 1))
        .map((date) => date.getTime()),
      labelsVisible: true,
      labelFormatFunction: (value) => {
        const event = events.find(
          (s) => new Date(s.date, 0, 1).getTime() === value
        );
        return event.name;
      },
      tickCreatedFunction: (value, tickElement, labelElement) => {
        tickElement.classList.add("custom-ticks");
        labelElement.classList.add("custom-labels");
      }
    }
  ]
});

// add time slider to view
view.ui.add(timeSlider);

// Creating view layer???
view.whenLayerView(layer).then((layerView) => {
  OGLayerView = layerView;

// Setting start date for time slider
  const start = new Date(1970, 0, 1);

 // Extent for time Slider 
  timeSlider.fullTimeExtent = {
    start: start,
    end: layer.timeInfo.fullTimeExtent.end
  };

// Show 5 year intervals

let end = new Date(start);

end.setDate(end.getDate() + 364); // the number here is in days (1825 = 5 years)

timeSlider.timeExtent = {start,end};

});

// watch timeslider timeExtent change
timeSlider.watch("timeExtent", () => {
  //oil wells that popped up before the end of the current time extent
OGLayerView.filter = {
  where: "OrigComplDate <=" + timeSlider.timeExtent.end.getTime(), ///PERHAPS THIS IS THE ISSUE WITH IT GOING BEFORE
}                                                                  /// I have removed it/adjusted it and continues so maybe not
// add grayscale effect to old wells (may or may not keep this)
  OGLayerView.effect = {
    filter: {
      timeExtent:timeSlider.timeExtent //Thought this filter would do the time extent as I wanted
    },
    excludedEffect: "grayscale(80%) opacity(20%)"
  };

// Run statistics for GDP within current time extent
const statQuery = OGLayerView.effect.filter.createQuery(); //Is this where I need to filter to extent? I don't think so
statQuery.outStatistics = [
GDPAvg,
employmentCount,
wellCounts
];

layer.queryFeatures(statQuery).then((result) => {
statsDiv.innerHTML = "";
if (result.error) {
  return result.error;
} else {
  if (result.features.length >= 1) {

    var yearOnly = {year:'numeric'}; //set to show year only in date strings
    const yearHtml =
      "Between " +
      "<span>" +
      timeSlider.timeExtent.start.toLocaleDateString("en-US", yearOnly) +
      "</span> and <span>" +
      timeSlider.timeExtent.end.toLocaleDateString("en-US", yearOnly) +
      "</span> the Oil and Gas Industry in Utah:<br />";
    
      var thousandsSep = {maximumFractionDigits:0}; //create thousands seperators  
    const oilHtml =
    "Had <span>" +
    result.features[0].attributes["Well_Counts"].toLocaleString("en-US", thousandsSep) +
    "</span> oil and gas wells" +
    ".<br/ >";

    const GDPHtml =
    "Added " +
    "<span>" +
    result.features[0].attributes["GDP_Average"].toFixed(1) +
    "</span> million dollars to Utah's Gross Domestic Product" +
    ".<br />";

    var thousandsSep = {maximumFractionDigits:0}; //create thousands seperators
    const employmentHtml =
    "Employed " +
    "<span>" +
    result.features[0].attributes["Employment_Count"].toLocaleString("en-US", thousandsSep) +
    "</span> employees. This includes full and part-time direct and supporting employees" +
    ".<br />";

    const referenceHtml =
    "<i><font size = '1'>" +
    "Estimates from the US Bureau of Economic Analysis and Utah's Division of Oil, Gas, and Mining" +
     "</font></i>";

    statsDiv.innerHTML =
      yearHtml + "<ul> <li>" + oilHtml + "</li> <li>"  + GDPHtml + "</li> <li>" + employmentHtml + "</li> </ul>" + referenceHtml;
}
}
})
.catch((error) => {
console.log(error);
});
});

const GDPAvg = {
  onStatisticField: "GDP_billions_",
  outStatisticFieldName: "GDP_Average",
  statisticType: "avg"
};

const employmentCount = {
  onStatisticField: "Employed",
  outStatisticFieldName: "Employment_Count",
  statisticType: "avg"
};

const wellCounts = {
  onStatisticField: "API",
  outStatisticFieldName: "Well_Counts",
  statisticType: "count"
};

const statsDiv = document.getElementById("statsDiv");
      const infoDiv = document.getElementById("infoDiv");
      const infoDivExpand = new Expand({
        collapsedIconClass: "esri-icon-collapse",
        expandIconClass: "esri-icon-expand",
        expandTooltip: "Expand Oil and Gas Industry info",
        view: view,
        content: infoDiv,
        expanded: true
      });
      view.ui.add(infoDivExpand, "top-right");

});

Any help is appreciated. Thanks!

0 Kudos