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:
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.
Solved! Go to Solution.
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>
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>
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.
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
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"
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/>";
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!